Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony3 Form component trying to pass null to a type hinted method in PHP 7

In my entity class I have defined all the expected argument types for the setters and return types of the getters. Later, when I have a form which uses the said class, I get an error if some of the fields in the form is empty because the form component tries to pass null to the setter instead of string.

I get the following exception when I submit the form:

Expected argument of type "string", "NULL" given

500 Internal Server Error - InvalidArgumentException

The exception is thrown from vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php at line 254

Is there a way to convert the "null" value to empty string before passing it to the object, and let the validator argue about it?

like image 336
Angelov Avatar asked Jun 01 '16 19:06

Angelov


2 Answers

If an Entity Property cannot be null (and you use PHP 7.1+), then also the application of the nullable return type declaration sounds more like a dirty and fast workaround to maintain a direct data binding between Entities and Forms (using the Symfony Form Component).

A better global approach (in my opinion) is to decouple the Form data binding from your Entities using a DTO (Data Transfer Object), that is a simple POPO (Plain Old PHP Object) to contain your form data.

Using a DTO will allow you to maintain a strict type hinting in your Entities (no loss of data consistency) and will decouple Form data binding (but also data validation) from your Entities.

DTO's allows reusability and have many other advantages.

Some useful references about the use of DTO's with Symfony Forms:

  • Avoiding Entities in Forms (by Iltar van der Berg)
  • Rethinking Form Development (by Iltar van der Berg)
  • Symfony Forms 101 (by Bernhard Schussek the creator of the Form component)
  • Don't Use Entities in Symfony Forms. Use Custom Data Objects Instead
  • Entities should always be valid
like image 148
gp_sflover Avatar answered Nov 09 '22 17:11

gp_sflover


I see two options here:

Quick and Dirty - make the argument passed to the setter optional:

public function setTitle(String $title = null)
{
    $this->title = $title;
    return $this;
}

Probably better - use a data transformer in the FormType:

Data transformers allow you to modify the data before it gets used.

   $builder
    // ...
        ->add('title', 'text')
    // ...
    ;

    $builder->get('title')->addModelTransformer(new CallbackTransformer(
        function($originalInput){
            return $string;
        },
        function($submittedValue){ 
            // When null is cast to a string, it will be empty.
            return (string) $submittedValue;
        }
    ));

I've posted another answer before using this to retrieve method to retrieve an Entity object before. See that if it helps to see a more complicated example.

like image 5
HPierce Avatar answered Nov 09 '22 17:11

HPierce