ステップ 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.yml という設定ファイルに必要な情報を書き込んでいます。主な設定項目は、クレデンシャルや host, port などの接続に関する情報を含んだ文字列である データベースのDSN です。デフォルトでは、 Doctrine は、 DATABASE_URL 環境変数を探すようになっています。

Symfony の環境変数の規約を理解する

.env.env.local ファイルに手動で DATABASE_URL を定義することもできます。実際、パッケージのレシピを使うことで、 .env ファイル内に DATABASE_URL がサンプルが書かれていますので確認してください。しかし、Docker が PostgreSQL のローカルポートは変えることがありますので、少し面倒なときもあります。そんなときのために、より良い方法があります。

ファイル内に 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
# ...

Docker や SymfonyCloud の設定で使われている サービス名 database を覚えていますか?サービス名は、DATABASE_URL のような環境変数を定義の接頭辞として使われます。Symfony の規約に沿ってサービスが命名されていれば、特に設定は必要ありません。

注釈

Symfony の規約による利点は、データベースのサービスのみではなく、メーラーなどにもあります(環境変数 MAILER_DSN )

.env のデフォルトの DATABSE_URL の値を変更する

.env ファイルを変更して、PostgreSQL を使うためのデフォルトの DATABASE_DSN をセットアップしましょう:

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 が設定情報のために、データベースエンジンを知る必要があるからです。ホスト名やユーザー名、パスワードは知る必要はありません。

エンティティクラスを作成する

conference オブジェクトはプロパティで記述することができます:

  • city はカンファレンスの開催場所です;
  • year は、カンファレンスの開催年です;
  • international フラグは、カンファレンスがローカルイベントなのか国際イベントなのかを表します(SymfonyLive vs SymfonyCon)。

Maker バンドルで conference を表現するための エンティティ クラスを生成することができます:

1
$ symfony console make:entity Conference

このコマンドはインタラクティブに動きます: 全ての必要なフィールドを追加することを補助してくれます。以下のように答えましょう(ほとんどはデフォルトで、ただ "エンター"キーを押すだけで良いはずです):

  • 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 に生成してくれます 。

生成されたコードは次のようになっています(ここではファイルを少しだけ表示しています):

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

    // ...
}

クラス自体は、Doctrine に関する情報がない、ただの PHP クラスです。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.

エンティティ間の関連付け

カンファレンスとコメントのエンティティは連携する必要があります。カンファンレスは n個のコメントを持ちますので、one-to-many の関連となります。

Conference クラスにリレーションを追加するために、make:entity コマンドをもう一度使いましょう:

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

注釈

タイプの回答に ? を入力すると、全てのサポートしているタイプを表示することができます:

 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

リレーションを追加した前後のエンティティクラスの diff を見てみましょう:

 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

データベースのマイグレーション

これで、2つの生成されたクラスでプロジェクトのモデルを記述できました。

次に、これらの PHP のエンティティに関連するデータベースのテーブルを作成する必要があります。

そのニーズには、Doctrine Migrations が使えます。 orm をインストールした際に一緒に入っています。

マイグレーション は、データベーススキーマをエンティティアノテーションで定義した新しい状態に更新するのに必要な変更を記述するクラスです。まだデータベースに何も作成していないので、マイグレーションは、テーブルを2つ作成することになります。

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.