· 9 min read

4 Nouveautés de Symfony 7.2

Symfony 7.2 va bientôt sortir, découvrons ensemble quatre nouveautés qui vont vous faciliter la vie avec cette nouvelle version.

Symfony 7.2 va bientôt sortir, découvrons ensemble quatre nouveautés qui vont vous faciliter la vie avec cette nouvelle version.

Introduction

L’un des avantages de Symfony est sa roadmap bien définie. Cela permet de savoir à l’avance quand doit sortir une nouvelle version et quelles sont les fonctionnalités qui seront ajoutées.

J’écris cet article le 4 novembre 2024 quelques jours ou semaines avant la sortie de Symfony 7.2.0, mais nous connaissons déjà quelques-unes des nouveautés qui seront ajoutées.

Pour ce tuto je me beserais sur la version Symfony 7.2.0-BETA1.

Un projet pour tester les nouveautés

Pour tester les nouveautés de Symfony 7.2, créez un nouveau projet Symfony avec la commande suivante :

symfony new test-sf72 --version=next --webapp
cd test-sf72
symfony serve -d
docker-compose up -d

1. String Component Improvements

Avec Symfony 7.2, le composant String gagne en puissance et en polyvalence grâce à des fonctionnalités qui simplifient la manipulation des chaînes de caractères. Si vous travaillez sur des applications où les transformations de texte sont importantes, ces nouveautés vont vraiment vous intéresser !

🆕 Nouvelle méthode kebab()

Le composant String ajoute désormais la méthode kebab() pour transformer une chaîne de caractères en “kebab-case”, c’est-à-dire en chaîne composée de mots en minuscules séparés par des tirets. Ce format est souvent utilisé dans les URLs et les identifiants de CSS.

Testons cette nouvelle méthode, crées un controller avec la commande suivante :

symfony console make:controller KebabController
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use function Symfony\Component\String\u;

class KebabController extends AbstractController
{
    #[Route('/kebab', name: 'app_kebab')]
    public function index(): Response
    {
        $string = 'Kebab Démo Symfony 7.2';
        $kebab = u($string)->kebab();

        dd($kebab->toString());
    }
}

🆕 Modes de troncature plus flexibles

Tronquer des chaînes de caractères est une opération fréquente, que ce soit pour les aperçus d’articles, les notifications, ou les résumés. Symfony 7.2 introduit des modes de troncature avancés qui permettent de mieux contrôler la coupe des chaînes :

  • Couper par caractère : tronquer une chaîne en nombre exact de caractères.
  • Couper par mot entier (avant/après) : s’assurer qu’une chaîne est coupée uniquement à la fin d’un mot, ou conserver un mot entier si la coupe tombe en plein milieu.

Testons ces nouvelles fonctionnalités dans un nouveau controller :

symfony console make:controller TruncateController
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\String\TruncateMode;

use function Symfony\Component\String\u;

class TruncateController extends AbstractController
{
    #[Route('/truncate', name: 'app_truncate')]
    public function index(): Response
    {
        $string = 'Truncate Démo Symfony 7.2';

        // On tronque la chaîne de caractères à 10 caractères
        $truncate = u($string)->truncate(10);

        // On tronque la chaîne de caractères à 10 caractères sans couper un mot
        $truncateAfter = u($string)->truncate(10, cut: TruncateMode::WordAfter);
    
        // Autre méthode pour ne pas couper un mot
        $truncateFalse = u($string)->truncate(10, cut: false);

        // On tronque la chaîne de caractères à 10 caractères, en s'arrêtant à la fin du mot précédent
        $truncateBefore = u($string)->truncate(10, cut: TruncateMode::WordBefore);

        dd(
            'Truncate : '.$truncate->toString(),
            'Truncate After : '.$truncateAfter->toString(),
            'Truncate False : '.$truncateFalse->toString(),
            'Truncate Before : '.$truncateBefore->toString()
        );
    }
}

2. Week, WordCount and Yaml Constraints

Dans Symfony 7.2, les contraintes de validation continuent de s’enrichir pour répondre aux besoins d’une gestion plus fine des données en entrée. Cette version introduit trois nouvelles contraintes de validation puissantes : Week, WordCount et Yaml. Chacune d’elles permet de gérer des cas d’usage spécifiques, et simplifie considérablement la validation de données complexes. On découvre ?

🆕 1. Contrainte Week

La contrainte Week vérifie qu’une chaîne de caractères suit le format d’une semaine ISO-8601 (année + semaine), souvent utilisé pour structurer des données calendaires. Par exemple, pour un agenda ou une planification annuelle, il est utile de travailler avec des semaines standards.

