Slide 1

Slide 1 text

New in Symfony 4.3 #sfugos, 11.07.2019

Slide 2

Slide 2 text

Christian Flothmann christian.fl[email protected] @xabbuh @xabbuh

Slide 3

Slide 3 text

Roadmap

Slide 4

Slide 4 text

Roadmap

Slide 5

Slide 5 text

Deprecations

Slide 6

Slide 6 text

BrowserKit

Slide 7

Slide 7 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 8

Slide 8 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 9

Slide 9 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 10

Slide 10 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 11

Slide 11 text

Cache

Slide 12

Slide 12 text

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

Slide 13

Slide 13 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 14

Slide 14 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 15

Slide 15 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 16

Slide 16 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 17

Slide 17 text

Dependency Injection

Slide 18

Slide 18 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): 'float:1.5'

Slide 19

Slide 19 text

Dotenv

Slide 20

Slide 20 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 21

Slide 21 text

Event Dispatcher

Slide 22

Slide 22 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 23

Slide 23 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 24

Slide 24 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 25

Slide 25 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 26

Slide 26 text

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

Slide 27

Slide 27 text

Form

Slide 28

Slide 28 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 29

Slide 29 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 30

Slide 30 text

HttpFoundation

Slide 31

Slide 31 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 32

Slide 32 text

Security

Slide 33

Slide 33 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 34

Slide 34 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 35

Slide 35 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 36

Slide 36 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 37

Slide 37 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 38

Slide 38 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 39

Slide 39 text

Questions?

Slide 40

Slide 40 text

New Components

Slide 41

Slide 41 text

Mailer/Mime experimental

Slide 42

Slide 42 text

Mailer Contributed by Fabien Potencier in #30741 use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Email; $email = (new Email()) ->from('[email protected]') ->to('[email protected]') ->subject('Time for Symfony Mailer!') ->text('Sending emails is fun again!') ->html('

See Twig integration for HTML integration!

'); /** @var MailerInterface $mailer */ $mailer->send($email);

Slide 43

Slide 43 text

HttpClient experimental

Slide 44

Slide 44 text

HttpClient Contributed by Nicolas Grekas in #30413 use Symfony\Component\HttpClient\HttpClient; $url = 'https://api.github.com/repos/symfony/symfony-docs'; $httpClient = HttpClient::create(); $response = $httpClient->request('GET', $url); $statusCode = $response->getStatusCode(); // $statusCode = 200 $contentType = $response->getHeaders()['content-type'][0]; // $contentType = 'application/json' $content = $response->getContent(); // $content = '{"id":521583, "name":"symfony-docs", ...}' $content = $response->toArray(); // $content = ['id' => 521583, 'name' => 'symfony-docs', ...]

Slide 45

Slide 45 text

New Features

Slide 46

Slide 46 text

Dependency Injection

Slide 47

Slide 47 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 48

Slide 48 text

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

Slide 49

Slide 49 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 50

Slide 50 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 51

Slide 51 text

Form

Slide 52

Slide 52 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 53

Slide 53 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 54

Slide 54 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 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 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 58

Slide 58 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 59

Slide 59 text

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

Slide 60

Slide 60 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 61

Slide 61 text

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

Slide 62

Slide 62 text

Intl

Slide 63

Slide 63 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 64

Slide 64 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 65

Slide 65 text

Messenger still experimental

Slide 66

Slide 66 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 67

Slide 67 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. WAHR

Slide 68

Slide 68 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 69

Slide 69 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 70

Slide 70 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 71

Slide 71 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 72

Slide 72 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 73

Slide 73 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 74

Slide 74 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 75

Slide 75 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 76

Slide 76 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 77

Slide 77 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 78

Slide 78 text

Testing

Slide 79

Slide 79 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 80

Slide 80 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 81

Slide 81 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 82

Slide 82 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 83

Slide 83 text

Translation

Slide 84

Slide 84 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 85

Slide 85 text

Validator

Slide 86

Slide 86 text

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

Slide 87

Slide 87 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 88

Slide 88 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 89

Slide 89 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 90

Slide 90 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 91

Slide 91 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 92

Slide 92 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 93

Slide 93 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 94

Slide 94 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 95

Slide 95 text

Workflow

Slide 96

Slide 96 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 97

Slide 97 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 98

Slide 98 text

Questions?

Slide 99

Slide 99 text

Questions?

Slide 100

Slide 100 text

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