Doctrine - là một bộ các thư viện giúp dev quản lý cơ sở dữ liệu - Doctrine giúp xử lý CSLD từ PHP, trong đó có có các thư viện Doctrine DBAL, Doctrine ORM và Doctrine Migrations.
Doctrine ORM là thư viện giúp kiểm soát nội dung CLSD sử dụng các đối tượng PHP.
Doctrine ORM Lên Cấu Hình
Cách mà Doctrin nhận ra kết nối CSDL? Công thức cuả Doctrine đã thêm một file thiết lập cấu hình ở config/packages/doctrine.yaml
, để kiểm soát hành vi của việc nhận biết kết nối CSDL. Thiết lập chính là database DSN (Data Soure Name), một chuối chứa tất cả các thông tin về kết nối như host, post, redentials...Doctrine tìm một biến môi trường DATABASE_URL
theo mặc định.
Hầu như tất cả các gói được cài đặt theo một cấu hình ở thư mục config/packages/
directory. Và hầu hết mặc định từ cấu hình đã được lựa chọn để thích hợp cho phần lớn các ứng dụng.
Symfony Lề Lối Biến Môi Trường
Bạn có thể định nghĩa DATABASE_URL
(đây là chuỗi chứa tất cả các thông tin về kết nối với CSLD như đã chỉ ra ở mục trên) thủ công ở .env
hoặc .env.local
. Thực tế theo công thức của gói cài đặt, bạn sẽ thấy vài ví dụ về DATABASE_URL
ở trong .env
file. Nhưng vì tự thay đổi DATABASE_URL
có thể làm cho cổng local PostgreSQL phơi ra bởi Docker có thể thay đổi theo, vì vậy có một cách tốt hơn.
Thay vì phải tự code DATABASE_URL
trong .env
file, chúng ta có thể tiền tố tất cả các lệnh với symfony
. Điều này sẽ phát hiện services chạy bởi Docker hay Platform.sh (khi kênh mở) và thiết lập biến môi trường một các tự động.
Docker Compose và Platform.sh làm việc nhịp nhàng với Symfony là nhờ các biến môi trường.
Kiểm tra tất các các biên môi trường đã phơi ra bằng thực hiện lênh symfony var:export
:
$ symfony var:export
Copy
DATABASE_URL=postgres://app:[email protected]:32781/app?sslmode=disable&charset=utf8
# ...
Để định nghĩa biến môi trường thì biến đó được thêm vào tiền tố DATABASE_.
Tên này tương đương với tên service database
sử dụng trong file cấu hình Docker và Platform.sh. Tên của services được Symfony đặt theo lề lối nên không cần thêm thao tác nào. CSLD không chỉ là service có lợi ích từ Symfony các service khác như Mailer cũng được hưởng lợi ví dụ biến môi trường MAILER_DSN
.
Thay đổi giá trị mặc định của DATABASE_URL Value trong file .env
Để sử dụng PostgreSQL, thay đổi giá trị mặc định DATABASE_URL
--- a/.env
+++ b/.env
@@ -29,7 +29,7 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=8&charset=utf8mb4"
-DATABASE_URL="postgresql://app:[email protected]:5432/app?serverVersion=14&charset=utf8"
+DATABASE_URL="postgresql://127.0.0.1:5432/db?serverVersion=14&charset=utf8"
###< doctrine/doctrine-bundle ###
###> symfony/messenger ###
Để ý bạn sẽ thấy có phần comment có các giá trị của DATABASE_URL
điều này là do một số Cloud flatforms ở lúc build, URL vẫn chưa biết, mà Doctrine cần phải biết CSLD's engine để xây dựng cho nó cấu hình. Vì vây host, username, và mật khẩu không thực sự là vấn đề.
Creating Entity Classes
A conference can be described with a few properties:
- The city where the conference is organized;
- The year of the conference;
- An international flag to indicate if the conference is local or international (SymfonyLive vs SymfonyCon).
The Maker bundle can help us generate a class (an Entity class) that represents a conference.
It is now time to generate the Conference
entity:
$ symfony console make:entity Conference
This command is interactive: it will guide you through the process of adding all the fields you need. Use the following answers (most of them are the defaults, so you can hit the "Enter" key to use them):
city
,string
,255
,no
;year
,string
,4
,no
;isInternational
,boolean
,no
.
Here is the full output when running the command:
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
The Conference
class has been stored under the App\Entity\
namespace.
The command also generated a Doctrine repository class: App\Repository\ConferenceRepository
.
The generated code looks like the following (only a small portion of the file is replicated here):
Copy
src/Entity/Conference.php
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;
}
// ...
}
Note that the class itself is a plain PHP class with no signs of Doctrine. Attributes are used to add metadata useful for Doctrine to map the class to its related database table.
Doctrine added an id
property to store the primary key of the row in the database table. This key (ORM\Id()
) is automatically generated (ORM\GeneratedValue()
) via a strategy that depends on the database engine.
Now, generate an Entity class for conference comments:
$ symfony console make:entity Comment
Enter the following answers:
author
,string
,255
,no
;text
,text
,no
;email
,string
,255
,no
;createdAt
,datetime_immutable
,no
.
Liên kết các Entities
Có hai entities (thực thể) đã tạo là Conference và Comment và hai entities này phải được liên kết với nha để comment xuất hiện ở conferences. Một Conference có thể không có (zero) hoặc có nhiều Comment, cái này gọi là mối quan hệ one-to-many (một tới nhiều).
Sử dụng lệnh make:entity
để thêm mối quan hệ này tới Conference
class:
$ symfony console make:entity Conference
Đặt tên mối quan hệ này là comments (đây cũng là property tương tự như các field đã thêm vào ở mục trước)
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
Nếu gõ ?
vào mục trả lời bạn sẽ có tất cả các kiểu hỗ trợ. Đối với Relationships - relation, ManyToOne, OneToMany, ManyToMany và OneToOne.
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
Have a look at the full diff for the entity classes after adding the relationship:
--- 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;
+ }
}
Everything you need to manage the relationship has been generated for you. Once generated, the code becomes yours; feel free to customize it the way you want.
Thêm Properties
Người tham dự có thể muốn đính thêm hình ảnh của hội thảo để minh hoạ cho phản hồi của họ. Cái này thì mình quên mất chưa thêm và Comment entity.
I just realized that we have forgotten to add one property on the Comment entity: attendees might want to attach a photo of the conference to illustrate their feedback.
Run make:entity
once more and add a photoFilename
property/column of type string
, but allow it to be null
as uploading a photo is optional:
$ symfony console make:entity Comment
Migrating the Database
The project model is now fully described by the two generated classes.
Next, we need to create the database tables related to these PHP entities.
Doctrine Migrations is the perfect match for such a task. It has already been installed as part of the orm
dependency.
A migration is a class that describes the changes needed to update a database schema from its current state to the new one defined by the entity attributes. As the database is empty for now, the migration should consist of two table creations.
Let's see what Doctrine generates:
$ symfony console make:migration
Notice the generated file name in the output (a name that looks like migrations/Version20191019083640.php
):
migrations/Version20191019083640.php
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
{
// ...
}
}
Updating the Local Database
You can now run the generated migration to update the local database schema:
$ symfony console doctrine:migrations:migrate
The local database schema is now up-to-date, ready to store some data.
Updating the Production Database
The steps needed to migrate the production database are the same as the ones you are already familiar with: commit the changes and deploy.
When deploying the project, Platform.sh updates the code, but also runs the database migration if any (it detects if the doctrine:migrations:migrate
command exists).
https://php.watch/versions/8.0/static-return-type