Slide 1

Slide 1 text

hosted by

Slide 2

Slide 2 text

New in Symfony 4.3

Slide 3

Slide 3 text

Jan Schädlich [email protected] @jschaedl @janschaedlich

Slide 4

Slide 4 text

Looking for a Job? https://sensiolabs.de/jobs/96454

Slide 5

Slide 5 text

Agenda Deprecations New Features New Components

Slide 6

Slide 6 text

Roadmap

Slide 7

Slide 7 text

Roadmap

Slide 8

Slide 8 text

Deprecations

Slide 9

Slide 9 text

BrowserKit

Slide 10

Slide 10 text

Rename Client to Browser Contributed by Fabien Potencier in #30541 // Symfony\Component\BrowserKit\Client.php /** * @deprecated since Symfony 4.3, use AbstractBrowser instead. */ abstract class Client { … } // Symfony\Component\BrowserKit\AbstractBrowser.php abstract class AbstractBrowser extends Client { … }

Slide 11

Slide 11 text

Rename Client to Browser Contributed by Fabien Potencier in #30541 // Symfony\Component\HttpKernel/Client.php /** * @deprecated since Symfony 4.3, use HttpKernelBrowser instead. */ class Client extends AbstractBrowser { ... } // Symfony\Component\HttpKernel/Client.php class HttpKernelBrowser extends Client { ... }

Slide 12

Slide 12 text

Rename Client to Browser Contributed by Fabien Potencier in #30541 // Symfony\Bundle\FrameworkBundle/Client.php /** * @deprecated since Symfony 4.3, use KernelBrowser instead. */ class Client extends HttpKernelBrowser { ... } // Symfony\Bundle\FrameworkBundle/KernelBrowser.php class KernelBrowser extends Client { ... }

Slide 13

Slide 13 text

Rename Client to Browser Contributed by Fabien Potencier in #30541 use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class UserTest extends WebTestCase { public function testSomething() { // before: Symfony\Bundle\FrameworkBundle\Client // after: Symfony\Bundle\FrameworkBundle\KernelBrowser $browser = $this->createClient(); } }

Slide 14

Slide 14 text

Cache

Slide 15

Slide 15 text

Deprecated all PSR-16 adapters Contributed by Nicolas Grekas in #29236 // before $cache = new FilesystemCache(); // after $cache = new Psr16Cache(new FilesystemAdapter());

Slide 16

Slide 16 text

Deprecated SimpleCacheAdapter Contributed by Nicolas Grekas in #29236 /** @var \Psr\SimpleCache\CacheInterface $psr16Cache */ // before $cache = new SimpleCacheAdapter($psr16Cache); // after $cache = new Psr16Adapter($psr16Cache);

Slide 17

Slide 17 text

Deprecated the „Psr\SimpleCache\CacheInterface" and „cache.app.simple" service Contributed by Nicolas Grekas in #29236 // before use Psr\SimpleCache\CacheInterface; class FooService { private $cache; public function __construct(CacheInterface $cache) { $this->cache = $cache; } public function doFoo() { $value = $this->cache->get('foo'); if (null === $value) { // compute value here $this->cache->set('foo', $value); } } }

Slide 18

Slide 18 text

Deprecated the „Psr\SimpleCache\CacheInterface" and „cache.app.simple" service Contributed by Nicolas Grekas in #29236 // after use Symfony\Contracts\Cache\CacheInterface; class FooService { private $cache; public function __construct(CacheInterface $cache) { $this->cache = $cache; } public function doFoo() { $value = $this->cache->get('foo', function () { // compute value here }); } }

Slide 19

Slide 19 text

Deprecated the „Psr\SimpleCache\CacheInterface" and „cache.app.simple" service Contributed by Nicolas Grekas in #29236 services: app.simple_cache: class: Symfony\Component\Cache\Psr16Cache arguments: - '@cache.app' App\FooService: arguments: - ‘@app.simple_cache'

Slide 20

Slide 20 text

Dependency Injection

Slide 21

Slide 21 text

Deprecated support for non-string default env() parameters Contributed by Roland Franssen in #27808 # before parameters: env(NAME): 1.5 # after parameters: env(NAME): '1.5'

Slide 22

Slide 22 text

Dotenv

Slide 23

Slide 23 text

