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: ~
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);
}
}
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);
}
}
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'
]
])
;
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 😁