Symfony3 et Bananas

Kevin Verschaeve

Quoi qu'on va faire ?

  • Découvir ce qu'est un Framework
  • Voir ce qu'est le MVC
  • Voir Composer
  • Installer Symfony (youhou!)
  • Faire notre premier projet
  • Voir les bases de Symfony
  • Boire une bière ?

C'est quoi un framework ?

« Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony. »

  1. Une structure

  2. Des composants

  3. Une manière de coder

  • Routing
  • Sécurité
  • Bundles
  • ...

Une structure

Des composants

35 composants

Dont:

  • Console
  • Debug
  • Form
  • HttpKernel
  • Routing
  • Security
  • VarDumper
  • ...

Une manière de coder

  • Design patterns
  • MVC
  • Separation of concerns
  • Coding Style (PSR-2)

Composer

Gestionnaire de dépendances

"J'ai besoin de cette lib, qui elle même a besoin de telle lib, qui elle même a besoin de telle lib, qui ... "

$ composer install

C'est tout !

Enfin presque

On déclare les librairies dont on a besoin dans le fichier composer.json à la racine du projet

{
    "require": {
        "pop/lalib": "~1.0"
    }
}

La commande composer install, va télécharger la librairie "pop/lalib" en version 1.0 minimum et toutes les dépendances de celle ci, puis l'installer dans le projet

Là c'est tout

Mais y'a encore pleins de trucs cool à faire avec composer

C'est maintenant qu'on bosse !

Installer Symfony

Installer l'installateur ....

$ sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony

$ sudo chmod a+x /usr/local/bin/symfony

Pour les gens biens (sous Linux) et les riches (Mac OS X)

=> nouvelle commande : 

$ symfony

Pour ceux qui cherchent les problèmes (sous Windows)

c:\> php -r "readfile('https://symfony.com/installer');" > symfony

c:\> move symfony c:\projects

=> nouvelle commande :

c:\projects\> php symfony

Créer un projet

$ symfony new pop 3.0

Créé un projet Symfony, avec la version 3.0 dans le dossier "pop"

$ cd pop
$ php bin/console server:run

Va dans le dossier du projet et lance un serveur

Dans le navigateur, aller sur http://localhost:8000

Ca marche !

La structure du projet

app/
 -> fichiers de configurations, vues, ...

src/
 -> le code PHP de l'application, on passe la majeure partie du temps dans ce dossier

bin/
 -> fichiers exécutables, comme bin/console

tests/
 -> test automatisés

var/
 -> fichiers créés automatiquement (logs, cache)

vendor/
 -> librairies/bundles externes téléchargés par composer

web/
 -> root du projet, ici va tout ce qui est publique, css, js, images...

Configuration

Configurer une base de données

Activer la traduction et régler la langue par défaut

# app/config/config.yml

# sous la clé parameters, régler la clé locale sur la langue souhaitée (ici fr)
parameters:
     locale: fr

# décommenter la ligne "translator" sous la clé "framework"
framework:
    translator:      { fallbacks: ["%locale%"] }
# app/config/parameters.yml

parameters:
    database_host: 127.0.0.1
    database_port: null
    database_name: pop
    database_user: root
    database_password: pass

Les Bundles !!

J'ai pas envie de faire ce truc, y'a pas quelqu'un qui l'aurais déjà fait ?

Surement !

 

Et il en a surement aussi fait un bundle

$ composer require lexik/maintenance-bundle

Installer un bundle

<?php

// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        // ...
        new Lexik\Bundle\MaintenanceBundle\LexikMaintenanceBundle(),
    );
    // ...
}

Enregistrer le bundle pour Symfony dans app/AppKernel.php

Configurer le bundle


lexik_maintenance:
    driver:
        class: '\Lexik\Bundle\MaintenanceBundle\Drivers\ShmDriver'

Dans app/config/config.yml

Tester

# passer le site en mode maintenance
$ bin/console lexik:maintenance:lock

# passer le site en mode normal
$ bin/console lexik:maintenance:unlock

Controller et actions

  • Un controller contient un ensemble d'Actions

  • La logique métier est écrite dans les controllers

  • Le nom des Actions se termine par 'Action', ex: showAction()

  • Les actions sont atteintes via une route et retournent obligatoirement un objet de type Response

  • Une Response est ce qui est affiché à l'utilisateur

  • ​Une action doit être la plus courte possible (moins de 10 lignes)