Le format ISO-8601 est strict : une semaine est désignée par l’année et le numéro de la semaine, par exemple, 2023-W01 pour la première semaine de l’année 2023.

Cette contrainte permet de spécifier des limites minimales et maximales, pour restreindre les entrées dans une plage de semaines définies.

Testons cette contrainte !

  • Créons une nouvelle entité Event avec la commande suivante :
symfony console make:entity Event

New property name (press <return> to stop adding fields):
 > week

 Field type (enter ? to see all types) [string]:
 > string

 Field length [255]:
 > 255

 Can this field be null in the database (nullable) (yes/no) [no]:
 > no

symfony console make:migration
symfony console doctrine:migrations:migrate
  • Ajoutons la contrainte Week à notre entité :
<?php

namespace App\Entity;

use App\Repository\EventRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: EventRepository::class)]
class Event
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[Assert\Week]
    #[ORM\Column(length: 255)]
    private ?string $week = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getWeek(): ?string
    {
        return $this->week;
    }

    public function setWeek(string $week): static
    {
        $this->week = $week;

        return $this;
    }
}
  • Créons un formulaire pour tester la contrainte :
symfony console make:form EventFormType
<?php

namespace App\Form;

use App\Entity\Event;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class EventFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('week')
            ->add('submit', SubmitType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Event::class,
        ]);
    }
}
  • Créons un controller pour tester la contrainte :
symfony console make:controller EventController
<?php
<?php

namespace App\Controller;

use App\Entity\Event;
use App\Form\EventFormType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class EventController extends AbstractController
{
    #[Route('/event', name: 'app_event')]
    public function index(Request $request): Response
    {
        $event = new Event();
        $form = $this->createForm(EventFormType::class, $event);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            dd($event->getWeek());
        }

        return $this->render('event/index.html.twig', [
            'form' => $form,
        ]);
    }
}
  • Créons une vue pour afficher le formulaire :
{# templates/event/index.html.twig #}
{{ form(form) }}
  • Si on test avec 2025-W01 aucune message d’erreur ne sera affiché, mais si on test avec test une erreur sera affichée.

  • On peu même spécifier des limites minimales et maximales :

#[Assert\Week(min: '2025-W01', max: '2025-W02')]

🆕 2. Contrainte WordCount pour la limitation des mots

La contrainte WordCount est ultra pratique pour les contenus textuels. Elle permet de spécifier un nombre minimum et maximum de mots dans une chaîne. Dans une application de blogging, un formulaire de commentaires, ou pour une section de description de profil, elle aide à gérer précisément la longueur des textes.4

Testons cette contrainte !

  • Ajoutons un champ description à notre entité Event :
symfony console make:entity Event

New property name (press <return> to stop adding fields):
 > description

 Field type (enter ? to see all types) [string]:
 > text

 Can this field be null in the database (nullable) (yes/no) [no]:
 > yes

 updated: src/Entity/Event.php

symfony console make:migration
symfony console doctrine:migrations:migrate
  • Ajoutons la contrainte WordCount à notre entité :
// ...
    #[ORM\Column(type: Types::TEXT, nullable: true)]
    #[Assert\WordCount(min: 100, max: 200)]
    private ?string $description = null;
// ...
  • Ajoutons un champ description à notre formulaire :
// ...
    $builder
        ->add('week')
        ->add('description')
        ->add('submit', SubmitType::class)
    ;
// ...
  • Testons avec une description de moins de 100 mots ou plus de 200 mots.

🆕 3. Contrainte Yaml pour la validation de YAML

La contrainte Yaml est une nouveauté particulièrement utile pour les applications qui utilisent YAML comme format de configuration. Elle vérifie qu’une chaîne de caractères est un YAML valide, ce qui peut être essentiel dans des formulaires où les utilisateurs saisissent directement du code ou des données structurées en YAML.

Testons cette contrainte !

  • Ajoutons un champ config à notre entité Event :
symfony console make:entity Event

New property name (press <return> to stop adding fields):
 > config

 Field type (enter ? to see all types) [string]:
 > text

 Can this field be null in the database (nullable) (yes/no) [no]:
 > yes

 updated: src/Entity/Event.php

symfony console make:migration
symfony console doctrine:migrations:migrate
  • Ajoutons la contrainte Yaml à notre entité :
// ...
    #[ORM\Column(type: Types::TEXT, nullable: true)]
    #[Assert\Yaml(message: 'The value is not a valid YAML string.')]
    private ?string $config = null;
// ...
  • Ajoutons un champ config à notre formulaire :
// ...
    $builder
        //->add('week')
        //->add('description')
        ->add('config')
        ->add('submit', SubmitType::class)
    ;
// ...
  • Testons avec une chaîne de caractères qui n’est pas un YAML valide et une chaîne de caractères qui est un YAML valide.

3. Desktop Notifications

Dans une application web, il est souvent utile de communiquer des informations en temps réel à l’utilisateur, surtout pour des systèmes de monitoring, d’alertes, ou des applications de messagerie. Les notifications de bureau permettent de capter instantanément l’attention, sans exiger que l’utilisateur soit actif dans l’application ou connecté.

Symfony 7.2 propose un nouveau canal de notifications : les notifications de bureau !

JoliNotif permet d’envoyer des notifications locales sur différents systèmes, Symfony simplifie l’envoi de notifications directement visibles par les utilisateurs sans passer par des emails ou des SMS.

🆕 Installation de JoliNotif

Pour utiliser les notifications de bureau, installez le package symfony/joli-notif-notifier :

composer require symfony/joli-notif-notifier

Pensez bien à décommenter la ligne suivante dans le fichier .env :

###> symfony/joli-notif-notifier ###
JOLINOTIF_DSN=jolinotif://default
###< symfony/joli-notif-notifier ###

En arière plan, Symfony Flex a ajouté la configuration suivante dans le fichier config/packages/notifier.yaml :

framework:
    notifier:
        chatter_transports:
        texter_transports:
            jolinotif: '%env(JOLINOTIF_DSN)%'
# ...

🆕 Envoi de notifications de bureau

On va envoyer une notification lors de la soumission d’un formulaire, pour cela on va modifier le controller EventController :

// ...

    public function __construct(
        private TexterInterface $texter,
    ) {
    }

    #[Route('/event', name: 'app_event')]
    public function index(Request $request): Response
    {
        $event = new Event();
        $form = $this->createForm(EventFormType::class, $event);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $message = new DesktopMessage(
                'New event ✨',
                sprintf('Date: %s', $event->getWeek())
            );
    
            $this->texter->send($message);
        }

        return $this->render('event/index.html.twig', [
            'form' => $form,
        ]);
    }

