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

What's new in Symfony 5.2

What's new in Symfony 5.2

Malte Schlüter

November 17, 2020
Tweet

More Decks by Malte Schlüter

Other Decks in Programming

Transcript

  1. Deprecations from 5.1 to 5.2 Code will be removed in

    Symfony 6.0 Symfony 5.2 https://github.com/symfony/symfony/blob/5.x/UPGRADE-5.2.md
  2. Deprecated setPrivate() use Symfony\Component\DependencyInjection\Alias ; use Symfony\Component\DependencyInjection\Definition ; /** @var

    Definition $definition * / $definition->setPublic(true) ; $definition->setPrivate(false) ; /** @var Alias $alias * / $alias->setPublic(true) ; $alias->setPrivate(false) ; DependencyInjection
  3. Deprecated public services to private FrameworkBundle validator form.factory form.type.file translator

    security.csrf.token_manager serializer cache_clearer filesystem use Symfony\Component\DependencyInjection\ContainerInterface ; class SomeServic e { private ContainerInterface $container ; public function doSomething(): voi d { $translator = $this->container->get('translator') ; } } // Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead .
  4. Deprecated public services to private TwigBundle twig use Symfony\Component\DependencyInjection\ContainerInterface ;

    class SomeServic e { private Environment $container ; public function doSomething(): voi d { $twig = $this->container->get('twig') ; } } // Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead .
  5. Deprecated the allowEmptyString option Validator use Symfony\Component\Validator\Constraints as Assert ;

    // Befor e /* * * @Assert\Length(min=5, allowEmptyString=true ) * / // Afte r /* * * @Assert\AtLeastOneOf( { * @Assert\Blank() , * @Assert\Length(min=5 ) * } ) * /
  6. Changed return type Noti f i er namespace Symfony\Component\Notifier\Transport ;

    use Symfony\Component\Notifier\Message\MessageInterface ; use Symfony\Component\Notifier\Message\SentMessage ; /* * * @experimental in 5. 1 * / interface TransportInterfac e { // Befor e public function send(MessageInterface $message): void ; // Afte r public function send(MessageInterface $message): ?SentMessage; }
  7. Changed return type Noti f i er namespace Symfony\Component\Notifier\Transport ;

    use Symfony\Component\Notifier\Message\MessageInterface ; use Symfony\Component\Notifier\Message\SentMessage ; /* * * @experimental in 5. 1 * / abstract class AbstractTransport implements TransportInterfac e { // Befor e abstract protected function doSend(MessageInterface $message): void ; // Afte r abstract protected function doSend(MessageInterface $message): SentMessage ; // ... }
  8. Changed type-hint Noti f i er namespace Symfony\Component\Notifier\Notification ; use

    Symfony\Component\Notifier\Message\EmailMessage ; use Symfony\Component\Notifier\Recipient\EmailRecipientInterface ; use Symfony\Component\Notifier\Recipient\Recipient ; /* * * @experimental in 5. 1 * / interface EmailNotificationInterfac e { // Befor e public function asEmailMessage(Recipient $recipient, string $transport = null): ?EmailMessage ; // Afte r public function asEmailMessage(EmailRecipientInterface $recipient, string $transport = null): ?EmailMessage ; }
  9. Changed type-hint Noti f i er namespace Symfony\Component\Notifier\Notification ; use

    Symfony\Component\Notifier\Message\SmsMessage ; use Symfony\Component\Notifier\Recipient\Recipient ; use Symfony\Component\Notifier\Recipient\SmsRecipientInterface ; /* * * @experimental in 5. 1 * / interface SmsNotificationInterfac e { // Befor e public function asSmsMessage(Recipient $recipient, string $transport = null): ?SmsMessage ; // Afte r public function asSmsMessage(SmsRecipientInterface $recipient, string $transport = null): ?SmsMessage ; }
  10. Changed type-hint Noti f i er namespace Symfony\Component\Notifier ; use

    Symfony\Component\Notifier\Notification\Notification ; use Symfony\Component\Notifier\Recipient\Recipient ; use Symfony\Component\Notifier\Recipient\RecipientInterface ; /* * * @experimental in 5. 1 * / interface NotifierInterfac e { // Befor e public function send(Notification $notification, Recipient ...$recipients): void ; // Afte r public function send(Notification $notification, RecipientInterface ...$recipients): void ; }
  11. Changed type-hint Noti f i er namespace Symfony\Component\Notifier\Notification ; use

    Symfony\Component\Notifier\Recipient\Recipient ; use Symfony\Component\Notifier\Recipient\RecipientInterface ; /* * * @experimental in 5. 1 * / class Notificatio n { // Befor e public function getChannels(Recipient $recipient): array ; // Afte r public function getChannels(RecipientInterface $recipient): array ; }
  12. Changed type-hint Noti f i er namespace Symfony\Component\Notifier\Channel ; use

    Symfony\Component\Notifier\Notification\Notification ; use Symfony\Component\Notifier\Recipient\Recipient ; use Symfony\Component\Notifier\Recipient\RecipientInterface ; /* * * @experimental in 5. 1 * / interface ChannelInterfac e { // Before public function notify(Notification $notification, Recipient $recipient, string $transportName = null): void ; public function supports(Notification $notification, Recipient $recipient): bool ; // After public function notify(Notification $notification, RecipientInterface $recipient, string $transportName = null): void ; public function supports(Notification $notification, RecipientInterface $recipient): bool ; }
  13. Updated Security System Security // Remove d \Symfony\Component\Security\Http\Firewall\AccessListener::PUBLIC_ACCES S //

    Use instea d \Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter::PUBLIC_ACCES S https://symfony.com/blog/new-in-symfony-5-1-updated-security-system In the experimental authenticator-based system, * TokenInterface::getUser( ) returns null in case of unauthenticated session.
  14. True colors in the console Contributed by fabpot in #36802

    // using a predefined styl e $output->writeln('<info>... contents ...</>') ; // custom style using basic color s $output->writeln('<fg=green;bg=blue>... contents …</>') ; // custom style using true color s $output->writeln('<fg=#00ff00;bg=#00f>... contents ...</>') ; // the third optional argument defines the style s $color = new Color('#000', '#fff', ['underscore', 'reverse']) ; echo $color->apply('... contents ...') ; Console
  15. Doctrine types for UUID and ULID Contributed by gennadigennadigennadi in

    #37678 /* * * @ORM\Entit y * / class Produc t { /* * * @ORM\Column(type="uuid" ) * / private $someProperty ; /* * * @ORM\Column(type="uuid_binary" ) * / private $anotherProperty ; } Doctrine /* * * @ORM\Entit y * / class Produc t { /* * * @ORM\Column(type="ulid" ) * / private $someProperty ; /* * * @ORM\Column(type="ulid_binary" ) * / private $anotherProperty ; }
  16. Doctrine types for UUID and ULID Contributed by gennadigennadigennadi in

    #37678 // there are generators for UUID V1 and V6 to o use Symfony\Bridge\Doctrine\IdGenerator\UuidV4Generator; /* * * @ORM\Entit y * / class Produc t { /* * * @ORM\I d * @ORM\Column(type="uuid", unique=true ) * @ORM\GeneratedValue(strategy="CUSTOM" ) * @ORM\CustomIdGenerator(class=UuidV4Generator::class ) * / private $id ; } Doctrine
  17. Doctrine types for UUID and ULID Contributed by gennadigennadigennadi in

    #37678 use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator; /* * * @ORM\Entit y * / class Produc t { /* * * @ORM\I d * @ORM\Column(type="ulid", unique=true ) * @ORM\GeneratedValue(strategy="CUSTOM" ) * @ORM\CustomIdGenerator(class=UlidGenerator::class ) * / private $id ; } Doctrine
  18. Uid normalizer Contributed by guillbdx and norkunas in #36406 and

    #38151 /* * * @ORM\Entit y * / class Produc t { /* * * @ORM\Column(type="uuid" ) * / private $id ; } Serializer $product = new Product() ; $jsonContent = $serializer->serialize($product, 'json') ; // $jsonContent contains {"id":"9b7541de-6f87-11ea-ab3c-9da9a81562fc","...":"..." }
  19. Ulid validation Contributed by laurent35240 in #38322 /* * *

    @ORM\Entit y * / class Produc t { /* * * @ORM\Column(type="ulid" ) * @Assert\Uli d * / private $id ; } Validator
  20. Translatable objects Contributed by natewiebe13 in #37670 // Befor e

    return $this->render('...', [ 'order_status' => [ 'message' => 'order.status_message' , 'params' => ['%status%' => $order->getStatus(), '%order_id%' => $order->getId()] , 'domain' => 'admin' , ] , ]); Translation {# Before # } {{ order_status.message|trans(order_status.params, order_status.domain) }}
  21. Translatable objects Contributed by natewiebe13 in #37670 // Afte r

    use Symfony\Component\Translation\TranslatableMessage; return $this->render('...', [ 'order_status' => new TranslatableMessage ( 'order.status_message' , ['%status%' => $order->getStatus(), '%order_id%' => $order->getId()] , 'admin ' ) , ]); Translation {# After # } {{ order_status|trans }}
  22. Translatable objects Contributed by natewiebe13 in #37670 {# Before #}

    {{ message is defined ? message|trans : fallback|trans({'%param%': value}) }} {# After #} {{ message|default(t(fallback, {'%param%': value}))|trans }} Translation
  23. Simpler DataCollectors Contributed by lvo in #37332 # config/services.yam l

    services : App\DataCollector\MyCustomDataCollector : tags : - name: data_collecto r template: 'data_collector/template.html.twig ' id: 'app.my_custom_collector ' use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface ; class MyCustomDataCollector implements DataCollectorInterfac e { // ... } WebPro f i lerBundle
  24. Simpler DataCollectors Contributed by lvo in #37332 use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector ;

    class MyCustomDataCollector extends AbstractDataCollecto r { public function collect(Request $request, Response $response, \Throwable $exception = null): voi d { $this->data = '...' ; } public static function getTemplate(): ?strin g { return 'data_collector/template.html.twig' ; } public function getName(): strin g { return 'app.my_custom_collector' ; } } WebPro f i lerBundle
  25. New integrations Noti f i er • Infobip SMS in

    PR #36480 provided by Jérémy Romey • Google Chat in PR #36488 provided by Jérôme Tamarelle • Esendex SMS in PR #36573 provided by Olivier Dolbeau • Zulip Chat in PR #36616 provided by Mohammad Emran Hasan • Mobyt SMS in PR #36648 provided by Bastien Durand • SMSAPI SMS in PR #36940 provided by Marcin Szepczynski • LinkedIn in PR #37830 provided by Smaine Milianni • Sendinblue SMS in PR #38298 provided by Pierre Tondereau • Discord Chat in PR #38522 provided by Karoly Gossler and Mathieu Piot • Improved Telegram Chat to allow de f i ne some options in PR #36496 provided by Mihail Krasilnikov
  26. DKIM email authentication Contributed by fabpot in #37165 use Symfony\Component\Mime\Crypto\DkimSigner

    ; use Symfony\Component\Mime\Email ; $email = (new Email() ) ->from('[email protected]' ) // .. . ->html('...') ; $signer = new DkimSigner('file:///path/to/private-key.key', 'example.com', 'sf') ; $signedEmail = $signer->sign($email) ; /** @var /Symfony\Component\Mailer\MailerInterface $mailer * / $mailer->send($signedEmail); Mailer
  27. Retryable HTTP client Contributed by fabpot in #37165 framework :

    http_client : retry_failed : # only retry errors with these HTTP code s http_codes: [429, 500 ] max_retries: 2 # waiting time between retries (in milliseconds ) delay: 100 0 # if set, the waiting time of each retry increases by this facto r # (e.g. first retry: 1000ms; second retry: 3 * 1000ms; etc. ) multiplier: 3 HttpClient framework : http_client : retry_failed: true # 423, 425, 429, 500, 502, 503, 504, 507, 510
  28. Form mapping callbacks Contributed by yonelceruto in #37968 class PersonType

    extends AbstractTyp e { public function buildForm(FormBuilderInterface $builder, array $options ) { $builde r ->add('name', TextType::class, [ 'getter' => function (Person $person, FormInterface $form): string { return $person->getUserData()->getFirstName() ; } , 'setter' => function (Person &$person, ?string $name, FormInterface $form): void { $person->rename($name) ; } , ] ) ; } } Form
  29. Form testing asserts Contributed by mnapoli in #38287 and #38288

    // Befor e $view = $this->factory->create(TestedType::class, $formData)->createView() ; $this->assertArrayHasKey('custom_var', $view->vars) ; $this->assertSame('expected value', $view->vars[‚custom_var‘]) ; // Afte r class SomeTest extends WebTestCas e { public function testIndex(): voi d { $client = static::createClient() ; $client->request('GET', '/some-page') ; $client->submitForm('Save', [ 'activateMembership' => 'on' , 'trialPeriod' => '7' , ]) ; self::assertFormValue('#form', 'trialPeriod', '7') ; self::assertCheckboxChecked('activateMembership') ; } } PHPUnit Bridge
  30. Shared locks Contributed by jderusse in #37752 use Symfony\Component\Lock\LockFactory ;

    use Symfony\Component\Lock\Store\InMemoryStore ; $factory = new LockFactory(new InMemoryStore()) ; $lock1 = $factory->createLock('test') ; $lock2 = $factory->createLock('test') ; $lock1->acquire(); // tru e $lock2->acquire(); // fals e $lock1->release() ; $lock2->acquire(); // true Lock
  31. Shared locks Contributed by jderusse in #37752 use Symfony\Component\Lock\LockFactory ;

    use Symfony\Component\Lock\Store\InMemoryStore ; $factory = new LockFactory(new InMemoryStore()) ; $lock1 = $factory->createLock('test') ; $lock2 = $factory->createLock('test') ; $lock3 = $factory->createLock('test') ; $lock1->acquireRead(); // tru e $lock2->acquireRead(); // tru e $lock3->acquire(); // false Lock
  32. Shared locks Contributed by jderusse in #37752 use Symfony\Component\Lock\LockFactory ;

    use Symfony\Component\Lock\Store\InMemoryStore ; $factory = new LockFactory(new InMemoryStore()) ; $lock1 = $factory->createLock('test') ; $lock2 = $factory->createLock('test') ; $lock3 = $factory->createLock('test') ; $lock1->acquire(); // tru e $lock2->acquire(); // fals e $lock3->acquireRead(); // false Lock
  33. Shared locks Contributed by jderusse in #37752 Lock use Symfony\Component\Lock\LockFactory

    ; use Symfony\Component\Lock\Store\SemaphoreStore ; $factory = new LockFactory(new SemaphoreStore()) ; $lock1 = $factory->createLock('test') ; $lock2 = $factory->createLock('test') ; $lock3 = $factory->createLock('test') ; $lock1->acquireRead(); // tru e $lock2->acquireRead(); // fals e $lock3->acquire(); // false
  34. Front controller con f i guration Contributed by nicolas-grekas in

    #37351 FrameworkBundle // public/index.ph p use App\CacheKernel ; use App\Kernel ; // .. . $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']) ; if ('prod' === $kernel->getEnvironment()) { $kernel = new CacheKernel($kernel) ; } $request = Request::createFromGlobals() ; // ...
  35. Front controller con f i guration Contributed by nicolas-grekas in

    #37351 FrameworkBundle # config/packages/framework.yam l framework : # use the HTTP Cache default s http_cache: tru e # configure every HTTP Cache optio n http_cache : private_headers: ['Authorization', 'Cookie', 'MyCustomHeader' ] default_ttl: 360 0 allow_revalidate: tru e stale_if_error: 600
  36. Front controller con f i guration Contributed by nicolas-grekas in

    #37357 FrameworkBundle // public/index.ph p // .. . if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) { Request::setTrustedProxies ( explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOS T ) ; } if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) { Request::setTrustedHosts([$trustedHosts]) ; } $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']) ; // ...
  37. Front controller con f i guration Contributed by nicolas-grekas in

    #37357 FrameworkBundle # config/packages/framework.yam l framework: # configure proxies to trust directly in the config file : trusted_proxies: '127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 ' # or use an env var if this value is dynami c trusted_proxies: '%env(TRUSTED_PROXIES)% ' # you can also define the trusted header s trusted_headers: ['x-forwarded-all', '!x-forwarded-host', '!x-forwarded-prefix']
  38. Login links Contributed by weaverryan in #38177 Security security :

    enable_authenticator_manager: true firewalls: main: login_link : check_route: 'magic_link_verify ' signature_properties: [id, password, email ] lifetime: 600 # optional (default=600 ) max_uses: 3 # optional (default=null ) used_link_cache: cache.app # Cache service id used if max_uses is set # Generated URL looks something like this : # https://127.0.0.1:9033/login/verify? [email protected]&expires=1601342578&hash=YzE1ZDJlYjM3YTMyMjgwZDdkYzg2ZjFlMjZhN2E5ZWRmM zk3NjAxNjRjYThiMjMzNmIxYzAzYzQ4NmQ2Zjk4NA%3D%3D
  39. class MagicLinkLoginController extends AbstractControlle r { /* * * @Route("/login",

    name="magic_link_login" ) * / public function requestMagicLink(Request $request, LoginLinkHandlerInterface $loginLinkHandler, UserRepository $userRepository, AuthenticationUtils $authenticationUtils ) { if ($request->isMethod('POST')) { $email = $request->request->get('email') ; $user = $userRepository->findOneBy(['email' => $email]) ; if ($user) { $magicLink = $loginLinkHandler->createLoginLink($user) ; // TODO: in a real app, use notifier to email thi s dump($magicLink->getUrl()) ; } return $this->redirectToRoute('magic_link_check_email') ; } return $this->render('magic_link/login.html.twig', [ 'error' => $authenticationUtils->getLastAuthenticationError( ) ]) ; } /* * * @Route("/login/check-email", name="magic_link_check_email" ) * / public function magicLinkCheckEmail( ) { return $this->render('magic_link/check_email.html.twig') ; } /* * * @Route("/login/verify", name="magic_link_verify" ) * / public function checkMagicLink( ) { throw new \Exception('will be handled by authenticator') ; Security
  40. Login Throttling Contributed by wouterj in #38204 Security security :

    firewalls : default : # by default, the feature allows 5 login attempts per minut e login_throttling: ~ # configuring the maximum login attempts (per minute ) login_throttling : max_attempts: 1 # you can even use a custom rate limiter via its service I D login_throttling : limiter: app.my_login_rate_limite r
  41. PHP 8 attributes Contributed by derrabus in #37474 and #37545

    PHP 8 // Befor e use Symfony\Component\Routing\Annotation\Route ; class SomeControlle r { /* * * @Route("/path", name="action" ) * / public function someAction( ) { // .. . } } // Afte r use Symfony\Component\Routing\Annotation\Route ; class SomeControlle r { #[Route(‚/path', name: 'action')] public function someAction( ) { // .. . } }
  42. PHP 8 attributes Contributed by derrabus in #37474 and #37545

    PHP 8 use Symfony\Contracts\Service\Attribute\Required ; class SomeServic e { /** @Required */ #[Required ] public Bar $bar ; /** @Required */ #[Required ] public function setFoo(Foo $foo): voi d { // .. . } }
  43. Controller argument attributes Contributed by jvasseur in #37829 PHP 8

    use App\Entity\MyUser ; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController ; use Symfony\Component\Security\Http\Attribute\CurrentUser ; class SomeController extends AbstractControlle r { public function index(#[CurrentUser] MyUser $user) { // .. . } }
  44. Constraints as PHP attributes Contributed by derrabus in #38309 and

    #38499 PHP 8 // Befor e use Symfony\Component\Validator\Constraints as Assert ; class Autho r { /* * * @Assert\Choice ( * choices = { "fiction", "non-fiction" } , * message = "Choose a valid genre. " * ) * / private $genre ; } // Afte r use Symfony\Component\Validator\Constraints as Assert ; class Autho r { #[Assert\Choice ( choices: ['fiction', 'non-fiction'] , message: 'Choose a valid genre.' , )] private $genre ; } Join the discussion for an alternative of nested attributes in #38503.