Chapters
41 Chapters
|
4:45:40
|
Login to bookmark this video
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.1.3
Subscribe to download the code!Compatible PHP versions: ^7.1.3
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
33.
Form Model Classes (DTOs)
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Subscribe to jump to this part in the video!
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/annotations": "^1.0", // 1.10.2
"doctrine/doctrine-bundle": "^1.6.10", // 1.10.2
"doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
"doctrine/orm": "^2.5.11", // v2.7.2
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
"knplabs/knp-time-bundle": "^1.8", // 1.8.0
"nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.0
"sensio/framework-extra-bundle": "^5.1", // v5.2.1
"stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
"symfony/asset": "^4.0", // v4.1.6
"symfony/cache": "^3.3|^4.0", // v4.1.6
"symfony/console": "^4.0", // v4.1.6
"symfony/flex": "^1.0", // v1.21.6
"symfony/form": "^4.0", // v4.1.6
"symfony/framework-bundle": "^4.0", // v4.1.6
"symfony/property-access": "^3.3|^4.0", // v4.1.6
"symfony/property-info": "^3.3|^4.0", // v4.1.6
"symfony/security-bundle": "^4.0", // v4.1.6
"symfony/serializer": "^3.3|^4.0", // v4.1.6
"symfony/twig-bundle": "^4.0", // v4.1.6
"symfony/validator": "^4.0", // v4.1.6
"symfony/web-server-bundle": "^4.0", // v4.1.6
"symfony/yaml": "^4.0", // v4.1.6
"twig/extensions": "^1.5" // v1.5.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
"easycorp/easy-log-handler": "^1.0.2", // v1.0.7
"fzaninotto/faker": "^1.7", // v1.8.0
"symfony/debug-bundle": "^3.3|^4.0", // v4.1.6
"symfony/dotenv": "^4.0", // v4.1.6
"symfony/maker-bundle": "^1.0", // v1.8.0
"symfony/monolog-bundle": "^3.0", // v3.3.0
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.6
"symfony/stopwatch": "^3.3|^4.0", // v4.1.6
"symfony/var-dumper": "^3.3|^4.0", // v4.1.6
"symfony/web-profiler-bundle": "^3.3|^4.0" // v4.1.6
}
}
26 Comments
Hi! Do you habe an idea why I get the following error on SF 5.4? I think I did it like you suggest but something has to be wrong. :(
Could not load type "App\Form\Model\MySpecialFormModel": class does not implement "Symfony\Component\Form\FormTypeInterface".I think it happens here, but you do the same?!
$form = $this->createForm(MySpecialFormModel::class);Damn, please delete this comment and my comment above...I just failed with copy & paste, sorry!
Haha, do not worry man :)
I use this DTO approach. There is one thing that does not work for me when I use typed properties (PHP 7.4) for the public properties of these DTO.
EXAMPLE:
`class ProductDto
{
}`
This generally seems to work quite well - in case the user submits the form with a blank title or description, the validation kicks in and the form is displayed with validation warnings.
BUT THERE IS A PROBLEM when data is added while creating a form (e.g. the edit form):
$productDto = new ProductDto();<br />$productDto->title = 'Foo';<br />$form = $this->createForm(ProductFormType::class, $productDto);Initially the form is displayed as expected with "Foo" as the value for the "title". When a user clears the title input form field and submits the form an exception like this is thrown:
Typed property
App\Form\Dto\ProductDto::$titlemust be string, null usedAs far as I can see this is caused by the fact that during
Form->handleRequest()the title is set to an empty string (or null) after it was set to "Foo" before, right?Is there a solution for this problem?
This is what I just came up with:
DTO:
`use GenericSetterTrait;
/**
*/
public string $title;
public function setTitle(?string $title): void
{
}
/**
*/
public Foo $foo;
public function setFoo(?Foo $foo): void
{
}`
Trait:
`trait GenericSetterTrait
{
}`
Seems to work. What do you think? Any objections?
if you're using public properties, then you don't need setter methods. About the
set(string $propertyName, $value)method. I particularly don't like that kind of magic methods but if you have a valid use-case where it will save you a lot of time, then go ahead.This setter method prevents the "Typed property App\Form\Dto\ProductDto::$title must be string, null used" error. That's the reason I added it.
In that case, you can make the "title" property private, right?
Hey Roland W.
Do you get that error by submitting the form with an empty title? If that's the case, then, that's the right behavior because you updated the
ProductDtotitle's fromfootonulland it's not allowed to be null. Try setting a different value for the title and see if that works (it should).Cheers!
Yes, that is exactly my problem. So type properties must be used with the ? Operator in these DTOs when data is passed to the form, right?
Isn’t there another way - I would really like to use typed properties without the ? operator.
I'm afraid that's a downside of using Symfony forms. Using a DTO helps at it so your entities can *stop* allowing nulls but the DTO class still have to.
..
I have a suggestion for your new SF 5 track, if you plan to showcase DTOs again, a new way to overcome the "code duplication" of validations/constraints with DTOs:
https://symfony.com/blog/ne...
Yo Mike P.!
I just saw that feature! It's super cool - I *love* it. But I guess you would need to create one constraint class per property that has constraints... and then apply that on both the entity and DTO properties. So, it *would* cut down on some duplication (if a property has 3 constraints, you wouldn't need to repeat that on each constraint), but I guess you would still need to have each constraint on both entity & DTO properties. So... it *helps*, but doesn't eliminate it entirely, I think. Or do you disagree? I'm asking because... I hope I'm not seeing something and would love to be wrong :).
Cheers!
I agree 100% 👍
Could you please take a look at this case:
https://symfonycasts.com/sc...
I would love to hear feedback from you to clear my mind! :)
„ Because you might have to do all sorts of crazy things to get that to work, including using embedded forms, which isn't even something I like to talk about„
Would you choose DTO over a embedded form every time? If yes, why (use cases for both)?
I can’t yet decide when to use embedded forms and when a dto. DTO is better maintanable?
Cey Mike P.
DTO's are good when you have a form that is conformed of many different entities
Embedded forms are good when you want to add or remove items of the form. For example, you have a Users and Articles, a User may have many Articles, so in your form you may want to manage the user information and the list of his articles (It may not be the best example but I hope it helps you clarifying things a little bit)
Cheers!
And if you want a combination of both worlds, we need to use both?
By example:
I haven an Recipe Entity, with a ManyToMany to Ingredients and Categories.
The "new recipe" page does a have a new "ingredient" field for each ingredient. (Which means each ingredient has its OWN text field)
So in this example, we have different entities (recipe & ingredient & category) and items (ingredients) can be added or removed.
So in this case I should use both, DTO and embedded forms?
Could you clear my mind please :)
Thanks in advance!
Hey Mike P.!
It sounds like you actually have a fairly simple (I mean "fairly", because this situation is never that simple) example of an entity (Recipe) which has an embedded collection of other entities (Recipe). As far as I can tell, the Categories relationship is not important for this form. And also, there are no extra/weird fields.
So, this seems like a fairly simple CollectionType situation... which still makes it tough. If you used entities, you would use the CollectionType on a RecipeFormType over to an IngredientFormType. The IngredientFormType would probably only have one field - a text field. That could definitely work - you would need
cascade={"persist"}on theingredientsrelationship... but it should work. Still, the JavaScrip with a CollectionType is a pain.So... I would probably instead do this:
1) Create a RecipeFormType that is bound to the Recipe entity (because, as far as I know, there aren't many weird fields - other than the ingredients - that would necessitate using a DTO).
2) For the "ingredients" field on that form, make it
'mapped' => falseand a CollectionType using TextType - I think the code is:3) In the template, you would still be dealing with a CollectionType, but hopefully it will be a bit simpler because it will just be a collection of input boxes. You can use the normal Symfony way of doing the "add new" thing, or (honestly), you could render the widget
{{ form_widget('ingredients'), which I think will render nothing on this "new" form - then just write your own JavaScript to do everything. As long as you end up with a bunch of input fields with the correct name attribute, the form won't know the difference.4) On submit, use
$form->get('ingredients')->getData()to get the array of strings. Turn these into Ingredient objects and link them.So... it's all about trying to reduce complexity - and, for me, complexity is all about the CollectionType. Let me know what you end up doing and how it goes!
Cheers!
How do I run this DTO validation on multiple entries of the same input fields? For example, I'm posting 10 entries of names and addresses at a time, how do I validating them all?
hey Larry Lu
For such cases there is
Symfony\Component\Validator\Constraints\Allconstraint, nit sure that it will work with annotations, but if constraints defined programmatically it should work in combination withCollectionconstraintIt will be something like this:
if I didn't missed something =)
Cheers! Hope this will help!
Thanks it works. I followed that $constraint with the validator check and it's able to check my $data array.
`$errors = $validator->validate($data, $constraint);
if (count($errors) > 0 ) {
}`
So does this mean that I don't have to use the DTO model class anymore? And that check for validator error is the replacement for the
if ($form->isSubmitted() && $form->isValid())block if I were working with form?You can add this $constraint to your form class, and continue using form, I think controllers are more readable when you using forms =)
Cheers!
What would be the best way to populate a dropdown with this DTO approach? Basically I want to receive a list of objects from the database and display them in the dropdown. Putting it into the FormType class in the builder as "choices" seems not to be the most elegant version.
Hey Rita M., probably a DataTransformer is what you need: https://symfony.com/doc/cur...
but if it doesn't fit your needs, then probably you may have to fetch entity objects and then code a function for transforming them into a DTO list.
Cheers!
This looks like a pretty annoying way of doing things !
"Houston: no signs of life"
Start the conversation!