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 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?
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.
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.
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.1.*", // v3.1.4
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.4
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
"doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0", // v3.1.3
"nelmio/alice": "^2.1", // 2.1.4
"doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
}
}