Buy

Doctrine will create a genus_id integer column for this property and a foreign key to genus.

Use the "Code"->"Generate" menu to generate the getter and setter:

95 lines src/AppBundle/Entity/GenusNote.php
... lines 1 - 10
class GenusNote
{
... lines 13 - 84
public function getGenus()
{
return $this->genus;
}
public function setGenus($genus)
{
$this->genus = $genus;
}
}

Add a Genus type-hint to setGenus():

95 lines src/AppBundle/Entity/GenusNote.php
... lines 1 - 10
class GenusNote
{
... lines 13 - 89
public function setGenus(Genus $genus)
{
$this->genus = $genus;
}
}

Yes, when we call setGenus(), we'll pass it an entire Genus object not an ID. More on that soon.

Generate the Migration

Generate the migration for the change:

./bin/console doctrine:migrations:diff

And then go check it out... Wow - look at this!

33 lines app/DoctrineMigrations/Version20160207091756.php
... lines 1 - 10
class Version20160207091756 extends AbstractMigration
{
public function up(Schema $schema)
{
... lines 15 - 17
$this->addSql("ALTER TABLE genus_note ADD genus_id INT DEFAULT NULL");
$this->addSql("ALTER TABLE genus_note ADD CONSTRAINT FK_6478FCEC85C4074C FOREIGN KEY (genus_id) REFERENCES genus (id)");
$this->addSql("CREATE INDEX IDX_6478FCEC85C4074C ON genus_note (genus_id)");
}
... lines 22 - 31
}

Even though we called the property genus, it sets up the database exactly how you would have normally: with a genus_id integer column and a foreign key. And we did this with basically 2 lines of code.

Run the migration to celebrate!

./bin/console doctrine:migrations:migrate

Now, how do we actually save this relationship?

Saving a Relation

Head back to GenusController. In newAction(), create a new GenusNote - let's see how we can relate this to a Genus:

109 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 12
class GenusController extends Controller
{
... lines 15 - 17
public function newAction()
{
$genus = new Genus();
$genus->setName('Octopus'.rand(1, 100));
$genus->setSubFamily('Octopodinae');
$genus->setSpeciesCount(rand(100, 99999));
$note = new GenusNote();
... lines 26 - 37
}
... lines 39 - 107
}

I'll paste in some code here to set each of the normal properties - they're all required in the database right now:

109 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 24
$note = new GenusNote();
$note->setUsername('AquaWeaver');
$note->setUserAvatarFilename('ryan.jpeg');
$note->setNote('I counted 8 legs... as they wrapped around me');
$note->setCreatedAt(new \DateTime('-1 month'));
... lines 30 - 109

So how can we link this GenusNote to this Genus? Simple: $note->setGenus() and pass it the entire $genus object:

109 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 24
$note = new GenusNote();
$note->setUsername('AquaWeaver');
$note->setUserAvatarFilename('ryan.jpeg');
$note->setNote('I counted 8 legs... as they wrapped around me');
$note->setCreatedAt(new \DateTime('-1 month'));
$note->setGenus($genus);
... lines 31 - 109

That's it. Seriously! The only tricky part is that you set the entire object, not the ID. With Doctrine relations, you almost need to forget about ID's entirely: your job is to link one object to another. When you save, Doctrine works out the details of how this should look in the database.

Don't forget to persist the $note:

109 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 12
class GenusController extends Controller
{
... lines 15 - 17
public function newAction()
{
... lines 20 - 32
$em->persist($genus);
$em->persist($note);
$em->flush();
... lines 36 - 37
}
... lines 39 - 107
}

And, you can persist in any order: Doctrine automatically knows that it needs to insert the genus first and then the genus_note. That's really powerful.

Defaulting the isPublished Field

And simple! Head to the browser to check it out - /genus/new. Whoops - an error: the is_published property cannot be null. My bad - that's totally unrelated.

In Genus, give the $isPublished field a default value of true:

