So I’m starting a new project now, I’ve set up the logger, I’ve set up the mailing, and now it’s time I start to set up the model layer with Doctrine. In Symfony2 you can choose to have the configuration of this layer in xml files, yml files or as annotations directly in the entities classes. Personaly I find xml files too verbose, and annotations put logic in the entities which makes them less readable and if we want to change ORM we will have Doctrine configurations in entities that do not deal with Doctrine, so I prefer to have the configurations in yml files. Symfony has some documentation on how to set this up, but its not complete, as far as I can remember it only has documentation about one to many relationships, but in real life situations we rarely find only those types of relationships in a DB schema, so here I will document how to configure one to one, one to many, many to many and many to many to many relationships.
The first thing we have to do is create the entities, and for that we will use Symfony console tool:
php app/console generate:doctrine:entity
For sake of simplicity we will create entities with no properties, although Doctrine will add the $id property by default. So we start with these tree entities:
AaaAaa |
$id |
BbbBbb |
$id |
CccCcc |
$id |
Although the entities are classes and, as such, their names should be CamelCased, the tables created should have their names lower cased and with underscores to separate the words (snake_cased). So the next thing we do is make sure the table names created for each entity will be snake cased instead of camel cased. For that, in each entities configuration file we specify the name we want for that entity table:
## AaaAaa.orm.yml
MainNameSpaceBundleModelBundleEntityAaaAaa:
type: entity
table: aaa_aaa
...
One to one
First lets imagine we have entities AaaAaa and BbbBbb, where for each A corresponds only one B and vice versa. For example, a Persons entity and a Collector entity where the collector is a specific person and obviously a specific person can only be a specific collector. We will only need to configure one entity in its corresponding yml file, by adding:
## AaaAaa.orm.yml
oneToOne:
bbbBbb:
targetEntity: BbbBbb
joinColumn:
name: bbb_bbb_id
referencedColumnName: id
After running
php app/console doctrine:generate:entities MainNameSpace
we should end up with our entities updated like this:
AaaAaa |
$id $bbbBbbb |
BbbBbb |
$id |
Having this ready, we can create the DB with Symfony console commands:
php app/console doctrine:database:drop --force
php app/console doctrine:database:create
php app/console doctrine:schema:update --force
Which would give us the following tables:
aaa_aaa |
id bbb_bbb_id |
bbb_bbb |
id |
Where aaa_aaa.bbb_bbb_id connects to bbb_bbb.id. This is similar to the result in a one to many relation, BUT in this case aaa_aaa.bbb_bbb_id will have a unique constraint.
One to many
So lets suppose now that we have entities AaaAaa and BbbBbb, and for each A we have several Bs but for each B there’s only one A possible, ie a father can have several sons but a son can have only one father (philosophical issues aside).
In this case we have to configure a oneToMany relation in one table and a manyToOne relation in another table:
## AaaAaa.orm.yml
oneToMany:
bbbBbbCollection:
targetEntity: BbbBbb
mappedBy: aaaAaa
and
## BbbBbb.orm.yml
manyToOne:
aaaAaa:
targetEntity: AaaAaa
inversedBy: bbbBbbCollection
joinColumn:
name: aaa_aaa_id
referencedColumnName: id
After running
php app/console doctrine:generate:entities MainNameSpace
we should end up with our entities updated like this:
AaaAaa |
$id $bbbBbbCollection |
BbbBbb |
$id $aaaAaa |
Having this ready, we can create the DB with Symfony console commands:
php app/console doctrine:database:drop --force
php app/console doctrine:database:create
php app/console doctrine:schema:update --force
Which will give us the following tables:
aaa_aaa |
id |
bbb_bbb |
id aaa_aaa_id |
Where aaa_aaa.id connects to bbb_bbb.aaa_aaa_id. As said above, this is similar to the result in a one to one relation, BUT in this case bbb_bbb.aaa_aaa_id does NOT have a unique constraint.
Many to many
Now lets see the case where for each A we can have several Bs, and for each B we can have several As. For example, a restaurant has several clients, and each client can be a client of several restaurants.
In this case we have to configure a manyToMany relation in both tables, although in a slightly different way:
## AaaAaa.orm.yml
manyToMany:
bbbBbbCollection:
targetEntity: BbbBbb
inversedBy: aaaAaaCollection
mappedBy: aaaAaaCollection
and
## BbbBbb.orm.yml
manyToMany:
aaaAaaCollection:
targetEntity: AaaAaa
inversedBy: bbbBbbCollection
cascade: ["persist"]
joinTable:
name: bbb__aaa
joinColumns:
bbb_bbb_id:
referencedColumnName: id
inverseJoinColumns:
aaa_aaa_id:
referencedColumnName: id
After running
php app/console doctrine:generate:entities MainNameSpace
we should end up with our entities updated like this:
AaaAaa |
$id $bbbBbbCollection |
BbbBbb |
$id $aaaAaaCollection |
Having this ready, we can create the DB with Symfony console commands:
php app/console doctrine:database:drop --force
php app/console doctrine:database:create
php app/console doctrine:schema:update --force
Which will give us the following tables:
aaa_aaa |
id |
bbb__aaa |
aaa_aaa_id bbb_bbb_id |
bbb_bbb |
id |
Where aaa_aaa.id connects to bbb__aaa.aaa_aaa_id and bbb_bbb.id connects to bbb__aaa.bbb_bbb_id.
Many to many to many (connecting 3 entities with one relationship)
Finally we have a situation where we have three entities and they all connect in one many to many relation.
In this case we will have to create an extra entity, used exclusively for the connection, and which I will refer to as the connection entity:
AaaBbbCcc |
$id |
In this case we have to configure a oneToMany relation in all tree entity and tree many to one relations in the connection entity:
## AaaAaa.orm.yml
oneToMany:
aaaBbbCccCollection:
targetEntity: AaaBbbCcc
mappedBy: aaaAaa
## BbbBbb.orm.yml
oneToMany:
aaaBbbCccCollection:
targetEntity: AaaBbbCcc
mappedBy: bbbBbb
## CccCcc.orm.yml
oneToMany:
aaaBbbCccCollection:
targetEntity: AaaBbbCcc
mappedBy: cccCcc
and finally in the connection entity:
## AaaBbbCcc.orm.yml
manyToOne:
aaaAaa:
targetEntity: AaaAaa
inversedBy: aaaBbbCccCollection
joinColumn:
name: aaa_aaa_id
referencedColumnName: id
bbbBbb:
targetEntity: BbbBbb
inversedBy: aaaBbbCccCollection
joinColumn:
name: bbb_bbb_id
referencedColumnName: id
cccCcc:
targetEntity: CccCcc
inversedBy: aaaBbbCccCollection
joinColumn:
name: ccc_ccc_id
referencedColumnName: id
After running
php app/console doctrine:generate:entities MainNameSpace
we should end up with our entities updated like this:
AaaAaa |
$id $aaaBbbCccCollection |
BbbBbb |
$id $aaaBbbCccCollection |
CccCcc |
$id $aaaBbbCccCollection |
AaaBbbCcc |
$id $aaaAaa $bbbBbb $cccCcc |
Having this ready, we can create the DB with Symfony console commands:
php app/console doctrine:database:drop --force
php app/console doctrine:database:create
php app/console doctrine:schema:update --force
Which will give us the following tables:
aaa_aaa |
id |
bbb_bbb |
id |
ccc_ccc |
id |
aaa_bbb_ccc |
id aaa_aaa_id bbb_bbb_id ccc_ccc_id |
And, I hope, the tables connections are obvious by now.
Any other situation not covered here, I suppose it’s pretty easy deduce from the examples above, but if you have some weird situation not covered here or some question, feel free to ask.