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

Symfony43.pdf

 Symfony43.pdf

B50a40d1c576ef0aa065cbc5d4d6dec9?s=128

Christian Flothmann

July 11, 2019
Tweet

Transcript

  1. New in Symfony 4.3 #sfugos, 11.07.2019

  2. Christian Flothmann christian.flothmann@sensiolabs.de @xabbuh @xabbuh

  3. Roadmap

  4. Roadmap

  5. Deprecations

  6. BrowserKit

  7. 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 { … }
  8. 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 { ... }
  9. 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 { ... }
  10. 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(); } }
  11. Cache

  12. Deprecated all PSR-16 adapters Contributed by Nicolas Grekas in #29236

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

    \Psr\SimpleCache\CacheInterface $psr16Cache */ // before $cache = new SimpleCacheAdapter($psr16Cache); // after $cache = new Psr16Adapter($psr16Cache);
  14. 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); } } }
  15. 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 }); } }
  16. 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'
  17. Dependency Injection

  18. 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'
  19. Dotenv

  20. 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);
  21. Event Dispatcher

  22. 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()
  23. 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()
  24. 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
  25. 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*/);
  26. Updated HttpKernel event classes Before After FilterControllerArgumentsEvent ControllerArgumentsEvent FilterControllerEvent ControllerEvent

    FilterResponseEvent ResponseEvent GetResponseEvent RequestEvent GetResponseForControllerResultEvent ViewEvent GetResponseForExceptionEvent ExceptionEvent PostResponseEvent TerminateEvent
  27. Form

  28. 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, ]);
  29. 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', ]);
  30. HttpFoundation

  31. 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);
  32. Security

  33. 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']; } }
  34. 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;
  35. 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();
  36. 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() );
  37. 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);
  38. 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
  39. Questions?

  40. New Components

  41. Mailer/Mime experimental

  42. Mailer Contributed by Fabien Potencier in #30741 use Symfony\Component\Mailer\MailerInterface; use

    Symfony\Component\Mime\Email; $email = (new Email()) ->from('hello@example.com') ->to('you@example.com') ->subject('Time for Symfony Mailer!') ->text('Sending emails is fun again!') ->html('<p>See Twig integration for HTML integration!</p>'); /** @var MailerInterface $mailer */ $mailer->send($email);
  43. HttpClient experimental

  44. 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', ...]
  45. New Features

  46. Dependency Injection

  47. 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
  48. EnvProcessor: trim Contributed by … in … # config/services.yaml parameters:

    private_key: '%env(trim:file:PRIVATE_KEY)%' Contributed by Maxime Steinhausser in #29781
  49. EnvProcessor: url und query_string Contributed by … in … #

    .env MONGODB_URL=“mongodb://user:password@127.0.0.1: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
  50. 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
  51. Form

  52. 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', ], ]);
  53. 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
  54. NumberType: String Input Contributed by Bernhard Schussek in #30893 use

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

    NumberType::class, [ 'input' => 'string', // default is "number" ]);
  56. 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]">
  57. 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."
  58. 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."
  59. TimezoneType: Intl Support Contributed by Roland Franssen in #31195 //

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

    after $builder->add('timezone', TimezoneType::class, [ 'input' => 'intltimezone', ]);
  62. Intl

  63. 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'
  64. 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'
  65. Messenger still experimental

  66. 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
  67. 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
  68. 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)%"
  69. 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
  70. 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:///'
  71. 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()); } }
  72. 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://...
  73. 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
  74. 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'
  75. 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
  76. 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.
  77. 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
  78. Testing

  79. 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()); } }
  80. 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'); } }
  81. 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(...)
  82. 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
  83. Translation

  84. 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
  85. Validator

  86. NotCompromisedPassword Contributed by Kévin Dunglas in #27738 use Symfony\Component\Validator\Constraints as

    Assert; class User { /** * @Assert\NotCompromisedPassword */ protected $rawPassword; }
  87. 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; }
  88. 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; }
  89. 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; }
  90. 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; }
  91. 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(...)
  92. 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
  93. 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
  94. 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\': []
  95. Workflow

  96. 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
  97. 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>
  98. Questions?

  99. Questions?

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