Опис структури даних
Для роботи з базою даних з PHP ми будемо використовувати Doctrine, набір бібліотек, які допомагають розробникам керувати базами даних: Doctrine DBAL (шаблон абстракції бази даних), Doctrine ORM (бібліотека для маніпулювання вмістом нашої бази даних з використанням об'єктів PHP), і Doctrine Migrations.
Налаштування Doctrine ORM
Як Doctrine підключається до бази даних? Рецепт Doctrine додав файл конфігурації config/packages/doctrine.yaml
, який містить параметри для підключення. Основним параметром є DSN бази даних — рядок, що містить всю інформацію про з'єднання: облікові дані, адреса, порт тощо. За замовчуванням Doctrine шукає змінну середовища DATABASE_URL
.
Майже всі встановлені пакети мають конфігурацію в каталозі config/packages/
. Значення за замовчуванням здебільшого були ретельно підібрані для роботи в більшості застосунків.
Домовленості про іменування змінних середовища в Symfony
Ви можете визначити параметр DATABASE_URL
вручну у файлі .env
або .env.local
. Насправді, завдяки рецепту пакета, ви побачите приклад DATABASE_URL
у вашому файлі .env
. Але він досить громіздкий, оскільки локальний порт PostgreSQL, що надає нам Docker, може змінюватися. Є кращий спосіб.
Замість того щоб жорстко задати значення DATABASE_URL
у файлі, ми можемо додати префікс symfony
до всіх команд. Це дозволить виявити сервіси запущені у Docker і/чи Platform.sh (коли відкрито тунель) і автоматично встановити змінну середовища.
Docker Compose й Platform.sh легко працюють із Symfony завдяки цим змінним середовища.
Перевірте всі доступні змінні середовища, виконавши команду symfony var:export
:
1
$ symfony var:export
1 2
DATABASE_URL=postgres://main:main@127.0.0.1:32781/main?sslmode=disable&charset=utf8
# ...
Пам'ятаєте ім'я сервісу database
, що використовується у конфігураціях Docker і Platform.sh? Імена сервісів використовуються як префікси для визначення змінних середовища, таких як DATABASE_URL
. Якщо ваші сервіси іменуються відповідно до домовленостей Symfony, то додаткові налаштування не потрібні.
Note
База даних не є єдиним сервісом, що використовує домовленості Symfony. Це ж стосується, наприклад, Mailer (через змінну середовища MAILER_DSN
).
Зміна значення DATABASE_URL за замовчуванням у .env
Ми все одно змінимо файл .env
для налаштування значення DATABASE_URL
за замовчуванням, щоб використовувати 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 ###
Чому інформація має дублюватися у двох різних місцях? Тому, що на деяких хмарних платформах, під час збірки, URL-адреса бази даних може бути ще невідома, але Doctrine потрібно знати про систему керування базами даних, щоб створити свою конфігурацію. Таким чином, адреса, ім'я користувача й пароль, насправді, не мають значення.
Створення класів сутностей
Конференцію можна описати кількома властивостями:
- Місто, де організована конференція;
- Рік проведення конференції;
- Прапорець international, що вказує, чи є конференція локальною або міжнародною (SymfonyLive або SymfonyCon).
Бандл Maker може допомогти нам згенерувати сутність (клас сутності), який являє собою конференцію.
Тепер настав час створити сутність Conference
:
1
$ symfony console make:entity Conference
Ця команда є інтерактивною: вона допоможе вам в процесі додавання всіх необхідних властивостей. Використовуйте наступні відповіді (більшість з них встановлено за замовчуванням, тому ви можете просто натискати клавішу "Enter", щоб застосувати їх):
city
,string
,255
,no
;year
,string
,4
,no
;isInternational
,boolean
,no
.
Ось повний вивід під час виконання команди:
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
Клас Conference
знаходиться у просторі імен App\Entity\
.
Команда також згенерувала клас репозиторію Doctrine: App\Repository\ConferenceRepository
.
Згенерований код виглядає наступним чином (тут наводиться тільки невелика частина файлу):
Зверніть увагу, що сам клас є простим класом PHP, не пов'язаним з Doctrine. Для додавання метаданих використовуються атрибути, що дозволяють Doctrine зв'язувати клас сутності з відповідною таблицею в базі даних.
Doctrine додала властивість id
, щоб зберігати первинний ключ рядка в таблиці бази даних. Цей ключ (ORM\Id()
) генерується автоматично (ORM\GeneratedValue()
) за допомогою стратегії, яка залежить від системи керування базою даних.
Тепер згенеруйте клас сутності для коментарів конференції:
1
$ symfony console make:entity Comment
Введіть наступні відповіді:
author
,string
,255
,no
;text
,text
,no
;email
,string
,255
,no
;createdAt
,datetime_immutable
,no
.
Зв'язування сутностей
Дві сутності, конференція та коментар, мають бути зв'язаними між собою. Конференція може мати нуль або більше коментарів, це називається зв'язком один до багатьох.
Використовуйте команду 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
Якщо ви введете ?
, у якості відповіді на питання про тип даних, то отримаєте список всіх підтримуваних типів:
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
Погляньте на повну різницю для класів сутностей після додавання зв'язку:
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;
+ }
}
Все, що вам потрібно для управління зв'язком, було згенеровано для вас. Після генерування код стає вашим; сміливо налаштовуйте його так, як вам хочеться.
Додавання додаткових властивостей
Я щойно зрозумів, що ми забули додати одну властивість до сутності коментаря: відвідувачі, можливо, захочуть долучити фото конференції, щоб проілюструвати свої враження.
Виконайте команду make:entity
ще раз та додайте властивість/стовпчик photoFilename
з типом string
, але дозвольте йому мати значення null
, оскільки процес завантаження фото є необов'язковим:
1
$ symfony console make:entity Comment
Міграція бази даних
Модель проекту тепер повністю описана двома згенерованими класами.
Далі нам потрібно створити таблиці бази даних, пов'язані з цими сутностями PHP.
Doctrine Migrations ідеально підходить для такого завдання. Цей пакет вже встановлено у вигляді залежності для orm
.
Міграція є класом, що описує зміни, які необхідні для оновлення схеми бази даних із її поточного стану до нового, визначеного в атрибутах сутності. Оскільки база даних поки що порожня, міграція має складатися зі створення двох нових таблиць.
Подивімося, що генерує Doctrine:
1
$ symfony console make:migration
Зверніть увагу на згенероване ім'я файлу у виводі (ім'я, схоже на migrations/Version20191019083640.php
)
Оновлення локальної бази даних
Тепер можна запустити згенеровану міграцію, для оновлення схеми локальної бази даних:
1
$ symfony console doctrine:migrations:migrate
Схема локальної бази даних тепер оновлена і готова до зберігання наших даних.
Оновлення бази даних у продакшн
Кроки, що необхідні для міграції бази даних у продакшн ті самі, з якими ви вже знайомі: фіксація змін і розгортання.
Під час розгортання проекту Platform.sh оновлює код, але також виконує міграцію бази даних, якщо така є (він виявляє, чи існує команда doctrine:migrations:migrate
).
Йдемо далі
$ symfony console make:entity Conference