WEBOPIXEL

CakePHP4のアソシエーション:一対多(hasMany)

Posted: 2020.04.29 / Category: CakePHP 

CakePHP4でブログシステムを作ろう第2回目です。
前回は、ログイン認証と投稿できるだけの簡単な機能を作ってみました。今回は、投稿したのが誰かわかるように、投稿とユーザーの関連付けしてみましょう。

Sponsored Link
この記事は下記の続きになります。
CakePHP4でブログサイト作るチュートリアル

誰が投稿したのかわかるように、するにはアソシエーションという機能を使ってモデル同士を関連付けする必要があります。
一人のユーザーが複数の投稿をしているので、一対多(hasMany)の関係になります。

一対多(hasMany)の関係図

データベース

データベースから編集しましょう。多であるPostsテーブルにuser_idカラムを追加します。
bakeでマイグレーションファイルを作成します。

$ bin/cake bake migration AddUserIdToPosts user_id:integer

生成したマイグレーションファイルを編集します。

config/Migrations/xxxxxxx_AddUserIdToPosts.php

public function change()
{
	$table = $this->table('posts');
	$table->addColumn('user_id', 'integer', [
		'default' => 1,
		'limit' => 11,
		'null' => false,
	]);
	$table->update();
}

マイグレーションを実行します。

$ bin/cake migrations migrate

Model

モデルエンティティにuser_idを追加。

src/Model/Entity/Post.php

class Post extends Entity
{
	protected $_accessible = [
		'title' => true,
		'description' => true,
		'body' => true,
		'published' => true,
		'created' => true,
		'modified' => true,
		'user_id' => true,	// 追加
	];
}

リレーションの設定はテーブルで行います。
一対多の場合は多の方が一に属しているということになるので、 多である投稿(Posts)にはbelongsToを指定します。

src/Model/Table/PostsTable.php

class PostsTable extends Table
{
public function initialize(array $config): void
{
	//...

	$this->belongsTo('Users');
}

編集&追加機能

編集できるようにコントローラーとビューを編集します。
コントローラーのeditaddアクションではユーザーを選択できるようにする必要があるので、リストで取得してビューに渡しましょう。

src/Controller/Admin/PostsController.php

class PostsController extends AdminController
{
	public function initialize(): void
    {
        parent::initialize();
        $this->loadModel('Users');
	}

	public function edit($id = null)
    {
        // ...

        // ユーザーリストを取得
        $users = $this->Users->find('list');

        $this->set(compact('post', 'users'));
    }
}

ビューでは特に考える必要がなく、他の入力と同じようにフォームコントロールでいけます。
ただセレクトボックスになります。

templates/Admin/Posts/edit.php or add.php

echo $this->Form->control('user_id');

詳細画面(view)

詳細画面はgetするときにcontain['Users']を指定します。

src/Controller/Admin/PostsController.php

public function view($id = null)
    {
	$post = $this->Posts->get($id, [
		'contain' => ['Users'],
	]);

	$this->set('post', $post);
}

ビューではこんな感じで表示します。

templates/Admin/Posts/view.php

<?= h($post->user->username) ?>

一覧画面(index)

一覧はページネートを使ってると思いますが、その場合はpaginatecontainを設定します。

src/Controller/Admin/PostsController.php

public function index()
{
	$this->paginate = [
		'contain' => ['Users']
	];
	$posts = $this->paginate($this->Posts);

	$this->set(compact('posts'));
}

ユーザーからの関連付け

投稿からの表示をしたので、今度はユーザーから表示します。

Model

ユーザーモデルは複数の投稿を持つのでhasManyを設定します。

src/Model/Table/UsersTable.php

class UsersTable extends Table
{
    public function initialize(array $config): void
    {
        //...

        $this->hasMany('Posts');
	}
}

Controller

例えばユーザー詳細画面に記事一覧を表示させたい場合はコントローラーのViewアクションを下記のようにします。

src/Controller/Admin/UsersController.php

public function view($id = null)
{
	$user = $this->Users->get($id, [
		'contain' => ['Posts'],
	]);

	$this->set(compact('user'));
}

View

あとはビューで$user->postsを展開していけば表示できます。
ただ、一覧の場合はページネートを使用することが多いと思いますのであまりこのような使い方はしないかもしれません。

templates/Admin/Users/view.php

<table>
	<tbody>
		<?php foreach ($user->posts as $post): ?>
		<tr>
			<td><?= $this->Number->format($post->id) ?></td>
			<td><?= h($post->title) ?></td>
			<td><?= $post->published ? __('Yes') : __('No'); ?></td>
			<td><?= h($post->created) ?></td>
		</tr>
		<?php endforeach; ?>
	</tbody>
</table>
ここまでのソースコードはこちら