La plus part du temps, une Response est une vue (du code HTML, ici compilé avec Twig)

Mais ca peut aussi être une redirection, un  stream, du JSON, du Binaire ou du custom

Le Routing

Une route :

  • Fait la liaison entre une URL et une Action

  • À un nom unique

  • À un ordre déterminant

  • Peut prendre des paramètres (entre {}) et des valeurs par défaut

  • Peut avoir des restrictions

  • Peut être sécurisée

Twig

"Moteur de templates"

Permet de rendre du PHP dans du HTML de façon plus triviale, propre, sécurisée et facile à comprendre pour un intégrateur

avec PHP

<h1>Hello <?php echo $name; ?></h1>

avec Twig

<h1>Hello {{ name }}</h1>

Syntaxe de base

avec PHP

<ul>
    <?php foreach ($articles as $article): ?>
        <li><?php echo $article->getName(); ?></li>
    <?php endforeach; ?>
</ul>

avec Twig

<ul>
    {% for article in articles if article.name is not empty %}
        <li>{{ article.name }}</li>
    {% else %}
        Ya pas d articles
    {% endfor %}
</ul>

Foreach

avec PHP

<h1>Hello <?php echo strtoupper($name); ?></h1>
<p>Liste: <?php echo implode(', ', $liste); ?></p>

avec Twig

<h1>Hello {{ name|upper }}</h1>
<p>Liste: {{ liste|join }}</p>

Les filtres

Héritage de templates

{# index.html.twig #}
{% extends '::base.html.twig' %}

{% block title %}Index{% endblock %}

{% block body %}
    Hello {{ name }} !
{% endblock %}
{# base.html.twig #}
<html>
<head>
    <title>{% block title %}Fichier de base{% endblock %}</title>
</head>
<body>
    {% block body %}
        Le contenu par défaut
    {% endblock %}
</body>
</html>

Plein d'autres trucs

  • Includes
  • Macros
  • Auto escaping
  • Comparaisons
  • Extensions
  • ...

Base de données

Créer la base de données

$ php bin/console doctrine:database:create

Créer une entité

$ php bin/console doctrine:generate:entity
# AppBundle:Article
# annotation
# title, string 255, true, false
# author, string 255, false, false

$ php bin/console doctrine:schema:create

Requêter

Doctrine utilise des Manager pour gérer nos entités

 

Ces managers fournissent des Repository qui eux fournissent des méthodes clés en main pour requeter la base de données

# dans une action de controller

public function monAction()
{
    $articleRepository = $this->getDoctrine()->getRepository(Article::class);

    $articles = $articleRepository->findAll();

    // ce qu'on veut, avec la liste d'articles
}
$repository->findAll(); // retourne toutes les entités

$repository->find(123); // retourne l'entité avec l'id 123

$repository->findBy(['author' => 'Kevin']); // retourne la ou les entités dont l'auteur == 'Kevin'

$repository->findOneBy(...); // identique à la précédente, excepté qu'ici on ne retourne que le premier résultat 

$repository->findOneByName('Kevin'); // identique à la précédente, en passant par des méthodes magiques

Quelques méthodes fournies par Doctrine

Que faire si on souhaite des requêtes plus complexes ?

Un repository custom

<?php

namespace AppBundle\Repository;

class ArticleRepository extends EntityRepository 
{
    public function findFirstArticlesOfAuthor($author, $nbArticles)
    {
        return $this->createQueryBuilder('a')
            ->where('a.author = :author')
            ->setParameter('author', $author)
            ->setMaxResults($nbArticles)
            ->getQuery()
            ->getResult()
        ;
    }
}

Le repository

Lier le repository à l'entité

/**
* @ORM\Entity(repositoryClass="AppBundle\Repository\ArticleRepository")
*/
class Article 
{
    // ...
}
public function monAction()
{
    // ...
    $myFirst5Articles = $this
            ->getDoctrine()
            ->getRepository(Article::class)
            ->findFirstArticlesOfAuthor('Kevin', 5)
    ;
    // ...
}

Pour requêter, ex dans un controller

Les formulaires

Créer un formulaire

Dans le dossier AppBundle\Form, créer un fichier nommé ArticleType.php

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class ArticleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class)
            ->add('author', TextType::class)
            ->add('submit', SubmitType::class)
        ;
    }
}