Deprecate default usage of “putenv()” Contributed by Tobias Nyholm in #31062 // before $dotenv = new Dotenv(); // after $dotenv = new Dotenv($usePutenv = true); // if you don't need to read env vars with getenv() $dotenv = new Dotenv($usePutenv = false);

Slide 24

Slide 24 text

Event Dispatcher

Slide 25

Slide 25 text

Simplier event dispatching Contributed by Nicolas Grekas in #28920 Changed signature of EventDispatcherInterface::dispatch() Subscribe to events using the events FQCN Optional Event name in EventDispatcherInterface::dispatch()

Slide 26

Slide 26 text

Simplier event dispatching $order = new Order(); $newOrderEvent = new OrderPlacedEvent($order); // before $dispatcher->dispatch(OrderEvents::NEW_ORDER, $newOrderEvent); // after $dispatcher->dispatch($newOrderEvent, OrderEvents::NEW_ORDER); Contributed by Nicolas Grekas in #28920 Changed signature of EventDispatcherInterface::dispatch()

Slide 27

Slide 27 text

Simplier event dispatching class StoreSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents() { // before return [ OrderEvents::NEW_ORDER => 'onStoreOrder', ]; // after return [ OrderPlacedEvent::class => 'onStoreOrder', ]; } } Contributed by Nicolas Grekas in #28920 Subscribe to events using the events FQCN

Slide 28

Slide 28 text

Simplier event dispatching Contributed by Nicolas Grekas in #28920 Optional Event name in EventDispatcherInterface::dispatch() // before $dispatcher->dispatch(OrderEvents::NEW_ORDER, $newOrderEvent); // after $dispatcher->dispatch($newOrderEvent); // Symfony\Contracts\EventDispatcher\EventDispatcherInterface.php public function dispatch($event/*, string $eventName = null*/);

Slide 29

Slide 29 text

Supporting both dispatchers use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; class CustomController { public function __invoke(EventDispatcherInterface $dispatcher) { $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); } public function someMethod() { $this->dispatcher->dispatch(OrderEvents::NEW_ORDER, $newOrderEvent); $this->dispatcher->dispatch($newOrderEvent, OrderEvents::NEW_ORDER); $this->dispatcher->dispatch($newOrderEvent); } }

Slide 30

Slide 30 text

Updated HttpKernel event classes Before After FilterControllerArgumentsEvent ControllerArgumentsEvent FilterControllerEvent ControllerEvent FilterResponseEvent ResponseEvent GetResponseEvent RequestEvent GetResponseForControllerResultEvent ViewEvent GetResponseForExceptionEvent ExceptionEvent PostResponseEvent TerminateEvent

Slide 31

Slide 31 text

Form

Slide 32

Slide 32 text

Deprecate custom formats with HTML5 widgets Contributed by Christian Flothmann in #28723 // before $builder->add('start_at', DateType::class, [ 'format' => 'dd.MM.yyyy', 'html5' => true, ]); // after $builder->add('start_at', DateType::class, [ 'html5' => true, ]); // or $builder->add('start_at', DateType::class, [ 'format' => 'dd.MM.yyyy', 'html5' => false, ]);

Slide 33

Slide 33 text

Deprecate some options for single_text widgets Contributed by Christian Flothmann in #28721 // before $builder->add('start_at', DateTimeType::class, [ 'widget' => 'single_text', 'date_format' => 'dd.MM.yyyy', 'date_widget' => 'single_text', 'time_widget' => 'choice', ]); // after $builder->add('start_at', DateTimeType::class, [ 'widget' => 'single_text', ]); // or $builder->add('start_at', DateTimeType::class, [ 'widget' => 'text', 'date_format' => 'dd.MM.yyyy', 'date_widget' => 'single_text', 'time_widget' => 'single_text', ]);

Slide 34

Slide 34 text

HttpFoundation

Slide 35

Slide 35 text

Deprecated the MIME type guessing classes Contributed by Fabien Potencier in #29896 // before use Symfony\Component\HttpFoundation\File\MimeType as HttpFoundation; $guesser = new HttpFoundation\FileinfoMimeTypeGuesser(); $mimeType = $guesser->guess($filename); $guesser = new HttpFoundation\MimeTypeExtensionGuesser(); $extension = $guesser->guess($filename); // after use Symfony\Component\Mime; $guesser = new Mime\FileinfoMimeTypeGuesser(); $mimeType = $guesser->guess($filename); $guesser = new Mime\MimeTypes(); $extensions = $guesser->getExtensions($filename);

