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

What's new in Symfony 4.3

What's new in Symfony 4.3

Talk at the Symfony User Group Hamburg

Jan Schädlich

July 02, 2019
Tweet

More Decks by Jan Schädlich

Other Decks in Programming

Transcript

  1. 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 { … }
  2. 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 { ... }
  3. 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 { ... }
  4. 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(); } }
  5. Deprecated all PSR-16 adapters Contributed by Nicolas Grekas in #29236

    // before $cache = new FilesystemCache(); // after $cache = new Psr16Cache(new FilesystemAdapter());
  6. Deprecated SimpleCacheAdapter Contributed by Nicolas Grekas in #29236 /** @var

    \Psr\SimpleCache\CacheInterface $psr16Cache */ // before $cache = new SimpleCacheAdapter($psr16Cache); // after $cache = new Psr16Adapter($psr16Cache);
  7. 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); } } }
  8. 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 }); } }
  9. 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'
  10. 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'
  11. 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);
  12. 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()
  13. 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()
  14. 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
  15. 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*/);
  16. 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); } }
  17. Updated HttpKernel event classes Before After FilterControllerArgumentsEvent ControllerArgumentsEvent FilterControllerEvent ControllerEvent

    FilterResponseEvent ResponseEvent GetResponseEvent RequestEvent GetResponseForControllerResultEvent ViewEvent GetResponseForExceptionEvent ExceptionEvent PostResponseEvent TerminateEvent
  18. 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, ]);
  19. 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', ]);
  20. 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);
  21. 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']; } }
  22. 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;
  23. 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();
  24. 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() );
  25. 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);
  26. 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
  27. 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
  28. EnvProcessor: trim Contributed by … in … # config/services.yaml parameters:

    private_key: '%env(trim:file:PRIVATE_KEY)%' Contributed by Maxime Steinhausser in #29781
  29. 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
  30. 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
  31. 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', ], ]);
  32. 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
  33. NumberType: String Input Contributed by Bernhard Schussek in #30893 use

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

    NumberType::class, [ 'input' => 'string', // default is "number" ]);
  35. NumberType: HTML5 Contributed by Christian Flothmann in #30267 // before:

    // <input type="text" name="form[number]"> // after: $builder->add('number', NumberType::class, [ 'html5' => true, ]); // <input type="number" name="form[number]">
  36. 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."
  37. 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."
  38. TimezoneType: Intl Support Contributed by Roland Franssen in #31195 //

    before: $builder->add('timezone', TimezoneType::class, [ 'input' => 'string', ]);
  39. 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
  40. TimezoneType: Intl Support Contributed by Roland Franssen in #31195 //

    after $builder->add('timezone', TimezoneType::class, [ 'input' => 'intltimezone', ]);
  41. 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'
  42. 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'
  43. 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
  44. 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
  45. 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)%"
  46. 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
  47. 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:///'
  48. 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()); } }
  49. 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://...
  50. 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
  51. 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'
  52. 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
  53. 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.
  54. 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
  55. 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 { ... }
  56. 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']; } }
  57. 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 } }
  58. 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()); } }
  59. 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'); } }
  60. 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(...)
  61. 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
  62. 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
  63. NotCompromisedPassword Contributed by Kévin Dunglas in #27738 use Symfony\Component\Validator\Constraints as

    Assert; class User { /** * @Assert\NotCompromisedPassword */ protected $rawPassword; }
  64. 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; }
  65. 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; }
  66. 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; }
  67. 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; }
  68. 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(...)
  69. 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
  70. 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
  71. 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\': []
  72. 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
  73. 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
  74. 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
  75. Added workflow_transition_blockers() Twig function Contributed by Grégoire Pineau in #30908

    <h2>Publication was blocked because:</h2> <ul> {% for blocker in workflow_transition_blockers(article, 'publish') %} <li> {{ blocker.message }} {# Display the guard expression #} {% if blocker.parameters.expression is defined %} <code>{{ blocker.parameters.expression }}</code> {% endif %} </li> {% endfor %} <ul>