Slide 1

Slide 1 text

What’s new in Symfony 5.3

Slide 2

Slide 2 text

Malte Schlüter [email protected] @malteschlueter [email protected] @ocrampete16 Marco Petersen [email protected] @jschaedl Jan Schädlich

Slide 3

Slide 3 text

Agenda

Slide 4

Slide 4 text

Agenda Deprecations

Slide 5

Slide 5 text

Agenda Deprecations Improvements

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Deprecations Symfony 5.3 from 5.2 to 5.3 Code will be removed in Symfony 6.0

Slide 8

Slide 8 text

Rename master request to main request HttpKernelInterface::MASTER_REQUEST -> HttpKernelInterface::MAIN_REQUEST KernelEvent::isMasterRequest() -> KernelEvent::isMainRequest() RequestStack::getMasterRequest() -> RequestStack::getMainRequest() HttpFoundation|HttpKernel

Slide 9

Slide 9 text

Deprecated the SessionInterface alias use Symfony\Component\HttpFoundation\Session\SessionInterface; final class SessionAwareService { public function __construct( private SessionInterface $session ) { } public function someMethodUsingTheSession(): void { $foo = $this->session->get('foo'); } } FrameworkBundle

Slide 10

Slide 10 text

Deprecated the SessionInterface alias use Symfony\Component\HttpFoundation\RequestStack; final class SessionAwareService { public function __construct( private RequestStack $requestStack ) { } public function someMethodUsingTheSession(): void { $foo = $this->requestStack->getSession()->get('foo'); } } final class TestController { public function __invoke(Request $request): Response { $foo = $request->getSession()->get('foo'); } } FrameworkBundle

Slide 11

Slide 11 text

Removed dependency on symfony/intl composer req symfony/intl Form LocaleType CountryType CurrencyType LanguageType TimezoneType

Slide 12

Slide 12 text

Deprecated the use of TLS option for Redis Bridge # .env # before MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages?tls=true # after MESSENGER_TRANSPORT_DSN=rediss://localhost:6379/messages Messenger

Slide 13

Slide 13 text

https://github.com/symfony/symfony/blob/master/UPGRADE-5.3.md

Slide 14

Slide 14 text

Improvements

Slide 15

Slide 15 text

More PHP 8 Attributes

Slide 16

Slide 16 text

