Découplage Équilibré

Avec Symfony

Robin Chalas

chalas_r
chalasr

baksla.sh

@chalas_r

COUPLAGE

Niveau d'interaction entre deux composants.

@chalas_r

NULL BESOIN De TOUT DECOUPLER

@chalas_r

PLACER LE CURSEUR.

RAPPORT COÛT/BÉNÉFICE

@chalas_r

ESSAYER,

ÉVALUER,

CHANGER.

1 - FAVORISER LES

CONTROLLERS

à ACTION UNIQUE

MULTI-TÂCHES

-> SRP

@chalas_r
namespace AfupDay\Talk\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;

#[Route("/talk/schedule", name: "schedule_talk")]
class ScheduleTalkController
{
    public function __invoke(Request $request): Response
    {
		// ... 

        return new Response(...);
    }
}

2 - Déclarer

LES dépendances

EXPLICITEMENT

INJECTER UNIQUEMENT

CE DONT

LE CONTROLLER

A BESOIN

DE préférence

VIA son CONSTRUCTEUR

@chalas_r
class ScheduleTalkController
{
    public function __construct(
		private Security $security, 
		private ValidatorInterface $validator,
        // ...
    ) {}

    public function __invoke(Request $request): Response
    {
		// ...
    }
}

__CONSTRUCT()

EST

FRAMEWORK-AGNOSTIC

3- NE PAS Étendre ABSTRACT CONTROLLER

SYSTÉMATIQUEMENT

ABSTRACT CONTROLLER MASQUE LES DEPENDANCES,

donc OBSCURCIT le scope;

ET CRÉE UN COUPLAGE FORT ENTRE VOTRE CONTROLLER ET SYMFONY.

4 - PEU D'INTérêT

À SÉPARER LE ROUTING

5 - séparer

la logique métier

DE L'INFRASTRUCTURE

CONTROLLER,

CONSOLE COMMAND

-> INFRA

FORMS -> UI

(= INFrA)

ENTITés -> DOMAIN

6 - UTILISER DES d.t.o

@chalas_r
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;

class ScheduleTalkController
{
    public function __invoke(
        #[MapRequestPayload] ScheduleTalkDto $scheduleTalk,
    ): Response {
        // ...
    }
}

LES D.T.O

fonctionnent partout

LES ENTITés sont

la source de vérité

de votre système

NE PAS TORDRE SA SOURCE DE VERITé

pour satisfaire symfony (OU doctrine)

7 - MAPPER DES D.TO.

AUX FORMS

@chalas_r
$form = $this->formFactory->createForm(ScheduleTalk::class);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
	/** @var ScheduleTalkDto $scheduleTalk */
	$scheduleTalk = $form->getData();
	$talk = new Talk(
    	$scheduleTalk->title, 
        $scheduleTalk->speaker, 
        $scheduleTalk->timeSlot
    );
    
    if ($scheduleTalk->confirmed) {
    	$talk->confirm();
    }
    
    $this->talkRepository->add($talk);
}

TRAITEMENTS MÉTIER

-> DOMAIN

8 - GO APPLICATION SERVICES

(+ D.T.O)

ENVISAGER

LE PATTERN COMMAND 

@chalas_r
use Symfony\Component\Messenger\HandleTrait;
use Symfony\Component\Messenger\MessageBusInterface;

final class ScheduleTalkCommand
{
    public function __construct(
		public string $title,
        public string $author,
        public string $timeSlot,
        // ...
    ) {}
}
@chalas_r
use AfupDay\Talk\Repository\TalkRepositoryInterface;
use Symfony\Component\Messenger\HandleTrait;
use Symfony\Component\Messenger\MessageBusInterface;

final class ScheduleTalkCommandHandler
{
	public function __construct(
    	private TalkRepositoryInterface $talkRepository
    ) {}
    
    public function handle(ScheduleTalkCommand $command)
    {
    	$talk = new Talk(
        	$command->title, 
            $command->author, 
            $command->timeSlot
        );
        
        // ...
        
        $this->talkRepository->add($talk);
    }
}
@chalas_r
use Symfony\Component\Messenger\HandleTrait;
use Symfony\Component\Messenger\MessageBusInterface;

class ScheduleTalkController
{
	use HandleTrait;

    public function __construct(
		private MessageBusInterface $commandBus
        // ...
    ) {}

    public function __invoke(Request $request): Response
    {
    	$this->commandBus->handle(
        	new ScheduleTalkCommand($request->request->get('...'))
        );
        
        //...
    }
}

9 - NE PAS FAIRE TRANSITER LES VALUE OBJETS

DU FRAMEWORK

À TRAVERS VOS SERVICES

NE PAS balader

l'objet REQUEST -> GO DTO

Côté console

NE PAS balader

l'objet Input/OUTPUt

-> GO DTO

10 - Mettez des contrats sur vos domain services

DEFINIR une INTERFACE maison PAR dessus chaque repository

Merci !

@chalas_r

Recettes de découplage équilibré avec Symfony

By Robin Chalas

Recettes de découplage équilibré avec Symfony

Un projet web, on sait quand ça commence mais jamais quand ça se termine. De nouveaux besoins métiers émergent, d'autres évoluent, certains disparaissent, et notre code doit s'adapter. Ceci ne s'applique pas seulement à notre projet, mais à tous ceux dont il dépend. SGBD, langage, framework ... Même combat. Personne ne sait à quoi ressemblera la prochaine version majeure de Symfony, ni combien de temps il sera maintenu. Ce qu'on peut en conclure, c'est que la capacité de nos projets à évoluer et résister dans le temps dépend fortement de la façon dont nous utilisons le code qui ne nous appartient pas. Si vous avez déjà eu à mettre à jour un projet de Symfony 2 à 6 en une itération, vous voyez de quoi je parle. La bonne nouvelle c'est que dernièrement, Symfony a pris une direction qui, en plus de nous permettre de relever les challenges les plus complexes, embrasse des patterns qui permettent d'écrire du code plus résistant au changement. Dans ce talk, je vous parlerai de bonnes habitudes et pratiques visant à tirer le meilleur parti du framework sans créer de couplage fort entre votre code et le sien, de sorte à trouver le bon équilibre entre adaptabilité et productivité.

  • 389