SymfonyWorld Online 2020
100% online
30+ talks + workshops
Live + Replay watch talks later

Pas 8: Descrierea structurii datelor

5.0 version
Maintained

Descrierea structurii datelor

Pentru a gestiona baza de date din PHP, vom depinde de Doctrine, un set de biblioteci care ajută dezvoltatorii să gestioneze bazele de date:

1
$ symfony composer req orm

Această comandă instalează câteva dependențe: Doctrine DBAL (o librărie care abstractizează modul de a te conecta la o bază de date), Doctrine ORM (o librărie pentru a manipula conținutul bazei noastre de date folosind obiecte PHP) și Doctrine Migrations.

Configurarea Doctrine ORM

De unde cunoaște Doctrine datele de acces la baza de date? Rețeta Doctrine a adăugat un fișier de configurare, config/packages/doctrine.yaml, care îi controlează comportamentul. Setarea principală este DSN-ul bazei de date, un șir care conține toate informațiile despre conexiune: credențiale, gazdă, port, etc. În mod implicit, Doctrine caută o variabilă de mediu DATABASE_URL.

Înțelegerea convențiilor de variabile ale mediului Symfony

Poți defini manual DATABASE_URL în fișierul .env sau .env.local. De fapt, datorită rețetei pachetului, vei vedea un exemplu DATABASE_URL în fișierul tău .env. Dar, deoarece portul local către PostgreSQL expus de Docker se poate schimba, pot apărea probleme. Există o cale mai bună.

În loc de a scrie DATABASE_URL direct într-un fișier, putem prefixa toate comenzile cu symfony. Aceasta va detecta serviciile administrate de Docker și / sau SymfonyCloud (când tunelul este deschis) și va seta variabila de mediu automat.

Docker Compose și SymfonyCloud funcționează perfect cu Symfony, datorită acestor variabile de mediu.

Verificați toate variabilele de mediu expuse executând symfony var:export:

1
$ symfony var:export
1
2
DATABASE_URL=postgres://main:[email protected]:32781/main?sslmode=disable&charset=utf8
# ...

Îți amintești de numele serviciului database utilizat în configurațiile Docker și SymfonyCloud? Numele serviciilor sunt utilizate ca prefixe pentru a defini variabile de mediu precum DATABASE_URL. Dacă serviciile tale sunt numite în conformitate cu convențiile Symfony, nu este necesară altă configurație.

Notă

Bazele de date nu sunt singurul serviciu care beneficiază de convențiile Symfony. Același lucru este valabil și pentru Mailer, de exemplu (prin variabila de mediu MAILER_DSN).

Modificarea valorii implicite DATABASE_URL în .env

Vom modifica în continuare fișierul .env pentru a configura DATABASE_DSN să utilizeze PostgreSQL:

1
2
3
4
5
6
7
8
9
--- a/.env
+++ b/.env
@@ -25,5 +25,5 @@ APP_SECRET=447c9fa8420eb53bbd4492194b87de8f
 # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
 # For a PostgreSQL database, use: "postgresql://db_user:[email protected]:5432/db_name?serverVersion=11&charset=utf8"
 # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
-DATABASE_URL=mysql://db_user:[email protected]:3306/db_name?serverVersion=5.7
+DATABASE_URL=postgresql://127.0.0.1:5432/db?serverVersion=11&charset=utf8
 ###< doctrine/doctrine-bundle ###

De ce informațiile trebuie duplicate în două locuri diferite? Deoarece pe unele platforme Cloud, la build time (compilare), URL-ul bazei de date s-ar putea să nu fie cunoscut încă, dar Doctrine trebuie să știe ce bază de date va fi folosită pentru a se configura. Deci, valorile pentru adresa serverului, numele de utilizator și parola nu contează cu adevărat.

Crearea claselor entitate

Obiectul Conference poate avea următoarele proprietăți:

  • city - orașul în care este organizată conferința;
  • year - anul conferinței;
  • Un câmp international pentru a indica dacă conferința este locală sau internațională (SymfonyLive vs SymfonyCon).

Pachetul Maker ne poate ajuta să generăm o entitate (o clasă Entity) care să reprezinte o conferință:

1
$ symfony console make:entity Conference