Slide 36

Slide 36 text

Security

Slide 37

Slide 37 text

The Role and SwitchUserRole classes are deprecated Contributed by Christian Flothmann in #22048 class User implements UserInterface { // before public function getRoles(): array { return [new Role('ROLE_USER')]; } // after public function getRoles(): array { return ['ROLE_USER']; } }

Slide 38

Slide 38 text

The Role and SwitchUserRole classes are deprecated Contributed by Christian Flothmann in #22048 // before $isSwitched = false; foreach ($token->getUser()->getRoles() as $role) { if ($role instanceof SwitchUserRole) { $isSwitched = true; } } // after $isSwitched = $token instanceof SwitchUserToken;

Slide 39

Slide 39 text

The Role and SwitchUserRole classes are deprecated Contributed by Christian Flothmann in #22048 // before $roleNames = []; foreach ($token->getRoles() as $role) { $roleNames[] = $role->getRole(); } // after $roleNames = $token->getRoleNames();

Slide 40

Slide 40 text

The Role and SwitchUserRole classes are deprecated Contributed by Christian Flothmann in #22048 // before $reachableRoles = $roleHierarchy->getReachableRoles( $user->getRoles() ); // after $reachableRoles = $roleHierarchy->getReachableRoleNames( $user->getRoleNames() );

Slide 41

Slide 41 text

The Argon2iPasswordEncoder and BCryptPasswordEncoder class have been deprecated Contributed by Robin Chalas in #31019 and Nicolas Grekas in #31170 // before $encoder = new Argon2iPasswordEncoder(); $encoder->encodePassword('foo', null); $encoder = new BCryptPasswordEncoder(13); $encoder->encodePassword('bar', null); // after $encoder = new SodiumPasswordEncoder(); $encoder->encodePassword('foo', null); $encoder = new NativePasswordEncoder(); $encoder->encodePassword('bar', null);

Slide 42

Slide 42 text

Configuring encoders using argon2i or bcrypt as algorithm has been deprecated Contributed by Robin Chalas in #31019 and Nicolas Grekas in #31170 security: # before encoders: App\Entity\User: algorithm: bcrypt cost: 12 App\Security\User: argon2i # after encoders: App\Entity\User: auto App\Security\User: auto

Slide 43

Slide 43 text

Questions?

Slide 44

Slide 44 text

New Features

Slide 45

Slide 45 text

Dependency Injection

Slide 46

Slide 46 text

EnvProcessor: default # config/services.yaml parameters: # if PRIVATE_KEY is not a valid file path, # the content of raw_key is returned private_key: '%env(default:raw_key:file:PRIVATE_KEY)%' raw_key: '%env(PRIVATE_KEY)%' Contributed by Jérémy Derussé in #28976

Slide 47

Slide 47 text

EnvProcessor: trim Contributed by … in … # config/services.yaml parameters: private_key: '%env(trim:file:PRIVATE_KEY)%' Contributed by Maxime Steinhausser in #29781

Slide 48

Slide 48 text

EnvProcessor: url und query_string Contributed by … in … # .env MONGODB_URL=“mongodb://user:[email protected]:27017/db_name?timeout=3000” # config/packages/mongodb.yaml mongo_db_bundle: clients: default: hosts: - { host: '%env(key:host:url:MONGODB_URL)%', port: '%env(key:port:url:MONGODB_URL)%' } username: '%env(key:user:url:MONGODB_URL)%' password: '%env(key:pass:url:MONGODB_URL)%' connectTimeoutMS: '%env(int:key:timeout:query_string:MONGODB_URL)%' connections: default: database_name: '%env(key:path:url:MONGODB_URL)%' Contributed by ... in #... Contributed by Jérémy Derussé in #28976

Slide 49

Slide 49 text

EnvProcessor: require Contributed by … in … # config/packages/framework.yaml parameters: env(PHP_FILE): ‘../config/.runtime-evaluated.php' app: auth: '%env(require:PHP_FILE)%' Contributed by Matthias Pigulla in #30897

Slide 50

Slide 50 text

Form

Slide 51

Slide 51 text

