Save 37% off PRO during our Black Friday Sale! »

What's New in Symfony 5.3

What's New in Symfony 5.3

Symfony 5.3 is the last minor release before the LTS and the next major, which means it's the last chance to introduce new experimental features and deprecate things to be removed in Symfony 6. If you want to be prepared for the update at the end of the month, join our speakers Marco, Jan & Malte on a grand tour of what they consider to be the biggest changes.

3dd28ad260d202a12c2e93fc28fad5d7?s=128

Marco Petersen

May 18, 2021
Tweet

Transcript

  1. What’s new in Symfony 5.3

  2. Malte Schlüter malte.schlueter@qossmic.com @malteschlueter marco.petersen@qossmic.com @ocrampete16 Marco Petersen jan.schadlich@siemens.de @jschaedl

    Jan Schädlich
  3. Agenda

  4. Agenda Deprecations

  5. Agenda Deprecations Improvements

  6. None
  7. Deprecations Symfony 5.3 from 5.2 to 5.3 Code will be

    removed in Symfony 6.0
  8. Rename master request to main request HttpKernelInterface::MASTER_REQUEST -> HttpKernelInterface::MAIN_REQUEST KernelEvent::isMasterRequest()

    -> KernelEvent::isMainRequest() RequestStack::getMasterRequest() -> RequestStack::getMainRequest() HttpFoundation|HttpKernel
  9. 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
  10. 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
  11. Removed dependency on symfony/intl composer req symfony/intl Form LocaleType CountryType

    CurrencyType LanguageType TimezoneType
  12. 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
  13. https://github.com/symfony/symfony/blob/master/UPGRADE-5.3.md

  14. Improvements

  15. More PHP 8 Attributes

  16. 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
  17. #[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
  18. 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
  19. #[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
  20. 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
  21. #[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
  22. #[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
  23. Autocon fi gure services via Attributes services: _defaults: autoconfigure: true

    # Automatically registers your services # as commands, event subscribers, etc. Symfony 5.2
  24. 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']
  25. 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') ; } }
  26. 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') ; } }
  27. #[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(); }
  28. #[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, ) { } }
  29. #[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(); }
  30. Use tagged services use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; final class LoaderIterator { public

    function __construct( #[TaggedIterator('app.loader')] private iterable $loaders, ) { } } Symfony 5.3
  31. 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
  32. 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
  33. 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
  34. 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
  35. Add support for sorting fi elds Form

  36. 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'); } }); } }
  37. 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'); } }); } } <input name="form[first_name]"> <input name="form[last_name]"> <button type="submit" name="form[register]" value="register">Register</button> {# wrong place #} <input name="form[company]"> {# wrong place #}
  38. 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
  39. Noti fi er Component Symfony 5.3

  40. Noti fi er Component Symfony 5.3 not experimental anymore

  41. New Noti fi er integrations Noti fi er Texter Chatter

  42. 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)%'
  43. (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; }
  44. (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;
  45. New Tailwind CSS form theme TwigBridge # twig.yaml twig: form_themes:

    - tailwind_2_layout.html.twig
  46. Export concatenated translations {{ ('aa' ~ 'bb') | trans }}

    TwigBridge php bin/console translation:update --dump-messages de
  47. Twig serialize fi lter {{ object|serialize(format = 'json', context =

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

    []) }} TwigBridge {{ stimulus_controller('product-show', { product: product|serialize('json', { groups: 'product:read'}) }) }}
  49. PHP Con fi guration Builder Con fi g

  50. PHP Con fi guration Builder Con fi g

  51. Removing Directories without Race Conditions Filesystem

  52. No-Lock Rate Limiters RateLimiter

  53. A Quick Security Recap

  54. 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.
  55. Mandatory Passport Badges Security

  56. bin/console debug: fi rewall Security

  57. bin/console debug: fi rewall Security

  58. Generate Login Link with User Locale Security

  59. Mask CSRF Token against BREACH Security

  60. UID Component (experimental) 🎉

  61. New UID Factory Methods UID

  62. Generate UIDs via CLI UID

  63. Generate UIDs via CLI UID

  64. UUID & ULID Form Types Form / UID

  65. New UID Normalization Formats Serializer / UID

  66. Support binary / negatable options Contributed by jderusse in #39642

    Console class SomeCommand extends Comman }
  67. 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') ; // .. . } }
  68. Linter: add Github annotations format for errors Contributed by ogizanagi

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

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

    in #38982 Console
  71. 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) ; };
  72. 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' ) ) ; };
  73. 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)%’
  74. 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 ; } }
  75. 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' , ]) ; } }
  76. 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
  77. Con fi gure Multiple Environments in a Single File Contributed

    by nicolas-grekas in #40214 and #40782 FrameworkBundle
  78. 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 # ... <!-- config/packages/framework.xml -- >
  79. Con fi gure Multiple Environments in a Single File Contributed

    by nicolas-grekas in #40214 and #40782 FrameworkBundle // config/packages/framework.ph > <?xml version="1.0" encoding="UTF-8" ? > <container xmlns="..." > <framework:config secret="%env(APP_SECRET)%" / > <when env="dev" > <services > <service id="App\SomeServiceForDev" / > </services > </when > <when env="test" > <framework:config test="true" / > </when > </container>
  80. 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) ; } // .. . };
  81. Add SYMFONY_PHPUNIT_REQUIRE env variable Contributed by acasademont in #40059 PhpUnitBridge

    <phpunit > <php > <!-- ... -- > <server name="SYMFONY_PHPUNIT_REQUIRE" value="phpspec/prophecy-phpunit" / > </php > <!-- ... -- > </phpunit>
  82. Add SYMFONY_PHPUNIT_REQUIRE env variable Contributed by acasademont in #40059 PhpUnitBridge

    <phpunit > <php > <!-- ... -- > <server name="SYMFONY_PHPUNIT_REQUIRE" value="phpspec/prophecy-phpunit:^1.0" / > </php > <!-- ... -- > </phpunit>
  83. Add SYMFONY_PHPUNIT_REQUIRE env variable Contributed by acasademont in #40059 PhpUnitBridge

    <phpunit > <php > <!-- ... -- > <server name="SYMFONY_PHPUNIT_REQUIRE" value="phpspec/prophecy-phpunit phpunit/phpunit-selenium" / > </php > <!-- ... -- > </phpunit>
  84. 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
  85. 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
  86. 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

  87. New Components

  88. Questions?

  89. Thank you!