95 lines src/AppBundle/Entity/Genus.php
... lines 1 - 10
class Genus
{
... lines 13 - 39
/**
* @ORM\Column(type="boolean")
*/
private $isPublished = true;
... lines 44 - 93
}

Now, if you forget to set this field - it'll default to true instead of null.

Woo! No errors this time. Check out the queries for the page. Nice! Two insert queries: INSERT INTO genus and then INSERT INTO genus_note using 46: the new genus's ID.

With two lines to setup the relationship, and one line to link a GenusNote to a Genus, you've got a fantastic new relationship.

Leave a comment!

  • 2016-09-02 Paolo Piazza

    Hi weaverryan, thank you very much for your kind reply!
    Take care!

  • 2016-08-25 weaverryan

    Hey @Paolo!

    Cool question! And yes! This is done with the JoinColumn: https://knpuniversity.com/scre..., specifically the onDelete setting. I usually let it stay as RESTRICT, until I know that I definitely want CASCADE (since that's inherently a little more dangerous). As far as I know, there is no onUpdate control, which might be on purpose, as you should really never have a reason to update the id of a row in a table.

    Oh, and obviously, after you add the onDelete, don't forget to generate a migration to actually tweak things in the table!

    Cheers!

  • 2016-08-24 Paolo Piazza

    Hi @weaverryan ,

    thanks very much for your nice tutorials, they are really helping me a lot!
    I've got a quick question for you; so, ok, I've created and saved the relationship as in your example, but when examining the relations for the genus_note table via phpmyadmin, I've notice that for the genus_id field I have ON DELETE RESTRICT and ON UPDATE RESTRICT.

    How is it possible to set up this relationship in such a manner to have, for instance, ON DELETE SET NULL and ON UPDATE CASCADE?

    Thanks!

  • 2016-08-11 weaverryan

    Hey Zoé!

    You're right! The code doesn't match up with the video variable name :). I've just fixed this (https://github.com/knpuniversi... and it should be up shortly.

    Thanks!

  • 2016-08-11 Zoé Belleton

    In the Script code there is an error ( it's $genusNote->setSomething not $note )

  • 2016-07-29 weaverryan

    Hey Nate!

    This is probably just a small mapping thing. The PHP code in GenusNote should look identical to mine and so will all the rest of the code (controller, form, etc). I think you already know that - but just for others :).

    For the YAML-equivalent of the annotations, check out the first code-block here: http://symfony.com/doc/current.... there's a YAML tab: you can use that to compare YAML with annotations. As you can see, the genus property should *not* be under the "fields" key, like normal properties/columns.

    One quick word of warning with YAML: for various reasons, the Doctrine core team will remove YAML support from Doctrine version 3. So, unfortunately, YAML is not a great long-term plan (however, Doctrine already has tools to convert to different formats, like XML or annotations).

    Cheers!

  • 2016-07-29 Nathaniel Kolenberg

    Hi weaverryan,

    Thanks for the great tutorial! I've followed along but tried a slightly different route, by creating the entities via doctrine using ```bin/console doctrine:generate:entity``` and choosing yml as the mapping format. This basically worked the same up until now when we're trying to save the relationships.

    The problem that I'm having is that in this way, I need to map all the GenusNote entity's properties via GenusNote.orm.yml. Specifically, I need to define a type for the genus property on the GenusNote entity, but no matter what type I choose, I keep getting either that genus can't be converted to the type I choose or that the data for column genus in the database is too long.

    Could you give me any advice on this? Should I just start over with the entities and follow your directions?

    Thanks in advance!

    N8

  • 2016-07-28 Artjan van Horssen

    Hi Ryan.

    Thanx for the quick response and the good explanation about the relationships. Everything works fine now!

  • 2016-07-28 weaverryan

    Hi Artjan!

    Actually, this is expected! For a detailed explanation, check out our Symfony2 tutorial, which talks about OneToMany relationships: http://knpuniversity.com/scree...

    Basically, when you create a ManyToOne relation, this will cause the foreign key column to be added to the database (e.g. genus_id). But, when you add a OneToMany relation, this is *not* a new relationship: it's the *same* relationship seen from the opposite direction. In other words, you can say that "many GenusNote have one Genus" (ManyToOne) or you can say "one Genus has many GenusNotes" (OneToMany). So when you map the OneToMany, there is no change to the database: you're simply adding a convenience layer on Doctrine so that it's easier to use your relationship (e.g. you can now say $genus->getNotes() and it will query for all the related GenusNote objects).

    Let me know if this makes sense! Understanding the "owning" versus "inverse" side of a relation is quite difficult. We're planning on making a Doctrine relations tutorial that will talk about this and ManyToMany relationships.

    Cheers!

  • 2016-07-28 Artjan van Horssen

    Hi Ryan,
    Thanx voor de great and easy to follow tutorials!

    I have a little weird thing with the migrations. When i run ./bin/console doctrine:migrations:diff i get the message that a mappedBy attribute is needed. When adding this "@ORM\OneToMany(targetEntity="Genus", mappedBy="genus_note")" and run again; i get the message that there are no changes detected. Unfortunately i can't run the migrate because there is no new file.

    Any suggestion what's wrong?

  • 2016-06-21 weaverryan

    Hey JLChafardet!

    Ok cool! It sounds to me like you have the following relationships (just looking at the owning sides):

    1) Operation is ManyToOne to OperationType (so it will have an operation_type_id).
    This means that each Operation will have *one* OperationType. And if you looked in the database, you'll find that one OperationType may be assigned to many Operations.

    2) Transaction is ManyToOne to Operation (so it will have an operation_id)
    This means that each Transaction will have *one* Operation. And if you looked in the database, you'll find that one Operation may be assigned to (own) many Transactions.

    So you asked basically "how do I get a Transaction from an OperationType?". But the better question is, "Howe can I find *all* Transactions for an OperationType". This is because 1 OperationType will potentially have many Operations, and each Operation will potentially have many Transactions.

    The answer is to map the inverse - OneToMany side of both relationships. Specifically, you would:

    A) Add an operations property to OperationType that is a OneToMany relation to Operation.
    B) Add a transactions property to Operation that is a OneToMany relation to Transaction.

    Then, you could have code like this:


    $operationType = // some way to get a single OperationType
    $allOperations = $operationType->getOperations(); // use the OneToMany relationship property

    $allTransactions =[];
    foreach ($allOperations as $operation) {
    $allTransactions = array_merge(
    $operation->getTransactions(), // use the OneToMany relationship property
    $allTransactions
    );
    }

    This would give you a giant array. You could also create a query in the database to do this directly. We have some details about those types of queries here: http://knpuniversity.com/scree...

    Good lucks! This stuff is the hardest part of Doctrine in my opinion: trying to keep your head straight about all the different directions for each relationship. Always think back to how you want it to look in the database. Wherever you have the foreign key column (e.g. operation_type_id) will be the entity that should have the ManyToOne side of the relationship.

    Cheers!

  • 2016-06-18 JLChafardet

    well TableA is the one with relationships to both TableAType and
    TableB, TableAType shouldnt have any way to get to TableB without going
    through TableA

    see it this way, a bit more natural

    operation (TableA)
    OperationType(TableAType)
    Transaction(TableB)

    Each Operation has an operation Type (but the owner should be Operation not OperationType)
    each Operation can have MANY transaction, but each Transaction can belong to a single operation.

    so in this spirit, to get a Transaction from an Operation Type, it would have to go through Operation first, as there is no relation at all other than the OperationId on the Transaction ?

    hmmm I'm starting to see a way to "join" these 2 columns.(gota learn more on doctrine joins tho.)

  • 2016-06-18 weaverryan

    Yo JLChafardet!

    First, you described all the relationships perfectly - so nice job - your pretend setup is very clear. But I'm not completely clear on the question - but let me take a shot.

    First, with OneToOne relationships, you can choose which side is the "owning" side and which side is the "inverse" side. Make the "owning" side which ever side of the relationship you will more likely want to set.

    Suppose you have a `$tableA` object, then you can do things like this:


    // this assume you've called your property tableAtType inside TableA (and made a corresponding getter function for it)
    // depending on your setup, this may be the inverse side of your relationship. If you want to do something like this,
    // then you *must* map the inverse side
    echo $tableA->getTableAType()->getName(); // print some make-believe getName() method

    // this assumes you have mapped the *inverse* (optional), OneToMany relationship in TableAType to TableB on a property called tableBs, with a corresponding getter function.
    $tableBs = $tableA->getTableAType()->getTableBs();
    foreach ($tableBs as $tableB) {

    }

    I hope that helps a bit!

  • 2016-06-16 JLChafardet

    weaverryan

    a question here on relationships.

    lets assume the following scenario.

    I have a relationship between a couple of tables

    TableA and TableA-type in a 1-1 relationship (too many TableA-types to really repeat them over and over in the db)

    and TableA at the same time is on a 1 - n relationship with TableB,
    where Many TableB records belong to a single TableA record.

    how would I do, to get the information from TableA and get me its TableA-type and its TableB childrens?

    the inverse view isnt a bad idea to have, but for the time beign i can live without it.

  • 2016-05-18 Kosta Andonovski

    hey brother I figured it out over the weekend. Thanks so much for the reply though awesome work man awesome.

  • 2016-05-18 weaverryan

    Hi Kosta!

    No matter what, if you want to link a GenusNote to its Genus, you will do it via `$genusNote->setGenus($genus)`. If you imagine an endpoint that the user can submit to in order to create a new GenusNote, it might look like this:


    /**
    * @Route("/genus/{id}/notes")
    */
    public function newNoteAction(Genus $genus, Request $request)
    {
    // Symfony queries for the Genus object automatically
    $genusNote = new GenusNote();
    // populate this object with data from the request, probably
    // with a Form

    $genusNote->setGenus($genus);
    // get the entity manager, persist($genusNote), flush() and redirect
    }

    So ultimately, you just need to get a Genus object. You might create this (like we did in this chapter). Or more likely, you will query for the Genus object and then set it on the GenusNote.

    Let me know if this helps!

    Cheers!

  • 2016-05-14 Kosta Andonovski

    Hello again Ryan, what if I didn't want to make a genusnote everytime I made a genus. so lets say the note is added at a later time. How would you link the correct $user then?

  • 2016-03-29 weaverryan

    Hi Ziad!

    Ah, that makes things a bit more complex :). I'm sure you're aware, but OpenSky was the company that famously setup their Doctrine to do types of things like this. There are some resources out there (http://doctrine-orm.readthedoc..., http://www.slideshare.net/spf1..., but I've never done it myself. I would certainly see if you can hook this up like the cookbook article. BUT, if that gives you any trouble, personally, I would go a very old-fashioned route: do *not* map relationships between the ORM and ODM. Instead, I would map the id's only (e.g. I would have a GenusNote.genusId normal column, and it would contain the mongo ID of the related Genus object). It would be *your* job to manually query for the Genus whenever you needed it (of course, you'd have some service functions to make this easy and not repeat yourself). This is much less convenient, but way more predictable. You *would* need to also control the order that things are persisted. And yes, you *could* also add a listener - I think on postLoad - and use it to automatically use the genusId value to query for the Genus object from Mongo, for example. You just need to weigh if that's worth it, since you may not always need this object.

    That's a long way of saying that I don't have personal experience here. So if you can follow the cookbook and have good success, then awesome! Otherwise, treat these like two unrelated data stores (imagine storing some things in Redis and other things in MySQL - we probably wouldn't try to hook up some fancy relation system).

    Cheers!

  • 2016-03-27 Ziad Jammal

    Hi Ryan,
    Thank you for the great tutorial.
    Assuming that the Genus object and the GenusNote Object are both persisted in different databases. One in MySQL and one in Mongo.
    At which level do you you control how the GenusNote document should be persisted (key and value structure). Would you do that in a doctrine listener such as pre-persist? and upon fetching the relation, at what stage would you convert the document to an object?