Această comandă este interactivă: te va ghida prin procesul de adăugare a tuturor câmpurilor de care ai nevoie. Folosește răspunsurile următoare (cele mai multe dintre acestea sunt valorile implicite, așa că poți apăsa tasta „Enter” pentru a le utiliza):

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

Iată rezultatul executării comenzii:

 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

Clasa Conference a fost stocată sub spațiul de nume App\Entity\.

Comanda a generat, de asemenea, o clasă repository pentru Doctrine: App\Repository\ConferenceRepository.

Codul generat arată așa (doar o mică parte din fișier este reprodusă aici):

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
32
33
34
35
36
37
38
namespace App\Entity;

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

/**
 * @ORM\Entity(repositoryClass=ConferenceRepository::class)
 */
class Conference
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    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;
    }

    // ...
}

Reține că entitatea în sine este o clasă PHP simplă, fără elemente din Doctrine. Adnotările sunt utilizate pentru a adăuga metadate utile pentru Doctrine pentru a facilita maparea elementelor clasei cu elementele tabelei din baza de date.

Doctrine a adăugat o proprietate id pentru a stoca cheia primară a rândului în tabel. Această cheie (@ORM\Id()) este generată automat (@ORM\GeneratedValue()) printr-o strategie care depinde de motorul bazei de date utilizat.

Acum, generează o entitate pentru comentariile conferinței:

1
$ symfony console make:entity Comment

Introdu următoarele răspunsuri:

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

Relațiile între Entități

Cele două entități, Conference și Comment, ar trebui să fie legate între ele. Aceasta este o relație de tip one-to-many - o conferință poate avea unul, mai multe sau nici un comentariu.

Folosește din nou comanda make:entity pentru a adăuga această relație la entitatea Conference:

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

Notă

Dacă introduci ? ca răspuns pentru tip, vei primi toate tipurile de date acceptate:

 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

Aruncă o privire asupra modificărilor făcute în entitate în urma adăugării relației:

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

+    /**
+     * @ORM\ManyToOne(targetEntity=Conference::class, inversedBy="comments")
+     * @ORM\JoinColumn(nullable=false)
+     */
+    private $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;
+    }
 }

Tot ceea ce ai nevoie pentru a gestiona relația a fost generat pentru tine. Odată generat, codul devine al tău; nu ezita să-l personalizezi așa cum dorești.

Adăugarea mai multor proprietăți

Tocmai mi-am dat seama că am uitat să adăugăm o proprietate la entitatea Comment: participanții ar putea dori să atașeze o fotografie a conferinței pentru a ilustra feedback-ul lor.

Execută încă o dată make:entity și adăugă o proprietate/coloană photoFilename de tip string, dar permite-i să fie null, deoarece încărcarea unei fotografii este opțională:

1
$ symfony console make:entity Comment

Migrarea bazei de date

Modelul proiectului este acum complet descris de cele două clase generate.

În continuare, trebuie să creăm tabele de baze de date legate de aceste entități PHP.

Doctrine Migrations este instrumentul perfect pentru asta. A fost deja instalat ca parte a dependenței orm.

O migrare este o clasă care descrie modificările necesare pentru a actualiza o schemă a bazei de date de la starea ei curentă la cea nouă, așa cum este definită de adnotările din entități. Deoarece baza de date este goală deocamdată, migrația va consta în crearea celor două tabele.

Să vedem ce generează Doctrine:

1
$ symfony console make:migration

Observă numele de fișier generat (un nume care arată ca 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 Version20191019083640 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
    {
        // ...
    }
}

Actualizarea bazei de date locale

Acum poți rula migrația generată pentru a actualiza schema bazei de date locale:

1
$ symfony console doctrine:migrations:migrate

Schema bazei de date locale este actualizată, gata să stocheze date.

Actualizarea bazei de date de producție

Pașii necesari pentru migrarea bazei de date de producție sunt similari celor cunoscuți deja: salvează modificările și lansează.

La implementarea proiectului, SymfonyCloud actualizează codul, dar execută și migrația bazei de date, dacă există (detectează dacă există comanda doctrine:migrations:migrate).


  • « Previous Pas 7: Configurarea unei baze de date
  • Next » Pas 9: Configurarea unei interfețe de administrare

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