クイックスタートガイド

CakePHPを学ぶ最も良い方法はなにか作ってみることです。 簡単なブックマークアプリケーションから作ることを始めましょう。

ブックマークチュートリアル

このチュートリアルは簡単なブックマークのためのアプリケーション (bookmarker) を作ります。 はじめに CakePHP のインストールを行い、データベースの作成、 そしてアプリケーションを素早く仕上げるための CakePHP が提供するツールを使います。

必要なもの:

  1. データベースサーバ。このチュートリアルでは MySQL サーバを使います。 データベースを作成するための SQL の知識が必要です。CakePHP は、それを前提としています。 MySQL を使用するとき、 PHP で pdo_mysql が有効になっていることを確認してください。
  2. 基礎的な PHP の知識。

始める前に、最新の PHP バージョンであることを確認してください。

php -v

最低でも PHP 5.6.0 (CLI) 以上をインストールしてください。 あなたのウェブサーバーの PHP バージョンもまた、5.6.0 以上でなければなりません。そして、 コマンドラインインターフェース (CLI) の PHP バージョンと同じバージョンがベストです。 完全なアプリケーションを確認したい場合、 cakephp/bookmarker をチェックアウトしてください。 さあ、はじめましょう!

CakePHP の取得

最も簡単な CakePHP のインストール方法は Composer を使う方法です。Composer は、 ターミナルやコマンドラインプロンプトから CakePHP をインストールのシンプルな方法です。 まだ準備ができていない場合、最初に Composer をダウンロードとインストールが必要です。 cURL をインストールされていたら、以下のように実行するのがが簡単です。

curl -s https://getcomposer.org/installer | php

もしくは Composer のウェブサイト から composer.phar をダウンロードすることができます。

そして、CakePHP アプリケーションのスケルトンを bookmarker ディレクトリにインストールするために、 インストールディレクトリからターミナルに以下の行をシンプルにタイプしてください。

php composer.phar create-project --prefer-dist cakephp/app bookmarker

Composer Windows Installer をダウンロードして実行した場合、インストールディレクトリ (例えば、 C:\wamp\www\dev\cakephp3) からターミナルに以下の行をタイプしてください。

composer self-update && composer create-project --prefer-dist cakephp/app bookmarker

Composer を使うメリットは、 正しいファイルパーミッションの設定や、 config/app.php ファイルの作成などのように、自動的に完全なセットアップをしてくれることです。

CakePHP をインストールする他の方法があります。 Composer を使いたくない場合、 インストール セクションをご覧ください.

CakePHP のダウンロードやインストール方法にかかわらず、いったんセットアップが完了すると、 ディレクトリ構成は以下のようになります。

/bookmarker
    /bin
    /config
    /logs
    /plugins
    /src
    /tests
    /tmp
    /vendor
    /webroot
    .editorconfig
    .gitignore
    .htaccess
    .travis.yml
    composer.json
    index.php
    phpunit.xml.dist
    README.md

CakePHP のディレクトリ構造がどのように働くかを学ぶのにいい機会かもしれません。 CakePHPのフォルダ構成 セクションをご覧ください。

インストールの確認

デフォルトホームページを確認することで、インストールが正しいことをざっと確かめることができます。 その前に、開発用サーバを起動する必要があります。

bin/cake server

注釈

Windows では、このコマンドは bin\cake server (バックスラッシュ) です。.

これで、 8765 ポートで PHP のビルドインウェブサーバーが起動します。ウェルカムページを見るために http://localhost:8765 をウェブブラウザーで開いてください。CakePHP がデータベース接続が 可能かどうか以外は、すべての確認事項がチェック済みになるべきです。そうでなければ、PHP 拡張の 追加のインストールやディレクトリのパーミッション設定が必要かもしれません。

データベースの作成

次に、ブックマークアプリケーションのデータベースをセットアップしましょう。 まだセットアップしていない場合、例えば cake_bookmarks のように、あなたの好きな名前で、 このチュートリアルで使用する空のデータベースを作成してください。必要なテーブルを作成するために、 以下の SQL を実行することができます。

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE bookmarks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(50),
    description TEXT,
    url TEXT,
    created DATETIME,
    modified DATETIME,
    FOREIGN KEY user_key (user_id) REFERENCES users(id)
);

