Creating relationships between entities with Symfony2, Doctrine and yml

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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s