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

Symfony in 2025: Scaling to 0

Symfony in 2025: Scaling to 0

Fabien Potencier

March 27, 2025
Tweet

More Decks by Fabien Potencier

Other Decks in Technology

Transcript

  1. 2014 DX initiative 2017 Symfony Flex Symfony 4 2020 Symfony

    UX 2022 recipes:update 2018 Silex symfony/symfony Symfony DX in 5 key dates Each new version brings DX improvements "New in Symfony X.Y" posts / tag on Github Since 2014 DX 🤝
  2. use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Attribute\Route; class AppKernel

    extends Kernel { use MicroKernelTrait; #[Route('/hello/{name}', defaults: ['name' => 'World'])] public function __invoke(string $name): Response { return new Response('Hello '.$name); } } return static function (array $context) { return new AppKernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); }; Classical HTTP Hello World "application" Hello
  3. Features cache config dependency-injection event-dispatcher framework-bundle http-foundation http-kernel routing runtime

    Interfaces and PHP compat layers Some PSR interfaces Some polyfills Some Symfony contracts symfony composer install --no-dev Utilities Enhance PHP core error-handler filesystem finder var-dumper var-exporter 💪 Error handling, dumper, PHP attributes, ... 💪 No third-party code Hello
  4. Not a toy framework Production ready - like caching for

    better performance Lot of useful features - without adding any deps Extensible - scale to the full Symfony capabilities Hello
  5. use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Attribute\Route; class AppKernel

    extends Kernel { use MicroKernelTrait; #[Route('/', methods: 'GET', defaults: ['name' => 'World'])] #[Route('/hello/{name}', requirements: ['name' => '\w+'])] public function __invoke(string $name): Response { return new Response('Hello '.$name); } } return static function (array $context) { return new AppKernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); }; Multi-route support, full configurability Hello
  6. #[Route('/')] public function wow(): Response { return new Response('wow'); }

    #[Route('/hello/{name}', defaults: ['name' => 'World'])] public function hello(string $name): Response { return new Response('Hello '.$name); } Multi-controllers in the same file Any method name Hello
  7. #[Route('/hello')] class AppKernel extends Kernel { use MicroKernelTrait; #[Route('/')] public

    function wow(): Response { return new Response('wow'); } #[Route('/{name}', defaults: ['name' => 'World'])] public function hello(string $name): Response { return new Response('Hello '.$name); } } Mount routes Hello
  8. use Psr\Log\LoggerInterface; class AppKernel extends Kernel { use MicroKernelTrait; #[Route('/hello')]

    public function hello(Request $request, LoggerInterface $logger): Response { $logger->info('Hello'); return new Response('Hello '.$request->query->get('name')); } } Inject Services ❯ symfony server:log Following Web Server log file (/Users/fabien/.symfony5/log/95...b1.log) Following PHP-FPM log file (/Users/fabien/.symfony5/log/95...b1/53...75.log) [Web Server ] Mar 14 16:57:08 |INFO | SERVER GET (200) /hello.php/hello?name=Fabien ip="127.0.0.1" [PHP-FPM ] [14-Mar-2025 15:57:08 UTC] [info] Matched route "appkernel_hello". [PHP-FPM ] [14-Mar-2025 15:57:08 UTC] [info] Hello Hello
  9. Debug with ease return static function (array $context) { dump($context['APP_ENV'],

    $context['APP_DEBUG'], $context); return new AppKernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); }; With auto-navigation in the browser Hello
  10. Classical Console Hello World "application" use Symfony\Component\Console\Application; use Symfony\Component\Console\Attribute\AsCommand; use

    Symfony\Component\Console\Command\Command; $app = new Application(); $app->add(new #[AsCommand(name: 'app:hello')] class extends Command { public function __invoke(): int { echo 'Hello World'; return 0; } } ); $app->setDefaultCommand('app:hello')->run(); Hello
  11. Classical Console Hello World "application" $app = new Application(); $app->add(new

    #[AsCommand(name: 'app:hello')] class extends Command { public function __invoke( SymfonyStyle $io, #[Argument] string $name = 'World', #[Option] bool $formal = false, ): int { $io->title(sprintf('%s %s!', $formal ? 'Hello' : 'Hey', $name)); return Command::SUCCESS; } } ); $app->setDefaultCommand('app:hello')->run(); Hello ❯ php app.php app:hello --formal Fabien Hello Fabien! =============
  12. Using the Symfony Runtime #[AsCommand(name: 'app:hello')] class AppKernel extends Kernel

    { use MicroKernelTrait; public function __invoke( SymfonyStyle $io, #[Argument] string $name = 'World', #[Option] bool $formal = false, ): int { $io->title(sprintf('%s %s!', $formal ? 'Hello' : 'Hey', $name)); return Command::SUCCESS; } } return static function (array $context) { return (new Application(new AppKernel('dev', true)))->setDefaultCommand('app:hello'); }; Hello
  13. A front controller for CLI and HTTP Solo use App\Kernel;

    use Symfony\Bundle\FrameworkBundle\Console\Application; require_once __DIR__.'/../vendor/autoload_runtime.php'; return static function (array $context) { $kernel = new Kernel('dev', true); return \PHP_SAPI === 'cli' ? new Application($kernel) : $kernel; }; public/index.php
  14. Your choice of a directory structure Solo public/ index.php src/

    HelloController.php HelloCommand.php Kernel.php SomeService.php composer.json
  15. Doing some configuration Solo namespace App; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\HttpKernel\Kernel

    as BaseKernel; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; class Kernel extends BaseKernel { use MicroKernelTrait; private function configureRoutes(RoutingConfigurator $routes): void { $routes->import(dirname(__DIR__).'/src/*Controller.php', 'attribute'); } } src/Kernel.php
  16. Controllers as classes Solo namespace App; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController;

    use Symfony\Component\Routing\Attribute\Route; #[AsController] class HelloController { #[Route('/hello')] public function hello(): Response { return new Response('Hello'); } } src/HelloController.php No base class
  17. Add Twig support Solo public function registerBundles(): iterable { yield

    new FrameworkBundle(); yield new TwigBundle(); } private function configureContainer(ContainerConfigurator $container): void { $container->services() ->defaults()->autowire()->autoconfigure() ->load('App\\', dirname(__DIR__).'/src') ; } src/Kernel.php public function hello(Environment $twig): Response { return new Response($twig->render('hello.twig')); } src/HelloController.php public/ index.php src/ HelloController.php Kernel.php templates/ hello.twig Default config
  18. public function registerBundles(): iterable { yield new FrameworkBundle(); yield new

    WebProfilerBundle(); } private function configureRoutes(RoutingConfigurator $routes): void { $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.xml')->prefix('/_profiler'); $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.xml')->prefix('/_wdt'); } private function configureContainer(ContainerConfigurator $container): void { $container->extension('web_profiler', [ 'toolbar' => 'dev' === $this->getEnvironment(), ]); } src/Kernel.php Add Profiler Bundle Solo Will be .php in 7.3+
  19. namespace App; #[AsCommand(name: 'app:hello')] class HelloCommand { public function __invoke(SymfonyStyle

    $io, #[Argument] string $name = 'World'): int { $io->title(sprintf('Hello %s!', $name)); return Command::SUCCESS; } } src/HelloCommand.php Commands as classes Solo
  20. Send Emails Solo private function configureContainer(ContainerConfigurator $container): void { $container->extension('framework',

    [ 'mailer' => [ 'dsn' => '%env(MAILER_DSN)%', ], ]); } src/Kernel.php #[Route('/send-email', methods: 'GET')] public function sendEmail(MailerInterface $mailer): Response { $mailer->send((new Email()) ->from('[email protected]')->to('[email protected]') ->subject('Time for Symfony Mailer!') ->text('Sending emails is fun again!') ); return new Response('<html><body><h1>Email sent</h1></body></html>'); } src/HelloController.php
  21. Send Emails Solo MAILER_DSN=smtp://localhost:1025 .env 1 services: mailer: image: schickling/mailcatcher

    ports: [1025, 1080] docker-compose.yml 2 ❯ docker compose up -d ❯ symfony server:start -d 3
  22. Solo Hello Team Single file Learning / Demo None None

    Examples / Demo Small to medium Individual Self-defined Self-defined Personal projects Single-purpose apps Quick proof of concepts Medium to large Team Standardized Documented Team collaboration Long-term maintenance Complex business logic Scale Developers Structure Conventions Best for
  23. LLMs vs Human vs Community Symfony 7.3 introduces a way

    to define and inject arguments directly. Use it. More documentation here: @https://github.com/symfony/symfony/pull/59340 Looking at the pull request, Symfony 7.3 introduces some exciting DX improvements for console commands, particularly the ability to use invokable commands with attribute-based arguments. Let's update your ColorSchemeCommand to use these new features: - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(SymfonyStyle $io, #[Argument(description: 'Hex color (e.g. #FFFFFF)')] string $color): int Human LLM
  24. Good title Great rationale behind the change Before/After examples Helps

    reviewers Helps writing docs Helps LLMs Community