Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • SensioLabs Professional services to help you with Symfony
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by
  1. Home
  2. Documentation
  3. Symfony: The Fast Track
  4. English
  5. Describing the Data Structure

Describing the Data Structure

To deal with the database from PHP, we are going to depend on Doctrine, a set of libraries that help developers manage databases: Doctrine DBAL (a database abstraction layer), Doctrine ORM (a library to manipulate our database content using PHP objects), and Doctrine Migrations.

Configuring Doctrine ORM

How does Doctrine know the database connection? Doctrine's recipe added a configuration file, config/packages/doctrine.yaml, that controls its behavior. The main setting is the database DSN, a string containing all the information about the connection: credentials, host, port, etc. By default, Doctrine looks for a DATABASE_URL environment variable.

Almost all installed packages have a configuration under the config/packages/ directory. Most of the time, the defaults have been chosen carefully to work for most applications.

Understanding Symfony Environment Variable Conventions

You can define the DATABASE_URL manually in the .env or .env.local file. In fact, thanks to the package's recipe, you'll see an example DATABASE_URL in your .env file. But because the local port to PostgreSQL exposed by Docker can change, it is quite cumbersome. There is a better way.

Instead of hard-coding DATABASE_URL in a file, we can prefix all commands with symfony. This will detect services ran by Docker and/or Platform.sh (when the tunnel is open) and set the environment variable automatically.

Docker Compose and Platform.sh work seamlessly with Symfony thanks to these environment variables.

Check all exposed environment variables by executing symfony var:export:

1
$ symfony var:export
1
2
DATABASE_URL=postgres://main:main@127.0.0.1:32781/main?sslmode=disable&charset=utf8
# ...

Remember the database service name used in the Docker and Platform.sh configurations? The service names are used as prefixes to define environment variables like DATABASE_URL. If your services are named according to the Symfony conventions, no other configuration is needed.

Note

Databases are not the only service that benefit from the Symfony conventions. The same goes for Mailer, for example (via the MAILER_DSN environment variable).

Changing the Default DATABASE_URL Value in .env

We will still change the .env file to setup the default DATABASE_URL to use PostgreSQL:

1
2
3
4
5
6
7
8
9
10
11
--- a/.env
+++ b/.env
@@ -28,7 +28,7 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
 #
 # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
 # DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
-DATABASE_URL="postgresql://symfony:ChangeMe@127.0.0.1:5432/app?serverVersion=13&charset=utf8"
+DATABASE_URL="postgresql://127.0.0.1:5432/db?serverVersion=13&charset=utf8"
 ###< doctrine/doctrine-bundle ###

 ###> symfony/messenger ###

Why does the information need to be duplicated in two different places? Because on some Cloud platforms, at build time, the database URL might not be known yet but Doctrine needs to know the database's engine to build its configuration. So, the host, username, and password do not really matter.

Creating Entity Classes

A conference can be described with a few properties:

  • The city where the conference is organized;
  • The year of the conference;
  • An international flag to indicate if the conference is local or international (SymfonyLive vs SymfonyCon).

The Maker bundle can help us generate a class (an Entity class) that represents a conference.

It is now time to generate the Conference entity:

1
$ symfony console make:entity Conference

This command is interactive: it will guide you through the process of adding all the fields you need. Use the following answers (most of them are the defaults, so you can hit the "Enter" key to use them):

  • city, string, 255, no;
  • year, string, 4, no;
  • isInternational, boolean, no.

Here is the full output when running the command:

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
created: src/Entity/Conference.php
created: src/Repository/ConferenceRepository.php

Entity generated! Now let's add some fields!
You can always add more fields later manually or by re-running this command.

New property name (press <return> to stop adding fields):
> city

Field type (enter ? to see all types) [string]:
>

Field length [255]:
>

Can this field be null in the database (nullable) (yes/no) [no]:
>

updated: src/Entity/Conference.php

Add another property? Enter the property name (or press <return> to stop adding fields):
> year

Field type (enter ? to see all types) [string]:
>

Field length [255]:
> 4

Can this field be null in the database (nullable) (yes/no) [no]:
>

updated: src/Entity/Conference.php

Add another property? Enter the property name (or press <return> to stop adding fields):
> isInternational

Field type (enter ? to see all types) [boolean]:
>

Can this field be null in the database (nullable) (yes/no) [no]:
>

updated: src/Entity/Conference.php

