Symfony 4 Workshop

Models with Doctrine ORM

Theory

Let's talk a bit about Doctrine.

First we have to install Doctrine ORM:

composer req orm

Let's create two entities

Copy this code into src/Entity/Movie.php

Tip

Instead of copying the code, you can also use the MakerBundle to generate the entity. Install it with:

composer require symfony/maker-bundle --dev

Type bin/console make:entity and look at the code below to answer the questions of the interactive generator. Don't forget to check the resulting code. You can run the command several times if you want to edit something in the entity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * @ORM\Entity()
 * @ORM\Table(name="movie")
 */
class Movie
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     */
    private $name;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private $director;

    /**
     * @ORM\Column(type="smallint")
     */
    private $year;

    /**
     * @ORM\Column(type="string")
     */
    private $picture;

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName(string $name)
    {
        $this->name = $name;

        return $this;
    }

    public function getDirector()
    {
        return $this->director;
    }

    public function setDirector(string $director)
    {
        $this->director = $director;

        return $this;
    }

    public function getYear()
    {
        return $this->year;
    }

    public function setYear(int $year)
    {
        $this->year = $year;

        return $this;
    }

    public function getPicture()
    {
        return $this->picture;
    }

    public function setPicture(string $picture)
    {
        $this->picture = $picture;

        return $this;
    }

}

Let's update the schema of our database with the mapping of this entity.

First, we need to configure our database. In the file .env we store our configuration parameters. This file should be ignored by Git.

Edit the .env to configure the database:

1
DATABASE_URL="mysql://travolta:travolta@127.0.0.1:3306/travolta?charset=utf8mb4&serverVersion=5.7"

Now we should be able to update the schema. Run:

php bin/console doctrine:schema:update --dump-sql

You can inspect the SQL commands that will be applied. To execute them, run:

php bin/console doctrine:schema:update --force

And the table movie has been created. You can inspect it with PHPMyAdmin or other tools

Exercise

Can you write the Entity Actor? Tip: use the maker bundle.

It should have the following properties. Notice that the maker bundle already creates the id property for you.

  • Id
  • name (string, length=100)
  • picture (string)

At the end it should look like the solution.

Reveal the solution

Relationships

We would like to express the relationship between Movies and Actors. A movie can have many actors and an actor can appear in many movies.

This is a many-to-many relationship.

We can define unidirectional or bidirectional relationships. It is clear that we will want to retrieve the actors of a movie. But would we like to retrieve the movies of an actor? If the answer is yes, then the relationship is bidirectional. If not, it is unidirectional. For this example, we will assume that the relationship is bidirectional.

Bidirectional relationships have an owning side and an inverse side. The rule here is to think which entity is the responsible of the connection management. Are we mentally adding actors to movies or movies to actors?

As our app is about movies, it feels more natural to add actors to movies. If we were building an actor DB it would be the opposite.

Let's define the owning side of the Relationship in src/Entity/Movie.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//... Add use directive in the top of the file, with the other use
use Doctrine\Common\Collections\ArrayCollection;

//...

/**
 * @ORM\ManyToMany(targetEntity="Actor", inversedBy="movies")
 */
private $actors;

public function __construct()
{
    $this->actors = new ArrayCollection();
}

public function getActors()
{
    return $this->actors;
}

public function addActor(Actor $actor)
{
    $actor->addMovie($this);
    $this->actors[] = $actor;

    return $this;
}

public function removeActor(Actor $actor)
{
    $this->actors->removeElement($actor);
    $actor->setMovie(null);

    return $this;
}

Let's define the the inverse side of the Relationship in src/Entity/Actor.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//... Add use directive in the top of the file, with the other use
use Doctrine\Common\Collections\ArrayCollection;

//...

/**
 * @ORM\ManyToMany(targetEntity="Movie", mappedBy="actors")
 */
private $movies;

public function __construct()
{
    $this->movies = new ArrayCollection();
}

public function addMovie(Movie $movie)
{
    $this->movies[] = $movie;
}

public function getMovies()
{
    return $this->movies;
}

public function removeMovie(Movie $movie)
{
    $this->movies->removeElement($movie);
    return $this;
}

After editing the entities, let's update our schema. We first check the SQL that is going to be executed

php bin/console doctrine:schema:update --dump-sql

And then run the actual update:

php bin/console doctrine:schema:update --force