Chapters
-
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!
ManyToMany Joins & When to Avoid ManyToMany
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot 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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe have the N+1 query problem once again. Click to view those queries. The new queries are mixed in here, but you'll see 7 new queries that select from tag
with an INNER JOIN so that it can find all the tags for just one Article. Each time we reference the tags for a new Article
, it makes a new query for that article's tags.
This is quite possibly not something you need to worry about, at least, not until you can see a real performance issue on production. But, we should be able to fix it. The first query on this page finds all of the published articles. Could we add a join to that query to select the tag data all at once?
Totally! Open ArticleController
and find the homepage()
action. Right now, we're using $articles = $repository->findAllPublishedOrderedByNewest()
:
Show Lines
|
// ... lines 1 - 13 |
class ArticleController extends AbstractController | |
{ | |
Show Lines
|
// ... lines 16 - 28 |
public function homepage(ArticleRepository $repository) | |
{ | |
$articles = $repository->findAllPublishedOrderedByNewest(); | |
Show Lines
|
// ... lines 32 - 35 |
} | |
Show Lines
|
// ... lines 37 - 63 |
} |
Open ArticleRepository
to check that out:
Show Lines
|
// ... lines 1 - 15 |
class ArticleRepository extends ServiceEntityRepository | |
{ | |
Show Lines
|
// ... lines 18 - 22 |
/** | |
* @return Article[] | |
*/ | |
public function findAllPublishedOrderedByNewest() | |
{ | |
return $this->addIsPublishedQueryBuilder() | |
->orderBy('a.publishedAt', 'DESC') | |
->getQuery() | |
->getResult() | |
; | |
} | |
Show Lines
|
// ... lines 34 - 56 |
} |
This custom query finds the Article
objects, but does not do any special joins. Let's add one. But.... wait. This is weird. If you think about the database, we're going to need to join twice. We first need a LEFT JOIN from article
to article_tag
. Then, we need a another JOIN from article_tag
to tag
so that we can select the tag's data.
This is where Doctrine's ManyToMany
relationship really shines. Don't think at all about the join table. Instead, ->leftJoin()
on a.tags
and use t
as the new alias:
Show Lines
|
// ... lines 1 - 15 |
class ArticleRepository extends ServiceEntityRepository | |
{ | |
Show Lines
|
// ... lines 18 - 22 |
/** | |
* @return Article[] | |
*/ | |
public function findAllPublishedOrderedByNewest() | |
{ | |
return $this->addIsPublishedQueryBuilder() | |
->leftJoin('a.tags', 't') | |
Show Lines
|
// ... lines 30 - 33 |
; | |
} | |
Show Lines
|
// ... lines 36 - 58 |
} |
The a.tags
refers to the tags
property on Article
. And because Doctrine knows that this is a ManyToMany relationship, it knows how to join all the way over to tag
. To actually fetch the tag data, use ->addSelect('t')
:
Show Lines
|
// ... lines 1 - 15 |
class ArticleRepository extends ServiceEntityRepository | |
{ | |
Show Lines
|
// ... lines 18 - 22 |
/** | |
* @return Article[] | |
*/ | |
public function findAllPublishedOrderedByNewest() | |
{ | |
return $this->addIsPublishedQueryBuilder() | |
->leftJoin('a.tags', 't') | |
->addSelect('t') | |
Show Lines
|
// ... lines 31 - 33 |
; | |
} | |
Show Lines
|
// ... lines 36 - 58 |
} |
That is it. Go back to our browser. The 15 queries are... back down to 8! Open the profiler to check them out. Awesome! The query selects everything from article
and all the fields from tag
. It can do that because it has both joins! That's nuts!
When a ManyToMany Relationship is Not What You Need
Ok guys, there is one last thing we need to talk about, and, it's a warning about ManyToMany relations.
What if we wanted to start saving the date of when an Article
was given a Tag. Well, crap! We can't do that. We could record the date that a Tag
was created or the date an Article
was created, but we can't record the date when an Article
was linked to a Tag
. In fact, we can't save any extra data about this relationship.
Why? Because that data would need to live on this article_tag
table. For example, we might want a third column called created_at
. The problem is, when you use a ManyToMany
relationship, you cannot add any more columns to the join table. It's just not possible.
This means that if, in the future, you do need to save extra data about the relationship, well, you're in trouble.
So, here's my advice: before you set up a ManyToMany relationship, you need to think hard and ask yourself a question:
Will I ever need to store additional metadata about this relationship?
If the answer is yes, if there's even one extra piece of data that you want to store, then you should not use a ManyToMany relationship. In fact, you can't use Doctrine at all, and you need to buy a new computer.
I'm kidding. If you need to store extra data on the article_tag
table, then, instead, create a new ArticleTag
entity for that table! That ArticleTag
entity would have a ManyToOne relationship to Article
and a ManyToOne relationship to Tag
. This would effectively give you the exact same structure in the database. But now, thanks to the new ArticleTag
entity, you're free to add whatever other fields you want.
If you generated a ManyToMany relationship by mistake and want to switch, it's not the end of the world. You can still create the new entity class and generate a migration so that you don't lose your existing data. But, if you can configure things in the beginning... well, even better.
Ok guys, you are now Doctrine pros! Your relationship skills strike fear at the heart of your enemies, and your ability to JOIN across tables is legendary among your co-workers.
Yes, there is more to learn, like how to write even more complex queries, and there are a lot of other, cool features - like Doctrine inheritance. But, all of the super important stuff that you need to create a real site? Yea, you got it down. So go out there, SELECT * FROM world
, and build something amazing with Doctrine.
Alright guys, seeya next time.
55 Comments
I am curious how to get the comment count and reduce this page down to one query, all while using the same repository method - I am not clear on how to do this with the query builder / DQL and this is the type of thing I'd want to do all the time. Can DQL give us merely the count of a relation or do we need to rebuild the query from scratch with count(), join, and group by? We can use ->leftJoin('a.comments', 'c')->addSelect('c') but then we are retrieving all comment rows rather than just a count.
This has been an amazing course, I learned so much, thank you to the KNP team!
edit: I see we can do something like this:
->leftJoin('a.comments', 'c')
->addSelect('count(c)')
->addGroupBy('a.id')
but then we are returning an array of arrays rather than array of Articles, which seems to introduce more complications...
Hey Will H.
For that case you can do something like this:
$result = $qb
->leftJoin('a.comments', 'c')
->select('COUNT(a.id)')
->setMaxResults(1)
->getQuery()
->getSingleScalarResult()
Cheers!
Great tutorials, thank you.
Can you cover more topics, especially the ones most web developers encounter, navigation sortable trees, saving entities with translatable fields, versioning and logging best practices, and of course uploading media. I saw most of them in Stof Doctrine Extensions. However, you guys making everything easier then it really is :)
Another topic but is there AJAX based alternative to knp-paginator-bundle?
Thanks for all your help.
Hey Yahya,
Thanks! :) Well, we're going to make an entire course about translations, it should be released soon. For other topics, some extensions from StofDoctrineExtensionsBundle we mention in our Symfony tutorials, use our updated search to find ones. About other ideas - would be cool to have screencasts about them too, and we have them in mind for the future, but I have no estimations when it may be released yet.
> Another topic but is there AJAX based alternative to knp-paginator-bundle?
I personally do not know any specific bundle, but we do talk about pagination in this course: https://knpuniversity.com/s... . You may also check this screencast: https://knpuniversity.com/s... . That's an API way to do pagination. But you can check some popular JS libs on GitHub that helps with infinite scroll, etc. Along with KnpPaginatorBundle or PagerfantaBundle you can implement it easy.
Cheers!
victor Am waiting for the translations course, thank you.
https://datatables.net/ is super-cool for JS pagination.
Hey Greg,
Thanks for sharing it! IIRC, by default DataTables load ALL the data and then paginate over the loaded data with JavaScript, i.e. it's not AJAX-based by default. But it can be configured to load data from the server by AJAX requests, so yeah, it may work for Yahya as well.
Cheers!
Awesome knowledge
Thanks alote
Hey Marwen,
Thank you for your feedback! We really glad you liked this :) This tutorial is a bit outdated and has a fresher version here: https://symfonycasts.com/sc... - I'd recommend you to check it too if you haven't yet :)
Cheers!
yeh thx I already see it
Unbelievable that a mature orm framerowk like Doctrine doesn't support having attributes on a many-to-many relationship. To me that's enough to dismiss Doctrine as garbage and start looking for alternatives. What is disturbing is that Symfony seems to "promote" Doctrine as the go-to choice for orm.
Hey there!
I see what you mean, but it's incorrect to think that Doctrine is garbage just because it does not have extra columns on many-to-many relationship. Well, first of all, any ManyToMany relationship is just 2 other relationships: OneToMany & ManyToOne. So, you can just create a separate entity that will hold all those extra fields you need and has OneToMany & ManyToOne to other 2 entities. That's hw it works behind the scene. Doctrine's ManyToMany in turn just helps to avoid creating an additional entity in case when you just need to "link" 2 entities without extra attributes. So, may think about this as Doctrine just do less code (that would be definitely a complex code) and so do less magic behind the scene. If you need extra fields - go with creating an extra entity with OneToMany & ManyToOne and do it explicitly, no magic, no complex code, less bugs. There also might be more pros for this approach I didn't know about, but if you're curious - I think you can search for some answers in Doctrine issues on GitHub.
And yes, Symfony promotes Doctrine because it's a great solid project and there's a good integration between Doctrine and Symfony. But it does not mean you can't use any other ORM you liked more. Feel free to install and use your favorite in your Symfony project ;)
Cheers!
I made a OneToMany relation. But now it gives me this error:
Argument 1 passed to App\Entity\Measurement::setMeasurementMeetstaat() must be an instance of App\Entity\Meetstaat or null, int given, called in C:\xampp\htdocs\doesburg3\doesburg\src\Controller\MeasurementController.php on line 58
I am trying to insert a float as a value in the database. But it says that it should be an instance of Entity/Meetstaat. How do I do that?
Hey @Farry7!
Can you share your code? Especially the code (or related code) that calls the setMeasurementMeetstaat() method.
One of the odd (you'll eventually like it, but it's odd at first) things about Doctrine relations is that when you call setMeasurementMeetstaat() you need to pass this a Meetstaat *object*, NOT a database id. From the sound of the error, it seems like that might be the cause. If It is, take that id, query for the Meetstaat from the database, and then set that object.
But let me know if I'm wrong... or if you're in a spot where this is difficult to do for some reason :).
Cheers!
Thanks for your feedback. My colleague said the same. Can you maybe give an example of how I set the Meetstaat object in the setMeasurementMeetstaat() ?
Hey Farry7!
That’s a good colleague :).
Exactly how you set it depends on the situation. Well, the last step is always the same (you call the set method and pass the object) but where the object comes from will vary. Can you describe your situation? Is this a form? Something else?
Let’s take a simple example. Imagine you have some controller with a url of /new/measurement/:meetstaatId where, for some reason, you include the meetstaatId that you want in the url. Then, you would:
1) query for the Meetstaat object. We do that by autowiring MeetstaatRepository, then saying $meetstaat = $repository->find($meetstaatId)
Then, on the next line, it’s $measurement->setMeetstaat($meetstaat);
I probably have a few typos in there, but that’s the idea :)
Let me know what your situation is.
Cheers!
I have a issue with shopping cart options.
Orders entity has:
-UserID (ManyToOne relation to User entity)
-ProductID (ManyToMany relation to Product entity)
-ProductModelID (ManyToMany relation to ProductModel Entity)
User entity is standart.
Product entity has only "ProductName" property
ProductModel entity has only "ProductModelName" property
I have two seperated tables which are:
Orders_Product:
OrderID: 1, ProductID: 1
Orders_ProductModel:
OrderID: 1, ProductModelID: 1
This is a example of the issue which I think can mess:
Orders:
id: 1, UserID: 1
Orders_Product:
OrderID: 1, ProductID: 5
OrderID: 1, ProductID: 6
Orders_ProductModel:
OrderID: 1, ProductModelID: 10
OrderID: 1, ProductModelID: 17
When a order has more than one product - product 5 must be productmodel 10, but i think it can mess up and display 17 instead, because it's related to order 1. Am I right or it's fine this way?
Is it better option to use json field in orders entity and store data as array in this way [1:['product':'5','productmodel':'10']]?
Hey Nikolay S.
> Is it better option to use json field in orders entity and store data as array in this way [1:['product':'5','productmodel':'10']]?
I don't think that's a good idea because it's going to be hard to make it work with Doctrine. The question here is why you need to store the ProductModel?
Hey,
I'll deserialize the info from the Json and i will query for the data.
I need to store it because in my case every product has model
Ohh, so "ProductModel" is indeed an entity or your business? I thought it was just a DTO (Data transfer object). Hmm, what you can do is to wrap that relationship in another entity, something like ProductOrder and it will hold a reference to Order
`<br />Product
and ProductModel
. And, in your Order
you will have a collection of those ProductOrder's. Does it makes sense to you?
MolloKhan I need in Order Product to make separate column "ProductModel" so it can be OrderID|Product|ProductModel
Yea, you can add another entity for holding those relationships. Like a middle ware between your the order and everything related to the product
I am setting up a many to many relationship following the Article to Tag entity example in the tutorial. I have the Fixture class setup as described and loading the fixtures sets the relationship as expected. However, I notice the Forms tutorial does not cover persisting the Article to Tag manyToMany relationship using a form; either for creating or editing a Article.
So I have run into issue with my manyToMany between my Profile entity and Color entity. When I buildForm I get the error :
"Neither the property "color" nor one of the methods "getColor()", "color()", "isColor()", "hasColor()", "__get()" exist and have public access in class "App\Entity\Profile".
If I show my code below, perhaps you see where I am going wrong. Thanks in advance!
PROFILE ENTITY BELOW:
class Profile
{
/**
* @ORM\ManyToMany(targetEntity="App\Entity\Color", inversedBy="profiles")
*/
private $colors;
/**
* @return Collection|Color[]
*/
public function getColors(): Collection
{
return $this->colors;
}
public function addColor(Color $color): self
{
if (!$this->colors->contains($color)) {
$this->colors[] = $color;
}
return $this;
}
public function removeColor(Color $color): self
{
if ($this->colors->contains($color)) {
$this->colors->removeElement($color);
}
return $this;
}
COLOR ENTITY BELOW:
class Color
{
/**
* @ORM\ManyToMany(targetEntity="App\Entity\Profile", mappedBy="colors")
*/
private $profiles;
ProfileFormType BELOW
class ProfileFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('color', EntityType::class, [
'class' => Color::class,
'choice_label' => function(Color $color) {
return sprintf('(%d) %s', $color->getId(), $color->getName());
},
'placeholder' => 'Choose a color',
'choices' => $this->colorRepository->findAllByName(),
'invalid_message' => 'Symfony is too smart for your hacking!',
])
;
}
_form.html.twig BELOW
{{ form_start(profileForm) }}
{{ form_row(profileForm.title, {
label: 'Profile Title'
}) }}
{{ form_row(profileForm.color) }}
{{ form_row(profileForm.content) }}
{{ form_row(profileForm.author) }}
{{ form_end(profileForm) }}
Hey Robert V.
Everything looks good except your color
form field. It should be called colors
as entity property you want to bind.
Cheers!
Hi sadikoff , yes I used colors on my form field but something else is causing entity property not to bind. When using colors I get this runtime error: "Neither the property "colors" nor one of the methods "colors()", "getcolors()"/"iscolors()"/"hascolors()" or "__call()" exist and have public access in class "Symfony\Component\Form\FormView"."
I've searched for solutions for why I can't get a many to many relationship to persist using Symfony Forms. The tutorials for using Fixtures and the RandomReferences code works great but can't figure it out using forms.
Hey Robert V.
IIRC for many to many relations to persist everything correctly is better to use DataTransformers for data linking.
And once again about you situation, there is one limitation with color
fields, sometime it can work unexpected because there is a Color Form type, but without deep investigation, I cannot say it for 100%
Cheers
Hi,
I have followed the tutorial but I cannot deny I am quite confused, although the tutorial is very well eplained.
Basically I did manage to successfully create ManyToMany relationship that holds two columns, but I cannot figure out how to create a third one
Basically I have a "User" table and "Social" table (that holds social media information in the form of id, name).
Now, each User can have many socials and each social can have many users, but I also need to add an extra column that hold the url of that specific social, so the ManyToMany table would have something like "user_id, social_id, url".
II tried to manually remove the ManyToMany table and create an entity called UserSocial, where the first property name is "user_Id" set it to ManyToOne relation and second to be "social_id" set to OneToMany with Social Entity, but I don't know if the method suggested by console "$user->getUserSocials()" is the appropriate one. When I created ManyToMany, the method called "$user->getSocials()" felt more appropriate.
I really need some clarification, because my head is kind of exploding trying to figure out how it works.
Thanks,
Alessandro
Hey Alessandro!
​
​That's a very fair question :)
​
​When you are working with a ManyToMany relationship but you need to add extra fields, then, you can't use Doctrine's ManyToMany relationship anymore. What you need is a third entity which will join the other 2 and will hold the extra data per item. So, let's say you have entity A and B, and we will name the join-entity as "AB", so you will end up with a relationship as follows:
A OneToMany AB
B OneToMany AB
Probably this answer may help you out to understand it a bit more: https://groups.google.com/f...
Cheers!
By adding <br />->leftJoin('a.tags', 't')<br />->addSelect('t')<br />
to the "findAllPublishedOrderedByNewest" function (in ArticleRepository), the articles returned on index.twig.html were reduced from 10 to 6!
Is there any fix for this?
Hey Josan!
Hmm, interesting! If you look at the query it generates at around time 2:34 (https://symfonycasts.com/sc... ) it's a LEFT JOIN from article to article_tag, then another LEFT JOIN from article_tag to tag. Those two left joins should not change the number of results returned. I just tried this locally - after loading the database fixtures. Both with and without the query, I'm seeing 5 results on the homepage (of course, the exact number of published articles if you load the fixtures will be random).
Can you take a look again to be sure that it's changing the number of results?
Cheers!
This is all bad... resolving one problem creates another.... have split my Many to Many relation because in joining table i have an extra data and now I do not know how to render this split relation as a many to many relation in my form, relation is between User and Asset, so I have extra entity AssignedAsset, when I render:
`
->add('assignedAssets', null, [
'multiple' => true,
'label' => 'Assigned Assets',
'attr' => [
'size' => 12,
]
])
`
I got blank, I do not know how to configure it to display entries form Asset table.
Tested few things and nothing works...
Mainly how can I tell Symfony: yes Symfony I have split this ManyToMany relation but please for Form rendering treat it like typical ManyToMany :D
Hey Krzysztof K.!
I'm don't have full context, but I may be able to answer this last, direct question. You can do this by making custom getter, adder & remover methods to "fake" a traditional ManyToMany relationship. For example, in User, you could create a getAssets()
method, which iterates over assignedAssets and returns the array of actual Asset objects. You would also create an addAsset(Asset $asset), which would create the AssignedAsset() and set it on the assignedAssets property. And finally, you would create a removeAsset(Asset $asset), which would find the correct AssignedAsset based on the Asset argument and remove it (you will probably need some orphanRemoval on the relationship for the AssignedAsset to be deleted correctly.
If you do all of this and get it working, then, to the form system, it will appear like you have a ManyToMany relationship on User to Asset called "assets". It will be able to call getAssets(), addAsset() and removeAsset() just like a normal ManyToMany relationship.
Let me know if that helps!
Cheers!
Thanks Ryan, I will investigate it tomorrow. I did different solution (works but don't like it), but after your answer I have realized that I need orphanRemoval.
I did some tests and yes, it works as you explained, I have added these methods and mapped field definition to form.
My previous solution was done with unmapped field, and then in controllers I had to run extra method to process that field from form.
I am not sure what is the best solution here.
I am thinking also about adding sensiolabs-de/rich-model-forms-bundle and do it with custom setter defined in configured in form, so there will be no need to do it in controller.
Hello, thank you for the awesome course!
I have a question about adding a LIMIT clause to this new query, how I can limit the number of articles retrieved from the database?
Something like SELECT a0_.id AS id_0, [...] t1_.updated_at AS updated_at_14 FROM article a0_ LEFT JOIN article_tag a2_ ON a0_.id = a2_.article_id LEFT JOIN tag t1_ ON t1_.id = a2_.tag_id WHERE a0_.id in (SELECT id FROM article LIMIT 10 ORDER BY published_at DESC)
Because if I simply add a setMaxResults(10) to the query, it stops retrieving results after 10 Tags, meaning our hydration is not complete since we didn't get our 10 articles.
Hey Emakina F.
I think you are declaring the "LIMIT" clause in the wrong place, it should limit your outter SELECT instead of inner SELECT
Cheers!
I have a many-to-many relationship between Roles (user roles) and Groups (user groups). Ownership side is roles. When I use the code suggested by the tutorial and try to add an existing group (selected from a dropdown) to a role over a Symfony form, I get the error that
"A new entity was found through the relationship '...Entity\Role#groups' that was not configured to cascade persist operations for entity: 1. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"})."
When I put cascade persist on the "groups" definition in "Role", however, the result is that a NEW, empty group is inserted into the group table, and this new id is saved with the selected role in the role_group table. WHY? The existing group disappears somewhere in the flush() operation...
Hey Jennifer K.!
Hmm! This is a bit of a mystery! A few things to look into:
1) You said "Ownership side is roles". So, the owning side os the Group.roles property, right? And in your form, it sounds like you are setting the Role.groups side of the relation (the inverse side). Did you generate the relationship with the make:entity
command? That generated code "synchronizes" the owning side when you set the inverse side. Basically, I'm first just making sure that we're setting the owning side, at some point. Though that wouldn't entirely explain your weird behavior.
2) You mentioned that you're selecting an existing group from a drop-down in a Symfony form. Is this a true, "select one" drop-down (using the EntityType)? If so, that's a slight mismatch (unless you have some additional code that's handling this). I'm assuming that this field in the form is called "groups". And, if you've set it to an EntityType where you just select one, then it will try to call setGroups()
but only pass it one Group object. The problem is that we have an array of groups - not just one. So, in theory, that should be causing some issues as well.
3) Setting the cascade option should have been an ok solution. But, because it seems to be causing some unexplained issues, I would recommend removing it. Then, in your controller, try persisting the groups manually. Something like:
// after the form submit handling
foreach ($role->getGroups() as $group) {
$em->persist($group);
}
In theory, that's what the cascade persist was doing anyways, but this could "shed some light" on things.
Let me know what you find out!
Cheers!
It was a Symfony form problem after all: in my GroupFormType, in addition to specifying the entity type of the dropdown field as Group, I was implementing the configureOptions and setting the defaults data_class to Group as well, which Symfony did not like! Once I deleted this method from my GroupFormType and got rid of cascade persist, it worked as expected!
Nice find Jennifer K.! Thanks for sharing!
Hey there!
Oh yea, definitely a bug... made by me :). The class is called ArticleFixtures
(notice the s) - I made some changes, and messed that up! It's all better now - thanks for letting us know!
Cheers!
I have similar error, please help to fix.
Hi there!
The course download code has since been fixed - if you download the course code from this page, you will now not have any problems. If you ARE still having problems, post your exact error message here and we'll be happy to help.
Cheers!
Great Symfony Tutorials i ever found in the web, Thank you, you are the best.
Ah man, thanks!
Hey , thanks for those tutorials !
But got a question for you guys :
My code :
I got a relation OneToMany / ManyToOne : Entity User ==> Entity UsersIndicators <== Entity Indicator.
(Note : I got a composite primary key in UsersIndicators on the properties "$user" and "$indicator" )
The Entity "UsersIndicators" got extra fields like "$color" and "$displayed".
My problem :
I have trouble creating my form.
I need to create a Form that, for a connected User, a list of his Indicators are displayed and for each indicator, the user could select the color (string), and if if want to display it (boolean). Not to add or delete indicators.
I try to create a formType for the UsersIndicators Entity, but in my Controller, when i createForm with the FormType, i passed at the second argument the $userIndicator propertie of the User Entity. This is actually an ArrayCollection of UsersIndicators !
Symfony tells me, in the exception, to add a viewTransformer but i'm a little lost...
Any idea ?
Thanks in advance
Hey Etienne L.!
First, nice job setting up the OneToMany / ManyToOne relationships so that you can store your extra fields on the "middle" entity! Second, yep, forms are where things typically get really complex. In your case, fortunately, your form and your entities are actually pretty "close". I mean, you are trying to render a form with the same hierarchy and fields as your entities. And so, we should be able to build a form that does this pretty easily. Here is my advice:
1) Create a form for your UserIndicators entity - e.g. UserIndicatorsFormType (I think you already did this, but just listing it for clarity). Give this two fields: color & displayed. And, set the data_class option to UserIndicators.
2) Create a form for your User class first. I'm going to assume that the entire purpose of this form is to edit the "indicators", but not edit any other user information. In that case, you could have a EditIndicatorsUserFormType
class. Inside, give it just one field (you can put other fields from your User if you want to): userIndicators
(or whatever the property name is on User for the relationship). This field should look like this:
$builder->add('userIndicators', UserIndicatorsFormType::class);
3) Finally, in your controller, you will actually build the form around your User entity:
$form = $this->createForm(EditIndicatorsUserFormType::class, $user);
So, you're actually creating a form for your User, inside, we're modifying the userIndicators property.
Let me know if this helps! Btw, at some point, if your form became even more and more complex, it would be better to build a JavaScript front-end instead of using the form component. But, from what you've told me, the form system should work great here.
Cheers!
Hey weaverryan,
So much tanks for your answer !
I changed my code in order to have the same as you expeted.
I think i'm close to win this buuut, i got already the same error exception. I Know why it crashed but i dunno how to fix it.
In my controller, the `$user` variable is an User Object. In this User Object (Entity), i got the property $userIndicators that map to the `UsersIndicators` Entity. BUT, this is an arrayCollection, and when i passed the $user Object to the Form, Symfony crash with :
> "The form's view data is expected to be an instance of class App\Entity\UsersIndicators, but is an instance of class Doctrine\ORM\PersistentCollection. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class Doctrine\ORM\PersistentCollection to an instance of App\Entity\UsersIndicators."
Maybe i totally misunderstood something ?
Note : Maybe not very important , but someone tell me that the composite primary keys are no longuer supported in Doctrine, should I instead add an ID autoincrement in `UsersIndicators` Entity ?
Thanks again !
______
Edit : Just in order to illustrate where I wanna go :
Indicator 1 : checkbox (for displayed (boolean)) --- color picker (for color (string))
Indicator 2 : checkbox (for displayed (boolean)) --- color picker (for color (string))
Indicator 3 : checkbox (for displayed (boolean)) --- color picker (for color (string))
"Houston: no signs of life"
Start the conversation!
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/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.7.2
"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
"sensio/framework-extra-bundle": "^5.1", // v5.1.4
"stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
"symfony/asset": "^4.0", // v4.0.4
"symfony/console": "^4.0", // v4.0.14
"symfony/flex": "^1.0", // v1.21.6
"symfony/framework-bundle": "^4.0", // v4.0.14
"symfony/lts": "^4@dev", // dev-master
"symfony/twig-bundle": "^4.0", // v4.0.4
"symfony/web-server-bundle": "^4.0", // v4.0.4
"symfony/yaml": "^4.0", // v4.0.14
"twig/extensions": "^1.5" // v1.5.1
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
"easycorp/easy-log-handler": "^1.0.2", // v1.0.4
"fzaninotto/faker": "^1.7", // v1.7.1
"symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
"symfony/dotenv": "^4.0", // v4.0.14
"symfony/maker-bundle": "^1.0", // v1.4.0
"symfony/monolog-bundle": "^3.0", // v3.1.2
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
"symfony/stopwatch": "^3.3|^4.0", // v4.0.4
"symfony/var-dumper": "^3.3|^4.0", // v4.0.4
"symfony/web-profiler-bundle": "^3.3|^4.0" // v4.0.4
}
}
Another great course. Kudos!