-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Form] Finally document Data Mappers #6900
Conversation
cdc959c
to
1da6c48
Compare
form/data_mappers.rst
Outdated
reference. It recieves the form fields in a :phpclass:`RecursiveIteratorIterator` | ||
and the view data. | ||
|
||
When an errors occurs, a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo; When an error
occurs
single: Form; Data mappers | ||
|
||
How to Use Data Mappers | ||
======================= |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
form/data_mappers.rst
Outdated
the form. They are responsible for mapping the data to the fields and back. The | ||
built-in data mapper uses the :doc:`PropertyAccess component </components/property_access>` | ||
and will fit most cases. However, you can create your own data mapper for the | ||
other cases, for instance when dealing with immutable objects. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, you can create your own data mapper that could, for example, pass data to immutable objects via their constructor.
form/data_mappers.rst
Outdated
|
||
* **Data transformers** change the representation of a value. E.g. from | ||
``"2016-08-12"`` to a ``DateTime`` instance; | ||
* **Data mappers** map data (e.g. an object) to form fields. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... to form fields, and vice versa.
Nice, short explanation between the two
form/data_mappers.rst
Outdated
|
||
Changing a ``YYYY-mm-dd`` string value to a ``DateTime`` instance is done by a | ||
data transformer. Mapping this ``DateTime`` instance as value of a property on | ||
the entity is done by a data mapper. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mapping this ``DateTime`` instance to a property on your object (e.g. by calling a setter or some other method) is done by a data mapper.
form/data_mappers.rst
Outdated
Creating a Data Mapper | ||
---------------------- | ||
|
||
Assume you're saving a set of colors in the database. For this, you're using an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose that you want to save a set of colors to the database.
form/data_mappers.rst
Outdated
} | ||
|
||
The form type should be allowed to edit a color. As the color object is | ||
immutable, a new color object has to be created each time one of the values is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the color -> But because you've decided to make the Color
object immutable, a new...
form/data_mappers.rst
Outdated
$green = $forms['green']->getData(); | ||
$blue = $forms['blue']->getData(); | ||
|
||
$data = new Color($red, $green, $blue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
woh! I pass by reference so rarely, that I'm honestly shocked that you can simply override the $data
variable and this works! If so (I'm sure it does - Bernhard's blog post uses this same method), maybe we should add a quick comment:
// Overriding $data is enough, since it is passed by reference.
Status: Reviewed This is wonderful! I left minor comments, but this is ready to go :) 👍 |
|
||
The data passed to the mapper is *not yet validated*. This means that your | ||
objects should allow being created in an invalid state in order to produce | ||
user-friendly errors in the form. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to explain what you have to do when your object's constructor raises exceptions in case some assertions are not fulfilled (@webmozart didn't address that concern in his blog post neither and that's the reason I am not sure if we should really cover it here or rather advise to use DTOs instead).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also why scalar type hints in PHP7 cause some troubles. The form maps submitted data before validating its underlying data.
What we have to do in such case, is to validate data before mapping it ourselves :( I think he talked about it in his last conferences (suggestion to use custom data mappers for PHP 7).
Not sure what's the best way to approach this problem though, a "caution" note might be enough after all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the underlying fields use proper form types (like an IntegerType
for an object property typehinted int
in the constructor), the field itself will throw a TransformationFailedException
which will be rendered as an error on the view on this particular field.
Important part is that the field won't be mapped back and (IIRC) keeps original value. Thus, in most cases, I think it won't cause any issue with scalar typehints.
See ogizanagi/symfony@4f76c21 and FormTypeTest.php#L745-L752
(it doesn't use a scalar typehint, but a \DomainException
is thrown if Money::amount
isn't numeric. The case is similar).
Appart from the above point, catching the error and throwing a TranformationFailedException
ourself is a good safe guard (I recommend to use the invalid_message
option in order to render a less cryptic error message, but that's limited).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ogizanagi Your test does not cover what I'm talking about. When clear missing is true or if you submit an empty string, the returned model data will be null and the call of the setter will end with a php error because of the type hint which does not trigger a casting of null even if strict_type is false although it does cast a string to an int.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@HeahDude: Right, it won't cover everything anyway. However, regarding what you're talking about, maybe it would be interesting to make the transformers use the required
option (or a new one if necessary for BC) in order to disallow the transformation and throw a TransformationFailedException('A number is required')
in case the value is an empty string.
@HeahDude Would be nice to get your feedback on this too. |
{ | ||
$resolver->setDefaults(array( | ||
// when creating a new color, the initial data should be null | ||
'empty_data' => null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the default unless data_class
is set or if some data prepopulates the form
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Afaics, the default it ''
and not null
. I think I might added this because of that
form/data_mappers.rst
Outdated
} | ||
} | ||
|
||
All data mappers have to implement :class:`Symfony\\Component\\Form\\DataMapperInterface`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should maybe introduce the interface before showing an implementation
You can also implement ``DataMapperInterface`` in the ``ColorType`` and add | ||
the ``mapDataToForms()`` and ``mapFormsToData()`` in the form type directly | ||
to avoid creating a new class. You'll then have to call | ||
``$builder->setDataMapper($this)``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example should do it like that since this does not makes sense to reuse it elsewhere, and the note could then say that you can handle it in a specific class to easily share it between types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started doing this, but I rejected it. If we directly implement it like that, the structure of the article gets less clear (now it's create DTO, create mapper, use mapper).
@@ -13,9 +13,15 @@ to render the form, and then back into a ``DateTime`` object on submit. | |||
|
|||
.. caution:: | |||
|
|||
When a form field has the ``inherit_data`` option set, Data Transformers | |||
When a form field has the ``inherit_data`` option set, data transformers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure about that one?
form/data_mappers.rst
Outdated
use Symfony\Component\Form\DataMapperInterface; | ||
use Symfony\Component\Form\Exception\UnexpectedTypeException; | ||
|
||
class ColorMapper extends DataMapperInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implements
Awesome that finally some work has begun to fix this! What the new section still does not explain is how More specifically, does "data mapping" occur at a single point during the event flow? Could we add a note there saying that "at this point, a DataMapper (link) is used to ..."? And with regard to data transformers, is the |
Skimming the code at https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Form.php#L307, it looks as if the following happens:
And then at https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Form.php#L496:
|
@wouterj Is this PR dead ? |
For the record, here's a use case https://github.com/EnMarche/en-marche.fr/pull/1324/files#diff-8a3dda9de49dd1624e55e7e7d409853cR17. I could have used a model transformer to handle Doing nothing won't work either as the component expects the type norm data to be an array, an object or null for a compound form, so it throws an exception on initializing data when trying to map the scalar data to the child. This cannot be changed in my case since I want to dynamically add this nested form. Note that the form is compound by default anyway when extending AbstractType which has FormType as parent defining it. TextType originally simply extends it and override the There still are valid cases for using compound data (array and objects) with non compound types as long as we use a model transformer. For instance, a model date can be a text or a timestamp while the date form is compound when using select inputs or many text inputs, it then depends on a model transformer to normalize it to a date time object, a view transformer is also needed so the object is converting so a string for a single input or an array of sting that will be mapped to children choice or text fields). Hope it helps while waiting for an improved docs. |
I'd love for this PR to be resurrected. I've been using Symfony since it was released and only learned about data mappers yesterday. |
1da6c48
to
bc61395
Compare
bc61395
to
ba0526a
Compare
Sorry for ignoring this PR for that long. If I'm correct, I fixed all comments in this article. Let's do a final review + merge @symfony/team-symfony-docs |
I think I would still add a warning that any exception being thrown in the constructor will not be handled or needs some extra code. |
Hey @wouterj do you need someone to take this over to finish it? I'm willing to help but I'm not sure that I understood the limitation that needs to be explained. |
This PR was squashed before being merged into the 3.4 branch (closes #10756). Discussion ---------- [Forms] Added data-mapper docs #SymfonyConHackday2018 Improved version of #6900 Thank you @wouterj for a great job and the initial text! Thanx @HeahDude for helping me with this PR. Commits ------- b1cb1c0 [Forms] Added data-mapper docs
It's probably the biggest undocumented feature in Symfony for years. Today, I was sad I had to tell yet another beginner "there is this awesome feature, but it's undocumented...". Let's finally document this great feature!
/fixes #5459
/ping @webmozart I would like your feedback on this article. The difference between transformers and mappers is hard to understand and we have to describe it pitch perfect to avoid confusion.