Autowire arguments in controller actions final class Symfony52Controller { #[Route('/symfony52', name: 'symfony52', methods: ['GET'])] public function __invoke(UserRepository $userRepository): Response { return new Response('Go and get PHP 8!'); } } Symfony 5.2 # services.yaml App\Controller\Symfony52Controller: tags: ['controller.service_arguments'] Symfony 5.2

Slide 17

Slide 17 text

#[AsController] #[AsController] final class Symfony53Controller { #[Route('/symfony52', name: 'symfony52', methods: ['GET'])] public function __invoke(UserRepository $userRepository): Response { return new Response('Symfony 💖 PHP 8!'); } } Symfony 5.3 # services.yaml App\Controller\Symfony52Controller: tags: ['controller.service_arguments'] Symfony 5.3

Slide 18

Slide 18 text

Event Listeners/Subscribers final class Symfony52Listener implements EventSubscriberInterface { public static function getSubscribedEvents() { return [ CustomEvent::class => [ ['onCustomEvent', 10], ], ]; } public function onCustomEvent(CustomEvent $event): void { $event->messages[] = 'Greetings from Symfony52Listener 👋'; } } Symfony 5.2

Slide 19

Slide 19 text

#[AsEventListener] #[AsEventListener( event: CustomEvent::class, method: 'onCustomEvent', priority: 10 )] final class Symfony53Listener { public function onCustomEvent(CustomEvent $event): void { $event->messages[] = 'Greetings from Symfony53Listener 👋'; } } Symfony 5.3

Slide 20

Slide 20 text

Commands final class Symfony52Command extends Command { protected static $defaultName = 'app:symfony52'; protected static $defaultDescription = 'an awesome description'; protected function configure() { $this ->setAliases(['symfony52']) ->setHidden(false); } protected function execute(InputInterface $input, OutputInterface $output) { (new SymfonyStyle($input, $output))->text('Go and get PHP 8!'); return 0; } } Symfony 5.2

Slide 21

Slide 21 text

#[AsCommand] #[AsCommand( name: 'app:symfony53', description: 'an awesome description', aliases: ['symfony53'], hidden: false )] final class Symfony53Command extends Command { protected function execute(InputInterface $input, OutputInterface $output) { (new SymfonyStyle($input, $output))->text('Symfony 💖 PHP 8!'); return 0; } } Symfony 5.3

Slide 22

Slide 22 text

#[When] #[When(env: 'dev')] #[AsCommand(name: 'app:when-dev')] final class WhenDevCommand extends Command { protected function execute(InputInterface $input, OutputInterface $output) { (new SymfonyStyle($input, $output)) ->text(‘Symfony 💖 PHP 8, but for now only in dev environment!'); return 0; } } Symfony 5.3

Slide 23

Slide 23 text

Autocon fi gure services via Attributes services: _defaults: autoconfigure: true # Automatically registers your services # as commands, event subscribers, etc. Symfony 5.2

Slide 24

Slide 24 text

Autocon fi gure services via Attributes services: _defaults: autoconfigure: true # Automatically registers your services # as commands, event subscribers, etc. Symfony 5.2 services: _instanceof: App\Security\CustomInterface: tags: ['app.custom_tag']

Slide 25

Slide 25 text

Autocon fi gure services via Attributes services: _defaults: autoconfigure: true # Automatically registers your services # as commands, event subscribers, etc. Symfony 5.2 services: _instanceof: App\Security\CustomInterface: tags: ['app.custom_tag'] // src/Kernel.php class Kernel extends BaseKernel { // ... protected function build(ContainerBuilder $container): void { $container->registerForAutoconfiguration(CustomInterface::class) ->addTag('app.custom_tag') ; } }

Slide 26

Slide 26 text

Autocon fi gure services via Attributes services: _defaults: autoconfigure: true # Automatically registers your services # as commands, event subscribers, etc. Symfony 5.2 services: _instanceof: App\Security\CustomInterface: tags: ['app.custom_tag'] // src/Kernel.php class Kernel extends BaseKernel { // ... protected function build(ContainerBuilder $container): void { $container->registerForAutoconfiguration(CustomInterface::class) ->addTag('app.custom_tag') ; } } // src/DependencyInjection/MyBundleExtension.php class MyBundleExtension extends Extension { // ... public function load(array $configs, ContainerBuilder $container): void { $container->registerForAutoconfiguration(CustomInterface::class) ->addTag('app.custom_tag') ; } }

Slide 27

Slide 27 text

#[Autocon fi gure] Symfony 5.3 use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; #[Autoconfigure(tags: ['app.loader'])] interface LoaderInterface { public function supports(string $type): bool; public function load(); }

Slide 28

Slide 28 text

#[Autocon fi gure] Symfony 5.3 use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; #[Autoconfigure(tags: ['app.loader'])] interface LoaderInterface { public function supports(string $type): bool; public function load(); } namespace Symfony\Component\DependencyInjection\Attribute; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] class Autoconfigure { public function __construct( public ?array $tags = null, public ?array $calls = null, public ?array $bind = null, public bool|string|null $lazy = null, public ?bool $public = null, public ?bool $shared = null, public ?bool $autowire = null, public ?array $properties = null, public array|string|null $configurator = null, ) { } }

Slide 29

Slide 29 text

#[Autocon fi gureTag] Symfony 5.3 namespace App\Loader; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; #[AutoconfigureTag('app.loader')] #[AutoconfigureTag()] # tag name => App\Loader\LoaderInterface interface LoaderInterface { public function supports(string $type): bool; public function load(); }

Slide 30

Slide 30 text

Use tagged services use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; final class LoaderIterator { public function __construct( #[TaggedIterator('app.loader')] private iterable $loaders, ) { } } Symfony 5.3

Slide 31

Slide 31 text

Use tagged services use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; final class LoaderIterator { public function __construct( #[TaggedIterator('app.loader')] private iterable $loaders, ) { } } use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; #[AsTaggedItem(index: 'yaml_loader', priority: 10)] final class YamlLoader implements LoaderInterface { public function supports(string $type): bool { ... } public function load() { ... } } Symfony 5.3

Slide 32

Slide 32 text

Use tagged services use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; final class LoaderIterator { public function __construct( #[TaggedIterator('app.loader')] private iterable $loaders, ) { } } use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; #[AsTaggedItem(index: 'xml_loader', priority: 20)] final class XmlLoader implements LoaderInterface { public function supports(string $type): bool { ... } public function load() { ... } } use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; #[AsTaggedItem(index: 'yaml_loader', priority: 10)] final class YamlLoader implements LoaderInterface { public function supports(string $type): bool { ... } public function load() { ... } } Symfony 5.3

Slide 33

Slide 33 text

Use tagged services use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; final class LoaderIterator { public function __construct( #[TaggedIterator('app.loader')] private iterable $loaders, ) { } } use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; #[AsTaggedItem(index: 'xml_loader', priority: 20)] final class XmlLoader implements LoaderInterface { public function supports(string $type): bool { ... } public function load() { ... } } use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; #[AsTaggedItem(index: 'yaml_loader', priority: 10)] final class YamlLoader implements LoaderInterface { public function supports(string $type): bool { ... } public function load() { ... } } use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; final class LoaderIterator { public function __construct( #[TaggedIterator('app.loader')] private iterable $loaders, ) { } } Symfony 5.3

Slide 34

Slide 34 text

Use tagged services use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; final class LoaderIterator { public function __construct( #[TaggedIterator('app.loader')] private iterable $loaders, ) { } } use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; #[AsTaggedItem(index: 'xml_loader', priority: 20)] final class XmlLoader implements LoaderInterface { public function supports(string $type): bool { ... } public function load() { ... } } use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; #[AsTaggedItem(index: 'yaml_loader', priority: 10)] final class YamlLoader implements LoaderInterface { public function supports(string $type): bool { ... } public function load() { ... } } use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; final class LoaderIterator { public function __construct( #[TaggedIterator('app.loader')] private iterable $loaders, ) { } } use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; final class LoaderLocator { public function __construct( #[TaggedLocator('app.loader')] private ContainerInterface $container, ) { $yamlLoader = $container->get('yaml_loader'); $xmlLoader = $container->get('xml_loader'); } } Symfony 5.3

Slide 35

Slide 35 text

Add support for sorting fi elds Form

Slide 36

Slide 36 text

Add support for sorting fi elds Form class PersonRegistrationType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('firstName') ->add('lastName') ->add(‘register', SubmitType::class); $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { $person = $event->getData(); if ($person->isLegalType()) { $event->getForm()->add('company'); } }); } }

Slide 37

Slide 37 text

Add support for sorting fi elds Form class PersonRegistrationType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('firstName') ->add('lastName') ->add(‘register', SubmitType::class); $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { $person = $event->getData(); if ($person->isLegalType()) { $event->getForm()->add('company'); } }); } } Register {# wrong place #} {# wrong place #}

Slide 38

Slide 38 text

class PersonRegistrationType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('firstName') ->add('lastName') ->add(‘register', SubmitType::class, ['priority' => -1/*, ... */]); $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { $person = $event->getData(); if ($person->isLegalType()) { $event->getForm()->add('company', null, ['priority' => 1]); } }); } } Add support for sorting fi elds Form

Slide 39

Slide 39 text

Noti fi er Component Symfony 5.3

Slide 40

Slide 40 text

Noti fi er Component Symfony 5.3 not experimental anymore

Slide 41

Slide 41 text

New Noti fi er integrations Noti fi er Texter Chatter

Slide 42

Slide 42 text

FakeSms & FakeChat Noti fi er # .env FAKE_SMS_DSN=fakesms+email://default?to=TO&from=FROM FAKE_CHAT_DSN=fakechat+email://default?to=TO&from=FROM # config/packages/dev/notifier.yaml framework: notifier: chatter_transports: slack: '%env(FAKE_CHAT_DSN)%' telegram: '%env(FAKE_CHAT_DSN)%' texter_transports: twilio: '%env(FAKE_SMS_DSN)%' nexmo: '%env(FAKE_SMS_DSN)%'

Slide 43

Slide 43 text

(de)normalization context in mapping Serializer use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; class Foo { /** * @Serializer\Context({ DateTimeNormalizer::FORMAT_KEY = 'Y-m-d' }) */ public \DateTime $date; // In PHP 8 applications you can use PHP attributes instead: #[Serializer\Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])] public \DateTime $anotherDate; }

Slide 44

Slide 44 text

(de)normalization context in mapping Serializer #[Serializer\Context( normalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'], denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => \DateTimeImmutable::COOKIE] )] public \DateTime $date; #[Serializer\Context( normalizationContext: [DateTimeNormalizer::FORMAT_KEY => \DateTimeImmutable::RFC3339], groups: ['extended'] )] public \DateTime $date;

Slide 45

Slide 45 text

New Tailwind CSS form theme TwigBridge # twig.yaml twig: form_themes: - tailwind_2_layout.html.twig

Slide 46

Slide 46 text

Export concatenated translations {{ ('aa' ~ 'bb') | trans }} TwigBridge php bin/console translation:update --dump-messages de

Slide 47

Slide 47 text

Twig serialize fi lter {{ object|serialize(format = 'json', context = []) }} TwigBridge

Slide 48

Slide 48 text

Twig serialize fi lter {{ object|serialize(format = 'json', context = []) }} TwigBridge {{ stimulus_controller('product-show', { product: product|serialize('json', { groups: 'product:read'}) }) }}

Slide 49

Slide 49 text

PHP Con fi guration Builder Con fi g

Slide 50

Slide 50 text

PHP Con fi guration Builder Con fi g

Slide 51

Slide 51 text

Removing Directories without Race Conditions Filesystem

Slide 52

Slide 52 text

No-Lock Rate Limiters RateLimiter

Slide 53

Slide 53 text

A Quick Security Recap

Slide 54

Slide 54 text

New Authentication System (since 5.1) • Remove everything but Guard. • Use an event-driven system (like HttpKernel). • Guards are now called Authenticators, and are a bit di ff erent. • Unauthenticated Tokens are now Passports, and are also a bit di ff erent.

Slide 55

Slide 55 text

Mandatory Passport Badges Security

Slide 56

Slide 56 text

bin/console debug: fi rewall Security

Slide 57

Slide 57 text

bin/console debug: fi rewall Security

Slide 58

Slide 58 text

Generate Login Link with User Locale Security

Slide 59

Slide 59 text

Mask CSRF Token against BREACH Security

Slide 60

Slide 60 text

UID Component (experimental) 🎉

Slide 61

Slide 61 text

New UID Factory Methods UID

Slide 62

Slide 62 text

Generate UIDs via CLI UID

Slide 63

Slide 63 text

Generate UIDs via CLI UID

Slide 64

Slide 64 text

UUID & ULID Form Types Form / UID

Slide 65

Slide 65 text

New UID Normalization Formats Serializer / UID

Slide 66

Slide 66 text

Support binary / negatable options Contributed by jderusse in #39642 Console class SomeCommand extends Comman }

Slide 67

Slide 67 text

Support binary / negatable options Contributed by jderusse in #39642 Console class SomeCommand extends Comman d { // ... protected function execute(InputInterface $input, OutputInterface $output): in t { // if command is run as `command-name`, $useAnsi = nul l // if command is run as `command-name --ansi`, $useAnsi = tru e // if command is run as `command-name --no-ansi`, $useAnsi = fals e $useAnsi = $input->getOption('ansi') ; // .. . } }

Slide 68

Slide 68 text

Linter: add Github annotations format for errors Contributed by ogizanagi in #38982 Console

Slide 69

Slide 69 text

Linter: add Github annotations format for errors Contributed by ogizanagi in #38982 Console

Slide 70

Slide 70 text

Linter: add Github annotations format for errors Contributed by ogizanagi in #38982 Console

Slide 71

Slide 71 text

Add a remove() method to the PHP con fi gurator Contributed by dunglas in #39806 DependencyInjection // config/services.ph p namespace Symfony\Component\DependencyInjection\Loader\Configurator ; use App\Service\FooService ; return function(ContainerConfigurator $configurator) { // .. . $services->remove(FooService::class) ; };

Slide 72

Slide 72 text

Add env() and EnvCon fi gurator in the PHP-DSL Contributed by fancyweb in #40682 DependencyInjection // config/services.ph p namespace Symfony\Component\DependencyInjection\Loader\Configurator ; use App\Service\FooService ; use App\Service\BarService ; return function(ContainerConfigurator $configurator) { // .. . $services->set(FooService::class ) ->arg('$myArg', '%env(default:my_param:key:path:url:MY_ENV_VAR)%' ) ; $services->set(BarService::class ) ->arg ( '$myArg' , env('MY_ENV_VAR' ) ->url( ) ->key('path' ) ->default('my_param' ) ) ; };

Slide 73

Slide 73 text

Negated (not:) env var processor Contributed by bpolaszek in #40169 DependencyInjection # .en v TRUE_ENV_VAR=tru e FALSE_ENV_VAR=false # config/services.yam l parameters : is_processed_as_false: ‘%env(not:TRUE_ENV_VAR)% ’ is_processed_as_true: ‘%env(not:FALSE_ENV_VAR)%’

Slide 74

Slide 74 text

New Form Handler Helper "renderForm" Contributed by lyrixx and nicolas-grekas in #41178 and #41190 FrameworkBundle use Symfony\Bundle\FrameworkBundle\Controller\AbstractController ; class TaskController extends AbstractControlle r { public function new(Request $request): Respons e { $form = $this->createForm(TaskType::class ) ->handleRequest($request ) ; $response = $this->render('task/new.html.twig', [ 'form' => $form->createView() , 'other_variable' => 'foo' , ]) ; if ($form->isSubmitted() && !$form->isValid()) { $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY) ; } return $response ; } }

Slide 75

Slide 75 text

New Form Handler Helper "renderForm" Contributed by lyrixx and nicolas-grekas in #41178 and #41190 FrameworkBundle use Symfony\Bundle\FrameworkBundle\Controller\AbstractController ; class TaskController extends AbstractControlle r { public function new(Request $request): Respons e { $form = $this->createForm(TaskType::class ) ->handleRequest($request ) ; return $this->renderForm('task/new.html.twig', [ 'form' => $form , 'other_variable' => 'foo' , ]) ; } }

Slide 76

Slide 76 text

Command cache:pool:clear warns and fails when one of the pools fails to clear Contributed by jderusse in #39910 FrameworkBundle Symfony <=5.2 Symfony >=5.3 Return code: 0 Return code: 1

Slide 77

Slide 77 text

Con fi gure Multiple Environments in a Single File Contributed by nicolas-grekas in #40214 and #40782 FrameworkBundle

Slide 78

Slide 78 text

Con fi gure Multiple Environments in a Single File Contributed by nicolas-grekas in #40214 and #40782 FrameworkBundle // config/packages/framework.ph l framework: secret: '%env(APP_SECRET)%' when@dev: services: App\SomeServiceForDev: ~ when@test: framework: test: true # ...

Slide 79

Slide 79 text

Con fi gure Multiple Environments in a Single File Contributed by nicolas-grekas in #40214 and #40782 FrameworkBundle // config/packages/framework.ph >

Slide 80

Slide 80 text

Con fi gure Multiple Environments in a Single File Contributed by nicolas-grekas in #40214 and #40782 FrameworkBundle # config/packages/framework.yam p namespace Symfony\Component\DependencyInjection\Loader\Configurator ; use App\SomeServiceForDev ; use Symfony\Config\FrameworkConfig ; return static function (FrameworkConfig $framework, ContainerConfigurator $container) { $framework->secret(env('APP_SECRET')) ; if ($container->env() === 'dev') { $container->services( ) ->set(SomeServiceForDev::class ) ; } if ($container->env() === 'test') { $framework->test(true) ; } // .. . };

Slide 81

Slide 81 text

Add SYMFONY_PHPUNIT_REQUIRE env variable Contributed by acasademont in #40059 PhpUnitBridge

Slide 82

Slide 82 text

Add SYMFONY_PHPUNIT_REQUIRE env variable Contributed by acasademont in #40059 PhpUnitBridge

Slide 83

Slide 83 text

Add SYMFONY_PHPUNIT_REQUIRE env variable Contributed by acasademont in #40059 PhpUnitBridge

Slide 84

Slide 84 text

class BooksTest extends WebTestCas e { public function testGetCollection(): voi d { static::createClient()->request('GET', '/books') ; $this->assertResponseFormatSame('jsonld') ; } } // Predefined formats see \Symfony\Component\HttpFoundation\Request::initializeFormats() add assertResponseFormatSame() Contributed by dunglas in #39666 FrameworkBundle

Slide 85

Slide 85 text

class FooTest extends WebTestCas e { public function testGetContainerA(): void { $kernel = self::bootKernel() ; $container = $kernel->getContainer() ; } public function testGetContainerB(): void { self::bootKernel() ; $container = self::$container; // @deprecated since Symfony 5.3, use static::getContainer() instea d } public function testGetContainerC(): void { $client = self::createClient() ; $container = $client->getContainer() ; } public function testGetContainerTheOnlyWayYouShouldUse(): void { $container = self::getContainer() ; } Add KernelTestCase::getContainer() Contributed by Nyholm in #40366 FrameworkBundle

Slide 86

Slide 86 text

There is even more 😱 https://symfony.com/blog/category/living-on-the-edge/5.3 https://github.com/symfony/symfony/blob/5.x/CHANGELOG-5.3.md https://github.com/symfony/symfony/blob/5.x/UPGRADE-5.3.md

Slide 87

Slide 87 text

New Components

Slide 88

Slide 88 text

Questions?

Slide 89

Slide 89 text

Thank you!