Doctrine Collections: ManyToMany, Forms & other Complex Relations

Buy Access

Tip: This course is built on Symfony 3, but most of the concepts apply just fine to Symfony 4.

With the basic of Doctrine relations behind us, it's time to dive deeper and get our hands dirty with some real-world, but complex collections. Honestly, this is the hardest part of Doctrine: so let's get it right:

  • Creating ManyToMany associations
  • Querying on ManyToMany relations
  • Mapping the inverse (collection) side of a relation
  • (bonus) Using DoctrineExtension for clean URL slugs
  • Keeping the owning & inverse sides in-sync
  • Using orphanRemoval to delete items in a collection
  • Using cascade to save items in a collection
  • Counting collections in the EXTRA_LAZY way
  • Rendering relationship data from both sides
  • Filtering collections efficiently with Criteria (mind will be blown)
  • Dealing with complex collection forms
  • Adding "extra" data to the "join table"

Doctrine relationships take a little bit more work to get setup. So let's master this stuff, setup our relationships to win, and get back to work.


Your Guides

Ryan Weaver

Questions? Conversation?

  • 2018-04-26 Victor Bocharsky

    No problem! Actually, maybe you just need to clear the cache and your cascade annotation will work too, but at KnpU we don't want this cascade feature, we prefer manually call persist() on entities we need.

    Cheers!

  • 2018-04-26 Paweł Chry

    It solve my problem. I'm so sure I tried that also, myself without effect. Thank You.

  • 2018-04-26 Victor Bocharsky

    Hey Pawel,

    Hm, could try manually call persist() on each entity you create during the process?

    Cheers!

  • 2018-04-26 Paweł Chry

    Hi guys, could You take a look whats wrong (or give me tip how to debug it) with my ManyToMany configuration? I got error while persisting object saying I need to configure cascade persist option in ManyToMany relation, but it is configured.
    https://stackoverflow.com/q...

  • 2018-02-28 Piotr Potrawiak

    Hey, hey Ryan! : -)

    I am a supporter of solutions for UX kind like:,
    "The less you click the better",
    - if you can choose at a certain stage - a group of available options,
    I try to find such places in process processing,
    separate them,
    process them most easy for user way.

    Many-To-Many as 2x many-to-one with aditional fields is a very powerful way of processing data. I like separating for processing of data (as int only tables) and build aditional Entities to hold "dictonary data (vchar) as description of these data", and joining those information by Doctrine engine.

    User have vchar description, but engine works on integers only. That's why that 2x many-to-one owning side - was so special for me.

    Thank you for helpful examples and explanations.
    That is kind, helpful, and it's motivating to learn more about how symfony is processing data.

    ty, br,
    Peter

  • 2018-02-28 weaverryan

    Hey Piotr!

    Yea, you totally *could* solve this with data transformers... but it just gets more complex... so, I don't like it! Forms are already complicated enough... and so are ManyToMany + extra fields relationships. If you try to combine them perfectly, it gets *crazy* complex :).

    That's why I like the foreach-processing solution. And yea, if you need to re-use this code, you can move it into a service instead of in your controller - that's a great idea. There's also another solution, which is what *we* would probably use. Instead of creating a big Symfony form, we would probably build a frontend using JavaScript and make the changes via AJAX calls. For example, the user click to "Create an Album", and you would save this to the database. Then, they would search & select tracks. When they do this, you would make an AJAX call to add that track to the Album immediately. We would probably use this solution for 2 reasons: (1) Unless this is an admin area, the user experience is WAY better than a big form and (2) it's probably *less* complex (unless you really don't like JavaScript).

    Cheers!

  • 2018-02-28 Piotr Potrawiak

    ...and by the way: Albums - multiple tracks idea - is ok for example if you have hundrets of artists, their tracks for example are scored by users for long time and every 5 years you just make an album "best of" based on scored tracks.

    seriously - Just thinking how to defend my example (true is that I was trying resolve my many-to-many with aditional data issue with many examples of natural connections (users-groups, product-category, etc), and finaly started with some abstract - that is not involving so much idea - but realization.

    thx

  • 2018-02-28 Piotr Potrawiak

    Hi Ryan!
    Thank You for good words. You can be shure that You (and incredible team: Victor & Diego & Leanna (grettings for them)) have big part in my way of understanding of Symfony framework.

    Example will fit or sound better if instead "track" You take for example ppl assigned to task. Albums & tracks are just abstract example. Like programming is. ; -)

    Still feel that may be some other way, like some Data Transformer? Service. Other undiscowered by me way. Still thinking, how secure / check for example where you have collection of 1000 ppl running in competition, is that "foreach" will handle the job, and all objects will be properly writed into database... but this is just an other story : -)

    thank you,
    Cheers!

  • 2018-02-27 weaverryan

    Hey Piotr!

    Ok, I see your setup! Code is best :). Yes, it looks like a classic ManyToMany + extra fields setup, so it's not *too* weird fortunately (but still complex enough). I believe that I see the code in AlbumTrackController::newAction. Is that correct? And honestly, I think your code is almost correct already. Here's the key thing: the way that you want your form to behave doesn't really match with your entity structure. Specifically, you want to be able to allow the user to select one Album + multiple Tracks and then (from this) create AlbumTrack objects, but without needing to write any extra PHP processing code. It's just not *quite* going to work (well, it probably is possible, but not worth it). Ultimately, a form is used to create *one* object, but you want to create *many* new AlbumTrack objects inside one form.

    In other words, keep the processing code! It's totally fine - it's easy to read and it works. However, there is one problem which I think might be making your life more complex :). The AlbumTrackType is perfectly built to model how you want your form to behave: you want a select drop-down to select one Album and the ability to select multiple tracks. So, nice job :). But, as I mentioned, this doesn't match your AlbumTrack entity (each one AlbumTrack has 1 Album, but not multiple tracks). So, you should NOT set the data_class option in that form to AlbumTrack - it makes no sense for the form system to try to give you back an AlbumTrack object with data that doesn't really fit on an AlbumTrack object. Instead, either (A) set no data_class, and use $form->getData() as a simple array or (b) create a model class that has an "album" property and "tracks" array property, set the data_class to that class, and use that object inside your controller to create the actual, underlying objects.

    You've done a great job of making your form class look like how you want your form to look/behave. But since this doesn't fit nicely to your entity data structure, keep the processing code (the foreach) and be very happy about it - it's simple!

    Cheers!

  • 2018-02-26 Piotr Potrawiak

    Hi Rayan,
    Glad to hear Your response so fast! You are amazing.

    I'm using your example to achieve the functionality for adding group of users (scientists in your case) to "study" a one genus, saved in genus_scientists table. I figured it out, that in controller i can do smth like:


    foreach ($user as $data)
    {
    $genusScientist = new GenusScientist();
    $genusScientist->setGenus($genus);
    $genusScientist->setYearsStudied(rand(10,100));
    $genusScientist->setUser($data);
    $em->persist($genusScientist);
    }

    $em->persist($genus);
    $em->persist($genusNote);
    $em->flush();But this is not the way I want to work with collections.

    As you right discover - I'm trying to work on Many-To-Many with extra fields (so it's 2x many-to-one on owning side) Real Many-To-Many relation - can work with no problem when in formType class EntityType is seted to 'multiple' (just in owning side of relation have to change field type to Collection, and "add" method is needed)

    But it is not working in double many-to-one on owning side.

    To not expand your code to the world I have prepared example with Tracks and Albums. Lets think about it like there is a bigg mess, and you have to add gropu ot Track into Albums, no mater if there are some repeats.

    "working" example of code:
    https://gitlab.com/pietrqu/...
    please checkout branch "#ticket-1", install dependencies, load fixtures

    paths: tracks, albums, albumtrack, common actions

    The Goal to achieve is to get ArrayCollection and save it into by one perist&flush not using foreach loop.
    Then edit that record in AlbumTrack Entity properly.

    Cheers!

  • 2018-02-26 weaverryan

    Hey Piotr!

    Ok, I think I understand! You have basically the same situation as the tutorial, EXCEPT that each GenusScientist is related to MANY User objects. I have several suggestions, but also i have many questions :).

    1) A ManyToMany relationship from GenusScientist to User sounds correct to me. But, it depends on your end goal. *Why* do you need to have many users for each GenusScientist? You said "main goal in tutorial was to replace many-to-many, to add additional fields, which is exactly what i'm looking for)". So how is your situation different?

    2) If you *do* simply want the exact same form as this tutorial, except that you select *many* users when creating/updating a GenusScientist, I think that it should be quite simple... and what you've done is correct. Specifically, you would change the relationship to ManyToMany to User (make sure GenusScientist is the owning side of the relation), change the form to multiple => true... and, that's it! It really should not complicate the form to change the user(s) field on the GenusScientist form from a "one" to a "many". So, what errors are you getting? Can you post some code

    Cheers!

  • 2018-02-25 Piotr Potrawiak

    hi, About year ago I was following this tutorial.

    Have idea to expand it a little, but no luck to resolve some issues. Main goal is to add all sientist to examine new genus. Yes, we found a really rare one, we want move all our forces to examine - In one choose for example EntityType, User::class, multiple => true (or just by changing in this tutorial in GenusController line 40 $user = $em->getRepository('AppBundle:User)->findAll().

    GenusSientist.php will have array of users I would like to add. no luck Errors. Trying change type of fields to Collection, (works with many-to-many), ArrayCollection, etc.

    If there is Many-To-many relation - it will somehow work, but when I'm building relation like in GenusScientist (main goal in tutorial was to replace many-to-many, to add aditional fields, which is exactly what i'm looking for)

    So I need to add into GenusScientist many users at once. Get errors about type of data - could you please help me (and other ppl for shure too) how to add multiple users into relation kind like GenusScientist i one flush(); ?

  • 2017-03-21 Diego Aguiar

    Hey Julien!
    I'm glad to hear it
    If you have any more questions don't hesitate to contact us :)

  • 2017-03-20 julien moulis

    Hey Diego! Thanks for reply. Finally I used Blameable

  • 2017-03-17 Diego Aguiar

    Hey Julien!

    Indeed there are many ways of doing it, but I preffer using a setter because its more explicit in what are you doing.
    Also there is a Doctrine Extension that might help you "Blameable"
    it works like magic via annotations, some guys doesn't like it because it hides details, like in your case, the user must be the logged in user, but that's up to you :)

    Blameable info link:
    https://github.com/Atlantic...

  • 2017-03-17 julien moulis

    Hi. I would like to know if you could give me a piece of advice. I have followed the symfony track and still have a question about mantTomany relation. I have 3 entities user, ticket. I created a third entity "answers" with user id and ticket id as a foreign key and two attributes (message, datecreated). My pb is that when a create a new answer it persists properly in the database with the right ticket id but I can't make the user id which has to be the actuel logged user persist in the database. So I was wondering what would be the best way... FormEvent, DoctrineEvent, directly in the setter of the answer entity, controller... AHHHHH I'm lost... I probably spent much more time eating cookie and watching turtles pictures than listen the course ;-)

  • 2016-12-02 weaverryan

    Haha, that just made my night ;)

  • 2016-12-02 Micheal

    I don't know how you have the time to do all of this, but cheers. :)

  • 2016-11-10 weaverryan

    Hey Vince!

    Hmm, I don't quite understand the setup. I assume that each Product is related to one ProductCategory, correct? And the ProductCategory has "tags"? Or are speed, capacity and screen resolution properties on the ProductCategory? I wasn't clear on that :). And also, is the ProductCategory used to determine the data/fields needed for the Product? I mean, if a Product is related to ProductCategory A, then it will have data for speed, capacity and screen resolution. But if a Product is related to a ProductCategory B, then it may have data for color, width, and height? Is this what your requirements are?

    If so, it's a difficult problem to solve, because basically: this doesn't fit well into a relational database. Some people use a no-SQL database for this, or, more commonly, they use a pattern called EAV, which is a way of organizing data into a relational database that allows for "random" fields to be added to a Product for example. It's fairly simple in some sense, but can also be really inefficient... but it's really the only way to solve the problem (unless you know that you will have a static, small number of categories - e.g. you will always have only 5 categories, then we could create 5 different entities for these).

    Let me know! Cheers!

  • 2016-11-09 Vince Liem

    Hi. I'm wondering what would be the best practice of making an entity about products, product categories and product specifications. For example giving a product category tags like (speed, capacity, resolution screen) and a product that is bound to that category, its values for those tags. But now I have a product category entity with tag1, tag2, tag3 to a max of 15. I know that there must be a better way.

  • 2016-11-09 Hermen

    YES, we're off!!!

  • 2016-11-07 weaverryan

    Yo xdrew!

    Yes! And no! And maybe :). We show using the EntityType with a ManyToMany for checkboxes. And then, we later show the CollectionType with add/remove features once that relationship gets a bit more complicated and has extra fields. So, I'm *pretty* sure we'll be covering exactly what you're looking for :). It'll start coming out this week.

    Cheers!

  • 2016-11-07 xdrew

    I wonder if it possible to render many to many relation as checkboxes (EntityType attributes) but with CollectionType-specific add/remove features? Will that case be covered with this tutorial?)

  • 2016-11-03 weaverryan

    This tut is in the editing phase! It will likely start releasing next week :)

  • 2016-11-02 Hermen

    Ooh yeh, almost...

  • 2016-10-07 Victor Bocharsky

    Well, we're working on this course, Ansible for Automation! and Design Patterns from Space. Most probably they'll be released in the same order I mentioned. That's it for now :)

    Cheers!

  • 2016-10-07 Greg

    Hey Victor,

    Thanks for your answer.
    I already subscribed to this course a long time before I asked ;)

    Do you have a schedule about the next courses ?
    Thanks again

  • 2016-10-06 Victor Bocharsky

    Hey Greg,

    Thank you for kind words! This course in progress right now and most probably it will be released in October/November. You can subscribe to this course and we'll notice you when it happened.

    Cheers!

  • 2016-10-06 Greg

    Hi

    Any idea when this tutorial will be available?

    Thanks for your awesome job ;)

    Greg