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