$30 off During Our Annual Pro Sale. View Details »

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. hosted by

    View Slide

  2. New in Symfony 4.3

    View Slide

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

    View Slide

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

    View Slide

  5. Agenda
    Deprecations

    New Features

    New Components

    View Slide

  6. Roadmap

    View Slide

  7. Roadmap

    View Slide

  8. Deprecations

    View Slide

  9. BrowserKit

    View Slide

  10. 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 { … }

    View Slide

  11. 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 { ... }

    View Slide

  12. 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 { ... }

    View Slide

  13. 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();
    }
    }

    View Slide

  14. Cache

    View Slide

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

    View Slide

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

    View Slide

  17. 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);
    }
    }
    }

    View Slide

  18. 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
    });
    }
    }

    View Slide

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

    View Slide

  20. Dependency Injection

    View Slide

  21. 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'

    View Slide

  22. Dotenv

    View Slide

  23. 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);

    View Slide

  24. Event Dispatcher

    View Slide

  25. 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()

    View Slide

  26. 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()

    View Slide

  27. 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

    View Slide

  28. 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*/);

    View Slide

  29. 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);
    }
    }

    View Slide

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

    View Slide

  31. Form

    View Slide

  32. 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,
    ]);

    View Slide

  33. 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',
    ]);

    View Slide

  34. HttpFoundation

    View Slide

  35. 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);

    View Slide

  36. Security

    View Slide

  37. 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'];
    }
    }

    View Slide

  38. 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;

    View Slide

  39. 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();

    View Slide

  40. 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()
    );

    View Slide

  41. 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);

    View Slide

  42. 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

    View Slide

  43. Questions?

    View Slide

  44. New Features

    View Slide

  45. Dependency Injection

    View Slide

  46. 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

    View Slide

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

    View Slide

  48. 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

    View Slide

  49. 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

    View Slide

  50. Form

    View Slide

  51. 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',
    ],
    ]);

    View Slide

  52. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  56. 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."

    View Slide

  57. 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."

    View Slide

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

    View Slide

  59. 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

    View Slide

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

    View Slide

  61. Intl

    View Slide

  62. 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'

    View Slide

  63. 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'

    View Slide

  64. Messenger
    still experimental

    View Slide

  65. 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

    View Slide

  66. 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

    View Slide

  67. 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)%"

    View Slide

  68. 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

    View Slide

  69. 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:///'

    View Slide

  70. 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());
    }
    }

    View Slide

  71. 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://...

    View Slide

  72. 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

    View Slide

  73. 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'

    View Slide

  74. 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

    View Slide

  75. 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.

    View Slide

  76. 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

    View Slide

  77. 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 { ... }

    View Slide

  78. 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'];
    }
    }

    View Slide

  79. 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
    }
    }

    View Slide

  80. Testing

    View Slide

  81. 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());
    }
    }

    View Slide

  82. 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');
    }
    }

    View Slide

  83. 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(...)

    View Slide

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

    View Slide

  85. Translation

    View Slide

  86. 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

    View Slide

  87. Validator

    View Slide

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

    View Slide

  89. 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;
    }

    View Slide

  90. 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;
    }

    View Slide

  91. 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;
    }

    View Slide

  92. 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;
    }

    View Slide

  93. 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(...)

    View Slide

  94. 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

    View Slide

  95. 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

    View Slide

  96. 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\': []

    View Slide

  97. Workflow

    View Slide

  98. 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

    View Slide

  99. 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

    View Slide

  100. 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

    View Slide

  101. 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 %}

    View Slide

  102. Questions?

    View Slide

  103. New Components

    View Slide

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

    View Slide

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

    View Slide

  106. Questions?

    View Slide

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

    View Slide