Slide 1

Slide 1 text

What is Symfony? Fabien Potencier

Slide 2

Slide 2 text

What is Symfony? Fabien Potencier

Slide 3

Slide 3 text

Doc > Code

Slide 4

Slide 4 text

Community > Doc > Code

Slide 5

Slide 5 text

Community > > Doc > Code DX

Slide 6

Slide 6 text

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 🤝

Slide 7

Slide 7 text

DX: The last mile

Slide 8

Slide 8 text

DX: The last mile

Slide 9

Slide 9 text

Scaling to 0

Slide 10

Slide 10 text

Micro-frameworks? 2018 2021

Slide 11

Slide 11 text

Symfony Hello

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

#[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

Slide 17

Slide 17 text

#[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

Slide 18

Slide 18 text

#[Route('/hello')] public function hello(Request $request): Response { return new Response('Hello '.$request->query->get('name')); } Inject Services Hello

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Simplest Console Hello World "application" use Symfony\Component\Console\SingleCommandApplication; (new SingleCommandApplication()) ->setCode(function () { echo 'Hello World'; return 0; }) ->run() ; Hello

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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! =============

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

The power of the community Hello ... ...

Slide 26

Slide 26 text

Symfony Solo

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Your choice of a directory structure Solo public/ index.php src/ HelloController.php HelloCommand.php Kernel.php SomeService.php composer.json

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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 Use .php in 7.3+

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Profile Commands Solo ❯ php public/index.php app:hello Fabien --profile

Slide 35

Slide 35 text

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('

Email sent

'); } src/HelloController.php

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Solo DX ♥

Slide 38

Slide 38 text

Symfony Team

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Good title Great rationale behind the change Before/After examples Helps reviewers Helps writing docs Helps LLMs Community

Slide 42

Slide 42 text

https://symfony.com/sponsor Sponsor Symfony Thank you!

Slide 43

Slide 43 text

No content