Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Réinventer le composant Console de Symfony

Réinventer le composant Console de Symfony

Console est le composant Symfony le plus utilisé. Des outils critiques comme Composer jusqu'aux autres frameworks PHP populaires, en passant par nos applications finales, il est omniprésent. L'inconvénient de cela est que changer quoi que ce soit n'est pas une mince affaire. Même le plus petit bug fix est susceptible de casser des milliers d'usages. Néanmoins, le composant s'améliore constamment grâce aux innombrables contributions qu'il reçoit depuis son introduction en 2010, tout en conservant sa rétrocompatibilité. Mais nous pensons qu'il est temps de faire peau neuve, notamment pour ouvrir le composant à davantage de possibilités et le débarrasser de certains problèmes de design. C'est pourquoi nous, quelques contributeurs clés dont Théo Fidry, Kevin Bond et moi-même, avons travaillé intensivement à le revisiter. C'est ce ce que je vais vous présenter dans ce talk. Préparez-vous à redécouvrir la Console !

Robin Chalas

March 24, 2023
Tweet

More Decks by Robin Chalas

Other Decks in Programming

Transcript

  1. Enregistrer une commande, c'était comment avant ? // src/AppBundle/Command/FooCommand.php namespace

    AppBundle\Command; class FooCommand extends Command { protected function configure() { $this->setName('app:foo'); } protected function execute() { // ... } } @chalas_r
  2. Tout était convention // src/Symfony/Component/HttpKernel/Bundle/Bundle.php public function registerCommands(Application $application) {

    if (!is_dir($dir = $this->getPath().'/Command')) { return; } $finder = new Finder(); $finder->files()->name('*Command.php')->in($dir); foreach ($finder as $file) { $class = $file->getBasename('.php'); $r = new \ReflectionClass($class); $application->add($r->newInstance()); } } @chalas_r
  3. Commands as services à la rescousse ! class FooCommand extends

    Command { protected function configure() { $this->setName('app:foo'); } protected function execute() { // ... } } services: App\Command\FooCommand: tags: - { name: 'console.command' } @chalas_r
  4. ROUTING EN AMONT class FooCommand extends Command { protected function

    configure() { // ... } } App\Command\FooCommand: tags: - { name: 'console.command', command: 'app:foo' } @chalas_r
  5. Laziness Round 2 : AutoConfiguration class FooCommand extends Command {

    public static $defaultName = 'app:foo'; protected function execute($input, $output) { } } @chalas_r
  6. Snippet montrant propriété static $defaultName et $defaultDescription et la méthode

    configure() mais cette fois avec le setName() et setDescription() barrées genre effacé parce que plus utile) class FooCommand extends Command { public static $defaultName = 'app:foo'; public static $defaultDescription = 'A foo command'; protected function configure() { $this->setName('app:foo'); $this->setDescription('A foo command'); } } @chalas_r Encore plus lazy
  7. Attributes FTW screenshot de #[AsCommand] en remplacement de $defaultName et

    $defaultDescription #[AsCommand( name: 'app:foo', description: 'A foo command', )] class FooCommand extends Command { protected function execute($input, $output) { } } @chalas_r
  8. En CLI, les commandes sont les points d’entrée et de

    sortie de nos traitements métiers. Tout comme le sont les contrôleurs dans un contexte HTTP. 〞 @chalas_r
  9. Un Controller peut être n'importe qu'elle callable, tandis que les

    Commandes doivent étendre la classe Command. De fait, une Commande hérite de méthodes dont elle n'a potentiellement pas besoin (90% du temps). Il existe des Controller Argument Value Resolvers, rien de tel pour les commandes. DIFFÉRENCES MAJEURES @chalas_r
  10. Déclaration d'une commande via #[AsCommand] uniquement. Définition de l'input via

    #[InputArgument] et #[InputOption] Command Argument Value Resolvers Going full attributes @chalas_r
  11. Invokable Command #[AsCommand(name: "user:create", description: "Creates a user")] final class

    CreateUserCommand { public function __invoke(...): int { // ... } } @chalas_r
  12. #[AsCommand(name: "user:create") final class CreateUserCommand { public function __invoke( OutputInterface

    $output, UserRepository $userRepository, #[Autowire(service: 'mailer')] MailerInterface $mailer, #[InputArgument(mode: InputArgument::REQUIRED)] string $email, #[InputOption(mode: InputOption::VALUE_IS_ARRAY)] array $roles, ): int { $user = new User($email, $role); $userRepository->save($user); $mailer->send(...); } } @chalas_r
  13. Autres choses à revoir Abolir le couplage étroit entre Command

    & Application Simplifier l'usage de SymfonyStyle Améliorer l’API de sélection du flux de sortie (stdout vs stderr) Enrichir l'API de Testing @chalas_r