CREATE TABLE tags (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    created DATETIME,
    modified DATETIME,
    UNIQUE KEY (title)
);

CREATE TABLE bookmarks_tags (
    bookmark_id INT NOT NULL,
    tag_id INT NOT NULL,
    PRIMARY KEY (bookmark_id, tag_id),
    FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
    FOREIGN KEY bookmark_key(bookmark_id) REFERENCES bookmarks(id)
);

複合主キーを持つ bookmarks_tags テーブルにお気づきでしょうか。CakePHP は、 ほぼどこでも複合主キーをサポートします。それは、マルチテナントなアプリケーションの構築が しやすくなります。

私たちが使用するテーブルやカラムの名前は恣意的ではありませんでした。CakePHP の 命名規則 を使用することによって、CakePHP がより効果的になり、 フレームワークの設定を避けられます。CakePHP はレガシーなデータベーススキーマに対応できるくらい 十分に柔軟ですが、規約に従うことで、時間を節約できます。

データベースの設定

次に、どこにデータベースあるか、そしてどうやってテータベースに接続するかを CakePHP に伝えましょう。おそらく、これが何らかの設定が必要となる最初で最後です。

この設定はとても単純です。あなたのセットアップを適用するために config/app.php ファイルの中の Datasources.default 配列の値を置き換えてください。 完全な設定配列の例は、以下のようになります。

return [
    // More configuration above.
    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            'username' => 'cakephp',
            'password' => 'AngelF00dC4k3~',
            'database' => 'cake_bookmarks',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'cacheMetadata' => true,
        ],
    ],
    // More configuration below.
];

1度 config/app.php ファイルを保存して、 ‘CakePHP is able to connect to the database’ がチェック済みであることを確認してください。

注釈

CakePHP のデフォルト設定ファイルは config/app.default.php にあります。

Scaffold コードの生成

データベースが CakePHP の命名規則に従っているので、 基本的なアプリケーションを 素早く生成するために bake コンソール アプリケーションが使用できます。 コマンドライン上で、以下のコマンドを実行してください。

// Windows 上では、代わりに bin\cake を使用する必要があります。
bin/cake bake all users
bin/cake bake all bookmarks
bin/cake bake all tags

これは、 users、 bookmarks、 tags リソースのためのコントローラ、モデル、ビュー、 それらに対応するテストケース、フィクスチャを生成します。あなたのサーバが停止している場合、 再起動して http://localhost:8765/bookmarks に行ってください。

アプリケーションのデータベーステーブルへのデータアクセスを提供する基本的だが機能的な アプリケーションを見てください。1度、ブックマーク一覧を表示して、いくつかの ユーザー、ブックマーク、タグを追加してください。

注釈

Not Found (404) ページが表示された場合、Apache の mod_rewrite モジュールが ロードされているか確かめてください。

パスワードハッシュを追加

