Page d'accueil Mes articles

Évolutions des bridges sur Symfony 6.2

Les bridges permettent de faire des ponts avec des projets open-source comme Doctrine ou Twig par exemple.

Publié en September 2022.

Introduction

À chaque fois que vous utilisez sur Twig la méthode path pour générer le chemin d'une route, et bien ce n'est pas twig/twig qui vous fourni cette méthode mais symfony/twig-bridge. Sur Symfony, nous avons actuellement des Bridge pour Doctrine, Monolog, PhpUnit, ProxyManager et Twig. Voyons voir ce que cette nouvelle version de Symfony nous reserve à leur sujet 😁

Doctrine Bridge

Le EntityValueResolver de Doctrine Bridge va venir remplacer le célèbre ParamConverter du SensioFrameworkExtraBundle. Cela fait suite à la décision prise en décembre 2021 d'abandonner le SensioFrameworkExtraBundle .

Depuis la version 5.3 du framework, nous avons la possibilité d'utiliser l'attribut #[CurrentUser] pour injecter l'utilisateur connecté en paramètre d'une méthode de contrôleur. Le problème est que si User est une entité Doctrine, et bien le ParamConverter générait la bug suivant

    #[Route('/', name: 'app_home')]
    public function index(#[CurrentUser] User $user): Response
    {
        dump($user);
        return $this->render('home/index.html.twig');
    }
Unable to guess how to get a Doctrine instance from the request information for parameter "user".

Maintenant sur la 6.2, ce problème est résolu. Vous devez par contre désactiver le ParamConverter et définir en tant que service EntityValueResolver.

sensio_framework_extra:
    request:
        converters: false
services:
    Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver: ~

Le bug est résolu

Son utilisation est la même que le ParamConverter

    #[Route('/quote/{id}', name: 'app_show_quote')]
    public function showQuote(Quote $quote): Response
    {
        dump($quote);
        return $this->render('home/show_quote.html.twig', compact('quote'));
    }

Vous pouvez comme avec le ParamConverter venir définir une expression en particulier. Vous devrez néanmoins définir ExpressionLanguage en tant que service.

services:
    Symfony\Component\ExpressionLanguage\ExpressionLanguage: ~
public function showQuote(
        #[MapEntity(expr: 'repository.findOneById(id)')]
        Quote $quote
    ): Response { }

Une étape de plus vers la disparition du SensioFrameworkExtraBundle 😁

Autre évolution sur le DoctrineBridge, l'ajout d'une constante pour nommer les types Ulid et Uuid afin d'être cohérent avec la documentation de Doctrine qui maintenant propose de typer les colonnes à l'aide de constantes et non de simple chaîne de caractères. Vous pouvez maintenant utiliser UuidType::NAME et UlidType::NAME

use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;

#[ORM\Entity(repositoryClass: QuoteRepository::class)]
class Quote
{
    #[ORM\Id]
    #[ORM\Column(type: UuidType::NAME, unique: true)]
    #[ORM\GeneratedValue(strategy: 'CUSTOM')]
    #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
    private ?Uuid $id = null;

PHPUnit Bridge

Peut être avez-vous déjà utilisé lors de vos tests unitaires ClockMock. Il s'agit d'une classe qui va vous permettre de tester vos classes et méthodes utilisant les fonctions PHP liées à l'horloge : time, microtime, sleep, usleep, date et gmdate.

Pour l'exemple, je me suis créé un service avec une méthode inutile needToSleep10Seconds

namespace App\Timer;

class Timer
{
    public function needToSleep10Seconds(): array
    {
        $start = time();
        sleep(10);
        $end = time();
        return [$end - $start, date('d/m/Y H:i:s')];
    }
}

Et j'ai ajouté le test associé à cette fonctionnalité


namespace App\Tests\Timer;

use App\Timer\Timer;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PhpUnit\ClockMock;

class TimerTest extends TestCase
{
    public function testWeAreAllowedToMockTheClock(): void
    {
        ClockMock::register(Timer::class);
        ClockMock::withClockMock(mktime(17, 0, 0, 9, 20, 2022));

        $timer = new Timer();
        list($elapsedTime, $now) = $timer->needToSleep10Seconds();

        $this->assertEquals(10, $elapsedTime);
        $this->assertEquals('20/09/2022 17:00:10', $now);

        ClockMock::withClockMock(false);
    }
}

PHPUnit est très rapide

L'horloge système réglée dans mon test au 20 septembre 2022 à 17h a bien pris 10 secondes lors de l'exécution de mon test mais à aucun moment la fonction sleep n'est venu ralentir l'exécution de PHPUnit.

Dans la liste des fonctions citées précédemment, hrtime n'était pas présente jusqu'à maintenant. Elle rejoint ClockMock sur cette nouvelle version. À la différence de microtime, hrtime est une fonction monotonique. Dans notre cas elle sera toujours croissante même en cas de modification de l'horloge système. Parfait pour mesurer des temps d'exécution par exemple. 😁

Twig Bridge

Il est désormais possible de récupérer la valeur du label et de l'aide

Si vous avez déjà fait de la surcharge de templates avec Twig, vous vous êtes peut être déjà pris la tête pour surcharger un form_label ou un form_help. Dans le cas le plus basique, le label correspond à notre champ "humanisé". Mais dans des cas plus avancés, il peut être défini dans notre FormType en text ou en html grâce à l'option label_html. Votre label peut également être une clé de traduction avec même éventuellement des paramètres de traduction. Bref, cela rendait le code assez complexe lors de la création de templates avec Twig.

Pour vous simplifier la vie, vous pouvez désormais utiliser les block form_label_content and form_help_content lors de vos surcharges.

<div class="custom-label">
    {%- if label is not same as(false) -%}
        {{- block('form_label_content') -}}
    {%- endif -%}
</div>

#[Template] est maintenant présent sur TwigBridge !

On continue de désosser le SensioFrameworkExtraBundle avec l'ajout de l'attribut #[Template] sur TwigBride. La différence étant que le chemin vers votre template est maintenant requis. L'idée étant de rendre le code du TwigBridge plus efficace, et pour nous autres utilisateurs, un code plus simple à comprendre.

    #[Route('/{id}', name: 'app_quote_show', methods: ['GET'])]
    #[Template('quote/show.html.twig', vars: ['quote'])]
    public function show(Quote $quote) { }

Il est en effet plus clair de spécifier le nom du template plutôt que de le deviner en fonction du nom de la méthode.

Là où c'est très fort par contre, c'est pour la gestion des formulaires. Le passage de FormInterface vers FormView fonctionne parfaitement et si la validation de votre formulaire n'est pas correct, cela vous retourne bien une 422.

    #[Route('/{id}/edit', name: 'app_quote_edit', methods: ['GET', 'POST'])]
    #[Template('quote/edit.html.twig', vars: ['form', 'quote'])]
    public function edit(Request $request, Quote $quote, QuoteRepository $quoteRepository)
    {
        $form = $this->createForm(QuoteType::class, $quote);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $quoteRepository->save($quote, true);

            return $this->redirectToRoute('app_quote_index', [], Response::HTTP_SEE_OTHER);
        }
    }

Erreur 422

Les Toogle Buttons de Bootstrap 5 disponibles

Si vous utilisez la classe css btn-check sur votre CheckboxType, votre case à cocher est transformée en bouton de type toogle.

$builder
            ->add('price')
            ->add('agree', CheckboxType::class, [
                'mapped' => false,
                'label' => 'Agree to Terms and Conditions',
                'label_attr' => [
                    'class' => 'btn btn-light'
                ],
                'attr' => [
                    'class' => 'btn-check'
                ]
            ])
        ;

Button Toogle de Bootstrap 5 disponible sur Symfony

AppVariable vous permet désormais d'accéder directement à la route courante

Jusqu'à maintenant, lorsque l'on voulait récupérer le nom de la route courante pour, par exemple, ajouter une classe css sur un lien, nous devions passer par la Request.

<a
   href="{{ path('app_quote_index') }}"
   class="nav-link {% if app.request.attributes.get('_route') == 'app_quote_index' %}active{% endif %}"
   >Quotes</a>

Désormais, sur Symfony 6.2, c'est plus simple avec l'arrivé de current_route et current_route_parameters.

<a
   href="{{ path('app_quote_index') }}"
   class="nav-link {% if app.current_route == 'app_quote_index' %}active{% endif %}"
   >Quotes</a>

Pour conclure

Nous avons fait un rapide tour des évolutions présentes sur les bridges sur Symfony 6.2. Le prochain sujet à traiter est celui de l'évolution des Bundles sur cette nouvelle version. Vos remarques et questions sont les bienvenues 😁

Cet article vous a été utile ? Faites le connaître sur les réseaux sociaux Twitter twitter LinkedIn linkedin

Un commentaire ?

codeur
Ou connectez-vous avec GitHub