// ...

Et… c’est tout ! Vous devriez voir une notification de bureau s’afficher lors de la soumission du formulaire.

4. Compound Constraint Improvements

Les contraintes composées de Symfony permettent de regrouper plusieurs règles de validation sous une même contrainte, ce qui est particulièrement utile pour encapsuler des validations complexes et spécifiques dans une seule unité logique. Cela évite de multiplier les règles au niveau de chaque propriété ou champ dans un formulaire.

🆕 Un “PasswordRequirements” pour les mots de passe

  • Nous allons ajouter un champ password à notre entité Event :
symfony console make:entity Event

 New property name (press <return> to stop adding fields):
 > password

 Field type (enter ? to see all types) [string]:
 >

 Field length [255]:
 >

 Can this field be null in the database (nullable) (yes/no) [no]:
 > yes

 updated: src/Entity/Event.php

symfony console make:migration
symfony console doctrine:migrations:migrate
  • Créons une contrainte composée PasswordRequirements dans le répertoire src/Validator/Constraints :
<?php

declare(strict_types=1);

namespace App\Validator\Constraints;

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Constraints\Compound;

#[\Attribute]
class PasswordRequirements extends Compound
{
    protected function getConstraints(array $options): array
    {
        return [
            new Assert\NotBlank(allowNull: false),
            new Assert\Length(min: 8, max: 255),
            new Assert\NotCompromisedPassword(),
            new Assert\Type('string'),
        ];
    }
}
  • Ajoutons la contrainte PasswordRequirements à notre entité Event :
// ...
use App\Validator\Constraints\PasswordRequirements;
// ...
    #[ORM\Column(length: 255, nullable: true)]
    #[PasswordRequirements]
    private ?string $password = null;
// ...
  • Ajoutons un champ password à notre formulaire :
// ...
    $builder
        //->add('week')
        //->add('description')
        //->add('config')
        ->add('password')
        ->add('submit', SubmitType::class)
    ;
// ...
  • And voilà ! Vous avez maintenant une contrainte composée qui vérifie que le mot de passe respecte les règles définies. Facile, non ?!

Conclusion

Symfony 7.2 apporte de nombreuses améliorations et nouveautés qui vont faciliter la vie des développeurs. Les améliorations du composant String, les nouvelles contraintes de validation, les notifications de bureau et les contraintes composées sont autant de fonctionnalités qui vont vous permettre de gagner du temps et de la sérénité dans vos projets Symfony. Et il y a bien d’autres nouveautés à découvrir dans cette version !

Back to Blog