The 4 (2?) Possible Relation Types

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Officially, there are four types of relations in Doctrine: ManyToOne, OneToMany, OneToOne and ManyToMany. But... that's kind of a lie! In reality, there are only two types.

Let me explain. We already know that a ManyToOne relationship and a OneToMany relationship are really just the same relationship seen from the two different sides. So that means that instead of four different types of relations, there are really only three.

OneToOne is ManyToOne in Disguise

But... the OneToOne relationship is... kind of not a different relationship.

For example, you decide to add a OneToOne relationship from a User entity to a Profile entity... which would hold more data about that user. If you did this, in the database, your user table would have a profile_id foreign key column. But wait: isn't that exactly what a ManyToOne relationship looks like?

Yup! In reality, a OneToOne relationship is the same as a ManyToOne, except that Doctrine puts a unique key on the profile_id column to prevent a single profile from being being linked to multiple users. But... that's really the only difference!

And, by the way, I try to avoid OneToOne relationships. Instead of splitting user data across two different entities, I tend to put it all in one class to reduce complexity. Splitting into two different entities could help performance, but I think it's almost always more of a bother than a help. Wait until you have real performance problems and then debug it.

Generating the Tag Entity

Anyways, this means that ManyToOne, OneToMany and OneToOne are all... really just the same relationship! That leaves only ManyToMany, which is a bit different. So let's build one!

Imagine that every Question can get be tagged with text descriptors.

In order to store tags in the database, let's make a Tag entity. Spin over to your console and run:

symfony console make:entity

Call the new entity Tag... and it's going to be real simple: a single field called name that will be a string type, 255 length, not nullable. Hit enter again to finish up.

Before I generate that migration, open up the new Tag class...

... lines 1 - 2
namespace App\Entity;
use App\Repository\TagRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=TagRepository::class)
*/
class Tag
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $name;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
}

because you know that I love to use TimestampableEntity.

... lines 1 - 6
use Gedmo\Timestampable\Traits\TimestampableEntity;
... lines 8 - 11
class Tag
{
use TimestampableEntity;
... lines 16 - 43
}

We could also add a slug column if we wanted to be able to go to a nice url like /tags/{slug} to show all the questions related a slug. I won't do that... mostly because we showed how to do that in the last tutorial: how to generate a slug automatically from some other property.

Ok: we now have a functional Tag entity. So let's generate a migration for it:

symfony console make:migration

Beautiful! Go give it a quick peek to make sure nothing funny snuck in. Nope! That looks boring: CREATE TABLE tag with id, name and the date fields.

... lines 1 - 6
use Gedmo\Timestampable\Traits\TimestampableEntity;
... lines 8 - 11
class Tag
{
use TimestampableEntity;
... lines 16 - 43
}

Go run it:

symfony console doctrine:migrations:migrate

Awesomesauce. So let's think about our goal: each Question could have many tags... and each Tag could be related to many questions. In other words, this is a many to many relationship. Next: let's generate that and see what it looks like!

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.4.1 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.3", // v3.3.0
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.3
        "doctrine/doctrine-bundle": "^2.1", // 2.4.2
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
        "doctrine/orm": "^2.7", // 2.9.5
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "knplabs/knp-time-bundle": "^1.11", // v1.16.1
        "pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
        "pagerfanta/twig": "^3.3", // v3.3.0
        "sensio/framework-extra-bundle": "^6.0", // v6.1.5
        "stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
        "symfony/asset": "5.3.*", // v5.3.4
        "symfony/console": "5.3.*", // v5.3.7
        "symfony/dotenv": "5.3.*", // v5.3.7
        "symfony/flex": "^1.3.1", // v1.15.1
        "symfony/framework-bundle": "5.3.*", // v5.3.7
        "symfony/monolog-bundle": "^3.0", // v3.7.0
        "symfony/runtime": "5.3.*", // v5.3.4
        "symfony/stopwatch": "5.3.*", // v5.3.4
        "symfony/twig-bundle": "5.3.*", // v5.3.4
        "symfony/webpack-encore-bundle": "^1.7", // v1.12.0
        "symfony/yaml": "5.3.*", // v5.3.6
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.1
        "twig/string-extra": "^3.3", // v3.3.1
        "twig/twig": "^2.12|^3.0" // v3.3.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
        "symfony/debug-bundle": "5.3.*", // v5.3.4
        "symfony/maker-bundle": "^1.15", // v1.33.0
        "symfony/var-dumper": "5.3.*", // v5.3.7
        "symfony/web-profiler-bundle": "5.3.*", // v5.3.5
        "zenstruck/foundry": "^1.1" // v1.13.1
    }
}