Skip to content

Beschrijving van de gegevensstructuur

Om in PHP met een database te werken gaan we gebruik maken van Doctrine , een set libraries die ons, ontwikkelaars, helpt om met databases om te gaan: Doctrine DBAL (een database abstractie laag), Doctrine ORM (een library om database inhoud te manipuleren door gebruik te maken van PHP objecten), en Doctrine Migrations.

Doctrine ORM configureren

Hoe weet Doctrine met welke database te verbinden? Doctrine's recipe voegde een configuratiebestand toe config/packages/doctrine.yaml, dat zijn gedrag regelt. De belangrijkste instelling is de database DSN, een string die alle informatie over de verbinding bevat: gebruikersnaam, wachtwoord, host, poort, enz. Standaard probeert Doctrine deze gegevens uit de DATABASE_URL omgevingsvariabele te halen.

Bijna alle geïnstalleerde packages bevatten configuratie in de config/packages/-map. Meestal zijn de standaardinstellingen zorgvuldig gekozen om voor de meeste toepassingen te werken.

Conventies van Symfony-omgevingsvariabelen begrijpen

Je kan de DATABASE_URL handmatig in het .env of .env.local bestand definiëren. Dankzij het recipe van de package zie je bijvoorbeeld DATABASE_URL in jouw .env bestand. Maar omdat Docker de lokale poort voor PostgreSQL vrij kiest, is dat hinderlijk. Er is een betere manier.

In plaats van de DATABASE_URL hard te coderen in een bestand, kunnen we alle commando's met symfony prefixen. Dit zorgt ervoor dat de Docker en/of Platform.sh (wanneer de tunnel open is) services gedetecteerd worden en automatisch als omgevingsvariabele ingesteld worden.

Docker Compose en Platform.sh werken naadloos samen met Symfony dankzij deze omgevingsvariabelen.

Bekijk alle beschikbare omgevingsvariabelen door het uitvoeren van symfony var:export:

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

Herinner je je de database servicenaam die in de Docker en Platform.sh configuraties wordt gebruikt? De servicenamen worden gebruikt als prefix bij het definiëren van omgevingsvariabelen zoals DATABASE_URL. Als services de Symfony naamgevingsconventies volgen, is er geen extra configuratie nodig.

Note

De database is niet de enige service die profiteert van de Symfony conventies. Hetzelfde geldt bijvoorbeeld voor Mailer (via de MAILER_DSN omgevingsvariabele).

De standaard DATABASE_URL waarde in .env aanpassen

We zullen het .env bestand nog steeds aanpassen om de standaard DATABASE_URL voor het gebruik van PostgreSQL in te stellen:

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 ###

Waarom moet de informatie op twee verschillende plaatsen worden herhaald? Omdat op sommige Cloud platformen tijdens de build, de URL van de database nog onbekend kan zijn, maar Doctrine wel het database systeem moet kennen om de configuratie te kunnen opbouwen. Dus, de host, gebruikersnaam en wachtwoord doen er niet toe.

Entity classes aanmaken

Een conferentie kunnen we beschrijven aan de hand van een aantal eigenschappen:

  • De stad waar de conferentie wordt georganiseerd;
  • Het jaar van de conferentie;
  • Een internationale vlag om aan te geven of de conferentie lokaal of internationaal is (SymfonyLive vs SymfonyCon).

De Maker bundle kan ons helpen om een class (een Entity class) te genereren voor de conferentie.

Het is nu tijd om de Conference entity te genereren:

1
$ symfony console make:entity Conference

Dit commando is interactief: het begeleidt je bij het toevoegen van de nodige velden. Gebruik de volgende antwoorden (de meeste zijn de standaard antwoorden, dus je kunt op de "Enter" toets drukken om ze te aanvaarden):

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

Dit is de volledige uitvoer van het commando:

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

De Conference class is opgeslagen onder de App\Entity\ namespace.

Het commando genereerde ook een Doctrine repository class: App\Repository\ConferenceRepository .

De gegenereerde code ziet er als volgt uit (slechts een klein deel van het bestand wordt hier getoond):

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;
    }

    // ...
}

Merk op dat de class een gewone PHP class is zonder invloeden van Doctrine. Attributen worden gebruikt om metadata toe te voegen die Doctrine gebruikt om de class te kunnen koppelen aan de bijhorende databasetabel.

Doctrine heeft een id eigenschap toegevoegd om de primaire sleutel van de rij te bewaren in de tabel. Deze sleutel (ORM\Id()) wordt automatisch gegenereerd (ORM\GeneratedValue()) via een strategie die afhankelijk is van het gebruikte databasesysteem.

Genereer nu een Entity class voor reacties op de conferentie:

1
$ symfony console make:entity Comment

Geef de volgende antwoorden:

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

Entities aan elkaar koppelen

De twee entities, Conference en Comment, moeten aan elkaar worden gekoppeld. Een conferentie kan nul of meer reacties hebben, wat een one-to-many-relatie wordt genoemd.

Gebruik het make:entity commando opnieuw om de relatie toe te voegen aan de 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

Als je ? als antwoord voor het type intypt, krijg je een lijst met alle ondersteunde 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

Bekijk de volledige diff van de entity class na het toevoegen van de relatie:

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;
+    }
 }

Alles wat nodig is om de relatie te beheren is nu voor je gegenereerd. Eenmaal gegenereerd, wordt dit jouw code; je bent vrij om de code aan te passen als dat nodig is.

Extra eigenschappen toevoegen

Ik realiseerde me net dat we vergeten zijn een eigenschap toe te voegen aan de Comment entity: de deelnemers willen misschien een foto van de conferentie toevoegen om hun feedback kracht bij te zetten.

Voer make:entity opnieuw uit en voeg een photoFilename eigenschap/kolom van het type string toe, maar laat null toe omdat het uploaden van een foto optioneel is:

1
$ symfony console make:entity Comment

De database migreren

Het model van het project wordt nu volledig beschreven door de twee gegenereerde classes.

Vervolgens moeten we ook nog de databasetabellen aanmaken die bij deze PHP entities horen.

Doctrine Migrations is hiervoor het beste gereedschap. Het werd al geïnstalleerd als onderdeel van de orm dependency.

Een migratie is een class die database schemawijzigingen beschrijft. Met die schemawijzigingen kan je de database van de huidige naar de nieuwe versie brengen. De schemawijzigingen worden gegenereerd op basis van de attributen die op de entity gedefinieerd zijn. De database is momenteel leeg, dus de migratie zou de creatie van twee tabellen moeten bevatten.

Laten we eens bekijken wat Doctrine genereert:

1
$ symfony console make:migration

Let op de gegenereerde bestandsnaam (ziet eruit als 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
    {
        // ...
    }
}

Bijwerken van de lokale database

Je kan nu de gegenereerde migratie uitvoeren om het lokale database schema bij te werken:

1
$ symfony console doctrine:migrations:migrate

Het lokale database-schema is nu up-to-date, klaar om gegevens te bewaren.

De productiedatabase bijwerken

De stappen die nodig zijn om de productiedatabase te migreren zijn dezelfde als die waarmee je al bekend bent: commit de wijzigingen en deploy deze.

Bij het deployen van het project brengt Platform.sh de code up-to-date en voert ook de databasemigraties uit (indien het doctrine:migrations:migrate commando bestaat).

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version