Comment Symfony 4 vous rend un meilleur développeur

Nous avons abordé de nombreux sujets , et vous êtes sur la bonne voie pour mieux comprendre Symfony 4.

Pour finir, nous allons regarder l'une des parties les plus amusantes et potentiellement confuses de Symfony: les services.

Tout au long de cette courte série, nous nous sommes fréquemment référés aux meilleures pratiques de Symfony .

Avant d'aller plus loin: Ces meilleures pratiques sont des lignes directrices, pas des règles. Les règles peuvent être courbées ou cassées. Mais adhérer à eux rendra la vie de vos coéquipiers et de vos coéquipiers plus facile, la plupart du temps.

Selon moi, les meilleures pratiques devraient être traitées exactement de la même manière.

symfony-4-controller-meilleures-pratiques-abstractcontroller

Étendre AbstractController

Nous allons commencer avec un changement que nous avons fait tout au long de la deuxième vidéo.

Nous avons généré notre WelcomeController , puis immédiatement apporté les deux modifications suivantes:

<?php

namespace App\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
-use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
-use Symfony\Component\HttpFoundation\Response;

-class WelcomeController extends Controller
+class WelcomeController extends AbstractController
{
    /**
     * @Route("/welcome", name="welcome")
     */
    public function index()
    {
        // replace this line with your own code!
        return $this->render('@Maker/demoPage.html.twig',
          'path' => str_replace($this->getParameter('kernel.project_dir').'/', '', __FILE__)
        ]);
    }
}

J'ai mentionné que nous supprimons l' use ... Response car elle est inutilisée dans le code généré.

Nous n'avons pas expliqué pourquoi nous avons modifié le AbstractController .

Laisse

Pourquoi AbstractController

Symfony peut être une bête mystérieuse.

Nous, en tant que consommateurs du framework, bénéficions d'un accès immédiat à la sagesse et aux avantages du code créé, et sommes évalués par des pairs parmi les développeurs les plus intelligents qui écrivent du code PHP aujourd'hui.

Pour cette raison, et bien d'autres, je suis sûr que vous vous joindrez à moi pour dire un énorme MERCI à tous ceux qui sont impliqués dans la communauté Symfony.

En pensant à cela, il est implicite que nous faisons confiance à ces développeurs pour faire des choses intelligentes en notre nom.

Il ne s'ensuit pas nécessairement que nous comprenons pourquoi ils font ce qu'ils font. Cela ne signifie pas non plus que tout ce qu'ils font nous profitera directement.

Comme une note de côté: Cela fait partie de l'inconvénient d'utiliser un cadre. Nous recevons tout ce dont nous avons besoin, et potentiellement beaucoup d'autres choses que nous n'avons pas.

C'est la raison pour laquelle Symfony 4 est à l'origine uniquement fourni avec symfony/skeleton .

Nous devrions seulement installer les bits que nous voulons.

Mais cela rend la vie plus difficile pour un débutant, ou une personne moins impliquée dans les subtilités du cadre.

Certains d'entre nous veulent juste utiliser un bon ensemble de valeurs par défaut, et compromettre que peut-être nous obtenons un peu plus de ballonnement mais sommes heureux de payer ce petit prix.

Ok, mais qu'est-ce que ça a à voir avec AbstractController , spécifiquement?

La nouvelle approche

Une grande poussée dans Symfony 3.x était d'obtenir l'autowiring et l'autoconfiguration ajouté. Que vous ayez une opinion à ce sujet ou non, c'est ainsi que les choses se sont passées.

Les contrôleurs sont maintenant des services par défaut.

Ils ont une configuration spéciale:

# config/services.yaml

parameters:
    locale: 'en'

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        public: false       # Allows optimizing the container by removing unused services; this also means
                            # fetching services directly from the container via $container->get() won't work.
                            # The best practice is to be explicit about your dependencies anyway.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Tests}'

    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones

Cela apporte probablement beaucoup plus d'avantages que directement sur moi, mais permettez-moi de couvrir la partie que j'apprécie le plus:

Un chemin défini.

Comme cela a déjà été couvert dans cette courte série, il y a eu plusieurs fois où il y a plusieurs façons d'atteindre un résultat.

