Opis struktury danych
Do komunikacji PHP z bazą danych użyjemy Doctrine. Jest to zestaw bibliotek, które pomagają zarządzać bazami danych: Doctrine DBAL (warstwa abstrakcji bazy danych), Doctrine ORM (biblioteka do zarządzania danymi w bazie danych, za pomocą obiektów PHP), oraz Doctrine Migrations.
Konfigurowanie Doctrine ORM
Skąd Doctrine wie, jak połączyć się z bazą danych? Przepis (ang. recipe) instalujący Doctrine dodał odpowiedni plik konfiguracyjny (config/packages/doctrine.yaml
), który kontroluje jego zachowanie. Głównym ustawieniem jest database DSN, napis zawierający wszystkie informacje o połączeniu: dane uwierzytelniające, host, port itp. Domyślnie Doctrine szuka zmiennej środowiskowej DATABASE_URL
.
Prawie wszystkie zainstalowane pakiety posiadają swoją konfigurację w katalogu config/packages/
. W większości przypadków, wartości domyślne były wybierane ostrożnie, aby działały w większości aplikacji.
Zrozumienie konwencji zmiennych środowiskowych w Symfony
Możesz zdefiniować zmienną środowiskową DATABASE_URL
ręcznie w pliku .env
lub .env.local
. Dzięki przepisowi (ang. recipe) pakietu, możesz bazować na przykładowej wartości zmiennej środowiskowej DATABASE_URL
już wpisanej w pliku .env
. Pojawia się jednak problem uciążliwej ręcznej aktualizacji wpisu po każdej zmianie portu bazy danych PostgreSQL udostępnionego przez Dockera. Lepiej więc podejść do sprawy w inny sposób.
Zamiast dokonywać sztywnego ustawienia zmiennej środowiskowej DATABASE_URL
w pliku, możesz poprzedzać wszystkie polecenia słowem symfony
. Dzięki temu wszystkie usługi działające w kontenerze Docker i/lub Platform.sh (wyłącznie jeśli mamy otwarty tunel z Platform.sh) będą automatycznie ustawione jako zmienne środowiskowe.
Dzięki zmiennym środowiskowym integracja Symfony z Docker Compose i Platform.sh jest bezproblemowa.
Możesz sprawdzić aktualne zmienne środowiskowe w konsoli poprzez użycie polecenia symfony var:export
:
1
$ symfony var:export
1 2
DATABASE_URL=postgres://main:main@127.0.0.1:32781/main?sslmode=disable&charset=utf8
# ...
Pamiętasz nazwę usługi database
, której użyliśmy w konfiguracji Docker i Platform.sh? Nazwy usług są używane jako prefiksy do definiowania zmiennych środowiskowych, takich jak DATABASE_URL
. Jeśli twoje usługi są nazwane zgodnie z konwencjami Symfony, żadna dodatkowa konfiguracja nie jest potrzebna.
Note
Bazy danych nie są jedyną usługą, która korzysta tej z konwencji. To samo dotyczy na przykład Mailera (zmienna środowiskowa MAILER_DSN
).
Zmiana domyślnej wartości DATABASE_URL w pliku .env
Zmienimy plik .env
tak, aby ustawić domyślną wartość zmiennej środowiskowej DATABASE_URL
dla 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 ###
Dlaczego musimy powielać tę samą informację w dwóch różnych miejscach? Ponieważ na niektórych platformach chmurowych, w czasie budowania aplikacji, adres URL bazy danych może nie być jeszcze znany, a Doctrine potrzebuje informacji o silniku bazy danych, aby zbudować odpowiednią dla niego konfigurację. Tak więc host, nazwa użytkownika i hasło nie mają większego znaczenia.
Tworzenie klas encji
Konferencję można opisać kilkoma atrybutami:
- city - miasto, w którym organizowana jest konferencja;
- year - rok, w którym odbywa się konferencja;
- isInternational - flaga wskazująca, czy konferencja jest krajowa, czy międzynarodowa (SymfonyLive vs SymfonyCon).
Maker Bundle pomoże nam wygenerować klasę encji (ang. entity), która będzie reprezentowała konferencję.
Pora wygenerować encję Conference
:
1
$ symfony console make:entity Conference
To polecenie jest uruchamiane w trybie interaktywnym - poprowadzi Cię przez proces dodawania wszystkich potrzebnych pól. Użyj następujących odpowiedzi (większość z nich to odpowiedzi domyślne, więc możesz nacisnąć klawisz "Enter", aby ich użyć):
city
,string
,255
,no
;year
,string
,4
,no
;isInternational
,boolean
,no
.
Oto pełne wyjście (ang. output) po uruchomieniu polecenia:
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
Klasa Conference
została zapisana w przestrzeni nazw App\Entity\
.
Polecenie wygenerowało również klasę repozytorium (ang. repository) Doctrine: App\Repository\ConferenceRepository
.
Wygenerowany kod wygląda następująco (tylko niewielka część pliku jest tu pokazana):
Zauważ, że właśnie utworzona klasa jest zwykłą klasą PHP - nie ma w niej elementów Doctrine. Metadane wykorzystywane przez Doctrine do powiązania klasy z tabelą w bazie danych dodajemy, używając atrybutów.
Doctrine dodał atrybut id
, aby zachować klucz główny w tabeli bazy danych. Ten klucz (ORM\Id()
) jest automatycznie generowany (ORM\GeneratedValue()
) w sposób zależny od silnika bazy danych (oparty o wzorzec strategii).
Teraz wygeneruj klasę encji dla komentarzy:
1
$ symfony console make:entity Comment
Wprowadź następujące odpowiedzi:
author
,string
,255
,no
;text
,text
,no
;email
,string
,255
,no
;createdAt
,datetime_immutable
,no
.
Łączenie encji
Obie encje, Conference
i Comment
, powinny być ze sobą powiązane. Konferencja może mieć zero lub więcej komentarzy, co nazywamy relacją jeden do wielu (ang. one to many).
Użyj ponownie polecenia make:entity
, aby zdefiniować tę relację w klasie 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
Jeśli wpiszesz ?
jako odpowiedź w pytaniu o typ pola, otrzymasz listę wszystkich obsługiwanych typów:
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
Przyjrzyj się liście różnic dla klas encji po dodaniu relacji:
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;
+ }
}
Wszystko, czego potrzebujesz do zarządzania tą relacją, zostało wygenerowane automatycznie. Później kod możesz zmieniać jak chcesz.
Dodawanie kolejnych atrybutów
Właśnie zdałem sobie sprawę, że zapomnieliśmy dodać pewien atrybut w encji Comment
: uczestnicy mogą chcieć dołączyć zdjęcie z konferencji, aby zilustrować swoje opinie.
Uruchom make:entity
jeszcze raz i dodaj atrybut photoFilename
jako kolumnę typu string
. Pozwól jej przyjmować wartość null
, ponieważ dodanie zdjęcia jest opcjonalne:
1
$ symfony console make:entity Comment
Migracja bazy danych
Model projektu składa się teraz z dwóch właśnie wygenerowanych klas.
W kolejnym kroku musimy utworzyć tabele w bazie danych związane z naszymi encjami w PHP.
Biblioteka Doctrine Migrations to narzędzie idealnie dopasowane do tego zadania. Została ona już zainstalowana jako część zależności orm
.
Migracja (ang. migration) jest klasą, która opisuje zmiany wykonywane w bazie danych, aby z obecnego schematu przejść na nowy, zdefiniowany w atrybutach encji. Ponieważ baza danych jest na razie pusta, migracja powinna składać się z operacji tworzących dwie tabele.
Zobaczmy, co wygeneruje Doctrine:
1
$ symfony console make:migration
Zwróć uwagę na wygenerowaną nazwę pliku, która powinna przypominać migrations/Version20191019083640.php
:
Aktualizacja lokalnej bazy danych
Możesz teraz uruchomić migrację, aby zaktualizować schemat lokalnej bazy danych:
1
$ symfony console doctrine:migrations:migrate
Schemat lokalnej bazy danych jest teraz aktualny i przygotowany do przechowywania niektórych danych.
Aktualizacja produkcyjnej bazy danych
Kroki potrzebne do wykonania migracji na produkcyjnej bazie danych są takie same jak te, które już znasz: zatwierdź zmiany (ang. commit) i wdrażaj.
Podczas wdrażania projektu, Platform.sh oprócz aktualizacji kodu uruchamia także migrację bazy danych, jeśli taka istnieje (wykrywa, czy istnieje polecenie doctrine:migrations:migrate
).
Idąc dalej
$ symfony console make:entity Conference