Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

ManyToMany Joins & When to Avoid ManyToMany

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.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We 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():

... lines 1 - 13
class ArticleController extends AbstractController
{
... lines 16 - 28
public function homepage(ArticleRepository $repository)
{
$articles = $repository->findAllPublishedOrderedByNewest();
... lines 32 - 35
}
... lines 37 - 63
}

Open ArticleRepository to check that out:

... lines 1 - 15
class ArticleRepository extends ServiceEntityRepository
{
... lines 18 - 22
/**
* @return Article[]
*/
public function findAllPublishedOrderedByNewest()
{
return $this->addIsPublishedQueryBuilder()
->orderBy('a.publishedAt', 'DESC')
->getQuery()
->getResult()
;
}
... 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:

... lines 1 - 15
class ArticleRepository extends ServiceEntityRepository
{
... lines 18 - 22
/**
* @return Article[]
*/
public function findAllPublishedOrderedByNewest()
{
return $this->addIsPublishedQueryBuilder()
->leftJoin('a.tags', 't')
... lines 30 - 33
;
}
... 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'):

... lines 1 - 15
class ArticleRepository extends ServiceEntityRepository
{
... lines 18 - 22
/**
* @return Article[]
*/
public function findAllPublishedOrderedByNewest()
{
return $this->addIsPublishedQueryBuilder()
->leftJoin('a.tags', 't')
->addSelect('t')
... lines 31 - 33
;
}
... 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.

Leave a comment!

55
Login or Register to join the conversation

Another great course. Kudos!

2 Reply
Will H. Avatar
Will H. Avatar Will H. | posted 4 years ago

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...

1 Reply

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!

Reply
Yahya E. Avatar
Yahya E. Avatar Yahya E. | posted 4 years ago

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.

1 Reply

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!

1 Reply
Mouad E. Avatar
Mouad E. Avatar Mouad E. | victor | posted 4 years ago | edited

victor Am waiting for the translations course, thank you.

Reply
Greg B. Avatar

https://datatables.net/ is super-cool for JS pagination.

Reply

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!

Reply
Default user avatar
Default user avatar Marwen Tlili | posted 1 year ago

Awesome knowledge
Thanks alote

Reply

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!

Reply
Default user avatar
Default user avatar Marwen Tlili | victor | posted 1 year ago

yeh thx I already see it

Reply
Matteo S. Avatar
Matteo S. Avatar Matteo S. | posted 1 year ago

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.

Reply

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!

Reply
Farshad Avatar
Farshad Avatar Farshad | posted 1 year ago

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?

Reply

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!

Reply
Farshad Avatar

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() ?

Reply

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!

1 Reply
Nikolay S. Avatar
Nikolay S. Avatar Nikolay S. | posted 2 years ago

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']]?

Reply

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?

Reply
Nikolay S. Avatar

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

Reply

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 Product and ProductModel. And, in your Order you will have a collection of those ProductOrder's. Does it makes sense to you?

Reply
Nikolay S. Avatar
Nikolay S. Avatar Nikolay S. | MolloKhan | posted 2 years ago | edited

MolloKhan I need in Order Product to make separate column "ProductModel" so it can be OrderID|Product|ProductModel

Reply

Yea, you can add another entity for holding those relationships. Like a middle ware between your the order and everything related to the product

Reply
Robert V. Avatar
Robert V. Avatar Robert V. | posted 3 years ago

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) }}

Reply

Hey Robert V.

Everything looks good except your color form field. It should be called colors as entity property you want to bind.

Cheers!

Reply
Robert V. Avatar
Robert V. Avatar Robert V. | sadikoff | posted 3 years ago | edited

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"."

Reply
Robert V. Avatar

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.

Reply

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

Reply
Alessandro D. Avatar
Alessandro D. Avatar Alessandro D. | posted 3 years ago

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

Reply

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!

Reply

By adding
->leftJoin('a.tags', 't')
->addSelect('t')

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?

Reply

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!

Reply
Krzysztof K. Avatar
Krzysztof K. Avatar Krzysztof K. | posted 3 years ago

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...

Reply
Krzysztof K. Avatar

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

Reply

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!

1 Reply
Krzysztof K. Avatar

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.

1 Reply
Emakina F. Avatar
Emakina F. Avatar Emakina F. | posted 3 years ago

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.

Reply

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!

Reply
Jennifer K. Avatar
Jennifer K. Avatar Jennifer K. | posted 4 years ago

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...

Reply

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!

Reply
Jennifer K. Avatar

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!

Reply

Nice find Jennifer K.! Thanks for sharing!

Reply
weaverryan Avatar weaverryan | SFCASTS | posted 4 years ago | edited

Hey @siebzig3!

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!

Reply
Otto K. Avatar

I have similar error, please help to fix.

Reply

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!

Reply
Mouad E. Avatar
Mouad E. Avatar Mouad E. | posted 4 years ago

Great Symfony Tutorials i ever found in the web, Thank you, you are the best.

Reply

Ah man, thanks!

Reply
Etienne L. Avatar
Etienne L. Avatar Etienne L. | posted 4 years ago

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

Reply

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!

Reply
Etienne L. Avatar
Etienne L. Avatar Etienne L. | weaverryan | posted 4 years ago | edited

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))

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

The course is built on Symfony 4, but the principles still apply perfectly to Symfony 5 - not a lot has changed in the world of relations!

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
        "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.17.6
        "symfony/framework-bundle": "^4.0", // v4.0.14
        "symfony/lts": "^4@dev", // dev-master
        "symfony/orm-pack": "^1.0", // v1.0.6
        "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/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.4
    }
}