Gérer le formulaire

La validation

Le titre de mon article est obligatoire et ne doit pas dépasser 60 caractères

Le nom de l'auteur est aussi obligatoire et ne doit pas contenir de chiffre

Comment valide-t-on ces règles ?

=> Avec le validator !

<?php

namespace AppBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Article 
{
    // ...

    /**
    * @ORM\Column(type="string", length="60", nullable=false)
    * @Assert\NotBlank()
    * @Assert\Length(max=60, message="Le titre de l'article ne doit pas contenir plus de 60 caractères")
    */
    private $title;

    /**
    * @ORM\Column(type="string", nullable=false)
    * @Assert\NotBlank()
    * @Assert\Regexp("[a-zA-Z]", message="Le nom de l'auteur ne doit pas contenir de caractères spéciaux")
    */
    private $author;

    // getters and setters
    // ...
}

Dans le controller

<?php

namespace AppBundle\Controller;

class ArticleController extends Controller 
{
    /**
    * @Route("/article/new", name="article_new")
    */
    public function newAction(Request $request)
    {
        $form = $this->createForm(ArticleType::class, new Article());

        if ($form->handleRequest($request)->isValid()) {
            $article = $form->getData();
            $manager = $this->getDoctrine()->getManagerForClass(Article::class);
            $manager->persist($article);
            $manager->flush();

            return new RedirectResponse($this->generateUrl('article_show', [
                'id' => $article->getId(),
            ]));
        }

        return $this->render(':article:new.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

Exercice

  1. Créer un nouveau Controller: ArticleController
  2. Ajouter 2 actions:
    1. newAction
    2. showAction
  3. Créer les templates associés aux actions
  4. Dans newAction, gérer le formulaire:
    1. s'il a été envoyé et valide, créer un nouvel article
    2. sinon, réafficher le formulaire
  5. Ajouter des contraintes, dans la classe Article

Les services

Un bout de code réutilisable partout dans l'application

Créer le service

services:
    app.my_project.calculator:
        class: 'AppBundle\Services\Calculator'

Dans app/config/services.yml

<?php

namespace AppBundle\Services;

class Calculator
{
    public function add($a, $b)
    {
        return $a + $b;
    }
}

Dans src/AppBundle/Services/Calculator.php

Utiliser le service

Dans une action de controller

<?php 
// ...

public function helloAction($name) 
{
    // ...
    $calculator = $this->container->get('app.my_project.calculator'); 
    // ou, $this->get('...');

    $result = $calculator->add(5, 5);

    return $this->render(':default:hello.html.twig', [
        // ...
        'result' => $result
    ]);
}

Puis afficher le résultat dans la vue

Pleins de services dans Symfony

  • mailer
  • doctrine
  • twig
  • translator
  • logger
  • router
  • session
  • ...

La traduction

Activer la traduction

# app/config/config.yml

# décommenter la ligne
translator:      { fallbacks: ["%locale%"] } 

Utiliser le service de traduction

# En PHP
$this->get('translator')->trans('translation_key');


# En Twig
{{ 'tranlsation_key'|trans }}

Écrire la traduction

# app/Resources/translation/messages.fr.yml
translation_key: Le texte affiché en français

Le nom des fichiers de traduction doivent se nommer de cette manière: <domain>.<locale>.<loader>

 

ex: messages.fr.yml

Convention de nommage

Les domaines

  • Le service translator va chercher les traductions correspondantes a des clés dans des "domaines"
  • Un domaine est une manière de regrouper les traductions suivant le type
  • Le domaine par défaut est: messages
  • Il peut être changé en ce que l'on veut

Locale et loader

Locale: La langue dans laquelle on écrit la traduction

Loader: le type de format qu'on souhaite utiliser pour les traductions

Les différents loader supportés par Symfony sont:

  • xml (xliff, format recommandé)
  • yml
  • php

Chacun des formats a ses avantages et inconvénients

Exercice

  1. Traduire les labels de formulaire
  2. Traduire les messages d'erreurs de validation

Bière ?

Symfony and Bananas v2

By keversc

Symfony and Bananas v2

  • 1,165