Die Datenstruktur beschreiben
Um in PHP mit der Datenbank umzugehen, werden wir Doctrine verwenden, eine Reihe von Bibliotheken, die Entwickler*innen helfen, Datenbanken zu verwalten: Doctrine DBAL (eine Datenbank-Abstraktions-Schicht), Doctrine ORM (eine Bibliothek um unseren Datenbank-Inhalt anzupassen mit Hilfe von PHP-Objekten) und Doctrine Migrations.
Doctrine ORM konfigurieren
Woher kennt Doctrine die Datenbankverbindung? Das Doctrine-Recipe hat eine Konfigurationsdatei hinzugefügt, config/packages/doctrine.yaml
, die das Verhalten steuert. Die wichtigste Einstellung ist die Datenbank-DSN, eine Zeichenkette, die alle Informationen über die Verbindung enthält: Anmeldeinformationen, Host, Port, etc. Standardmäßig sucht Doctrine nach der Environment-Variable DATABASE_URL
.
Fast alle installierten Pakete haben eine Konfigurationsdatei im config/packages/
-Verzeichnis. Normalerweise sind die Standardeinstellungen so gewählt, dass sie für die meisten Anwendungen funktionieren.
Konventionen für Symfony-Environment-Variablen verstehen
Du kannst DATABASE_URL
manuell in der .env
- oder .env.local
-Datei definieren. Dank des Recipes des Paketes siehst Du sogar eine beispielhafte DATABASE_URL
in Deiner .env
-Datei. Aber da sich der lokale Port auf PostgreSQL, der von Docker festgelegt wird, ändern kann, ist dieser Weg recht umständlich. Es gibt einen besseren Weg.
Anstatt in einer Datei fest DATABASE_URL
einzusetzen, können wir alle Befehle mit symfony
prefixen. Dadurch werden Dienste erkannt, die von Docker und/oder Platform.sh ausgeführt werden (wenn der Tunnel geöffnet ist) und die Environment-Variable wird automatisch gesetzt.
Docker Compose und Platform.sh arbeiten dank dieser Environment-Variablen nahtlos mit Symfony zusammen.
Du überprüfst alle exponierten Environment-Variablen, indem Du symfony var:export
ausführst:
1
$ symfony var:export
1 2
DATABASE_URL=postgres://main:main@127.0.0.1:32781/main?sslmode=disable&charset=utf8
# ...
Erinnerst Du dich an den database
-Servicenamen, der in den Konfigurationen von Docker und Platform.sh verwendet wird? Die Servicenamen werden als Präfixe für Environment-Variablen wie DATABASE_URL
verwendet. Wenn Deine Services nach den Symfony-Konventionen benannt sind, ist keine weitere Konfiguration erforderlich.
Note
Datenbanken sind nicht der einzige Service, der von den Symfony-Konventionen profitiert. Das Gleiche gilt z. B. für Mailer (über die Environment-Variable MAILER_DSN
).
Den Standardwert DATABASE_URL in .env ändern
Wir werden die .env
-Datei dennoch ändern, um die Standard-DATABASE_URL
für die Verwendung von PostgreSQL festzulegen:
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&charset=utf8mb4"
-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 ###
Warum müssen die Informationen an zwei verschiedenen Stellen dupliziert werden? Da auf einigen Cloud-Plattformen zum Zeitpunkt des Builds die Datenbank-URL möglicherweise noch nicht bekannt ist, muss Doctrine die Engine der Datenbank kennen, um ihre Konfiguration zu erstellen. Daher sind der Host und die Zugangsdaten nicht wirklich wichtig.
Entity-Klassen anlegen
Eine Konferenz kann mit einigen wenigen Eigenschaften beschrieben werden:
- Die Stadt, in der die Konferenz organisiert wird;
- Das Jahr der Konferenz;
- Ein international-Flag, die angibt, ob die Konferenz lokal oder international ist (SymfonyLive vs. SymfonyCon).
Das Maker-Bundle kann uns helfen, eine Klasse (eine Entity-Klasse) zu generieren, die eine Konferenz repräsentiert.
Jetzt ist es an der Zeit die Conference
-Entity zu generieren:
1
$ symfony console make:entity Conference
Dieser Befehl ist interaktiv: Er führt Dich durch den Prozess des Hinzufügens aller benötigten Felder. Verwende die folgenden Antworten (die meisten davon sind die Standardwerte, Du kannst die Taste "Enter" drücken, um sie zu verwenden):
city
,string
,255
,no
;year
,string
,4
,no
;isInternational
,boolean
,no
.
Das ist die vollständige Ausgabe des Befehls:
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
Die Conference
-Klasse wurde unter dem App\Entity\
-Namespace abgelegt.
Der Befehl erzeugte auch eine Doctrine Repository-Klasse: App\Repository\ConferenceRepository
.
Der generierte Code sieht wie folgt aus (nur ein kleiner Teil der Datei wird hier gezeigt):
Beachte, dass die Klasse selbst eine einfache PHP-Klasse ohne Anzeichen von Doctrine ist. Mittels Attributen werden Metadaten hinzugefügt, die Doctrine verwendet, um die Klasse der zugehörigen Datenbanktabelle zuzuordnen.
Doctrine hat ein id
-Property/Spalte hinzugefügt, um den Primärschlüssel der Zeile in der Datenbanktabelle zu speichern. Dieser Schlüssel (ORM\Id()
) wird abhängig vom verwendeten Datenbanksystem automatisch generiert (ORM\GeneratedValue()
).
Erzeuge nun eine Entity-Klasse für Konferenzkommentare:
Gebe die folgenden Antworten ein:
author
,string
,255
,no
;text
,text
,no
;email
,string
,255
,no
;createdAt
,datetime_immutable
,no
.
Entities miteinander verknüpfen
Die beiden Entities, Conference und Comment, sollten miteinander verbunden werden. Eine Konferenz kann null oder mehr Kommentare haben, was als One-to-Many-Beziehung bezeichnet wird.
Verwende erneut den make:entity
-Befehl, um diese Beziehung zur Conference
-Klasse hinzuzufügen:
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
Wenn Du als Antwort auf den Typ ?
eingibst, erhältst Du alle unterstützten Typen:
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
Wirf einen Blick auf das vollständige Diff für die Entity-Klassen, nachdem Du die Beziehung (Relation) hinzugefügt hast:
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<int, 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, was Du für die Verwaltung von relations benötigst, wurde für Dich generiert. Sobald der Code generiert ist, gehört er Dir; zöger nicht, ihn nach Deinen Wünschen anzupassen.
Weitere Properties (Spalten) hinzufügen
Mir ist gerade aufgefallen, dass wir vergessen haben, ein Property zur Comment-Entity hinzuzufügen: Die Teilnehmer*innen möchten vielleicht ein Foto der Konferenz anhängen, um ihr Feedback zu veranschaulichen.
Führe make:entity
noch einmal aus und füge ein photoFilename
Property/Spalte vom Typ string
hinzu, aber lass es null
sein, da das Hochladen eines Fotos optional ist:
1
$ symfony console make:entity Comment
Die Datenbank migrieren
Das Projektmodell wird nun durch die beiden generierten Klassen vollständig beschrieben.
Als nächstes müssen wir die Datenbanktabellen erstellen, die sich auf diese PHP-Entitys beziehen.
Doctrine Migrations ist die perfekte Ergänzung für eine solche Aufgabe. Es wurde bereits als Teil der orm
-Dependency installiert.
Eine Migration ist eine Klasse, welche die Änderungen beschreibt, die erforderlich sind, um ein Datenbankschema von seinem aktuellen Zustand auf den neuen Zustand, der durch die Attribute in den Entities definiert ist, zu aktualisieren. Da die Datenbank vorerst leer ist, sollte die Migration aus zwei Einträgen bestehen.
Mal sehen, was Doctrine erzeugt:
1
$ symfony console make:migration
Beachte den generierten Dateinamen in der Ausgabe (ein Name, der so aussieht migrations/Version20191019083640.php
):
Die lokale Datenbank aktualisieren
Du kannst nun die generierte Migration ausführen, um das lokale Datenbankschema zu aktualisieren:
1
$ symfony console doctrine:migrations:migrate
Das lokale Datenbankschema ist nun auf dem aktuellen Stand und bereit, einige Daten zu speichern.
Die Datenbank der Produktivumgebung aktualisieren
Die Schritte, die für die Migration der Datenbank für die Produktivumgebung erforderlich sind, sind die gleichen wie die, mit denen Du bereits vertraut bist: Committe die Änderungen und deploye.
Beim Deployment des Projekts aktualisiert Platform.sh den Code, führt aber auch die Datenbankmigration durch, falls vorhanden (Platform.sh erkennt, ob der doctrine:migrations:migrate
-Befehl existiert).
$ symfony console make:entity Conference