Saving Relations
Our Comment
entity has an article
property and an article_id
column in the database:
// ... lines 1 - 10 | |
class Comment | |
{ | |
// ... lines 13 - 31 | |
/** | |
* @ORM\ManyToOne(targetEntity="App\Entity\Article", inversedBy="comments") | |
* @ORM\JoinColumn(nullable=false) | |
*/ | |
private $article; | |
// ... lines 37 - 77 | |
} |
So, the question now is: how do we actually populate that column? How can we relate a Comment
to an Article
?
The answer is both very easy, and also, quite possibly, at first, weird! Open up the ArticleFixtures
class. Let's hack in a new comment object near the bottom: $comment1 = new Comment()
:
// ... lines 1 - 5 | |
use App\Entity\Comment; | |
// ... lines 7 - 8 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 11 - 27 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) use ($manager) { | |
// ... lines 31 - 56 | |
$article->setAuthor($this->faker->randomElement(self::$articleAuthors)) | |
// ... lines 58 - 59 | |
; | |
$comment1 = new Comment(); | |
// ... lines 63 - 65 | |
}); | |
// ... lines 67 - 68 | |
} | |
} |
Then, $comment1->setAuthorName()
, and we'll go copy our favorite, always-excited astronaut commenter: Mike Ferengi:
// ... lines 1 - 8 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 11 - 27 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) use ($manager) { | |
// ... lines 31 - 61 | |
$comment1 = new Comment(); | |
$comment1->setAuthorName('Mike Ferengi'); | |
// ... lines 64 - 65 | |
}); | |
// ... lines 67 - 68 | |
} | |
} |
Then, $comment1->setContent()
, and use one of our hardcoded comments:
// ... lines 1 - 8 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 11 - 27 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) use ($manager) { | |
// ... lines 31 - 61 | |
$comment1 = new Comment(); | |
$comment1->setAuthorName('Mike Ferengi'); | |
$comment1->setContent('I ate a normal rock once. It did NOT taste like bacon!'); | |
// ... line 65 | |
}); | |
// ... lines 67 - 68 | |
} | |
} |
Perfect! Because we're creating this manually, we need to persist it to Doctrine. At the top, use
the $manager
variable:
// ... lines 1 - 8 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 11 - 27 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) use ($manager) { | |
// ... lines 31 - 65 | |
}); | |
// ... lines 67 - 68 | |
} | |
} |
Then, $manager->persist($comment1)
:
// ... lines 1 - 8 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 11 - 27 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) use ($manager) { | |
// ... lines 31 - 61 | |
$comment1 = new Comment(); | |
$comment1->setAuthorName('Mike Ferengi'); | |
$comment1->setContent('I ate a normal rock once. It did NOT taste like bacon!'); | |
$manager->persist($comment1); | |
}); | |
// ... lines 67 - 68 | |
} | |
} |
If we stop here, this is a valid Comment
... but it is NOT related to any article. In fact, go to your terminal, and try the fixtures:
php bin/console doctrine:fixtures:load
JoinColumn & Required Foreign Key Columns
Boom! It fails with an integrity constraint violation:
Column
article_id
cannot be null
It is trying to create the Comment
, but, because we have not set the relation, it doesn't have a value for article_id
:
// ... lines 1 - 10 | |
class Comment | |
{ | |
// ... lines 13 - 31 | |
/** | |
* @ORM\ManyToOne(targetEntity="App\Entity\Article", inversedBy="comments") | |
* @ORM\JoinColumn(nullable=false) | |
*/ | |
private $article; | |
// ... lines 37 - 77 | |
} |
Oh, and also, in Comment
, see this JoinColumn
with nullable=false
? That's the same as having nullable=false
on a property: it makes the article_id
column required in the database. Oh, but, for whatever reason, a column defaults to nullable=false
, and JoinColumn defaults to the opposite: nullable=true
.
Setting the Article on the Comment
ANYways, how can we relate this Comment
to the Article
? By calling $comment1->setArticle($article)
:
// ... lines 1 - 8 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 11 - 27 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) use ($manager) { | |
// ... lines 31 - 61 | |
$comment1 = new Comment(); | |
$comment1->setAuthorName('Mike Ferengi'); | |
$comment1->setContent('I ate a normal rock once. It did NOT taste like bacon!'); | |
$comment1->setArticle($article); | |
$manager->persist($comment1); | |
}); | |
// ... lines 68 - 69 | |
} | |
} |
And that's it! This is both the most wonderful and strangest thing about Doctrine relations! We do not say setArticle()
and pass it $article->getId()
. Sure, it will ultimately use the id in the database, but in PHP, we only think about objects: relate the Article
object to the Comment
object.
Once again, Doctrine wants you to pretend like there is no database behind the scenes. Instead, all you care about is that a Comment
object is related to an Article
object. You expect Doctrine to figure out how to save that.
Copy that entire block, paste, and use it to create a second comment to make things a bit more interesting: $comment2
. Copy a different dummy comment and paste that for the content:
// ... lines 1 - 8 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 11 - 27 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) use ($manager) { | |
// ... lines 31 - 61 | |
$comment1 = new Comment(); | |
$comment1->setAuthorName('Mike Ferengi'); | |
$comment1->setContent('I ate a normal rock once. It did NOT taste like bacon!'); | |
$comment1->setArticle($article); | |
$manager->persist($comment1); | |
$comment2 = new Comment(); | |
$comment2->setAuthorName('Mike Ferengi'); | |
$comment2->setContent('Woohoo! I\'m going on an all-asteroid diet!'); | |
$comment2->setArticle($article); | |
$manager->persist($comment2); | |
}); | |
// ... lines 74 - 75 | |
} | |
} |
And now, let's see if it works! Reload the fixtures:
php bin/console doctrine:fixtures:load
No errors! Great sign! Let's dig into the database:
php bin/console doctrine:query:sql 'SELECT * FROM comment'
There it is! We have 20 comments: 2 for each article. And the article_id
for each row is set!
This is the beauty of Doctrine: we relate objects in PHP, never worrying about the foreign key columns. But of course, when we save, it stores things exactly like it should.
Next, let's learn how to fetch related data, to get all of the comments for a specific Article
.
Hey @Rayen, hey there ,
Doctrine encourage to take over and control transaction demarcation ourself! So how to deal with it? Can you tell me a "good" use-case when to you use it?
Big thanks!