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

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.

Marco Petersen

May 18, 2021
Tweet

More Decks by Marco Petersen

Other Decks in Programming

Transcript

  1. Rename master request to main request HttpKernelInterface::MASTER_REQUEST -> HttpKernelInterface::MAIN_REQUEST KernelEvent::isMasterRequest()

    -> KernelEvent::isMainRequest() RequestStack::getMasterRequest() -> RequestStack::getMainRequest() HttpFoundation|HttpKernel
  2. 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
  3. 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
  4. 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
  5. 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
  6. #[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
  7. 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
  8. #[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
  9. 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
  10. #[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
  11. #[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
  12. Autocon fi gure services via Attributes services: _defaults: autoconfigure: true

    # Automatically registers your services # as commands, event subscribers, etc. Symfony 5.2
  13. 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']
  14. 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') ; } }
  15. 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') ; } }
  16. #[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, ) { } }
  17. #[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(); }
  18. Use tagged services use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; final class LoaderIterator { public

    function __construct( #[TaggedIterator('app.loader')] private iterable $loaders, ) { } } Symfony 5.3
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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'); } }); } }
  24. 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 #}
  25. 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
  26. 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)%'
  27. (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; }
  28. (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;
  29. Export concatenated translations {{ ('aa' ~ 'bb') | trans }}

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

    []) }} TwigBridge {{ stimulus_controller('product-show', { product: product|serialize('json', { groups: 'product:read'}) }) }}
  31. 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.
  32. 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') ; // .. . } }
  33. 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) ; };
  34. 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' ) ) ; };
  35. 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)%’
  36. 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 ; } }
  37. 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' , ]) ; } }
  38. 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
  39. Con fi gure Multiple Environments in a Single File Contributed

    by nicolas-grekas in #40214 and #40782 FrameworkBundle
  40. 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 -- >
  41. 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>
  42. 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) ; } // .. . };
  43. 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>
  44. 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>
  45. 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>
  46. 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
  47. 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