Translation Improvements Contributed by Webnet team in #28635 $builder->add('accept_terms', CheckboxType::class, [ 'label' => 'Comment for the order of %company_name%’, 'label_translation_parameters' => [ '%company_name%' => 'Acme', ], 'help' => ‘Help text for the order of %company_name%’, 'help_translation_parameters' => [ '%company_name%' => 'Acme', ], 'attr_translation_parameters' => [ '%company_name%' => ‘Acme', ], ]);

Slide 52

Slide 52 text

Date Types: input_format use Symfony\Component\Form\Extension\Core\Type\DateTimeType; $builder->add('startsAt', DateTimeType::class, [ // ..., 'input' => 'string', 'input_format' => 'm-d H:i', ]); Contributed by Thomas Calvet in #29887

Slide 53

Slide 53 text

NumberType: String Input Contributed by Bernhard Schussek in #30893 use Doctrine\ORM\Mapping as ORM; /** * @\Entity */ class Product { /** * @ORM\Column(type="decimal") */ private $price; // ... }

Slide 54

Slide 54 text

NumberType: String Input Contributed by Bernhard Schussek in #30893 $builder->add('price', NumberType::class, [ 'input' => 'string', // default is "number" ]);

Slide 55

Slide 55 text

NumberType: HTML5 Contributed by Christian Flothmann in #30267 // before: // // after: $builder->add('number', NumberType::class, [ 'html5' => true, ]); //

Slide 56

Slide 56 text

Transformation Failure Messages Contributed by Maxime Steinhausser in #20978 // before class MoneyDataMapper implements DataMapperInterface { // ... public function mapFormsToData($forms, &$data) { $forms = iterator_to_array($forms); if (!is_numeric($forms['amount']->getData())) { throw new TransformationFailedException('Expected numeric value'); } } } // -> "This value is not valid."

Slide 57

Slide 57 text

Transformation Failure Messages Contributed by Maxime Steinhausser in #20978 // after class MoneyDataMapper implements DataMapperInterface { // ... public function mapFormsToData($forms, &$data) { $forms = iterator_to_array($forms); if (!is_numeric($forms['amount']->getData())) { $failure = new TransformationFailedException('Expected numeric value'); $failure->setInvalidMessage('Money amount should be numeric. {{ amount }} is invalid.', [ '{{ amount }}' => json_encode($forms['amount']->getData()) ]); throw $failure; } } } // -> "Money amount should be numeric. "foo" is invalid."

Slide 58

Slide 58 text

TimezoneType: Intl Support Contributed by Roland Franssen in #31195 // before: $builder->add('timezone', TimezoneType::class, [ 'input' => 'string', ]);

Slide 59

Slide 59 text

TimezoneType: Intl Support // before: $builder->get('timezone')->addModelTransformer(new CallbackTransformer( function ($value) { if ($value instanceof \IntlTimeZone) { return $value->getID(); } return null; }, function ($value) { if (null !== $value) { return \IntlTimeZone::createTimeZone($value); } return null; } )); Contributed by Roland Franssen in #31195

Slide 60

Slide 60 text

TimezoneType: Intl Support Contributed by Roland Franssen in #31195 // after $builder->add('timezone', TimezoneType::class, [ 'input' => 'intltimezone', ]);

Slide 61

Slide 61 text

Intl

Slide 62

Slide 62 text

Simplified API Contributed by Roland Franssen in #28846 // before use Symfony\Component\Intl\Intl; \Locale::setDefault('en'); $countries = Intl::getRegionBundle()->getCountryNames(); // => ['AF' => 'Afghanistan', ...] $country = Intl::getRegionBundle()->getCountryName('GB'); // => 'United Kingdom'

Slide 63

Slide 63 text

Simplified API Contributed by Roland Franssen in #28846 // after use Symfony\Component\Intl\Countries; $countries = Countries::getNames(); // => ['AF' => 'Afghanistan', ...] $country = Countries::getName('GB'); // => 'United Kingdom'

Slide 64

Slide 64 text

Messenger still experimental

Slide 65

Slide 65 text

Transport: Doctrine Transport Contributed by Vincent Touzet in #29007 # .env MESSENGER_TRANSPORT_DSN=doctrine://default # config/packages/messenger.yaml framework: messenger: transports: async_priority_high: "%env(MESSENGER_TRANSPORT_DSN)%?queue_name=high" async_normal: dsn: "%env(MESSENGER_TRANSPORT_DSN)%" options: queue_name: normal

Slide 66

Slide 66 text

Transport: Doctrine Transport Contributed by Vincent Touzet in #29007 Option Description Default table_name Name of the table messenger_messages queue_name Name of the queue (a column in the table, to use one table for multiple transports) default redeliver_timeout Timeout before retrying a message in seconds. 3600 auto_setup Create the db table when the transport is first used. TRUE

Slide 67

Slide 67 text

Transport: Redis Transport Contributed by Alexander Schranz in #30917 # .env MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages # config/packages/messenger.yaml framework: messenger: transports: async_priority_normal: "%env(MESSENGER_TRANSPORT_DSN)%"

Slide 68

Slide 68 text

Transport: Redis Transport Contributed by Alexander Schranz in #30917 Option Description Default Stream The Redis stream name messages Group The Redis consumer group name symfony Consumer Consumer name used in Redis consumer Serializer How to serialize the final payload in Redis (the Redis::OPT_SERIALIZER option) Redis::SERIALIZER_PHP

Slide 69

Slide 69 text

Transport: In Memory Transport Contributed by Gary PEGEOT and Samuel ROZE in #29097 # config/packages/test/messenger.yaml framework: messenger: transports: async_priority_normal: 'in-memory:///'

Slide 70

Slide 70 text

Transport: In Memory Transport Contributed by Gary PEGEOT and Samuel ROZE in #29097 class DefaultControllerTest extends WebTestCase { public function testSomething() { $client = static::createClient(); // ... $this->assertSame(200, $client->getResponse()->getStatusCode()); /* @var InMemoryTransport $transport */ $transport = self::$container->get('messenger.transport.async_priority_normal'); $this->assertCount(1, $transport->get()); } }

Slide 71

Slide 71 text

Transport: Sync Transport Contributed by Ryan Weaver in #30759 # config/packages/messenger.yaml framework: messenger: transports: async: '%env(MESSENGER_TRANSPORT_DSN)%' routing: 'App\Message\SmsNotification': async 'App\Message\OtherMessage': async # .env by default, handle this sync MESSENGER_TRANSPORT_DSN=sync:// # .env.local on production (or set this via real env vars) MESSENGER_TRANSPORT_DSN=amqp://...

Slide 72

Slide 72 text

Retry strategy Contributed by Ryan Weaver in #30557 # config/packages/messenger.yaml framework: messenger: transports: async_priority_high: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' retry_strategy: max_retries: 3 delay: 1000 # ms delay multiplier: 2 # delay multiplier before each retry max_delay: 0 # max ms a retry should ever be delayed # 0 => infinite # override all of this with a service that implements # Symfony\Component\Messenger\Retry\RetryStrategyInterface # service: null

Slide 73

Slide 73 text

Failure Transport support Contributed by ... in #... Contributed by Ryan Weaver in #30970 # config/packages/messenger.yaml framework: messenger: # after retrying, messages will be sent # to the "failed" transport failure_transport: failed transports: # ... other transports failed: 'doctrine://default?queue_name=failed'

Slide 74

Slide 74 text

Improved Commands Contributed by Vincent Touzet in #29476 $ php bin/console messenger:setup-transports [transport] Contributed by Ryan Weaver in #30754 $ php bin/console messenger:stop-workers $ bin/console messenger:consume amqp_high amqp_medium amqp_low Contributed by Ryan Weaver in #30708 New New

Slide 75

Slide 75 text

Native php serialize() support for transports Contributed by Ryan Weaver and Christian Flothmann in 29958 Messages are now serialized using PHP’s native serialize() & unserialize() functions. Symfony Serializer not the default serializer anymore.

Slide 76

Slide 76 text

Making the serializer configurable by transport Contributed by Ryan Weaver in #30628 # config/packages/messenger.yaml framework: messenger: serializer: default_serializer: messenger.transport.symfony_serializer symfony_serializer: format: json context: { } transports: async_priority_normal: dsn: # ... serializer: messenger.transport.symfony_serializer

Slide 77

Slide 77 text

WorkerMessageEvents Contributed by Ryan Weaver in #30557 namespace Symfony\Component\Messenger\Event; class WorkerMessageFailedEvent extends AbstractWorkerMessageEvent { public function getThrowable(): \Throwable; public function willRetry(): bool; } class WorkerMessageHandledEvent extends AbstractWorkerMessageEvent { ... } class WorkerMessageReceivedEvent extends AbstractWorkerMessageEvent { ... }

Slide 78

Slide 78 text

SendMessageToTransportsEvent Contributed by Ryan Weaver in #30650 class MessageSendToTransportListener implements EventSubscriberInterface { public function onSendMessage(SendMessageToTransportsEvent $event) { $envelope = $event->getEnvelope(); if (!$envelope->getMessage() instanceof SomeMailerMessage) { return; } $envelope->with(new AmpqRoutingKeyStamp('mailer-route')) $event->setEnvelope($envelope); } public static function getSubscribedEvents() { return [SendMessageToTransportsEvent::class => 'onSendMessage']; } }

Slide 79

Slide 79 text

WorkerStoppedEvent Contributed by Robin Chalas in #31282 use Symfony\Component\Messenger\Event\WorkerStoppedEvent; class WorkerStoppedListener implements EventSubscriberInterface { public static function getSubscribedEvents() { return [WorkerStoppedEvent::class => 'onWorkerStopped',]; } public function onWorkerStopped(WorkerStoppedEvent $workerStoppedEvent) { // e.g. do some cleanup or other business tasks } }

Slide 80

Slide 80 text

Testing

Slide 81

Slide 81 text

New PHPUnit assertions for the WebTestCase Contributed by Fabien Potencier and Alex Rock in #30813 // before use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class DefaultControllerTest extends WebTestCase { public function testSomething() { $client = static::createClient(); $crawler = $client->request('GET', '/test'); $this->assertSame(200, $client->getResponse()->getStatusCode()); $this->assertContains('Hello World', $crawler->filter('h1')->text()); } }

Slide 82

Slide 82 text

New PHPUnit assertions for the WebTestCase Contributed by Fabien Potencier and Alex Rock in #30813 // after use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class DefaultControllerTest extends WebTestCase { public function testSomething() { $client = static::createClient(); $crawler = $client->request('GET', '/test'); $this->assertResponseIsSuccessful(); $this->assertSelectorTextContains('h1', 'Hello World'); } }

Slide 83

Slide 83 text

New PHPUnit assertions for the WebTestCase Contributed by Fabien Potencier and Alex Rock in #30813 $this->assertResponseIsSuccessful(...) $this->assertResponseStatusCodeSame(...) $this->assertResponseRedirects(...) $this->assertResponseHasHeader(...) $this->assertResponseNotHasHeader(...) $this->assertResponseHeaderSame(...) $this->assertResponseHeaderNotSame(...) $this->assertResponseHasCookie(...) $this->assertResponseNotHasCookie(...) $this->assertResponseCookieValueSame(...) $this->assertSelectorExists(...) $this->assertSelectorNotExists(...) $this->assertSelectorTextContains(...) $this->assertSelectorTextSame(...) $this->assertSelectorTextNotContains(...) $this->assertPageTitleSame(...) $this->assertPageTitleContains(...) $this->assertInputValueSame(...) $this->assertInputValueNotSame(...) $this->assertRequestAttributeValueSame(...) $this->assertRouteSame(...)

Slide 84

Slide 84 text

DomCrawler Improvement Contributed by Titouan Galopin in #29306 The DomCrawler can make use of the Masterminds/html5-php library to parse HTML5. $ composer req masterminds/html5:^2.6

Slide 85

Slide 85 text

Translation

Slide 86

Slide 86 text

Extracting Translation Content from PHP Files Contributed by Yonel Ceruto in #30120 # updates the german translation with missing strings $ php bin/console translation:update --dump-messages --force de The translation update command now also extracts translation contents from PHP files. ControllerArguments ServiceArguments ServiceMethodCalls ServiceSubscriber

Slide 87

Slide 87 text

Validator

Slide 88

Slide 88 text

NotCompromisedPassword Contributed by Kévin Dunglas in #27738 use Symfony\Component\Validator\Constraints as Assert; class User { /** * @Assert\NotCompromisedPassword */ protected $rawPassword; }

Slide 89

Slide 89 text

Json Constraint Contributed by Imad Zairig in #28477 use Symfony\Component\Validator\Constraints as Assert; class Book { /** * @Assert\Json(message = "This is not valid JSON") */ protected $chapters; }

Slide 90

Slide 90 text

Unique Constraint Contributed by Yevgeniy Zholkevskiy in #26555 use Symfony\Component\Validator\Constraints as Assert; class Person { /** * @Assert\Unique( * message="The {{ value }} email is repeated." * ) */ protected $contactEmails; }

Slide 91

Slide 91 text

Number Contraints Contributed by Jan Schädlich in #28637 use Symfony\Component\Validator\Constraints as Assert; class Settings { /** * @Assert\Positive * @Assert\PositiveOrZero * @Assert\Negative * @Assert\NegativeOrZero */ protected $value; }

Slide 92

Slide 92 text

TimeZone Contraints Contributed by Javier Spagnoletti and Hugo Hamon in #30900 use Symfony\Component\Validator\Constraints as Assert; class UserSettings { /** * @Assert\Timezone * @Assert\Timezone(zone=\DateTimeZone::AMERICA) * @Assert\Timezone(zone=\DateTimeZone::PER_COUNTRY, countryCode="CN") */ protected $timezone; }

Slide 93

Slide 93 text

Automatic Validation Contributed by Kévin Dunglas in #27735 This new feature introspects your doctrine mappings and adds the corresponding validation automatically. Doctrine mapping Automatic validation constraint nullable=false @Assert\NotNull type=... @Assert\Type(...) unique=true @UniqueEntity length=... @Assert\Length(...)

Slide 94

Slide 94 text

Automatic Validation /** @ORM\Entity */ class SomeEntity { /** @ORM\Column(length=4) */ public $pinCode; } $entity = new SomeEntity(); $entity->setPinCode('1234567890'); $violationList = $validator->validate($entity); var_dump((string) $violationList); // Value too long. It should have 4 chars or less. Contributed by Kévin Dunglas in #27735

Slide 95

Slide 95 text

Automatic Validation Contributed by Kévin Dunglas in #27735 PropertyInfo Component is required Will be disabled by default Comes out of the box for Form component

Slide 96

Slide 96 text

Automatic Validation Contributed by Kévin Dunglas in #27735 # config/framwork.yaml framework: property_info: { enabled: true } validation: auto_mapping: 'App\Entity\Core\': ['User', 'Group'] 'App\Entity\Customer': ['Customer', 'Address'] 'App\Entity\Payment\': []

Slide 97

Slide 97 text

Workflow

Slide 98

Slide 98 text

Added a context to Workflow::apply() Contributed by Grégoire Pineau in #29146 $workflow->apply($article, ‘published', [ 'time' => date('y-m-d H:i:s'), ]); It’s now possible to pass a custom context when applying a transition. class Article { - public function setMarking($marking) + public function setMarking($marking, $context = []) framework: workflows: article: type: workflow marking_store: - type: multiple_state + type: method

Slide 99

Slide 99 text

use Symfony\Component\Workflow\Event\TransitionEvent; class TransitionEventSubscriber implements EventSubscriberInterface { public function onWorkflowArticleTransition(TransitionEvent $event) { $context = $event->getContext(); // add some context, e.g. $context['user'] = $user->getUsername(); $event->setContext($context); } public static function getSubscribedEvents() { return [TransitionEvent::class => 'onWorkflowArticleTransition']; } } Allow to modify the context in a listener Contributed by Grégoire Pineau in #30902

Slide 100

Slide 100 text

Allow to configure many initial places Contributed by Grégoire Pineau in #30468 and #30890 When using a Workflow, it’s possible to have a subject in many places. And now this also works for initial places. # Before workflows: article: type: workflow initial_place: draft places: [draft, in_progress, published] # After workflows: article: type: workflow initial_marking: [draft] places: [draft, in_progress, published] Deprecated

Slide 101

Slide 101 text

Added workflow_transition_blockers() Twig function Contributed by Grégoire Pineau in #30908

Publication was blocked because:

    {% for blocker in workflow_transition_blockers(article, 'publish') %}
  • {{ blocker.message }} {# Display the guard expression #} {% if blocker.parameters.expression is defined %} {{ blocker.parameters.expression }} {% endif %}
  • {% endfor %}

Slide 102

Slide 102 text

Questions?

Slide 103

Slide 103 text

New Components

Slide 104

Slide 104 text

Mailer Component https://www.meetup.com/de-DE/sfughh/events/qmlmcfyzlbjb/ experimental

Slide 105

Slide 105 text

HttpClient Component https://www.meetup.com/de-DE/sfughh/events/qmlmcfyzlbjb/ experimental

Slide 106

Slide 106 text

Questions?

Slide 107

Slide 107 text

Thank you! https://speakerdeck.com/jschaedl