Chapters
28 Chapters
|
2:24:59
|
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!
16.
ManyToMany with Extra Fields
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.
This course is built on Symfony 3, but most of the concepts apply just fine to newer versions of Symfony.
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": "^7.1.3",
"symfony/symfony": "3.4.*", // v3.4.49
"doctrine/orm": "^2.5", // 2.7.5
"doctrine/doctrine-bundle": "^1.6", // 1.12.13
"doctrine/doctrine-cache-bundle": "^1.2", // 1.4.0
"symfony/swiftmailer-bundle": "^2.3", // v2.6.7
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.23.0
"sensio/distribution-bundle": "^5.0", // v5.0.25
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
"incenteev/composer-parameter-handler": "^2.0", // v2.1.4
"composer/package-versions-deprecated": "^1.11", // 1.11.99.4
"knplabs/knp-markdown-bundle": "^1.4", // 1.9.0
"doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
"stof/doctrine-extensions-bundle": "^1.2" // v1.3.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.7
"symfony/phpunit-bridge": "^3.0", // v3.4.47
"nelmio/alice": "^2.1", // v2.3.6
"doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
}
}
46 Comments
Hey there ,
I'm re phrasing my original question because it was marked as spam for some reason...anyway:
I have two entities: Product & File with a ManyToMany relationships but since i needed extra fields i switched it to two OneToMany relationships with a middle table called ProductFile.
I'm also using apiplatform with symfony 5 and before this change, if i wanted to create a new product and add an existing file to it, the body request looked like this:
`
{
"title": "string",
"description": "string",
"files": [
]
}
`
After the change, it looks like this:
`
{
"title": "string",
"description": "string",
"productFiles": [
]
}
`
Which result in an error:
<blockquote>A new entity was found through the relationship 'App\Entity\Product#productFiles' that was not configured to cascade persist operations for entity: App\Entity\ProductFile@00000000590c148e0000000001d14e6f. 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\"}). If you cannot find out which entity causes the problem implement 'App\Entity\ProductFile#__toString()' to get a clue.</blockquote>
Can you help me with this please? P.S. the code formatting might have some issues? Sorry for that, i can't make it work
Hey Petru L.
I believe your problem relies on setting both sides of the relationship, double-check if your
setteroraddFile()method is setting the relationship between objects. That's the first thing I'd check. Let me know if it workedCheers!
Hey MolloKhan ,
I'm sorry for the late response, been a bit busy this week. I'm not sure i understand what you mean by 'setting both sides of the relationship'. I'm pasting my code in case it helps:
ProductFile.php ( the join table )
`class ProductFile
{
}`
On the File entity:
`public function addProductFile(ProductFile $productFile): self
Also, i don't know why the code formatting is no longer working, even though i have it enclosed in it's tags
I love using Symfony 4's make:entity, but I'm a bit confused as to how to use it to create a Many-to-Many relationship with extra fields. Is this correct?
* Make Genus and User Entities
* Make UserGenus entity, with ManyToOne relationship to User, and ManyToOne relationship to Genus
The related methods then come out to something like $genus->getUserGenuses(). Something doesn't feel right, though, in this setup.
Hey Tac :D
This is 100% correct. Yes,
$genus->getUserGenuses()does sound weird. Unfortunately, once you need an extra field, you can't think about the relationship as "Genus" and "User" anymore. You need to think about it as "Genus" has many "UserGenuses" and "User" has many "UserGenuses". Sometimes, depending on the naming, this feels natural... and sometimes it doesn't. For example, the naming we used here Genus <-> GenusScientist <-> User feels more natural than Genus <-> GenusUser <-> User... even though these are the EXACT same thing, except for calling the middle entity GenusScientist instead of GenusUser.Cheers!
I'm afraid I didn't get the tutorial for what I needed. I want to make a create/edit form that contains two entities related manytomany with extrafield, and that creates/edits both at the same time, on the fly. After a long time trying, I haven't been able to do it. Do you have a tutorial or do you know where to see an example? Thanks
Hey Jedediah,
We're sorry you didn't answer all your questions with this tutorial! It looks like you're interested in Symfony Forms also, have you seen our tutorials about forms: https://symfonycasts.com/sc... for Sf3 or https://symfonycasts.com/sc... for Sf4? It might bring you more context about forms. Anyway, why you haven't been able to do that? Did you have any errors trying to do that? As I understand you were able to configure ManyToMany with Extra Fields in your project, right? So, the problem in a compound form for them only? We would be glad to help with your question in comments.
Cheers!
Hey, excellent team!
What would be the proper way to implement the following: Let's suppose, via GenusFormType, we need to list all (i have only 10 fixed names) user names by checkboxes + an extra field "numberOfYears" with each of them. Till now, without any extra field that was simpler, I had ManyToMany relation between "Genus" and "User" and in my GenusFormType: "user, EntityType::class, ['class'=>User::class, 'multiple' =>true, 'expanded'=>true,'choice-label'=>'name'] ..." and that was working finely, but after adding an extra field like "numberOfYears", all changes...I created as in this tutorial, "GenusUser" entity with "numberOfYears" property. Then, in my GenusFormType, should i introduce them via CollectionType? Thanks
Hey Alexander,
Yeah, ManyToMany relation is implemented with an auxiliary table behind the scene where Doctrine keep entities IDs. And as soon as you need to add extra data on this auxiliary table - you can't use simple ManyToMany relation anymore, you would need a new entity and 2 relations instead: oneToMany and manyToOne to it. So, in your case you need 3 entities: User, Genus, UserGenus (but you can called it whatever you want to make more sense for you). And so, User relates to UserGenus as OneToMany, and Genus relates to UserGenus as OneToMany as well, so you don't have direct relations between User and Genus, it's done via UserGenus, where you can add whatever extra data you want, like numberOfYears as in your case.
P.S. These screencasts might be helpful for you:
https://symfonycasts.com/sc...
And actually, we even have a screencast about this topic, but for Symfony 3 only, thought it's not important as you just need to configure relations in your entities: https://symfonycasts.com/sc...
I hope it's clear and helps!
Cheers!
Thanks, Victor!
That's exactly i'had done.
Sorry for my re-question, but i'm a newbie in Symfony
I just wanted to know, if there was a Symfony way to show all users via Checkoxes its "own" extra field with each one. That's the particularity of my case.
To resolve this, I've coded hardly in php/html way: input type=checkbox ... etc. Then i retrieve Ids and set them as foreigh keys into "UserGenus" table with so desired extra field data.
But, i'd like so to know Symfony shortcut, if there's any.
Yours
Hey Alexander,
That's great! So, what I said in my previous message is really a "Symfony" way to handle extra fields on manyToMany relation. I'm not sure about your forms, you would probably need 3 forms now: one for User entity, one for UserGenus, and one for Genus - they probably will be nested to each other. And along with CollectionType you can build a one form that will handle all this, for your information, you can specify your custom forms in "entry_type" option of CollectionType like:
I hope this helps!
Cheers!
Thanks a lot Victor, that's clear and i'm eager to try it!
Have a good day
Shouldn't it be like this...
class Genus {
// Instead: public function addGenusScientist(User $user)
public function addGenusScientist(GenusScientist $genusScientist) {}
// Instead: public function removeGenusScientist(User $user)
public function removeGenusScientist(GenusScientist $genusScientist) {}
}
Hey Mina!
You're totally right :). We leave that broken in this video (we mention it's broken, but only briefly). We fix it a bit later - https://knpuniversity.com/s...
Sorry if that confused you!
Cheers!
I'm a little confused by the mappedby attribute on scientists in Genus
/**
* @ORM\OneToMany(targetEntity="GenusScientist", mappedBy="Genus", fetch="EXTRA_LAZY")
* @ORM\JoinTable(name="genus_scientists")
*/
private $scientists;
Why is it not mapped by User? Isn't that the join table for scientists? Or is it referring to the class that $scientists exists in?
Hey Sara,
It used to be a join table if we had a simple ManyToMany relation between User and Genus, but since we need to store extra data on this join table - we can't use simple ManyToMany relation. That's why we provide a new entity - GenusScientist - that allow as to store extra data. So, we no more have simple ManyToMany but two relations: OneToMany and ManyToOne. In other words, we expand ManyToMany relation into 2 simple OneToMany and ManyToOne that technically is the same as ManyToMany in the database, but now we can add extra data (add extra columns on GenusScientist) to this intermediate table with Doctrine. I hope it clear to you now. I'd also recommend to re-watch this video one more time because yeah, it's kinda complex.
Cheers!
Generating the migration creates two new tables: genus_scientist_genus, genus_scientist_user. How can I avoid that?
GenusScientist.php
Genus.php
User.php
Hey Sergiu,
You should use "ManyToOne" relationships instead of "ManyToMany" in GenusScientist class, since you use "OneToMany" in Genus and User. I bet you have an invalid mapping if you do "bin/console doctrine:schema:validate" - it's a nice tip btw ;)
So the result GenusScientist entity mapping should be:
Cheers!
Thanks. Copy-pasta error
Great! Sorry for no code blocks in the recent chapters - I'll add it soon
Ah hah! I got it, finally! The tutorial uses doctrine/dbal 2.5.4, and there is a behavior change in doctrine/dbal 2.5.5! Here is the issue about it: https://github.com/doctrine/dbal/issues/2501
So, it's quite an annoying thing. There are 2 fixes:
1) Downgrade to doctrine/dbal 2.5.4. This would mean adding the following line to your composer.json file:
Then run
composer update2) Manually rename
genus_scientistto something else (e.g.genus_scientist_old) and then generate the migration. Then, rename the table back. The generated migration will be incorrect, because it will think that you need to create a genus_scientist table, but we do not. So, you'll need to manually update the migration code by hand and test it.Ultimately, the bug in Doctrine only prevents us from automatically generating the migration file. If you can write that file by hand, or get it partially-generated and then fix it, all is right with the world after.
Thanks for all the information guys - I couldn't reproduce this for a LONG time and you finally gave me enough information to do that!
Cheers!
Woohoo! I know what you mean - there are so many subtle options with relationships... but if you get them right, man, Doctrine relations really kill it.
Cheers and good luck!
Hello
When I type in the console
php bin/console doctrine:migrations:diff
I have the error:
[Doctrine\Common\Annotations\AnnotationException]
[Semantical Error] The annotation "@Doctrine\ORM\Mapping" in property AppBundle\Entity\GenusScient
ist::$genus does not exist, or could not be auto-loaded.
Please, help me understand how can I to fix this?
Hey Nina,
Have you imported namespace "use Doctrine\ORM\Mapping as ORM;" before GenusScientist class declaration? If so, could you show us your GenusScientist::$genus property definition with its annotations?
Cheers!
Yes, I imported namespace "use Doctrine\ORM\Mapping as ORM;" before GenusScientist class declaration.
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="genus_scientist")
*/
class GenusScientist
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM|ManyToOne(targetEntity="Genus", inversedBy="genusScientists")
* @ORM|JoinColumn(nullable=false)
*/
private $genus;
I understood my mistake )
I forgot to write
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="studiedGenuses")
* @ORM\JoinColumn(nullable=false)
*/
private $user;
in the User.php
Great, so everything works fine now?
I noticed that you have a pipe " | " instead of inverted backslash " \ " in GenusScientist::$genus property
Cheers!
Hey,
I have a little error when I want to make my migrations:diff.
"There is no column with name 'id' on table 'genus_scientist' "
But I have this one.
If I rename my old table the migration is ok and create the new table, how can I have like you an update and not a creation ?
Thanks again.
Greg
Hey Greg!
Hmm, when exactly do you get this error? Is it when you run
doctrine:migrations:diff? And are you using MySQL or something different? Oh, and did you remove the ManyToMany on Genus and User (i.e. change them to OneToMany and give each the mappedBy option instead of inversedBy). And last thing :)... do you definitely have the @ORM\Column annotation above your id property in GenusScientist (with two stars to begin the comments -/**).I'm listing a bunch of possible tiny things because this error tells me two things:
1) Something is pointing to an "id" column on
genus_scientist. I'm not sure what this is. We added theidcolumn in the screencast... but nothing really depends on it. Your error seems to suggest that there is either a foreign key or some sort of index that's referring to this.2) And of course, this error tells me that Doctrine doesn't see the
idcolumn ongenus_scientist(you might have it in your database, but Doctrine doesn't see any evidence for it when it reads all of its mapping annotation metadata).Let me know if this helps! Cheers!
Hi Ryan
Yes I have this error when I run the doctrine command. I use MySQL and I change the relation in Genus and User by the OneToMany.
I do exactly the same as the tutorial but if I rename my old genus_scientist table the migration is ok.
I will check again this evening.
Thanks again for your really awesome works.
Cheers
> but if I rename my old genus_scientist table the migration is ok
Do you mean that if you manually rename the table in your database, then the migration generates with no errors? If so, that's very interesting - let me know! :)
Cheers!
Yes I renamed it in sequel pro, but I have a migration with a create table not an alter table.
Hey Greg!
I was just looking for a bit more information, because I'm not convinced I've given you enough to solve this yet (and I'm curious about what's going on). As I said earlier, for some reason, Doctrine is looking at your annotation metadata and expecting there to be an "id" column on your genus_scientist table and not finding it. This can happen for a few reasons:
1) You have an Index above one of your entities referring to this column
2) There is a foreign key constraint that references this column (this would be a JoinTable or JoinColumn - did you remove the JoinTable from Genus?)
If none of the pointers are helping you find a possible cause, re-run the command that causes the error with a -vvv flag (so
bin/console doctrine:migrations:diff -vvv) and then paste the output here / take a screenshot. I'm curious to see exactly where the error is coming from.Cheers!
Hey Ryan
This is the result of the command
https://gist.github.com/Gre...
I hope that is help you to understand.
Cheers.
Hey Greg!
Ah, it does! Well, sort of :). I was thinking about the problem incorrectly, but your gist cleared it up. Basically, when Doctrine tries to calculate what is different in your
genus_scientisttable between the database and your annotations metadata, for some reason, it thinks that both the old and new tables have an index on it on the columnid. The question is why? It could be a bug in Doctrine, after all, there is the small bug I mention in the screencast, even when the diff works. If that's that case, I'm not sure why you're seeing it and I'm not (I also haven't heard anyone else mention this yet, but this is also a new screencast - so time will tell). Or, there is something very subtly wrong in your code somewhere. If you are willing, I would love to see your GenusScientist, Genus and User entities, to see if I can spot anything.Cheers!
Hey Ryan
I will send you my entities this evening for me ( I am french ).
Thanks you again for your time.
Sorry for the delay this is my entities
https://gist.github.com/Gre...
Like Stéphane , doctrine/dbal v2.5.5
Cheers.
Hey Ryan,
For information, I have the same error than Greg also.
Dang! Then, I wonder if it's a Doctrine or MySQL weird version problem. What version of MySQL do you have? And what version of doctrine/dbal? You can run
composer infoto find out.Thanks for letting me know - hopefully we can find out what the issue is :).
Cheers!
I use MySQL Ver 14.14 Distrib 5.7.16, for Linux (x86_64) using EditLine wrapper and doctrine/dbal v2.5.5.
Cheers.
Hello,
One question.
You got 'FOREIGN KEY FK_66CF3FA885C4074C'
Do we need to use indexes: @ORM\Table( indexes={@ORM\Index(name="user_fk_idx"... ??
When and where are indexes important?
Hey Axa!
Normally you don't have to worry about indexes, Doctrine will take care for you. Unless you already have a running app, and you already configured you DB, in that case, I think you will have to specify your indexes for your tables, but I'm not 100% sure about this (I'm just guessing)
Cheers!
Hello Diego,
I am confused.
@ORM\Table(indexes={@ORM\Index(name="name_idx", columns={"name"})})
So, we do not need to do this because Doctrine will add indexes? Or you speak only about foreign keys?
Foreign keys (for relationships) and primary keys should be managed automatically by Doctrine, but if you want to index a field, you will have to do it as you said above. I'm not an expert about how to manage indexes, but maybe if you tell me more about your use case, I'll be able to help you more :)
I only want to ask whether we need to index a field. I did not notice in KNP tutorials that you put index somewhere. Have you used them in your projects or ignored them?
Hey @axa,
Yes, you're right, probably we do not show it in our screencasts. And probably the main reason for that is simplicity. Sometimes you need to add indexes, but it makes sense to do when you need to improve performance, like when you often JOIN or search by those columns, i.e. you can see your website is going to be slow due to some queries, you can debug those slow queries and in some cases you'll find that you just need to add an index to a column and then queries will be fast as they were before. So, this question does not related to Doctrine but to DB in general, Doctrine just gives a way to do it. I mean, when to add indexes you need to know from your DB architecture and from queries you execute in your application.
What about foreign keys, you don't need to add indexes for those columns, because a foreign key can be considered as an index.
But keep in mind, that every index could slow down write performance, so you need carefully add indexes, i.e. add it only when you *really* need them. Otherwise you'll get more bad than good.
Cheers!
Thanks
"Houston: no signs of life"
Start the conversation!