Saving a Relationship
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 SubscribeDoctrine 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:
// ... lines 1 - 10 | |
class GenusNote | |
{ | |
// ... lines 13 - 89 | |
public function getGenus() | |
{ | |
return $this->genus; | |
} | |
public function setGenus($genus) | |
{ | |
$this->genus = $genus; | |
} | |
} |
Add a Genus
type-hint to setGenus()
:
// ... lines 1 - 10 | |
class GenusNote | |
{ | |
// ... lines 13 - 94 | |
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!
// ... 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
:
// ... 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:
// ... 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:
// ... 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
:
// ... 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
:
// ... 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.
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?