Symfony blog hosted last week a series of blog post about Symfony 5.2 new features including :

  • Form testing asserts
  • Controller argument attributes 
  • Form mapping callbacks
  • Uid serialization and validation 
  • Twig helpers to get field variables
  • Retryable HTTP client

We covered the last two features with the announcement of Symfony 5.2 beta1, and we cover briefly new features here. But make sure to check Symfony blog for more detailed blog post about new Symfony 5.2 features.

Form testing asserts

Testing is an essential part of Symfony applications. That’s why we promote testing in the docs and provide specific testing utilities like the PhpUnit bridge. In Symfony 5.2 we’ve improved our list of custom test asserts with new asserts for the Symfony Forms.

In previous Symfony versions, testing Symfony forms required you to deal with the form view variables and do things like this:

$view = $this->factory->create(TestedType::class, $formData)->createView();

$this->assertArrayHasKey('custom_var', $view->vars);
$this->assertSame('expected value', $view->vars['custom_var']);

Now you can use the assertFormValue() and assertCheckboxChecked() methods to check form values without dealing with low-level details like the view variables:

namespace App\Tests\Controller;

use App\Entity\Post;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class SomeTest extends WebTestCase
{
    public function testIndex(): void
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/some-page');

        $client->submitForm('Save', [
            'activateMembership' => 'on',
            'trialPeriod' => '7',
        ]);

        $this->assertFormValue('#form', 'trialPeriod', '7');
        $this->assertCheckboxChecked('activateMembership');
    }
}

Controller argument attributes 

One of the great features coming in PHP8 is the annotations, or attributes. Symfony 5.2 already includes attributes to define routes and required dependencies, but we continued adding attributes support where it makes sense.

That’s why in Symfony 5.2 you can also use PHP attributes for controller arguments. Thanks to this new feature, a #[CurrentUser] attribute is introduced to turn a controller argument into the object that represents the currently logged user:

// src/Controller/SomeController.php
namespace App\Controller;

use App\Entity\MyUser;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Security\Http\Attribute\CurrentUser;

class SomeController extends AbstractController
{
    public function index(#[CurrentUser] MyUser $user)
    {
        // ...
    }
}

In practice, this works by adding a new method to the ArgumentMetadata object passed to the argument value resolvers. If you define your own resolvers, you can now use the getAttribute() method, which returns the attribute that was set on the argument (or null if none was set).

Form mapping callbacks

Sometimes, the objects handled with Symfony forms don’t define the expected getter/setter methods (e.g. getName() and setName()) but other methods better aligned with the application needs (e.g. getName() and rename()).

In those cases, you can use a form data mapper to move the object data into the form fields and the other way around. In Symfony 5.2 it was improved by allowing the use of callback functions to get/set form fields. You only need to define the new getter or setter options (or both) and Symfony will run that callback to get/set the value from/into the object/array:

namespace App\Form\Type;

use App\Entity\Person;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;

class PersonType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class, [
                'getter' => function (Person $person, FormInterface $form): string {
                    return $person->getUserData()->getFirstName();
                },
                'setter' => function (Person &$person, ?string $name, FormInterface $form): void {
                    $person->rename($name);
                },
            ])

            // ...
        ;
    }

    // ...
}

This new feature means that you no longer need to create a data mapper to solve this problem. However, you still need to use data mappers in certain situations (when several form fields are mapped to a single method, when the mapping of the model depends on the submitted form data, etc.)

Notice that Symfony 5.2 will be released in November 2020 and its support will finish by July 2021. More information at https://symfony.com/