cakephp3でDBのテーブルのリレーションの関係を整理する〜1対多編

## 命名規則
### DB
- テーブル名は小文字で複数形です。
- 複数の単語がある場合は、アンダースコアでつなぐ
- カラム名は小文字で単数形
- 複数の単語がある場合は、アンダースコアでつなぐ
- 外部キーカラムは`AAAAs(複数形)_id`
- 外部キーカラムを定義した場合、外部テーブルAAAAsが必要
- DBのテーブル自体にリレーションの定義は必要？→なくてもいい。というよりはなくても動く。でもあったほうがいいと思う。

## ①マスターテーブルパターン
Lessons belongs to Category
特にテーブル定義でリレーションは設定しないでやってみます。
```sql
-- テーブル定義
CREATE TABLE lessons (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    categories_id INT UNSIGNED
);

CREATE TABLE categories (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50)
);


-- データを入れる
insert into categories(name) values ('理系');
insert into categories(name) values ('文系');

insert into lessons(name, categories_id) values ('Math1', 1);
insert into lessons(name, categories_id) values ('English1', 2);
insert into lessons(name, categories_id) values ('Science1', 2);
insert into lessons(name, categories_id) values ('Japanese', 2);
```

cakephpプロジェクトを作成
`$ php composer.phar create-project --prefer-dist cakephp/app:^3.8 test1`

モデル作成
`$bin/cake bake all lessons`

確認用のnginxのconfとapp_local.phpをいじって確認できるようにする。(この辺は各々の環境で。)

`xxx.xxx.xxx.xxx/lessons`にアクセス

![スクリーンショット 2022-06-14 16.58.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154053/756399d7-4ab5-f0c4-3ab7-32420a07bb6d.png)

特にリレーション組んでいないのに、勝手にCategoriesのテーブルと紐付けて、nameを引っ張ってきてくれている。


## ②マスターテーブルパターン(マスターテーブルのカラム名を変更)
①と同じ条件で、categoriesテーブルのカラム名だけ変更する。


```sql
-- テーブル定義
CREATE TABLE lessons (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    categories_id INT UNSIGNED
);

CREATE TABLE categories (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(50)
);


-- データを入れる
insert into categories(title) values ('理系');
insert into categories(title) values ('文系');

insert into lessons(name, categories_id) values ('Math1', 1);
insert into lessons(name, categories_id) values ('English1', 2);
insert into lessons(name, categories_id) values ('Science1', 2);
insert into lessons(name, categories_id) values ('Japanese', 2);
```

## ③マスターテーブルパターン(マスターテーブルが複数カラム)
```sql
-- テーブル定義
CREATE TABLE lessons (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    categories_id INT UNSIGNED
);

CREATE TABLE categories (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(50),
    description VARCHAR(50)
);


-- データを入れる
insert into categories(title, description) values ('理系', '難しい');
insert into categories(title, description) values ('文系', '簡単');

insert into lessons(name, categories_id) values ('Math1', 1);
insert into lessons(name, categories_id) values ('English1', 2);
insert into lessons(name, categories_id) values ('Science1', 2);
insert into lessons(name, categories_id) values ('Japanese', 2);
```

`$ bin/cake bake all lessons`

![スクリーンショット 2022-06-14 17.16.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154053/4772cdc4-b298-126e-9a30-107efacb1076.png)

2カラム以上定義するとCategoriesでどのカラムの値を反映していいのかわからないからidが表示されるのかと思ったが、2カラム目のtitleが表示された。
これは、2カラム目が優先的に表示されているのか、それとも、`title`というカラム名を見て表示してきているのか。④で確認する。



## ④

```sql
-- テーブル定義
CREATE TABLE lessons (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    categories_id INT UNSIGNED
);

CREATE TABLE categories (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    description VARCHAR(50),
    title VARCHAR(50)
    
);


-- データを入れる
insert into categories(description, title) values ('難しい', '理系');
insert into categories(description, title) values ('簡単', '文系');

insert into lessons(name, categories_id) values ('Math1', 1);
insert into lessons(name, categories_id) values ('English1', 2);
insert into lessons(name, categories_id) values ('Science1', 2);
insert into lessons(name, categories_id) values ('Japanese', 2);
```

![スクリーンショット 2022-06-14 17.26.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154053/ef76f70b-04ef-5dde-5caa-cf99446095f8.png)

変わらず、titleが表示される。
ということは、titleというカラム名に意味を持たせられているっぽい。
適当なカラム名を設定してみる。

## ⑤
```sql
-- テーブル定義
CREATE TABLE lessons (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    categories_id INT UNSIGNED
);

CREATE TABLE categories (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    foo VARCHAR(50),
    bar VARCHAR(50)
    
);


-- データを入れる
insert into categories(foo, bar) values ('難しい', '理系');
insert into categories(foo, bar) values ('簡単', '文系');

insert into lessons(name, categories_id) values ('Math1', 1);
insert into lessons(name, categories_id) values ('English1', 2);
insert into lessons(name, categories_id) values ('Science1', 2);
insert into lessons(name, categories_id) values ('Japanese', 2);
```

![スクリーンショット 2022-06-14 17.30.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154053/4860a15f-7c5f-6242-7a54-dd1743cda0f3.png)


Categoriesにfoo, barの名前ではなく、そのままidが入っている。ということは、やっぱり`title`や`name`というカラム名に意味があり、勝手にやってくれているみたいだ。

ただし、生成されるcontrollerのindexアクション内は、特に変わりはなさそう。
```php
    public function index()
    {
        $this->paginate = [
            'contain' => ['Categories'],
        ];
        $lessons = $this->paginate($this->Lessons);

        $this->set(compact('lessons'));
    }
```

