Saving Relations
Our answer
table has a new question_id
column. Cool... but how do we populate that column? How do we relate an Answer
to a Question
? This is actually pretty easy... but it might feel weird if you're used to working with databases directly.
Open up src/DataFixtures/AppFixtures.php
. We're using Foundry to add rich fixtures, or fake data, into our project.
// ... lines 1 - 2 | |
namespace App\DataFixtures; | |
use App\Entity\Question; | |
use App\Factory\QuestionFactory; | |
use Doctrine\Bundle\FixturesBundle\Fixture; | |
use Doctrine\Persistence\ObjectManager; | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager) | |
{ | |
QuestionFactory::createMany(20); | |
QuestionFactory::new() | |
->unpublished() | |
->many(5) | |
->create() | |
; | |
$manager->flush(); | |
} | |
} |
But to see how relationships work, let's do some good ol' fashioned manual coding.
Creating some Dummy Question and Answer Objects
Start by creating a new Answer
object... and populate it with enough data to get it to save. Repeat this to create a new Question
object... and also give that some data.
// ... lines 1 - 10 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager) | |
{ | |
// ... lines 15 - 22 | |
$answer = new Answer(); | |
$answer->setContent('This question is the best? I wish... I knew the answer.'); | |
$answer->setUsername('weaverryan'); | |
$question = new Question(); | |
$question->setName('How to un-disappear your wallet.'); | |
$question->setQuestion('... I should not have done this...'); | |
// ... lines 30 - 34 | |
} | |
} |
Save these boring objects to the database by calling $manager->persist()
on both of them.
// ... lines 1 - 10 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager) | |
{ | |
// ... lines 15 - 22 | |
$answer = new Answer(); | |
$answer->setContent('This question is the best? I wish... I knew the answer.'); | |
$answer->setUsername('weaverryan'); | |
$question = new Question(); | |
$question->setName('How to un-disappear your wallet.'); | |
$question->setQuestion('... I should not have done this...'); | |
$manager->persist($answer); | |
$manager->persist($question); | |
// ... lines 33 - 34 | |
} | |
} |
Cool. If we stop now, these objects won't be related... and the Answer
won't even save! Try it:
symfony console doctrine:fixtures:load
And... woh! My bad! We generated a migration in the last chapter, and then I totally forgot to run it! Time to do that:
symfony console doctrine:migrations:migrate
Now try the fixtures:
symfony console doctrine:fixtures:load
JoinColumn
That's the error I expected:
question_id
cannot be null on theanswer
table
That's because we made question_id
required: it was one of the questions that make:entity
command asked us. Oh, and I can show you where this is configured. Open up the Answer
class and find the question
property. It's this JoinColumn(nullable=false)
: that makes the question_id
column required.
// ... lines 1 - 10 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager) | |
{ | |
// ... lines 15 - 22 | |
$answer = new Answer(); | |
$answer->setContent('This question is the best? I wish... I knew the answer.'); | |
$answer->setUsername('weaverryan'); | |
$question = new Question(); | |
$question->setName('How to un-disappear your wallet.'); | |
$question->setQuestion('... I should not have done this...'); | |
$manager->persist($answer); | |
$manager->persist($question); | |
// ... lines 33 - 34 | |
} | |
} |
Relating an Answer to a Question
Anyways, the thing we want to know is: how can I relate this Answer
to this Question
? How do we say that the Answer
belongs to the Question
? It's as simple as $answer->setQuestion($question)
.
// ... lines 1 - 10 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager) | |
{ | |
// ... lines 15 - 28 | |
$question->setQuestion('... I should not have done this...'); | |
$answer->setQuestion($question); | |
$manager->persist($answer); | |
// ... lines 34 - 36 | |
} | |
} |
Notice that we do not say $question->getId()
. We're not passing the ID to the question
property, we're setting the entire Question
object onto the property. Doctrine will be smart enough to save these in the correct order: it'll save the question
first, grab its new id, and use that to save the Answer
.
To prove it, reload the fixtures:
symfony console doctrine:fixtures:load
Ok, no errors. Let's see what the database looks like. We can use the doctrine:query:sql
command as an easy way to do this: SELECT * FROM answer
.
symfony console doctrine:query:sql 'SELECT * FROM answer'
Yes! We have one
answer in the database and its question_id
is set to 103. Let's query for that question:
symfony console doctrine:query:sql 'SELECT * FROM question WHERE id = 103'
And... there it is!
The big takeaway here is this: in PHP, we just think about objects. We think:
Hey! I'd really like to relate this
Answer
object to thisQuestion
object.
Then, when we save these, Doctrine handles all the nitty gritty details of figuring out how to save that for us. The database is almost an implementation detail that we don't need to think about much.
Next: now that we've seen how to relate objects, let's update our fixtures to use Foundry. That will let us create a ton of fake questions and answers and relate them with very little code.
Hi, I am trying to make a migration. I am prompted with the following error
In MappingException.php line 364:
Class "App\Entity\Question" is not a valid entity or mapped super class.
I am removing the "type:annotation" from the doctrine.yaml and it seems to work BUT when I finally make a migration I am prompted with:
[WARNING] No database changes were detected.
The database schema and the application mapping information are already in sync.
Also when I try to run "symfony console doctrine:fixtures:load" I get the same error.
At localhost I am getting a 500 error with this:
Could not find the entity manager for class "App\Entity\Question". Check your Doctrine configuration to make sure it is configured to load this entity’s metadata.
What should I do to continue?
Thanks in advance!