Add another property? Enter the property name (or press <return> to stop adding fields):
>



 Success!


Next: When you're ready, create a migration with make:migration

The Conference class has been stored under the App\Entity\ namespace.

The command also generated a Doctrine repository class: App\Repository\ConferenceRepository.

The generated code looks like the following (only a small portion of the file is replicated here):

src/App/Entity/Conference.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
namespace App\Entity;

use App\Repository\ConferenceRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: ConferenceRepository::class)]
class Conference
{
    #[ORM\Column(type: 'integer')]
    #[ORM\Id, ORM\GeneratedValue()]
    private $id;

    #[ORM\Column(type: 'string', length: 255)]
    private $city;

    // ...

    public function getCity(): ?string
    {
        return $this->city;
    }

    public function setCity(string $city): self
    {
        $this->city = $city;

        return $this;
    }

    // ...
}

Note that the class itself is a plain PHP class with no signs of Doctrine. Attributes are used to add metadata useful for Doctrine to map the class to its related database table.

Doctrine added an id property to store the primary key of the row in the database table. This key (ORM\Id()) is automatically generated (ORM\GeneratedValue()) via a strategy that depends on the database engine.

Now, generate an Entity class for conference comments:

1
$ symfony console make:entity Comment

Enter the following answers:

  • author, string, 255, no;
  • text, text, no;
  • email, string, 255, no;
  • createdAt, datetime_immutable, no.

Linking Entities

The two entities, Conference and Comment, should be linked together. A Conference can have zero or more Comments, which is called a one-to-many relationship.

Use the make:entity command again to add this relationship to the Conference class:

1
$ symfony console make:entity Conference
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
Your entity already exists! So let's add some new fields!

New property name (press <return> to stop adding fields):
> comments

Field type (enter ? to see all types) [string]:
> OneToMany

What class should this entity be related to?:
> Comment

A new property will also be added to the Comment class...

New field name inside Comment [conference]:
>

Is the Comment.conference property allowed to be null (nullable)? (yes/no) [yes]:
> no

Do you want to activate orphanRemoval on your relationship?
A Comment is "orphaned" when it is removed from its related Conference.
e.g. $conference->removeComment($comment)

NOTE: If a Comment may *change* from one Conference to another, answer "no".

Do you want to automatically delete orphaned App\Entity\Comment objects (orphanRemoval)? (yes/no) [no]:
> yes

updated: src/Entity/Conference.php
updated: src/Entity/Comment.php

Note

If you enter ? as an answer for the type, you will get all supported types:

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
Main types
  * string
  * text
  * boolean
  * integer (or smallint, bigint)
  * float

Relationships / Associations
  * relation (a wizard will help you build the relation)
  * ManyToOne
  * OneToMany
  * ManyToMany
  * OneToOne

Array/Object Types
  * array (or simple_array)
  * json
  * object
  * binary
  * blob

Date/Time Types
  * datetime (or datetime_immutable)
  * datetimetz (or datetimetz_immutable)
  * date (or date_immutable)
  * time (or time_immutable)
  * dateinterval

Other Types
  * decimal
  * guid
  * json_array

Have a look at the full diff for the entity classes after adding the relationship:

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
--- a/src/Entity/Comment.php
+++ b/src/Entity/Comment.php
@@ -36,6 +36,12 @@ class Comment
      */
     private $createdAt;

+    #[ORM\ManyToOne(inversedBy: 'comments')]
+    #[ORM\JoinColumn(nullable: false)]
+    private Conference $conference;
+
     public function getId(): ?int
     {
         return $this->id;
@@ -88,4 +94,16 @@ class Comment

         return $this;
     }
+
+    public function getConference(): ?Conference
+    {
+        return $this->conference;
+    }
+
+    public function setConference(?Conference $conference): self
+    {
+        $this->conference = $conference;
+
+        return $this;
+    }
 }
--- a/src/Entity/Conference.php
+++ b/src/Entity/Conference.php
@@ -2,6 +2,8 @@

 namespace App\Entity;

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

 /**
@@ -31,6 +33,16 @@ class Conference
      */
     private $isInternational;

+    #[ORM\OneToMany(targetEntity: Comment::class, mappedBy: "conference", orphanRemoval: true)]
+    private $comments;
+
+    public function __construct()
+    {
+        $this->comments = new ArrayCollection();
+    }
+
     public function getId(): ?int
     {
         return $this->id;
@@ -71,4 +83,35 @@ class Conference

         return $this;
     }