なので、viewを変更すれば、
```php
<td><?= $lesson->has('category') ? $this->Html->link($lesson->category->id, ['controller' => 'Categories', 'action' => 'view', $lesson->category->id]) : '' ?></td>
```
を
```php
<td><?= $lesson->has('category') ? $this->Html->link($lesson->category->foo, ['controller' => 'Categories', 'action' => 'view', $lesson->category->id]) : '' ?></td>
```
と変更すれば、表示も単純に変更される。

![スクリーンショット 2022-06-14 17.38.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154053/49fba42d-b056-374e-6b8e-13e4b21fac5c.png)


## ⑥マスターテーブルパターン(失敗)
lessonsテーブルで、subcategories_idというカラムを設定してみる。
ただしsubcategoriesテーブルは作成しない。


```sql
-- テーブル定義
CREATE TABLE lessons (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    categories_id INT UNSIGNED,
    subcategories_id INT UNSIGNED
);

CREATE TABLE categories (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50)
    
);


-- データを入れる
insert into categories(name) values ('理系');
insert into categories(name) values ('文系');

insert into lessons(name, categories_id, subcategories_id) values ('Math1', 1, 1);
insert into lessons(name, categories_id, subcategories_id) values ('English1', 2, 2);
insert into lessons(name, categories_id, subcategories_id) values ('Science1', 2, 2);
insert into lessons(name, categories_id, subcategories_id) values ('Japanese', 2, 1);
```

![スクリーンショット 2022-06-14 17.57.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154053/675629bd-ebde-86fe-5b9f-3eecb09e56e3.png)

`subcategories`テーブルがないと怒られる。
特にリレーションを定義しているわけではないのに、勝手にリレーションがあるかのようになっている。

```LessonsController.php
    public function index()
    {
        $this->paginate = [
            'contain' => ['Categories', 'Subcategories'],
        ];
        $lessons = $this->paginate($this->Lessons);

        $this->set(compact('lessons'));
    }
```

```Table/LessonsTable.php
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('lessons');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');

        $this->belongsTo('Categories', [
            'foreignKey' => 'categories_id',
        ]);
        $this->belongsTo('Subcategories', [
            'foreignKey' => 'subcategories_id',
        ]);
    }

```
```Entity/Lesson.php
    protected $_accessible = [
        'name' => true,
        'categories_id' => true,
        'subcategories_id' => true,
        'category' => true,
        'subcategory' => true,
    ];
```

この3つを見ても、リレーションが勝手に組まれているっぽい。
なので、以下のように変更すると正常に表示できた。
```LessonsController.php
    public function index()
    {
        $this->paginate = [
            'contain' => ['Categories'],
        ];
        $lessons = $this->paginate($this->Lessons);

        $this->set(compact('lessons'));
    }
```

```Table/LessonsTable.php
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('lessons');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');

        $this->belongsTo('Categories', [
            'foreignKey' => 'categories_id',
        ]);
    }
.
.
.
    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->existsIn(['categories_id'], 'Categories'));

        return $rules;
    }
```

```Entity/Lesson.php
    protected $_accessible = [
        'name' => true,
        'categories_id' => true,
        'subcategories_id' => true,
        'category' => true,
    ];
```

![スクリーンショット 2022-06-14 18.18.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154053/0863acde-eea6-1dd5-f4e4-152fe0027355.png)


## ⑦DB側でも外部キーの定義をする
```sql
-- テーブル定義
CREATE TABLE categories (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50)
);

CREATE TABLE lessons (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    categories_id INT UNSIGNED,
    FOREIGN KEY (categories_id) REFERENCES categories(id) ON DELETE CASCADE
);


-- データを入れる
insert into categories(name) values ('理系');
insert into categories(name) values ('文系');

insert into lessons(name, categories_id) values ('Math1', 1);
insert into lessons(name, categories_id) values ('English1', 2);
insert into lessons(name, categories_id) values ('Science1', 2);
insert into lessons(name, categories_id) values ('Japanese', 2);
```
いくつかのファイルを見ると、多分普通にリレーションを定義しないときと同じ気がする。

ここかから外部テーブル`subcategories`を追加したい。

```sql
CREATE TABLE subcategories (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50)
);

insert into subcategories(name) values ('数学');
insert into subcategories(name) values ('語学');

alter table lessons add subcategories_id INT UNSIGNED;

delete from lessons

insert into lessons(name, categories_id, subcategories_id) values ('Math1', 1, 1);
insert into lessons(name, categories_id, subcategories_id) values ('English1', 2, 1);
insert into lessons(name, categories_id, subcategories_id) values ('Science1', 2, 2);
insert into lessons(name, categories_id, subcategories_id) values ('Japanese', 2, 2);
```

subcategoriesを表示するために以下変更。
```LessonsController.php
    public function index()
    {
        $this->paginate = [
            'contain' => ['Categories', 'Subcategories'],
        ];
        $lessons = $this->paginate($this->Lessons);

        $this->set(compact('lessons'));
    }
```

```Table/LessonsTable.php
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('lessons');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');

        $this->belongsTo('Categories', [
            'foreignKey' => 'categories_id',
        ]);
        $this->belongsTo('Subcategories', [
            'foreignKey' => 'subcategories_id',
        ]);
    }
```

```Entity/Lesson.php
    protected $_accessible = [
        'name' => true,
        'categories_id' => true,
        'category' => true,
        'subcategories_id' => true,
        'subcategory' => true,
    ];
```
これでTemplateも修正すれば以下のように表示されます。
多分本当は`LessonsFixture.php`とかも修正しないといけないっぽいが、それは一旦置いとく。
![スクリーンショット 2022-06-14 19.36.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154053/e74f45e4-1dfe-0571-c968-c92ee37a6cc0.png)