Parfois, c'est bon. Une flexibilité accrue et la facilité de pouvoir rouler les nôtres constituent un argument de vente important.

D'autres fois nous rendons notre code accidentellement pire, mais toujours "dans les règles".

Un exemple de ceci serait en utilisant les deux paramètres, et les services dans une application typique de Symfony 3:

<?php

namespace AppBundle\Controller;

use AppBundle\Service\SomeService;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(SomeService $someService)
    {
        $someParameter = $this->container->getParameter('app.my.parameter');

        $someService->doSomethingWith($someParameter);

Encore une fois, ne pas dire que c'est un bon ou un mauvais code. C'est un code valide.

Nous injectons une chose mais en demandons une autre.

La plupart du temps, ma vie de mainteneur de code a été facilitée quand une fonction fait une chose.

Nous avons ici l'option de dire à notre fonction / méthode indexAction sur nos dépendants.

Ou nous pouvons le faire demander pour eux.

Ma suggestion pour vous ici est: Dites, ne demandez pas .

Choisissez-en un, et respectez-le.

Symfony suggère une injection (c'est Tell btw).

Vous pouvez injecter des paramètres. Mais il y a un peu de travail supplémentaire requis.

Exemple

Ajoutons notre propre paramètre personnalisé:

# config/services.yaml

parameters:
    locale: 'en'
+   app.mySweetParam: 'nerding out on Symfony is fun'

Avant de pouvoir passer ce paramètre dans un service, nous devons créer un nouveau service.

Comme nous envoyons actuellement des e-mails à partir de notre méthode de contrôleur, il semble maintenant aussi bon que n'importe quel pour extraire ce processus à un service. Notre service Emailer .

mkdir src/Mailer
touch src/Mailer/Emailer.php

L' Emailer est juste un fichier vide à ce stade. Il n'y a pas de générateurs intégrés pour make services pour nous. Chaque service est différent, car le but de chaque application est différent.

Le plan est notre Emailer va create Swift_Message pour nous, qui pour commencer, nous allons également envoyer via l' Emailer .

Maintenant, ouvrez src/Mailer/Emailer.php et ajoutez ce qui suit:

<?php

namespace App\Mailer;

class Emailer
{
    public function create()
    {

    }
}

Notre méthode Emailer::create doit faire ce que nous avons besoin de faire, et nous renvoyer une instance de \Swift_Message . C'est l'essentiel de notre «exigence métier» pour ce processus.

Utilisons une fonctionnalité de PHP 7, et déclarons un type de retour sur notre signature de méthode:

<?php

namespace App\Mailer;

class Emailer
{
-   public function create()
+   public function create() : \Swift_Message

Notez que vous n'avez pas besoin d'une instruction d' use pour \Swift_Message car elle est quelque peu inhabituelle dans l'espace de noms racine.

Avant d'implémenter d'autres fonctionnalités, injectons notre nouveau paramètre.

Nous le ferons en déclarant une fonction __construct pour notre classe Emailer :

<?php

namespace App\Mailer;

class Emailer
{
+   public function __construct()
+   {
+   }
+
    public function create() : \Swift_Message
    {

    }

Et nous savons que notre parameter s'appelle mySweetParam , alors comment pouvons-nous injecter cela?

<?php

namespace App\Mailer;

class Emailer
{
-   public function __construct()
+   public function __construct(string $mySweetParam)

Je l'ai déjà dit dans cette série, Symfony, aussi bon soit-il, ce n'est pas un lecteur d'esprit.

Oui, nous avons nommé notre variable $mySweetParam . Oui, nous avons le type l'a indiqué comme une string .

Non, ce n'est pas - assez - suffisant pour que Symfony connecte les points entre le paramètre app.mySweetParam nous avons défini dans config/services.yaml , et ce nom de variable.

Nous devons lui donner de l'aide.

Nous allons ajouter une entrée personnalisée sous la clé de services dans config/services.yaml :

services:
    # other stuff

    App\Mailer\Emailer:
        arguments:
            $mySweetParam: "%app.mySweetParam%"

C'est suffisant pour dire à Symfony ce que nous entendons par $mySweetParam . Votre nom de variable n'est pas important. Mais si vous le modifiez dans les arguments de votre définition de service, veillez à utiliser le même nom dans la Emailer::__construct .

Cependant, nous n'utilisons pas ce service partout.

Et Symfony ne renouvelle pas tous les services configurés dans notre projet pour chaque requête entrante. Ce serait mauvais.

Nous pouvons tromper Symfony dans la construction de l' Emailer .

Tout ce que nous devons faire est de l'injecter dans une méthode appelée, et nous pouvons tester cela.

Utilisons src/Controller/WelcomeController.php .

Je vais injecter Emailer , mais ne l'utilise pas réellement dans la méthode du contrôleur:

<?php

namespace App\Controller;

+use App\Mailer\Emailer;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class WelcomeController extends AbstractController
{
    /**
     * @Route("/", name="welcome")
     */
-   public function index()
+   public function index(Emailer $emailer)
    {

Nous avons besoin d'un moyen de valider visuellement ce qui devrait se passer ici.

Il y a plusieurs façons.

Nous pourrions dump la variable directement dans le corps de la méthode __construct .

Ou nous pourrions injecter l' Psr\Log\LoggerInterface (qui est Monolog btw, dans Symfony).

Faisons les deux, car pourquoi pas?

<?php

namespace App\Mailer;

+use Psr\Log\LoggerInterface;

class Emailer
{
-    public function __construct(string $mySweetParam)
+   public function __construct(string $mySweetParam, LoggerInterface $logger)
    {
+       $logger->alert('BOOM!');
+       $logger->debug($mySweetParam);
+
+       dump($mySweetParam);
    }

Maintenant, à partir de votre terminal / shell:

cd {project_root}
tail -f var/log/dev.log

Puis naviguez jusqu'à:

http://127.0.0.1:8000 - aka la "page d'accueil", et nous voyons deux choses:

  • Sur la barre d'outils de débogage Web, nous voyons l'icône en forme de réticule, qui survole montre que «jouer sur Symfony est amusant» (aka la sortie de dump )
  • Sur la coque, vous devriez voir:
tail -f var/log/dev.log

[2018-01-19 12:40:00] app.ALERT: BOOM [] []
[2018-01-19 12:40:00] app.DEBUG: nerding out on Symfony is fun [] []

Notez qu'à aucun moment nous n'avons écrit de configuration de service pour Emailer . La configuration de service par défaut de Symfony 4 assure automatiquement le câblage de ces services avec les dépendances dont ils peuvent avoir besoin. Nous n'avons besoin d'intervenir que si nous avons besoin d'aide, ou nos besoins sont assez spécifiques.

À venir plein cercle

"Tout cela est très intéressant, Chris", pourriez-vous dire, "mais qu'est-ce que tout cela a à voir avec AbstractController ?"

Revenons à notre exemple de Symfony 3.x:

<?php

namespace AppBundle\Controller;

use AppBundle\Service\SomeService;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(SomeService $someService)
    {
        $someParameter = $this->container->getParameter('app.my.parameter');

        $someService->doSomethingWith($someParameter);

Nous savons maintenant que nous n'avons pas besoin de demander un paramètre au container .

Nous pouvons l'injecter.

Si nous n'avons pas besoin de demander des choses à partir du container , nous n'avons plus besoin d'être ContainerAware .

La classe de contrôleur générée extends Controller , qui use ContainerAwareTrait; .

Nous n'avons pas besoin de ce trait .

Nous injectons tout.

Par conséquent, nous pouvons supprimer cette dépendance.

AbstractController nous donne essentiellement la même fonctionnalité, mais en supprimant la possibilité pour nous deux de raconter et de demander.

C'est plus restrictif.

Mais cela a votre intérêt à l'esprit. Même si tu ne le savais pas.

symfony-4-abstractcontroller-poll

De toute façon, même si vous n'avez rien lu de tout cela, ou que vous l'avez lu et que vous avez besoin de confirmation, ne prenez pas ma parole, prenez Nicholas.

Cela dit, parce que nous continuons à utiliser le ControllerTrait , si nous utilisons l'une des méthodes de commodité, nous utilisons encore le conteneur comme précédemment, juste indirectement. Vous pouvez retirer votre confiance à même AbstractController et définir explicitement des dépendances spécifiques, si vous le souhaitez.