+
+    /**
+     * @return Collection|Comment[]
+     */
+    public function getComments(): Collection
+    {
+        return $this->comments;
+    }
+
+    public function addComment(Comment $comment): self
+    {
+        if (!$this->comments->contains($comment)) {
+            $this->comments[] = $comment;
+            $comment->setConference($this);
+        }
+
+        return $this;
+    }
+
+    public function removeComment(Comment $comment): self
+    {
+        if ($this->comments->contains($comment)) {
+            $this->comments->removeElement($comment);
+            // set the owning side to null (unless already changed)
+            if ($comment->getConference() === $this) {
+                $comment->setConference(null);
+            }
+        }
+
+        return $this;
+    }
 }

Everything you need to manage the relationship has been generated for you. Once generated, the code becomes yours; feel free to customize it the way you want.

Adding more Properties

I just realized that we have forgotten to add one property on the Comment entity: attendees might want to attach a photo of the conference to illustrate their feedback.

Run make:entity once more and add a photoFilename property/column of type string, but allow it to be null as uploading a photo is optional:

1
$ symfony console make:entity Comment

Migrating the Database

The project model is now fully described by the two generated classes.

Next, we need to create the database tables related to these PHP entities.

Doctrine Migrations is the perfect match for such a task. It has already been installed as part of the orm dependency.

A migration is a class that describes the changes needed to update a database schema from its current state to the new one defined by the entity attributes. As the database is empty for now, the migration should consist of two table creations.

Let's see what Doctrine generates:

1
$ symfony console make:migration

Notice the generated file name in the output (a name that looks like migrations/Version20191019083640.php):

migrations/Version20191019083640.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version00000000000000 extends AbstractMigration
{
    public function up(Schema $schema): void
    {
        // this up() migration is auto-generated, please modify it to your needs
        $this->addSql('CREATE SEQUENCE comment_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
        $this->addSql('CREATE SEQUENCE conference_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
        $this->addSql('CREATE TABLE comment (id INT NOT NULL, conference_id INT NOT NULL, author VARCHAR(255) NOT NULL, text TEXT NOT NULL, email VARCHAR(255) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, photo_filename VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
        $this->addSql('CREATE INDEX IDX_9474526C604B8382 ON comment (conference_id)');
        $this->addSql('CREATE TABLE conference (id INT NOT NULL, city VARCHAR(255) NOT NULL, year VARCHAR(4) NOT NULL, is_international BOOLEAN NOT NULL, PRIMARY KEY(id))');
        $this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C604B8382 FOREIGN KEY (conference_id) REFERENCES conference (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
    }

    public function down(Schema $schema): void
    {
        // ...
    }
}

Updating the Local Database

You can now run the generated migration to update the local database schema:

1
$ symfony console doctrine:migrations:migrate

The local database schema is now up-to-date, ready to store some data.

Updating the Production Database

The steps needed to migrate the production database are the same as the ones you are already familiar with: commit the changes and deploy.

When deploying the project, Platform.sh updates the code, but also runs the database migration if any (it detects if the doctrine:migrations:migrate command exists).

Going Further

  • Databases and Doctrine ORM in Symfony applications;
  • SymfonyCasts Doctrine tutorial;
  • Working with Doctrine Associations/Relations;
  • DoctrineMigrationsBundle docs.
Previous page Setting up a Database
Next page Setting up an Admin Backend
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version

    Symfony 5.4 is backed by

    Measure & Improve Symfony Code Performance

    Measure & Improve Symfony Code Performance

    Online exam, become Sylius certified today

    Online exam, become Sylius certified today

    Version:
    Locale:
    ebook

    This book is backed by:

    see all backers

    Symfony footer

    Avatar of Michał Jusięga, a Symfony contributor

    Thanks Michał Jusięga for being a Symfony contributor

    2 commits • 4 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • What is Symfony?
      • Symfony at a Glance
      • Symfony Components
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • Symfony Community
      • SymfonyConnect
      • Events & Meetups
      • Projects using Symfony
      • Contributors
      • Symfony Jobs
      • Backers
      • Code of Conduct
      • Downloads Stats
      • Support
    • Blog

      • All Blog Posts
      • A Week of Symfony
      • Case Studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Living on the edge
      • Releases
      • Security Advisories
      • Symfony Insight
      • Twig
      • SensioLabs Blog
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Powered by

    Follow Symfony