خطوة 8: وصف هيكل البيانات

5.0 version
Maintained

وصف هيكل البيانات

للتعامل مع قاعدة البيانات من PHP ، سوف نعتمد على Doctrine ، وهي مجموعة من المكتبات التي تساعد المطورين على إدارة قواعد البيانات:

1
$ symfony composer req orm

يقوم هذا الأمر بتثبيت بعض التبعيات: Doctrine DBAL (طبقة تجريد قاعدة البيانات) ، Doctrine ORM (مكتبة لمعالجة محتوى قاعدة البيانات الخاصة بنا باستخدام كائنات PHP) ، و Doctrine Migrations.

تكوين Doctrine ORM

كيف يقوم Doctrine بمعرفة الاتصال بقاعدة البيانات؟ وصفة Doctrine تقوم بإضافة ملف تكوين، config/packages/doctrine.yaml الذي بدوره يقوم بالتحكم ب Doctrine سلوكيا. الإعداد الرئيسي هنا هو Database DSN، نص يحوي كل المعلومات عن الاتصال، بيانات التوثيق، المضيف، المنفذ .. الخ، افتراضيا، تبحث Doctrine عن متغير البيئة DATABASE_URL.

فهم اتفاقيات متغير البيئة الخاص ب Symfony

يمكنك تحديد DATABASE_URL يدويًا في ملف .env أو .env.local. في الحقيقة، بفضل وصفة الحزمة ، سترى DATABASE_URL مثالًا في ملف .env. ولكن نظرًا لأن المنفذ المحلي لـ PostgreSQL الذي تتعرض له Docker يمكن أن يتغير، إنه أمر مرهق للغاية. هناك طريقة افضل.

بدلاً من الترميز الثابت DATABASE_URL في ملف ، يمكننا بداية جميع الأوامر بـ symfony. سيؤدي هذا إلى اكتشاف الخدمات التي يديرها Docker و / أو SymfonyCloud (عندما يكون النفق مفتوحًا) وتعيين متغير البيئة تلقائيًا.

يعمل Docker Compose و SymfonyCloud بسلاسة مع Symfony بفضل متغيرات البيئة هذه.

تحقق من جميع متغيرات البيئة المكشوفة عن طريق تنفيذ symfony var:export:

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

هل تتذكر``database`` اسم الخدمة المستخدمة في تكوينات Docker و SymfonyCloud؟ يتم استخدام أسماء الخدمات كبادئات لتحديد متغيرات البيئة مثل DATABASE_URL. إذا تم تسمية خدماتك وفقًا لاتفاقيات Symfony ، فلن تكون هناك حاجة إلى تكوين آخر.

Note

قواعد البيانات ليست هي الخدمة الوحيدة التي تستفيد من اتفاقيات Symfony. ينطبق الشيء نفسه على Mailer ، على سبيل المثال (عبر متغير البيئة MAILER_DSN).

تغيير القيمة الافتراضية DATABASE_URL في .env

سنستمر في تغيير ملف .env لإعداد الإعداد الافتراضي DATABASE_DSN لاستخدام 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 ###

لماذا تحتاج المعلومات إلى تكرارها في مكانين مختلفين؟ لأنه في بعض الأنظمة الأساسية السحابية ، في وقت البناء ، قد لا يكون عنوان URL لقاعدة البيانات معروفًا بعد ، لكن Doctrine بحاجة إلى معرفة محرك قاعدة البيانات لبناء تكوينها. لذلك ، المضيف ، اسم المستخدم وكلمة المرور لا يهم حقا.

إنشاء فئات الكيان Entity Classes

يمكن وصف المؤتمر ببعض الخصائص:

  • المدينة حيث يتم تنظيم المؤتمر؛
  • السنة متى تم انعقاد المؤتمر؛
  • علامة دولية للإشارة إلى ما إذا كان المؤتمر محليًا أم دوليًا (SymfonyLive vs SymfonyCon).

يمكن أن تساعدنا حزمة Maker في إنشاء فئة (فئة Entity) تمثل مؤتمرًا:

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

تم تخزين فئة "المؤتمر" ضمن مساحة الاسم App\Entity\.

قام الأمر أيضًا بإنشاء فئة Doctrine repository: App\Repository\ConferenceRepository.

يبدو الرمز الذي تم إنشاؤه كما يلي (يتم نسخ جزء صغير فقط من الملف هنا):

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;
    }

    // ...
}

لاحظ أن الفئة class نفسه عبارة عن فئة PHP عادي بدون أي علامات على Doctrine. يتم استخدام التعليقات التوضيحية Annotations لإضافة بيانات تعريف مفيدة لـ 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, no.

ربط الكيانات

ينبغي ربط الكيانين ، المؤتمر والتعليق ، معًا. يمكن أن يحتوي المؤتمر على صفر أو أكثر من التعليقات ، والتي تسمى علاقة واحد لكثير.

استخدم الأمر make:entity مرة أخرى لإضافة هذه العلاقة إلى فئة 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

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
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;
+    }
 }

تم إنشاء كل ما تحتاجه لإدارة العلاقة من أجلك. وقت أن يتم توليد الرمز، يصبح ملكا لك. لا تتردد في تخصيصه بالطريقة التي تريدها.

إضافة المزيد من الخصائص

لقد أدركت تمامًا أننا نسينا إضافة خاصية واحدة على كيان التعليق: قد يرغب الحاضرون في إرفاق صورة للمؤتمر لتوضيح ملاحظاتهم.

شغِّل make:entity مرة أخرى وأضف خاصية / عمود photoFilename من نوع string ، لكن اسمح له أن يكون null لأن تحميل الصورة اختياري:

1
$ symfony console make:entity Comment

ترحيل قاعدة البيانات

يتم الآن وصف نموذج المشروع بالكامل من خلال فئتين تم إنشاؤهما.

بعد ذلك ، نحتاج إلى إنشاء جداول قاعدة البيانات المتعلقة بكيانات PHP هذه.

Doctrine Migrations هي الأداة المثالية لمثل هذه المهمة. تم تثبيتها بالفعل كجزء من تبعية orm.

الترحيل هو فئة تصف التغييرات اللازمة لتحديث مخطط قاعدة البيانات من حالته الحالية إلى الحالة الجديدة المحددة بواسطة التعليقات التوضيحية للكيان. نظرًا لأن قاعدة البيانات فارغة في الوقت الحالي ، يجب أن تتكون عملية الترحيل من إنشاء جدولين.

دعونا نرى ما أنتجته Doctrine:

1
$ symfony console make:migration

لاحظ اسم الملف الذي تم إنشاؤه في الإخراج (اسم يشبه 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
    {
        // ...
    }
}

تحديث قاعدة البيانات المحلية

يمكنك الآن تشغيل الترحيل الذي تم إنشاؤه لتحديث مخطط قاعدة البيانات المحلية:

1
$ symfony console doctrine:migrations:migrate

مخطط قاعدة البيانات المحلية الآن محدث ، جاهز لتخزين بعض البيانات.

تحديث قاعدة بيانات الإنتاج

الخطوات اللازمة لترحيل قاعدة بيانات الإنتاج هي نفسها التي تعرفها بالفعل: قم بإجراء التغييرات ونشرها.

عند نشر المشروع ، يقوم SymfonyCloud بتحديث الكود ، لكنه يدير أيضًا ترحيل قاعدة البيانات إن وجد (يكتشف ما إذا كان الأمر doctrine:migrations:migrate موجودًا).


  • « Previous خطوة 7: إعداد قاعدة بيانات
  • Next » خطوة 9: إعداد النظام الخلفي

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