本記事では、複数テーブルを結合したアソシエーションの使い方を説明いたします。
複数テーブルを利用するプログラムは、複雑な印象を持たれると思いますが、基本的な機能をbakeコマンドで自動生成することができます。
アソシエーションの使い方を説明するにあたり、事前準備が必要になります。
事前準備ができない場合には、各テーブルのデータ構造をイメージしながら読み進めていただければと思います。
今回のサンプルプログラムは、ユーザー毎に複数のメモを登録できるアプリケーションで説明いたします。
アソシエーションとは
アプリケーションでは、異なるオブジェクト同士を関連付けたいことはよくあります。
例えば、ユーザー毎に複数のメモを登録できるようなモデルの関係性などです。ユーザーは多くのメモを持っています。
CakePHP はこうしたアソシエーションが簡単に管理できます。
CakePHP には、以下の4つのアソシエーションがあります。
- hasOne
- hasMany
- belongsTo
- belongsToMany
事前準備
準備としては、テーブルの作成と、サンプルデータの登録を行います。
サンプルテーブルの作成
作成するテーブルは、ユーザー情報を管理するテーブルと、メモ帳を管理するテーブルを作成します。
ユーザー情報とメモ帳のアソシエーションは、1対多となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
ユーザー情報テーブル CREATE TABLE IF NOT EXISTS `users` ( `id` int(11) NOT NULL auto_increment, `name` varchar(256) NOT NULL, `passwd` varchar(256) NOT NULL, `status` varchar(32) NOT NULL, `logined_at` timestamp NOT NULL default CURRENT_TIMESTAMP, `created` datetime NOT NULL, `modified` datetime NOT NULL, UNIQUE KEY `id` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='ユーザー情報' AUTO_INCREMENT=0 ; コメント情報テーブル CREATE TABLE IF NOT EXISTS `notes` ( `id` int(11) NOT NULL auto_increment, `user_id` int(11) NOT NULL, `note` varchar(1024), `created` datetime NOT NULL, `modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='メモ情報' AUTO_INCREMENT=0 ; |
サンプルデータの登録
下記のクエリーを実行し、各テーブルへデータ登録を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
users テーブルへのデータ登録 insert into `users` (`name`, `passwd`, `status`, `logined_at`, `created`, `modified`) values ('guest-1', 'passwd-1', 'enabled', now(), now(), now()), ('guest-2', 'passwd-2', 'enabled', now(), now(), now()), ('guest-3', 'passwd-3', 'enabled', now(), now(), now()); notes テーブルへのデータ登録 insert into `notes` (`user_id`, `note`, `created`, `modified`) values (1, 'メモ1ー1',now(), now()), (1, 'メモ1-2',now(), now()), (2, 'メモ2-1',now(), now()), (3, 'メモ3ー1',now(), now()), (3, 'メモ3-2',now(), now()); |
サンプルデータの確認
正常にサンプルデータが登録されているかを、テーブルを検索して確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
MySQL> select * from users; +----+---------+----------+---------+---------------------+---------------------+---------------------+ | id | name | passwd | status | logined_at | created | modified | +----+---------+----------+---------+---------------------+---------------------+---------------------+ | 1 | guest-1 | passwd-1 | enabled | 2019-01-24 09:56:43 | 2019-01-24 09:56:43 | 2019-01-24 09:56:43 | | 2 | guest-2 | passwd-2 | enabled | 2019-01-24 09:56:43 | 2019-01-24 09:56:43 | 2019-01-24 09:56:43 | | 3 | guest-3 | passwd-3 | enabled | 2019-01-24 09:56:43 | 2019-01-24 09:56:43 | 2019-01-24 09:56:43 | +----+---------+----------+---------+---------------------+---------------------+---------------------+ 3 rows in set (0.01 sec) MySQL> select * from notes; +----+---------+-----------------+---------------------+---------------------+ | id | user_id | note | created | modified | +----+---------+-----------------+---------------------+---------------------+ | 1 | 1 | メモ1ー1 | 2019-01-24 09:55:29 | 2019-01-24 09:55:29 | | 2 | 1 | メモ1-2 | 2019-01-24 09:55:29 | 2019-01-24 09:55:29 | | 3 | 2 | メモ2-1 | 2019-01-24 09:55:29 | 2019-01-24 09:55:29 | | 4 | 3 | メモ3ー1 | 2019-01-24 09:55:29 | 2019-01-24 09:55:29 | | 5 | 3 | メモ3-2 | 2019-01-24 09:55:29 | 2019-01-24 09:55:29 | +----+---------+-----------------+---------------------+---------------------+ 5 rows in set (0.00 sec) |
プログラムファイルの生成
cakePHPでは、bakeによるソースコードのジェネレート機能があります。
今回は、この bake を使って、データベースに登録されているテーブルを基に、各プログラムファイルを生成していきます。
作成できるモデル一覧
下記のコマンドを実行し、作成できるモデル一覧を確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[hogehoge@sv7108 app]$ ./bin/cake bake model Welcome to CakePHP v3.1.14 Console --------------------------------------------------------------- App : src Path: /home/hogehoge/example.com/public_html/sandbox/app/src/ PHP : 5.4.16 --------------------------------------------------------------- Choose a model to bake from the following: - Notes - Users |
モデルの作成
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 |
Users モデルの生成 [hogehoge@sv7108 app]$ bin/cake bake model Users Welcome to CakePHP v3.1.14 Console --------------------------------------------------------------- App : src Path: /home/hogehoge/example.com/public_html/sandbox/app/src/ PHP : 5.4.16 --------------------------------------------------------------- One moment while associations are detected. Baking table class for Users... Creating file /home/hogehoge/example.com/public_html/sandbox/app/src/Model/Table/UsersTable.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Model/Table/UsersTable.php` Deleted `/home/hogehoge/example.com/public_html/sandbox/app/src/Model/Table/empty` Baking entity class for User... Creating file /home/hogehoge/example.com/public_html/sandbox/app/src/Model/Entity/User.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Model/Entity/User.php` Deleted `/home/hogehoge/example.com/public_html/sandbox/app/src/Model/Entity/empty` Baking test fixture for Users... Creating file /home/hogehoge/example.com/public_html/sandbox/app/tests/Fixture/UsersFixture.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/tests/Fixture/UsersFixture.php` Deleted `/home/hogehoge/example.com/public_html/sandbox/app/tests/Fixture/empty` Bake is detecting possible fixtures... Baking test case for App\Model\Table\UsersTable ... Creating file /home/hogehoge/example.com/public_html/sandbox/app/tests/TestCase/Model/Table/UsersTableTest.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/tests/TestCase/Model/Table/UsersTableTest.php` [hogehoge@sv7108 app]$ Notes モデルの生成 [hogehoge@sv7108 app]$ bin/cake bake model Notes Welcome to CakePHP v3.1.14 Console --------------------------------------------------------------- App : src Path: /home/hogehoge/example.com/public_html/sandbox/app/src/ PHP : 5.4.16 --------------------------------------------------------------- One moment while associations are detected. Baking table class for Notes... Creating file /home/hogehoge/example.com/public_html/sandbox/app/src/Model/Table/NotesTable.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Model/Table/NotesTable.php` Baking entity class for Note... Creating file /home/hogehoge/example.com/public_html/sandbox/app/src/Model/Entity/Note.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Model/Entity/Note.php` Baking test fixture for Notes... Creating file /home/hogehoge/example.com/public_html/sandbox/app/tests/Fixture/NotesFixture.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/tests/Fixture/NotesFixture.php` Bake is detecting possible fixtures... Baking test case for App\Model\Table\NotesTable ... Creating file /home/hogehoge/example.com/public_html/sandbox/app/tests/TestCase/Model/Table/NotesTableTest.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/tests/TestCase/Model/Table/NotesTableTest.php` [hogehoge@sv7108 app]$ 各モデルは、下記の場所に保存されています。 /home/hogehoge/example.com/public_html/sandbox/app/src/Model |--Behavior | |--empty |--Entity | |--Note.php | |--User.php |--Table | |--NotesTable.php | |--UsersTable.php [hogehoge@sv7108 Model]$ |
コントローラーの作成
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 |
Users コントローラーの生成 [hogehoge@sv7108 app]$ bin/cake bake controller Users Welcome to CakePHP v3.1.14 Console --------------------------------------------------------------- App : src Path: /home/hogehoge/example.com/public_html/sandbox/app/src/ PHP : 5.4.16 --------------------------------------------------------------- Baking controller class for Users... Creating file /home/hogehoge/example.com/public_html/sandbox/app/src/Controller/UsersController.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Controller/UsersController.php` Bake is detecting possible fixtures... Baking test case for App\Controller\UsersController ... Creating file /home/hogehoge/example.com/public_html/sandbox/app/tests/TestCase/Controller/UsersControllerTest.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/tests/TestCase/Controller/UsersControllerTest.php` [hogehoge@sv7108 app]$ Notes コントローラーの生成 [hogehoge@sv7108 app]$ bin/cake bake controller Notes Welcome to CakePHP v3.1.14 Console --------------------------------------------------------------- App : src Path: /home/hogehoge/example.com/public_html/sandbox/app/src/ PHP : 5.4.16 --------------------------------------------------------------- Baking controller class for Notes... Creating file /home/hogehoge/example.com/public_html/sandbox/app/src/Controller/NotesController.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Controller/NotesController.php` Bake is detecting possible fixtures... Baking test case for App\Controller\NotesController ... Creating file /home/hogehoge/example.com/public_html/sandbox/app/tests/TestCase/Controller/NotesControllerTest.php Wrote `/home/hogehoge/example.com/public_html/sandbox/app/tests/TestCase/Controller/NotesControllerTest.php` [hogehoge@sv7108 app]$ |
テンプレートの作成
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 97 98 99 |
Users テンプレートの生成 [hogehoge@sv7108 app]$ bin/cake bake template Users Welcome to CakePHP v3.1.14 Console --------------------------------------------------------------- App : src Path: /home/hogehoge/example.com/public_html/sandbox/app/src/ PHP : 5.4.16 --------------------------------------------------------------- Baking `index` view template file... File `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Users/index.ctp` exists Do you want to overwrite? (y/n/a/q) [n] > y Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Users/index.ctp` Baking `view` view template file... File `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Users/view.ctp` exists Do you want to overwrite? (y/n/a/q) [n] > y Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Users/view.ctp` Baking `add` view template file... File `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Users/add.ctp` exists Do you want to overwrite? (y/n/a/q) [n] > y Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Users/add.ctp` Baking `edit` view template file... File `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Users/edit.ctp` exists Do you want to overwrite? (y/n/a/q) [n] > y Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Users/edit.ctp` Notes テンプレートの生成 [hogehoge@sv7108 app]$ ./bin/cake bake template Notes Welcome to CakePHP v3.1.14 Console --------------------------------------------------------------- App : src Path: /home/hogehoge/example.com/public_html/sandbox/app/src/ PHP : 5.4.16 --------------------------------------------------------------- Baking `index` view template file... Creating file /home/hogehoge/example.com/public_html/sandbox/app/src/Template/Notes/index.ctp Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Notes/index.ctp` Baking `view` view template file... Creating file /home/hogehoge/example.com/public_html/sandbox/app/src/Template/Notes/view.ctp Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Notes/view.ctp` Baking `add` view template file... Creating file /home/hogehoge/example.com/public_html/sandbox/app/src/Template/Notes/add.ctp Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Notes/add.ctp` Baking `edit` view template file... Creating file /home/hogehoge/example.com/public_html/sandbox/app/src/Template/Notes/edit.ctp Wrote `/home/hogehoge/example.com/public_html/sandbox/app/src/Template/Notes/edit.ctp` [hogehoge@sv7108 app]$ 各テンプレートは、下記の場所に保存されます。 /home/hogehoge/example.com/public_html/sandbox/app/src/Template |--Element | |--Flash | | |--default.ctp | | |--error.ctp | | |--success.ctp |--Email | |--html | | |--default.ctp | |--text | | |--default.ctp |--Error | |--error400.ctp | |--error500.ctp |--Layout | |--Email |--Notes ※今回生成したファイル郡 | |--add.ctp | |--edit.ctp | |--index.ctp | |--view.ctp |--Pages | |--home.ctp |--Users ※今回生成したファイル郡 | |--add.ctp | |--edit.ctp | |--index.ctp | |--view.ctp |
テーブルの結合条件の確認
Users と Notes テーブルのアソシエーションの設定は、各コントローラーの paginate にて、テーブルの設定を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
./src/Controller/UsersController.php (一部抜粋) public function index() { $users = $this->paginate($this->Users); ※関連の設定 $this->set(compact('users')); $this->set('_serialize', ['users']); } ./src/Controller/NotesController.php (一部抜粋) public function index() { $this->paginate = [ 'contain' => ['Users'] ※関連の設定 ]; $notes = $this->paginate($this->Notes); $this->set(compact('notes')); $this->set('_serialize', ['notes']); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
テンプレートファイル内では、下記のように関連するテーブルへのアクセスが可能です。 ./src/Template/Notes/index.ctp (一部抜粋) <tbody> <?php foreach ($notes as $note): ?> <tr> <td><?= $this->Number->format($note->id) ?></td> <td><?= $note->has('user') ? $this->Html->link($note->user->name, ['controller' => 'Users', 'action' => 'view', $note ->user->id]) : '' ?></td> <td><?= h($note->note) ?></td> <td><?= h($note->created) ?></td> <td><?= h($note->modified) ?></td> <td class="actions"> <?= $this->Html->link(__('View'), ['action' => 'view', $note->id]) ?> <?= $this->Html->link(__('Edit'), ['action' => 'edit', $note->id]) ?> <?= $this->Form->postLink(__('Delete'), ['action' => 'delete', $note->id], ['confirm' => __('Are you sure you want to delete # {0}?', $note->id)]) ?> </td> </tr> <?php endforeach; ?> </tbody> |
動作確認
下記のURLにアクセスすると、生成した画面が表示されます。
http://ドメイン名/app/users
http://ドメイン名/app/notes
まとめ
ここまで説明したとおり、複数テーブルを結合したアソシエーションの実装は、非常に簡単になっています。
それぞれ、適切なコマンドを実行することで、大幅に工数を削減することができると思います。
ただ、自動生成したソースを、しっかりとメンテナンスできるように把握しておく必要があります。
自動生成だけに頼った場合に、深い部分の実装に手を入れることが難しくなってしまいます。
そのため、しっかりと、コマンドと、実行後に生成されるファイルの関連性を理解しておく必要があります。
手間がかかる部分もありますが、上記の一連の作業の通り、圧倒的に作業工数が削減できます。
複数テーブルを結合する検索処理でも、SQLクエリーでJOINするよりも、非常に簡単となります。