(http://localhost:8765/users にアクセスして) ユーザーを作成した時、パスワードが平文で保存されることにおそらく気づくでしょう。 これはセキュリティの観点から、とても良くありませんので修正しましょう。

これはまた、CakePHP のモデル層について説明する良い機会です。CakePHP では、 オブジェクトの集合と、異なるクラスの単一オブジェクトを操作する方法を分けてます。 エンティティの集合は、 Table クラス内に格納され、1つのレコードに属する機能は、 Entity クラス内に格納されます。

例えば、パスワードのハッシュ化は、個々のレコードで行われ、エンティティオブジェクトに この振る舞いを実装します。パスワードがセットされるたびにハッシュ化したいので、 ミューテーターメソッドやセッターメソッドを使います。CakePHP は規約に基づいて、 エンティティの一つにプロパティをセットするセッターメソッドを呼びます。 では、パスワードのためのセッターを追加してみましょう。 src/Model/Entity/User.php に 以下を追加してください。

namespace App\Model\Entity;

use Cake\Auth\DefaultPasswordHasher; // この行を追加してください
use Cake\ORM\Entity;

class User extends Entity
{

    // bake で生成されたコード

    protected function _setPassword($value)
    {
        $hasher = new DefaultPasswordHasher();
        return $hasher->hash($value);
    }
}

今から既存のユーザのパスワードを更新してくだい。パスワードを変更した際、一覧もしくは詳細ページで、 入力した値の代わりにハッシュ化されたパスワードがあることを確認してください。CakePHP は、 デフォルトで bcrypt を使ってパスワードをハッシュ化します。既存のデータベースが動作している場合、 sha1 や md5 も 使用できます。

注釈

パスワードがハッシュ化されない場合、セッター関数の命名について、 クラスのパスワードメンバーと大文字小文字が同じかを確認してください。

タグを指定してブックマークを取得

これで、パスワードを安全に保存できますので、アプリケーションにもっと面白い機能を構築できます。 一度ブックマークのコレクションを蓄えて、タグでの検索ができるようになると便利です。 次は、タグでのブックマークを検索するため、ルート、コントローラのアクション、finder メソッドを実装します。

理想的には、 http://localhost:8765/bookmarks/tagged/funny/cat/gifs のような URL にしたいと思います。この URL は、 ‘funny’, ‘cat’, もしくは ‘gifs’ タグが付いたブックマークすべてを検索することを意図しています。これを実装する前に、 新しいルートを追加します。 config/routes.php を以下のようにしてください。

<?php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\Router;

Router::defaultRouteClass(DashedRoute::class);

// 新しいルートを tagged アクションのために追加します。
// 末尾の `*` は、渡された引数を持っていることを
// CakePHP に伝えます。
Router::scope(
    '/bookmarks',
    ['controller' => 'Bookmarks'],
    function ($routes) {
        $routes->connect('/tagged/*', ['action' => 'tags']);
    }
);

Router::scope('/', function ($routes) {
    // デフォルトのホームと /pages/* ルートへの接続
    $routes->connect('/', [
        'controller' => 'Pages',
        'action' => 'display', 'home'
    ]);
    $routes->connect('/pages/*', [
        'controller' => 'Pages',
        'action' => 'display'
    ]);

    // デフォルトのルートへ接続
    $routes->fallbacks();
});

上記は、 /bookmarks/tagged/ パスを BookmarksController::tags() に接続する 新しい「ルート」を定義します。ルートを定義することによて、 URL の見た目と、 それらどのように実装されたかを分離することができます。 http://localhost:8765/bookmarks/tagged にアクセスした場合、CakePHP から コントローラのアクションがないことを伝える役に立つエラーページが表示されます。 今から存在しないメソッドを実装してみましょう。 src/Controller/BookmarksController.php に以下を追加してください。

public function tags()
{
    // CakePHP によって提供された 'pass' キーは全ての
    // リクエストにある渡された URL のパスセグメントです。
    $tags = $this->request->getParam('pass');

    // タグ付きのブックマークを探すために BookmarksTable を使用
    $bookmarks = $this->Bookmarks->find('tagged', [
        'tags' => $tags
    ]);

    // ビューテンプレートに変数を渡します
    $this->set([
        'bookmarks' => $bookmarks,
        'tags' => $tags
    ]);
}

リクエストデータの他の部分にアクセスするためには リクエスト セクションを 参考にしてください。

Finder メソッドの作成

CakePHP では、コントローラのアクションをスリムに保ち、アプリケーションの多くのロジックを モデルに置くことをお勧めします。 /bookmarks/tagged の URL にアクセスした場合、 findTagged() メソッドがまだ実装されていないエラーが表示されます。 src/Model/Table/BookmarksTable.php に以下を追加してください。

// $query 引数は、クエリービルダーのインスタンスです。
// $options 配列には、コントローラのアクション中で find('tagged') に渡した
// 'tag' オプションが含まれます。
public function findTagged(Query $query, array $options)
{
    $bookmarks = $this->find()
        ->select(['id', 'url', 'title', 'description']);

    if (empty($options['tags'])) {
        $bookmarks
            ->leftJoinWith('Tags')
            ->where(['Tags.title IS' => null]);
    } else {
        $bookmarks
            ->innerJoinWith('Tags')
            ->where(['Tags.title IN ' => $options['tags']]);
    }

    return $bookmarks->group(['Bookmarks.id']);
}

カスタム Finder メソッド を実装しました。 これは、再利用可能なクエリーをまとめることを実現する CakePHP の非常に強力な概念です。 Finder メソッドは、常に クエリビルダ オブジェクトとオプション配列を パラメータとして取得します。Finder メソッドは、クエリーを操作し、任意の必須条件や抽出条件を 追加することができます。完了時、Finder メソッドは更新されたクエリーオブジェクトを 返さなければなりません。finder の中で、一致するタグを持つ特定のブックマークを検索するために、 innerJoinWith()where() そして group メソッドを使います。 タグの指定がない場合、タグなしでブックマークを検索するために leftJoinWith() を使用して、 ‘where’ 条件を変更します。

ビューの作成

/bookmarks/tagged の URL にアクセスすると、 CakePHP は、ビューファイルがないことを 知らせるエラーを表示します。次に、ビューファイルを tags() アクションのために作りましょう。 src/Template/Bookmarks/tags.ctp に以下の内容を追加します。

<h1>
    Bookmarks tagged with
    <?= $this->Text->toList(h($tags)) ?>
</h1>

<section>
<?php foreach ($bookmarks as $bookmark): ?>
    <article>
        <!-- Use the HtmlHelper to create a link -->
        <h4><?= $this->Html->link($bookmark->title, $bookmark->url) ?></h4>
        <small><?= h($bookmark->url) ?></small>

        <!-- Use the TextHelper to format text -->
        <?= $this->Text->autoParagraph(h($bookmark->description)) ?>
    </article>
<?php endforeach; ?>
</section>

上記のコードは HtmlText を ビューの出力生成を補助するために使いました。また、 HTMLエンコード出力するために h ショートカット関数を使いました。HTML インジェクション問題を防ぐために ユーザーデータ出力時には、必ず h() を使用することを覚えておいて下さい。

ビューテンプレートファイルのための CakePHP の規約に従って tags.ctp ファイルを作りました。 この規約は、小文字を使い、コントローラのアクション名をアンダースコア化したテンプレート名にすることです。

ビューで $tags$bookmarks 変数を使えることにお気づきでしょう。 コントローラで set() メソッドを使って、指定した変数をビューに送るためにセットします。 ビューは、渡されたすべての変数をテンプレート内でローカル変数として利用可能にします。

/bookmarks/tagged/funny の URL にアクセスできるようにして、 全ての ‘funny’ でタグ付けされたブックマークを確認しましょう。

ここまで、ブックマーク、タグ、ユーザーを管理する基本的なアプリケーションを作成してきました。 しかしながら、全員のタグが全員に見えてしまいます。次の章では、認証を実装し、現在のユーザーに 属するブックマークのみを表示するよう制限します。

あなたのアプリケーションの構築を続けるために ブックマークチュートリアル パート2 を読み続けるか、 CakePHP で出来ることをより詳しく学ぶために ドキュメントの中に飛び込んで ください。

ブックマークチュートリアル パート2

チュートリアルの前編 を終えると、 ごく基本的なブックマークアプリケーションができているでしょう。このチャプターでは認証機能と 各ユーザーが自分のブックマークだけを閲覧/編集できるように制限する機能を追加します。

ログインを追加

CakePHP では、認証は コンポーネント によって制御されます。 コンポーネントは再利用可能な特定の機能や概念を作成するための方法と考えることができます。 コンポーネントもまた、コントローラのイベントのライフサイクルをフックすることでアプリケーションに 作用することができます。初めに、 AuthComponent をアプリケーションに追加しましょう。 すべてのメソッドに認証を必須にすることをおすすめします。では AuthComponent を AppController に追加しましょう。

// src/Controller/AppController.php の中で
namespace App\Controller;

use Cake\Controller\Controller;

class AppController extends Controller
{
    public function initialize()
    {
        $this->loadComponent('Flash');
        $this->loadComponent('Auth', [
            'authenticate' => [
                'Form' => [
                    'fields' => [
                        'username' => 'email',
                        'password' => 'password'
                    ]
                ]
            ],
            'loginAction' => [
                'controller' => 'Users',
                'action' => 'login'
            ],
            'unauthorizedRedirect' => $this->referer() // 未認証時、元のページを返します。
        ]);

        // PagesController が動作し続けるように
        // display アクションを許可
        $this->Auth->allow(['display']);
    }
}

これで、 FlashAuth のコンポーネントを読み込むと CakePHP に示しました。 さらに users テーブルの email をユーザー名として使用するように AuthComponent の設定を カスタマイズしました。何かの URL にアクセスすると /users/login に遷移するようになりますが、 まだそのコードが存在しないというエラーページが表示されるでしょう。 それでは、ログインアクションを作成しましょう。

// src/Controller/UsersController.php の中で
public function login()
{
    if ($this->request->is('post')) {
        $user = $this->Auth->identify();
        if ($user) {
            $this->Auth->setUser($user);
            return $this->redirect($this->Auth->redirectUrl());
        }
        $this->Flash->error('あなたのユーザー名またはパスワードが不正です。');
    }
}

さらに src/Template/Users/login.ctp に以下のように追記します。

<h1>Login</h1>
<?= $this->Form->create() ?>
<?= $this->Form->control('email') ?>
<?= $this->Form->control('password') ?>
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>

注釈

control() は 3.4 以降で使用可能です。それより前のバージョンでは、代わりに input() を使用してください。

これでシンプルなログインフォームができました。ハッシュ化されたパスワードを持つユーザーで ログインすることができるはずです。

注釈

もしハッシュ化されたパスワードを持つユーザーがいなければ、 loadComponent('Auth') の行をコメントアウトして、ユーザーを編集して新しいパスワードを保存して下さい。

これでログインできるようになっているはずです。もしできない場合、ユーザーのパスワードが ハッシュ化されていることを確認してください。

ログアウトを追加

これで人々はログインできますので、ログアウトする方法も同じように提供したいでしょう。 ここでも UsersController に以下のコードを追加します。

public function initialize()
{
    parent::initialize();
    $this->Auth->allow(['logout']);
}

public function logout()
{
    $this->Flash->success('ログアウトします。');
    return $this->redirect($this->Auth->logout());
}

このコードは、パブリックなアクションでログアウトのメソッドとして logout アクションを ホワイトリストに加えます。これで /users/logout にアクセスするとログアウトされて ログインページに遷移します。

新規登録を有効にする

ログインしていない状態で /users/add にアクセスした場合、ログインページに遷移してしまうでしょう。 人々がアプリケーションにサインアップできるように修正しましょう。 UsersController に以下を 追記します。

public function initialize()
{
    parent::initialize();
    // 許可するアクション一覧に logout を追加
    $this->Auth->allow(['logout', 'add']);
}

上記では add() アクションは認証や許可が不要であることを AuthComponent に示しています。 Users/add.ctp をクリーンアップする時間を作り、誤解を招くようなリンクを削除しても、 このまま次のセクションに進んでもかまいません。このチュートリアルではユーザーの編集、 表示または一覧は構築しません。 それらのアクションは AuthComponent が拒否します。

ブックマークへのアクセスを制限する

ユーザーがログインできるようになったので、ユーザーが自分が作成したブックマークだけを表示できるよう 制限しましょう。これは ‘authorization’ アダプタを使用して制限します。 要件は非常に単純です。 いくつかの簡単なコードを BookmarksController に書きます。 しかし、これをやる前にアプリケーションがどのようにアクションを許可するかを AuthComponent に示しましょう。 AppController に以下を追加します。

public function isAuthorized($user)
{
    return false;
}

また、 AppControllerAuth の設定を以下のように追加します。

'authorize' => 'Controller',

initialize() メソッドはこのようになります。

public function initialize()
{
    $this->loadComponent('Flash');
    $this->loadComponent('Auth', [
        'authorize'=> 'Controller',//この行を追加
        'authenticate' => [
            'Form' => [
                'fields' => [
                    'username' => 'email',
                    'password' => 'password'
                ]
            ]
        ],
        'loginAction' => [
            'controller' => 'Users',
            'action' => 'login'
        ],
        'unauthorizedRedirect' => $this->referer()
    ]);

    // PagesController が動作し続けるように
    // display アクションを許可
    $this->Auth->allow(['display']);
}

デフォルトではアクセスを拒否し、必要に応じて一つづつアクセス権を付与しましょう。 はじめに、ブックマークに許可ロジックを追加します。 BookmarksController に以下を追加します。

public function isAuthorized($user)
{
    $action = $this->request->getParam('action');

    // add と index アクションは常に許可します。
    if (in_array($action, ['index', 'add', 'tags'])) {
        return true;
    }
    // その他のすべてのアクションは、id を必要とします。
    if (!$this->request->getParam('pass.0')) {
        return false;
    }

    // ブックマークが現在のユーザに属するかどうかをチェック
    $id = $this->request->getParam('pass.0');
    $bookmark = $this->Bookmarks->get($id);
    if ($bookmark->user_id == $user['id']) {
        return true;
    }
    return parent::isAuthorized($user);
}

これで、自分のものではないブックマークを表示または編集、削除しようとすると、 元のページにリダイレクトされるはずです。ただし、何のエラーメッセージはされないでしょう。 それでは次のように修正しましょう。

// src/Template/Layout/default.ctp の中の
// 既存のフラッシュメッセージの下で
<?= $this->Flash->render() ?>

これで許可エラーメッセージが表示されるはずです。

一覧表示とフォームを修正する

詳細と削除が動作する一方で、追加と一覧表示には少し問題があります:

  1. ブックマークを追加するときにユーザーを選べる
  2. ブックマークを編集するときにユーザーを選べる
  3. 一覧ページに他のユーザーのブックマークが表示される

まず追加のフォームから取り組みましょう。はじめに src/Template/Bookmarks/add.ctp から control('user_id') を削除します。 削除したら、 src/Controller/BookmarksController.phpadd() アクションを以下のように修正します。

public function add()
{
    $bookmark = $this->Bookmarks->newEntity();
    if ($this->request->is('post')) {
        $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->getData());
        $bookmark->user_id = $this->Auth->user('id');
        if ($this->Bookmarks->save($bookmark)) {
            $this->Flash->success('ブックマークを保存しました。');
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error('ブックマークは保存できませんでした。もう一度お試しください。');
    }
    $tags = $this->Bookmarks->Tags->find('list');
    $this->set(compact('bookmark', 'tags'));
    $this->set('_serialize', ['bookmark']);
}

エンティティのプロパティにセッションデータを設定することで、ブックマークがほかのユーザーに変更される 可能性を排除しています。編集フォームとアクションも同様にします。 src/Controller/BookmarksController.phpedit() アクションを以下のようにします。

public function edit($id = null)
{
    $bookmark = $this->Bookmarks->get($id, [
        'contain' => ['Tags']
    ]);
    if ($this->request->is(['patch', 'post', 'put'])) {
        $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->getData());
        $bookmark->user_id = $this->Auth->user('id');
        if ($this->Bookmarks->save($bookmark)) {
            $this->Flash->success('ブックマークを保存しました。');
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error('ブックマークは保存できませんでした。もう一度お試しください。');
    }
    $tags = $this->Bookmarks->Tags->find('list');
    $this->set(compact('bookmark', 'tags'));
    $this->set('_serialize', ['bookmark']);
}

一覧表示

さて、現在ログインしているユーザーのブックマークだけを表示する必要があります。 paginate() の呼び出しを修正をすることでそのようにできます。 src/Controller/BookmarksController.phpindex() アクションを以下のようにします。

public function index()
{
    $this->paginate = [
        'conditions' => [
            'Bookmarks.user_id' => $this->Auth->user('id'),
        ]
    ];
    $this->set('bookmarks', $this->paginate($this->Bookmarks));
    $this->set('_serialize', ['bookmarks']);
}

同様に tags() アクションと関連する検索メソッドを修正しましょう。 これはあなた自身で完了できるように宿題として残しておきます。

タグ付け機能を改良する

現在は、TagsController ではすべてのアクセスが拒否されるため、新しいタグを追加することは困難です。 アクセスを許可する代わりに、カンマ区切りのテキストフィールドを使用してタグ選択UIを改良できます。 これはユーザーに良い体験を与え、ORM の素晴らしい機能をさらに使うことができます。

計算済みフィールドを追加

エンティティの整形済みのタグを取得するする簡単な方法が必要なので、バーチャル/計算済みのフィールドを エンティティに追加しましょう。 src/Model/Entity/Bookmark.php に以下を追加します。

use Cake\Collection\Collection;

protected function _getTagString()
{
    if (isset($this->_properties['tag_string'])) {
        return $this->_properties['tag_string'];
    }
    if (empty($this->tags)) {
        return '';
    }
    $tags = new Collection($this->tags);
    $str = $tags->reduce(function ($string, $tag) {
        return $string . $tag->title . ', ';
    }, '');
    return trim($str, ', ');
}

計算済みのプロパティ $bookmark->tag_string にアクセスできるようになります。 このプロパティはあとで入力時に使用します。 あとで保存するので tag_string プロパティを エンティティの _accessible リストに追加することを忘れないでください。

src/Model/Entity/Bookmark.php$_accessibletag_string を このように追加してください。

protected $_accessible = [
    'user_id' => true,
    'title' => true,
    'description' => true,
    'url' => true,
    'user' => true,
    'tags' => true,
    'tag_string' => true,
];

ビューを修正する

エンティティを修正するとタグ用の新しいインプットを追加することができます。 src/Template/Bookmarks/add.ctpsrc/Template/Bookmarks/edit.ctp の すでにある tags._ids のインプットを以下と置き換えます。

echo $this->Form->control('tag_string', ['type' => 'text']);

タグ文字列を保存する

これで存在するタグを文字列として表示できます。同様にデータを保存したいでしょう。 tag_string をアクセス可能に設定したので、ORMはリクエストからエンティティにデータをコピーします。 beforeSave() フックメソッドを使用して、タグ文字列を解析し、関連するエンティティを検索/構築します。 src/Model/Table/BookmarksTable.php に以下を追加します。

public function beforeSave($event, $entity, $options)
{
    if ($entity->tag_string) {
        $entity->tags = $this->_buildTags($entity->tag_string);
    }
}

protected function _buildTags($tagString)
{
    // タグに trim 適用
    $newTags = array_map('trim', explode(',', $tagString));
    // すべての空のタグを削除
    $newTags = array_filter($newTags);
    // 重複するタグの削減
    $newTags = array_unique($newTags);

    $out = [];
    $query = $this->Tags->find()
        ->where(['Tags.title IN' => $newTags]);

    // 新しいタグの一覧から既存のタグを削除
    foreach ($query->extract('title') as $existing) {
        $index = array_search($existing, $newTags);
        if ($index !== false) {
            unset($newTags[$index]);
        }
    }
    // 既存のタグの追加
    foreach ($query as $tag) {
        $out[] = $tag;
    }
    // 新しいタグの追加
    foreach ($newTags as $tag) {
        $out[] = $this->Tags->newEntity(['title' => $tag]);
    }
    return $out;
}

このコードはこれまでに行ったことよりも少し複雑ですが、これは CakePHP の ORM がいかに強力かを お見せするのに役立ちます。 コレクション メソッドを使用してクエリ結果を 操作することができます。また、エンティティをその場で容易に作成するシナリオを扱うことができます。

まとめ

認証と基本的な許可/アクセス制御シナリオを処理できるようブックマークアプリケーションを拡張してきました。 また、FormHelper と ORM の機能を活用することで、いくつかの素晴らしい UX の改善を追加しました。

CakePHP を探求する時間を割いていただきありがとうございます。次は ブログチュートリアル を完了するか、 データベースアクセス & ORM について更に学ぶか、もしくは CakePHP の利用 を熟読してください。