CakePHP

Release 4.x

Cake Software Foundation

CakePHP 概要

CakePHP はウェブ開発を単純に簡単にできるように開発されました。 オールインワンのツールボックスは色々なパーツが一緒に動いたり、バラバラに動いたりできるようにします。

この概要の目的は、CakePHP の一般的なコンセプトとそのコンセプトがどのように CakePHP の中で働くのかを紹介することです。 プロジェクトをすぐに始めたいなら、 チュートリアルから始める か 直接ドキュメントを見て下さい 。

設定より規約

CakePHP は基礎的な構造をクラス名、ファイル名、DB のテーブル名や他の規約から決定します。規約を学ぶことで、不必要な設定や他の一般的なアプリと同じ構造をいちいち書かなくて済むので、簡単に色々なプロジェクトを進められます。この 規約 は、いろいろな CakePHP で使う規約をカバーしています。

モデル層

モデル層はビジネスロジックを実装するアプリケーションの部品を表します。アプリケーションにおいてデータを取得し、それを最初の意味ある形に変換する役割を担います。これには、加工、検証 (validating) 、関連付け (associating) 、あるいはデータの処理に関係のあるその他のタスクが含まれます。

ソーシャルネットワークの場合では、モデル層はユーザーのデータの保存、友人の繋がりの保存、ユーザーの写真の保管と取得、新しい友人候補の検出などのタスクを引き受けることでしょう。このとき、モデルオブジェクトは「友達 (Friend)」、「ユーザー (User)」、「コメント (Comment)」、あるいは「写真 (Photo)」と考えることができます。もし users テーブルからデータを読み出したいのであれば次のようにできます。

use Cake\ORM\Locator\LocatorAwareTrait;

$users = $this->getTableLocator()->get('Users');
$resultset = $users->find()->all();
foreach ($resultset as $row) {
    echo $row->username;
}

データの処理を始める前に一切コードを書く必要がなかったことに気付いたかもしれません。規約を使うことによって、CakePHP はテーブルとエンティティーのクラスを定義していない場合には標準のクラスを使用します。

新しいユーザーを作成し、それを (検証して) 保存したい場合には次のようにします。

use Cake\ORM\Locator\LocatorAwareTrait;

$users = $this->getTableLocator()->get('Users');
$user = $users->newEntity(['email' => 'mark@example.com']);
$users->save($user);

ビュー層

ビュー層は、モデルから来たデータをレンダリングします。ビューはモデルオブジェクトとは別に存在します。そして、扱っている情報に対してレスポンシブルなアプリケーションが必要としている表示インターフェイスをすべて提供可能です。

例えば、このビューはモデルのデータを利用して HTML ビューテンプレートや他で利用するための XML 形式の結果をレンダリングできます。

// ビューテンプレートファイルで 'element' をそれぞれのユーザーに対してレンダリングする
<?php foreach ($users as $user): ?>
    <li class="user">
        <?= $this->element('user_info', ['user' => $user]) ?>
    </li>
<?php endforeach; ?>

このビュー層は ビューテンプレート や エレメント や ビューセルのようなしくみで表示のためのロジックを再利用可能にして、 沢山の表示を拡張するための機能を提供します。

ビュー層は HTML やテキストのレンダリングを制御出来るだけではなく、一般的な JSON や XML、加えてプラグインで追加可能なアーキテクチャによるフォーマットなら何にでも対応します。

コントローラーレイヤー

コントローラー層はユーザーからのリクエストを扱います。これはモデル層とビュー層の助けを借りてレスポンスをレンダリングして返す責任を負います。

コントローラーは、タスクを終える為の全ての必要とされるリソースが正しいワーカーに委譲されることに注意を払うマネージャーと見ることができます。クライアントからの要求を待ち、認証と承認のルールによる検証を行い、データの取得または処理をモデルに委譲し、クライアントが受け入れる適切な表示上のデータの種類を採択し、最終的にその描画処理をビュー層に委譲します。例えば、ユーザー登録ではこのようになります。

public function add()
{
    $user = $this->Users->newEmptyEntity();
    if ($this->request->is('post')) {
        $user = $this->Users->patchEntity($user, $this->request->getData());
        if ($this->Users->save($user, ['validate' => 'registration'])) {
            $this->Flash->success(__('You are now registered.'));
        } else {
            $this->Flash->error(__('There were some problems.'));
        }
    }
    $this->set('user', $user);
}

明示的にビューをレンダリングしないことに気付くかもしれません。 CakePHP は規約によって正しいビューを選択し、 set() で用意したビューデータでそのビューをレンダリングします。

CakePHP のリクエストサイクル

色々なレイヤーに親しんでいただきました。次は、リクエストサイクルがどのように働くのか見て行きましょう:

典型的な CakePHP のリクエストサイクルはユーザーがアプリケーション内でページまたはリソースにリクエストを投げるところから始まります。上位レベルの各リクエストは以下のステップを実行します。

ウェブサーバーが webroot/index.php へのリクエストを制御するルールを書き換えます。

あなたのアプリケーションがロードされ、 HttpServer にひも付きます。

あなたのアプリケーションのミドルウェアが初期化されます。

リクエストとレスポンスは、あなたのアプリケーションで使用する PSR-7 ミドルウェアを経由してディスパッチされます。これは、一般的にエラートラップとルーティングを含みます。

ミドルウェアからレスポンスが返らない場合やリクエストがルーティング情報を含む場合、コントローラーとアクションが選択されます。

コントローラーのアクションが呼ばれ、コントローラーが要求されたモデルとコンポーネントと通信します。

コントローラーが出力を生成するためにレスポンスの生成をビューに委任します。

ビューがヘルパーとセルを使ってボディーとヘッダーを生成して返す。

レスポンスは、 ミドルウェア を経由して送信されます。

HttpServer は、ウェブサーバーにレスポンスを送ります。

さっそく始めましょう

この文章があなたの興味を惹くことを願っています。CakePHP には他にもとてもいい特徴があります。

Memcached, Redis や他のバックエンドと統合された キャッシュフレームワーク。

強力な コード生成ツール bake ですぐに簡単なモックを作ってプロジェクトを始める。

統合されたテストフレームワーク でコードが完璧に動いているか確かめられる。

次の明白なステップは download CakePHP で, チュートリアルとなにかすごいものを作る を読んで下さい。

付録

情報の探し方

CakePHP の規約

CakePHP のフォルダー構成

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

情報の探し方

公式 CakePHP ウェブサイト

https://cakephp.org

公式 CakePHP ウェブサイトは、いつでも見に行く価値のある素敵な場所です。よく使う開発ツール、スクリーンキャスト、寄付の方法、ダウンロードなどへのリンクがあります。

Cookbook

https://book.cakephp.org

このマニュアルは、疑問の解決のための最初の場所になるはずです。他のさまざまなオープンソースプロジェクトと同じように、常に新しい仲間が入ってきます。疑問の答えをこの場所で探してみてください。時間がかかるかもしれませんが、貴重な知識を多く身につけることができるでしょう。また、どんなサポートがあるのかについても見えてくるはずです。マニュアルと API の両方の情報がオンラインで入手可能です。

Bakery

https://bakery.cakephp.org

CakePHP の Bakery は、CakePHP に関する情報中継所 (clearing house) です。チュートリアル、事例研究、コード例などについて情報が得られます。 CakePHP に関する理解が深まったらログインして知識をコミュニティーと共有し、名声と富を手に入れましょう。

API

https://api.cakephp.org/

直接的な答えがコア開発者から直接得られるので、 CakePHP API (Application Programming Interface) は、フレームワークの内部動作の詳細に関する、もっとも総合的なドキュメントです。この文書は、直接的なコードリファレンスですので、技術的に十分な理解を持っていることを前提としています。

テストケース

API で提供される情報が十分ではないと感じる場合、CakePHP で提供されるテストケースのコードをチェックしてください。関数の実践的な例やクラスのメンバーの使用法として使用できます。

tests/TestCase/

IRC チャンネル

irc.freenode.net での IRC チャンネル:

#cakephp -- 一般的なディスカッション

#cakephp-docs -- ドキュメント化について

#cakephp-bakery -- Bakery

#cakephp-fr -- フランス語チャンネル

途方にくれたら、CakePHP の IRC チャンネルで叫んでみましょう。（注：ただし英語）北アメリカと南アメリカの昼間の時間帯であれば、たいていは 開発チーム [https://cakephp.org/team] のだれかがそこにいます。助けが必要な場合でも、同じ地域での知り合いが必要でも、最新のスポーツカーを寄付したいという場合でも、喜んで聞いてくれるはずです。

公式 CakePHP フォーラム

CakePHP 公式フォーラム [https://discourse.cakephp.org]

公式フォーラムは、助けを求めたり、アイディアを提案したり、CakePHP について語り合う場所です。素早く答えや助けを得るために最適です。サインアップして、CakePHP ファミリーに参加しましょう。

Stackoverflow

https://stackoverflow.com/ [https://stackoverflow.com/questions/tagged/cakephp/]

Stack Overflow の既存ユーザーが質問を見つけやすくするために、質問の際には cakephp タグと利用中のバージョンを明記して下さい。

あなたの言語で情報を得るには

デンマーク語

Danish CakePHP Slack Channel [https://cakesf.slack.com/messages/denmark/]

フランス語

French CakePHP Community [https://cakephp-fr.org]

ドイツ語

German CakePHP Slack Channel [https://cakesf.slack.com/messages/german/]

German CakePHP Facebook Group [https://www.facebook.com/groups/146324018754907/]

Iranian

Iranian CakePHP Community [https://cakephp.ir]

Dutch

Dutch CakePHP Slack Channel [https://cakesf.slack.com/messages/netherlands/]

日本語

Japanese CakePHP Slack Channel [https://cakesf.slack.com/messages/japanese/]

Japanese CakePHP Facebook Group [https://www.facebook.com/groups/304490963004377/]

ポルトガル語

Portuguese CakePHP Google Group [https://groups.google.com/group/cakephp-pt]

スペイン語

Spanish CakePHP Slack Channel [https://cakesf.slack.com/messages/spanish/]

Spanish CakePHP IRC Channel

Spanish CakePHP Google Group [https://groups.google.com/group/cakephp-esp]

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CakePHP の規約

私たちは「設定より規約」(convention over configuration) という考え方に賛成です。 CakePHP の規約を習得するには少し時間がかかりますが、長い目で見ると時間を節約していることになります。規約に従うと自由に使える機能が増えますし、設定ファイルを調べまわってメンテナンスするという悪夢からも開放されます。 規約によって開発が統一感を持つため、開発者が加わってすぐに手伝うということがやりやすくなります。

コントローラーの規約

コントローラーのクラス名は複数形でパスカルケースで、最後に Controller が付きます。 UsersController 、 ArticleCategoriesController は規約に合ったコントローラー名の例となります。

コントローラーにある public メソッドは、アクションとしてブラウザーからアクセス可能になります。例えば、 /users/view は UsersController の view() メソッドにアクセスします。 protected メソッドや private メソッドはルーティングしてアクセスすることはできません。

コントローラー名と URL

前節の通り、ひとつの単語からなる名前のコントローラーは、簡単に小文字の URL パスにマップできます。例えば、 UsersController （ファイル名は UsersController.php）には、 http://example.com/users としてアクセスできます。

複数語のコントローラーをあなたの好きなようにルーティングできますが、 DashedRoute クラスを使用すると URL は小文字とダッシュを用いる規約であり、 ArticleCategoriesController::viewAll() アクションにアクセスするための正しい形式は /article-categories/view-all となります。

$this->Html->link を使用してリンクを作成した時、URL 配列に以下の規約を使用できます。

$this->Html->link('link-title', [
    'prefix' => 'MyPrefix', // パスカルケース
    'plugin' => 'MyPlugin', // パスカルケース
    'controller' => 'ControllerName', // パスカルケース
    'action' => 'actionName' // キャメルバック
]

CakePHP の URL とパラメーターの取り扱いに関するより詳細な情報は、ルートを接続 をご覧ください。

ファイルとクラス名の規約

通常、ファイル名はクラス名と一致し、オートローディングのために PSR-4 標準に準拠してください。以下に、クラス名とファイル名の例を挙げます。

LatestArticlesController というコントローラークラスは、 LatestArticlesController.php というファイル名にします。

MyHandyComponent というコンポーネントクラスは、 MyHandyComponent.php というファイル名にします。

OptionValuesTable という Table クラスは、 OptionValuesTable.php というファイル名にします。

OptionValue という Entity クラスは、 OptionValue.php というファイル名にします。

EspeciallyFunkableBehavior というビヘイビアークラスは、 EspeciallyFunkableBehavior.php というファイル名にします。

SuperSimpleView というビュークラスは、 SuperSimpleView.php というファイル名にします。

BestEverHelper というヘルパークラスは、 BestEverHelper.php というファイル名にします。

各ファイルは、 app フォルダー内の適切なフォルダー・名前空間の中に配置します。

データベースの規約

CakePHP のモデルに対応するテーブル名は、複数形でアンダースコア記法です。上記の例で言えば、テーブル名はそれぞれ、 users 、 article_categories 、 user_favorite_pages になります。

二個以上の単語で構成されるフィールド/カラムの名前は、 first_name のようにアンダースコア記法になります。

hasMany, blongsTo, hasOne 中の外部キーは、デフォルトで関連するモデルの(単数形の)名前に _id を付けたものとして認識されます。ユーザーが記事を複数持っている (Users hasMany Articles) としたら、 articles テーブルは、 user_id を外部キーとして users テーブルのデータを参照します。 article_categories のような複数の単語のテーブルでは、外部キーは article_category_id のようになるでしょう。

モデル間の BelongsToMany の関係で使用される join テーブルは、結合するテーブルに合わせて、アルファベット順に (tags_articles ではなく、 articles_tags) 並べた名前にしてください。そうでなければ bake コマンドは動作しません。連結用テーブルにカラムを追加する必要がある場合は、そのテーブル用に別のエンティティークラスやテーブルクラスを作成する必要があります。

主キーとしてオートインクリメントな整数型を使用することに加えて UUID カラムも使用できます。 Table::save() メソッドを使って新規レコードを保存するとき、CakePHP はユニークな 36 文字の UUID (Cake\Utilitiy\Text::uuid) を用いようとします。

モデルの規約

Table クラスの名前は複数形でパスカルケースで、最後に Table が付きます。 UsersTable, ArticleCategoriesTable, UserFavoritePagesTable などは users, article_categories, user_favorite_pages テーブルに対応するテーブルクラス名の例です。

Entity クラスの名前は単数形でパスカルケースで、サフィックスはありません。 User, ArticleCategory, UserFavoritePage などは users, article_categories, user_favorite_pages テーブルに対応するエンティティー名の例です。

ビューの規約

ビューのテンプレートファイルは、それを表示するコントローラーの関数に合わせた、アンダースコア記法で命名されます。 ArticlesController クラスの viewAll() 関数は、ビューテンプレートとして、 templates/Articles/view_all.php を探すことになります。

基本パターンは、 templates/コントローラー名/アンダースコア記法_関数名.php です。

注釈

デフォルトで、CakePHP は英単語の語形変化を使用します。もし、別の言語を使ったデータベースのテーブルやカラムがある場合、語形変化規則 (単数形から複数形、逆もまた同様) の追加が必要になります。カスタム語形変化規則を定義するために Cake\Utility\Inflector を使うことができます。より詳しい情報は、 Inflector をご覧ください。

プラグインの規約

CakePHP プラグインのパッケージ名にプレフィックスとして "cakephp-" を付けると便利です。これにより、名前が意味的にフレームワークに依存することを関連付けられます。

CakePHP 所有のプラグインに予約されているため、ベンダー名として CakePHP ネームスペース（cakephp）を 使用しない でください。規約では、小文字の文字とダッシュを区切り記号として使用します。

// 悪い例
cakephp/foo-bar

// 良い例
your-name/cakephp-foo-bar

詳しくは awesome list recommendations [https://github.com/FriendsOfCake/awesome-cakephp/blob/master/CONTRIBUTING.md#tips-for-creating-cakephp-plugins] をご覧ください。

要約

各部分を CakePHP の規約に合わせて命名しておくことで、混乱を招く面倒な設定をしなくても機能的に動作するようになります。以下が最後の規約に合った命名の例です。

データベースのテーブル: "articles"

Table クラス: ArticlesTable の場所は src/Model/Table/ArticlesTable.php

Entity クラス: Article の場所は src/Model/Entity/Article.php

Controller クラス: ArticlesController は src/Controller/ArticlesController.php

ビューテンプレートの場所は templates/Articles/index.php

これらの規約により、CakePHP は、 http://example.com/articles へのリクエストを、 ArticlesController の index() 関数にマップします。そして、Articles モデルが自動的に使える（データベースの 'articles' テーブルに自動的に接続される）ようになり、表示されることになります。必要なクラスとファイルを作成しただけでこれらの関係が設定されています。

Example

articles

menu_links

Database Table

articles

menu_links

Table names corresponding to CakePHP models are plural and underscored.

File

ArticlesController.php

MenuLinksController.php

Table

ArticlesTable.php

MenuLinksTable.php

Table class names are plural, CamelCased and end in Table

Entity

Article.php

MenuLink.php

Entity class names are singular, CamelCased: Article and MenuLink

Class

ArticlesController

MenuLinksController

Controller

ArticlesController

MenuLinksController

Plural, CamelCased, end in Controller

Behavior

ArticlesBehavior.php

MenuLinksBehavior.php

View

ArticlesView.php

MenuLinksView.php

View template files are named after the controller functions they display, in an underscored form

Helper

ArticlesHelper.php

MenuLinksHelper.php

Component

ArticlesComponent.php

MenuLinksComponent.php

Plugin

Bad: cakephp/articles Good: you/cakephp-articles

cakephp/menu-links you/cakephp-menu-links

Useful to prefix a CakePHP plugin with "cakephp-" in the package name. Do not use the CakePHP namespace (cakephp) as vendor name as this is reserved to CakePHP owned plugins. The convention is to use lowercase letters and dashes as separator.

Each file would be located in the appropriate folder/namespace in your app folder.

Database Convention Summary

Foreign keys

hasMany belongsTo/ hasOne BelongsToMany

Relationships are recognized by default as the (singular) name of the related table followed by _id. Users hasMany Articles, articles table will refer to the users table via a user_id foreign key.

Multiple Words

menu_links whose name contains multiple words, the foreign key would be menu_link_id.

Auto Increment

In addition to using an auto-incrementing integer as primary keys, you can also use UUID columns. CakePHP will create UUID values automatically using (Cake\Utility\Text::uuid()) whenever you save new records using the Table::save() method.

Join tables

Should be named after the model tables they will join or the bake command won't work, arranged in alphabetical order (articles_tags rather than tags_articles). Additional columns on the junction table you should create a separate entity/table class for that table.

さて、これで CakePHP の基本について一通り理解できました。物事がどう組み合わせられるかを確かめるために、コンテンツ管理チュートリアル を体験することができるでしょう。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CakePHP のフォルダー構成

CakePHP アプリケーションスケルトンをダウンロードすると、次のようないくつかのトップレベルのフォルダーがあるはずです。

bin フォルダーには実行可能な Cake コンソールを保持します。

config フォルダーは、CakePHP が使用する 構成設定ファイルが入る場所です。データーベース接続の詳細、ブートストラップ、コアの設定ファイルなどがここに格納されます。

plugins フォルダーは、あなたのアプリケーションが使う プラグイン が格納されます。

logs フォルダーは、通常、ログ設定に応じたログファイルが含まれます。

src フォルダーは、あなたのアプリケーションのファイルが配置される場所です。

templates フォルダーは、次のプレゼンテーションファイルが格納されます。: エレメント、エラーページ、レイアウト、ビューテンプレートファイル。

resources フォルダーには、様々な種類のリソースファイルのサブフォルダーがあります。 locales サブフォルダーには、国際化のための言語ファイルを格納します。

tests フォルダーは、あなたのアプリケーションのテストケースを置く場所です。

tmp フォルダーは、CakePHP が一時的なデータを格納する場所です。格納する実際のデータは、CakePHP の設定方法によって異なりますが、このフォルダーは、通常、翻訳メッセージ、モデルの詳細、および時にはセッション情報を格納するために使用されます。

vendor フォルダーは、CakePHP と他のアプリケーションの依存ライブラリーが Composer [https://getcomposer.org] によってインストールされる場所です。これらのファイルを編集することは推奨されません。次回の更新時に Composer があなたの変更を上書きしてしまうからです。

webroot ディレクトリーは、あなたのアプリケーションのパブリックドキュメントルートです。それはあなたが公開したいすべてのファイルを含みます。

tmp フォルダーと logs フォルダーは存在していてかつ書き込み可能な状態にしておいてください。そうでない場合、あなたのアプリケーションのパフォーマンスに影響を及ぼす場合があります。これらのディレクトリーが書き込み可能ではない場合、デバッグモードでは CakePHP は警告を出します。

src フォルダー

アプリケーション開発の大部分は、CakePHP の src フォルダー内で行われます。 src 内のフォルダーを少し近づいて見てみましょう。

Command

アプリケーションのコンソールコマンドを含みます。更に学ぶためには、 コマンドオブジェクト をご覧ください。

Console

Composer によって実行されるインストールスクリプトを含みます。

Controller

アプリケーションの コントローラー とコンポーネントを含みます。

Middleware

アプリケーションの ミドルウェア を格納します。

Model

アプリケーションのテーブル、エンティティー、ビヘイビアーを含みます。

Shell

アプリケーションのシェルタスクを含みます。更に学ぶためには、 シェル をご覧ください。

View

表示用のクラスが、ここに配置されます。ビュー、セル、ヘルパーなどです。

注釈

Shell フォルダーは、デフォルトでは存在しません。必要に応じて追加することができます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

クイックスタートガイド

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

コンテンツ管理チュートリアル

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

必要なもの:

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

基礎的な PHP の知識。

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

php -v

最低でも PHP 7.4 (CLI) 以上をインストールしてください。あなたのウェブサーバーの PHP バージョンもまた、 7.4 以上でなければなりません。そして、コマンドラインインターフェイス (CLI) の PHP バージョンと同じバージョンにしてください。

CakePHP の取得

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

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

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

そして、インストールディレクトリーからターミナルに次の行を入力するだけで、現在の作業ディレクトリーの cms ディレクトリーに CakePHP アプリケーションのスケルトンをインストールすることができます。

php composer.phar create-project --prefer-dist cakephp/app:4.* cms

Composer Windows Installer [https://getcomposer.org/Composer-Setup.exe] をダウンロードして実行した場合、インストールディレクトリー (例えば、 C:\wamp\www\dev\cakephp3) からターミナルに次の行を入力してください。

composer self-update && composer create-project --prefer-dist cakephp/app:4.* cms

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

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

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

/cms
  /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 のフォルダー構成 セクションをご覧ください。

このチュートリアルで迷ったら、 GitHub [https://github.com/cakephp/cms-tutorial] で完成した結果を見ることができます。

インストールの確認

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

cd /path/to/our/app

bin/cake server

注釈

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

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

次に、 データベースの構築と最初のモデルの作成をします。

CMS チュートリアル - データベース作成

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

USE cake_cms;

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 articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(255) NOT NULL,
    slug VARCHAR(191) NOT NULL,
    body TEXT,
    published BOOLEAN DEFAULT FALSE,
    created DATETIME,
    modified DATETIME,
    UNIQUE KEY (slug),
    FOREIGN KEY user_key (user_id) REFERENCES users(id)
) CHARSET=utf8mb4;

CREATE TABLE tags (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(191),
    created DATETIME,
    modified DATETIME,
    UNIQUE KEY (title)
) CHARSET=utf8mb4;

CREATE TABLE articles_tags (
    article_id INT NOT NULL,
    tag_id INT NOT NULL,
    PRIMARY KEY (article_id, tag_id),
    FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
    FOREIGN KEY article_key(article_id) REFERENCES articles(id)
);

INSERT INTO users (email, password, created, modified)
VALUES
('cakephp@example.com', 'secret', NOW(), NOW());

INSERT INTO articles (user_id, title, slug, body, published, created, modified)
VALUES
(1, 'First Post', 'first-post', 'This is the first post.', 1, now(), now());

複合主キーを持つ articles_tags テーブルにお気づきでしょうか。CakePHP は、ほぼどこでも複合主キーをサポートしているので、追加の id カラムを必要としない単純なスキーマを持つことができます。

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

データベースの設定

次に、どこにデータベースあるか、そしてどうやってデータベースに接続するかを CakePHP に伝えましょう。あなたのセットアップを適用するために config/app.php ファイルの中の Datasources.default 配列の値を置き換えてください。完全な設定配列の例は、以下のようになります。

<?php
return [
    // 上には他の設定があります
    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            'username' => 'cakephp',
            'password' => 'AngelF00dC4k3~',
            'database' => 'cake_cms',
            'encoding' => 'utf8mb4',
            'timezone' => 'UTC',
            'cacheMetadata' => true,
        ],
    ],
    // 下には他の設定があります
];

一度 config/app.php ファイルを保存して、 'CakePHP is able to connect to the database' が緑色のコック帽であることを確認してください。

注釈

app フォルダーに config/app_local.php がある場合、 app.php の設定を上書きします。

最初のモデルの作成

モデルは、CakePHP アプリケーションの心臓部です。データを読んだり変更することができます。それらは、データ間のリレーションの構築、データの検証、アプリケーションルールの適用をすることができます。モデルは、コントローラーアクションとテンプレートを構築するために必要な基礎を構築します。

CakePHP のモデルは Table と Entity オブジェクトで構成されています。 Table オブジェクトは、指定されたテーブルの中に保存されたエンティティーの集合へのアクセスを提供します。それらは src/Model/Table の中に保存されます。私たちが今から作成するファイルは、 src/Model/Table/ArticlesTable.php に保存されます。完成したファイルは次のようになります。

<?php
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config) : void
    {
        $this->addBehavior('Timestamp');
    }
}

このテーブルの created や modified カラムを自動的に更新する Timestamp ビヘイビアーを追加しました。 Table オブジェクトを ArticlesTable と名付けることで、CakePHP は、命名規則により articles テーブルを使用するモデルであると解釈します。また、CakePHP は、 id カラムがテーブルの主キーであると解釈する規約を使用します。

注釈

もし一致するファイルが src/Model/Table に見つけられなければ、CakePHP は動的にモデルオブジェクトを生成します。これはまた、不意に間違ったファイル名 (例えば、 articlestable.php や ArticleTable.php) をつけると、CakePHP はどの設定も認識できず、代わりに生成されたモデルを使うことになるということも意味します。

また、Articles のための Entity クラスも作成します。エンティティーは、データベースの１つのレコードを表し、データに対して行レベルの振る舞いを提供します。このエンティティーは、 src/Model/Entity/Article.php に保存されます。完成したファイルは、次のようになります。

<?php
// src/Model/Entity/Article.php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
    protected $_accessible = [
        '*' => true,
        'id' => false,
        'slug' => false,
    ];
}

エンティティーは、今はとてもスリムです。そして、 一括代入 (Mass Assignment) によってどのようにプロパティーを変更できるかを制御するプロパティー _accessible をセットアップしました。

このモデルは、今は動きませんが、次は最初のコントローラーとテンプレートを作成し、このモデルとのやりとりができるようにします。

CMS チュートリアル - Articles コントローラーの作成

モデルが作成できたので、記事のコントローラーが必要です。CakePHP のコントローラーは、レスポンスを準備するために、HTTP リクエストを処理し、モデルのメソッドに含まれるビジネスロジックを実行します。この新しいコントローラーは src/Controller ディレクトリーの中で ArticlesController.php と呼ばれるファイルに記述します。基本的なコントローラーの外観は次の通りです。

<?php
// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController
{
}

それでは、コントローラーにアクションを追加しましょう。アクションは、ルートに接続させるコントローラーのメソッドです。例えば、ユーザーが www.example.com/articles/index (www.example.com/articles と同じ) をリクエストした場合、CakePHP は、 ArticlesController の index メソッドを呼びます。このメソッドは、モデル層に問い合わせ、ビューでテンプレートを描画してレスポンスの準備する必要があります。そのアクションのコードは、次のようになります。

<?php
// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController
{
    public function index()
    {
        $this->loadComponent('Paginator');
        $articles = $this->Paginator->paginate($this->Articles->find());
        $this->set(compact('articles'));
    }
}

ArticlesController の index() 関数を定義することで、ユーザーは、 www.example.com/articles/index をリクエストすることで、そこにあるロジックにアクセスできるようになります。同様に、 foobar() という関数を定義した場合、ユーザーはそのメソッドに www.example.com/articles/foobar で、アクセスできます。特定の URL を取得できるように、コントローラーとアクションの名前を付けたいという誘惑に駆られるかもしれません。その誘惑に抗ってください。代わりに、 CakePHP の規約にしたがって、読みやすく意味のあるアクション名を作成してください。そうすればルーティング を使って、あなたが望む URL を、あなたが作成したアクションに接続することができます。

このコントローラーのアクションはとてもシンプルです。これは、命名規則によって自動的にロードされる Articles モデルを使用して、データベースからページ分けされた記事を取得します。次に (これからすぐに作成する) テンプレートの中に記事を渡すために set() を使用します。CakePHP は、コントローラーのアクションが完了した後、自動的にテンプレートを描画します。

記事一覧のテンプレート作成

モデルからデータを取得し、ビューのコンテキストを準備するコントローラーを持っていますので、 index アクションのビューテンプレートを作成しましょう。

CakePHP のビューテンプレートは、アプリケーションのレイアウト内に挿入される表示用の PHP コードです。ここで HTML が作成されますが、 View は JSON、CSV、または PDF などのバイナリファイルの生成もできます。

レイアウトは、ビューを囲む表示用のコードです。レイアウトファイルは、ヘッダー・フッター・ナビゲーション要素のような一般的なサイトの要素が含まれます。アプリケーションは、複数のレイアウトを持つことができ、それらを切り替えることができますが、今回はデフォルトのレイアウトを使用しましょう。

CakePHP のテンプレートファイルは、 templates の中で対応するコントローラーの名前をつけたフォルダーの中に保存されます。今回の場合、 'Articles' という名前のフォルダーを作成する必要があります。あなたのアプリケーションに以下のコードを追加してください。

<!-- File: templates/Articles/index.php -->

<h1>記事一覧</h1>
<table>
    <tr>
        <th>タイトル</th>
        <th>作成日時</th>
    </tr>

    <!-- ここで、$articles クエリーオブジェクトを繰り返して、記事の情報を出力します -->

    <?php foreach ($articles as $article): ?>
    <tr>
        <td>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
    </tr>
    <?php endforeach; ?>
</table>

前のセクションでは、 set() を使って 'articles' 変数をビューに割り当てました。ビューに渡された変数は、上記のコードの中で使用したローカル変数として、ビューテンプレートの中で利用できます。

$this->Html というオブジェクトを使っていることにお気づきでしょうか。これは、 CakePHP の HtmlHelper のインスタンスです。 CakePHP には、リンク、フォーム、ページ制御ボタンの作成などのタスクを簡単に行うための一連のビューヘルパーが付属しています。 ヘルパー について、その章で詳しく学べますが、ここで重要なことは、 link() メソッドは、与えられたリンクテキスト(第１パラメーター) と URL (第２パラメーター) を元に HTML リンクを生成することです。

CakePHP で URL を指定する際、配列や 名前付きルート が推奨されます。これらの構文で、CakePHP が提供するリバースルーティング機能を活用することができます。

この時点で、ブラウザーで http://localhost:8765/articles/index を見ることができるはずです。記事のタイトルとテーブルのリストで正しくフォーマットされたリストビューが表示されます。

view アクションの作成

記事一覧の 'view' リンクの一つをクリックした際、アクションが実装されてないというエラーページが表示されるはずです。今から、それを修正しましょう。

// 既存の src/Controller/ArticlesController.php ファイルに追加

public function view($slug = null)
{
    $article = $this->Articles->findBySlug($slug)->firstOrFail();
    $this->set(compact('article'));
}

これはシンプルなアクションですが、いくつかの強力な CakePHP 機能を使用しています。動的なファインダー である findBySlug() を使用することによりアクションを開始します。このメソッドは、与えられたスラグによって記事を検索する基本的なクエリーを作成することができます。その時、最初のレコードを取得するか NotFoundException を投げるかのいずれかをする firstOrFail() を使います。

このアクションは $slug パラメーターを持ちますが、そのパラメーターはどこから来るのでしょう？ユーザーが /articles/view/first-post をリクエストした際、 値 'first-post' が CakePHP のルーティングとディスパッチレイヤーで $slug に渡されます。新しいアクションを保存してブラウザーをリロードすると、別の CakePHP エラーページが表示され、 view テンプレートが見つからないことがわかります。それを修正しましょう。

view テンプレートの作成

新しい 'view' アクションのビューを作成し、 templates/Articles/view.php に置きましょう。

<!-- File: templates/Articles/view.php -->

<h1><?= h($article->title) ?></h1>
<p><?= h($article->body) ?></p>
<p><small>作成日時: <?= $article->created->format(DATE_RFC850) ?></small></p>
<p><?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?></p>

/articles/index のリンクを使うことによって、動作することを確認できますし、 /articles/view/slug-name のような URL にアクセスすることによって手動で記事をリクエストすることを確認できます。

記事の追加

基本的な読み込みビューを作成できたので、新しい記事を作成できるようにする必要があります。まず、 ArticlesController に add() アクションを作成してください。コントローラーは次のようになります。

// src/Controller/ArticlesController.php

namespace App\Controller;

use App\Controller\AppController;

class ArticlesController extends AppController
{

    public function initialize(): void
    {
        parent::initialize();

        $this->loadComponent('Paginator');
        $this->loadComponent('Flash'); // FlashComponent をインクルード
    }

    public function index()
    {
        $articles = $this->Paginator->paginate($this->Articles->find());
        $this->set(compact('articles'));
    }

    public function view($slug)
    {
        $article = $this->Articles->findBySlug($slug)->firstOrFail();
        $this->set(compact('article'));
    }

    public function add()
    {
        $article = $this->Articles->newEmptyEntity();
        if ($this->request->is('post')) {
            $article = $this->Articles->patchEntity($article, $this->request->getData());

            // user_id の決め打ちは一時的なもので、あとで認証を構築する際に削除されます。
            $article->user_id = 1;

            if ($this->Articles->save($article)) {
                $this->Flash->success(__('Your article has been saved.'));
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('Unable to add your article.'));
        }
        $this->set('article', $article);
    }
}

注釈

フラッシュ コンポーネントを、使用するコントローラーに含める必要があります。また、 AppController に含めてもよいでしょう。

こちらがこの add() アクションの行うことです。

リクエストの HTTP メソッドが POST だった場合、Articles モデルを使用してデータを保存しようとします。

なんらかの理由で保存しなかった場合、ただビューを描画します。これは、ユーザーバリデーションエラーやその他の警告を表示する機会を与えてくれます。

全ての CakePHP のリクエストは、 $this->request を使用してアクセス可能なリクエストオブジェクトを含みます。リクエストオブジェクトは、今受信したリクエストに関する情報を含みます。リクエストが HTTP POST リクエストであることを確認するために Cake\Http\ServerRequest::is() メソッドを使用します。

POST データは、 $this->request->getData() で利用可能です。それがどのような内容かを知りたい場合に pr() 関数や debug() 関数を使って出力することができます。データを保存するために、まず POST データを Article エンティティーに 「変換 (marshal)」します。エンティティーは、以前作成した ArticlesTable を使用して永続化されます。

新しい記事を保存した後、セッションにメッセージをセットするために FlashComponent の success() メソッドを使用します。 success メソッドは PHP のマジックメソッド機能 [https://php.net/manual/ja/language.oop5.overloading.php#object.call] を使用して提供されます。フラッシュメッセージは、リダイレクトした後の次のページ上で表示されます。レイアウトの中に、フラッシュメッセージを表示し、対応するセッション変数をクリアする <?= $this->Flash->render() ?> があります。最後に、保存が完了した後、 Cake\Controller\Controller::redirect を使ってユーザーを記事一覧に戻します。パラメーター ['action' => 'index'] は、例えば ArticlesController の index アクションの場合、 URL /articles に変換します。 API [https://api.cakephp.org] の Cake\Routing\Router::url() 関数を参照すると、様々な CakePHP 関数の URL を指定できる書式を見ることができます。

add テンプレートの作成

こちらが add ビューテンプレートです。

<!-- File: templates/Articles/add.php -->

<h1>記事の追加</h1>
<?php
    echo $this->Form->create($article);
    // 今はユーザーを直接記述
    echo $this->Form->control('user_id', ['type' => 'hidden', 'value' => 1]);
    echo $this->Form->control('title');
    echo $this->Form->control('body', ['rows' => '3']);
    echo $this->Form->button(__('Save Article'));
    echo $this->Form->end();
?>

FormHelper を使うと、 HTML フォームの開始タグを生成できます。こちらが $this->Form->create() が生成する HTML です。

<form method="post" action="/articles/add">

URL オプションなしで create() を呼び出したので、 FormHelper は、フォームを現在のアクションに戻したいと仮定します。

$this->Form->control() メソッドは、同じ名前のフォーム要素を作成するために使われます。１番目のパラメーターは、どのフィールドに対応するかを CakePHP に伝えます。そして、２番目のパラメーターは、さまざまなオプションを指定できます。上記の場合、テキストエリアの行数などです。ここではちょっとした内部情報の確認 (introspection) と規約の使用があります。 control() は、指定されたモデルフィールドにもとづいて異なるフォーム要素を出力し、語形変化 (inflection) を使ってラベルを生成します。オプションを使用して、フォームコントロールのラベル、入力、または、その他の要素をカスタマイズすることができます。 $this->Form->end() の呼び出しでフォームを閉じます。

さて、 templates/Articles/index.php ビューを更新して、新しい「記事の追加」リンクを追加しましょう。 <table> の前に以下の行を追加してください。

<?= $this->Html->link('記事の追加', ['action' => 'add']) ?>

シンプルなスラグ生成の追加

記事を今保存すると、スラグ属性は、作成されておらず、カラムは NOT NULL なので保存に失敗します。スラグの値は、通常、URL セーフなバージョンの記事タイトルです。スラグを作成するために ORM の beforeSave() コールバック が使用できます。

<?php
// src/Model/Table/ArticlesTable.php の中で
namespace App\Model\Table;

use Cake\ORM\Table;
// Text クラス
use Cake\Utility\Text;
// EventInterface クラス
use Cake\Event\EventInterface;

// 次のメソッドを追加してください。

public function beforeSave(EventInterface $event, $entity, $options)
{
    if ($entity->isNew() && !$entity->slug) {
        $sluggedTitle = Text::slug($entity->title);
        // スラグをスキーマで定義されている最大長に調整
        $entity->slug = substr($sluggedTitle, 0, 191);
    }
}

このコードはシンプルで、重複したスラグを考慮していません。しかし、後でそれを修正します。

edit アクションの追加

今のアプリケーションは、記事を保存できますが編集はできません。今から修正しましょう。 ArticlesController に次のアクションを追加してください。

// src/Controller/ArticlesController.php の中で

// 次のメソッドを追加してください。

public function edit($slug)
{
    $article = $this->Articles->findBySlug($slug)->firstOrFail();
    if ($this->request->is(['post', 'put'])) {
        $this->Articles->patchEntity($article, $this->request->getData());
        if ($this->Articles->save($article)) {
            $this->Flash->success(__('Your article has been updated.'));
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error(__('Unable to update your article.'));
    }

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

このアクションは、まずユーザーが既存のレコードにアクセスすることを確保します。 $slug パラメーターの中に渡されなかったり、記事が存在しなかった場合、 NotFoundException が投げられ、 CakePHP の ErrorHandler が、適切なエラーページを描画します。

次に、このアクションはリクエストが POST または PUT いずれかのリクエストかどうかをチェックします。そうであれば、POST/PUT データを元に、 patchEntity() メソッドを使って article エンティティーを更新します。最後に、 save() を呼び出して、適切なフラッシュメッセージを設定し、リダイレクトするか検証エラーを表示します。

edit テンプレートの作成

edit テンプレートは次のようになります。

<!-- File: templates/Articles/edit.php -->

<h1>記事の編集</h1>
<?php
    echo $this->Form->create($article);
    echo $this->Form->control('user_id', ['type' => 'hidden']);
    echo $this->Form->control('title');
    echo $this->Form->control('body', ['rows' => '3']);
    echo $this->Form->button(__('Save Article'));
    echo $this->Form->end();
?>

このテンプレートは、編集フォーム（値が入力された状態）に加えて、必要な検証エラーメッセージを出力します。

特定の記事を編集するためのリンクで index ビューを更新できるようになりました。

<!-- File: templates/Articles/index.php  (編集リンク付き) -->

<h1>記事一覧</h1>
<p><?= $this->Html->link("記事の追加", ['action' => 'add']) ?></p>
<table>
    <tr>
        <th>タイトル</th>
        <th>作成日時</th>
        <th>操作</th>
    </tr>

<!-- ここで、$articles クエリーオブジェクトを繰り返して、記事情報を出力します -->

<?php foreach ($articles as $article): ?>
    <tr>
        <td>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
        <td>
            <?= $this->Html->link('編集', ['action' => 'edit', $article->slug]) ?>
        </td>
    </tr>
<?php endforeach; ?>

</table>

Articles の検証ルールの更新

この時点まで、記事は入力検証が行われていませんでした。バリデーター を使って修正しましょう。

// src/Model/Table/ArticlesTable.php

// この use 文を名前空間宣言のすぐ下に追加して、
// Validator クラスをインポートします。
use Cake\Validation\Validator;

// 次のメソッドを追加してください。
public function validationDefault(Validator $validator): Validator
{
    $validator
        ->notEmptyString('title')
        ->minLength('title', 10)
        ->maxLength('title', 255)

        ->notEmptyString('body')
        ->minLength('body', 10);

    return $validator;
}

validationDefault() メソッドは、 save() メソッドが呼ばれる際のデータの検証方法を CakePHP に伝えます。ここでは、title フィールドと body フィールドの両方が空であってはならず、長さに制約があることを指定しました。

CakePHP の検証エンジンは強力で柔軟性があります。メールアドレス、IP アドレスなどのようにタスクに頻繁に使用されるルール一式を提供し、独自の検証ルールを追加する柔軟性を提供します。その設定の詳細については、 バリデーション のドキュメントを確認してください。

さて、検証ルールが整いましたので、それがどのように動くかを見るためにアプリを使って空のタイトルや本文で記事を追加してみてください。 FormHelper の Cake\View\Helper\FormHelper::control() メソッドを使用して、フォーム要素を作成しているので、検証エラーメッセージが自動的に表示されます。

delete アクションの追加

次に、ユーザーが記事を削除する方法を作ってみましょう。 ArticlesController の中の delete() アクションから始めましょう。

// src/Controller/ArticlesController.php

// 次のメソッドを追加してください。

public function delete($slug)
{
    $this->request->allowMethod(['post', 'delete']);

    $article = $this->Articles->findBySlug($slug)->firstOrFail();
    if ($this->Articles->delete($article)) {
        $this->Flash->success(__('The {0} article has been deleted.', $article->title));
        return $this->redirect(['action' => 'index']);
    }
}

このロジックは $slug で指定された記事を削除し、 $this->Flash->success() を使って /articles にリダイレクトした後に確認メッセージを表示します。ユーザーが GET リクエストを使って記事を削除しようとすると、 allowMethod() は例外をスローします。キャッチされない例外は CakePHP の例外ハンドラによって捕捉され、素晴らしいエラーページが表示されます。アプリケーションで生成する必要のあるさまざまな HTTP エラーを示すために使用できる組み込みの例外 が多数あります。

警告

ウェブクローラーが誤ってすべてのコンテンツを削除する可能性があるため、 GET リクエストを使用してコンテンツを削除することは とても 危険です。それでコントローラーの中で allowMethod() を使ったのです。

私たちはロジックを実行して、別のアクションにリダイレクトしているだけなので、このアクションにはテンプレートはありません。ユーザーが記事を削除できるリンク付きに index テンプレートを更新するといいでしょう。

<!-- File: templates/Articles/index.php  (削除リンク付き) -->

<h1>記事一覧</h1>
<p><?= $this->Html->link("記事の追加", ['action' => 'add']) ?></p>
<table>
    <tr>
        <th>タイトル</th>
        <th>作成日時</th>
        <th>操作</th>
    </tr>

<!-- ここで、$articles クエリーオブジェクトを繰り返して、記事情報を出力します -->

<?php foreach ($articles as $article): ?>
    <tr>
        <td>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
        <td>
            <?= $this->Html->link('編集', ['action' => 'edit', $article->slug]) ?>
            <?= $this->Form->postLink(
                '削除',
                ['action' => 'delete', $article->slug],
                ['confirm' => 'よろしいですか?'])
            ?>
        </td>
    </tr>
<?php endforeach; ?>

</table>

postLink() を使用すると、 JavaScript を使用して記事を削除する POST リクエストを行うリンクが作成されます。

注釈

また、このビューコードは FormHelper を使って記事を削除しようとする前に JavaScript の確認ダイアログを表示します。

基本的な記事管理のセットアップの後は、 タグとユーザーテーブルの基本的な操作を作成します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

移行ガイド

移行ガイドは、各バージョンで導入された新機能に関する情報と、3.x と 4.x の間の移行手順を解説します。

4.0 アップグレードガイド

4.0 移行ガイド

4.1 移行ガイド

4.2 移行ガイド

4.3 移行ガイド

4.4 移行ガイド

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

4.0 アップグレードガイド

4.0 にアップグレードする前に、まず 最新の CakePHP 3.x へのアップグレードを済ませておいてください。

注釈

アップグレードツールは、最新のCakePHP3.xで実行されているアプリケーションでのみ機能します。CakePHP 4.0 にアップデートした後は、アップグレードツールを実行できません。

非推奨の警告を修正

まず、非推奨警告の表示を有効にします。

// config/app.php
'Error' => [
    'errorLevel' => E_ALL,
]

次に、アプリケーションとそのプラグインが発行する非推奨の警告を修正していきます。

PHP 7.2 へのアップグレード

CakePHP 4.0 は PHP 7.2 を必要とします。PHPのバージョンが古い場合は、CakePHP を更新する前に PHP をアップグレードしてください。

注釈

CakePHP 4.0を動作させるには、 PHP 7.2以上 が必要です。.

アップグレードツールの使用

CakePHP 4 は、 strict モードを採用し、多くのタイプヒントを使用するため、メソッドシグネチャおよびファイル名の変更に関する後方互換性のない変更が多数あります。この単調な書き換え作業を半自動的に修正するために、CLI によるアップグレードツールを使用することができます。

警告

cakephp40およびphpunit80の file_rename コマンドとrectorのルールスクリプトは、アプリケーションの依存関係を4.0に更新する 前 に実行することを前提としています。アプリケーションの依存関係がすでに4.xまたはPHPUnit8に更新されている場合、 cakephp40 のrectorルールスクリプトは正しく実行されません。

# アップグレードツールをインストール
git clone https://github.com/cakephp/upgrade
cd upgrade
git checkout 4.x
composer install --no-dev

アップグレードツールをインストールすると、アプリケーションまたはプラグインで実行できるようになります。

# ロケールファイルの名前を変更する
bin/cake upgrade file_rename locales <path/to/app>

# テンプレートファイルの名前を変更する
bin/cake upgrade file_rename templates <path/to/app>

テンプレートファイルとロケールファイルの名前を変更したら、 /config/app.php 内の App.paths.locales と App.paths.templates のパスを必ず更新してください。必要に応じて、 app config のスケルトン [https://github.com/cakephp/app/blob/4.x/config/app.php] を参照してください。

Rector によるリファクタリングを適用する

次に、 rector コマンドを使用して、非推奨となった多くの CakePHP および PHPUnit のメソッドコールを自動的に修正します。依存関係をアップグレードする 前 に、 rector を適用することが重要です。

bin/cake upgrade rector --rules phpunit80 <path/to/app/tests>
bin/cake upgrade rector --rules cakephp40 <path/to/app/src>

アップグレードツールを使用して、CakePHPのマイナーバージョンごとに新しいrectorルールを適用することもできます。:

# Run the rector rules for the 4.0 -> 4.1 upgrade.
bin/cake upgrade rector --rules cakephp41 <path/to/app/src>

CakePHP の依存関係をアップデートする

rector によるリファクタリングを適用した後、下記の composer コマンドを使用して CakePHP と PHPUnit をアップデートします。

php composer.phar require --dev --update-with-dependencies "phpunit/phpunit:^8.0"
php composer.phar require --update-with-dependencies "cakephp/cakephp:4.0.*"

Application.php

次に、 src/Application.php が更新され、cakephp/appにあるものと同じメソッドシグネチャを持つようになっていることを確認します。最新の Application.php [https://github.com/cakephp/app/blob/4.x/src/Application.php] はGitHubにあります。

ある種のRESTAPIを提供している場合は、 ボディパーサミドルウェア を含める必要があります。

最後に、 AuthComponent をまだ使用している場合は、新しい AuthenticationMiddleware および AuthorizationMiddleware へのアップグレードを検討する必要があります。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

4.0 移行ガイド

CakePHP 4.0 には、重大な変更が含まれており、3.x リリースとの後方互換性はありません。 4.0 にアップグレードする前に、最初に 3.8 にアップグレードし、すべての非推奨警告を解消してください。

4.0 にアップグレードする方法の段階的な手順については、 4.0 アップグレードガイド を参照してください。

非推奨機能の削除

3.8 から非推奨の警告を発していたすべてのメソッド、プロパティと機能が削除されました。

認証機能がスタンドアロンのプラグイン Authentication [https://github.com/cakephp/authentication] および Authorization [https://github.com/cakephp/authorization] に分割されました。以前の RssHelper については、同様の機能を持つスタンドアロンの Feed plugin [https://github.com/dereuromark/cakephp-feed] をご覧ください。

非推奨

以下は、非推奨メソッド、プロパティと動作の一覧です。これらの機能は、 4.x でも引き続き機能し、 5.0.0 で削除されます。

Component

AuthComponent および関連するクラスは廃止され、 5.0.0 で削除されます。代わりに、上記の認証および認可ライブラリを使用する必要があります。

SecurityComponent は非推奨です。代わりに、フォーム改ざん保護のためには FormProtectionComponent を使用し、 requireSecure 機能のためには HTTPSエンフォーサミドルウェア を使用してください。

Filesystem

このパッケージは非推奨であり、 5.0 で削除されます。多くの設計上の問題があり、そして、すでに多くの素晴らしいパッケージが存在する場合、この頻繁に使用されるパッケージを修正することに努力する価値はありません。

ORM

Entity::isNew() をセッターとして使うことは非推奨です。代わりに setNew() を使用してください。

Entity::unsetProperty() は、他のメソッドに合わせて Entity::unset() に名前が変更されました。

TableSchemaInterface::primaryKey() は TableSchemaInterface::getPrimaryKey() に名前が変更されました。

View

JsonView の特別なビュー変数 _serialize 、 _jsonOptions および _jsonp は非推奨になりました。代わりに、 viewBuilder()->setOption($optionName, $optionValue) を、それらのオプションを設定するために使用してください。

XmlView の特別なビュー変数 _serialize 、 _rootNode および _xmlOptions は非推奨になりました。代わりに、 viewBuilder()->setOption($optionName, $optionValue) を、それらのオプションを設定するために使用してください。

HtmlHelper::tableHeaders() は、ネストされたリストとして定義される属性を持つヘッダーセルを優先するようになりました。例: ['Title', ['class' => 'special']]

ContextInterface::primaryKey() は ContextInterface::getPrimaryKey() に名前が変更されました。

Mailer

Cake\Mailer\Email クラスは非推奨になります。代わりに、 Cake\Mailer\Mailer を使用してください。

App

App::path() はクラスパスでは非推奨になりました。代わりに \Cake\Core\App::classPath() を使用してください。

破壊的変更

非推奨機能の削除に加えて、破壊的変更が行われました。

Cache

Cake\Cache\CacheEngine::gc() と、このメソッドのすべての実装が削除されました。このメソッドは、ほとんどのキャッシュドライバーでノーオペレーションであり、ファイルキャッシュでのみ使用されていました。

Controller

Cake\Controller\Controller::referer() は、デフォルトで local パラメーターを、 false ではなく true に設定します。これにより、デフォルトでアプリケーションのドメインに制限されるため、リファラルヘッダーの使用がより安全になります。

アクションの呼び出し時のコントローラーメソッド名のマッチングで、大文字と小文字が区別されるようになりました。たとえば、コントローラーメソッドが forgotPassword() の場合、 URL で文字列 forgotpassword を使用すると、アクション名としてマッチしません。

Console

ConsoleIo::styles() は getStyle() および setStyle() に分割されました。これは ConsoleOutput にも影響します。

Component

Cake\Controller\Component\RequestHandlerComponent は、リクエストパラメーターではなくリクエスト属性として、 isAjax を設定するようになりました。したがって、 $request->getParam('isAjax') の代わりに $request->getAttribute('isAjax') を使用する必要があります。

RequestHandlerComponent の入力データ解析機能は削除され。代わりに、 ボディパーサミドルウェア を使用する必要があります。

Cake\Controller\Component\PaginatorComponent は、リクエストパラメーターではなくリクエスト属性として、ページングパラメーター情報を設定するようになりました。したがって、 $request->getParam('paging') の代わりに、 $request->getAttribute('paging') を使用する必要があります。

Database

Cake\Database\TypeInterface の型マッピングクラスは Type を継承しなくなり、 BatchCastingInterface 機能を活用します。

Cake\Database\Type::map() は、セッターとしてのみ機能します。型インスタンスを検査するには Type::getMap() を使用する必要があります。

Date 、 Time 、 Timestamp および Datetime カラムタイプは、デフォルトで不変の時刻オブジェクトを返すようになりました。

BoolType は、空でない文字列値を true にマーシャリングしたり、空文字列を false にマーシャリングしなくなりました。代わりに、非ブール文字列値は null に変換されます。

DecimalType は、浮動小数ではなく文字列を使用して 10 進数値を表すようになりました。浮動小数を使用することで、精度が低下していました。

JsonType は、データベースコンテキストの値を準備するときに null を保持するようになりました。 3.x では、 'null' を出力します。

StringType は、配列値を、空文字列の代わりに null にマーシャリングします。

Cake\Database\Connection::setLogger() は ロギングを無効化するために null を受け入れなくなりました。代わりに、 Psr\Log\NullLogger のインスタンスを渡して、ロギングを無効にします。

Database\Log\LoggingStatement 、 Database\QueryLogger および Database\Log\LoggedQuery の内部実装が変更されました。これらのクラスを拡張する場合は、コードを更新する必要があります。

Cake\Database\Log\LoggingStatement 、 Cake\Database\QueryLogger および Cake\Database\Log\LoggedQuery の内部実装が変更されました。これらのクラスを拡張する場合は、コードを更新する必要があります。

Cake\Database\Schema\CacheCollection および Cake\Database\SchemaCache の内部実装が変更されました。これらのクラスを拡張する場合は、コードを更新する必要があります。

データべーススキーマは、 CHAR カラムを string ではなく、新しい char 型にマッピングするようになりました。

SqlServer の datetime カラムは、名前を一致させるために 'timestamp' ではなく 'datetime' 型にマップされるようになりました。

MySQL 、 PostgreSQL および SqlServer のデータベーススキーマは、少数秒をサポートするカラムを、新しい抽象少数型にマップするようになりました。

MySQL

DATETIME(1-6) => datetimefractional

TIMESTAMP(1-6) => timestampfractional

PostgreSQL

TIMESTAMP => timestampfractional

TIMESTAMP(1-6) => timestampfractional

SqlServer

DATETIME2 => datetimefractional

DATETIME2(1-7) => ``datetimefractional

PostgreSQL のスキーマは、タイムゾーンをサポートするカラムを、新しい抽象タイムゾーン型にマップするようになりました。 (0) 精度を指定しても、上記の通常の分数型の場合のように、型マッピングは変更されません。

PostgreSQL

TIMESTAMPTZ => timestamptimezone

TIMESTAMPTZ(0-6) => timestamptimezone

TIMESTAMP WITH TIME ZONE => timestamptimezone

TIMESTAMP(0-6) WITH TIME ZONE => timestamptimezone

Datasources

ModelAwareTrait::$modelClass は protected になりました。

Error

エラーハンドラークラス BaseErrorHandler 、 ErrorHandler および ConsoleErrorHandler の内部が変更されました。これらのクラスを拡張した場合は、それに応じて更新する必要があります。

ErrorHandlerMiddleware は、例外レンダラークラス名またはインスタンスではなく、コンストラクター引数として、エラーハンドラークラス名またはインスタンスを受け取るようになりました。

Event

件名のないイベントで getSubject() を呼び出すと、例外が発生するようになりました。

Http

Cake\Http\ServerRequest::referer() は、デフォルトで local パラメーターを false ではなく true に設定します。これにより、リファラーヘッダーはデフォルトでアプリケーションのドメインに制限されるため、リファラーヘッダーの使用がより安全になります。

パラメーターが欠落している場合の Cake\Http\ServerRequest::getParam() のデフォルト値は、 false ではなく null になりました。

Cake\Http\Client\Request::body() は削除されました。代わりに、 getBody() か withBody() を使用してください。

Cake\Http\Client\Response::isOk() は、すべての 2xx および 3xx レスポンスコードに対して、 true を返すようになりました。

Cake\Http\Cookie\Cookie::getExpiresTimestamp() は、数値を返すようになりました。これにより、 setcookie() で使用されているものと型が一致します。

Cake\Http\ServerRequest::referer() は、現在のリクエストにリファラーがない場合、 null を返すようになりました。以前は、 / を返していました。

Cake\Cookie\CookieCollection::get() は、存在しないクッキーにアクセスすると、例外を返すようになりました。クッキーの存在をチェックするために has() を使用してください。

Cake\Http\ResponseEmitter::emit() のシグネチャが変更され、 2 番目の引数がなくなりました。

App.mergeFilesAsObjects のデフォルト値は true になりました。アプリケーションがファイルアップロードを使用する場合、このフラグを false に設定することで、 3.x の動作との互換性を維持できます。

Cake\Http\Response::getCookie() によって返される配列キーが変更されました。 expire が expires に、 httpOnly が httponly に変わりました。

HttpSession

セッションクッキー名は、デフォルトで CAKEPHP に設定されなくなりました。代わりに、 php.ini ファイルで定義された、デフォルトのクッキー名が使用されます。Session.cookie 設定オプションを使用してクッキー名を設定できます。

セッションクッキーは、デフォルトで SameSite 属性が Lax に設定されるようになります。詳細については、 セッションの設定 セクションを確認してください。

I18n

Cake\I18n\Date や Cake\I18n\FrozenDate オブジェクトを JSON エンコードすると、以前の形式 yyyy-MM-dd'T'HH:mm:ssxxx ではなく、 yyyy-MM-dd 形式で日付部分のみの文字列が生成されるようになりました。

Mailer

Email::set() は削除されました。代わりに Email::setViewVars() を使用してください。

Email::createView() は削除されました。

Email::viewOptions() は削除されました。代わりに $email->getRenderer()->viewBuilder()->setOption() を使用してください。

ORM

Table::newEntity() は、入力として配列を必要とし、検証が実行されずに偶発的な保存がされることを防ぐために、検証を実施します。つまり、入力無しでエンティティーを作成するには、 Table::newEmptyEntity() を使用する必要があります。

Query::where() に ['name' => null] のような条件を使用すると、例外が発生します。 3.x では、 SQL の name = NULL のような条件のSQLを生成していましたが、これは常に 0 行と一致するため、誤った結果を返します。 null と比較するときは、 ['name IS' => null] のような IS 演算子を使用する必要があります。

false ではなく、エンティティーではない結果で、 Model.beforeSave イベントを停止すると、例外が発生します。この変更により、 Table::save() は常にエンティティーまたは false を返します。

テーブルクラスは、テーブル名とカラム名のエイリアスがデータベースによって切り捨てられた場合に、例外を投げるようになりました。これは実際のエイリアスが一致しない場合、隠れたエラーが発生する前に警告を発するものです。

TableLocator::get() および TableRegistry::get() は常に CamelCase 形式のエイリアスを期待するようになりました。予期せぬ形式のエイリアスが渡された場合、テーブルやエンティティクラスが正しくロードされません。

IsUnique ルールはデフォルトで有効になっていた allowMultipleNulls オプションを受け付けなくなりました。このオプションは 4.2 で再度追加されましたが、デフォルトでは無効になっています。

Router

Router::prefix() および $routes->prefix() で生成されたルーティングプレフィックスは、アンダースコアーではなく、キャメルケースになりました。my_admin の代わりに、 MyAdmin を使用する必要があります。この変更により、プレフィックスが他のルーティングパラメーターで正規化され、語尾変化のオーバーヘッドが削除されます。

RouteBuilder::resources() は、 URL 内のリソース名をデフォルトではアンダースコア形式の代わりにダッシュ形式で変換するようになりました。 $options 引数で、 'inflect' => 'underscore' を使用することで、アンダースコア形式での変換を保持できます。

Router::plugin() および Router::prefix() は、デフォルトで URL のダッシュ形式のプラグイン/プレフィックス名を使用するようになりました。$options 引数で、 'path' キーを使用して、下線（または他のカスタムパス）を保持できます。

Router は、リクエストのスタックではなく、リクエストの単一インスタンスのみへの参照を維持します。 Router::pushRequest() 、 Router::setRequestInfo() および Router::setRequestContext() は削除されました。代わりに、 Router::setRequest() を使用してください。 Router::popRequest() は削除されました。Router::getRequest() には、 $current 引数がなくなりました。

TestSuite

Cake\TestSuite\TestCase::$fixtures は、コンマ区切りの文字列にすることができなくなりました。配列でなければなりません。

Utility

Cake\Utility\Xml::fromArray() は $options パラメーターの配列を必要とします。

Cake\Filesystem\Folder::copy($to, array $options = []) および Cake\Filesystem\Folder::move($to, array $options = []) には、最初の引数として抽出されたターゲットパスがあります。

Xml::build() の readFile オプションは、デフォルトで true ではなくなりました。代わりに、ローカルファイルを読み取るために、 readFile を有効にする必要があります。

Hash::sort() は、方向パラメーターで SORT_ASC および SORT_DESC 定数を受け入れるようになりました。

Inflector::pluralize() は index を indices ではなく indexes に反映するようになりました。これは、この複数形のコアおよびエコシステムでの技術的な使用を反映しています。

View

テンプレートは、 app や plugin ルート上の src/Template/ から templates/ フォルダーへ移動されました。この変更により、 src フォルダーには、 composer のオートローダーを介してオートロードされるクラスを持つファイルのみが含まれるようになりました。

Cell 、 Element 、 Email および Plugin といった特別なテンプレートフォルダーは、それぞれ小文字の cell 、 element 、 email および plugin に名前が変更されました。これにより、特別なフォルダーとアプリケーションのコントローラー名に対応する CamelCase 形式のフォルダーを視覚的に区別しやすくなります。

テンプレートの拡張子も、 .ctp から .php に変更されました。特別な拡張子は、実際の利点を提供せず、代わりに、 .ctp 拡張子を持つファイルを PHP ファイルとして認識するようにエディターや IDE を設定する必要がありました。

ViewBuilder::setLayout() または View::setLayout() の引数として false を使用して、 View::$layout プロパティを false に設定することはできなくなりました。代わりに、 ViewBuilder::disableAutoLayout() や View::disableAutoLayout() を使用して、レイアウトなしでビューテンプレートを描画します。

Cake\View\View は、 render() が複数回呼び出された場合、 null を返す代わりに再描画します。

定数 View::NAME_ELEMENT と View::NAME_LAYOUT は削除されました。 View::TYPE_ELEMENT と View::TYPE_LAYOUT が使用できます。

Helper

Cake\View\Helper\PaginatorHelper::hasPage() の引数が逆になっています。これにより、 'model' が第 2 引数である他のページネーターメソッドとの一貫性が保たれます。

Cake\View\Helper\UrlHelper::build() は第 2 引数にブール値を受け入れなくなりました。代わりに、 ['fullBase' => true] を使用しなければなりません。

コンテキスト無しでフォームを作成するには、 FormHelper::create() の最初の引数として、 null のみを使用する必要があります。コンテキストを推測できない他の値を渡すと、例外がスローされます。

Cake\View\Helper\FormHelper および Cake\View\Helper\HtmlHelper は、 HTML データ属性 data-confirm-message を使用して、 confirm オプションを持つメソッドの確認メッセージを保持するようになりました。

Cake\View\Helper\FormHelper::button() は、 HTML エンティティーがデフォルトで、ボタンテキストと HTML 属性 をエンコードするようになりました。新しいオプション escapeTitle が追加され、他の HTML 属性とは別にタイトルのエスケープを制御できるようになりました。

Cake\View\Helper\SecureFieldTokenTrait が削除されました。そのフォームトークンデータ構築機能は、内部クラス FormProtector に含まれるようになりました。

HtmlHelper::docType() メソッドが削除されました。HTML4 および XHTML は廃止され、 HTML5 の doctype は非常に短く、直接入力するのが簡単です。

HtmlHelper::scriptBlock() および HtmlHelper::scriptStart() の safe オプションが削除されました。有効にすると、現在無効になっている XHTML のみに必要な CDATA タグを生成します。

Log

Cake\Log\LogTrait::log() および Cake\Log\Log::write() などのログ関連メソッドは、 $message 引数に文字列のみを受け入れるようになりました。この変更は、 API を PSR-3 [https://www.php-fig.org/psr/psr-3/] 標準に合わせるために必要でした。

その他

アプリケーションの config/bootstrap.php には、 Router::fullBaseUrl() への呼び出しを含めてください。最新のスケルトンアプリケーションの bootstrap.php を参照し、それに応じて更新します。

App::path() は、 Template の代わりに $type および templates を使用して、テンプレートへのパスを取得します。同様にロケールフォルダーのパスを取得するには、 Locale の代わりに locales を使用します。

ObjectRegistry::get() は、指定された名前のオブジェクトがロードされていない場合、例外をスローするようになりました。 ObjectRegistry::has() を使用して、オブジェクトがレジストリーに存在することを確認する必要があります。マジックゲッター ObjectRegistry::__get() は、指定された名前のオブジェクトがロードされない場合、引き続き null を返します。

ロケールファイルは、 src/Locale から resources/locales に移動しました。

CakePHP にバンドルされていた cacert.pem ファイルは、 composer/ca-bundle [https://packagist.org/packages/composer/ca-bundle] への依存関係に置き換えられました。

新機能

Console

コマンドクラスは、 defaultName() メソッドを実装して、慣習に基づいた CLI 名を上書きできます。

Core

InstanceConfigTrait::getConfigOrFail() および StaticConfigTrait::getConfigOrFail() が追加されました。他の orFail メソッドと同様に、これらのメソッドは要求されたキーが存在しないか null 値を持っている場合に例外を発生させます。

Database

データベースのタイムゾーンが PHP のタイムゾーンと一致しない場合は、 DateTime::setDatabaseTimezone() を使用できます。詳しくは、 datetime-type をご覧ください。

DateTime::setKeepDatabaseTimezone() により、クエリーによって生成された DateTime オブジェクトに、データベースのタイムゾーンを保持できます。

Cake\Database\Log\LoggedQuery は JsonSerializable を実装するようになりました。

Cake\Database\Connection で PSR-3 のロガーを使用できるようになりました。その結果、スタンドアロンのデータベースパッケージを使用しているユーザーは、ロギングに cakephp/log パッケージを使用することを強制されなくなりました。

Cake\Database\Connection で PSR-16 のキャッシャー を使用できるようになりました。その結果、スタンドアロンのデータベースパッケージを使用しているユーザーは、キャッシングに cakephp/cache パッケージを使用することを強制されなくなりました。新しいメソッド Cake\Database\Connection::setCache() および Cake\Database\Connection::getCache() が追加されました。

Cake\Databases\ConstraintsInterface が Cake\Datasource\FixtureInterface から抽出されました。このインターフェースは、制約をサポートするフィクスチャー実装によって実装する必要があります。これは、私たちの経験からは一般にリレーショナルデータベースです。

抽象型 char が追加されました。このタイプは、固定長の文字列カラムを処理します。

抽象型 datetimefractional および timestampfractional が追加されました。このタイプは、秒の小数部を持つカラムデータ型を処理します。

SqlServer スキーマは、 SYSDATETIME() などの関数を含むデフォルト値をサポートするようになりました。

抽象型 datetimetimezone および timestamptimezone が追加されました。このタイプは、タイムゾーンをサポートするカラムデータ型を処理します。

Error

接頭辞付きのコントローラーアクションによってエラーが発生した場合、 ErrorController は接頭辞付きのエラーテンプレートがある場合は、それを利用します。この動作は debug がオフの場合にのみ適用されます。

Http

フレームワーク全体を含めずに cakephp/http を使用できます。

CakePHP は PSR-15: HTTP Server Request Handlers [https://www.php-fig.org/psr/psr-15/] の仕様をサポートするようになりました。結果としてミドルウェアは Psr\Http\Server\MiddlewareInterface を実装するようになりました。 CakePHP 3.x スタイルの呼び出し可能なダブルパスミドルウェアは、後方互換性のために引き続きサポートされています。

Cake\Http\Client は PSR-18: HTTP Client [https://www.php-fig.org/psr/psr-18/] の仕様に準拠するようになりました。

Cake\Http\Client\Response::isSuccess() が追加されました。このメソッドは、レスポンスステータスコードが 2xx の場合 true を返します。

CspMiddleware が追加され、コンテンツセキュリティポリシーヘッダーの定義がより簡単になりました。

HttpsEnforcerMiddleware が追加されました。これにより SecureComponent の requireSecure 機能が置き換えられました。

Cookie は SameSite 属性をサポートするようになりました。

I18n

Date および FrozenDate は、 today('Asia/Tokyo') のようなさまざまなファクトリーヘルパーのタイムゾーンパラメーターを尊重するようになりました。

Mailer

メールメッセージ生成の責務は Cake\Mailer\Renderer に移されました。これは主にアーキテクチャーの変更であり、 Email クラスの使用方法には影響しません。唯一の違いは、テンプレート変数を設定するために Email::set() の代わりに Email::setViewVars() を使用する必要があることです。

ORM

Table::saveManyOrFail() メソッドが追加され、エラーの場合に失敗した特定のエンティティーで PersistenceFailedException をスローします。

コールバックを含む多くのエンティティーを一度に削除するための Table::deleteMany() および Table::deleteManyOrFail() メソッドが追加されました。エンティティーはトランザクションセーフで削除されます。

新しい空のエンティティーオブジェクトを作成するために Table::newEmptyEntity() が追加されました。これはフィールドバリデーションをトリガーしません。エンティティは、空のレコードとして検証エラーなしで永続化できます。

Cake\ORM\RulesChecker::isLinkedTo() および isNotLinkedTo() が追加されました。これらの新しいアプリケーションルールを使用すると、関連付けがあるかもしくは関連レコードがあるかどうかを確認できます。

新しい型クラス DateTimeFractionalType がマイクロ秒精度の日付型として追加されました。この型をデフォルトの datetime 型として TypeFactory に追加するか、個々のカラムに再マッピングすることで、この型の使用を選択できます。このタイプをデータベースタイプに自動的にマッピングする方法については、 Database migration notes を参照してください。

タイムゾーンをサポートする日時型に、新しい型クラス DateTimeTimezoneType が追加されました。この型をデフォルトの datetime 型として TypeFactory に追加するか、個々のカラムに再マッピングすることで、この型の使用を選択できます。このタイプをデータベースタイプに自動的にマッピングする方法については、 Database migration notes を参照してください。

Routing

Cake\Routing\Asset が追加されました。このクラスは、 Router::url() と同等の静的インターフェースで、アセットURL生成を公開します。詳しくは asset-routing をご覧ください。

TestSuite

TestSite\EmailTrait::assertMailContainsAttachment() が追加されました。

Validation

Validation::dateTime() がマイクロ秒を含む値を受け入れるようになりました。

View

FormHelper は、エンティティーの ORM テーブルクラスで "notEmpty" とマークされたフィールドの HTML5 検証メッセージを生成するようになりました。この機能は autoSetCustomValidity クラス設定オプションで切り替えることができます。

FormHelper は、日時フィールドのネイティブ HTML5 入力タグを生成するようになりました。詳しくは、Form Helper <create-datetime-controls> ページを参照してください。以前のマークアップを保持する必要がある場合は、シム化された FormHelper が Shim plugin [https://github.com/dereuromark/cakephp-shim] にあり、古い動作・生成が含まれています（4.x ブランチ）。

FormHelper は、時間コンポーネントを持つ datetime ウィジェットのデフォルトのステップサイズを秒に設定するようになりました。フィールドが新しい datetimefractional もしくは timestampfractional データベースタイプからのものである場合、デフォルトはミリ秒です。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

4.1 移行ガイド

CakePHP 4.1 は 4.0 からのAPI互換アップグレードです。このページでは、4.1で追加された非推奨事項と機能の概要を説明します。

4.1.0へのアップグレード

CakePHP 4.1.0にアップグレードするには、次の Composer コマンドを実行してください。:

php composer.phar require --update-with-dependencies "cakephp/cakephp:4.1.x"

非推奨

4.1では、いくつかの非推奨機能が導入されています。これらの機能はすべて 4.x の間は継続されますが、5.0 で削除されます。非推奨機能の更新を自動化するには、upgrade tool <upgrade-tool-use> を使用します。:

bin/cake upgrade rector --rules cakephp41 <path/to/app/src>

注釈

これはCakePHP 4.1の変更点のみを更新します。CakePHP 4.0の変更を最初に適用していることを確認してください。

Controller

PaginatorComponent の sortWhitelist オプションは非推奨になりました。代わりに、sortableFields を使用してください。

PaginatorComponent の whitelist オプションは非推奨になりました。代わりに、allowedParameters を使用してください。

Database

TableSchema::getPrimary() は非推奨になりました。代わりに、 getPrimaryKey() を使用してください。

Cake\Database\Schema\BaseSchema は Cake\Database\Schema\SchemaDialect に名前が変更されました。

Cake\Database\Schema\MysqlSchema は Cake\Database\Schema\MysqlSchemaDialect に名前が変更されました。

Cake\Database\Schema\SqliteSchema は Cake\Database\Schema\SqliteSchemaDialect に名前が変更されました。

Cake\Database\Schema\SqlserverSchema は Cake\Database\Schema\SqlserverSchemaDialect に名前が変更されました。

Cake\Database\Schema\PostgresSchema は Cake\Database\Schema\PostgresSchemaDialect に名前が変更され、内部マークされました。

DateTimeType::setTimezone() は非推奨になりました。代わりに、 setDatabaseTimezone() を使用してください。

FunctionBuilder::cast([...]) に対するマジックメソッド署名は非推奨になりました。代わりに、 FunctionBuilder::cast('field', 'type') を使用してください。

Cake\Database\Expression\Comparison は Cake\Database\Expression\ComparisonExpression に名前が変更されました。

Datasource

Paginator の sortWhitelist オプションは非推奨になりました。代わりに、 sortableFields を使用してください。

Paginator の whitelist オプションは非推奨になりました。代わりに、 allowedParameters を使用してください。

Form

Form::schema() は非推奨になりました。代わりに、 Form::getSchema() か Form::setSchema() を使用してください。

Http

CsrfProtectionMiddleware::whitelistCallback() は非推奨になりました。代わりに、 skipCheckCallback() を使用してください。

ServerRequest::input() は非推奨になりました。 PHP の生の入力を文字列として取得するには、 (string)$request->getBody() を使用してください。 BodyParserMiddleware を使ってリクエストのボディを解析してください。そうすれば、 $request->getParsedBody() で配列やオブジェクトとして利用できるようにします。

フレームワーク内でのクッキー作成との一貫性を高めるために、 CsrfProtectionMiddleware の httpOnly オプションは、httponly に変更されました。

ORM

QueryExpression::or_() と QueryExpression::and_() は非推奨になりました。代わりに or() と and() を使用してください。

Routing

Cake\Routing\Exception\RedirectException は非推奨になりました。代わりに、 Cake\Http\Exception\RedirectException を使用してください。

View

Form/ContextInterface::primaryKey() は非推奨になりました。代わりに、 getPrimaryKey() を使用してください。

Behaviorの変更

以下の変更はどのメソッドの署名も変更しませんが、メソッドのセマンティクスや動作を変更します。

Database

MySQL: （未だにブーリアン型にマップされている） TINYINT(1) 以外の整数の表示幅は無視されるようになりました。 MySQL 8では表示幅は非推奨です。

Http

アップロードしたファイルの正規化は、ServerRequest から ServerRequestFactory に移動されました。これは、ネストされたファイルアップロード配列を使用するリクエストオブジェクトを作成している場合、テストに影響を与える可能性があります。 IntegrationTestCaseTrait を使ったテストは変更する必要はありません。

ORM

Cake\ORM\TableRegistry は非推奨になりました。テーブルロケーターインスタンスを取得するためには、代わりに Cake\ORM\Locator\LocatorAwareTrait::getTableLocator() か Cake\Datasource\FactoryLocator::get('Table') を使用してください。 Controller, Command, TestCase のようなクラスは、すでに Cake\ORMLocator\LocatorAwareTrait を使用しています。そのため、これらのクラスでは $this->getTableLocator()->get('ModelName') を使うことができます。

BelongsToMany アソシエーションは、ジャンクションテーブルの BelongsTo アソシエーションで設定された bindingKey を尊重するようになりました。以前は、ターゲットテーブルの主キーが常に代わりに使用されていました。

アソシエーション名が適切に大文字と小文字を区別するようになりました。そのため、 Query::contain() や Table::hasMany() のような関数で参照する際には、適切にマッチさせなければなりません。

Cake\ORM\AssociationCollection は、内部で保持しているオブジェクトマップのキーを生成するために、アソシエーション名を小文字にしなくなりました。

TestSuite

TestCase::setAppNamespace() は、保存と復元が簡単になるように、以前のアプリの名前空間を返すようになりました。

MySQL予約キーワードの変更に伴い、GroupsFixtureの名前がSectionsFixtureに変更されました。

View

フォームヘルパーのデフォルトの値のソースが context の代わりに data, context に設定されるようになりました。もし setValueSources() を使って値のソースを変更する場合は、コードを更新する必要があるかもしれません。

CakePHPで提供されている FormHelper コンテキストクラスは、コンストラクタに $request オブジェクトを取らなくなりました。

新機能

Datasource

EntityInterface::getAccessible() が追加されました。

Console

環境変数 NO_COLOR が設定されている場合、すべての出力に色の ANSI エスケープコードは含まれません。詳しくは no-color.org [https://no-color.org/] を参照してください。

コマンドはシェルが $io->setInteractivate(false) を使っていたのと同じように対話モードを無効にすることができるようになりました。これは、必要に応じてプロンプトを回避し、デフォルト値を使用します。 --quiet / -q を使うことで、既存のすべてのコマンドに対して直接呼び出すことができるようになりました。

Database

MySQL 8 がサポートされました。テストも行われています。

SQL関数の集約を表現するために AggregateExpression を追加しました。 FunctionsBuilder::aggregate() は新しい集約SQL関数をラップするのに使えます。

任意の集約式にウィンドウ関数のサポートが追加されました。 AggregateExpression はウィンドウ式をラップしたため、連鎖的な呼び出しによって任意のインスタンスを簡単に拡張することができます。

集約関数が FILTER (WHERE ...) 区をサポートしました。

PostgreSQL と SQLServer は エイリアスを持つ集約関数に対して HAVING 条件をサポートするようになりました。

FunctionsBuilder::cast() が追加されました。

Common Table Expression (CTE) が追加されました。 CTEは、Query::with() を用いてクエリにアタッチすることが可能です。

Query::orderAsc() と Query::orderDesc() はClosureをフィールドとして受け付けるようになり、提供されている QueryExpression オブジェクトを使って複雑なオーダー式を構築できるようになりました。

Error

ウェブコンテキストではHTMLを、CLIコンテキストではANSIスタイルで出力するようになりました。周期的な構造や繰り返しオブジェクトの出力がよりシンプルになりました。周期的なオブジェクトは一度だけダンプされ、完全な値に戻るために参照 ID を使用します。

Debugger::addEditor() と Debugger::setEditor() が追加されました。これらのメソッドにより、エディタのフォーマットを追加したり、好みのエディタを設定したりすることができます。

設定値として Debugger.editor が追加されました。この値は、優先されるエディタのリンク形式として使用されます。

ErrorHandlerMiddleware は Http\Exception\RedirectException をハンドルするようになり、それらの例外を HTTP リダイレクトレスポンスに変換するようになりました。

BaseErrorHandler は設定されたエラーロガーを使ってPHPの警告やエラーをログに記録するようになりました。

カスタムエラーロガーに必要なインターフェイスを正式なものにするために ErrorLoggerInterface が追加されました。

Form

Form::set() を追加しました。このメソッドは View::set() や Controller::set() と同じようにフォームオブジェクトにデータを追加することができます。

Http

BaseApplication::addOptionalPlugin() を追加しました。このメソッドは、プラグインの読み込みや、開発者依存のため存在しない可能性のあるプラグインのエラー処理を行います。

Cake\HttpException\RedirectException を追加しました。この例外は Routing パッケージの RedirectException を置き換えるもので、 アプリケーションのどこでも発生させることができます。

CsrfProtectionMiddleware は samesite 属性を設定したクッキーを作成することができるようになりました。

Session::read() が2番目のパラメータでデフォルト値を設定できるようになりました。

Session::readOrFail() は、キーが見つからなかった場合に例外を発生させたい場合に便利な read() 操作のラッパーとして追加されました。

I18n

Time , FrozenTime , Date , FrozenDate の setJsonEncodeFormat メソッドは、カスタムの文字列を返すための callable を受け入れるようになりました。

parseDateTime() と parseDate() は、 disableLenientParsing() を使用して、簡潔な構文解析を無効にすることができます。デフォルトでは有効になっています。（IntlDateFormatter と同様です）

Log

ログメッセージに {foo} スタイルのプレースホルダを含めることができるようになりました。これらのプレースホルダは $context パラメータの値に置き換えられます。

ORM

ORMはリクエストデータから各エンティティがマージされた後に Model.afterMarshal イベントをトリガーするようになりました。

TranslateBehavior を使用しているときに、 locale finderオプションを使用して、単一の検索呼び出しのロケールを変更することができます。

Query::clearResult() が追加されました。このメソッドを使うと、クエリの結果を削除して再実行できるようになります。

Table::delete() は、cascadeCallbackの操作中に依存するアソシエーションが削除に失敗した場合、削除操作を中止してfalseを返すようになりました。

Table::saveMany() は保存されたエンティティに対して Model.afterSaveCommit イベントを起動するようになりました。

Routing

ルートパス文字列からURL配列を素早く生成するための便利な関数 urlArray() が導入されました。

TestSuite

FixtureManager::unload() は、フィクスチャがアンロードされている間、テストの end でテーブルを切り詰めることはなくなりました。テーブルはフィクスチャのセットアップ中にも切り捨てられます。切り捨て処理が少なくなったため、テストスイートの実行がより速くなるはずです。

メールボディアサーションは、失敗メッセージにメールの内容を含めるようになり、テストのデバッグがより簡単になりました。

チェーン可能なフィクスチャ設定を可能にするために、TestCase::addFixture() が追加されました。これは、IDEでも自動補完可能です。

View

TextHelper::::slug() を追加しました。このメソッドは、 Cake\Utility\Text::slug() を委譲します。

ヘルパーを追加するためのチェイン可能なラッパーメソッドとして ViewBuilder::addHelper() を追加しました。

ルートパスからのリンクやURLをより簡単に作成するために、ビュー層でIDEをサポートした HtmlHelper::linkFromPath() と UrlHelper::urlFromPath() を追加しました。

Utility

Hash::combine() は $keyPath パラメータに null を指定できるようになりました。 nullを指定すると、数値インデックス付きの出力配列になります。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

4.2 移行ガイド

CakePHP 4.2 は 4.0 からのAPI互換アップグレードです。このページでは、4.2で追加された非推奨事項と機能の概要を説明します。

4.2.0へのアップグレード

CakePHP 4.2.0にアップグレードするには、次の Composer コマンドを実行してください。:

php composer.phar require --update-with-dependencies "cakephp/cakephp:4.2.x"

非推奨

4.2では、いくつかの非推奨機能が導入されています。これらの機能はすべて 4.x の間は継続されますが、5.0 で削除されます。非推奨機能の更新を自動化するには、upgrade tool <upgrade-tool-use> を使用します。:

bin/cake upgrade rector --rules cakephp42 <path/to/app/src>

注釈

これはCakePHP 4.2の変更点のみを更新します。CakePHP 4.1の変更を最初に適用していることを確認してください。

パスごとに非推奨を無効にするための新しい設定オプションが追加されました。詳細は deprecation-warnings を参照してください。

Core

Exception::responseHeader() は非推奨になりました。 HTTP のレスポンスヘッダを設定するには HttpException::setHeaders() を使用しなければなりません。レスポンスヘッダを設定するアプリケーションやプラグインの例外は HttpException のサブクラスに更新する必要があります。

Cake\Core\Exception\Exception は Cake\Core\Exception\CakeException に名前が変更されました。

Database

Cake\Database\Exception は Cake\Database\Exception\DatabaseException に名前が変更されました。

ORM

TableLocator::allowFallbackClass() が追加されました。このメソッドは、自動的に生成されるフォールバックテーブルクラスを無効にします。現在は無効にすることがオプトインされていますが、将来的にはデフォルトになります。

ORM\Behavior::getTable() は非推奨になりました。代わりに、table() を使用してください。この変更により、ORM\Table 間でメソッドの戻り値が異なるようになったため、メソッド名も異なるものになりました。

Behaviorの変更

以下の変更はどのメソッドのシグネチャも変更しませんが、メソッドのセマンティクスや動作を変更します。

Collection

Collection::groupBy() と Collection::indexBy() は、パスが存在しない場合やパスに NULL 値が含まれている場合に例外をスローするようになりました。 null をサポートする必要がある場合は、コールバックを使用してデフォルト値を返すようにしました。

Controller

Controller::$components は protected になりました。以前は protected として文書化されていました。これは、実装で可視性を public に変更できるため、ほとんどのアプリケーションコードに影響を与えることはないはずです。

Component

FlashComponent::set() を Exception インスタンスと一緒に使用する場合、デフォルトで element オプションを error に設定するようになりました。

Database

TimeType は H:i 形式の値を正しくマーシャルするようになりました。 ※以前は、これらの値はバリデーション実行後に null にキャストされていました。

Error

例外コードは HttpException の HTTP ステータスコードとしてのみ使用されるようになりました。その他の例外で 500 以外の値を返すHTTPコードは ExceptionRenderer::$exceptionHttpCodes によって制御されます。

注釈

例外が更新されるまで以前の動作に戻す必要がある場合は、カスタムの ExceptionRenderer を作成して getHttpCode() 関数をオーバーライドすることができます。詳細は custom-exceptionrenderer を参照してください。

ConsoleErrorHandler は例外コードを ConsoleException の終了コードとしてのみ使用するようになりました。

Validation

Validation::time() は、分がない場合に文字列を拒否するようになりました。 ※以前は時間のみの数字を受け付けていましたが、APIのドキュメントでは分が必要だと示されていました。

破壊的変更

API の背後に、対応が必要ないくつかの破壊的変更があります。これらの変更は通常、テストにのみ影響します。

I18n

Aura.Intl [https://github.com/auraphp/Aura.Intl] パッケージへの依存性は、もはやメンテナンスされていないため削除されました。アプリ/プラグインが custom translation loaders を持っている場合、 Aura\Intl\Package の代わりに Cake\I18n\Package のインスタンスを返す必要があります。

テスト

UUID 周辺のフィクスチャ名が統合されました (UuidItemsFixture, BinaryUuidItemsFixture)。これらの名前を使用している場合は、必ず更新してください。 UuidportfoliosFixture は core では使われていませんでしたが、現在は削除されています。

新機能

私たちは、機能を出荷できるようにするために、新しいプロセスを追加し、コミュニティからのフィードバックを集めて それらの機能を進化させていきます。私たちはこの仕組みを experimental-features と呼んでいます。

Core

コンテナ 依存性の注入(DI) の実験的サポートが追加されました。

Console

ConsoleIo::comment() が追加されました。このメソッドは、生成されたヘルプテキストのコメントのように、テキストを青く整形します。

TableHelper は <text-right> 書式タグをサポートするようになりました。これは、セルの内容を（左ではなく）右端に揃えます。

Database

SqlServer はデフォルトでクライアント側のバッファリングされたカーソルを準備された文のために作成するようになりました。これは、サーバー側の SCROLL カーソルのパフォーマンスの問題を修正するために変更されました。ほとんどの結果セットでパフォーマンスが向上されることを確認してください。

警告

クエリ結果が大きいユーザの場合、Query::disableBufferedResults() が呼ばれないとクライアント側のバッファの割り当てにエラーが発生する可能性があります。最大バッファサイズは php.ini で pdo_sqlsrv.client_buffer_max_kb_size で設定できます。詳細は https://docs.microsoft.com/ja-jp/sql/connect/php/cursor-types-pdo-sqlsrv-driver?view=sql-server-ver15#pdo_sqlsrv-and-client-side-cursors を参照してください。

現在の結果キャスティングモードを取得するために Query::isResultsCastingEnabled() が追加されました。

照合（ collation ）に文字列リテラルを使えるように、 StringExpression が追加されました。

IdentifierExpression が照合（ collation ）をサポートするようになりました。

Http

Cake\Http\Middleware\SessionCsrfProtectionMiddleware が追加されました。このミドルウェアは、CSRFトークンをクッキーではなく、セッションに格納します。これにより、CSRFトークンはセッションに合わせてユーザスコープ化され、時間ベースのものとなり、クッキーベースのCSRFトークンよりもセキュリティが強化されます。このミドルウェアは CsrfProtectionMiddleware の代替品です。

hal+json, hal+xml, jsonld が Response に追加され、 withType() で使用できるようになりました。

Client::createFromUrl() を追加しました。このメソッドを使うと、ベースパスを含む特定のドメインにスコープされたHTTPクライアントを作成することができます。

新しいユーティリティクラス CakeHttp\\FlashMessage が追加され、そのインスタンスは ServerRequest::getFlash() を通して利用できるようになりました。 FlashComponent に似たクラスで、フラッシュメッセージを設定することができます。特にミドルウェアからのフラッシュメッセージを設定するのに便利です。

ORM

Table::subquery() と Query::subquery() が追加されました。これらのメソッドを使うと、自動エイリアスを持たないクエリオブジェクトを作成することができます。これにより、サブクエリや一般的なテーブル式を作成する際のオーバーヘッドや複雑さを軽減することができます。

3.xで利用可能だった IsUnique ルールは allowMultipleNulls オプションを受け入れるようになりました。 3.x とは異なり、デフォルトでは無効になっています。

TestSuite

EmailTrait::assertMailSubjectContains() と assertMailSubjectContainsAt() が追加されました。

mockService() に ConsoleIntegrationTestTrait と IntegrationTestCaseTrait が追加されました。このメソッドは 依存性の注入(DI) コンテナに注入されたサービスをモックオブジェクトやスタブオブジェクトに置き換えることを可能にします。

View

コンテキストクラスは attributes() の結果に comment, null, default のメタデータオプションを含めるようになりました。

ViewBuilder::addHelper() は、ヘルパーのコンストラクタにオプションを渡すための $options パラメータを受け付けるようになりました。

オプション assetUrlClassName` が UrlHelper` に追加されました。このオプションを使うと、デフォルトのアセットの URL リゾルバをアプリケーション固有のものに置き換えることができます。これは、アセットキャッシュバストのパラメータをカスタマイズする必要がある場合に便利です。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

4.3 移行ガイド

CakePHP 4.3 は 4.0 からのAPI互換アップグレードです。このページでは、4.3で追加された非推奨事項と機能の概要を説明します。

4.3.0へのアップグレード

CakePHP 4.3.0にアップグレードするには、次の Composer コマンドを実行してください。:

php composer.phar require --update-with-dependencies "cakephp/cakephp:^4.3"

非推奨

4.3では、いくつかの非推奨機能が導入されています。これらの機能はすべて 4.x の間は継続されますが、5.0 で削除されます。非推奨機能の更新を自動化するには、 upgrade tool を使用します。:

bin/cake upgrade rector --rules cakephp43 <path/to/app/src>

注釈

これはCakePHP 4.3の変更点のみを更新します。CakePHP 4.2の変更を最初に適用していることを確認してください。

新しい設定オプションが追加され、パスごとに非推奨を無効にすることができるようになりました。詳細は 非推奨の警告 を参照してください。

Connection

Connection::supportsDynamicConstraints() は非推奨となりました。フィクスチャが動的に制約を削除したり作成したりしないようになったためです。

Controller

Controller.shutdown コンポーネントのイベントコールバックの名前が shutdown から afterFilter に変更されました。これにより、コールバックの一貫性が高まりました。

Database

可変のdatetimeクラスを DateTimeType やその他の時間関連の型クラスで使用することは、非推奨となります。そのため、他の型クラスのメソッドである DateTimeType::useMutable(), DateTimeType::useImmutable() や同様のメソッドは非推奨となります。

DriverInterface で定義された機能定数を受け付ける DriverInterface::supports() を採用している DriverInterface::supportsQuoting() および DriverInterface::supportSavepoints() は現在、非推奨となっています。

DriverInterface::supportsDynamicConstraints() は、フィクスチャが動的な制約の削除、作成を試みないため、非推奨となりました。

I18n

datetime クラスの Time と Date は非推奨です。代わりに、不変的な代替クラスである FrozenTime と FrozenDate を使用してください。

Log

FileLog は、dateFormat 設定オプションを DefaultFormatter に移動しました。

ConsoleLog は、dateFormat 設定オプションを DefaultFormatter に移動しました。

SyslogLog では、format 設定オプションを LegacySyslogFormatter に移動しました。現在のデフォルトは DefaultFormatter です。

Middleware

"ダブルパス"のミドルウェア、つまり、__invoke($request, $response, $next) メソッドを持つクラスは非推奨です。代わりに、function($request, $handler) というシグネチャを持つ Closure や、 PsrhttpHttpServer\MiddlewareInterface を実装したクラスを使用してください。

Network

Socket::$connected は非推奨です。代わりに isConnected() を使用してください。

Socket::$description は非推奨です。

Socket::$encrypted は非推奨です。代わりに isEncrypted() を使用してください。

Socket::$lastError は非推奨です。代わりに lastError() を使用してください。

ORM

ModelAwareTrait::loadModel() は非推奨です。代わりに、新しい LocatorAwareTrait::fetchTable() を使用してください。例えば、コントローラではデフォルトのテーブルインスタンスを取得するために $this->fetchTable() を実行することもできますし、デフォルト以外のテーブルを取得するには $this->fetchTable('Foos') を使用することもできます。

クエリをプロキシするすべての ResultSetInterface メソッド（ `CollectionInterface` を含む）（これらは強制的に結果を取得し、その結果に対してプロキシされたメソッドを呼び出します）は非推奨になりました。非推奨な使い方の例として、 $query->combine('id', 'title'); があります。これを $query->all()->combine('id', 'title'); に変更する必要があります。

バリデータオブジェクトを Table::save() の validate オプションに渡すことは、非推奨です。バリデーターはテーブルクラス内で定義するか、代わりに setValidator() を使用してください。

Association::setName() は非推奨です。アソシエーション名は、アソシエーションが存在するときに定義されるべきです。

QueryExpression::addCase() は非推奨です。代わりに case() を使用してください。また、 ['value' => 'literal'] および ['column' => 'identifier'] の構文は、新しい流暢なケースビルダーではサポートされていません。

Routing

:controller のようなコロンの付いたルートプレースホルダーは非推奨です。代わりに {controller} のような波括弧付きのプレースホルダーを使用してください。

TestSuite

TestFixture::$fields と TestFixture::$import は非推奨です。アプリケーションを new fixture system に変換する必要があります。

TestCase::$dropTables は非推奨です。テスト実行中にテーブルを削除することは 新しいマイグレーションやスキーマダンプベースのフィクスチャとは互換性がなく、5.0では削除されます。

View

FormHelperメソッドの非関連性オプション（例： ['disabled'] ）は非推奨となりました。

ViewBuilder::setHelpers() の第2引数 $merge は非推奨となり、専用の ViewBuilder::addHelpers() メソッドを使うことで、マージと上書きの操作が明確に分離できるようになりました。

Behaviorの変更

以下の変更はどのメソッドのシグネチャも変更しませんが、メソッドのセマンティクスや動作を変更します。

Collection

同一の実装で、 $preserveKeys パラメータを $keepKeys に名称変更しました。

Command

cake i18n extract には --relative-paths オプションがなくなりました。このオプションはデフォルトでオンになっています。

Core

Configure::load() は、無効な設定エンジンが使用されている場合、例外を発生させるようになりました。

Database

ComparisonExpression は、生成された IdentifierExpression のsqlを()でラップしなくなりました。これは Query::where() や、その他の ComparisonExpression が生成される場所に影響します。

Datasource

ConnectionManager::alias() の $alias と $source のパラメータ名を、それぞれの内容に合わせて変更しました。これはドキュメントと名前付きパラメータにのみ影響します。

Http

Http\Client は、ini_get('user_agent') で 'CakePHP' をユーザーエージェントのフォールバックとして使用するようになりました。

ORM

Entity::isEmpty() と Entity::hasValue() を、'0'を空でない値として扱うように調整しました。これにより、ドキュメントや本来の意図に沿った動作となります。

TranslateBehavior のエンティティ検証エラーは、 {lang} ではなく _translations.{lang} のパスに設定されるようになりました。これにより、エンティティエラーのパスが、リクエストデータに使用されるフィールドと同じになります。一度に複数の翻訳を変更するフォームがある場合は、検証エラーの表示方法を更新する必要があるかもしれません。

カラムを選択する際に、カラムに設定されているデフォルトの型よりも関数式で指定された型が優先されるようになりました。例えば、 $query->select(['id' => $query->func()->min('id')]) を使うと、取得したエンティティの id の値は、integer ではなく float になります。

Routing

Router::connect(), Router::prefix(), Router::plugin(), Router::scope() は非推奨です。代わりに、対応する非静的な RouteBuilder メソッドを使用してください。

RouteBuilder::resources() は、'braced' プレースホルダーを使用するルートを生成するようになりました。

TestSuite

TestCase::deprecated() は、コールバックによって少なくとも1つのdeprecation warningが発生したことをアサートするようになりました。

Validation

Validator::setProvider() は、オブジェクトでも文字列でもないプロバイダ名が使用された場合、例外を発生させるようになりました。これまでは、エラーは発生しませんでしたが、そのプロバイダも動作しませんでした。

View

ViewBuilder::build() の $vars パラメータは非推奨です。代わりに setVar() を使用してください。

HtmlHelper::script() と HtmlHelper::css() は、スキームを含む絶対URLをエスケープするようになりました。

破壊的変更

API の背後に、対応が必要ないくつかの破壊的変更があります。これらの変更は通常、テストにのみ影響します。

Log

BaseLog::_getFormattedDate() と dateFormat の設定が削除されました。メッセージのフォーマットロジックがログフォーマッタに移されたためです。

View

TimeHelper::fromString() は、 Time ではなく FrozenTime のインスタンスを返すようになりました。

新機能

Controller

Controller::middleware() が追加され、単一のコントローラに対してのみミドルウェアを定義することができるようになりました。詳しくは Controller Middleware をご覧ください。

コントローラでは、アクションパラメータに float, int, bool, array のいずれかの型宣言をサポートするようになりました。ブール型で渡されるパラメータは、 0 か 1 のどちらかでなければなりません。

Core

deprecationWarning() は、重複した通知を出さなくなりました。代わりに 最初に表示されるようになりました。これにより、テスト出力の読みやすさ これにより、テスト出力の可読性が向上し、HTMLの文脈での視覚的ノイズも改善されます。 app_local.php で、 Error.allowDuplicateDeprecations を true に設定することで、重複した通知出力を復元することができます。

CakePHP の league/container への依存度が ^4.1.1 に変更されました。 DIコンテナは実験的とされていますが、このアップグレードにより、サービスプロバイダーの定義のアップグレードが必要になる可能性があります。

Database

データベースマッピングタイプは、カラムのSQL生成とカラムスキーマの反映を指定するために、 Cake\Database\SchemaAwareInterface を実装できるようになりました。これにより、カスタムタイプで非標準のカラムを扱うことができます。

ログに記録されるクエリは、postgres、sqlite、mysql の各ドライバで TRUE と FALSE を使用するようになりました。これにより、クエリをコピーして対話型プロンプトで実行することが容易になりました。

DateTimeType では、リクエストデータをユーザーのタイムゾーンからアプリケーションのタイムゾーンに変換できるようになりました。詳しくは リクエストデータをユーザーのタイムゾーンから変換する をご覧ください。

JsonType::setEncodingOptions() が追加されました。このメソッドでは、データを永続化する際にORMがJSONをシリアライズする際の json_encode() オプションを定義することができます。

すべての機能チェックをひとつの関数にまとめた DriverInterface::sets() を追加しました。ドライバーは、カスタム機能名、または 機能 constants: の定数のいずれかをサポートします。

FEATURE_CTE

FEATURE_JSON

FEATURE_QUOTE

FEATURE_SAVEPOINT

FEATURE_WINDOW

PDO::inTranaction() が返すステータスを反映する DriverInterface::inTransaction() を追加しました。

CASE, WHEN, THEN 文のための流暢なビルダーが追加されました。

Form

Form::execute() に $options パラメータが追加されました。このパラメータは、どのバリデータを適用するかを選択したり、バリデーションを無効にするために使用できます。

Form::validate() に $validator パラメータが追加され、適用するバリデーションセットを選択できるようになりました。このパラメータは、適用されるバリデーションセットを選択します。

Http

CspMiddleware は、cspScriptNonce と cspStyleNonce のリクエスト属性を設定するようになり、厳格なコンテンツセキュリティポリシーの採用を効率化しました。

Client::addMockResponse() と clearMockResponses() が追加されました。

Log

ログエンジンは、書き込む前にメッセージ文字列をフォーマットするためにフォーマッタを使用するようになりました。これは formatter 設定オプションで設定できます。詳細は ロギングフォーマッタ のセクションを参照してください。

JsonFormatter が追加され、任意のログエンジンの formatter オプションとして設定できるようになりました。

ORM

HasManyおよびBelongsToManyのアソシエーションを contain() するクエリは、結果キャストのステータスを伝搬するようになりました。これにより、すべてのアソシエーションからの結果は、タイプマッピングオブジェクトでキャストされるか、まったくキャストされないかのいずれかになります。

Table では、 displayField のデフォルトの候補となるフィールドのリストに label が含まれるようになりました。

nullを許容するカラムに対して、 Query::whereNotInListOrNull() と QueryExpression::notInOrNull() を追加しました。なぜなら、 null != value は常にfalseであり、カラムがnullの場合、 NOT IN のテストは常に失敗するからです。

LocatorAwareTrait::fetchTable() が追加されました。これにより、 $this->fetchTable() を使って、コントローラ、コマンド、メーラー、セルなどの traitを使ったクラスのテーブルインスタンスを取得することができます。 LocatorAwareTrait::$defaultTable プロパティを設定することで、デフォルトのテーブルエイリアスを指定することができます。

TestSuite

IntegrationTestTrait::enableCsrfToken() において、カスタムのCSRFクッキー/セッションキー名を使用できるようになりました。

HTTPモックを簡単に書けるように HttpClientTrait を追加しました。詳細は Testing を参照してください。

新しいフィクスチャシステムが導入されました。このフィクスチャシステムはスキーマとデータを分離し、既存のマイグレーションを再利用してテストスキーマを定義することができます。アップグレードの方法は フィクスチャのアップグレード ガイドで説明しています。

View

HtmlHelper::script() と HtmlHelper::css() は、 cspScriptNonce と cspStyleNonce のリクエスト属性が存在する場合、生成されたタグに nonce 属性を追加するようになりました。

FormHelper::control() はバリデータのメタデータに基づいて aria-invalid, aria-required , aria-describedby 属性を生成するようになりました。自動ラベル要素を無効にしてプレースホルダーを提供した場合には、 aria-label 属性が設定されます。

ViewBuilder::addHelpers() が追加され、マージと上書きの操作が明確に分離されました。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

4.4 移行ガイド

CakePHP 4.4 は 4.0 からのAPI互換アップグレードです。このページでは、4.4で追加された非推奨事項と機能の概要を説明します。

4.4.0へのアップグレード

CakePHP 4.4.0にアップグレードするには、次の Composer コマンドを実行してください。:

php composer.phar require --update-with-dependencies "cakephp/cakephp:^4.4"

注釈

CakePHP 4.4を動作させるには、PHP 7.4以上が必要です。

非推奨

4.4では、いくつかの非推奨機能が導入されています。これらの機能はすべて 4.x の間は継続されますが、5.0 で削除されます。

Controller

Controller::paginate() の paginator オプションは非推奨です。代わりに、 className を使用してください。

PaginatorComponent の paginator オプションは非推奨です。代わりに、 className を使用してください。

Datasource

FactoryLocator::add() はもはやクロージャ・ファクトリ関数を受け付けません。代わりに LocatorInterface のインスタンスを渡さなければなりません。

Cake\Datasource\Paging\Paginator は Cake\Datasource\Paging\NumericPaginator に名称変更されました。

ErrorHandler & ConsoleErrorHandler

ErrorHandler と ConsoleErrorHandler クラスは、非推奨となりました。これらのクラスは、新しい ExceptionTrap と ErrorTrap クラスに置き換わりました。トラップクラスは、より拡張可能で一貫性のあるエラーおよび例外処理フレームワークを提供します。新しいシステムにアップグレードするためには、（例えば config/bootstrap.php 中の） ErrorHandler と ConsoleErrorHandler の使い方を以下のように置き換えることができます。:

use Cake\Error\ErrorTrap;
use Cake\Error\ExceptionTrap;

(new ErrorTrap(Configure::read('Error')))->register();
(new ExceptionTrap(Configure::read('Error')))->register();

より詳細なドキュメントについては、 エラーと例外の処理 を参照してください。さらに、非推奨のエラー処理システムに関連する以下のメソッドも非推奨となります。:

Debugger::outputError()

Debugger::getOutputFormat()

Debugger::setOutputFormat()

Debugger::addFormat()

Debugger::addRenderer()

ErrorLoggerInterface::log()。 代わりに logException() を実装してください。

ErrorLoggerInterface::logMessage()。 代わりに logError() を実装してください。

RequestHandlerComponent

RequestHandlerComponent はゆるやかに非推奨になりました。 AuthComponent のように RequestHandler を使用しても、ランタイム非推奨にはなりませんが、 5.0では削除される 予定 です。

accepts() を $this->request->accepts() に置き換えてください。

requestedWith() をカスタムリクエスト検出器に置き換えてください。 (例えば、 $this->request->is('json') など)

prefers() を ContentTypeNegotiation に置き換えてください。コンテンツタイプのネゴシエーション を参照してください。

renderAs() を Controller のコントローラコンテンツネゴシエーション機能に置き換えてください。

checkHttpCache オプションを HTTPキャッシュの確認 に置き換えてください。

RequestHandlerComponent でビュークラスマッピングを定義する代わりに、 コンテンツタイプのネゴシエーションを使用してください。

PaginationComponent

PaginationComponent は非推奨で、5.0で削除される予定です。必要なページング設定を行うには、 Controller::$paginate プロパティ、または Controller::paginate() メソッドの $settings パラメータを使用します。

ORM

SaveOptionsBuilder は非推奨となりました。代わりにオプション配列を使用してください。

プラグイン

プラグインクラス名は、プラグイン名に "Plugin" サフィックスを付加したものと一致するようになりました。例えば、"ADmad/I18n" のプラグインクラスは、ADmad\I18n\Plugin ではなく、 ADmad\I18n\I18nPlugin となります。CakePHP 4.3以降のときと同様です。後方互換性のため、旧スタイルの名前も引き続きサポートされます。

TestSuite

ConsoleIntegrationTestTrait は、 cakephp/cakephp パッケージ全体を必要とせずにコンソールアプリケーションをテストできるように、依存関係とともに console パッケージに移動されました。

Cake\TestSuite\ConsoleIntegrationTestTrait は Cake\Console\TestSuite\ConsoleIntegrationTestTrait に移動しました。

Cake\TestSuite\Constraint\Console\* は Cake\Console\TestSuite\Constraint\* に移動しました。

Cake\TestSuite\Stub\ConsoleInput は Cake\Console\TestSuite\StubConsoleInput に移動しました。

Cake\TestSuite\Stub\ConsoleOutput は Cake\Console\TestSuite\StubConsoleOutput に移動しました。

Cake\TestSuite\Stub\MissingConsoleInputException は Cake\Console\TestSuite\MissingConsoleInputException に移動しました。

ContainerStubTrait は、 cakephp/cakephp パッケージ全体を必要とせずにコンソールアプリケーションをテストできるように、 core パッケージに移動されました。

Cake\TestSuite\ContainerStubTrait は Cake\Core\TestSuite\ContainerStubTrait に移動しました。

HttpClientTrait は、 cakephp/cakephp パッケージ全体を必要とせずにhttpアプリケーションをテストできるように、 http パッケージに移動されました。

Cake\TestSuite\HttpClientTrait は Cake\Http\TestSuite\HttpClientTrait に移動しました。

Behaviorの変更

以下の変更は、どのメソッドのシグネチャも変更しませんが、メソッドのセマンティクスや動作は変更します。

ORM

Table::saveMany() は、まだ 'dirty' で、オリジナルのフィールド値を含むエンティティで Model.afterSaveCommit イベントをトリガーするようになりました。これは Model.afterSaveCommit のイベントペイロードを Table::save() と一致させるためのものです。

Routing

Router::parseRequest() は、クライアントが無効な HTTP メソッドを使用した場合に、 InvalidArgumentException の代わりに BadRequestException を発生させるようになりました。

新機能

Cache

RedisEngine は deleteAsync() と clearBlocking() メソッドをサポートするようになりました。これらのメソッドは、Redis の UNLINK オペレーションを使用して、後で Redis が削除できるようにデータをマークします。

Command

bin/cake routes は、ルートテンプレート内の衝突をハイライトするようになりました。

Command::getDescription() では、カスタムの説明文を設定することができます。コマンド説明文の設定 を参照してください。

Controller

Controller::viewClasses() が追加されました。このメソッドは、コンテントタイプネゴシエーションを行う必要があるコントローラで実装する必要があります。ビュークラスは、content-type ネゴシエーションに参加するために、静的メソッド contentType() を実装する必要があります。

Database

SQLite ドライバが PHP8.1+ でメモリ内共有データベースをサポートするようになりました。

Query::newExpr() の代わりに Query::expr() が追加されました。

QueryExpression::case() ビルダーは、CakeDatabaseTypedResultInterface を実装した then() と else() に渡す式からの型推定をサポートするようになりました。

Error

ErrorTrap と ExceptionTrap が追加されました。これらのクラスは、アプリケーションのための最新のエラー処理システムの基礎を形成しています。詳しくは エラーと例外の処理 をご覧ください。

Http

BaseApplication::handle() は、 $request を常にサービスコンテナに追加するようになりました。

HttpsEnforcerMiddleware に hsts オプションが追加され、 Strict-Transport-Security ヘッダーを設定することができるようになりました。

Mailer

Mailer が autoLayout 設定を受け付けるようになり、 false を設定することで ViewBuilder での自動レイアウトを無効にできるようになりました。

ORM

TreeBehavior に cascadeCallbacks オプションが追加されました。このオプションを有効にすると、 TreeBehavior は find() の結果をイテレートして、個別にレコードを削除するようになります。これにより、ツリーノードを削除する際に ORM コールバックを使用することができるようになります。

Routing

RoutingMiddleware は、マッチした Route インスタンスに "route" リクエスト属性を設定するようになりました。

View

View::contentType() が追加されました。ビューは、content-typeネゴシエーションに参加するために、このメソッドを実装する必要があります。

View::TYPE_MATCH_ALL が追加されました。この特別なcontent-typeにより、content-typeネゴシエーションがマッチしない場合のフォールバックビューを構築することができます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

チュートリアルと例

このセクションで、典型的な CakePHP アプリケーションのデモンストレーションを行い、全てのピースがどう組み合わさっていくかを学ぶことができます。

その他にも、既存のアプリケーションやコンポーネントについて、非公式の CakePHP プラグイン倉庫 CakePackages [https://plugins.cakephp.org/] や、 Bakery [https://bakery.cakephp.org/] を参考にすることもできます。

コンテンツ管理チュートリアル

CMS チュートリアル - データベース作成

CMS チュートリアル - Articles コントローラーの作成

CMS チュートリアル - タグとユーザー

CMS チュートリアル - 認証

CMS Authorization

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コンテンツ管理チュートリアル

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

必要なもの:

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

基礎的な PHP の知識。

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

php -v

最低でも PHP 7.4 (CLI) 以上をインストールしてください。あなたのウェブサーバーの PHP バージョンもまた、 7.4 以上でなければなりません。そして、コマンドラインインターフェイス (CLI) の PHP バージョンと同じバージョンにしてください。

CakePHP の取得

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

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

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

そして、インストールディレクトリーからターミナルに次の行を入力するだけで、現在の作業ディレクトリーの cms ディレクトリーに CakePHP アプリケーションのスケルトンをインストールすることができます。

php composer.phar create-project --prefer-dist cakephp/app:4.* cms

Composer Windows Installer [https://getcomposer.org/Composer-Setup.exe] をダウンロードして実行した場合、インストールディレクトリー (例えば、 C:\wamp\www\dev\cakephp3) からターミナルに次の行を入力してください。

composer self-update && composer create-project --prefer-dist cakephp/app:4.* cms

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

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

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

/cms
  /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 のフォルダー構成 セクションをご覧ください。

このチュートリアルで迷ったら、 GitHub [https://github.com/cakephp/cms-tutorial] で完成した結果を見ることができます。

インストールの確認

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

cd /path/to/our/app

bin/cake server

注釈

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

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

次に、 データベースの構築と最初のモデルの作成をします。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CMS チュートリアル - データベース作成

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

USE cake_cms;

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 articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(255) NOT NULL,
    slug VARCHAR(191) NOT NULL,
    body TEXT,
    published BOOLEAN DEFAULT FALSE,
    created DATETIME,
    modified DATETIME,
    UNIQUE KEY (slug),
    FOREIGN KEY user_key (user_id) REFERENCES users(id)
) CHARSET=utf8mb4;

CREATE TABLE tags (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(191),
    created DATETIME,
    modified DATETIME,
    UNIQUE KEY (title)
) CHARSET=utf8mb4;

CREATE TABLE articles_tags (
    article_id INT NOT NULL,
    tag_id INT NOT NULL,
    PRIMARY KEY (article_id, tag_id),
    FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
    FOREIGN KEY article_key(article_id) REFERENCES articles(id)
);

INSERT INTO users (email, password, created, modified)
VALUES
('cakephp@example.com', 'secret', NOW(), NOW());

INSERT INTO articles (user_id, title, slug, body, published, created, modified)
VALUES
(1, 'First Post', 'first-post', 'This is the first post.', 1, now(), now());

複合主キーを持つ articles_tags テーブルにお気づきでしょうか。CakePHP は、ほぼどこでも複合主キーをサポートしているので、追加の id カラムを必要としない単純なスキーマを持つことができます。

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

データベースの設定

次に、どこにデータベースあるか、そしてどうやってデータベースに接続するかを CakePHP に伝えましょう。あなたのセットアップを適用するために config/app.php ファイルの中の Datasources.default 配列の値を置き換えてください。完全な設定配列の例は、以下のようになります。

<?php
return [
    // 上には他の設定があります
    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            'username' => 'cakephp',
            'password' => 'AngelF00dC4k3~',
            'database' => 'cake_cms',
            'encoding' => 'utf8mb4',
            'timezone' => 'UTC',
            'cacheMetadata' => true,
        ],
    ],
    // 下には他の設定があります
];

一度 config/app.php ファイルを保存して、 'CakePHP is able to connect to the database' が緑色のコック帽であることを確認してください。

注釈

app フォルダーに config/app_local.php がある場合、 app.php の設定を上書きします。

最初のモデルの作成

モデルは、CakePHP アプリケーションの心臓部です。データを読んだり変更することができます。それらは、データ間のリレーションの構築、データの検証、アプリケーションルールの適用をすることができます。モデルは、コントローラーアクションとテンプレートを構築するために必要な基礎を構築します。

CakePHP のモデルは Table と Entity オブジェクトで構成されています。 Table オブジェクトは、指定されたテーブルの中に保存されたエンティティーの集合へのアクセスを提供します。それらは src/Model/Table の中に保存されます。私たちが今から作成するファイルは、 src/Model/Table/ArticlesTable.php に保存されます。完成したファイルは次のようになります。

<?php
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config) : void
    {
        $this->addBehavior('Timestamp');
    }
}

このテーブルの created や modified カラムを自動的に更新する Timestamp ビヘイビアーを追加しました。 Table オブジェクトを ArticlesTable と名付けることで、CakePHP は、命名規則により articles テーブルを使用するモデルであると解釈します。また、CakePHP は、 id カラムがテーブルの主キーであると解釈する規約を使用します。

注釈

もし一致するファイルが src/Model/Table に見つけられなければ、CakePHP は動的にモデルオブジェクトを生成します。これはまた、不意に間違ったファイル名 (例えば、 articlestable.php や ArticleTable.php) をつけると、CakePHP はどの設定も認識できず、代わりに生成されたモデルを使うことになるということも意味します。

また、Articles のための Entity クラスも作成します。エンティティーは、データベースの１つのレコードを表し、データに対して行レベルの振る舞いを提供します。このエンティティーは、 src/Model/Entity/Article.php に保存されます。完成したファイルは、次のようになります。

<?php
// src/Model/Entity/Article.php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
    protected $_accessible = [
        '*' => true,
        'id' => false,
        'slug' => false,
    ];
}

エンティティーは、今はとてもスリムです。そして、 一括代入 (Mass Assignment) によってどのようにプロパティーを変更できるかを制御するプロパティー _accessible をセットアップしました。

このモデルは、今は動きませんが、次は最初のコントローラーとテンプレートを作成し、このモデルとのやりとりができるようにします。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CMS チュートリアル - Articles コントローラーの作成

モデルが作成できたので、記事のコントローラーが必要です。CakePHP のコントローラーは、レスポンスを準備するために、HTTP リクエストを処理し、モデルのメソッドに含まれるビジネスロジックを実行します。この新しいコントローラーは src/Controller ディレクトリーの中で ArticlesController.php と呼ばれるファイルに記述します。基本的なコントローラーの外観は次の通りです。

<?php
// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController
{
}

それでは、コントローラーにアクションを追加しましょう。アクションは、ルートに接続させるコントローラーのメソッドです。例えば、ユーザーが www.example.com/articles/index (www.example.com/articles と同じ) をリクエストした場合、CakePHP は、 ArticlesController の index メソッドを呼びます。このメソッドは、モデル層に問い合わせ、ビューでテンプレートを描画してレスポンスの準備する必要があります。そのアクションのコードは、次のようになります。

<?php
// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController
{
    public function index()
    {
        $this->loadComponent('Paginator');
        $articles = $this->Paginator->paginate($this->Articles->find());
        $this->set(compact('articles'));
    }
}

ArticlesController の index() 関数を定義することで、ユーザーは、 www.example.com/articles/index をリクエストすることで、そこにあるロジックにアクセスできるようになります。同様に、 foobar() という関数を定義した場合、ユーザーはそのメソッドに www.example.com/articles/foobar で、アクセスできます。特定の URL を取得できるように、コントローラーとアクションの名前を付けたいという誘惑に駆られるかもしれません。その誘惑に抗ってください。代わりに、 CakePHP の規約にしたがって、読みやすく意味のあるアクション名を作成してください。そうすればルーティング を使って、あなたが望む URL を、あなたが作成したアクションに接続することができます。

このコントローラーのアクションはとてもシンプルです。これは、命名規則によって自動的にロードされる Articles モデルを使用して、データベースからページ分けされた記事を取得します。次に (これからすぐに作成する) テンプレートの中に記事を渡すために set() を使用します。CakePHP は、コントローラーのアクションが完了した後、自動的にテンプレートを描画します。

記事一覧のテンプレート作成

モデルからデータを取得し、ビューのコンテキストを準備するコントローラーを持っていますので、 index アクションのビューテンプレートを作成しましょう。

CakePHP のビューテンプレートは、アプリケーションのレイアウト内に挿入される表示用の PHP コードです。ここで HTML が作成されますが、 View は JSON、CSV、または PDF などのバイナリファイルの生成もできます。

レイアウトは、ビューを囲む表示用のコードです。レイアウトファイルは、ヘッダー・フッター・ナビゲーション要素のような一般的なサイトの要素が含まれます。アプリケーションは、複数のレイアウトを持つことができ、それらを切り替えることができますが、今回はデフォルトのレイアウトを使用しましょう。

CakePHP のテンプレートファイルは、 templates の中で対応するコントローラーの名前をつけたフォルダーの中に保存されます。今回の場合、 'Articles' という名前のフォルダーを作成する必要があります。あなたのアプリケーションに以下のコードを追加してください。

<!-- File: templates/Articles/index.php -->

<h1>記事一覧</h1>
<table>
    <tr>
        <th>タイトル</th>
        <th>作成日時</th>
    </tr>

    <!-- ここで、$articles クエリーオブジェクトを繰り返して、記事の情報を出力します -->

    <?php foreach ($articles as $article): ?>
    <tr>
        <td>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
    </tr>
    <?php endforeach; ?>
</table>

前のセクションでは、 set() を使って 'articles' 変数をビューに割り当てました。ビューに渡された変数は、上記のコードの中で使用したローカル変数として、ビューテンプレートの中で利用できます。

$this->Html というオブジェクトを使っていることにお気づきでしょうか。これは、 CakePHP の HtmlHelper のインスタンスです。 CakePHP には、リンク、フォーム、ページ制御ボタンの作成などのタスクを簡単に行うための一連のビューヘルパーが付属しています。 ヘルパー について、その章で詳しく学べますが、ここで重要なことは、 link() メソッドは、与えられたリンクテキスト(第１パラメーター) と URL (第２パラメーター) を元に HTML リンクを生成することです。

CakePHP で URL を指定する際、配列や 名前付きルート が推奨されます。これらの構文で、CakePHP が提供するリバースルーティング機能を活用することができます。

この時点で、ブラウザーで http://localhost:8765/articles/index を見ることができるはずです。記事のタイトルとテーブルのリストで正しくフォーマットされたリストビューが表示されます。

view アクションの作成

記事一覧の 'view' リンクの一つをクリックした際、アクションが実装されてないというエラーページが表示されるはずです。今から、それを修正しましょう。

// 既存の src/Controller/ArticlesController.php ファイルに追加

public function view($slug = null)
{
    $article = $this->Articles->findBySlug($slug)->firstOrFail();
    $this->set(compact('article'));
}

これはシンプルなアクションですが、いくつかの強力な CakePHP 機能を使用しています。動的なファインダー である findBySlug() を使用することによりアクションを開始します。このメソッドは、与えられたスラグによって記事を検索する基本的なクエリーを作成することができます。その時、最初のレコードを取得するか NotFoundException を投げるかのいずれかをする firstOrFail() を使います。

このアクションは $slug パラメーターを持ちますが、そのパラメーターはどこから来るのでしょう？ユーザーが /articles/view/first-post をリクエストした際、 値 'first-post' が CakePHP のルーティングとディスパッチレイヤーで $slug に渡されます。新しいアクションを保存してブラウザーをリロードすると、別の CakePHP エラーページが表示され、 view テンプレートが見つからないことがわかります。それを修正しましょう。

view テンプレートの作成

新しい 'view' アクションのビューを作成し、 templates/Articles/view.php に置きましょう。

<!-- File: templates/Articles/view.php -->

<h1><?= h($article->title) ?></h1>
<p><?= h($article->body) ?></p>
<p><small>作成日時: <?= $article->created->format(DATE_RFC850) ?></small></p>
<p><?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?></p>

/articles/index のリンクを使うことによって、動作することを確認できますし、 /articles/view/slug-name のような URL にアクセスすることによって手動で記事をリクエストすることを確認できます。

記事の追加

基本的な読み込みビューを作成できたので、新しい記事を作成できるようにする必要があります。まず、 ArticlesController に add() アクションを作成してください。コントローラーは次のようになります。

// src/Controller/ArticlesController.php

namespace App\Controller;

use App\Controller\AppController;

class ArticlesController extends AppController
{

    public function initialize(): void
    {
        parent::initialize();

        $this->loadComponent('Paginator');
        $this->loadComponent('Flash'); // FlashComponent をインクルード
    }

    public function index()
    {
        $articles = $this->Paginator->paginate($this->Articles->find());
        $this->set(compact('articles'));
    }

    public function view($slug)
    {
        $article = $this->Articles->findBySlug($slug)->firstOrFail();
        $this->set(compact('article'));
    }

    public function add()
    {
        $article = $this->Articles->newEmptyEntity();
        if ($this->request->is('post')) {
            $article = $this->Articles->patchEntity($article, $this->request->getData());

            // user_id の決め打ちは一時的なもので、あとで認証を構築する際に削除されます。
            $article->user_id = 1;

            if ($this->Articles->save($article)) {
                $this->Flash->success(__('Your article has been saved.'));
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('Unable to add your article.'));
        }
        $this->set('article', $article);
    }
}

注釈

フラッシュ コンポーネントを、使用するコントローラーに含める必要があります。また、 AppController に含めてもよいでしょう。

こちらがこの add() アクションの行うことです。

リクエストの HTTP メソッドが POST だった場合、Articles モデルを使用してデータを保存しようとします。

なんらかの理由で保存しなかった場合、ただビューを描画します。これは、ユーザーバリデーションエラーやその他の警告を表示する機会を与えてくれます。

全ての CakePHP のリクエストは、 $this->request を使用してアクセス可能なリクエストオブジェクトを含みます。リクエストオブジェクトは、今受信したリクエストに関する情報を含みます。リクエストが HTTP POST リクエストであることを確認するために Cake\Http\ServerRequest::is() メソッドを使用します。

POST データは、 $this->request->getData() で利用可能です。それがどのような内容かを知りたい場合に pr() 関数や debug() 関数を使って出力することができます。データを保存するために、まず POST データを Article エンティティーに 「変換 (marshal)」します。エンティティーは、以前作成した ArticlesTable を使用して永続化されます。

新しい記事を保存した後、セッションにメッセージをセットするために FlashComponent の success() メソッドを使用します。 success メソッドは PHP のマジックメソッド機能 [https://php.net/manual/ja/language.oop5.overloading.php#object.call] を使用して提供されます。フラッシュメッセージは、リダイレクトした後の次のページ上で表示されます。レイアウトの中に、フラッシュメッセージを表示し、対応するセッション変数をクリアする <?= $this->Flash->render() ?> があります。最後に、保存が完了した後、 Cake\Controller\Controller::redirect を使ってユーザーを記事一覧に戻します。パラメーター ['action' => 'index'] は、例えば ArticlesController の index アクションの場合、 URL /articles に変換します。 API [https://api.cakephp.org] の Cake\Routing\Router::url() 関数を参照すると、様々な CakePHP 関数の URL を指定できる書式を見ることができます。

add テンプレートの作成

こちらが add ビューテンプレートです。

<!-- File: templates/Articles/add.php -->

<h1>記事の追加</h1>
<?php
    echo $this->Form->create($article);
    // 今はユーザーを直接記述
    echo $this->Form->control('user_id', ['type' => 'hidden', 'value' => 1]);
    echo $this->Form->control('title');
    echo $this->Form->control('body', ['rows' => '3']);
    echo $this->Form->button(__('Save Article'));
    echo $this->Form->end();
?>

FormHelper を使うと、 HTML フォームの開始タグを生成できます。こちらが $this->Form->create() が生成する HTML です。

<form method="post" action="/articles/add">

URL オプションなしで create() を呼び出したので、 FormHelper は、フォームを現在のアクションに戻したいと仮定します。

$this->Form->control() メソッドは、同じ名前のフォーム要素を作成するために使われます。１番目のパラメーターは、どのフィールドに対応するかを CakePHP に伝えます。そして、２番目のパラメーターは、さまざまなオプションを指定できます。上記の場合、テキストエリアの行数などです。ここではちょっとした内部情報の確認 (introspection) と規約の使用があります。 control() は、指定されたモデルフィールドにもとづいて異なるフォーム要素を出力し、語形変化 (inflection) を使ってラベルを生成します。オプションを使用して、フォームコントロールのラベル、入力、または、その他の要素をカスタマイズすることができます。 $this->Form->end() の呼び出しでフォームを閉じます。

さて、 templates/Articles/index.php ビューを更新して、新しい「記事の追加」リンクを追加しましょう。 <table> の前に以下の行を追加してください。

<?= $this->Html->link('記事の追加', ['action' => 'add']) ?>

シンプルなスラグ生成の追加

記事を今保存すると、スラグ属性は、作成されておらず、カラムは NOT NULL なので保存に失敗します。スラグの値は、通常、URL セーフなバージョンの記事タイトルです。スラグを作成するために ORM の beforeSave() コールバック が使用できます。

<?php
// src/Model/Table/ArticlesTable.php の中で
namespace App\Model\Table;

use Cake\ORM\Table;
// Text クラス
use Cake\Utility\Text;
// EventInterface クラス
use Cake\Event\EventInterface;

// 次のメソッドを追加してください。

public function beforeSave(EventInterface $event, $entity, $options)
{
    if ($entity->isNew() && !$entity->slug) {
        $sluggedTitle = Text::slug($entity->title);
        // スラグをスキーマで定義されている最大長に調整
        $entity->slug = substr($sluggedTitle, 0, 191);
    }
}

このコードはシンプルで、重複したスラグを考慮していません。しかし、後でそれを修正します。

edit アクションの追加

今のアプリケーションは、記事を保存できますが編集はできません。今から修正しましょう。 ArticlesController に次のアクションを追加してください。

// src/Controller/ArticlesController.php の中で

// 次のメソッドを追加してください。

public function edit($slug)
{
    $article = $this->Articles->findBySlug($slug)->firstOrFail();
    if ($this->request->is(['post', 'put'])) {
        $this->Articles->patchEntity($article, $this->request->getData());
        if ($this->Articles->save($article)) {
            $this->Flash->success(__('Your article has been updated.'));
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error(__('Unable to update your article.'));
    }

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

このアクションは、まずユーザーが既存のレコードにアクセスすることを確保します。 $slug パラメーターの中に渡されなかったり、記事が存在しなかった場合、 NotFoundException が投げられ、 CakePHP の ErrorHandler が、適切なエラーページを描画します。

次に、このアクションはリクエストが POST または PUT いずれかのリクエストかどうかをチェックします。そうであれば、POST/PUT データを元に、 patchEntity() メソッドを使って article エンティティーを更新します。最後に、 save() を呼び出して、適切なフラッシュメッセージを設定し、リダイレクトするか検証エラーを表示します。

edit テンプレートの作成

edit テンプレートは次のようになります。

<!-- File: templates/Articles/edit.php -->

<h1>記事の編集</h1>
<?php
    echo $this->Form->create($article);
    echo $this->Form->control('user_id', ['type' => 'hidden']);
    echo $this->Form->control('title');
    echo $this->Form->control('body', ['rows' => '3']);
    echo $this->Form->button(__('Save Article'));
    echo $this->Form->end();
?>

このテンプレートは、編集フォーム（値が入力された状態）に加えて、必要な検証エラーメッセージを出力します。

特定の記事を編集するためのリンクで index ビューを更新できるようになりました。

<!-- File: templates/Articles/index.php  (編集リンク付き) -->

<h1>記事一覧</h1>
<p><?= $this->Html->link("記事の追加", ['action' => 'add']) ?></p>
<table>
    <tr>
        <th>タイトル</th>
        <th>作成日時</th>
        <th>操作</th>
    </tr>

<!-- ここで、$articles クエリーオブジェクトを繰り返して、記事情報を出力します -->

<?php foreach ($articles as $article): ?>
    <tr>
        <td>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
        <td>
            <?= $this->Html->link('編集', ['action' => 'edit', $article->slug]) ?>
        </td>
    </tr>
<?php endforeach; ?>

</table>

Articles の検証ルールの更新

この時点まで、記事は入力検証が行われていませんでした。バリデーター を使って修正しましょう。

// src/Model/Table/ArticlesTable.php

// この use 文を名前空間宣言のすぐ下に追加して、
// Validator クラスをインポートします。
use Cake\Validation\Validator;

// 次のメソッドを追加してください。
public function validationDefault(Validator $validator): Validator
{
    $validator
        ->notEmptyString('title')
        ->minLength('title', 10)
        ->maxLength('title', 255)

        ->notEmptyString('body')
        ->minLength('body', 10);

    return $validator;
}

validationDefault() メソッドは、 save() メソッドが呼ばれる際のデータの検証方法を CakePHP に伝えます。ここでは、title フィールドと body フィールドの両方が空であってはならず、長さに制約があることを指定しました。

CakePHP の検証エンジンは強力で柔軟性があります。メールアドレス、IP アドレスなどのようにタスクに頻繁に使用されるルール一式を提供し、独自の検証ルールを追加する柔軟性を提供します。その設定の詳細については、 バリデーション のドキュメントを確認してください。

さて、検証ルールが整いましたので、それがどのように動くかを見るためにアプリを使って空のタイトルや本文で記事を追加してみてください。 FormHelper の Cake\View\Helper\FormHelper::control() メソッドを使用して、フォーム要素を作成しているので、検証エラーメッセージが自動的に表示されます。

delete アクションの追加

次に、ユーザーが記事を削除する方法を作ってみましょう。 ArticlesController の中の delete() アクションから始めましょう。

// src/Controller/ArticlesController.php

// 次のメソッドを追加してください。

public function delete($slug)
{
    $this->request->allowMethod(['post', 'delete']);

    $article = $this->Articles->findBySlug($slug)->firstOrFail();
    if ($this->Articles->delete($article)) {
        $this->Flash->success(__('The {0} article has been deleted.', $article->title));
        return $this->redirect(['action' => 'index']);
    }
}

このロジックは $slug で指定された記事を削除し、 $this->Flash->success() を使って /articles にリダイレクトした後に確認メッセージを表示します。ユーザーが GET リクエストを使って記事を削除しようとすると、 allowMethod() は例外をスローします。キャッチされない例外は CakePHP の例外ハンドラによって捕捉され、素晴らしいエラーページが表示されます。アプリケーションで生成する必要のあるさまざまな HTTP エラーを示すために使用できる組み込みの例外 が多数あります。

警告

ウェブクローラーが誤ってすべてのコンテンツを削除する可能性があるため、 GET リクエストを使用してコンテンツを削除することは とても 危険です。それでコントローラーの中で allowMethod() を使ったのです。

私たちはロジックを実行して、別のアクションにリダイレクトしているだけなので、このアクションにはテンプレートはありません。ユーザーが記事を削除できるリンク付きに index テンプレートを更新するといいでしょう。

<!-- File: templates/Articles/index.php  (削除リンク付き) -->

<h1>記事一覧</h1>
<p><?= $this->Html->link("記事の追加", ['action' => 'add']) ?></p>
<table>
    <tr>
        <th>タイトル</th>
        <th>作成日時</th>
        <th>操作</th>
    </tr>

<!-- ここで、$articles クエリーオブジェクトを繰り返して、記事情報を出力します -->

<?php foreach ($articles as $article): ?>
    <tr>
        <td>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
        <td>
            <?= $this->Html->link('編集', ['action' => 'edit', $article->slug]) ?>
            <?= $this->Form->postLink(
                '削除',
                ['action' => 'delete', $article->slug],
                ['confirm' => 'よろしいですか?'])
            ?>
        </td>
    </tr>
<?php endforeach; ?>

</table>

postLink() を使用すると、 JavaScript を使用して記事を削除する POST リクエストを行うリンクが作成されます。

注釈

また、このビューコードは FormHelper を使って記事を削除しようとする前に JavaScript の確認ダイアログを表示します。

基本的な記事管理のセットアップの後は、 タグとユーザーテーブルの基本的な操作を作成します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CMS チュートリアル - タグとユーザー

基本的な記事の作成機能が作成されたので、複数の作成者が CMS で作業できるようにしましょう。前回は、全てのモデル、ビュー、コントローラーを手作業で構築していました。今度は、 Bake コンソールを使ってスケルトンコードを作成します。 Bake は、CakePHP が CRUD アプリケーションのスケルトンをとても効率的に作成するために、規約を活用した強力なコード生成 CLI ツールです。ユーザーコードを構築するために bake を使ってみましょう。

cd /path/to/our/app

bin/cake bake model users
bin/cake bake controller users
bin/cake bake template users

これらの3つのコマンドは次を生成します。

テーブル、エンティティー、フィクスチャーファイル

コントローラー

CRUD テンプレート

生成された各クラスのテストケース

Bake はまた、CakePHP の規約を使用して、モデルが持つ関連付けやバリデーションを推測します。

記事へのタグ付けの追加

複数のユーザーが私たちの小さな CMS にアクセスできるので、私たちのコンテンツを分類する方法があるとうれしいでしょう。ユーザーがコンテンツに自由形式のカテゴリーやラベルを作成するためにタグを使用します。ここでも、アプリケーション用のスケルトンコードを素早く生成するために bake を使います。

# 一度にすべてのコードを生成します。
bin/cake bake all tags

足場になるコードを作成したら、 http://localhost:8765/tags/add に移動してサンプルタグをいくつか作成します。

Tags テーブルが作成されたので、Articles と Tags の関連付けを作成できます。 ArticlesTable の initialize メソッドに以下を追加することで、これを行うことができます。

public function initialize(array $config)
{
    $this->addBehavior('Timestamp');
    $this->belongsToMany('Tags'); // この行を追加
}

テーブルの作成時に CakePHP の規約に従ったので、この関連付けは簡単な定義で動作します。詳しくは、 アソシエーション - モデル同士を繋ぐ をご覧ください。

タグ付けを有効にする記事の更新

アプリケーションにタグが付いたので、ユーザーが記事にタグを付けるようにする必要があります。まず add アクションを次のように更新してください。

// src/Controller/ArticlesController.php の中で

namespace App\Controller;

use App\Controller\AppController;

class ArticlesController extends AppController
{
    public function add()
    {
        $article = $this->Articles->newEmptyEntity();
        if ($this->request->is('post')) {
            $article = $this->Articles->patchEntity($article, $this->request->getData());

            // user_id の決め打ちは一時的なもので、あとで認証を構築する際に削除されます。
            $article->user_id = 1;

            if ($this->Articles->save($article)) {
                $this->Flash->success(__('Your article has been saved.'));
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('Unable to add your article.'));
        }
        // タグのリストを取得
        $tags = $this->Articles->Tags->find('list')->all();

        // ビューコンテキストに tags をセット
        $this->set('tags', $tags);

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

    // 他のアクション
}

追加された行は id => title の連想配列としてタグのリストを読み込みます。この形式で、テンプレートに新しいタグ入力を作成できます。 templates/Articles/add.php のコントロールの PHP ブロックに以下を追加してください。

echo $this->Form->control('tags._ids', ['options' => $tags]);

これは、 $tags 変数を使用してセレクトボックスのオプションを生成する複数選択要素を描画します。次のセクションでタグで記事を見つける機能を追加しますので、タグを持つ新しい記事を2つ作成してください。

また、 edit メソッドを更新して、タグの追加や編集を許可する必要があります。 edit メソッドは次のようになります。

public function edit($slug)
{
    $article = $this->Articles
        ->findBySlug($slug)
        ->contain('Tags') // 関連づけられた Tags を読み込む
        ->firstOrFail();
    if ($this->request->is(['post', 'put'])) {
        $this->Articles->patchEntity($article, $this->request->getData());
        if ($this->Articles->save($article)) {
            $this->Flash->success(__('Your article has been updated.'));
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error(__('Unable to update your article.'));
    }

    // タグのリストを取得
    $tags = $this->Articles->Tags->find('list')->all();

    // ビューコンテキストに tags をセット
    $this->set('tags', $tags);

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

add.php テンプレートに追加した新しいタグの複数選択コントロールを templates/Articles/edit.php テンプレートに追加することを忘れないでください。

タグによる記事の検索

ユーザーがコンテンツを分類すると、使用したタグでそのコンテンツを探したいと思うでしょう。この機能のために、記事をタグで検索するためのルート、コントローラーのアクション、そしてファインダーメソッドを実装します。

理想的には、 http://localhost:8765/articles/tagged/funny/cat/gifs のような URL になります。これにより、「funny」、「cat」、または「gifs」タグを持つすべての記事を見つけることができます。これを実装する前に、新しいルートを追加します。 config/routes.php は次のようになるはずです。

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

$routes->setRouteClass(DashedRoute::class);

$routes->scope('/', function (RouteBuilder $builder) {
    $builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
    $builder->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);

    // タグ付けられたアクションのために追加された新しいルート。
    // 末尾の `*` は、このアクションがパラメーターを渡されることを
    // CakePHP に伝えます。
    $builder->scope('/articles', function (RouteBuilder $builder) {
        $builder->connect('/tagged/*', ['controller' => 'Articles', 'action' => 'tags']);
    });

    $builder->fallbacks();
});

上記は、 /articles/tagged/ パスを ArticlesController::tags() に接続する、新しい「ルート」を定義します。ルートを定義することにより、URL の外観とそれらの実装方法を分離することができます。 http://localhost:8765/articles/tagged にアクセスすると、コントローラーのアクションが存在しないことを知らせる CakePHP の役に立つエラーページが表示されます。その足りないメソッドを今から実装しましょう。 src/Controller/ArticlesController.php の中で、次のように追加してください。

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

    // ArticlesTable を使用してタグ付きの記事を検索します。
    $articles = $this->Articles->find('tagged', [
        'tags' => $tags
    ])
    ->all();

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

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

渡された引数はメソッドのパラメーターとして渡されるので、 PHP の可変引数を使ってアクションを記述することもできます。

public function tags(...$tags)
{
    // ArticlesTable を使用してタグ付きの記事を検索します。
    $articles = $this->Articles->find('tagged', [
        'tags' => $tags
    ])
    ->all();

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

ファインダーメソッドの追加

CakePHP では、コントローラーのアクションをスリムに保ち、アプリケーションのロジックのほとんどをモデルレイヤーに入れたいと思っています。 /articles/tagged URL にアクセスすると、 findTagged() メソッドがまだ実装されていないというエラーが表示されます。 src/Model/Table/ArticlesTable.php の中で次を追加してください。

// この use 文を名前空間宣言のすぐ下に追加して、
// Query クラスをインポートします
use Cake\ORM\Query;

// $query 引数はクエリービルダーのインスタンスです。
// $options 配列には、コントローラーのアクションで find('tagged') に渡した
// "tags" オプションが含まれています。
public function findTagged(Query $query, array $options)
{
    $columns = [
        'Articles.id', 'Articles.user_id', 'Articles.title',
        'Articles.body', 'Articles.published', 'Articles.created',
        'Articles.slug',
    ];

    $query = $query
        ->select($columns)
        ->distinct($columns);

    if (empty($options['tags'])) {
        // タグが指定されていない場合は、タグのない記事を検索します。
        $query->leftJoinWith('Tags')
            ->where(['Tags.title IS' => null]);
    } else {
        // 提供されたタグが1つ以上ある記事を検索します。
        $query->innerJoinWith('Tags')
            ->where(['Tags.title IN' => $options['tags']]);
    }

    return $query->group(['Articles.id']);
}

私たちは カスタムファインダーメソッド を実装しました。これは CakePHP の非常に強力な概念で、再利用可能なクエリーをパッケージ化することができます。ファインダーメソッドは常に クエリービルダー オブジェクトと options 配列をパラメーターとして取得します。ファインダーはクエリーを操作し、必須条件や抽出条件を追加できます。完了したら、ファインダーメソッドは変更されたクエリーオブジェクトを返す必要があります。上記のファインダーでは、 distinct() と leftJoin() メソッドを利用して、 '一致する' タグを持つ記事を見つけることができます。

ビューの作成

/articles/tagged URL にもう一度アクセスすると、CakePHP は新しいエラーを表示して、ビューファイルが作成されていないことを知らせます。次は、 tags() アクションのビューファイルを作成しましょう。 templates/Articles/tags.php の中に次の内容を入れてください。

<h1>
    Articles tagged with
    <?= $this->Text->toList(h($tags), 'or') ?>
</h1>

<section>
<?php foreach ($articles as $article): ?>
    <article>
        <!-- リンクの作成に HtmlHelper を使用 -->
        <h4><?= $this->Html->link(
            $article->title,
            ['controller' => 'Articles', 'action' => 'view', $article->slug]
        ) ?></h4>
        <span><?= h($article->created) ?></span>
    </article>
<?php endforeach; ?>
</section>

上記のコードの中で、 ビュー出力を支援するために Html ヘルパーと Text ヘルパーを使用します。また、HTML エンコード出力のために h ショートカット関数を使用します。HTML インジェクションの問題を防ぐためにデータを出力するときは、常に h() を使うことを忘れないでください。

先ほど作成した tags.php ファイルは、ビューテンプレートファイルの CakePHP 規約に従います。コントローラーのアクション名を小文字とアンダースコアーに変えたものをテンプレートに使用することが規約です。

ビューテンプレートに $tags と $articles 変数を使うことができることに気付くかもしれません。コントローラーで set() メソッドを使う際、ビューに送る特定の変数を設定します。ビューは、渡されたすべての変数をテンプレートスコープでローカル変数として使用可能にします。

以上で /articles/tagged/funny の URL にアクセスして、「funny」とタグ付けされたすべての記事を見ることができます。

タグ付け体験の改善

現在、新しいタグを追加するのは面倒なプロセスです。作成者は、使用したいタグをすべて事前に作成する必要があります。カンマ区切りのテキストフィールドを使用してタグ選択 UI を改善することができます。これにより、ユーザーにとってより良い体験を提供し、ORM でさらに優れた機能を使用することができます。

計算フィールドの追加

エンティティーの書式設定されたタグに簡単にアクセスできるようにするため、仮想/計算フィールドをエンティティーに追加できます。 src/Model/Entity/Article.php の中で次を追加してください。

// この use 文を名前空間宣言のすぐ下に追加して、
// Collection クラスをインポートします
use Cake\Collection\Collection;

// アクセス可能なプロパティに `tag_string` を含めるよう更新します
protected $_accessible = [
    // その他のフィールドも追加可能
    'tag_string' => true
];

protected function _getTagString()
{
    if (isset($this->_fields['tag_string'])) {
        return $this->_fields['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, ', ');
}

これにより $article->tag_string の計算されたプロパティにアクセスできます。後でコントロールでこのプロパティーを使用します。

ビューの更新

エンティティーが更新されると、タグの新しいコントロールを追加できます。 templates/Articles/add.php と templates/Articles/edit.php の中で、既存の tags._ids コントロールを次のものに置き換えてください。

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

また、記事のビューテンプレートを更新する必要があります。 /templates/Articles/view.php に以下の行を追加してください。

<!-- File: templates/Articles/view.php -->

<h1><?= h($article->title) ?></h1>
<p><?= h($article->body) ?></p>
// 以下の行を追加
<p><b>Tags:</b> <?= h($article->tag_string) ?></p>

また、既存のタグを取得できるようにviewメソッドを更新する必要があります。

// src/Controller/ArticlesController.phpの中で

public function view($slug = null)
{
   // Update retrieving tags with contain()
   $article = $this->Articles
        ->findBySlug($slug)
        ->contain('Tags')
        ->firstOrFail();
    $this->set(compact('article'));
}

タグ文字列の永続化

既存のタグを文字列として表示できるようになったので、そのデータも保存したいと考えています。 tag_string をアクセス可能とマークしたので、ORM はそのデータをリクエストからエンティティーにコピーします。beforeSave() フックメソッドを使用してタグ文字列を解析し、関連するエンティティーを検索/構築することができます。 src/Model/Table/ArticlesTable.php に次を追加してください。

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

    // 他のコード
}

protected function _buildTags($tagString)
{
    // タグをトリミング
    $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 がどれほど強力であるかを紹介するのに役立ちます。 コレクション のメソッドを使用してクエリー結果を操作したり、エンティティーを簡単に作成したりするシナリオを扱うことができます。

タグ文字列の自動入力

記事を読み込む際に、関連するタグをロードする仕組みが必要です。 src/Model/Table/ArticlesTable.php を変更してください。

public function initialize(array $config): void
{
    $this->addBehavior('Timestamp');
    // Change this line
    $this->belongsToMany('Tags', [
        'joinTable' => 'articles_tags',
        'dependent' => true
    ]);
}

これにより、Articlesテーブルモデルに対して、Tagsテーブルが関連付けられていることが通知されます。'dependent'オプションはarticlesのレコードが削除された時、関連するtagを削除するよう指示します。

最後に src/Controller/ArticlesController.php の findBySlug メソッドを更新します。

public function edit($slug)
{
    // Update this line
    $article = $this->Articles
        ->findBySlug($slug)
        ->contain('Tags')
        ->firstOrFail();
...
}

public function view($slug = null)
{
    // Update this line
    $article = $this->Articles
        ->findBySlug($slug)
        ->contain('Tags')
        ->firstOrFail();
    $this->set(compact('article'));
}

contain() メソッドはarticleが読み込まれた時、 ArticlesTable オブジェクトに関連したtagsを読み込むよう指示します。これで、Articleエンティティに対してtag_stringが呼び出されると、文字列を作成するためのデータが存在します。

次は 認証 を追加しましょう。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CMS チュートリアル - 認証

CMS にはユーザーがいますので、 cakephp/authentication [https://book.cakephp.org/authentication/2] プラグインを使用してログインできるようにします。まず、パスワードがデータベースに安全に保存されるようにします。次に、ログインとログアウト機能を提供し、新規ユーザーを登録できるようにします。

認証プラグインのインストール

composer を使用して認証プラグインをインストール

composer require "cakephp/authentication:^2.0"

パスワードハッシュ化の追加

データベースの users テーブル用に Controller, Table, Entity および テンプレートを作成する必要があります。 ArticlesController と同様に、手動、もしくは bake シェルを使用してこれらのクラスを生成することができます。

bin/cake bake all users

もしこの時点でユーザーを作成・更新していたとしたら、パスワードが平文で保存されることに気付いたかもしれません。これは、セキュリティの観点から本当に悪いことですので、修正しましょう。

これはまた、CakePHP のモデル層について話す良い時期です。CakePHP では、オブジェクトのコレクションに対して操作するメソッドと、単一のオブジェクトを別のクラスに分けています。エンティティーのコレクションに対して操作するメソッドは Table クラスにあり、一方、単一のレコードに属する機能は Entity クラスにあります。

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

<?php
namespace App\Model\Entity;

use Authentication\PasswordHasher\DefaultPasswordHasher; // この行を追加
use Cake\ORM\Entity;

class User extends Entity
{
    // bake で生成されたコード

    // このメソッドを追加
    protected function _setPassword(string $password) : ?string
    {
        if (strlen($password) > 0) {
            return (new DefaultPasswordHasher())->hash($password);
        }
    }
}

次に、ブラウザで http://localhost:8765/users にアクセスし、ユーザー一覧を表示します。ローカルサーバーを実行する必要があることに注意してください。 bin/cake server を使用して standalone PHP server を起動します。

Installation で作成したデフォルトユーザーを編集する事が出来ます。ユーザーのパスワードを変更すると、一覧画面または詳細画面に元の値の代わりにハッシュ化されたパスワードが表示されます。

CakePHP はデフォルトでは bcrypt [https://codahale.com/how-to-safely-store-a-password/] でパスワードをハッシュ化します。

セキュリティ基準を高く保つために、すべての新しいアプリケーションに bcrypt を使用することを推奨します。 bcrypt は PHPの推奨パスワードハッシュアルゴリズム [https://www.php.net/manual/en/function.password-hash.php] です。

注釈

少なくとも1つのユーザーアカウントのハッシュ化されたパスワードを今すぐ作成してください！次のステップで必要になります。パスワードを更新すると、パスワードカラムに長い文字列が保存されます。注意: 同じパスワードを2回保存した場合でも、bcrypt は異なるハッシュを生成します。

ログインの追加

次に、認証プラグインを設定します。プラグインは、3つの異なるクラスを使用して認証プロセスを処理します。

Application は Authentication Middleware を使用して AuthenticationService を提供し、 credentials のチェック方法と credentials を見つける場所を定義する為のすべての設定を保持します。

AuthenticationService は認証プロセスを設定できるユーティリティクラスになります。

AuthenticationMiddleware はミドルウェアキューの一部として実行され、コントローラーがフレームワークによって処理される前に、credentials を選り出して処理し、ユーザーが認証されているかどうかチェックします。

覚えているかもしれませんが、以前は、これらすべてのステップを処理するために AuthComponent を使用していました。現在では、ロジックは特定のクラスに分割され、認証プロセスはコントローラーレイヤーの前に行われます。ユーザーが(指定した構成に基づいて)認証されたかどうか確認し、ユーザーと認証結果をリクエストに挿入し、参照できるようにします。

src/Application.php に次の imports を追加します:

// src/Application.php に次の imports を追加します
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Routing\Router;
use Psr\Http\Message\ServerRequestInterface;

次に Application クラスに認証インターフェースを実装します:

// in src/Application.php
class Application extends BaseApplication
    implements AuthenticationServiceProviderInterface
{

次に以下を追加します:

// src/Application.php
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $middlewareQueue
        // ... 前に追加された他のミドルウェア
        ->add(new RoutingMiddleware($this))
        // RoutingMiddleware の後に認証を追加
        ->add(new AuthenticationMiddleware($this));

    return $middlewareQueue;
}

public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $authenticationService = new AuthenticationService([
        'unauthenticatedRedirect' => Router::url('/users/login'),
        'queryParam' => 'redirect',
    ]);

    // identifiers を読み込み、email と password のフィールドを確認します
    $authenticationService->loadIdentifier('Authentication.Password', [
        'fields' => [
            'username' => 'email',
            'password' => 'password',
        ]
    ]);

    //  authenticatorsをロードしたら, 最初にセッションが必要です
    $authenticationService->loadAuthenticator('Authentication.Session');
    // 入力した email と password をチェックする為のフォームデータを設定します
    $authenticationService->loadAuthenticator('Authentication.Form', [
        'fields' => [
            'username' => 'email',
            'password' => 'password',
        ],
        'loginUrl' => Router::url('/users/login'),
    ]);

    return $authenticationService;
}

AppController クラスに次のコードを追加します:

// src/Controller/AppController.php
public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('RequestHandler');
    $this->loadComponent('Flash');

    // 認証結果を確認し、サイトのロックを行うために次の行を追加します
    $this->loadComponent('Authentication.Authentication');

リクエスト毎に AuthenticationMiddleware はリクエストされたセッションを検査し、認証されたユーザーを探します。 /users/login ページを読み込む場合、 POSTされたフォームデータ(存在する場合)も検査し、credentials を抽出します。デフォルトでは credentials はリクエストデータの username and password から抽出されます。認証結果は、authentication という名前でリクエスト属性に挿入されます。コントローラーアクションで $this->request->getAttribute('authentication') を使用し、いつでも結果を調べることができます。 AuthenticationComponent がリクエストごとに結果をチェックしているため、すべてのページが制限されます。認証されたユーザーが見つからない場合、ユーザーを /users/login ページにリダイレクトします。注意: この時点では、ログインページがまだないため、サイトは機能しません。サイトにアクセスすると "infinite redirect loop" が発生するので、修正しましょう。

UsersController に次のコードを追加します:

public function beforeFilter(\Cake\Event\EventInterface $event)
{
    parent::beforeFilter($event);
    // 認証を必要としないログインアクションを構成し、
    // 無限リダイレクトループの問題を防ぎます
    $this->Authentication->addUnauthenticatedActions(['login']);
}

public function login()
{
    $this->request->allowMethod(['get', 'post']);
    $result = $this->Authentication->getResult();
    // POST, GET を問わず、ユーザーがログインしている場合はリダイレクトします
    if ($result && $result->isValid()) {
        // redirect to /articles after login success
        $redirect = $this->request->getQuery('redirect', [
            'controller' => 'Articles',
            'action' => 'index',
        ]);

        return $this->redirect($redirect);
    }
    // ユーザーが submit 後、認証失敗した場合は、エラーを表示します
    if ($this->request->is('post') && !$result->isValid()) {
        $this->Flash->error(__('Invalid username or password'));
    }
}

ログインアクション用のテンプレートロジックを追加します:

<!-- in /templates/Users/login.php -->
<div class="users form">
    <?= $this->Flash->render() ?>
    <h3>Login</h3>
    <?= $this->Form->create() ?>
    <fieldset>
        <legend><?= __('Please enter your username and password') ?></legend>
        <?= $this->Form->control('email', ['required' => true]) ?>
        <?= $this->Form->control('password', ['required' => true]) ?>
    </fieldset>
    <?= $this->Form->submit(__('Login')); ?>
    <?= $this->Form->end() ?>

    <?= $this->Html->link("Add User", ['action' => 'add']) ?>
</div>

これで、ログインページでアプリケーションに正しくログインできるようになります。サイトの任意ページをリクエストしてテストします。 /users/login ページにリダイレクトしたら、ユーザー作成時に入力した電子メールとパスワードを入力します。ログイン後、正常にリダイレクトされるはずです。

さらにいくつかの詳細をアプリケーションに追加する必要があります。ログインせずにすべての view 及び index ページにアクセスできるようにするため、特定の設定を AppController に追加します。:

// in src/Controller/AppController.php
public function beforeFilter(\Cake\Event\EventInterface $event)
{
    parent::beforeFilter($event);
    // アプリケーション内のすべてのコントローラーの index と view アクションをパブリックにし、認証チェックをスキップします
    $this->Authentication->addUnauthenticatedActions(['index', 'view']);
}

注釈

ハッシュ化されたパスワードを持つユーザーがまだいない場合、 AppController の loadComponent('Authentication.Authentication') 行をコメントアウトし、 /users/add に移動して、email と password を入力する新規ユーザーを作成します。一時的にコメントした行のコメントを外してください！

ログインする前に /articles/add にアクセスして試してください! このアクションは許可されていないため、ログインページにリダイレクトされます。ログインに成功すると、CakePHP は自動的に /articles/add にリダイレクトします。

ログアウト

logout アクションを UsersController に追加します。:

// in src/Controller/UsersController.php
public function logout()
{
    $result = $this->Authentication->getResult();
    // POST, GET を問わず、ユーザーがログインしている場合はリダイレクトします
    if ($result && $result->isValid()) {
        $this->Authentication->logout();
        return $this->redirect(['controller' => 'Users', 'action' => 'login']);
    }
}

これで /users/logout にアクセスしてログアウトできます。その後、ログインページに移動します。

ユーザー登録の有効化

ログインせずに /users/add にアクセスしようとすると、ログインページにリダイレクトされます。ユーザーがアプリケーションにサインアップできるようにしたいので、これを修正する必要があります。 UsersController の次の行を修正します:

// UsersController の beforeFilter メソッドに追加します
$this->Authentication->addUnauthenticatedActions(['login', 'add']);

上記は AuthenticationComponent に UsersController の add() アクションが認証または認可を必要と しない ことを伝えます。時間をかけて Users/add.php をクリーンアップし誤解を招くリンクを削除してもよいですし、もしくは次のセクションに進んでください。このチュートリアルでは、ユーザーによる編集、表示、一覧表示は行いませんが、これは自分自身で行うことができる演習です。

ユーザーがログインできるようになったので、 認可ポリシーの適用 でユーザーが作成した記事のみを編集できるように制限します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CMS Authorization

TODO

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

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

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

必要なもの:

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

基礎的な PHP の知識。

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

php -v

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

CakePHP の取得

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

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

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

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

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

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

composer self-update && composer create-project --prefer-dist cakephp/app:4.* 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.
];

１度 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 に行ってください。

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

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

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

これはまた、CakePHP のモデル層について説明する良い機会です。CakePHP では、オブジェクトの集合と、異なるクラスの単一オブジェクトを操作する方法を分けてます。エンティティーの集合は、 Table クラス内に格納され、１つのレコードに属する機能は、 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 [https://codahale.com/how-to-safely-store-a-password/] を使ってパスワードをハッシュ化します。既存のデータベースが動作している場合、 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() アクションのために作りましょう。 templates/Bookmarks/tags.php に以下の内容を追加します。

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

<section>
<?php foreach ($bookmarks as $bookmark): ?>
    <article>
        <!-- リンクを作成するために HtmlHelper を使用 -->
        <h4><?= $this->Html->link($bookmark->title, $bookmark->url) ?></h4>
        <small><?= h($bookmark->url) ?></small>

        <!-- テキストを整形するために TextHelper を使用 -->
        <?= $this->Text->autoParagraph(h($bookmark->description)) ?>
    </article>
<?php endforeach; ?>
</section>

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

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

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

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

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

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

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ブックマークチュートリアル パート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']);
    }
}

これで、 Flash と Auth のコンポーネントを読み込むと 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('あなたのユーザー名またはパスワードが不正です。');
    }
}

さらに templates/Users/login.php に以下のように追記します。

<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();
    // 許可するアクション一覧に 'add' アクションを追加
    $this->Auth->allow(['logout', 'add']);
}

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

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

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

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

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

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

これで、自分のものではないブックマークを表示または編集、削除しようとすると、元のページにリダイレクトされるはずです。もし、エラーメッセージが表示されないなら、レイアウトに以下を追加してください。

// templates/layout/default.php の中で
<?= $this->Flash->render() ?>

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

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

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

ブックマークを追加するときにユーザーを選べる

ブックマークを編集するときにユーザーを選べる

一覧ページに他のユーザーのブックマークが表示される

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

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.php の edit() アクションを以下のようにします。

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.php の index() アクションを以下のようにします。

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->_fields['tag_string'])) {
        return $this->_fields['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 で $_accessible に tag_string をこのように追加してください。

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

ビューを修正する

エンティティーを修正するとタグ用の新しいインプットを追加することができます。 templates/Bookmarks/add.php と templates/Bookmarks/edit.php のすでにある 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 の利用 を熟読してください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ブログチュートリアル

このチュートリアルでは、シンプルなブログアプリケーションを作成します。CakePHP を取得してインストールし、データベースの設定を行い、ブログの投稿記事の一覧表示 (list)、追加 (add)、編集 (edit)、削除 (delete) などのアプリケーションロジックを作成します。

必要なもの:

動作しているウェブサーバー。Apache を使っているという前提で書いてありますが、他の種類のサーバーを使用する場合でも、ほぼ同じにいけるはずです。サーバーの設定を少し変える必要があるかもしれませんが、たいていの人は、そのままの設定で CakePHP を動作させることが可能です。PHP 7.4 以上が動作していること、そして PHP で mbstring と intl が有効になっていることを確認してください。

データベースサーバー。このチュートリアルでは MySQL を使用します。データベースを作成できる程度の SQL の知識が必要です。その先は CakePHP が面倒をみてくれます。MySQL を使用するので、PHP で pdo_mysql が有効になっていることを確認してください。

PHP の基本的な知識。

それでは、はじめましょう！

CakePHP の取得

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

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

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

そして、CakePHP アプリケーションのスケルトンを使用したいディレクトリーにインストールするために、インストールディレクトリーからターミナルに以下の行をシンプルにタイプしてください。この例では、"blog" を使用しますが、他のものに自由に変更できます。

php composer.phar create-project --prefer-dist cakephp/app:4.* blog

Composer をグローバルにすでに設定している場合は、以下のようにタイプすることもできます。

composer self-update && composer create-project --prefer-dist cakephp/app:4.* blog

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

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

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

/cake_install
    /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 のフォルダー構成 セクションをご覧ください。

tmp と logs ディレクトリーのパーミッション

ウェブサーバーが書き込みを行うために、 tmp と logs ディレクトリーに適切なパーミッションの設定をする必要があります。インストールに Composer を用いた場合はこれはすでに終わっており、 "Permissions set on <folder>" というメッセージで確認できます。もしエラーメッセージが出ている場合、または手動で設定したい場合は、ウェブサーバーを動作させているユーザーを見つけるために <?= `whoami`; ?> をウェブサーバーで実行するのが最も良いでしょう。そしてそのユーザーにこれらの2つのディレクトリーの所有権を変更します。実行するコマンドは (*nixで) このようになります。

chown -R www-data tmp
chown -R www-data logs

何らかの理由で CakePHP がそのディレクトリーに書き込めない場合は、プロダクションモードでない限り警告で通知されます。

推奨はしませんが、もしこの方法でウェブサーバーに権限の設定ができない場合、以下のコマンドを実行することで書き込み権限をフォルダーに与えることができます。

chmod -R 777 tmp
chmod -R 777 logs

データベースの作成

次に、ブログで使用する基礎的なMySQLデータベースをセットアップしましょう。データベースをまだ作成していないのであれば、このチュートリアル用に好きな名前で、例えば cake_blog のような名前で空のデータベースを作成しておいてください。このページでは、投稿記事を保存するためのテーブルをひとつ作成します。そしてテスト用に、いくつかの記事も投入します。次の SQL をデータベースで実行してください。

# まず、articles テーブルを作成します
CREATE TABLE articles (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(50),
    body TEXT,
    created DATETIME DEFAULT NULL,
    modified DATETIME DEFAULT NULL
);

# それから、テスト用に記事をいくつか入れておきます
INSERT INTO articles (title,body,created)
    VALUES ('タイトル', 'これは、記事の本文です。', NOW());
INSERT INTO articles (title,body,created)
    VALUES ('またタイトル', 'そこに本文が続きます。', NOW());
INSERT INTO articles (title,body,created)
    VALUES ('タイトルの逆襲', 'こりゃ本当にわくわくする！うそ。', NOW());

テーブル名とフィールド名は適当に選んだわけではありません。CakePHP のデータベース命名規約とクラスの命名規約に従っておくと、（どちらも、 CakePHP の規約 の中で説明されています）たくさんの機能を自由に使うことができ、設定作業をする必要がなくなります。 CakePHP はレガシーなデータベーススキーマに対応できるくらい十分に柔軟ですが、規約に従うことで、時間を節約できます。

詳細は、 CakePHP の規約 を参照してください。簡単に言うと、 'articles' というテーブル名にしておけば、自動的に Articles モデルが呼び出され、'modified' と 'created' というフィールドがあると、自動的に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' => 'cake_blog',
            'password' => 'AngelF00dC4k3~',
            'database' => 'cake_blog',
            'encoding' => 'utf8',
            'timezone' => 'UTC'
        ],
    ],
    // More configuration below.
];

config/app.php を保存すると、ブラウザーでウェルカムページが表示されるはずです。データベースへの接続ファイルがみつかり、CakePHPがデータベースにきちんと接続されていることをも示しています。

注釈

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

追加の設定

設定できる項目があといくつかあります。たいていの開発者はこれらの詳細なリストも仕上げますが、このチュートリアルに必要不可欠、というわけではありません。ひとつは、セキュリティハッシュ用のカスタム文字列(「salt」ともいう)です。

セキュリティ用の salt は、ハッシュの生成に用いられます。 config/app.php を編集し、デフォルトの Security.salt の値を変更してください。この値は、ランダムで長い文字列にします。そうすることで推測がより困難になります。

'Security' => [
   'salt' => '長いもので、多くの異なる値を含むもの。',
],

mod_rewrite について

新しいユーザーは mod_rewrite でつまずくことがよくあります。例えば CakePHP のウェルカムページが少しおかしくなったりします (画像が表示されない、CSS が効いていない)。これはおそらく、システム上の mod_rewrite が機能していないということです。 URL Rewriting セクションを参照して、URL リライティングが有効になるように設定してください。

はじめての CakePHP アプリケーションを構築しはじめるには、続けてブログチュートリアル - パート2 をご覧ください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ブログチュートリアル - パート2

Article モデルの作成

モデルクラスは、CakePHP アプリケーションの基本中の基本 (bread and butter) です。 CakePHP のモデルを作成することで、データベースとやりとりできるようになり、表示 (view)、追加 (add)、編集 (edit)、削除 (delete) といった操作に必要な土台を手に入れることになります。

CakePHP のモデルクラスのファイルは、 Table オブジェクトと Entity オブジェクトに分離して存在します。 Table オブジェクトは、特定のテーブルに格納されているエンティティーの集合にアクセスし、 src/Model/Table の中に行きます。 src/Model/Table/ArticlesTable.php というファイルを作って保存します。完成したファイルは次のようになります。

// src/Model/Table/ArticlesTable.php

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Timestamp');
    }
}

命名規約は、CakePHP では非常に大切です。 Table オブジェクトを ArticlesTable という名前にすることで、CakePHP は自動的に、この Table オブジェクトは ArticlesController で使用されるのだろうと解釈します。また、 articles という名前のデータベーステーブルと結びつけられます。

注釈

もし一致するファイルが src/Model/Table に見つけられなければ、CakePHP は動的にモデルオブジェクトを生成します。これはまた、不意に間違ったファイル名 (例えば、 articlestable.php や ArticleTable.php) をつけると、CakePHP はどの設定も認識できず、代わりに生成されたモデルを使うことになるということも意味します。

コールバック、バリデーションといったモデルの詳細については、マニュアルの データベースアクセス & ORM の章を参照してください。

注釈

もし、すでに ブログチュートリアルのパート1 を完了していて articles テーブルをブログ用のデータベースに作成してあれば、 CakePHP の bake コンソールを活用して ArticlesTable モデルを作成することができます。

bin/cake bake model Articles

bake とコード生成についての詳細は、 Bake でコード生成 を参照してください。

Articles コントローラーの作成

次に、投稿記事 (articles) に対するコントローラーを作成します。コントローラーとは、投稿記事とのすべてのやりとりが発生するところです。簡単に言うと、それはモデルとのビジネスロジックを含み、投稿記事に関連する作業を行う場所です。この新しいコントローラーは ArticlesController.php という名前で、 src/Controller ディレクトリーの中に配置します。基本的なコントローラーは次のようになります。

// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController
{
}

では、コントローラーにひとつのアクションを追加してみましょう。アクションは、アプリケーションの中のひとつの関数か、インターフェイスをあらわしています。例えば、ユーザーが www.example.com/articles/index (www.example.com/articles/ と同じです) をリクエストした場合、投稿記事の一覧が表示されると期待するでしょう。このアクションのコードは次のようになります。

// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController
{
    public function index()
    {
        $articles = $this->Articles->find()->all();
        $this->set(compact('articles'));
    }
}

ArticlesController の中に index() という関数を定義することによって、ユーザーは、 www.example.com/articles/index というリクエストで、そのロジックにアクセスできるようになります。同様に、 foobar() という関数を定義すると、ユーザーは、www.example.com/articles/foobar でアクセスできるようになります。

警告

ある URL にさせたいために、コントローラー名とアクション名をそれに合わせて独自に命名したくなるかもしれませんが、その誘惑に抵抗してください。CakePHP の規約（大文字化、複数形の名前など）に従って、読みやすく、理解しやすいアクション名を付けるようにしましょう。あとで、ルーティング という機能を使って、URL とコードを結びつけることができます。

アクションの中にあるひとつの命令が、 set() を使って、コントローラーからビュー (次に作成します) にデータを渡しています。この行は、ArticlesTable オブジェクトの find('all') メソッドから返ってきた値で、「articles」というビューの変数を設定します。

注釈

もし、すでに:doc:ブログチュートリアルのパート1 </tutorials-and-examples/blog/blog> を完了していて articles テーブルをブログ用のデータベースに作成してあれば、 CakePHP の bake コンソールを活用して ArticlesController クラスを作成することができます。

bin/cake bake controller Articles

bake とコード生成についての詳細は、 Bake でコード生成 を参照してください。

CakePHP のコントローラーに関する詳細は、 コントローラー の章をチェックしてください。

Article ビューの作成

現在、モデルにはデータが入り、コントローラーにはアプリケーションロジックと流れが定義されています。今度は、作成した index アクション用のビューを作成しましょう。

CakePHP のビュー (view) は、アプリケーションのレイアウト (layout) の内側にはめこまれる、データ表示用の断片部品です。たいていのアプリケーションでは、PHP のコードが含まれる HTML になりますが、XML、CSV、バイナリのデータにもなりえます。

レイアウト (layout) は、ビューを囲む表示用のコードで、独自に定義したり、切り替えたりすることも可能ですが、今のところは、デフォルト (default) のものを使用することにしましょう。

一つ前のセクションの set() メソッドによって、ビューから「articles」変数が使えるように割り当てたのを覚えていますか。これはクエリーオブジェクトのコレクションを foreach イテレーションを呼び出した状態でビューに伝えます。

CakePHP のビューファイルは、 src/Template の中の、コントローラー名に対応するフォルダーの中に保存されています (この場合は、「Articles」というフォルダーを作成します)。この投稿記事データをテーブル表示するには、ビューのコードは次のようなものになります。

<!-- File: templates/Articles/index.php -->

<h1>Blog articles</h1>
<table>
    <tr>
        <th>Id</th>
        <th>Title</th>
        <th>Created</th>
    </tr>

    <!-- ここから、$articles のクエリーオブジェクトをループして、投稿記事の情報を表示 -->

    <?php foreach ($articles as $article): ?>
    <tr>
        <td><?= $article->id ?></td>
        <td>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
    </tr>
    <?php endforeach; ?>
</table>

シンプルなものであることがお分かりいただけるかと思います。

$this->Html というオブジェクトを使っていることに気づいたかもしれません。これは、CakePHP の Cake\View\Helper\HtmlHelper クラスのインスタンスです。 CakePHP には一連のビューヘルパーがあり、リンクの作成、フォームの出力などをすぐに使えます。使い方の詳細については、 ヘルパー を参照してください。ここで重要なのは、 link() メソッドが、指定されたタイトル（最初のパラメーター）と URL (二つ目のパラメーター) で HTML リンクを生成する、ということです。

CakePHP 内で URL を指定する場合、配列フォーマットの使用が推奨されます。これはルーティングの章で詳しく説明されます。URL に配列フォーマットを用いることによって、 CakePHP のリバースルーティング機能を活用することができます。また、 /コントローラー/アクション/パラメーター1/パラメーター2 という形のアプリケーションの基本パスに対する相対パスを単に書くこともできます。 named routes もご参照ください。

この時点で、ブラウザーから http://www.example.com/articles/index を開いてみてください。タイトルと投稿内容のテーブル一覧がまとめられているビューが表示されるはずです。

ビューの中のリンク (投稿記事のタイトルから /articles/view/どれかのID番号``いう表示を出します。もしそういう表示が出ない場合には、何かおかしくなってしまったか、もうすでにあなたがその定義作業をしてしまったから（仕事がハヤイ！）か、のどちらかです。そうでないなら、これから ``ArticlesController の中に作ってみましょう。

// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController
{

    public function index()
    {
         $this->set('articles', $this->Articles->find('all'));
    }

    public function view($id = null)
    {
        $article = $this->Articles->get($id);
        $this->set(compact('article'));
    }
}

set() の呼び出しはもう知っていますね。 find('all') の代わりに、 get() を使っていることに注目してください。今回は、ひとつの投稿記事の情報しか必要としないからです。

ビューのアクションが、ひとつのパラメーターを取っていることに注意してください。それは、これから表示する投稿記事のID番号です。このパラメーターは、リクエストされた URL を通して渡されます。ユーザーが、 /articles/view/3 とリクエストすると、「3」という値が $id として渡されます。

ユーザーが実在するレコードにアクセスすることを保証するために少しだけエラーチェックを行います。 Articles テーブルに対して get() を用いるとき、存在するレコードにアクセスしています。もしリクエスト記事がデータベースに存在しない場合、もしくは id が false の場合、 get() 関数は NotFoundException を送出します。

では、新しい「view」アクション用のビューを作って、 templates/Articles/view.php というファイルで保存しましょう。

<!-- File: templates/Articles/view.php -->

<h1><?= h($article->title) ?></h1>
<p><?= h($article->body) ?></p>
<p><small>Created: <?= $article->created->format(DATE_RFC850) ?></small></p>

/articles/index の中にあるリンクをクリックしたり、手動で、 /articles/view/1 にアクセスしたりして、動作することを確認してください。

記事の追加

データベースを読み、記事を表示できるようになりました。今度は、新しい投稿ができるようにしてみましょう。

まず、 ArticlesController の中に、 add() アクションを作ります。

// src/Controller/ArticlesController.php

namespace App\Controller;

use App\Controller\AppController;

class ArticlesController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();

        $this->loadComponent('Flash'); // Flashコンポーネントを含める
    }

    public function index()
    {
        $this->set('articles', $this->Articles->find()->all());
    }

    public function view($id)
    {
        $article = $this->Articles->get($id);
        $this->set(compact('article'));
    }

    public function add()
    {
        $article = $this->Articles->newEmptyEntity();
        if ($this->request->is('post')) {
            // 3.4.0 より前は $this->request->data() が使われました。
            $article = $this->Articles->patchEntity($article, $this->request->getData());
            if ($this->Articles->save($article)) {
                $this->Flash->success(__('記事が保存されました。'));
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('記事の保存が出来ませんでした。'));
        }
        $this->set('article', $article);
    }
}

注釈

フラッシュ コンポーネントを使うコントローラーで読み込む必要があります。必要不可欠なら、 AppController で読み込むようにしてください。

add() アクションの動作は次のとおりです: もし、リクエストの HTTP メソッドが POST なら、Articles モデルを使ってデータの保存を試みます。何らかの理由で保存できなかった場合には、単にビューを表示します。この時に、ユーザーバリデーションエラーやその他の警告が表示されることになります。

すべての CakePHP のリクエストは ServerRequest オブジェクトに格納されており、 $this->request でアクセスできます。リクエストオブジェクトには、受信したリクエストに関するいろんな情報が含まれているので、アプリケーションのフローの制御に利用できます。今回は、リクエストが HTTP POST かどうかの確認に Cake\Http\ServerRequest::is() メソッドを使用しています。

ユーザーがフォームを使ってデータを POST した場合、その情報は、 $this->request->getData() (CakePHP v3.3 以前の場合、 $this->request->data() ) の中に入ってきます。 pr() や debug() を使うと、内容を画面に表示させて、確認することができます。

FlashComponent の success() および error() メソッドを使ってセッション変数にメッセージをセットします。これらのメソッドは PHP の マジックメソッド [https://php.net/manual/en/language.oop5.overloading.php#object.call] を利用しています。 Flash メッセージはリダイレクト後のページに表示されます。レイアウトでは <?= $this->Flash->render() ?> を用いてメッセージを表示し、対応するセッション変数を削除します。コントローラーの Cake\Controller\Controller::redirect 関数は別の URL にリダイレクトを行います。 ['action' => 'index'] パラメーターは /articles、つまり articles コントローラーの index アクションを表す URL に解釈されます。多くの CakePHP の関数で指定できるURLのフォーマットについては、 API [https://api.cakephp.org] の Cake\Routing\Router::url() 関数を参考にすることができます。

save() メソッドを呼ぶと、バリデーションエラーがチェックされ、もしエラーがある場合には保存動作を中止します。これらのエラーがどのように扱われるのかは次のセクションで見てみましょう。

データのバリデーション

CakePHP はフォームの入力バリデーションの退屈さを取り除くのに大いに役立ちます。みんな、延々と続くフォームとそのバリデーションルーチンのコーディングは好まないでしょう。 CakePHP を使うと、その作業を簡単、高速に片付けることができます。

バリデーションの機能を活用するためには、ビューの中で CakePHP の Form を使う必要があります。 Cake\View\Helper\FormHelper はデフォルトで、すべてのビューの中で $this->Form としてアクセスできるようになっています。

add のビューは次のようなものになります。

<!-- File: templates/Articles/add.php -->

<h1>Add Article</h1>
<?php
    echo $this->Form->create($article);
    echo $this->Form->control('title');
    echo $this->Form->control('body', ['rows' => '3']);
    echo $this->Form->button(__('Save Article'));
    echo $this->Form->end();
?>

ここで、FormHelper を使って、HTML フォームの開始タグを生成しています。 $this->Form->create() が生成した HTML は次のようになります。

<form method="post" action="/articles/add">

create() にパラメーターを渡さないで呼ぶと、現在のコントローラーの add() アクション (または id がフォームデータに含まれる場合 edit() アクション) に、 POST で送るフォームを構築している、と解釈されます。

$this->Form->control() メソッドは、同名のフォーム要素を作成するのに使われています。最初のパラメーターは、どのフィールドに対応しているのかを CakePHP に教えます。２番目のパラメーターは、様々なオプションの配列を指定することができます。 - この例では、textarea の列の数を指定しています。ここではちょっとした内観的で自動的な手法が使われています。 control() は、指定されたモデルのフィールドに基づいて、異なるフォーム要素を出力します。

$this->Form->end() の呼び出しで、フォームの終了部分が出力されます。 hidden の input 要素の出力においては、CSRF/フォーム改ざん防止が有効です。

さて少し戻って、 templates/Articles/index.php のビューで「Add Article」というリンクを新しく表示するように編集しましょう。 <table> の前に、以下の行を追加してください。

<?= $this->Html->link('Add Article', ['action' => 'add']) ?>

バリデーション要件について、どうやって CakePHP に指示するのだろう、と思ったかもしれません。バリデーションのルールは、モデルの中で定義することができます。 Article モデルを見直して、幾つか修正してみましょう。

// src/Model/Table/ArticlesTable.php

namespace App\Model\Table;

use Cake\ORM\Table;
use Cake\Validation\Validator;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Timestamp');
    }

    public function validationDefault(Validator $validator): Validator
    {
        $validator
            ->notEmptyString('title')
            ->requirePresence('title', 'create')
            ->notEmptyString('body')
            ->requirePresence('body', 'create');

        return $validator;
    }
}

validationDefault() メソッドを使って save() メソッドが呼ばれた時に、どうやってバリデートするかを CakePHP に教えます。ここでは、本文とタイトルのフィールドが、空ではいけない、そして作成及び編集の際にどちらも必要であるということを設定しています。 CakePHP のバリデーションエンジンは強力で、組み込みのルールがいろいろあります (クレジットカード番号、メールアドレスなど）。また柔軟に、独自ルールを作って設定することもできます。この設定に関する詳細は、バリデーション を参照してください。

バリデーションルールを書き込んだので、アプリケーションを動作させて、タイトルと本文を空にしたまま、記事を投稿してみてください。 Cake\View\Helper\FormHelper::control() メソッドを使ってフォーム要素を作成したので、バリデーションエラーのメッセージが自動的に表示されます。

投稿記事の編集

それではさっそく投稿記事の編集ができるように作業をしましょう。もう CakePHP プロのあなたは、パターンを見つけ出したでしょうか。アクションをつくり、それからビューを作る、というパターンです。 ArticlesController の edit() アクションはこんな形になります。

// src/Controller/ArticlesController.php

public function edit($id = null)
{
    $article = $this->Articles->get($id);
    if ($this->request->is(['post', 'put'])) {
        // 3.4.0 より前は $this->request->data() が使われました。
        $this->Articles->patchEntity($article, $this->request->getData());
        if ($this->Articles->save($article)) {
            $this->Flash->success(__('記事が更新されました。'));
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error(__('記事の更新が出来ませんでした。'));
    }

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

このアクションではまず、ユーザーが実在するレコードにアクセスしようとしていることを確認します。もし $id パラメーターが渡されてないか、ポストが存在しない場合、 NotFoundException を送出して CakePHP の ErrorHandler に処理を委ねます。

次に、リクエストが POST か PUT であるかをチェックします。もしリクエストが POST か PUT なら、 patchEntity() メソッドを用いてPOST データを記事エンティティーに更新します。最終的にテーブルオブジェクトを用いて、エンティティーを保存したり、退けてバリデーションエラーを表示したりします。

edit ビューは以下のようになるでしょう。

<!-- File: templates/Articles/edit.php -->

<h1>Edit Article</h1>
<?php
    echo $this->Form->create($article);
    echo $this->Form->control('title');
    echo $this->Form->control('body', ['rows' => '3']);
    echo $this->Form->button(__('Save Article'));
    echo $this->Form->end();
?>

（値が入力されている場合、）このビューは、編集フォームを出力します。必要であれば、バリデーションのエラーメッセージも表示します。

save() が呼び出された時、エンティティーの内容によって CakePHP は挿入あるいは更新のどちらを生成するかを決定します。

これで、特定の記事をアップデートするためのリンクを index ビューに付けることができます。

<!-- File: templates/Articles/index.php  (edit links added) -->

<h1>Blog articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
    <tr>
        <th>Id</th>
        <th>Title</th>
        <th>Created</th>
        <th>Action</th>
    </tr>

<!-- $articles クエリーオブジェクトをループして、投稿記事の情報を表示 -->

<?php foreach ($articles as $article): ?>
    <tr>
        <td><?= $article->id ?></td>
        <td>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
        <td>
            <?= $this->Html->link('Edit', ['action' => 'edit', $article->id]) ?>
        </td>
    </tr>
<?php endforeach; ?>

</table>

投稿記事の削除

次に、ユーザーが投稿記事を削除できるようにする機能を作りましょう。 ArticlesController の delete() アクションを作るところから始めます。

// src/Controller/ArticlesController.php

public function delete($id)
{
    $this->request->allowMethod(['post', 'delete']);

    $article = $this->Articles->get($id);
    if ($this->Articles->delete($article)) {
        $this->Flash->success(__('ID：{0}の記事が削除されました。', h($id)));
        return $this->redirect(['action' => 'index']);
    }
}

このロジックは、 $id で指定された記事を削除し、 $this->Flash->success() を使って、ユーザーに確認メッセージを表示し、それから /articles にリダイレクトします。ユーザーが GET リクエストを用いて削除を試みようとすると、 allowMethod() が例外を投げます。捕捉されない例外は CakePHP の例外ハンドラーによって捕まえられ、気の利いたエラーページが表示されます。多くの組み込み Exceptions があり、アプリケーションが生成することを必要とするであろう様々な HTTP エラーを指し示すのに使われます。

ロジックを実行してリダイレクトするので、このアクションにはビューがありません。しかし、index ビューにリンクを付けて、投稿を削除するようにできるでしょう。

<!-- File: templates/Articles/index.php (delete links added) -->

<h1>Blog articles</h1>
<p><?= $this->Html->link('Add Article', ['action' => 'add']) ?></p>
<table>
    <tr>
        <th>Id</th>
        <th>Title</th>
        <th>Created</th>
        <th>Actions</th>
    </tr>

<!-- ここで $articles クエリーオブジェクトをループして、投稿情報を表示 -->

    <?php foreach ($articles as $article): ?>
    <tr>
        <td><?= $article->id ?></td>
        <td>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
        <td>
            <?= $this->Form->postLink(
                'Delete',
                ['action' => 'delete', $article->id],
                ['confirm' => 'Are you sure?'])
            ?>
            <?= $this->Html->link('Edit', ['action' => 'edit', $article->id]) ?>
        </td>
    </tr>
    <?php endforeach; ?>

</table>

postLink() を使うと、投稿記事の削除を行う POST リクエストをするための JavaScript を使うリンクが生成されます。

警告

ウェブクローラーが不意にコンテンツ全てを削除できてしまうので、 GETリクエストを用いたコンテンツの削除を許可することは危険です。

注釈

このビューコードは FormHelper を使い、削除する前に、 JavaScript による確認ダイアログでユーザーに確認します。

ルーティング(Routes)

CakePHP のデフォルトのルーティングの動作で十分だという人もいます。しかし、ユーザーフレンドリーで一般の検索エンジンに対応できるような操作に関心のある開発者であれば、CakePHP の中で、 URL がどのように特定の関数の呼び出しにマップされるのかを理解したいと思うはずです。このチュートリアルでは、routes を簡単に変える方法について扱います。

ルーティングテクニックの応用に関する情報は、 ルートを接続 を見てください。

今のところ、ユーザーがサイト (たとえば、 http://www.example.com) を見に来ると、 CakePHP は PagesController に接続し、「home」というビューを表示するようになっています。ではこれを、ルーティングルールを作成して ArticlesController に行くようにしてみましょう。

CakePHP のルーティングは、 config/routes.php の中にあります。デフォルトのトップページのルートをコメントアウトするか、削除します。この行です。

$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);

この行は、「/」という URL をデフォルトの CakePHP のホームページに接続します。これを、自分のコントローラーに接続させるために、次のような行を追加してください。

$builder->connect('/', ['controller' => 'Articles', 'action' => 'index']);

これで、「/」でリクエストしてきたユーザーを、ArticlesController の index() アクションに接続させることができます。

注釈

CakePHP は「リバースルーティング」も利用します。上記のルートが定義されている状態で、配列を期待する関数に ['controller' => 'Articles', 'action' => 'index'] を渡すと、結果のURLは「/」になります。つまり、URL の指定に常に配列を使うということが良策となります。これによりルートが URL の行き先を定義する意味を持ち、リンクが確実に同じ場所を指し示すようになります。

まとめ

気をつけてほしいのは、このチュートリアルは、非常に基本的な点しか扱っていない、ということです。 CakePHP には、もっともっと 多くの 機能があります。シンプルなチュートリアルにするために、それらはここでは扱いませんでした。マニュアルの残りの部分をガイドとして使い、もっと機能豊かなアプリケーションを作成してください。

基本的なアプリケーションの作成が終わったので ブログチュートリアル - パート3 に進むか、自分のプロジェクトを始めてください。CakePHP についてさらに学ぶために CakePHP の利用 や API [https://api.cakephp.org] を使いましょう。

もし困ったときは、いろんな方法で助けを得ることができます。情報の探し方 を見てみてください。 CakePHP にようこそ！

お勧めの参考文献

CakePHP を学習する人が次に学びたいと思う共通のタスクがいくつかあります。

レイアウト: ウェブサイトのレイアウトをカスタマイズする

エレメント: ビューのスニペットを読み込んで再利用する

Bake でコード生成: 基本的な CRUD コードの生成

シンプルな認証と認可のアプリケーション: ユーザーの認証と承認のチュートリアル

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ブログチュートリアル - パート3

ツリーカテゴリーの作成

ブログアプリケーションを進めましょう。投稿記事をカテゴライズすることを想像してください。順に並べられたカテゴリーが必要で、カテゴリーの作成のために Tree behavior を使いましょう。

しかしまずはじめに、テーブルを編集する必要があります。

Migrations プラグイン

データベース内にテーブルを作るのに migrations プラグイン [https://github.com/cakephp/migrations] を利用します。もし articles テーブルが既にデータベースに存在する場合、削除してください。

composer.json ファイルを開いてください。通常の場合、 require の下に既にマイグレーションプラグインが記述されているでしょう。もし無かったら以下を実行して追加してください。

composer require cakephp/migrations:~1.0

マイグレーションプラグインはアプリケーションの plugins フォルダーに存在します。そして、 Application の bootstrap メソッドに $this->addPlugin('Migrations'); を追加してください。

プラグインを読み込んだら、次のコマンドを実行してマイグレーションファイルを作成しましょう。

bin/cake bake migration CreateArticles title:string body:text category_id:integer created modified

/config/Migrations フォルダーにマイグレーションファイルが以下のように生成されます。

<?php

use Migrations\AbstractMigration;

class CreateArticles extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('articles');
        $table->addColumn('title', 'string', [
            'default' => null,
            'limit' => 255,
            'null' => false,
        ]);
        $table->addColumn('body', 'text', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('category_id', 'integer', [
            'default' => null,
            'limit' => 11,
            'null' => false,
        ]);
        $table->addColumn('created', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('modified', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->create();
    }
}

categories テーブルも同じようにコマンドを実行して作成してください。もしフィールドに対して特定の長さを指定する場合、カッコを使って以下のように設定することができます。

bin/cake bake migration CreateCategories parent_id:integer lft:integer[10] rght:integer[10] name:string[100] description:string created modified

これによって、 config/Migrations 内に以下が作成されます。

<?php

use Migrations\AbstractMigration;

class CreateCategories extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('categories');
        $table->addColumn('parent_id', 'integer', [
            'default' => null,
            'limit' => 11,
            'null' => false,
        ]);
        $table->addColumn('lft', 'integer', [
            'default' => null,
            'limit' => 10,
            'null' => false,
        ]);
        $table->addColumn('rght', 'integer', [
            'default' => null,
            'limit' => 10,
            'null' => false,
        ]);
        $table->addColumn('name', 'string', [
            'default' => null,
            'limit' => 100,
            'null' => false,
        ]);
        $table->addColumn('description', 'string', [
            'default' => null,
            'limit' => 255,
            'null' => false,
        ]);
        $table->addColumn('created', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('modified', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->create();
    }
}

ではマイグレーションファイルが作成されたので、テーブルを作成する前にこれを編集しましょう。 parent_id フィールドを 'null' => false を 'null' => true に変更してください。トップレベルのカテゴリーでは parent_id は null になるからです。

テーブルを作る際は以下のコマンドを実行してください。

bin/cake migrations migrate

テーブルの編集

テーブルの設定ができたので、投稿記事のカテゴライズに注力しましょう。

既にパート2でファイル (Articles の Table, Controller および Template) ができあがっているはずです。カテゴリーへ参照を追記していきましょう。

Articles と Categories テーブルとを結びつける必要があります。 src/Model/Table/ArticlesTable.php ファイルを開き、以下のように追加してください。

// src/Model/Table/ArticlesTable.php

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config)
    {
        $this->addBehavior('Timestamp');
        // Just add the belongsTo relation with CategoriesTable
        $this->belongsTo('Categories', [
            'foreignKey' => 'category_id',
        ]);
    }
}

Categories のスケルトンコードを作成する

bake コマンドを利用してすべてのファイルを作成しましょう。

bin/cake bake model Categories
bin/cake bake controller Categories
bin/cake bake template Categories

代わりに、1行で bake することもできます。

bin/cake bake all Categories

bake ツールによって、すぐにすべてのファイルが作成されました。もし CakePHP がどのように動いているかを知りたかったら簡単に bake ツールを読んでおきましょう。

注釈

Windows を利用している場合は / の代わりに \ を使用してください。

templates/Categories/add.php および templates/Categories/edit.php を以下のように修正してください。

echo $this->Form->control('parent_id', [
    'options' => $parentCategories,
    'empty' => 'No parent category'
]);

TreeBehavior を Categories テーブルに追加

データベーステーブルの階層的なツリー構造を管理するのに TreeBehavior が役に立つでしょう。データ管理の際に MPTT logic [https://www.sitepoint.com/hierarchical-data-database-2/] を利用しています。 MPTT ツリー構造は読み込みに最適化されているので、ときにブログのような読み込みが重いアプリケーションに適しています。

src/Model/Table/CategoriesTable.php ファイルの initialize() メソッド内で、 TreeBehavior が CategoriesTable と紐づけられていることがお分かりいただけるでしょう。

bake は lft 列と rght 列が存在するあらゆるテーブルにこのビヘイビアーを追加します。

$this->addBehavior('Tree');

紐づけられた TreeBehavior によって、カテゴリーの順を並べ直すような機能にアクセスすることが可能になります。すぐにわかるでしょう。

しかし今は、カテゴリーの中にある add と edit のテンプレートファイル内の次の control を削除てください。

echo $this->Form->control('lft');
echo $this->Form->control('rght');

さらに、Categories テーブルモデルの lft 列と rght 列のバリデーターの中の requirePresense を無効にするか削除してください。

public function validationDefault(Validator $validator): Validator
{
    $validator
        ->add('id', 'valid', ['rule' => 'numeric'])
        ->allowEmptyString('id', 'create');

    $validator
        ->add('lft', 'valid', ['rule' => 'numeric'])
    //    ->requirePresence('lft', 'create')
        ->notEmpty('lft');

    $validator
        ->add('rght', 'valid', ['rule' => 'numeric'])
    //    ->requirePresence('rght', 'create')
        ->notEmpty('rght');
}

カテゴリーが保存される際に、これらのフィールドは TreeBehavior によって自動的に管理されます。

ブラウザーを用いて、 /yoursite/categories/add コントローラーアクションからいくつかの新しいカテゴリーを登録してください。

TreeBehavior でカテゴリーを並べ替える

categories の index テンプレートファイルでは、categories を一覧したり並べ替えたりすることができます。

CategoriesController.php の index メソッドを編集して、ツリーでカテゴリーを並べ替えるために moveUp() および moveDown() メソッドを追加してください。

class CategoriesController extends AppController
{
    public function index()
    {
        $categories = $this->Categories->find()
            ->order(['lft' => 'ASC'])
            ->all();
        $this->set(compact('categories'));
        $this->viewBuilder()->setOption('serialize', ['categories']);
    }

    public function moveUp($id = null)
    {
        $this->request->allowMethod(['post', 'put']);
        $category = $this->Categories->get($id);
        if ($this->Categories->moveUp($category)) {
            $this->Flash->success('The category has been moved Up.');
        } else {
            $this->Flash->error('The category could not be moved up. Please, try again.');
        }
        return $this->redirect($this->referer(['action' => 'index']));
    }

    public function moveDown($id = null)
    {
        $this->request->allowMethod(['post', 'put']);
        $category = $this->Categories->get($id);
        if ($this->Categories->moveDown($category)) {
            $this->Flash->success('The category has been moved down.');
        } else {
            $this->Flash->error('The category could not be moved down. Please, try again.');
        }
        return $this->redirect($this->referer(['action' => 'index']));
    }
}

templates/Categories/index.php を以下のように置き換えてください。

<div class="actions large-2 medium-3 columns">
    <h3><?= __('Actions') ?></h3>
    <ul class="side-nav">
        <li><?= $this->Html->link(__('New Category'), ['action' => 'add']) ?></li>
    </ul>
</div>
<div class="categories index large-10 medium-9 columns">
    <table cellpadding="0" cellspacing="0">
    <thead>
        <tr>
            <th>Id</th>
            <th>Parent Id</th>
            <th>Lft</th>
            <th>Rght</th>
            <th>Name</th>
            <th>Description</th>
            <th>Created</th>
            <th class="actions"><?= __('Actions') ?></th>
        </tr>
    </thead>
    <tbody>
    <?php foreach ($categories as $category): ?>
        <tr>
            <td><?= $category->id ?></td>
            <td><?= $category->parent_id ?></td>
            <td><?= $category->lft ?></td>
            <td><?= $category->rght ?></td>
            <td><?= h($category->name) ?></td>
            <td><?= h($category->description) ?></td>
            <td><?= h($category->created) ?></td>
            <td class="actions">
                <?= $this->Html->link(__('View'), ['action' => 'view', $category->id]) ?>
                <?= $this->Html->link(__('Edit'), ['action' => 'edit', $category->id]) ?>
                <?= $this->Form->postLink(__('Delete'), ['action' => 'delete', $category->id], ['confirm' => __('Are you sure you want to delete # {0}?', $category->id)]) ?>
                <?= $this->Form->postLink(__('Move down'), ['action' => 'moveDown', $category->id], ['confirm' => __('Are you sure you want to move down # {0}?', $category->id)]) ?>
                <?= $this->Form->postLink(__('Move up'), ['action' => 'moveUp', $category->id], ['confirm' => __('Are you sure you want to move up # {0}?', $category->id)]) ?>
            </td>
        </tr>
    <?php endforeach; ?>
    </tbody>
    </table>
</div>

Articles コントローラーを編集する

ArticlesController ではすべてのカテゴリーを一覧できます。投稿記事を作成あるいは編集するときに、カテゴリーを選ぶことができるようになります。

// src/Controller/ArticlesController.php

namespace App\Controller;

// 3.6 より前は Cake\Network\Exception\NotFoundException を使用
use Cake\Http\Exception\NotFoundException;

class ArticlesController extends AppController
{
    // ...

    public function add()
    {
        $article = $this->Articles->newEmptyEntity();
        if ($this->request->is('post')) {
            $article = $this->Articles->patchEntity($article, $this->request->getData());
            if ($this->Articles->save($article)) {
                $this->Flash->success(__('Your article has been saved.'));
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('Unable to add your article.'));
        }
        $this->set('article', $article);

        // Just added the categories list to be able to choose
        // one category for an article
        $categories = $this->Articles->Categories->find('treeList')->all();
        $this->set(compact('categories'));
    }
}

Articles テンプレートを編集する

投稿記事の add ファイルは以下のようになっているはずです。

<!-- File: templates/Articles/add.php -->

<h1>Add Article</h1>
<?php
echo $this->Form->create($article);
// ここにカテゴリーのコントロールを追加
echo $this->Form->control('category_id');
echo $this->Form->control('title');
echo $this->Form->control('body', ['rows' => '3']);
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();

/yoursite/categories/add のアドレスに行くと、カテゴリーを選ぶための一覧を見れるでしょう。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

シンプルな認証と認可のアプリケーション

ブログチュートリアル の例で、ログインユーザーに基づく特定の URL に対してセキュアなアクセスをしたい、という状況を想像してください。また、別の要求もあります。 複数の著者が自身の記事を作成、編集、削除できて、一方で他の著者が自身のものではない記事に変更を加えるのを許可しないようなブログにすることができます。

ユーザーに関連するコードを作成する

まずはじめに、ユーザーデータを保持するためのブログデータベースの中に新しいテーブルを作成しましょう。

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

テーブルの命名には CakePHP の規則がありますが、別の規則も活用できます。 email と password のカラムをユーザーテーブルに使用すると、 CakePHP はユーザーログインの実装のときにほとんどのことを自動で定義します。

続いてのステップは、ユーザーデータを検索、保存、検証する UsersTable クラスを作成することです。

// src/Model/Table/UsersTable.php
namespace App\Model\Table;

use Cake\ORM\Table;
use Cake\Validation\Validator;

class UsersTable extends Table
{

    public function validationDefault(Validator $validator)
    {
        return $validator
            ->notEmpty('email', 'A email is required')
            ->email('email')
            ->notEmpty('password', 'A password is required')
            ->notEmpty('role', 'A role is required')
            ->add('role', 'inList', [
                'rule' => ['inList', ['admin', 'author']],
                'message' => 'Please enter a valid role'
            ]);
    }

}

では UsersController も作成しましょう。以下の内容は基本的に bake された UsersController の一部に対応するもので、 CakePHP にバンドルされているコード生成ユーティリティを利用しています。

// src/Controller/UsersController.php

namespace App\Controller;

use App\Controller\AppController;
use Cake\Event\EventInterface;

class UsersController extends AppController
{
    public function index()
    {
        $this->set('users', $this->Users->find('all'));
    }

    public function view($id)
    {
        $user = $this->Users->get($id);
        $this->set(compact('user'));
    }

    public function add()
    {
        $user = $this->Users->newEmptyEntity();
        if ($this->request->is('post')) {
            $user = $this->Users->patchEntity($user, $this->request->getData());
            if ($this->Users->save($user)) {
                $this->Flash->success(__('The user has been saved.'));
                return $this->redirect(['action' => 'add']);
            }
            $this->Flash->error(__('Unable to add the user.'));
        }
        $this->set('user', $user);
    }
}

同じように、コード生成ツールで記事のビューを作り、ユーザーのビューを実装することができます。このチュートリアルのために、 add.php をお見せしましょう。

<!-- templates/Users/add.php -->

<div class="users form">
<?= $this->Form->create($user) ?>
    <fieldset>
        <legend><?= __('Add User') ?></legend>
        <?= $this->Form->control('email') ?>
        <?= $this->Form->control('password') ?>
        <?= $this->Form->control('role', [
            'options' => ['admin' => 'Admin', 'author' => 'Author']
        ]) ?>
   </fieldset>
<?= $this->Form->button(__('Submit')); ?>
<?= $this->Form->end() ?>
</div>

認証の追加

これで認証レイヤーを追加する準備ができました。 CakePHPでは、これは authentication プラグインによって処理されます。まずはインストールしてみましょう。 composerを使ってAuthenticationプラグインをインストールします。

composer require "cakephp/authentication:^2.0"

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

次に User エンティティを作成し、パスワードハッシュを追加してみましょう。 src/Model/Entity/User.php エンティティファイルを作成し、以下を追加します。

// src/Model/Entity/User.php
namespace App\Model\Entity;

use Cake\Auth\DefaultPasswordHasher;
use Cake\ORM\Entity;

class User extends Entity
{
    // 主キーフィールドである「id」以外のすべてのフィールドを一括代入可能にします。
    protected $_accessible = [
        '*' => true,
        'id' => false
    ];

    // ...

    protected function _setPassword($password)
    {
        if (strlen($password) > 0) {
            return (new DefaultPasswordHasher)->hash($password);
        }
    }

    // ...
}

これでパスワードのプロパティがユーザに割り当てられるたびに DefaultPasswordHasher クラスを使ってハッシュ化されるようになりました。

認証の設定

それでは、認証プラグインを設定しましょう。このプラグインは3つの異なるクラスを使って認証処理を行います。 :

Application は認証ミドルウェアを使用して、認証サービスを提供し、どのようにクレデンシャルをチェックするのか、どこにあるのかを定義したいすべての設定を保持します。

AuthenticationService は、どのように定義するかを定義したいすべての設定を保持しています。認証情報をチェックして、どこにあるかを調べます。

AuthenticationService はユーティリティクラスです。認証プロセスの一部として実行されます。

AuthenticationMiddleware はミドルウェアキューの一部として実行されます。これは、コントローラがフレームワークによって処理される前に実行され、認証情報をピックアップして、ユーザーが認証されているかどうかをチェックするために処理します。

認証ロジックは特定のクラスに分割され、認証処理はコントローラ層の前に行われます。最初の認証は、ユーザーが認証されているかどうかをチェックし (あなたが提供した設定に基づいて)、ユーザーと認証結果をさらに参照するためのリクエストに注入します。

src/Application.php で、以下のインポートを追加します。

// src/Application.phpで以下のインポートを追加します
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Psr\Http\Message\ServerRequestInterface;

そして、アプリケーションクラスに認証インターフェースを実装します。

// src/Application.php で
class Application extends BaseApplication
    implements AuthenticationServiceProviderInterface
{

その後、次のように追加します。

// src/Application.php
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $middlewareQueue
        // ... other middleware added before
        ->add(new RoutingMiddleware($this))
        // add Authentication after RoutingMiddleware
        ->add(new AuthenticationMiddleware($this));

    return $middlewareQueue;
}

public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $authenticationService = new AuthenticationService([
        'unauthenticatedRedirect' => '/users/login',
        'queryParam' => 'redirect',
    ]);

    // 識別子をロードして、電子メールとパスワードのフィールドを確認します
    $authenticationService->loadIdentifier('Authentication.Password', [
        'fields' => [
            'username' => 'email',
            'password' => 'password',
        ]
    ]);

    // 認証子をロードするには、最初にセッションを実行する必要があります
    $authenticationService->loadAuthenticator('Authentication.Session');
    // メールとパスワードを選択するためのフォームデータチェックの設定
    $authenticationService->loadAuthenticator('Authentication.Form', [
        'fields' => [
            'username' => 'email',
            'password' => 'password',
        ],
        'loginUrl' => '/users/login',
    ]);

    return $authenticationService;
}

AppController クラスに以下のコードを追加します。:

// src/Controller/AppController.php
public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('RequestHandler');
    $this->loadComponent('Flash');

    // Add this line to check authentication result and lock your site
    $this->loadComponent('Authentication.Authentication');

これで、すべてのリクエストに対して AuthenticationMiddleware はリクエストセッションを検査して認証済みのユーザーを探すようになります。/users/login ページをロードしている場合は、投稿されたフォームデータ(もしあれば)も検査して資格情報を抽出します。デフォルトでは、認証情報はリクエストデータの email と password フィールドから抽出されます。認証結果は authentication という名前のリクエスト属性に注入されます。この結果はいつでもコントローラのアクションから $this->request->getAttribute('authentication')``を使って調べることができます。すべてのページは ``AuthenticationComponent がリクエストごとに結果をチェックしているため、制限されてしまいます。認証されたユーザを見つけられなかった場合は ユーザーを /users/login のページにリダイレクトします。この時点ではまだログインページがないため、サイトは動作しませんので注意してください。サイトにアクセスすると「無限リダイレクトループ」が発生します。ということで、これを修正しましょう！

UsersController に以下のコードを追加します。

public function beforeFilter(\Cake\Event\EventInterface $event)
{
    parent::beforeFilter($event);
    // ログインアクションを認証を必要としないように設定することで、
    // 無限リダイレクトループの問題を防ぐことができます
    $this->Authentication->addUnauthenticatedActions(['login']);
}

public function login()
{
    $this->request->allowMethod(['get', 'post']);
    $result = $this->Authentication->getResult();
    // POSTやGETに関係なく、ユーザーがログインしていればリダイレクトします
    if ($result->isValid()) {
        // ログイン成功後に /article にリダイレクトします
        $redirect = $this->request->getQuery('redirect', [
            'controller' => 'Articles',
            'action' => 'index',
        ]);

        return $this->redirect($redirect);
    }
    // ユーザーの送信と認証に失敗した場合にエラーを表示します
    if ($this->request->is('post') && !$result->isValid()) {
        $this->Flash->error(__('Invalid email or password'));
    }
}

ログインアクションのテンプレートロジックを追加します。

<!-- in /templates/Users/login.php -->
<div class="users form">
    <?= $this->Flash->render() ?>
    <h3>Login</h3>
    <?= $this->Form->create() ?>
    <fieldset>
        <legend><?= __('ユーザー名とパスワードを入力してください') ?></legend>
        <?= $this->Form->control('email', ['required' => true]) ?>
        <?= $this->Form->control('password', ['required' => true]) ?>
    </fieldset>
    <?= $this->Form->submit(__('Login')); ?>
    <?= $this->Form->end() ?>

    <?= $this->Html->link("Add User", ['action' => 'add']) ?>
</div>

これでログインページでアプリケーションに正しくログインできるようになりました。あなたのサイトの任意のページをリクエストしてテストしてください。 /users/login ページにリダイレクトされた後、ユーザーを作成した時に選択したメールアドレスとパスワードを入力してください。ログイン後、正常にリダイレクトされるはずです。

アプリケーションの設定を行うために、もう少し詳細を追加する必要があります。すべての view と index のページにログインせずにアクセスできるようにしたいので、この設定を AppController に追加します。

// src/Controller/AppController.php で
public function beforeFilter(\Cake\Event\EventInterface $event)
{
    parent::beforeFilter($event);
    // このアプリケーションのすべてのコントローラのために、
    // インデックスとビューのアクションを公開し、認証チェックをスキップします
    $this->Authentication->addUnauthenticatedActions(['index', 'view']);
}

ログアウト

ログアウトアクションを UsersController` クラスに追加します。

// src/Controller/UsersController.php で
public function logout()
{
    $result = $this->Authentication->getResult();
    // POSTやGETに関係なく、ユーザーがログインしていればリダイレクトします
    if ($result->isValid()) {
        $this->Authentication->logout();
        return $this->redirect(['controller' => 'Users', 'action' => 'login']);
    }
}

これで /users/logout にアクセスしてログアウトすることができます。そうするとログインページが表示されるはずです。

ここまで来れば、おめでとうございます。あなたは今、以下の機能を備えたシンプルなブログを持っているはずです。 :

認証されたユーザが、記事を作成・編集することができます。

認証されていないユーザーが、記事やタグを閲覧することができます。

より詳しく知りたい方のための読みもの

Bake でコード生成 基本的な CRUD コードの生成について

AuthComponent: ユーザーの登録とログインについて

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

貢献

CakePHP に貢献する方法は数多くあります。以下のセクションは、 CakePHP に貢献するための様々な方法について記述しています。

ドキュメント

チケット

コード

コーディング規約

後方互換性ガイド

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ドキュメント

ドキュメントへの貢献の方法はシンプルです。ファイルは https://github.com/cakephp/docs にホストされています。自由にリポジトリーをフォークして、変更・改善・翻訳を追加し、プルリクエストを発行してください。またファイルをダウンロードせず、 GitHub を使ってオンラインでドキュメントを編集することもできます。どのページにも存在している「Improve this Doc」ボタンを押すことで、 GitHub 上にあるそのページのオンラインエディターに飛ぶことができます。

CakePHP ドキュメントは、プルリクエストがマージされるごとに継続的インテグレーション [https://ja.wikipedia.org/wiki/%E7%B6%99%E7%B6%9A%E7%9A%84%E3%82%A4%E3%83%B3%E3%83%86%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3] が行われ、デプロイされます。

翻訳

ドキュメントチーム (docs at cakephp dot org) までEメールを送るか、 IRC (#cakephp on freenode) で、参加したい旨を連絡してください。

新たに翻訳する言語

できるだけ完全な翻訳を提供したいのですが、翻訳ファイルが最新になっていない部分もありえます。常に英語バージョンを正のバージョンと考えてください。

もし、あなたの言語が現在の言語の中に無いなら、我々に Github 経由で連絡を頂ければ、我々はスケルトンフォルダーの作成を考えます。以下のセクションは頻繁に書き換わらないファイルなので翻訳を検討するにはもっとも適しています:

index.rst

intro.rst

quickstart.rst

installation.rst

/intro folder

/tutorials-and-examples folder

ドキュメント管理者へのリマインド

すべての言語フォルダーの構造は英語フォルダーの構造のミラーであるべきです。もし英語版の構造が変わったら、我々はそれらの変更を他の言語にも適用します。

たとえば、新しい英語のファイルが en/file.rst に作成されたら、我々は次のようにします:

他のすべての言語の中にそのファイルを追加します : fr/file.rst, zh/file.rst, ...

中身を削除します。ただし、 title 、 meta 情報、 toc-tree 要素は残しておきます。次の記述は誰も翻訳していないファイルに記載されるものです。

File Title
##########

.. note::
    The documentation is not currently supported in XX language for this
    page.

    Please feel free to send us a pull request on
    `Github <https://github.com/cakephp/docs>`_ or use the **Improve This Doc**
    button to directly propose your changes.

    You can refer to the English version in the select top menu to have
    information about this page's topic.

// If toc-tree elements are in the English version
.. toctree::
    :maxdepth: 1

    one-toc-file
    other-toc-file

.. meta::
    :title lang=xx: File Title
    :keywords lang=xx: title, description,...

翻訳者tips

翻訳先の言語のページを閲覧・編集してください。そうしなければ、翻訳済みのものは見えません。

選択した言語が book 上にすでに存在しているなら、遠慮無くその先に飛び込んでください。

堅苦しくない文体を使ってください。

エンジニアに通じる言葉 [https://ja.wikipedia.org/wiki/%E4%BD%BF%E7%94%A8%E5%9F%9F] を使ってください。たとえば「Assert」という単語は直訳すると「主張」「表明」という意味になりますが、エンジニアに対しては「アサート」とすべきです。

タイトルと内容を同時に翻訳してください。

修正を投稿する前に、英語版との比較を行うようにしてください（何か修正しても、 'upstream' の変更が含まれていないなら、あなたの投稿は受け入れられません）。

用語を英語で書く必要があるなら、 <em> タグで囲んでください。例えば、「asdf asdf Controller asdf」 や 「asdf asdf Kontroller (Controller) asfd」 などです。

一部だけ翻訳して投稿しないでください。

保留されている項目があるセクションは編集しないでください。

アクセント文字のために HTML エンティティー [https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references] を使用しないでください。この book は UTF-8 を使っています。

マークアップ (HTML) の変更や新しいコンテンツを絶対に変更しないでください。

元のコンテンツに不備がある場合は、まずそれを編集するようにしてください。

ドキュメントのフォーマットガイド

新しい CakePHP のドキュメントは ReST 形式 [https://ja.wikipedia.org/wiki/ReStructuredText] で書かれています。 ReST (Re Structured Text) は markdown や textile に似たプレーンテキストのマークアップ記法です。一貫性を保持するために、CakePHP ドキュメントに追加をする際には、自分のテキストのフォーマットと構造をこのガイドラインに合わせてください。

行の長さ

テキストの行は半角 80 列を基準に改行してください。例外は長い URL とコードスニペットのみです。

見出しとセクション

セクションの見出しはテキストの長さ以上の区切り文字でタイトルにアンダーラインをつけることで作成されます。

# はページタイトルを示すのに使われます。

= はページ内のセクションを意味するのに使われます。

- はサブセクションを意味するのに使われます。

~ はサブ-サブセクションを意味するのに使われます。

^ はサブ-サブ-サブセクションを意味するのに使われます。

見出しは5レベルより深くネストしてはなりません。また、空行で囲まれる必要があります。

段落(Paragraphs)

段落というのは、全ての行が同じレベルのインデントがつけられた、単純なテキストの塊です。段落は1行の空行で区切られる必要があります。

インラインマークアップ

単一のアスタリスク: text 強調(斜体) 我々はこれを一般的なハイライト/強調に使います。

*text* 。

二つのアスタリスク: text 強い強調(太文字) 我々はこれを作業ディレクトリー、箇条書きリストのタイトル、テーブル名（後に続く単語 "table" は含めません）に使います。

**/config/Migrations** 、 **articles** など。

２つのバッククォート: text コード例。我々はこれをメソッドのオプション名、テーブルの列名、オブジェクト名（後に続く単語 "object" は含めません）、メソッド/関数名（ "()" を含めます ）に使います。

``cascadeCallbacks`` 、 ``true`` 、 ``id`` 、 ``PagesController`` 、 ``config()`` など。

もしアスタリスクやバッククォートが文章の中に現れて、インラインマークアップの区切り文字に間違えられうるなら、バックスラッシュでエスケープする必要があります。

インラインマークアップは多少の制限があります:

ネスト できません 。

マークアップ対象の最初や最後が空白ではいけません: * text* は間違いです。

マークアップ対象は非単語文字（訳注:空白等）で囲まれることで、それ以外と区別されていなければなりません。単語を分けたくない場合はバックスラッシュで空白をエスケープしてください: 一続きの長い\ *太字部分*\ を含む単語 。

リスト

リストマークアップは markdown に非常によく似ています。順番なしのリストは単一のアスタリスクと空白から始まる行によって示されます。順番がついたリストは同様に数字、または # で自動的なナンバリングがなされます。

* これは中黒(*bullet*)です
* これも同じです。しかしこの行は
  2行あります。

1. 一番目の行
2. 二番目の行

#. 自動的なナンバリング
#. は時間の節約をもたらします。

インデントされたリストも、セクションをインデントし、空行で区切ることによって作成できます。

* 一番目の行
* 二番目の行

    * 深くなってる
    * ワーオ！

* 最初のレベルに戻った。

定義リストは以下のようにして作成できます。

項目
    定義
CakePHP
    PHP の MVC フレームワーク

項目は1行以上にすることができませんが、定義は複数行にすることができ、全ての行は一貫したインデントをつける必要があります。

リンク

いくつかの用途に合った種類のリンクがあります。

外部リンク

外部のドキュメントへのリンクは以下のようにできます。

`php.net への外部リンク <https://php.net>`_

以上のものは次のようにリンクします: php.net への外部リンク [https://php.net]

他のページへのリンク

:doc:

ドキュメントの他のページへ :doc: ロール (role) を使ってリンクします。指定するドキュメントへ絶対パスまたは相対パス参照を用いてリンクできます。 .rst 拡張子は省く必要があります。例えば、 :doc:`form` が core-helpers/html に書かれていたとすると、リンクは core-helpers/form を参照します。もし参照が :doc:`/core-helpers` であったら、どこで使われるかを厭わずに、常に /core-helpers を参照します。

相互参照リンク

:ref:

:ref: ロールを使って任意のドキュメントに任意のタイトルを相互参照することができます。リンクのラベルはドキュメント全体に渡って一意のものに向けられる必要があります。クラスのメソッドのラベルを作る時は、リンクのラベルのフォーマットとして class-method を使うのがベストです。

ラベルの最も一般的な使い方はタイトルの上に書くことです。例:

.. _ラベル名:

セクションの見出し
------------------

続きの内容..

他の場所で、 :ref:`ラベル名` を用いて上記のセクションを参照することができます。リンクのテキストはリンクの先にあるタイトルになります。また、 :ref:`リンクテキスト <ラベル名>` として自由にリンクのテキストを指定することができます。

Sphinx が出力する警告を防ぐ

Sphinx は toc-tree 内に参照されないファイルがあると警告を出力します。これは、すべてのファイルが正しいリンクを持っていることを確認する良い方法ではありますが、ファイルへのリンクを挿入する必要がないときもありえます。たとえば、 epub-contents と pdf-contents などがそうです。これらのケースでは、ファイルの先頭に :orphan: を加えることで、このファイルが toc-tree にいないという警告を抑えることができます。

クラスとその内容を記述する

CakePHP のドキュメントは phpdomain [https://pypi.org/project/sphinxcontrib-phpdomain/] を用いて PHP のオブジェクトと構成物を記述するための独自のディレクティブを提供します。適切な索引 (index) と相互参照機能を与えるためにこのディレクティブとロールの利用は欠かせません。

クラスと構成物を記述する

各々のディレクティブは索引と名前空間の索引のどちらか、または両方を生成します。

.. php:global:: name

このディレクティブは新規の PHP のグローバル変数を定義します。

.. php:function:: name(signature)

クラスに属さない新規のグローバル関数を定義します。

.. php:const:: name

このディレクティブは新規の定数を定義します。これを class ディレクティブの中でネストして使うことにより、クラス定数を作成することもできます。

.. php:exception:: name

このディレクティブは現在の名前空間内で新規の例外 (Exception) を定義します。コンストラクターの引数を含める書き方もできます。

.. php:class:: name

クラスを記述します。クラスに属するメソッド、属性、定数はこのディレクティブの本文の中にある必要があります。

.. php:class:: MyClass

    クラスの説明

   .. php:method:: method($argument)

   メソッドの説明

属性、メソッド、定数はネストする必要はありません。これらは単にクラス定義の後につけることができます。

.. php:class:: MyClass

    クラスについての文

.. php:method:: methodName()

    メソッドについての文

参考

php:method, php:attr, php:const

.. php:method:: name(signature)

クラスのメソッドと、その引数、返り値、例外を記述します。

.. php:method:: instanceMethod($one, $two)

    :param string $one: 第一引数。
    :param string $two: 第二引数。
    :returns: なんらかの配列。
    :throws: InvalidArgumentException

   これはインスタンスメソッドです。

.. php:staticmethod:: ClassName::methodName(signature)

静的なメソッド、その引数、返り値、例外を記述します。オプションは php:method を見てください。

.. php:attr:: name

クラスのプロパティー・属性を記述します。

Sphinx が出力する警告を防ぐ

Sphinx は関数が複数のファイルから参照されていると警告を出力します。これは、関数を２度追加していないことを確認する良い方法ではありますが、実際には複数回にわたって関数を書きたいときもありえます。たとえば、 debug object が /development/debugging と /core-libraries/global-constants-and-functions から参照されます。このケースでは、debug 関数の下に :noindex: を加えることで、警告を抑えることができます。その関数が参照されるために :no-index: の 無い 参照を１つだけを残して下さい。

.. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true)
    :noindex:

相互参照

以下のロールは PHP のオブジェクトを参照し、適合するディレクティブがあればリンクが生成されます。

:php:func:

PHP の関数を参照します。

:php:global:

$ 接頭辞を持つグローバル変数を参照します。

:php:const:

グローバル定数、またはクラス定数のどちらかを参照します。クラス定数はそのクラスが先に付けられる必要があります。

DateTimeは :php:const:`DateTime::ATOM` 定数を持ちます。

:php:class:

名前でクラスを参照します。

:php:class:`ClassName`

:php:meth:

クラスのメソッドを参照します。このロールは両方の種類のメソッドをサポートします。

:php:meth:`DateTime::setDate`
:php:meth:`Classname::staticMethod`

:php:attr:

オブジェクトの属性を参照します。

:php:attr:`ClassName::$propertyName`

:php:exc:

例外を参照します。

ソースコード

段落の終わりの :: を用いて、リテラルコードブロックを生成します。リテラルブロックはインデントされる必要があり、各段落のように単一の行で区切られる必要があります。

これは段落です。 ::

    while ($i--) {
        doStuff()
    }

これは普通のテキストの再開です。

リテラルテキストは変更やフォーマットがされず、1レベル分のインデントが削除されたものが残ります。

注意と警告

重要なヒント、特別な注記、潜在的な危険を読者に知らせるためにしたいことがしばしばあります。 sphinx の勧告 (Admonitions) は、まさにそのために使われます。勧告には3つの種類があります。

.. tip:: tip は面白い情報や重要な情報を文書化、または再反復するために使用されています。ディレクティブの内容は完結した文章で書かれ、また全ての適切な句読点を含める必要があります。

.. note:: note は情報の特に重要なもののひとつを文書化するために使用されています。ディレクティブの内容は完結した文章で書かれ、また全ての適切な句読点を含める必要があります。

.. warning:: warning は潜在的な障害、またはセキュリティに関する情報を文書化するために使用されています。ディレクティブの内容は完結した文章で書かれ、また全ての適切な句読点を含める必要があります。

.. versionadded:: X.Y.Z "バージョン追加" 勧告は特定のバージョンで追加された新機能特有の注記を表示するために使われます。 X.Y.Z はその機能が追加されたバージョンです。

.. deprecated:: X.Y.Z "バージョン追加" 勧告とは反対に、 "撤廃" 勧告は、廃止される機能を通知するために使われます。 X.Y.Z はその機能が撤廃されるバージョンです。

全ての勧告は同じようになります。

.. note::

    インデントされ空の行に挟まれます。
    段落と一緒です。

この文は note の一部ではありません。

サンプル

Tip

これは忘れがちで役に立つ一言です。

注釈

ここに注意を払う必要があります。

警告

危ないかもしれません。

バージョン 4.0.0 で追加: すごい機能がバージョン 4.0.0 で追加されました。

バージョン 4.0.1 で非推奨: この古い機能はバージョン 4.0.1 で撤廃されます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

チケット

チケットの形式でコミュニティーからのフィードバックと助けを得ることは、 CakePHP の開発プロセスの極めて重要な部分です。CakePHP のチケットの全ては GitHub [https://github.com/cakephp/cakephp/issues] にホストされています。

バグの報告

よく書かれたバグの報告は非常に有用です。可能な限り最善のバグレポートを作成するのに役立ついくつかのステップがあります:

既存のよく似ているチケットを検索 [https://github.com/cakephp/cakephp/search?q=it+is+broken&ref=cmdform&type=Issues] して、誰かが既にあなたの問題を報告してるかを、またはリポジトリー内でまだバグが修正されていないかを確認 してください 。

バグを再現する方法 についての詳細な手順を必ず 書いてください 。これは問題の実例を示すテストケースかコードスニペットの形式となるでしょう。問題を再現する方法がないと、修正される可能性が低くなります。

環境 (OS、PHP バージョン、CakePHP バージョン) についてできるだけ詳しく 書いてください 。

サポートの質問にチケットシステムを使用 しないでください 。 Freenode [https://webchat.freenode.net] の #cakephp IRC チャンネルでは、あなたの質問の答えを手助けしてくれる多くの開発者に会えます。 Stack Overflow [https://stackoverflow.com/questions/tagged/cakephp] も覗いてみてください。

セキュリティ問題の報告

もし CakePHP でセキュリティ問題を見つけたら、通常のバグ報告システムの代わりに、以下の手順を使用してください。バグトラッカー、メーリングリスト、IRC を使う代わりに、どうか security [at] cakephp.org に Eメールを送るようにしてください。このアドレスに送られた Eメールは CakePHP コアチームにプライベートなメーリングリストで送られます。

各々の報告では、私たちは最初に脆弱性を確認しようとします。一旦確認されたならば、CakePHP チームは次の処置を講ずるでしょう:

私たちは問題を受け渡した報告者に受け取ったことを知らせた上で、修正に取り組みます。私達がそれを発表するまで報告者が問題の機密性を保持するようお願いします。

修正・パッチを準備します。

危弱性と、エクスプロイトコードの説明をする記事を準備します。

影響する全てのバージョンの新規バージョンをリリースします。

リリースの発表にその問題を大々的に載せます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コード

パッチと Pull Request はコードを CakePHP に送って貢献するための素晴らしい方法です。 Pull Request は GitHub で作成することができるもので、パッチファイルをチケットのコメントで伝えるよりも推奨される方法です。

最初のセットアップ

CakePHP の修正に取りかかる前に自分の環境を整えることをお勧めします。以下のソフトウェアが必要になります。

Git

PHP 7.4 以上

PHPUnit 5.7.0 以上

ユーザー情報（自分の名前/ハンドル名とメールアドレス）をセットしてください。

git config --global user.name 'Bob Barker'
git config --global user.email 'bob.barker@example.com'

注釈

Git を使うのが初めてなら、無料で素敵なドキュメント ProGit [https://git-scm.com/book/ja/] をぜひお読みください。

CakePHP のソースコードの clone を GitHub から取得してください。

GitHub [https://github.com] アカウントを持っていないなら、作成してください。

CakePHP リポジトリー [https://github.com/cakephp/cakephp] の Fork ボタンをクリックして Fork してください。

Fork できたら、Fork したものを自分のローカルマシンへと clone してください。

git clone git@github.com:YOURNAME/cakephp.git

オリジナルの CakePHP リポジトリーを remote リポジトリーとして追加してください。これは後で CakePHP リポジトリーから変更分を fetch するのに使うことになり、こうすることで CakePHP を最新の状態にしておくのです。

cd cakephp
git remote add upstream git://github.com/cakephp/cakephp.git

いまや CakePHP はセットアップされましたのでデータベース接続 で $test を定義すれば、すべてのテストを実行する ことができるでしょう。

修正に取りかかる

バグや新機能、改善に取り組むたびに毎回、 トピック・ブランチを作成してください。

ブランチは 修正/改善対象のバージョンをベースにして作成してください。たとえば、 3.x のバグを修正するなら、ブランチのベースとして master ブランチを使うことになります。もし、 2.x 系のバグ修正なら、 2.x ブランチを使用してください。これで、GitHub は、あなたに対象のブランチを編集させないので、後で変更をマージする際にとても簡単になります。

# 3.x のバグを修正
git fetch upstream
git checkout -b ticket-1234 upstream/master

# 2.x のバグを修正
git fetch upstream
git checkout -b ticket-1234 upstream/2.x

Tip

ブランチの名前は説明的につけてください。チケット名や機能名を含めるのは良い慣習です。例) ticket-1234, feature-awesome

上記は upstream (CakePHP) 2.x ブランチをベースにローカル・ブランチを作成します。修正に取り組み、必要なだけ commit してください。ただし、注意点があります:

コーディング規約 に従ってください。

バグが修正されたこと、もしくは新機能が機能することが判るテストケースを追加してください。

理にかなったコミットを心がけ、コミット文は解りやすく簡潔に書いてください。

Pull Request を送信する

変更し終え、 CakePHP へとマージされる準備が整ったら、あなたのブランチを更新したくなることでしょう。

# master のトップに修正をリベース
git checkout master
git fetch upstream
git merge upstream/master
git checkout <branch_name>
git rebase master

これは作業開始以降に CakePHP で行われたすべての変更を fetch + merge します。その後に rebase 、つまり現在のコードの先端にあなたの変更を適用します。 rebase 中、コンフリクトに出会うかもしれません。もし rebase が早期終了したら、 git status で、どのファイルがコンフリクト/マージ失敗したのかを見ることができます。各コンフリクトを解決させてください。その後、 rebase を continue してください。

git add <filename> # コンフリクトしたファイルごとにこれを行ってください。
git rebase --continue

あなたのテストがすべて通過し続けているか確認してください。その後、あなたのブランチをあなたの Fork へと push します。

git push origin <branch-name>

ブランチを push した後 rebase した場合、force push を使用する必要があります。

git push --force origin <branch-name>

あなたのブランチが GitHub に上がったら、GitHub で Pull Request を送ってください。

変更対象のマージ先を選ぶ

Pull Request を作る際には、ベースとなるブランチが正しく選ばれているか良く確認してください。ひとたび Pull Request を作った後ではもう変更することはできません。

あなたの変更が バグ修正 であり、新機能を追加しておらず、現在のリリースに存在している既存の振る舞いを正すだけなら、マージ先として master を選んでください。

あなたの変更が 新機能 もしくはフレームワークへの追加なら、次のバージョン番号のブランチを選んでください。たとえば、現在の安定版リリースが 3.2.10 なら、新機能を受け入れるブランチは 3.next になります。

あなたの変更が既存の機能性を壊すものであったり、API の仕様を変えるものであるなら、次のメジャーリリースを選ばなければなりません。たとえば、現在のリリースが 3.2.2 なら、次に既存の振る舞いを変更できるのは 4.x となりますので、そのブランチを選んでください。

注釈

あなたが貢献したすべてのコードは MIT License に基づき CakePHP にライセンスされることを覚えておいてください。 Cake Software Foundation [https://cakefoundation.org/old] がすべての貢献されたコードの所有者になります。貢献する人は CakePHP Community Guidelines [https://cakephp.org/get-involved] に従うようお願いします。

メンテナンス・ブランチへとマージされたすべてのバグ修正は、コアチームにより定期的に次期リリースにもマージされます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コーディング規約

CakePHP の開発者はコーディング規約として下記のルールに加え、 PSR-12 coding style guide [https://www.php-fig.org/psr/psr-12/] にも従って頂くことになります。

その他の CakePHP プラグイン等 (訳注:原文 CakeIngredients 、ケーキの材料のこと) の開発でも同じ規約に従うことが推奨されます。

CakePHP Code Sniffer [https://github.com/cakephp/cakephp-codesniffer] を使って、コードが規約に沿っているかどうかをチェックすることができます。

新しい機能の追加

新しい機能は、そのテストが無い限り、追加してはいけません。このテストはリポジトリーにコミットされる前にパスする必要があります。

IDE の設定

IDE が空白の "trim right" の設定がされているかどうかを確かめてください。その設定で、行の空白は残りません。

最新の IDE は、 .editorconfig ファイルをサポートします。CakePHP アプリのスケルトンは、デフォルトで提供しています。すでにデフォルトでベストプラクティスが入っています。

IDE との互換性を最大限にしたい場合、 IdeHelper [https://github.com/dereuromark/cakephp-ide-helper] プラグインの使用をお勧めします。アノテーションを最新の状態に保ち、IDE がすべてのクラスの仕組みを完全に理解し、より良いタイプヒントと自動補完を提供するのを支援します。

インデント

インデントには４つの空白を用います。

従って、インデントはこのようになります。

// 基本レベル
    // レベル1
        // レベル2
    // レベル1
// 基本レベル

または:

$booleanVariable = true;
$stringVariable = '大鹿';
if ($booleanVariable) {
    echo '真偽値は true です';
    if ($stringVariable === '大鹿') {
        echo '大鹿に遭遇しました';
    }
}

あなたが、複数行にわたる関数呼び出しを使用している場合、以下のガイドラインに従ってください。

複数行にわたる関数呼び出しのカッコを開く時には、行末になければなりません。

複数行にわたる関数呼び出しの中の１行ごとに１引数のみ許可します。

複数行にわたる関数呼び出しの閉じカッコは、１行にしなければなりません。

例として、以下の形式が使用されていた場合、

$matches = array_intersect_key($this->_listeners,
                array_flip(preg_grep($matchPattern,
                    array_keys($this->_listeners), 0)));

代わりに以下を使用してください。

$matches = array_intersect_key(
    $this->_listeners,
    array_flip(
        preg_grep($matchPattern, array_keys($this->_listeners), 0)
    )
);

行の長さ

コードを読みやすくするために、だいたい 100 文字程度の長さで改行することを推奨します。 80 文字や 120 文字に制限することで、複雑なロジックや式を関数によって分割するだけでなく、関数やオブジェクトに、短く、より表現的な名前をつけることが必要になります。行を 120 文字より長くしてはいけません。

要約すると:

100 文字がソフトな制限

120 文字がハードな制限

制御構造

制御構造は例えば "if"、"for"、"foreach"、"while"、"switch"などです。下記に、 "if" の例を示します。

if ((expr_1) || (expr_2)) {
    // action_1;
} elseif (!(expr_3) && (expr_4)) {
    // action_2;
} else {
    // default_action;
}

制御構造では1個の空白が最初の丸括弧の前に、1個の空白が最後の丸括弧と開き中括弧の間にある必要があります。

制御構造では、必要でなくとも常に中括弧を使います。これはコードの可読性を高め、論理エラーが発生しにくくなります。

開き中括弧は制御構造と同じ行に置かれる必要があります。閉じ中括弧は新しい行に置かれ、制御構造と同じレベルのインデントがされている必要があります。中括弧内に含まれているステートメントは新しい行で始まり、その中に含まれるコードは、新しいレベルのインデントが付けられる必要があります。

インラインの代入は、制御構造の中で使用するべきではありません。

// 間違い＝中括弧が無い、ステートメントの場所が悪い
if (expr) statement;

// 間違い＝中括弧が無い
if (expr)
    statement;

// よろしい
if (expr) {
    statement;
}

// 間違い＝インラインの代入
if ($variable = Class::function()) {
    statement;
}

// よろしい
$variable = Class::function();
if ($variable) {
    statement;
}

三項演算子

三項演算子は、三項演算子全体が1行に収まる場合に許容されます。長い三項演算子は if else ステートメントに分割するべきです。どのような場合でも、三項演算子はネストしてはいけません。見やすさのために、丸括弧を三項の条件チェックの周りに使ってもかまいません。

//良い。シンプルで読みやすい
$variable = isset($options['variable']) ? $options['variable'] : true;

//ネストされた三項はダメ
$variable = isset($options['variable']) ? isset($options['othervar']) ? true : false : false;

テンプレートファイル

テンプレートファイル (拡張子が .php のファイル) 内では、開発者は、キーワードの制御構造を使用する必要があります。キーワードの制御構造を使うと、複雑なテンプレートファイルが読みやすくなります。制御構造は、大きい PHP ブロック内、または別々の PHP タグに含めることができます。

<?php
if ($isAdmin):
    echo '<p>You are the admin user.</p>';
endif;
?>
<p>The following is also acceptable:</p>
<?php if ($isAdmin): ?>
    <p>You are the admin user.</p>
<?php endif; ?>

比較

値の比較は、常に可能な限り厳密に行うようにしてください。もし厳格でないテストが意図的なものであれば、混乱を避けるためにコメントを残しておいたほうがいいかもしれません。

変数が null かどうかのテストの場合は、厳密なチェックを使用することを推奨します。

if ($value === null) {
    // ...
}

チェック対象の値は右側に配置してください(※ヨーダ記法を避けるということ)。

// 非推奨
if (null === $this->foo()) {
    // ...
}

// 推奨
if ($this->foo() === null) {
    // ...
}

関数の呼び出し

関数は、関数の名前と開き括弧の間に空白を入れて呼び出してはいけません。関数呼び出しの引数各々に対して単一の空白がある必要があります。

$var = foo($bar, $bar2, $bar3);

上記をご覧の通り、イコール記号 (=) の両サイドには単一の空白がある必要があります。

メソッドの定義

メソッドの定義の例:

public function someFunction($arg1, $arg2 = '')
{
    if (expr) {
        statement;
    }

    return $var;
}

デフォルトを用いた引数は、関数の定義の最後に置く必要があります。関数は何かを、少なくとも true か false を、関数呼び出しが成功したかどうかを判定できるように、返すように作ってみてください。

public function connection($dns, $persistent = false)
{
    if (is_array($dns)) {
        $dnsInfo = $dns;
    } else {
        $dnsInfo = BD::parseDNS($dns);
    }

    if (!($dnsInfo) || !($dnsInfo['phpType'])) {
        return $this->addError();
    }

    return true;
}

イコール記号の両サイドには空白を置きます。

アーリーリターン

「アーリーリターン」テクニックを活用し、不必要なネストを避けるようにしてください。

public function run(array $data)
{
    ...
    if (!$success) {
        return false;
    }

    ...
}

public function check(array $data)
{
    ...
    if (!$success) {
        throw new RuntimeException(...);
    }

    ...
}

これは、コードの流れをシンプルかつ容易に保つのに役立ちます。

タイプヒンティング

オブジェクトや配列を期待する引数はタイプヒンティングを指定することができます。しかしながらタイプヒンティングはコストフリーではないので、public メソッドにだけ指定します。

/**
 * メソッドの説明。
 *
 * @param \Cake\ORM\Table $table 使用するテーブルクラス
 * @param array $array 配列。
 * @param callable $callback コールバック。
 * @param bool $boolean 真偽値。
 */
public function foo(Table $table, array $array, callable $callback, $boolean)
{
}

ここで $table は \Cake\ORM\Table のインスタンスで、また $array は array でなければならず、 $callback は callback (有効なコールバック) 型でなければなりません。

ちなみに、もし $array が \ArrayObject のインスタンスでも受け付けるようにしたい場合は、 array のタイプヒントを指定してプリミティブ型だけを受け入れるようにするべきではありません。

/**
 * メソッドの説明。
 *
 * @param array|\ArrayObject $array 配列。
 */
public function foo($array)
{
}

無名関数 (クロージャー)

無名関数の定義は PSR-12 [https://www.php-fig.org/psr/psr-12/] コーディングスタイルガイドに従ってください。そこでは function キーワードの後ろに空白１つ、 use キーワードの前後に空白１つずつが必要であると宣言されています。

$closure = function ($arg1, $arg2) use ($var1, $var2) {
    // code
};

メソッドチェーン

メソッドチェーンは複数の行にまたがる複数のメソッドとなり、空白４つでインデントする必要があります。

$email->from('foo@example.com')
    ->to('bar@example.com')
    ->subject('A great message')
    ->send();

コードのコメント

全てのコメントは英語で書かれ、コードのコメントブロックを明確な方法で記述する必要があります。

コメントは以下の phpDocumentor [https://phpdoc.org] タグを含めることができます。

@deprecated [https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/deprecated.html] @version <vector> <description> 形式を使用して、 version と description は必須です。バージョンは非推奨のバージョンを示します。

@example [https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/example.html]

@ignore [https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/ignore.html]

@internal [https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/internal.html]

@link [https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/link.html]

@see [https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/see.html]

@since [https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/since.html]

@version [https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/version.html]

PhpDoc タグは Java の JavaDoc タグによく似ています。タグはドキュメントブロックの行の最初のもののみ処理されます。例:

/**
 * タグの例。
 *
 * @author このタグは解析されますが、この @version は無視されます
 * @version 1.0 このタグも解析されます
 */

/**
 * インライン phpDoc タグの例。
 *
 * この関数は世界征服のために foo() を使って身を粉にして働きます。
 *
 * @return void
 */
function bar()
{
}

/**
 * Foo function.
 *
 * @return void
 */
function foo()
{
}

ファイルの最初のブロック以外のコメントブロックは、常に新しい行を先に置く必要があります。

変数の型

ドキュメントブロックで使う変数の型:

型名

説明

mixed

型が定義されていない(もしくは複数定義されている)変数。

int

Integer 型の変数 (整数)。

float

Float 型 (小数点のある数値)。

bool

論理型 (true または false)。

string

String 型 (" " や ' ' で囲まれるすべての値)。

null

Null 型。通常は他の型と一緒に使われる。

array

配列型。

object

オブジェクト型。可能なら特定のクラス名を指定するべきです。

resource

リソース型 (例えば mysql_connect() の戻り値)。型を mixed に指定する場合、不明 (unknown) なのか、取りうる型が何なのかを指し示すべきということを覚えていてください。

callable

呼び出し可能な関数。

パイプ文字を使って型を組合せます。

int|bool

３つ以上の型の場合は mixed を使うほうが最良です。

チェーンのように自分自身のオブジェクトを返すような場合は代わりに $this を使ってください。

/**
 * Foo function.
 *
 * @return $this
 */
public function foo()
{
    return $this;
}

ファイルの読み込み

include 、 require 、 include_once そして require_once は括弧を付けないようにしてください。

// 間違い = 括弧あり
require_once('ClassFileName.php');
require_once ($class);

// よろしい = 括弧なし
require_once 'ClassFileName.php';
require_once $class;

クラスまたはライブラリーを伴うファイルを読み込む場合、 require_once [https://php.net/require_once] 関数のみを常に使用してください。

PHP タグ

常にショートタグ (<? ?>) の代わりに、ロングタグ (<?php ?>) を使ってください。テンプレートファイル (.php) の中では適宜、ショート Echo を使ってください。

Echoの短縮記法

テンプレートファイルの中でEchoの短縮記法を <?php echo の代わりに使ってください。開始タグ、空白１つ、 変数もしくは echo とその引数、半角１つ、閉じタグのように記述してください。

// NG = セミコロンがあり、空白もない
<td><?=$name;?></td>

// OK = 空白があり、セミコロンもない
<td><?= $name ?></td>

PHP 5.4 以降、ショート Echo タグ (<?=) は、前述の 'ショートタグ' とは別物として扱われます。 ini ディレクティブの short_open_tag 設定にかかわらず、常に有効です。

命名規約

関数

全ての関数はキャメルバックで書いてください。

function longFunctionName()
{
}

クラス

クラス名はキャメルケースで書かれる必要があります。例:

class ExampleClass
{
}

変数

変数名はできる限り説明的に、しかしできる限り短くもしてください。すべての変数は小文字で始まり、複数の単語の場合はキャメルバックで書く必要があります。オブジェクトを参照する変数は、何らかの方法で変数がオブジェクトとなっているクラスに関連したものになるべきです。例:

$user = 'John';
$users = ['John', 'Hans', 'Arne'];

$dispatcher = new Dispatcher();

メンバーのアクセス権(visibility)

メソッドと変数には、PHP の public, protected そして private キーワードを指定してください。

アドレスの例示

全ての URL とメールアドレスの例には、「example.com」、「example.org」、「example.net」を使用してください。例を挙げます。

Eメール: someone@example.com

WWW: http://www.example.com

FTP: ftp://ftp.example.com

"example.com" ドメインはこの (RFC 2606 [https://datatracker.ietf.org/doc/html/rfc2606.html] を見てください) 為に予約されており、ドキュメント内の説明や例として使うことが推奨されています。

ファイル

クラスを含まないファイルの名前は、小文字でアンダースコアー化される必要があります。例:

long_file_name.php

キャスト

次のキャストを使用します。

型

説明

(bool)

boolean にキャスト。

(int)

integer にキャスト。

(float)

float にキャスト。

(string)

string にキャスト。

(array)

array にキャスト。

(object)

object にキャスト。

できるなら intval($var) よりも (int)$var を、 floatval($var) よりも (float)$var を使ってください。

定数

定数は大文字で定義する必要があります。

define('CONSTANT', 1);

もし定数の名前が複数の単語でできている場合は、アンダースコアー文字によって分割する必要があります。例:

define('LONG_NAMED_CONSTANT', 2);

empty()/isset() の使用に注意

empty() は、使いやすい関数ですが、エラーの隠蔽と '0' や 0 が与えられた際に意図しない効果を引き起こします。変数やプロパティーがすでに定義されていた場合、 empty() の利用は推奨されません。変数を操作する際、empty() ではなく boolean 型への強制変換を用いましょう。プロパティーのempty判定に関しては、CakePHP独自の仕組みによる問題もあり、特に慎重に検討する必要があります(!issetなプロパティの場合はCakePHPの場合はマジックメソッドが走るためです)。

function manipulate($var)
{
    // 推奨しません。 $var は引数を通じてすでにスコープ内で定義されています。
    if (empty($var)) {
        // ...
    }

    // boolean 型への強制変換を使用。
    if (!$var) {
        // ...
    }
    if ($var) {
        // ...
    }
}

全てのエンジニアがissetとemptyの違いをに完璧に理解しているわけではありません。定義されたプロパティーを扱っている際、 empty()/isset() ではなく null チェックを使ってください。これによりリーダブルなコードを維持することができます。

class Thing
{
    private $property; // 定義済み。つまりnullです。

    public function readProperty()
    {
        // issetはnullに対してfalseを返すことを体感的に理解
        // していないエンジニアが多いため、これは推奨しません。
        if (!isset($this->property)) {
            // ...
        }
        // 見た目どおりで分かりやすいため、推奨します。
        if ($this->property === null) {

        }
    }
}

配列を操作する際、 empty チェックを使うよりも、デフォルト値をマージする方が良いです。デフォルト値をマージすることによって、必要なキーが定義されることを保証できます。

function doWork(array $array)
{
    // empty チェックを避けるためにデフォルト値をマージ
    $array += [
        'key' => null,
    ];

    // 推奨しません。キーはすでにセットされています。
    if (isset($array['key'])) {
        // ...
    }

    // 推奨します。
    if ($array['key'] !== null) {
        // ...
    }
}

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

後方互換性ガイド

アプリケーションを簡単に、円滑にアップグレードができるようになっていることは重要なことです。メジャーリリースのマイルストーンでのみ互換性を破棄するのはそのためです。全ての CakePHP プロジェクトで使用する全般的なガイドラインである、セマンティックバージョニング [https://semver.org/] について理解したほうが良いでしょう。セマンティックバージョニングとは簡単に言うと、(2.0、3.0、4.0 のような) メジャーリリースのみ後方互換性を破棄することができ、(2.1、3.1、3.2 のような) マイナーリリースが新しい機能の導入をするが互換性を破棄することは許されず、 (2.1.2、3.0.1 のような) バグフィックスリリースは新機能の追加はせず、バグの修正かパフォーマンスの向上のみにすることを意味します。

注釈

非推奨は、フレームワークの次のメジャーバージョンで削除されます。非推奨のコメントや移行ガイドに記載されているように、コードにマイナーリリースの変更を早期に適用することをお勧めします。

それぞれのリリースに予定する変更を明確にするために、CakePHP を使う開発者と CakePHP の開発者のために、マイナーリリースでされ得る変更予定の助けとなる更に詳しい情報があります。メジャーリリースは必要に応じて多くの破壊的変更を含むことができます。

移行ガイド

メジャー・マイナーの各リリースについて、CakePHP チームは移行ガイドを提供します。このガイドはそれぞれのリリースの新機能と非互換な変更を説明します。これは Cookbook の 付録 セクションで見ることができます。

CakePHP の使用

CakePHP を用いてアプリケーションを構築している場合、次のガイドラインで示す堅牢性 (stability) が期待できます。

インターフェイス

メジャーリリース以外では、CakePHP が提供するどんな既存のメソッドのインターフェイスも変更 されません 。新しいメソッドは、既存のインターフェイスには追加 されません 。

クラス

CakePHP が提供するクラスを構成する、アプリケーションから使われる public なメソッドとプロパティーがメジャーリリース以外で後方互換性が保証されます。

注釈

CakePHP のいくつかのクラスは @internal API ドキュメントタグがつけられています。これらのクラスは堅牢 ではなく 、後方互換性は約束されていません。

マイナーリリースでは、クラスに新しいメソッドが追加されることがあり、既存のメソッドは新しい引数が追加されることもあります。新しい引数は必ずデフォルト値を持ちますが、異なる引数の形式でメソッドをオーバーライドしていると致命的な (fatal) エラーになることがあります。新しい引数が追加されたメソッドは、そのリリースの移行ガイドに掲載されます。

下記のテーブルはいくつかのユースケースと CakePHP に予定する互換性についての概要となります。

すること

互換性

クラスに対するタイプヒント

有り

新しいインスタンスの作成

有り

クラスの継承

有り

public プロパティーへのアクセス

有り

public メソッドの呼び出し

有り

クラスを継承して...

public プロパティーをオーバーライド

有り

protected プロパティーにアクセス

無し [1]

protected プロパティーをオーバーライド

無し [1]

protected メソッドをオーバーライド

無し [1]

protected メソッドの呼び出し

無し [1]

public プロパティーの追加

無し

public メソッドの追加

無し

オーバーライドされたメソッドへの引数の追加

無し [1]

既存メソッドの引数へのデフォルト値の追加

有り

CakePHP での作業

CakePHP をより良くする手助けをしようという場合、機能の追加・変更時に以下のガイドラインに沿うように頭にとどめておいてください。

マイナーリリースでは次のことができます。

マイナーリリースでできること

クラス

クラスの削除

不可

インターフェイスの削除

不可

トレイトの削除

不可

final にする

不可

abstract にする

不可

名前の変更

可 [2]

プロパティー

public プロパティーの追加

可

public プロパティーの削除

不可

protected プロパティーの追加

可

protected プロパティーの削除

可 [3]

メソッド

public メソッドの追加

可

public メソッドの削除

不可

protected メソッドの追加

可

親クラスへの移動

可

protected メソッドの削除

可 [3]

可視性の減少

不可

メソッド名の変更

可 [2]

デフォルト値つき引数の新規追加

可

既存メソッドへの必須引数の新規追加

不可

既存引数からのデフォルト値の削除

不可

void 型メソッドの変更

可

[1] (1,2,3,4,5)

マイナーリリースでコードが破壊される 恐れが あります。詳細は移行ガイドをチェックしてください。

[2] (1,2)

古いクラス名・メソッド名を利用可能なように残しておくことで名前の変更ができます。通常、名前の変更は重要な利点を持っていない限り避けられます。

[3] (1,2)

出来る限り避けましょう。削除したことは移行ガイドに掲載する必要があります。

非推奨

各マイナーリリースでは、機能が非推奨になる可能性があります。機能が非推奨になると、API ドキュメントや実行時の警告が追加されます。実行時エラーは、コードが壊れる前に更新する必要があるコードを見つけるのに役立ちます。実行時の警告を無効にするには、 Error.errorLevel 設定値を使用します。

// config/app.php の中で
// ...
'Error' => [
    'errorLevel' => E_ALL ^ E_USER_DEPRECATED,
]
// ...

これで、実行時の非推奨警告を無効にします。

Experimental Features

Experimental features are not included in the above backwards compatibility promises. Experimental features can have breaking changes made in minor releases as long as they remain experimental. Experiemental features can be identified by the warning in the book and the usage of @experimental in the API documentation.

Experimental features are intended to help gather feedback on how a feature works before it becomes stable. Once the interfaces and behavior has been vetted with the community the experimental flags will be removed.

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

インストール

CakePHP は素早く簡単にインストールできます。最小構成で必要なものは、ウェブサーバーと CakePHP のコピー、それだけです！本項では主に(最も一般的である) Apache でのセットアップに主眼を置いていますが、 CakePHP は nginx や lighttpd や Microsoft IIS のような様々なウェブサーバーで動きます。

システム要件

HTTP サーバー。例: Apache。mod_rewrite が推奨されますが、必須ではありません。

PHP 7.4 以上 (PHP 8.2 も含む)

mbstring PHP エクステンション

intl PHP エクステンション

simplexml PHP エクステンション

PDO PHP エクステンション

注釈

Laragon / XAMPP / WAMP のいずれでも、mbstring 拡張が初期インストール状態で動きます。

Laragon / XAMPP では intl 拡張は同梱されていますが、 php.ini の extension=php_intl.dll のコメントを外して コントロールパネルからサーバーの再起動を行う必要はあります。

WAMP では intl 拡張は最初からアクティブになっているのですが動作しません。動作させるためには php フォルダー（初期状態では C:\wamp\bin\php\php{version} ）にある icu*.dll というファイルを全て、apache の bin ディレクトリー（ C:\wamp\bin\apache\apache{version}\bin ）にコピーしてから、全てのサービスを再起動すれば動くようになります。

データベースエンジンは必ずしも必要ではありませんが、ほとんどのアプリケーションはこれを活用することが想像できます。 CakePHP は種々のデータベース・ストレージのエンジンをサポートしています：

MySQL (5.6 以上)

MariaDB (5.6 以上)

PostgreSQL (9.4 以上)

Microsoft SQL Server (2012 以上)

SQLite 3

注釈

組み込みのドライバーは全て PDO を必要とします。正しい PDO 拡張モジュールがインストールされているか必ず確かめてください。

CakePHP のインストール

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

php -v

PHP 7.4 (CLI) 以上がインストールされていなければなりません。ウェブサーバー版の PHP もまた 7.4 以上でなければりませんし、コマンドラインインターフェース (CLI) 版と同じバージョンを使用してください。

Composer のインストール

CakePHP の公式のインストール方法として、依存性管理ツール Composer [https://getcomposer.org] を使用します。

Linux や macOS に Composer をインストール

公式の Composer ドキュメント [https://getcomposer.org/download/] に書かれたインストーラースクリプトを実行し、Composer をインストールするために指示に従ってください。

composer.phar を指定したパスのディレクトリーに移すために以下のコマンドを実行してください。

mv composer.phar /usr/local/bin/composer

Windows に Composer をインストール

Windows 環境なら、 こちら [https://github.com/composer/windows-setup/releases/] から Windows インストーラーをダウンロードできます。Composer の Windows インストーラーについての詳細は、 README [https://github.com/composer/windows-setup] をご覧ください。

CakePHP プロジェクトを作成

以上で、Composer をダウンロードとインストールしましたので、 my_app_name フォルダーに CakePHP の新しいアプリケーションを作成してください。下記の composer コマンドを実行して作成します。

php composer.phar create-project --prefer-dist cakephp/app:"4.*" my_app_name

または Composer にパスが通っているのであれば下記のコマンドも使えます。

composer self-update && composer create-project --prefer-dist cakephp/app:"4.*" my_app_name

一度 Composer がアプリケーションの雛形とコアライブラリーをダウンロードしたら、インストールした CakePHP アプリケーションを Composer から操作できるようにしておくべきです。必ず composer.json と composer.lock ファイルは残しておきましょう。

これでインストールした CakePHP アプリケーションにアクセスして、デフォルトのホームページを見ることができるようになりました。このページの内容を変更するには templates/Pages/home.php を編集してください。

composer によるインストールが推奨されますが、 Github [https://github.com/cakephp/cakephp/tags] にはプリインストール版もあります。このファイルにはアプリケーションの雛形と全てのベンダーパッケージが同梱されています。また、 composer.phar も入っていますので、あなたのさらなる使用のために必要なものは全てそろっているのです。

CakePHP の変更に合わせて最新の状態に保つ

デフォルトではあなたのアプリケーションの composer.json は下記のようになっています。

"require": {
    "cakephp/cakephp": "4.0.*"
}

php composer.phar update を実行するたびに、このマイナーバージョンのパッチリリースが手に入ります。代わりに ^4.0 に変更して、 4.x ブランチの最新の安定版マイナーリリースを手に入れることができます。

Oven を使用したインストール

CakePHP を手軽にインストールするための別の方法は、 Oven [https://github.com/CakeDC/oven] です。これは、必要なシステム要件をチェック、CakePHP アプリケーションのスケルトンをインストール、そして、開発環境をセットアップするシンプルな PHP スクリプトです。

インストールが完了すれば、あなたの CakePHP アプリケーションはすぐに使えます！

注釈

重要: これはデプロイスクリプトではありません。はじめて CakePHP をインストールする開発者を助け、開発環境を素早くセットアップすることが狙いです。本番環境では、ファイルのパーミッション、バーチャルホストの設定など、いくつかの要因を考慮する必要があります。

パーミッション

CakePHP は、幾つかの操作のために tmp ディレクトリーを使用します。モデルの定義や、ビューのキャッシュ、セッション情報などです。 logs ディレクトリーは、デフォルトの FileLog エンジンがログファイルを出力するために使われます。

そのため、 CakePHP をインストールしたら logs, tmp ディレクトリーとその全てのサブディレクトリーに、ウェブサーバーの実行ユーザーによる書き込み権限があることを必ず確認してください。composer によるインストール処理では、なるべく早く動かせるように tmp フォルダーとそのサブフォルダーに全ユーザーが書き込みできるようにしますが、これをウェブサーバーの実行ユーザーだけが書き込みできるようにパーミッション設定を変更すれば、より良いセキュリティ状態にすることができます。

よくある課題として、 logs と tmp ディレクトリーとサブディレクトリーは、ウェブサーバーとコマンドラインユーザーの両方で書き込み権限が必要、ということがあります。 UNIX システム上で ウェブサーバーユーザーとコマンドラインユーザーが異なる場合、パーミッションのプロパティー設定を確保するために、あなたのプロジェクトのアプリケーションディレクトリーで一度だけ以下のコマンドを実行してください。

HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\  -f1`
setfacl -R -m u:${HTTPDUSER}:rwx tmp
setfacl -R -d -m u:${HTTPDUSER}:rwx tmp
setfacl -R -m u:${HTTPDUSER}:rwx logs
setfacl -R -d -m u:${HTTPDUSER}:rwx logs

CakePHP コンソールツールを使用するためには、 bin/cake ファイルが実行可能である必要があります。 *nix または macOS 上では、以下を実行します。

chmod +x bin/cake

Windows 上では、 .bat ファイルはすでに実行可能なはずです。もし、Vagrant または、そのほかの仮想化環境を使用している場合、共有ディレクトリーが実行可能なパーミッションで共有される必要があります。 (設定方法は仮想化環境のドキュメントを参照してください。)

もし、なんらかの理由で、 bin/cake ファイルのパーミッションを変更できない場合、 CakePHP コンソールは、以下のように実行できます。

php bin/cake.php

開発サーバー

開発用インストールは、CakePHP を最も速くインストールする方法です。この例では、CakePHP のコンソールを使って PHP の組み込みウェブサーバーを起動して、あなたのアプリケーションに http://host:port という形式でアクセスできるようにします。app ディレクトリーで下記のコマンドを実行しましょう。

bin/cake server

引数のないデフォルト状態では、 http://localhost:8765/ であなたのアプリケーションにアクセスできます。

もしあなたの環境で localhost や 8765番ポートが使用済みなら、CakePHP のコンソールから下記のような引数を使って特定のホスト名やポート番号でウェブサーバーを起動することができます。

bin/cake server -H 192.168.13.37 -p 5673

こうすればあなたのアプリケーションには http://192.168.13.37:5673/ でアクセスできます。

これだけです！あなたの CakePHP アプリケーションは ウェブサーバーを設定することなく動きます。

注釈

サーバーが他のホストから到達できない場合、 bin/cake server -H 0.0.0.0 を試してください。

警告

開発サーバーは公開環境に使用するべきでは ありません 。これはあくまでも基本的な開発サーバーと位置付けられています。

もしあなたが本物のウェブサーバーを使いたいのであれば、インストールした CakePHP のファイルを（隠しファイルも含めて）ウェブサーバーのドキュメントルート配下に移動させます。これでブラウザーから移動先のディレクトリーを指定すれば、あなたのアプリケーションにアクセスすることができます。

公開用

公開用インストールは、さらに柔軟に CakePHP をセットアップする方法です。この方法を使えば、全てのドメインで１つの CakePHP アプリケーションを使う事も可能です。今回の例では、あなたがファイルシステムのどこに CakePHP をインストールしたとしても、 http://www.example.com といったようにアクセスできるようになるでしょう。 Apache ウェブサーバーでこの方法を使う場合は、 DocumentRoot を変更する権限が必要になるかもしれないことに注意が必要です。

これまでに紹介したいずれかの方法で、あなたが指定したディレクトリー（ここでは「/cake_install」を指定したとしましょう）にアプリケーションをインストールしたら、あなたのファイルシステムには下記のような環境ができているでしょう。

/cake_install/
    bin/
    config/
    logs/
    plugins/
    resources/
    src/
    templates/
    tests/
    tmp/
    vendor/
    webroot/ (このディレクトリーが DocumentRoot になります)
    .gitignore
    .htaccess
    .travis.yml
    composer.json
    index.php
    phpunit.xml.dist
    README.md

Apache を利用している開発者は、当該ドメインの DocumentRoot ディレクティブに下記のように指定します。

DocumentRoot /cake_install/webroot

あなたのウェブサーバーが正しく設定されていれば、これで http://www.example.com からあなたの CakePHP アプリケーションにアクセスできるようになります。

始動

さぁ、CakePHP の動作を見てみましょう。あなたが選んだ方法に応じて、ブラウザーから http://example.com/ あるいは http://localhost:8765/ にアクセスしてください。これで CakePHP のデフォルトのホーム画面と、データベースへの接続状態を表すメッセージが表示されるでしょう。

おつかれさまです！ これでもう 最初の CakePHP アプリケーション作成の準備ができました。

URL Rewriting

Apache

CakePHP は、展開した状態では mod_rewrite を使用するようになっており、自分のシステムでうまく動作するまで苦労するユーザーもいます。

ここでは、正しく動作させるために行うことをいくつか示します。まず始めに httpd.conf を見てください（ユーザーやサイト個別の httpd.conf ではなく、必ずシステムの httpd.conf を編集してください）。

これらのファイルはディストリビューションや Apache のバージョンによって大きく異なります。詳細については https://cwiki.apache.org/confluence/display/httpd/DistrosDefaultLayout を見てもよいかもしれません。

適切な DocumentRoot に対して .htaccess による設定の上書きを許可するよう、 AllowOverride に All が設定されている事を確認します。これは下記のように書かれているでしょう。

# Each directory to which Apache has access can be configured with respect
# to which services and features are allowed and/or disabled in that
# directory (and its subdirectories).
#
# First, we configure the "default" to be a very restrictive set of
# features.
<Directory />
    Options FollowSymLinks
    AllowOverride All
#    Order deny,allow
#    Deny from all
</Directory>

下記のように mod_rewrite が正しくロードされている事を確認します。

LoadModule rewrite_module libexec/apache2/mod_rewrite.so

多くのシステムでこれらはデフォルトではコメントアウトされているでしょうから、先頭の「#」の文字を削除する必要があります。

変更した後は、設定変更を反映するために Apache を再起動してください。

.htaccess ファイルが正しいディレクトリーにあることを確認してください。一部のOSでは、ファイル名が「.」から始まるファイルは隠しファイルとみなされ、コピーされないでしょう。

サイトのダウンロードページや Git リポジトリーからコピーした CakePHP が正しく解凍できているか、 .htaccess ファイルをチェックします。

CakePHP のアプリケーションディレクトリー（あなたが Bake でコピーした一番上のディレクトリー）にはこのように書いてあります。

<IfModule mod_rewrite.c>
   RewriteEngine on
   RewriteRule    ^$    webroot/    [L]
   RewriteRule    (.*) webroot/$1    [L]
</IfModule>

webroot ディレクトリーにはこのように書いてあります。

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

まだあなたの CakePHP サイトで mod_rewrite の問題が起きているなら、仮想ホスト (virtualhosts) の設定の変更を試してみるといいかもしれません。 Ubuntu 上なら、/etc/apache2/sites-available/default (場所はディストリビューションによる)のファイルを編集してください。このファイルの中で AllowOverride None が AllowOverride All に変更されているかを確かめてください。 つまり以下のようになるでしょう。

<Directory />
    Options FollowSymLinks
    AllowOverride All
</Directory>
<Directory /var/www>
    Options FollowSymLinks
    AllowOverride All
    Order Allow,Deny
    Allow from all
</Directory>

macOS 上での別解は、仮想ホストをフォルダーに向けさせるのに、 virtualhostx [https://clickontyler.com/virtualhostx/] ツールを使うことが挙げられます。

多くのホスティングサービス (GoDaddy、1and1) では、ウェブサーバーが既に mod_rewrite を使っているユーザーディレクトリーから配信されます。 CakePHP をユーザーディレクトリー (http://example.com/~username/cakephp/) または既に mod_rewrite を活用しているその他の URL 構造にインストールしているなら、 RewriteBase ステートメントを CakePHP が使う .htaccess ファイル (/.htaccess、/app/.htaccess、/app/webroot/.htaccess) に追加する必要があります。

これは RewriteEngine ディレクティブと同じセクションに追加でき、例えば webroot の .htaccess ファイルは以下のようになります。

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /path/to/app
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

この変更の詳細はあなたの環境構成に依存しますので、CakePHP と関係ない内容が含まれることがあります。詳しくは Apache のオンラインドキュメントを参照するようにしてください。

(オプション) 公開環境の設定では、必要ないリクエストは CakePHP で処理されないようにしましょう。 webroot の .htaccess ファイルを次のように修正してください。

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /path/to/app/
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_URI} !^/(webroot/)?(img|css|js)/(.*)$
    RewriteRule ^ index.php [L]
</IfModule>

上の例は、正しくないアセットを index.php へ送信せず、ウェブサーバーの 404 ページを表示します。

また、HTML で 404 ページを作成することもできますし、 ErrorDocument ディレクティブへ追記することで、CakePHP のビルトインの 404 ページを使うこともできます。

ErrorDocument 404 /404-not-found

nginx

nginx は Apache のような .htaccess ファイルを利用しませんので、サイトの設定で URLの書き換えルールを作成する必要があります。これは大抵 /etc/nginx/sites-available/your_virtual_host_conf_file に記載します。あなたの環境構成に応じて、このファイルを書き換えなければなりませんが、少なくとも PHP を FastCGI として稼働させる必要はあるでしょう。下記の設定は、リクエストを webroot/index.php にリダイレクトします。

location / {
    try_files $uri $uri/ /index.php?$args;
}

server ディレクティブの例は、次の通りです。

server {
    listen   80;
    listen   [::]:80;
    server_name www.example.com;
    return 301 http://example.com$request_uri;
}

server {
    listen   80;
    listen   [::]:80;
    server_name example.com;

    root   /var/www/example.com/public/webroot;
    index  index.php;

    access_log /var/www/example.com/log/access.log;
    error_log /var/www/example.com/log/error.log;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_intercept_errors on;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

注釈

最近の PHP-FPM の設定では、アドレス 127.0.0.1 の TCP 9000 ポートの代わりに unix php-fpm ソケッットを待ち受けるように設定します。もし、上記の設定で 502 bad gateway エラーになった場合、 TCP ポートの代わりに unix ソケットパスを使用するために fastcgi_pass を更新してください (例: fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;)。

NGINX Unit

NGINX Unit [https://unit.nginx.org] is dynamically configurable in runtime; the following configuration relies on webroot/index.php, also serving other .php scripts if present via cakephp_direct:

{
    "listeners": {
        "*:80": {
            "pass": "routes/cakephp"
        }
    },

    "routes": {
        "cakephp": [
            {
                "match": {
                    "uri": [
                        "*.php",
                        "*.php/*"
                    ]
                },

                "action": {
                    "pass": "applications/cakephp_direct"
                }
            },
            {
                "action": {
                    "share": "/path/to/cakephp/webroot/",
                    "fallback": {
                        "pass": "applications/cakephp_index"
                    }
                }
            }
        ]
    },

    "applications": {
        "cakephp_direct": {
            "type": "php",
            "root": "/path/to/cakephp/webroot/",
            "user": "www-data"
        },

        "cakephp_index": {
            "type": "php",
            "root": "/path/to/cakephp/webroot/",
            "user": "www-data",
            "script": "index.php"
        }
    }
}

To enable this config (assuming it's saved as cakephp.json):

# curl -X PUT --data-binary @cakephp.json --unix-socket \
       /path/to/control.unit.sock http://localhost/config

IIS7 (Windows hosts)

IIS7 はネイティブで .htaccess ファイルをサポートしていません。このサポートを追加できるアドオンがありますが、CakePHP のネイティブな書き換えを使うように IIS に htaccess のルールをインポートすることもできます。これをするには、以下のステップを踏んでください:

URL Rewrite Module 2.0 [https://www.iis.net/downloads/microsoft/url-rewrite] をインストールするために、Microsoftの Web Platform Installer [https://www.microsoft.com/web/downloads/platform.aspx] を使うか、直接ダウンロードします。(32ビット [https://download.microsoft.com/download/D/8/1/D81E5DD6-1ABB-46B0-9B4B-21894E18B77F/rewrite_x86_en-US.msi] / 64ビット [https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi])

CakePHP のルートフォルダーに web.config という名前の新しいファイルを作成してください。

メモ帳か XML が編集可能なエディターを使って、以下のコードを今作った web.config ファイルにコピーしてください。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Exclude direct access to webroot/*"
                  stopProcessing="true">
                    <match url="^webroot/(.*)$" ignoreCase="false" />
                    <action type="None" />
                </rule>
                <rule name="Rewrite routed access to assets(img, css, files, js, favicon)"
                  stopProcessing="true">
                    <match url="^(font|img|css|files|js|favicon.ico)(.*)$" />
                    <action type="Rewrite" url="webroot/{R:1}{R:2}"
                      appendQueryString="false" />
                </rule>
                <rule name="Rewrite requested file/folder to index.php"
                  stopProcessing="true">
                    <match url="^(.*)$" ignoreCase="false" />
                    <action type="Rewrite" url="index.php"
                      appendQueryString="true" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>

いったん IIS で利用可能な書き換えルールを含む web.config ファイルができたら、 CakePHP のリンク、CSS、JavaScript、再ルーティング (rerouting) は正しく動作するでしょう。

Lighttpd

Lighttpd does not make use of .htaccess files like Apache, so it is necessary to add a url.rewrite-once configuration in conf/lighttpd.conf. Ensure the following is present in your lighthttpd configuration:

server.modules += (
    "mod_alias",
    "mod_cgi",
    "mod_rewrite"
)

# Directory Alias
alias.url       = ( "/TestCake" => "C:/Users/Nicola/Documents/TestCake" )

# CGI Php
cgi.assign      = ( ".php" => "c:/php/php-cgi.exe" )

# Rewrite Cake Php (on /TestCake path)
url.rewrite-once = (
    "^/TestCake/(css|files|img|js|stats)/(.*)$" => "/TestCake/webroot/$1/$2",
    "^/TestCake/(.*)$" => "/TestCake/webroot/index.php/$1"
)

The above lines include PHP CGI configuration and example application configuration for an application on the /TestCake path.

URL リライティングを使わない場合

もしあなたのサーバーで mod_rewrite (かそれと互換性のあるモジュール) を使いたくなかったり使えない場合は、 CakePHP の組み込みのままの URL を使う必要があります。 config/app.php の下記のコメントを解除します。

'App' => [
    // ...
    // 'baseUrl' => env('SCRIPT_NAME'),
]

そして、下記の .htaccess ファイルを削除します。

/.htaccess
webroot/.htaccess

これで URL は www.example.com/controllername/actionname/param ではなく www.example.com/index.php/controllername/actionname/param という書式になるでしょう。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

構成設定

規約は CakePHP のすべてを設定する必要性を取り除きますが、データベースの認証情報のようないくつかの設定をする必要があります。

さらに、デフォルト値と実装をアプリケーションに合わせて差し替えできるようにするオプションの設定オプションもあります。

アプリケーションの設定

設定は一般的に PHP か INI ファイルに保存され、アプリケーションのブート処理時に読み込まれます。 CakePHP はデフォルトで一つの設定ファイルからなりますが、もし必要であれば追加の設定ファイルを加え、ブート処理コードで読み込むことができます。 Cake\Core\Configure は一般的な設定に利用され、基底クラスのアダプターで提供されている config() メソッドは設定をシンプルで明快にします。

アプリケーションのスケルトンは、デプロイ済のアプリケーションがにおいて環境間で変わらない設定を config/app.php ファイルにを含むことを特徴としています。 config/app_local.php ファイルには環境間で異なる設定データを含むべきです。これらのファイルはどちらも env() 関数を使って環境変数を参照しており、サーバ環境で設定値を設定することができます。

追加の設定ファイルの読み込み

もしアプリケーションに多くの設定オプションがあるとき、設定を複数のファイルに分けることで役に立ちます。 config/ ディレクトリーに複数ファイルを作成したのち、 bootstrap.php でそれらを読み込めます。

use Cake\Core\Configure;
use Cake\Core\Configure\Engine\PhpConfig;

Configure::setConfig('default', new PhpConfig());
Configure::load('app', 'default', false);
Configure::load('other_config', 'default');

環境変数

例えば Heroku のように、多くの現代的なクラウド事業者では、設定データのために環境変数を定義できます。 12factor app style [https://12factor.net/] の環境変数を通して CakePHP を設定することができます。環境変数を使用すると、アプリケーションの状態を少なくして、多くの環境にデプロイされたアプリケーションの管理が容易になります。

app.php を参照の通り、 env() 関数は、環境から設定を読み込むために使用され、アプリケーションの設定を構築します。 CakePHP は、データベースやログ、メール送信やキャッシュ設定のための DSN 文字列を使用して、各環境でこれらのライブラリーを簡単に変更できます。

CakePHP は、環境変数を使ってローカル開発を容易にするために dotenv [https://github.com/josegonzalez/php-dotenv] を活用します。アプリケーションの中に config/.env.default があるでしょう。このファイルを config/.env にコピーし、値をカスタマイズすることで、アプリケーションを設定できます。

config/.env ファイルをあなたのリポジトリーにコミットすることは避けてください。代わりに、プレースホルダー値を持つテンプレートとして config/.env.default を使用して、チームの全員が、どの環境変数が使用されているのか、それぞれの環境変数を把握する必要があります。

環境変数がセットされると、環境からデータを読むために env() を使用することができます。

$debug = env('APP_DEBUG', false);

env 関数に渡された２番目の値は、デフォルト値です。この値は、与えられたキーの環境変数が存在しない場合に使用されます。

一般的な設定

以下は、変数の説明と CakePHP アプリケーションに与える影響です。

debug

CakePHP のデバッグ出力を制御します。 false = 本番モードです。エラーメッセージやエラー、ワーニング出力を行いません。 true = エラーとワーニングが出力されます。

App.namespace

app クラスを見つけるための名前空間。

注釈

名前空間の設定を変更した時は、おそらく composer.json ファイルもまた、この名前空間を利用するように更新する必要があります。加えて、 php composer.phar dumpautoload を実行して、新しいオートローダーを作成してください。

App.baseUrl

もし CakePHP で Apache の mod_rewrite を利用する 予定がない 場合、この定義のコメントを解除してください。 .htaccess ファイルを取り除くことを忘れないでください。

App.base

アプリの存在するベースディレクトリーです。もし false をセットしたら、自動で検出されます。 false 以外の場合、書き出しは / から始め、 / で終わらないことを確認してください。例えば、 /basedir は有効な App.base です。さもなければ、 AuthComponent は適切に動かなくなります。

App.encoding

あなたのアプリケーションで使用するエンコードを指定します。このエンコーディングはレイアウトの charset の生成やエンティティーのエンコードに利用されます。それは、データベースのエンコードの値と合うように指定すべきです。

App.webroot

webroot のディレクトリーです。

App.wwwRoot

webroot のファイルパスです。

App.fullBaseUrl

アプリケーションのルートまでの (プロトコルを含む) 完全修飾ドメイン名です。これは完全な URL を生成する際に利用されます。デフォルトでは、この値は $_SERVER の環境情報から生成されます。しかし、パフォーマンスを最適化したり、他人が Host ヘッダーを操作するのを心配するならば、自分で指定すべきでしょう。 CLI 環境 (シェル) ではウェブサーバーとの関連が無いので fullBaseUrl を $_SERVER から読むことができません。もしシェルから URL を作成する必要がある場合 (例えばメールの送信) 、自力で指定する必要があります。

App.imageBaseUrl

webroot 以下の公開画像ディレクトリーのパスになります。もし CDN を利用している場合、CDN の場所をセットすべきです。

App.cssBaseUrl

webroot 以下の公開 css ディレクトリーのパスになります。もし CDN を利用している場合、CDN の場所をセットすべきです。

App.jsBaseUrl

webroot 以下の公開 js ディレクトリーのパスになります。もし CDN を利用している場合、CDN の場所をセットすべきです。

App.paths

クラスベースではないリソースの Configure のパスです。 plugins 、 templates 、 locales などのサブキーをサポートし、それぞれプラグイン、ビューテンプレート、ロケールファイルのパスを指定できます。

App.uploadedFilesAsObjects

アップロードされたファイルをオブジェクトとして表現するか(true)、配列として表現するか(false)を指定します。このオプションはデフォルトで有効になっています。

Security.salt

ハッシュ化の時に利用されるランダムな文字列です。この値は 対称キー暗号化の際、HMAC ソルトとして利用されます。

Asset.timestamp

適切なヘルパーを使用した際、アセットファイルの URL (CSS, JavaScript, Image) の終端にそのファイルの最終更新時間のタイムスタンプを加えます。有効な値:

(bool) false - 何もしません (デフォルト)。

(bool) true - debug が true の時にタイムスタンプを加えます。

(string) 'force' - 常にタイムスタンプを加えます。

Asset.cacheTime

アセットのキャッシュ時間を設定します。 アセットのための HTTP ヘッダー Cache-Control の max-age と HTTP ヘッダーの Expire の時間を決定します。 php の strtotime 関数 [https://php.net/manual/ja/function.strtotime.php] の書式を設定できます。デフォルトは +1 day です。

CDNの利用

静的ファイルを読み込むために 例えば、 https://mycdn.example.com/ (最後の / に注意してください) のようなCDNを使う場合は、 App.imageBaseUrl 、 App.cssBaseUrl 、 App.jsBaseUrl をCDNのURIに変更してください。

HtmlHelper経由で読み込まれた全ての画像、スクリプト、スタイルは、アプリケーションで使用されているのと同じ相対パスに合わせて、CDNの絶対パスを前に付けます。プラグインベースのアセットを使う場合には、特定の用途があることに注意してください。プラグインは絶対パスの ...BaseUrl URIを使った場合、プラグインのプレフィックスを使わないようになっています。

デフォルトの場合:

$this->Helper->assetUrl('TestPlugin.logo.png') は test_plugin/logo.png に変換されます。

App.imageBaseUrl を https://mycdn.example.com/ に設定した場合:

$this->Helper->assetUrl('TestPlugin.logo.png') は https://mycdn.example.com/logo.png に変換されます

データベースの設定

データベース接続の設定は データベース設定 を参照してください。

キャッシュの設定

CakePHP のキャッシュ設定は キャッシュ設定 を参照してください。

エラーと例外ハンドリング設定

エラーの設定と例外のハンドリングは エラーと例外設定 を参照してください。

ログの設定

CakePHP のログの設定は ロギング設定 を参照してください。

メールの設定

CakePHP のメールプリセットの設定は メールの設定 を参照してください。

セッションの設定

CakePHP のセッション操作の設定は セッションの設定を参照してください。

ルーティングの設定

ルーティングの設定やアプリケーションのルートの作成に関する詳しい情報はルーティングの設定 を参照してください。

追加のクラスパス

追加のクラスパスはアプリケーションで利用されるオートローダーを通じてセットアップされます。 composer を利用してオートローダーを作成する際、以下のように記述してコントローラーの代わりのパスを提供します。

"autoload": {
    "psr-4": {
        "App\\Controller\\": "/path/to/directory/with/controller/folders/",
        "App\\": "src/"
    }
}

上記は App と App\Controller 両方の名前空間のパスをセットアップします。一つ目のキーが検索され、そのパスにクラス/ファイルが含まれていなければ二つ目のキーが検索されます。次のようにして、一つの名前空間に複数のディレクトリーをマップすることもできます。

"autoload": {
    "psr-4": {
        "App\\": ["src/", "/path/to/directory/"]
    }
}

プラグイン、ビュー、テンプレート、ロケールのパス

プラグイン、ビューテンプレート、そしてロケールはクラスではないので、オートローダーの設定はありません。 CakePHP はこれらのリソースの追加パスをセットアップするための 3 つの Configure 変数を提供します。 config/app.php の中でこれらの変数をセットできます。

return [
    // 他の設定
    'App' => [
        'paths' => [
            'plugins' => [
                ROOT . DS . 'plugins' . DS,
                '/path/to/other/plugins/'
            ],
            'templates' => [
                ROOT . DS . 'templates' . DS,
                ROOT . DS . 'templates2' . DS
            ],
            'locales' => [
                ROOT . DS . 'resources' . DS . 'locales' . DS
            ]
        ]
    ]
];

パスはディレクトリーセパレーター付きで終了し、そうでないと適切に動作しないです。

Inflection の設定

Inflection の設定 を参照してください。

Configure クラス

class Cake\Core\Configure

CakePHP の Configure クラスはアプリケーションもしくは実行時の特定の値の保存と取り出しで利用されます。このクラスは何でも保存でき、その後他のどのような箇所でも利用できるため、確実に CakePHP の MVC デザインパターンを破壊する誘惑に気をつけてください。Configure クラスの主なゴールは、中央集権化された変数を維持し、たくさんのオブジェクト間で共有できることです。「設定より規約」を維持することを忘れないでください。そうすれば、CakePHP が提供する MVC 構造を壊すことはないでしょう。

設定データの書き込み

static Cake\Core\Configure::write($key, $value)

write() を利用してアプリケーションの設定にデータを保存します。

Configure::write('Company.name','Pizza, Inc.');
Configure::write('Company.slogan','Pizza for your body and soul');

注釈

$key 変数に ドット記法 を使用すると、 論理的なグループに設定を整理できます。

上記の例は一度の呼び出しでも記述できます。

Configure::write('Company', [
    'name' => 'Pizza, Inc.',
    'slogan' => 'Pizza for your body and soul'
]);

Configure::write('debug', $bool) を利用してデバッグと本番モードを即時に変更できます。これはとりわけ JSON のやりとりで使いやすく、デバッグ情報がパースの問題を引き起こす際です。

注釈

Configure::write()``を使って行われた設定の変更はすべてメモリに保存され、リクエストをまたいでも持続しないようになっています。

設定データの読み込み

static Cake\Core\Configure::read($key = null, $default = null)

アプリケーションから設定データを読み込むために利用されます。もしキーが指定されれば、そのデータが返却されます。上記の write() の例を取り上げると、以下のようにデータを読み込みます。

// 'Pizza Inc.' を返します
Configure::read('Company.name');

// 'Pizza for your body and soul' を返します
Configure::read('Company.slogan');

Configure::read('Company');
// 戻り値:
['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'];

// Company.nope は定義されていないので 'fallback' を返します
Configure::read('Company.nope', 'fallback');

もし $key が null のままだと、Configure のすべての値が返却されます。

static Cake\Core\Configure::readOrFail($key)

設定データを単に Cake\Core\Configure::read で読み込みますが、一方で key/value ペアを検索することを期待します。要求されたペアが存在しない場合、 RuntimeException が投げられます。

Configure::readOrFail('Company.name');    // 出力: 'Pizza, Inc.'
Configure::readOrFail('Company.geolocation');  // 例外を投げる

Configure::readOrFail('Company');

// 出力:
['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'];

定義されている設定データのチェック

static Cake\Core\Configure::check($key)

キー / パス が存在しているか、値が null でないかチェックする場合に利用します。

$exists = Configure::check('Company.name');

設定データの削除

static Cake\Core\Configure::delete($key)

アプリケーションの設定から情報を削除するために利用されます。

Configure::delete('Company.name');

設定データの読み書き

static Cake\Core\Configure::consume($key)

Configure からキーの読み込みと削除を行います。もしあなたが値の読み込みと削除を単一の動作で組み合わせたい時に便利です。

static Cake\Core\Configure::consumeOrFail($key)

Cake\Core\Configure::consume のように設定データを消費しますが、一方でキーと値のペアが見つかることを期待します。要求されたペアが存在しない場合、 RuntimeException が投げられます。

Configure::consumeOrFail('Company.name');    // 出力: 'Pizza, Inc.'
Configure::consumeOrFail('Company.geolocation');  // 例外を投げる

Configure::consumeOrFail('Company');

// 出力:
['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'];

設定ファイルの読み書き

static Cake\Core\Configure::config($name, $engine)

CakePHP は 2 つの組み込み設定ファイルエンジンを搭載しています。 Cake\Core\Configure\Engine\PhpConfig は Configure が昔から読んできた同じフォーマットで PHP の設定ファイル形式を読み込むことができます。 Cake\Core\Configure\Engine\IniConfig は ini 設定ファイル形式を読み込めます。詳細な ini ファイルの仕様は PHP マニュアル [https://php.net/parse_ini_file] を参照してください。コアの設定エンジンを利用するにあたり、Configure に Configure::config() を設定する必要があります。

use Cake\Core\Configure\Engine\PhpConfig;

// config から設定ファイルを読み込み
Configure::config('default', new PhpConfig());

// 別のパスから設定ファイルを読み込み
Configure::config('default', new PhpConfig('/path/to/your/config/files/'));

複数のエンジンを Configure に設定することができ、それぞれ異なった種類もしくはパスの設定ファイルを読み込みます。Configure のいくつかのメソッドを利用して設定されたエンジンとやり取りできます。どのエンジンのエイリアスが設定されているかチェックするには、 Configure::configured() が利用できます。

// 配置されたエンジンのエイリアスの配列を取得する
Configure::configured();

// 特定のエンジンが配置されているかチェックする
Configure::configured('default');

static Cake\Core\Configure::drop($name)

配置されたエンジンを取り除くことができます。 Configure::drop('default') は default のエンジンエイリアスを取り除きます。この先、そのエンジンを使って設定ファイルを読み込もうとする試みは失敗します。

Configure::drop('default');

設定ファイルの読み込み

static Cake\Core\Configure::load($key, $config = 'default', $merge = true)

一旦設定エンジンに Configure を設定すると、設定ファイルを読み込むことができます。

// 'default' エンジンオブジェクトを使用して my_file.php を読み込む
Configure::load('my_file', 'default');

読み込まれた設定ファイルは、自身のデータを Configure 内に存在している実行時の設定とマージします。これは存在している実行時の設定へ値の上書きや新規追加を可能とします。 $merge を true にセットすることで、存在している設定の値を上書きしなくなります。

設定ファイルの作成や編集

static Cake\Core\Configure::dump($key, $config = 'default', $keys = [])

全て、もしくはいくつかの Configure にあるデータを、ファイルや設定エンジンがサポートしているストレージシステムにダンプします。シリアライズのフォーマットは、$config で配置された設定エンジンから決定されます。例えば、もし 'default' エンジンが Cake\Core\Configure\Engine\PhpConfig ならば、生成されたファイルは Cake\Core\Configure\Engine\PhpConfig によって読み込み可能な PHP の設定ファイルになるでしょう。

'default' エンジンは PhpConfig のインスタンスとして考えられます。 Configure の全てのデータを my_config.php に保存します。

Configure::dump('my_config', 'default');

エラーハンドリング設定のみ保存します。

Configure::dump('error', 'default', ['Error', 'Exception']);

Configure::dump() は Configure::load() で読み込み可能な設定ファイルを変更もしくは上書きするために利用できます。

実行時の設定を保存

static Cake\Core\Configure::store($name, $cacheConfig = 'default', $data = null)

将来のリクエストのために、実行時の設定を保存することができます。設定は現在のリクエストのみ値を記憶するので、もしその後のリクエストで編集された設定情報を利用したければ、それを保存する必要があります。

// 現在の設定を 'user_1234' キーに 'default' キャッシュとして保存
Configure::store('user_1234', 'default');

保存された設定データはその名前のキャッシュ設定で存続します。キャッシュに関するより詳しい情報は キャッシュ を参照してください。

実行時の設定を復元

static Cake\Core\Configure::restore($name, $cacheConfig = 'default')

実行時の設定を保存すると、おそらくそれを復元して、再びそれにアクセスする必要があります。 Configure::restore() がちょうどそれに該当します。

// キャッシュから実行時の設定を復元する
Configure::restore('user_1234', 'default');

設定情報を復元する場合、それを保存する時に使われたのと同じ鍵、およびキャッシュ設定で復元することが重要です。復元された情報は、既存の実行時設定の最上位にマージされます。

設定エンジン

CakePHP は、さまざまなソースから設定ファイルを読み込む機能を提供し、独自の設定エンジンを作成するための [https://api.cakephp.org/4.x/class-Cake.Core.Configure.ConfigEngineInterface.html] プラガブルなシステムを備えています。組み込みの設定エンジンは次の通りです。

JsonConfig [https://api.cakephp.org/4.x/class-Cake.Core.Configure.Engine.JsonConfig.html]

IniConfig [https://api.cakephp.org/4.x/class-Cake.Core.Configure.Engine.IniConfig.html]

PhpConfig [https://api.cakephp.org/4.x/class-Cake.Core.Configure.Engine.PhpConfig.html]

デフォルトでは、アプリケーションは PhpConfig を使用します。

汎用テーブルの無効化

新しいアプリケーションを素早く作成したり、モデルを生成する時に便利な auto-tables とも呼ばれる汎用テーブルクラスを利用していますが、汎用テーブルクラスは、ある場面ではデバッグが困難になることがあります。

DebugKit の SQL パネルから DebugKit 経由で汎用テーブルクラスからクエリーが発行されたかどうかを確認できます。もし、なおも auto-tables によって引き起こされたかもしれない問題を診断するのに困っている場合、次のように、 CakePHP が固有のクラスを使用する代わりに、暗黙的に汎用的な Cake\ORM\Table を使用する時に例外を投げることができます。

// bootstrap.php の中で
use Cake\Event\EventManager;
use Cake\Http\Exception\InternalErrorException;

$isCakeBakeShellRunning = (PHP_SAPI === 'cli' && isset($argv[1]) && $argv[1] === 'bake');
if (!$isCakeBakeShellRunning) {
    EventManager::instance()->on('Model.initialize', function($event) {
        $subject = $event->getSubject();
        if (get_class($subject) === 'Cake\ORM\Table') {
            $msg = sprintf(
                'データベーステーブル %s のテーブルクラスを登録する時、テーブルクラスが見つからないか、エイリアスが不正です。',
                $subject->getTable());
            throw new InternalErrorException($msg);
        }
    });
}

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ルーティング

class Cake\Routing\RouterBuilder

ルーティングは URL とコントローラーのアクションをマップするツールを提供します。ルートを設定することで、アプリケーションの実装方法を URL の構造から分離できます。

CakePHP でのルーティングはまた パラメーターの配列を URL 文字列に変換するリバースルーティングというアイディアも含まれます。リバースルーティングを使用することによって、アプリケーションの URL の構造を全部のコードの書き直しをせずに再調整できます。

クイックツアー

ここでは、 CakePHP の最も一般的なルーティングの方法について例を出して説明します。ランディングページとして何かを表示したい時がよくあるでしょう。そのときは、 routes.php ファイルに以下を加えます。

/** @var \Cake\Routing\RouteBuilder $routes */
$routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);

これはサイトのホームページにアクセスした時に ArticlesController の index メソッドを実行します。時々、複数のパラメーターを受け取る動的なルートが必要になると思います。それが必要になるケースは、例えば、記事の内容を表示するためのルートです。

$routes->connect('/articles/*', ['controller' => 'Articles', 'action' => 'view']);

上記のルートは、 /articles/15 のような URL を受け取り、 ArticlesController の view(15) メソッドを呼びます。しかし、これは /articles/foobar のような URL からのアクセスを防ぐわけではありません。もし、あなたが望むなら、いくつかのパラメーターを正規表現に従うように制限することができます。

// Using fluent interface
$routes->connect(
    '/articles/{id}',
    ['controller' => 'Articles', 'action' => 'view'],
)
->setPatterns(['id' => '\d+'])
->setPass(['id']);

// Using options array
$routes->connect(
    '/articles/{id}',
    ['controller' => 'Articles', 'action' => 'view'],
    ['id' => '\d+', 'pass' => ['id']]
);

上の例はスターマッチャーを新たにプレースホルダー :id に変更しました。プレースホルダーを使うことで、URL 部分のバリデーションができます。このケースでは \d+ という正規表現を使いました。なので、数字のみが一致します。最後に、 Router に id プレースホルダーを view() 関数の引数として渡すように pass オプションで伝えます。このオプションの詳細は後で説明します。

CakePHP の Router はルートを逆にできます。それは、一致するパラメーターを含む配列から、 URL 文字列を生成できることを意味します。

use Cake\Routing\Router;

echo Router::url(['controller' => 'Articles', 'action' => 'view', 'id' => 15]);
// 出力結果
/articles/15

ルートは一意の名前を付けることもできます。これは、リンクを構築する際に、ルーティングパラメーターをそれぞれ指定する代わりに、ルートを素早く参照することができます。

// routes.php の中で
$routes->connect(
    '/login',
    ['controller' => 'Users', 'action' => 'login'],
    ['_name' => 'login']
);

use Cake\Routing\Router;

echo Router::url(['_name' => 'login']);
// 出力結果
/login

ルーティングコードを DRY に保つために、Router は 'スコープ' (scopes) というコンセプトを持っています。スコープは一般的なパスセグメントを定義し、オプションとして、デフォルトにルーティングします。すべてのスコープの内側に接続されているルートは、ラップしているスコープのパスとデフォルトを継承します。

$routes->scope('/blog', ['plugin' => 'Blog'], function (RouteBuilder $routes) {
    $routes->connect('/', ['controller' => 'Articles']);
});

上記のルートは /blog/ と一致し、それを Blog\Controller\ArticlesController::index() に送ります。

アプリケーションの雛形は、いくつかのルートをはじめから持った状態で作られます。一度自分でルートを追加したら、デフォルトルートが必要ない場合は除去できます。

ルートを接続

コードを DRY に保つために 'ルーティングスコープ' を使用してください。ルーティングスコープはコードを DRY に保つためだけではなく、Router の操作を最適化します。このメソッドは / スコープがデフォルトです。スコープを作成しいくつかのルートを接続するために、 scope() メソッドを使います。

// config/routes.php 内で、
use Cake\Routing\Route\DashedRoute;

$routes->scope('/', function (RouteBuilder $routes) {
    // 標準のフォールバックルートを接続します。
    $routes->fallbacks(DashedRoute::class);
});

connect() メソッドは３つのパラメーターを持ちます。あなたが一致させたい URL テンプレート、ルート要素のためのデフォルト値、そしてルートのためのオプションの３つです。しばしば、オプションには、ルーターが URL の要素を一致することに役立つ正規表現ルールが含まれます。

ルートを定義するための基本のフォーマットは、次の通りです。

$routes->connect(
    '/url/template',
    ['targetKey' => 'targetValue'],
    ['option' => 'matchingRegex']
);

１番目のパラメーターは、Router にどの URL を制御しようとしているのか伝えます。この URL は普通のスラッシュで区切られた文字列ですが、ワイルドカード (*) や ルート要素を含むことができます。ワイルドカードは、すべての引数を受け付けることを意味します。 * なしだと、文字列に完全一致するものだけに絞られます。

URL が特定されたら、一致したときにどのような動作をするかを CakePHP に伝えるために connect() の残り２つのパラメーターを使います。２番目のパラメーターは、ルートの 'ターゲット' を定義します。 これは、配列または行先の文字列として定義できます。ルートターゲットの例をいくつか示します。

// アプリケーションのコントローラーへの配列ターゲット
$routes->connect(
    '/users/view/*',
    ['controller' => 'Users', 'action' => 'view']
);
$routes->connect('/users/view/*', 'Users::view');

// プレフィックス付きのプラグインコントローラーへの配列ターゲット
$routes->connect(
    '/admin/cms/articles',
    ['prefix' => 'Admin', 'plugin' => 'Cms', 'controller' => 'Articles', 'action' => 'index']
);
$routes->connect('/admin/cms/articles', 'Cms.Admin/Articles::index');

接続する最初のルートは、 /users/view から始まる URL と一致し、それらのリクエストを UsersController->view() にマップします。末尾の /* は、メソッド引数として追加の部分を渡すようにルーターに指示します。例えば、 /users/view/123 は、 UsersController->view(123) にマップされます。

上記は文字列ターゲットの例を示しています。文字列ターゲットは、ルートの宛先をコンパクトに定義する方法を提供します。文字列ターゲットの構文は次のとおりです。

[Plugin].[Prefix]/[Controller]::[action]

いくつかの文字列ターゲットの例です。

// アプリケーションのコントローラー
'Bookmarks::view'

// プレフィックス付きのアプリケーションコントローラー
Admin/Bookmarks::view

// プラグインのコントローラー
Cms.Articles::edit

// プレフィックス付きのプラグインコントローラー
Vendor/Cms.Management/Admin/Articles::view

先ほど、パスの追加の部分を取り込むために貪欲なスター (greedy star) /* を使用していましたが、 /** 流れ星 (trailing star) もあります。２つのアスタリスクをつなげると、 URL の残りを１つの引数として取り込みます。これは、 / を含む引数を使用したい時に便利です。

$routes->connect(
    '/pages/**',
    ['controller' => 'Pages', 'action' => 'show']
);

/pages/the-example-/-and-proof が URL として渡ってきたときに、 the-example-/-and-proof を引数として渡せます。

connect() の2番目のパラメーターは、デフォルトのルートパラメーターを構成する任意のパラメーターを定義できます。

$routes->connect(
    '/government',
    ['controller' => 'Pages', 'action' => 'display', 5]
);

この例では、 connect() の2番目のパラメーターを使用してデフォルトパラメーターを定義しています。もし、いろいろなカテゴリーの製品を顧客に対して提供するアプリケーションを作るのであれば、ルーティングすることを考えるべきです。上記は、 /pages/display/5 ではなく /government にリンクすることができます。

ルーティングの一般的な用途は、コントローラーとそのアクションの名前を変更することです。 /users/some_action/5 で users コントローラーにアクセスするのではなく、 /cooks/some_action/5 を使ってアクセスすることができます。次のルートでそれが処理できます。

$routes->connect(
    '/cooks/{action}/*', ['controller' => 'Users']
);

これは Router に /cooks/ で始まるすべての URL は UsersController に送るように伝えています。 アクションは :action パラメーターの値によって呼ばれるかどうか決まります。ルート要素 を使って、ユーザーの入力や変数を受け付けるいろいろなルーティングができます。上記のルーティングの方法は、貪欲なスター (greedy star） を使います。貪欲なスターは Router がすべての位置指定引数を受け取ることを意味します。それらの引数は 渡された引数 配列で有効化されます。

URL を生成するときにもルーティングは使われます。もし最初に一致するものがあった場合、 ['controller' => 'users', 'action' => 'some_action', 5] を使って /cooks/some_action/5 と出力します。

これまでに接続したルートは、任意の HTTP 動詞と一致します。REST API を構築している際、 HTTP アクションを異なるコントローラーメソッドにマップすることがよくあります。 RouteBuilder はヘルパーメソッドを提供し、特定の HTTP 動詞のルートをより簡単に定義します。

// GET リクエストへのみ応答するルートの作成
$routes->get(
    '/cooks/{id}',
    ['controller' => 'Users', 'action' => 'view'],
    'users:view'
);

// PUT リクエストへのみ応答するルートの作成
$routes->put(
    '/cooks/{id}',
    ['controller' => 'Users', 'action' => 'update'],
    'users:update'
);

上記のルートは、使用される HTTP 動詞に基づいて、同じ URL を異なるコントローラーアクションにマップします。GET リクエストは 'view' アクションに行き、PUT リクエストは 'update' アクションに行きます。次の HTTP ヘルパーメソッドがあります。

GET

POST

PUT

PATCH

DELETE

OPTIONS

HEAD

これらのメソッドはすべてルートインスタンスを返すので、 流暢なセッター を活用してルートをさらに設定することができます。

ルート要素

あなたは独自のルート要素を指定し、コントローラーのアクションのパラメーターを URL のどこに配置すべきなのかを定義することができます。リクエストされたとき、これらのルート要素の値は、コントローラーの $this->request->getParam() から取得できます。カスタムルート要素を定義した場合、正規表現をオプションで指定できます。これは CakePHP にどんな URL が正しいフォーマットなのかを伝えます。正規表現を使用しなかった場合、 / 以外の文字はパラメーターの一部として扱われます。

$routes->connect(
    '/{controller}/{id}',
    ['action' => 'view']
)->setPatterns(['id' => '[0-9]+']);

上記の例は、 /コントローラー名/{id} のような形の URL で、任意のコントローラーのモデルを表示するための、素早く作成する方法を示しています。 connect() に渡した URL は :controller と :id という２つのルート要素を指定します。この :controller 要素は CakePHP のデフォルトルート要素であるため、ルーターが URL のコントローラー名をどのように照合し識別するかを知っています。 :id 要素はカスタムルート要素で、 connect() の３番目のパラメーターに一致する正規表現を指定することで、より明確にする必要があります。

CakePHP は小文字とダッシュによって表された URL を :controller を使った時には出力しません。これを出力したい場合、上記の例を次のように書きなおしてください。

use Cake\Routing\Route\DashedRoute;

// 異なるルートクラスを持つビルダーを作成します。
$routes->scope('/', function ($routes) {
    $routes->setRouteClass(DashedRoute::class);
    $routes->connect('/{controller}/{id}', ['action' => 'view'])
        ->setPatterns(['id' => '[0-9]+']);
});

DashedRoute クラス :controller を確認し、 :plugin パラメーターを正しく小文字とダッシュによって表します。

注釈

ルート要素で使用する正規表現パターンはキャプチャーグループを含んではいけません。もし含んでいると、Router は正しく機能しません。

一度、ルートが定義されたら、 /apples/5 を呼ぶと ApplesController の view() メソッドを呼びます。 view() メソッドの中で、 $this->request->getParam('id') で渡された ID にアクセスする必要があります。

アプリケーションの中に一つのコントローラーがあり、URL にコントローラー名を表示されないようにするには、全ての URL をコントローラーのアクションにマップすることができます。たとえば、 home コントローラーのアクションにすべての URL をマップするために、 /home/demo の代わりに /demo という URL を持つとすると、次のようにできます。

$routes->connect('/{action}', ['controller' => 'Home']);

もし、大文字小文字を区別しない URL を提供したい場合、正規表現インライン修飾子を使います。

$routes->connect(
    '/{userShortcut}',
    ['controller' => 'Teachers', 'action' => 'profile', 1],
)->setPatterns(['userShortcut' => '(?i:principal)']);

もう一つ例を挙げます。これであなたはルーティングのプロです。

$routes->connect(
    '/{controller}/{year}/{month}/{day}',
    ['action' => 'index']
)->setPatterns([
    'year' => '[12][0-9]{3}',
    'month' => '0[1-9]|1[012]',
    'day' => '0[1-9]|[12][0-9]|3[01]'
]);

これは、いっそう複雑になりますが、ルーティングがとても強力になったことを示しています。この URL は４つのルート要素を持っています。１番目は、なじみがあります。デフォルトのルート要素で CakePHP にコントローラー名が必要なことを伝えています。

次に、デフォルト値を指定します。コントローラーにかかわらず index() アクションを呼び出す必要があります。

最後に、数字形式の年と月と日に一致する正規表現を指定しています。正規表現の中で、丸括弧（グループ化）はサポートされていないことに注意してください。上記のように、代わりのものを指定できますが、丸括弧によるグループ化はできません。

一度定義されると、このルートは /articles/2007/02/01 , /articles/2004/11/16 にマッチし、 $this->request->getParam() の中の日付パラメーターを伴ってそれぞれのコントローラーの index() アクションにリクエストを渡します。

予約済みルート要素

CakePHP には、いくつかの特別な意味を持つルート要素があります。そして、特別な意味を持たせたくないなら、使わないでください。

controller ルートのためのコントローラー名に使います。

action ルートのためのコントローラーアクション名に使います。

plugin コントローラーが配置されているプラグイン名に使います。

prefix プレフィックスルーティング のために使います。

_ext ファイル拡張子ルーティング のために使います。

_base 生成された URL からベースパスを削除するには false をセットしてください。アプリケーションがルートディレクトリーにない場合、 'cake relative' な URL の生成に使えます。

_scheme webcal や ftp のように異なるスキーマのリンクを作成するために設定します。現在のスキーマがデフォルトに設定されています。

_host リンクで使用するホストを設定します。デフォルトは、現在のホストです。

_port 非標準のポートにリンクを作成するときにポートを設定します。

_full true にすると FULL_BASE_URL 定数が生成された URL の前に加えられます。

# URL のハッシュフラグメントを設定できます。

_https true にすると生成された URL を https に変換します。 false にすると、強制的に http になります。

_method HTTP 動詞/メソッドを使うために定義します。 RESTful なルーティング と一緒に使うときに役に立ちます。

_name ルートの名前。名前付きルートをセットアップするときに、それを指定するためのキーとして使えます。

ルートオプションの設定

各ルートに設定できる多くのルートオプションがあります。ルートを接続したら、その流れるようなビルダーメソッドを使用してルートをさらに設定できます。これらのメソッドは、 connect() の $options パラメーターの多くのキーを置き換えます。

$routes->connect(
    '/{lang}/articles/{slug}',
    ['controller' => 'Articles', 'action' => 'view']
)
// GET と POST リクエストを許可
->setMethods(['GET', 'POST'])

// blog サブドメインにのみ一致
->setHost('blog.example.com')

// 渡された引数に変換されるルート要素を設定
->setPass(['slug'])

// ルート要素の一致するパターンを設定
->setPatterns([
    'slug' => '[a-z0-9-_]+',
    'lang' => 'en|fr|es',
])

// JSON ファイル拡張子も許可
->setExtensions(['json'])

// lang を永続的なパラメーターに設定
->setPersist(['lang']);

アクションへのパラメーター渡し

ルート要素 を使ってルートを接続している時に、ルート要素で引数を渡したい時があると思います。 pass オプションはルート要素がコントローラーの関数に引数を渡せるようにするためのホワイトリストです。

// src/Controller/BlogsController.php
public function view($articleId = null, $slug = null)
{
    // いくつかのコードがここに...
}

// routes.php
$routes->scope('/', function (RouteBuilder $routes) {
    $routes->connect(
        '/blog/{id}-{slug}', // 例えば /blog/3-CakePHP_Rocks
        ['controller' => 'Blogs', 'action' => 'view']
    )
    // 関数に引数を渡すためのルーティングテンプレートの中で、ルート要素を定義します。
    // テンプレートの中で、ルート要素を定義します。
    // ":id" をアクション内の $articleId にマップします。
    ->setPass(['id', 'slug'])
    // `id` が一致するパターンを定義します。
    ->setPatterns([
        'id' => '[0-9]+',
    ]);
});

今、リバースルーティング機能のおかげで、下記のように URL 配列を渡し、 CakePHP はルートに定義された URL をどのように整えるのかを知ることができます。

// view.php
// これは /blog/3-CakePHP_Rocks　へのリンクを返します
echo $this->Html->link('CakePHP Rocks', [
    'controller' => 'Blog',
    'action' => 'view',
    'id' => 3,
    'slug' => 'CakePHP_Rocks'
]);

// 数字のインデックスを持つパラメーターを使えます。
echo $this->Html->link('CakePHP Rocks', [
    'controller' => 'Blog',
    'action' => 'view',
    3,
    'CakePHP_Rocks'
]);

名前付きルートの使用

時々、ルートのためにすべての URL パラメーターを記述することがとても煩雑で、名前付きルートでパフォーマンスを上げたいと思うようになるでしょう。ルートを接続するときに、 _name オプションを指定できます。このプションは、あなたが使いたいルートを識別するために、リバースルーティングで使われます。

// 名前付きでルートを接続
$routes->connect(
    '/login',
    ['controller' => 'Users', 'action' => 'login'],
    ['_name' => 'login']
);

// HTTP メソッド指定でルートを命名
$routes->post(
    '/logout',
    ['controller' => 'Users', 'action' => 'logout'],
    'logout'
);

// 名前付きルートで URL の生成
$url = Router::url(['_name' => 'logout']);

// クエリー文字列引数付きの
// 名前付きルートで URL の生成
$url = Router::url(['_name' => 'login', 'username' => 'jimmy']);

あなたのルートテンプレートに :controller のようなルート要素が含まれている場合、 Router::url() にオプションの一部としてそれらを提供したい場合があると思います。

注釈

ルート名はアプリケーション全体で一意でなければなりません。違うルーティングスコープであっても、同じ _name を二度使えません。

名前付きルートを構築する時、ルート名にいくつかの命名規則を適用したいでしょう。 CakePHP は、各スコープで名前のプレフィックスを定義することで、より簡単にルート名を構築できます。

$routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) {
    // このルートの名前は `api:ping` になります。
    $routes->get('/ping', ['controller' => 'Pings'], 'ping');
});
// ping ルートのための URL を生成
Router::url(['_name' => 'api:ping']);

// plugin() で namePrefix を使用
$routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) {
    // ルートを接続。
});

// または、 prefix() で
$routes->prefix('Admin', ['_namePrefix' => 'admin:'], function (RouteBuilder $routes) {
    // ルートを接続。
});

_namePrefix オプションはネストしたスコープの中でも使えます。それは、あなたの期待通りに動きます。

$routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) {
    $routes->scope('/api', ['_namePrefix' => 'api:'], function ($routes) {
        // このルートの名前は `contacts:api:ping` になります。
        $routes->get('/ping', ['controller' => 'Pings'], 'ping');
    });
});

// ping ルートのための URL を生成
Router::url(['_name' => 'contacts:api:ping']);

名前付きスコープに接続されているルートは命名されていているルートのみ追加されます。名前なしルートはそれらに適用される _namePrefix がありません。

プレフィックスルーティング

static Cake\Routing\RouterBuilder::prefix($name, $callback)

多くのアプリケーションは特権を持ったユーザーが変更を加えるための管理者領域が必要です。これはしばしば、特別な /admin/users/edit/5 のような URL を通してなされます。 CakePHP では、プレフィックスルーティングは, prefix スコープメソッドによって有効化されます。

use Cake\Routing\Route\DashedRoute;

$routes->prefix('Admin', function (RouteBuilder $routes) {
    // ここのすべてのルートには、 `/admin` というプレフィックスが付きます。
    // また、 `'prefix' => 'Admin'` ルート要素が追加されます。
    // これは、これらのルートのURLを生成するときに必要になります
    $routes->fallbacks(DashedRoute::class);
});

プレフィックスは Controller 名前空間に属するようにマップされます。コントローラーと分離してプレフィックスすることによって、小さくて単純なコントローラーをもつことができます。プレフィックス付き及び付かないコントローラーに共通の動作は、継承やコンポーネント やトレイトを使用してカプセル化できます。このユーザーの例を使うと、 /admin/users/edit/5 にアクセスしたとき、 src/Controller/Admin/UsersController.php の edit() メソッドを 5 を１番目のパラメーターとして渡しながら呼びます。ビューファイルは、 templates/Admin/Users/edit.php が使われます。

/admin へのアクセスを pages コントローラーの index() アクションに以下のルートを使ってマップします。

$routes->prefix('Admin', function (RouteBuilder $routes) {
    // admin スコープの中なので、/admin プレフィックスや、
    // admin ルート要素を含める必要はありません。
    $routes->connect('/', ['controller' => 'Pages', 'action' => 'index']);
});

プレフィックスルートを作成するときに、 $options 引数で、追加のルートのパラメーターを設定できます。

$routes->prefix('Admin', ['param' => 'value'], function (RouteBuilder $routes) {
    // ここで接続されているルートは '/admin' でプレフィックスされており、
    // 'param' ルーティングキーを持っています。
    $routes->connect('/{controller}');
});

マルチワードのプレフィックスはデフォルトでダッシュの屈折を使用して変換されます。つまり、 MyPrefix はURLの my-prefix にマッピングされます。アンダースコアなどの別の形式を使用する場合は、このようなプレフィックスのパスを必ず設定してください:

$routes->prefix('MyPrefix', ['path' => '/my_prefix'], function (RouteBuilder $routes) {
    // ここに接続されているルートには、 ``/my_prefix`` というプレフィックスが付きます
    $routes->connect('/{controller}');
});

このようにプラグインスコープの中で、プレフィックスを定義できます。

$routes->plugin('DebugKit', function (RouteBuilder $routes) {
    $routes->prefix('Admin', function ($routes) {
        $routes->connect('/{controller}');
    });
});

上記は /debug_kit/admin/{controller} のようなルートテンプレートを作ります。接続されたルートは、 plugin と prefix というルート要素を持ちます。

プレフィックスを定義したときに、必要ならば複数のプレフィックスをネストできます。

$routes->prefix('Manager', function (RouteBuilder $routes) {
    $routes->prefix('Admin', function ($routes) {
        $routes->connect('/{controller}/{action}');
    });
});

上記のコードは、 /manager/admin/{controller}/{action} のようなルートテンプレートを生成します。接続されたルートは prefix というルート要素を Manager/Admin に設定します。

現在のプレフィックスはコントローラーのメソッドから $this->request->getParam('prefix') を通して利用可能です。

プレフィックスルートを使用する場合、 prefix オプションを設定し、 prefix() メソッドで使用されるのと同じキャメルケース形式を使用することが重要です。以下は、リンクを HTML ヘルパーで作る方法です。:

// プレフィックスルーティングする
echo $this->Html->link(
    'Manage articles',
    ['prefix' => 'Manager/Admin', 'controller' => 'Articles', 'action' => 'add']
);

// プレフィックスルーティングをやめる
echo $this->Html->link(
    'View Post',
    ['prefix' => false, 'controller' => 'Articles', 'action' => 'view', 5]
);

注釈

フォールバックルートを接続する 前 にプレフィックスルートを接続してください。

プレフィックスルートへのリンクの作成

プレフィックスキーをURL配列に追加することで、プレフィックスを指すリンクを作成できます:

echo $this->Html->link(
    'New admin todo',
    ['prefix' => 'Admin', 'controller' => 'TodoItems', 'action' => 'create']
);

ネストを使用するときは、それらを連結する必要があります:

echo $this->Html->link(
    'New todo',
    ['prefix' => 'Admin/MyPrefix', 'controller' => 'TodoItems', 'action' => 'create']
);

これは、名前空間 `App\\Controller\\Admin\\MyPrefix およびファイルパス src/Controller/Admin/MyPrefix/TodoItemsController.php を持つコントローラーにリンクします。

注釈

ルーティング結果が破線であっても、プレフィックスは常にここで大文字に変換されます。必要に応じて、ルート自体が屈折を行います。

プラグインのルーティング

static Cake\Routing\RouterBuilder::plugin($name, $options = [], $callback)

プラグイン のためのルートは plugin() メソッドを使って作成してください。このメソッドは、プラグインのルートのための新しいルーティングスコープを作成します。

$routes->plugin('DebugKit', function (RouteBuilder $routes) {
    // ここに接続されるルートは '/debug_kit' というプレフィックスが付き、
    // このプラグインのルート要素には 'DebugKit' がセットされています。
    $routes->connect('/{controller}');
});

プラグインスコープを作るときに、 path オプションでパス要素をカスタマイズできます。

$routes->plugin('DebugKit', ['path' => '/debugger'], function (RouteBuilder $routes) {
    // ここに接続されるルートは '/debugger' というプレフィックスが付き、
    // このプラグインのルート要素には 'DebugKit' がセットされています。
    $routes->connect('/{controller}');
});

スコープを使うときに、プレフィックススコープ内でプラグインスコープをネストできます。

$routes->prefix('Admin', function (RouteBuilder $routes) {
    $routes->plugin('DebugKit', function ($routes) {
        $routes->connect('/{controller}');
    });
});

上記は、 /admin/debug_kit/{controller} のようなルートを作成します。これは prefix と plugin のルート要素の組み合わせになります。プラグインルートの構築について、詳しくは プラグインのルート にあります。

プラグインルートへのリンクの作成

上記は、 /admin/debug_kit/{controller} のようなルーティングを作ります。これは、 prefix と plugin をルート要素として持ちます。

URL 配列に plugin キーを追加することによって、プラグインを指すリンクを作成できます。

echo $this->Html->link(
    'New todo',
    ['plugin' => 'Todo', 'controller' => 'TodoItems', 'action' => 'create']
);

逆に、現在のリクエストがプラグインに対してのリクエストであり、プラグインではないリンクを作成する場合、次のようにできます。

echo $this->Html->link(
    'New todo',
    ['plugin' => null, 'controller' => 'Users', 'action' => 'profile']
);

plugin => null を設定することで、プラグインの一部ではないリンクを作成したいことを Router に伝えられます。

SEO に親和性があるルーティング

良い検索エンジンのランキングを得るために、URL の中でダッシュを使うのを好む開発者もいるでしょう。 DashedRoute クラスは、アプリケーションの中で、プラグイン、コントローラー、およびキャメルケースで書かれたアクション名をダッシュ記法の URL にルーティングすることができます。

例えば、 TodoItems コントローラーの showItems() アクションを持つ ToDo プラグインを使っていたとして、 /to-do/todo-items/show-items でアクセスできるように、以下のルーター接続で可能になります。

$routes->plugin('ToDo', ['path' => 'to-do'], function (RouteBuilder $routes) {
    $routes->fallbacks('DashedRoute');
});

指定した HTTP メソッドとの照合

ルートは、HTTP 動詞へルパーを使用して指定した HTTP メソッドとマッチできます。

$routes->scope('/', function (RouteBuilder $routes) {
    // このルートは POST リクエスト上でのみマッチします。
    $routes->post(
        '/reviews/start',
        ['controller' => 'Reviews', 'action' => 'start']
    );

    // 複数 HTTP メソッドとマッチします
    $routes->connect(
        '/reviews/start',
        [
            'controller' => 'Reviews',
            'action' => 'start',
        ]
    )->setMethods(['POST', 'PUT']);
});

配列を使うことで複数の HTTP メソッドとマッチできます。 _method パラメーターはルーティングキーなので、 URL の解析と URL の生成の両方に使われます。メソッド固有のルートの URL を生成するには、URL を生成する際に _method キーを含める必要があります。

$url = Router::url([
    'controller' => 'Reviews',
    'action' => 'start',
    '_method' => 'POST',
]);

指定したホスト名との照合

ルートは、指定のホストのみとマッチするように _host オプションを使用できます。任意のサブドメインとマッチするために *. ワイルドカードを使用できます。

$routes->scope('/', function (RouteBuilder $routes) {
    // このルートは http://images.example.com のみマッチします。
    $routes->connect(
        '/images/default-logo.png',
        ['controller' => 'Images', 'action' => 'default']
    )->setHost('images.example.com');

    // このルートは http://*.example.com のみマッチします。
    $routes->connect(
        '/images/old-log.png',
        ['controller' => 'Images', 'action' => 'oldLogo']
    )->setHost('*.example.com');
});

_host オプションは URL 生成でも使用されます。 _host オプションで正確なドメインを指定する場合、そのドメインは生成された URL に含まれます。しかし、もしワイルドカードを使用する場合、URL の生成時に _host パラメーターを指定する必要があります。

// このルートを持つ場合、
$routes->connect(
    '/images/old-log.png',
    ['controller' => 'Images', 'action' => 'oldLogo']
)->setHost('images.example.com');

// url を生成するために指定が必要です。
echo Router::url([
    'controller' => 'Images',
    'action' => 'oldLogo',
    '_host' => 'images.example.com',
]);

ファイル拡張子のルーティング

static Cake\Routing\RouterBuilder::extensions(string|array|null $extensions, $merge = true)

異なるファイルの拡張子をルートで処理するために、スコープレベルだけでなくグローバルでも拡張子を定義できます。グローバルな拡張子を定義するには、スタティックな Router::extensions() メソッドを介して保存できます。

Router::extensions(['json', 'xml']);
// ...

これは、スコープに関係なく、 以後に 接続された 全て のルートに影響します。

拡張子を特定のスコープに制限するために、 Cake\Routing\RouteBuilder::setExtensions() メソッドを使用して定義することができます。

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->setExtensions(['json', 'xml']);
});

これは、 setExtensions() が呼ばれた 後の スコープの中で接続されている全てのルートのために名前付き拡張子を有効にします。それは、ネストされたスコープの中で接続されているルートも含まれます。グローバルの Router::extensions() メソッドと同様に、呼び出し前に接続されたルートは、拡張子を継承しません。

注釈

拡張子がセットされた 後 でのみ拡張子がルーティングに適用されるので、拡張子を設定することはスコープの中で一番最初にやるべきことです。

また、再度開かれたスコープは、前回開いたスコープで定義した拡張子を 継承しない ことにも注意してください。

拡張子を使うことで、一致したファイルの拡張子を除去し、残りをパースするように伝えられます。もし /page/title-of-page.html のような URL を生成したいなら、以下を使ってルートを設定します。

$routes->scope('/page', function (RouteBuilder $routes) {
    $routes->setExtensions(['json', 'xml', 'html']);
    $routes->connect(
        '/{title}',
        ['controller' => 'Pages', 'action' => 'view']
    )->setPass(['title']);
});

そして、ルートに対応するリンクを作成するために、次のようにします。

$this->Html->link(
    'Link title',
    ['controller' => 'Pages', 'action' => 'view', 'title' => 'super-article', '_ext' => 'html']
);

拡張子が リクエストハンドリング で使われ、それによってコンテンツタイプに合わせた自動的なビューの切り替えを行います。

スコープ付きミドルウェアの接続

ミドルウェアをアプリケーション全体に適用することができますが、特定のルーティングスコープにミドルウェアを適用すると、ミドルウェアが必要な場所にのみ適用できるため、適用の方法や範囲の配慮がいらないミドルウェアにすることができます。

注釈

適用されるスコープ付きミドルウェアは RoutingMiddleware によって実行され、通常、アプリケーションのミドルウェアキューの最後に配置されます。

ミドルウェアをスコープに適用する前に、ルートコレクションに登録する必要があります。

// config/routes.php の中で
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Http\Middleware\EncryptedCookieMiddleware;

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware());
    $routes->registerMiddleware('cookies', new EncryptedCookieMiddleware());
});

一度登録されると、スコープ付きミドルウェアは特定のスコープに適用されます。

$routes->scope('/cms', function (RouteBuilder $routes) {
    // CSRF & cookies ミドルウェアを有効化
    $routes->applyMiddleware('csrf', 'cookies');
    $routes->get('/articles/{action}/*', ['controller' => 'Articles']);
});

ネストされたスコープがある状況では、内部スコープは、スコープ内に適用されたミドルウェアを継承します。

$routes->scope('/api', function (RouteBuilder $routes) {
    $routes->applyMiddleware('ratelimit', 'auth.api');
    $routes->scope('/v1', function (RouteBuilder $routes) {
        $routes->applyMiddleware('v1compat');
        // ここにルートを定義。
    });
});

上記の例では、 /v1 で定義されたルートは 'ratelimit'、 'auth.api'、および 'v1compat' ミドルウェアが適用されます。スコープを再度開くと、各スコープ内のルートに適用されたミドルウェアが分離されます。

$routes->scope('/blog', function (RouteBuilder $routes) {
    $routes->applyMiddleware('auth');
    // ここに blog の認証が必要なアクションを接続
});
$routes->scope('/blog', function ($routes) {
    // ここに blog の公開アクションを接続
});

上記の例では、 /blog スコープの2つの用途はミドルウェアを共有しません。ただし、これらのスコープは両方とも、そのスコープ内で定義されたミドルウェアを継承します。

ミドルウェアのグループ化

ルートコードを DRY に保つ助けになるよう、ミドルウェアをグループにまとめることができます。一度まとめられたグループは、ミドルウェアのように適用することができます。

$routes->registerMiddleware('cookie', new EncryptedCookieMiddleware());
$routes->registerMiddleware('auth', new AuthenticationMiddleware());
$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware());
$routes->middlewareGroup('web', ['cookie', 'auth', 'csrf']);

// グループの適用
$routes->applyMiddleware('web');

RESTful なルーティング

Router はコントローラーへの RESTful なルートを簡単に生成します。 RESTful なルートはアプリケーションの API エンドポイントを作るときに有効です。 recipe コントローラーに REST アクセスできるようにしたい場合、このようにします。

// config/routes.php 内で...

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->setExtensions(['json']);
    $routes->resources('Recipes');
});

最初の行は、簡単に REST アクセス可能にするために、いくつかのデフォルトルートをセットしています。アクセス対象のメソッドには、最終的に受け取りたいフォーマット (例えば xml, json, rss) の指定が必要です。これらのルートは、HTTP リクエストメソッドに対応しています。

HTTP format

URL.format

対応するコントローラーアクション

GET

/recipes.format

RecipesController::index()

GET

/recipes/123.format

RecipesController::view(123)

POST

/recipes.format

RecipesController::add()

PUT

/recipes/123.format

RecipesController::edit(123)

PATCH

/recipes/123.format

RecipesController::edit(123)

DELETE

/recipes/123.format

RecipesController::delete(123)

使用されている HTTP メソッドは、いくつかの異なるソースから検出されます。優先順位の順のソースは次のとおりです。

_method POST 変数

X_HTTP_METHOD_OVERRIDE ヘッダー

REQUEST_METHOD ヘッダー

_method POST 変数の値を使う方法は、ブラウザーを使った REST クライアントの場合 (または POST でできる何か)に便利です。エミュレートしたい HTTP リクエストの名前を _method の値にセットするだけです。

ネストされたリソースのルートを作成

スコープの中で一度リソースに接続すると、サブリソース (リソースの下層) にもルートを接続できます。サブリソースのルートは、オリジナルのリソース名と id パラメーターの後に追加されます。例えば:

$routes->scope('/api', function (RouteBuilder $routes) {
    $routes->resources('Articles', function (RouteBuilder $routes) {
        $routes->resources('Comments');
    });
});

これで articles と comments 両方のリソースルートを生成します。この comments のルートは次のようになります。

/api/articles/{article_id}/comments
/api/articles/{article_id}/comments/{id}

CommentsController の article_id を次のように取得できます。

$this->request->getParam('article_id');

デフォルトでは、リソースルートは、スコープに含まれる同じプレフィックスにマップします。もし、ネストしたリソースのコントローラーとそうでないリソースのコントローラー両方を持つ場合、プレフィックスを利用して各コンテキスト内で異なるコントローラーを使用できます。

$routes->scope('/api', function (RouteBuilder $routes) {
    $routes->resources('Articles', function (RouteBuilder $routes) {
        $routes->resources('Comments', ['prefix' => 'Articles']);
    });
});

上記は、「Comments」リソースを App\Controller\Articles\CommentsController にマップします。コントローラーを分けることで、あなたのコントローラーのロジックをシンプルに保つことができます。このやり方で作成されたプレフィックスは、 プレフィックスルーティング と互換性があります。

注釈

あなたが望む深さまでリソースをネストできますが、２段階以上の深さにネストさせることはお勧めしません。

ルートの作成を制限

デフォルトでは CakePHP は、各リソースに対して６つのルートを接続します。特定のリソースに対して、特定のルートのみを接続させたい場合、 only オプションを使います。

$routes->resources('Articles', [
    'only' => ['index', 'view']
]);

これで読み込み専用のリソースルートが作られます。このルート名は create, update, view, index, と delete です。

使用するコントローラーアクションの変更

ルートを接続するときに使われるコントローラーのアクション名を変更したい場合があるでしょう。例えば、 edit() アクションを put() で呼びたいときに、 actions キーをアクション名のリネームに使います。

$routes->resources('Articles', [
    'actions' => ['update' => 'put', 'create' => 'add']
]);

上記は put() を edit() アクションの代わりに使い、 add() を create() の代わりに使います。

追加のリソースへのルートをマップする

map オプションを使用して、追加のリソースメソッドをマップできます。

$routes->resources('Articles', [
   'map' => [
       'deleteAll' => [
           'action' => 'deleteAll',
           'method' => 'DELETE'
       ]
   ]
]);
// これは /articles/deleteAll へ接続します。

デフォルトルートに加えて、これは /articles/delete_all へのルートに接続します。デフォルトでは、パスセグメントはキー名に一致します。'path' キーを、パスをカスタマイズするためのリソースの定義の中で使えます。

$routes->resources('Articles', [
    'map' => [
        'updateAll' => [
            'action' => 'updateAll',
            'method' => 'PUT',
            'path' => '/update-many'
        ],
    ]
]);
// これは /articles/update_many に接続します。

'only' と 'map' を定義した場合、マップされたメソッドが 'only' リストにもあるか確かめましょう。.

プレフィックス付きのリソースルーティング

リソースルートは、ルーティングプレフィックスでコントローラに接続できます。プレフィックス付きスコープ内で、または prefix オプションを使用してルートを接続します。:

$routes->resources('Articles', [
    'prefix' => 'Api',
]);

リソースルートのためのカスタムルートクラス

resources() の $options 配列の connectOptions キーで connect() を使った設定ができます。

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->resources('Books', [
        'connectOptions' => [
            'routeClass' => 'ApiRoute',
        ]
    ];
});

リソースルートのための URL 語形変化

デフォルトで、複数語のコントローラーの URL フラグメントは、コントローラー名のアンダースコアー形式です。例えば、 BlogPosts の URL フラグメントは、 /blog_posts になります。

inflect オプションを使って別の変化形を指定できます。

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->resources('BlogPosts', [
        'inflect' => 'dasherize' // ``Inflector::dasherize()`` を使用
    ]);
});

上記は、 /blog-posts/* スタイルの URL を生成します。

注釈

CakePHP 3.1 から、公式の app スケルトンは、 DashedRoute をデフォルトルートクラスとして使用します。 URL の一貫性を保つために、リソースルートを接続する際に 'inflect' => 'dasherize' オプションを使用することを推奨します。

パス要素の変更

デフォルトでは、リソースルートは、URL セグメントのリソース名の語形変化された形式を使用します。 path オプションでカスタム URL セグメントを設定することができます。

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->resources('BlogPosts', ['path' => 'posts']);
});

渡された引数

渡された引数は、リクエストされた際に使用される追加の引数、もしくはパスセグメントです。これらはしばしば、コントローラーメソッドにパラメーターを渡すために使われます。

http://localhost/calendars/view/recent/mark

上記の例では、 recent と mark の両方が CalendarsController::view() に引数として渡されます。渡された引数は３つの方法でコントローラーに渡されます。１番目は、引数としてアクションを呼ばれたときに渡し、２番目は、 $this->request->getParam('pass') で数字をインデックスとする配列で呼べるようになります。カスタムルートを使用するときに、渡された引数を呼ぶために特定のパラメーターを強制することができます。

前述の URL にアクセスして、次のようなコントローラーアクションがあるとすると、

class CalendarsController extends AppController
{
    public function view($arg1, $arg2)
    {
        debug(func_get_args());
    }
}

次の出力を得ます。

Array
(
    [0] => recent
    [1] => mark
)

コントローラーとビューとヘルパーの $this->request->getParam('pass') でも、これと同じデータが利用可能です。pass 配列中の値は、呼ばれた URL に現れる順番をもとにした数字のインデックスになります。

debug($this->request->getParam('pass'));

上記の出力は以下になります。

Array
(
    [0] => recent
    [1] => mark
)

ルーティング配列 を使って URL を生成するとき、配列に文字列キーなしの値として引数を加えます。

['controller' => 'Articles', 'action' => 'view', 5]

5 は引数として渡されるときには数字キーを持ちます。

URL の生成

static Cake\Routing\RouterBuilder::url($url = null, $full = false)

URL の生成やリバースルーティングは、すべてのコードの変更なしに URL の構造を簡単に変更する CakePHP の機能です。URL を定義するために ルーティング配列 を使用することで、あとで変更を加えても、生成された URL は自動的に更新されます。

次のように URL を文字列で作成し、

$this->Html->link('View', '/articles/view/' . $id);

そして、あとで /articles は本当は 'posts' と呼ぶべきであると判断した場合、アプリケーション全体で URL を変更する必要があります。一方、次のように link を定義し、

$this->Html->link(
    'View',
    ['controller' => 'Articles', 'action' => 'view', $id]
);

そして、URL を変更すると判断した場合、ルートを定義することで変更できます。これは、受け取る URL のマッピングと生成される URL の両方を変更します。

URL 配列を使うとき、特別なキーを使用して、文字列パラメーターによるクエリーとドキュメントフラグメントを定義できます。

$routes->url([
    'controller' => 'Articles',
    'action' => 'index',
    '?' => ['page' => 1],
    '#' => 'top'
]);

// このような URL が生成されます
/articles/index?page=1#top

Router はルーティング配列の中の未知のパラメーターをクエリー文字列パラメーターに変換します。 ? は CakePHP の古いバージョンとの後方互換性のために提供します。

URL を生成するときに、特別なルート要素が使用できます。

_ext ファイル拡張子のルーティング （拡張子）ルーティングに使います。

_base 生成された URL からベースパスを削除するには false をセットしてください。アプリケーションがルートディレクトリーにない場合、 'cake relative' な URL の生成に使います。

_scheme webcal や ftp のように異なるスキーマのリンクを作成するために設定します。現在のスキーマにデフォルト設定されています。

_host リンクのためのホストを設定します。デフォルトは、現在のホストです。

_port 非標準なポートのリンクを作成するときにポートを設定します。

_method URL が存在する HTTP メソッドを定義します。

_full true にすると、 FULL_BASE_URL 定数 が生成された URL の前に加えられます。

_https true にすると普通の URL から https に変換します。 false にすると、強制的に http になります。

_name ルートの名前。名前付きルートをセットアップするとき、それを指定するためのキーとして使います。

リダイレクトルーティング

リダイレクトルーティングは受け取るルートに対して HTTP ステータスの 30x リダイレクトを発行し、異なる URL に転送することができます。これは、リソースが移動し、同じ内容の 2 つの URL を公開したくないことをクライアントアプリケーションに通知する場合に便利です。

リダイレクトルートは通常のルートとは異なり、条件に一致した場合、実際にヘッダーリダイレクトを実行します。リダイレクトは、アプリケーション内部や外部へ遷移します。

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->redirect(
        '/home/*',
        ['controller' => 'Articles', 'action' => 'view'],
        ['persist' => true]
        // もしくは $id を引数として受け取る view アクションの
        // デフォルトルーティングは ['persist'=>['id']] のようにする
    );
});

/home/* から /articles/view へのリダイレクトし /articles/view にパラメーターを渡します。リダイレクト先として配列を使うことで、URL 文字列を定義するために、他のルートを使用できます。文字列 URL を遷移先として使用することで外部にリダイレクトできます。

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->redirect('/articles/*', 'https://google.com', ['status' => 302]);
});

これは、 /articles/* から https://google.com へ HTTP 302 ステータスのリダイレクトをします。

エンティティールーティング

エンティティールーティングでは、エンティティー、配列またはオブジェクトを使用して、 ArrayAccess をルーティングパラメーターのソースとして実装できます。これにより、ルートをより簡単にリファクタリングし、少ないコードで URL を生成することができます。たとえば、次のようなルートで開始するとします。

$routes->get(
    '/view/{id}',
    ['controller' => 'Articles', 'action' => 'view'],
    'articles:view'
);

次のようにして、このルートへの URL を生成できます。

// $article は、ローカルスコープ内のエンティティーです。
Router::url(['_name' => 'articles:view', 'id' => $article->id]);

後で、SEO 目的のために URL に記事のスラッグを付加したくなることもあるでしょう。これを行うには、 articles:view ルートへの URL を生成しているところを全て更新する必要があります。これには時間がかかることがあります。エンティティールートを使用する場合、article エンティティー全体を URL 生成に渡すことで、URL がより多くのパラメーターが必要となったとしても、すべての更新をスキップできます。

use Cake\Routing\Route\EntityRoute;

// このスコープの rest のためのエンティティールートを作成します。
$routes->setRouteClass(EntityRoute::class);

// 以前と同じようにルートを作成します。
$routes->get(
    '/view/{id}',
    ['controller' => 'Articles', 'action' => 'view'],
    'articles:view'
);

これで、 _entity キーを使って URL を生成することができます。

Router::url(['_name' => 'articles:view', '_entity' => $article]);

これは、提供されたエンティティーから id プロパティーと slug プロパティーの両方を抽出します。

カスタムルートクラス

カスタムルートクラスは、個々のルートがリクエストをパースし、リバースルーティングを扱えるようにします。 Route クラスには、いくつかの規約があります。

ルートクラスは、アプリケーションやプラグイン内にある Routing\\Route 名前空間で見つけられるはずです。

ルートクラス Cake\Routing\Route を拡張します。

ルートクラスは match() と parse() の一方もしくは両方を使います。

parse() メソッドは、受け取った URL をパースするために使用されます。これは、コントローラーとアクションに分解されるリクエストパラメーターの配列を生成する必要があります。このメソッドは、一致しなかった時に false を返します。

match() メソッドは URL パラメーターの配列に一致するか確かめるために使い、 URL を文字列で生成します。URL パラメーターがルートに一致しない時は、 false を返します。

カスタムルートクラスを routeClass オプションを使って設定することができます。

$routes->connect(
     '/{slug}',
     ['controller' => 'Articles', 'action' => 'view'],
     ['routeClass' => 'SlugRoute']
);

// また、スコープの中で routeClass を設定することもできます。
$routes->scope('/', function ($routes) {
    $routes->setRouteClass('SlugRoute');
    $routes->connect(
         '/{slug}',
         ['controller' => 'Articles', 'action' => 'view']
    );
});

このルートは SlugRoute のインスタンスを生成し、カスタムパラメーター処理を実装することができます。標準的な プラグイン記法 を使ってプラグインルートクラスを使用できます。

デフォルトルートクラス

static Cake\Routing\RouterBuilder::defaultRouteClass($routeClass = null)

デフォルト Route 以外のすべての他のルートクラスをデフォルトの基づくルートに使いたいときには、 Router::defaultRouteClass() を呼ぶことによって すべてのルートをセットアップする前に、それぞれのルートのための特定の routeClass オプションを持つことを避けます。例えば、下記を使います。

use Cake\Routing\Route\InflectedRoute;

Router::defaultRouteClass(InflectedRoute::class);

この後に接続されたすべてのルートに InflectedRoute ルートクラスが使用されます。引数なしにこのメソッドを呼ぶと、現在のデフォルトルートクラスを返します。

フォールバックメソッド

Cake\Routing\RouterBuilder::fallbacks($routeClass = null)

fallbacks メソッドはデフォルトルートをショートカットする簡単な方法です。このメソッドは、渡されたルーティングクラスによって定義されたルールを使用します。もし、クラスが定義されていない場合、 Router::defaultRouteClass() で返されたクラスが使用されます。

フォールバックを次のように呼びます。

use Cake\Routing\Route\DashedRoute;

$routes->fallbacks(DashedRoute::class);

次の明示的な呼び出しと同等です。

use Cake\Routing\Route\DashedRoute;

$routes->connect('/{controller}', ['action' => 'index'], ['routeClass' => DashedRoute::class]);
$routes->connect('/{controller}/{action}/*', [], ['routeClass' => DashedRoute::class]);

注釈

フォールバックを使ったデフォルトルートクラス (Route) もしくは、 :plugin や :controller ルート要素をもつ任意のルートを使用すると URL の大文字小文字の区別がなくなります。

永続的な URL パラメーターの生成

URL フィルター関数で URL の生成プロセスをホックできます。フィルター関数は、 ルーティングに一致する 前 に呼ばれます。これは、ルーティングする前に URL を用意します。

コールバックフィルター関数は以下のパラメーターを持ちます。

$params 生成されている URL パラメーター。

$request 現在のリクエスト

URL フィルター関数は 常に フィルターされていなくても、パラメーターを返します。

URL フィルターは永続的なパラメーターなどを簡単に扱う機能を提供します。

Router::addUrlFilter(function ($params, $request) {
    if ($request->getParam('lang') && !isset($params['lang'])) {
        $params['lang'] = $request->getParam('lang');
    }
    return $params;
});

フィルター関数は、それらが接続されている順番に適用されます。

別のユースケースでは、実行時に特定のルートを変更しています。 (プラグインルートの例)

Router::addUrlFilter(function ($params, $request) {
    if (empty($params['plugin']) || $params['plugin'] !== 'MyPlugin' || empty($params['controller'])) {
        return $params;
    }
    if ($params['controller'] === 'Languages' && $params['action'] === 'view') {
        $params['controller'] = 'Locations';
        $params['action'] = 'index';
        $params['language'] = $params[0];
        unset($params[0]);
    }
    return $params;
});

これは以下のルートを

Router::url(['plugin' => 'MyPlugin', 'controller' => 'Languages', 'action' => 'view', 'es']);

このように置き換えます。

Router::url(['plugin' => 'MyPlugin', 'controller' => 'Locations', 'action' => 'index', 'language' => 'es']);

警告

もし ルーティングミドルウェア のキャッシュ機能を利用しているのなら、フィルターはキャッシュされたデータの一部ではないため、アプリケーションの bootstrap() でフィルター関数を定義しなければなりません。

URL 内での名前付きパラメーターの扱い

CakePHP 3.0 から名前付きパラメーターが削除されたとしても、アプリケーションは URL にそれを含んだ状態で公開されています。名前付きパラメーターを含んだ URL の受け付けを継続できます。

コントローラーの beforeFilter() メソッドで、 parseNamedParams() 呼ぶことで渡された引数のすべての名前付きパラメーターを展開できます。

public function beforeFilter(Event $event)
{
    parent::beforeFilter($event);
    Router::parseNamedParams($this->request);
}

これは、 $this->request->getParam('named') にすべての渡された引数にある名前付きパラメーターを移します。すべての名前付きパラメーターとして変換された引数は渡された引数のリストから除去されます。

ディスパッチャーフィルター

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ディスパッチャーフィルター

バージョン 3.3.0 で非推奨: 3.3.0 でディスパッチャーフィルターは非推奨になりました。代わりにミドルウェア を使用してください。

コントローラーのコードが実行される前やクライアントからレスポンスを受けたり、ヘッダーをチューニングしたり、完全なリクエストが送られるより短い時間で特別な必要最低限なAPIにアクセスする権限を設定したりするために、レスポンスが返る直前にちょっとしたコードを実行する必要がある場合があります。

CakePHP はきれいなディスパッチサイクルに使う強固なフィルターためのインターフェイスを提供します。ミドルレイヤーに似ていますが、Cakeの他の場所で使ったコードを再利用できます。したがって、普通のミドルレイヤーそのものみたいに動きませんが、 Dispatcher Filters とみなせます。

既定のフィルター

いくつかの既定のフィルターがあります。それらは一般的なアプリがおおよそ必要とされる機能を操作できます。その機能は以下です:

AssetFilter テーマやプラグイン内 webroot フォルダーやテーマに関係している CSS や JavaScript や画像ファイルを参照しているかどうかチェックします。これは、ファイルが見つかったら、残りのディスパッチサイクルを止めて、それに応じてファイルを提供します。

// 静的アセットのためのキャッシュタイムの設定のためにオプションを使います。
// 設定されていない場合デフォルトは1時間です。
DispatcherFactory::add('Asset', ['cacheTime' => '+24 hours']);

RoutingFilter はリクエストされたURLに対してルーティング規則適用します。 $request->getParam() でルーティングの結果が読めます。

ControllerFactory は $request->getParam() を現在のリクエストを扱うためのコントローラーを設置するために使います。

LocaleSelector はブラウザーによって送られる Accept-Language ヘッダーによって自動的に言語を切り替えます。

フィルターの使用

フィルターは bootstrap.php で有効にされます。しかし、リクエストが送信される前にいつでも簡単に読み込めます。 Cake\Routing\DispatcherFactory を使って、フィルターをつけたり外したり出来ます。デフォルトでは、 CakePHP のテンプレートは既定で全てのリクエストに対して有効な 2つのフィルタークラスによってされます。それが、どのように追加されるか見てみましょう。

DispatcherFactory::add('Routing');
DispatcherFactory::add('ControllerFactory');

// プラグインの文法がここでも使えます。
DispatcherFactory::add('PluginName.DispatcherName');

// オプションで優先度を設定
DispatcherFactory::add('Asset', ['priority' => 1]);

高い優先度 (数値が小さい方) のディスパッチャーフィルターが先に実行されます。優先度のデフォルトは 10 です。

また、 add() で文字列を使ってフィルターを指定してインスタンスを渡せます。

use Cake\Routing\Filter\RoutingFilter;

DispatcherFactory::add(new RoutingFilter());

フィルターの順番の設定

フィルターを追加する時に、使用中のイベントハンドラに従って、順番を制御できます。 $_priority プロパティーを使ってフィルターがデフォルトの優先度を定義できる時に、特定のフィルターの優先度を加えて設定できます。

DispatcherFactory::add('Asset', ['priority' => 1]);
DispatcherFactory::add(new AssetFilter(['priority' => 1]));

より高優先度のこれより後にあるフィルターも呼ばれます。

条件にしたがって適用されるフィルター

全部のリクエストでフィルターしたくない場合、条件を使えます。 一部の場合にのみ適用したい場合、それは for と when オプションを使って設定できます。 for オプションは、URL との一致を制御し、 when オプションはフィルターが呼び出し可能な場合に実行を許可します。

// `/blog` で始まるリクエストの場合だけ実行
DispatcherFactory::add('BlogHeader', ['for' => '/blog']);

// GET の場合だけ
DispatcherFactory::add('Cache', [
    'when' => function ($request, $response) {
        return $request->is('get');
    }
]);

when で呼び出し可能なフィルターが実行される時に true を返します。呼び出し可能なフィルターは現在のリクエストとレスポンスを引数として取得します。

フィルターの作成

フィルターを作成するために src/Routing/Filter でクラスを定義します。この例では、ファーストランディングページの為のトラッキング用クッキー追加フィルターを作ります。最初に、以下のファイルを作ります。

namespace App\Routing\Filter;

use Cake\Event\Event;
use Cake\Routing\DispatcherFilter;

class TrackingCookieFilter extends DispatcherFilter
{

    public function beforeDispatch(Event $event)
    {
        $request = $event->getData('request');
        $response = $event->getData('response');
        if (!$request->getCookie('landing_page')) {
            $response->cookie([
                'name' => 'landing_page',
                'value' => $request->here(),
                'expire' => '+ 1 year',
            ]);
        }
    }
}

src/Routing/Filter/TrackingCookieFilter.php に保存します。他の CakePHP のクラスで見られるように、ディスパッチャーフィルターにはいくつかの規約があります。

クラス名は Filter で終わる。.

Routing\Filter 名前空間内にクラスを作る。例えば、 App\Routing\Filter

一般的に、 Cake\Routing\DispatcherFilter を拡張してクラスを作る。

DispatcherFilter サブクラスでオーバーライド可能な beforeDispatch() と afterDispatch() メソッドを提供します。それらのメソッドはそれぞれ個別に、コントローラーが実行された時にその前か後に実行されます。両方のメソッドは、 $data プロパティー内で ServerRequest と Response ( Cake\Http\ServerRequest と Cake\Http\Response インスタンス) オブジェクトを含む Cake\Event\Event オブジェクトを受け取ります。

フィルターがとてもシンプルであるにも関わらず、いくつかの興味深いことがフィルターメソッドで出来ます。 Response オブジェクトを返すことで、ディスパッチプロセスをスキップし、呼ばれているフィルターとコントローラーの干渉を防ぐことが出来ます。レスポンスを返す時に、 $event->stopPropagation() を他のフィルターを呼ばないために呼ぶことを覚えておいて下さい。

注釈

beforeDispatch メソッドがレスポンスを返した時に、コントローラーと afterDispatch イベントは呼ばれません。

次は、何らかの公開ページにおいて代替のレスポンスヘッダーを返すための新たなフィルターを作ってみましょう。このケースでは、 PagesController から提供されるものであれば何でも構いません。

namespace App\Routing\Filter;

use Cake\Event\Event;
use Cake\Routing\DispatcherFilter;

class HttpCacheFilter extends DispatcherFilter
{

    public function afterDispatch(Event $event)
    {
        $request = $event->getData('request');
        $response = $event->getData('response');

        if ($response->statusCode() === 200) {
            $response->sharable(true);
            $response->expires(strtotime('+1 day'));
        }
    }
}

// bootstrap.php　にて
DispatcherFactory::add('HttpCache', ['for' => '/pages'])

このフィルターは１日後に期限が切れるヘッダーをページコントローラーで送るレスポンスに付けて送ります。もちろんコントローラーでも同じことが出来ます。 これはただのフィルターができることの例一部です。実際は、レスポンスを操作する代わりに、 Cake\Cache\Cache でキャッシュして、 beforeDispatch() をコールバックしてレスポンスを制御できます。

強力なディスパッチャーフィルターはアプリを維持するのを難しくする可能性を持っていますが。賢く使えばとても強力ですが、それぞれのURLのためにいちいちレスポンスハンドラーを加えるのはよくありません。全てにフィルターが必要でないことを心に留めておいて下さい。 Controllers と Components は普通、全てのリクエスト要求コードを正確に生成します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

リクエストとレスポンスオブジェクト

リクエストとレスポンスオブジェクトは、HTTP リクエストとレスポンスの周辺の抽象化を提供します。 CakePHP のリクエストオブジェクトは、入ってきたリクエストを省みることができる一方、レスポンスオブジェクトは容易にコントローラーから HTTP レスポンスを作成することができます。

リクエスト

class Cake\Http\ServerRequest

ServerRequest は、CakePHP で使用されるデフォルトのリクエストオブジェクトです。リクエストデータへの応答と対話が中心的な機能となります。リクエストごとに Request は一つ作られ、リクエストデータを使うアプリケーションの様々なレイヤーに参照が渡されます。デフォルトのリクエストは $this->request に設定され、コントローラー、セル、ビュー、ヘルパーの中で利用できます。またコントローラーの参照を使うことでコンポーネントの中からもアクセスすることが出来ます。 ServerRequest の役割は以下の通りです。

GET, POST, そして FILES 配列を慣れ親しんだデータ構造に変換する処理を行います。

リクエストに関連する内省的環境を提供します。送信されたヘッダーやクライアントの IP アドレス、サーバーが実行されているサブドメイン/ドメインの情報などが含まれます。

リクエストパラメーターへのアクセス方法をインデックス付き配列とオブジェクトのプロパティーの両方の形式で提供します。

3.4.0 以降、 CakePHP のリクエストオブジェクトは、CakePHP の外部のライブラリーを使用しやすくするため PSR-7 ServerRequestInterface [https://www.php-fig.org/psr/psr-7/] を実装します。

リクエストパラメーター

リクエストは、 getParam() メソッドを介して、ルーティングパラメーターを用意しています。

$controllerName = $this->request->getParam('controller');

全てのルーティングパラメーターを配列として取得するためには getAttribute() を使用します。

$parameters = $this->request->getAttribute('params');

すべての ルート要素 は、このインターフェイスを通してアクセスされます。

ルート要素 に加えて 渡された引数 へのアクセスがしばしば必要になります。これらは両方ともリクエストオブジェクトと同様に利用可能です。

// 渡された引数
$passedArgs = $this->request->getParam('pass');

すべての渡された引数にアクセスする方法が提供されています。この中には CakePHP の内部で使っている重要で役に立つパラメーターが存在し、また、リクエストパラメーターの中ですべて見つけられます。

plugin リクエストを処理するプラグインです。プラグインが存在しない場合は null になります。

controller 現在のリクエストを処理するコントローラーです。

action 現在のリクエストを処理するアクションです。

prefix 現在のアクションのプレフィックスです。詳しくは、 プレフィックスルーティング をご覧ください。

クエリー文字列パラメーター

Cake\Http\ServerRequest::getQuery($name, $default = null)

クエリー文字列パラメーターは、 getQuery() メソッドを使って読み取ることができます。

// URL は /posts/index?page=1&sort=title
$page = $this->request->getQuery('page');

query プロパティーに直接アクセスするか、エラーが発生しない方法で URL クエリー配列を読むために getQuery() メソッドを使用することができます。キーが存在しない場合、 null が返ります。

$foo = $this->request->getQuery('value_that_does_not_exist');
// $foo === null

// デフォルト値も提供できます。
$foo = $this->request->getQuery('does_not_exist', 'default val');

getQueryParams() を使用すると全てのクエリー文字列パラメーターにアクセスできます。

$query = $this->request->getQueryParams();

リクエストのボディーデータ

Cake\Http\ServerRequest::getData($name, $default = null)

すべての POST データは Cake\Http\ServerRequest::getData() を使ってアクセスされます。フォームデータが data 接頭辞を含んでいる場合、接頭辞は取り除かれるでしょう。例えば

// name 属性が 'MyModel[title]' の入力は次のようにアクセスします。
$title = $this->request->getData('title');

ドット区切りの名前を使用して、ネストされたデータにアクセスできます。 例えば

$value = $this->request->getData('address.street_name');

存在しない名前の場合は $default の値が返されます。

$foo = $this->request->getData('Value.that.does.not.exist');
// $foo == null

また、異なるリクエストのボディをパースするために ボディパーサミドルウェア を使うこともできます。これは ServerRequest::getData() でアクセスできるようにするための配列です。

すべてのデータパラメータにアクセスしたい場合は getParsedBody() を使うことができます。

$data = $this->request->getParsedBody();

ファイルのアップロード

アップロードしたファイルは、上で説明した Cake\Http\ServerRequest::getData() メソッドを使用して、リクエスト内容のデータからアクセスすることができます。例えば、name属性が attachment であるinput要素のファイルは、以下のようにアクセスできます。

$attachment = $this->request->getData('attachment');

デフォルトでは、ファイルのアップロードは、リクエストデータの中で、 \Psr\Http\Message\UploadedFileInterface [https://www.php-fig.org/psr/psr-7/#16-uploaded-files] を実装したオブジェクトとして表現されます。上記の例では、 $attachment がオブジェクトを保持していますが、現在の実装では、上記の例の $attachment 変数は、デフォルトでは \LaminasDiactorosUploadedFile のインスタンスとなります。

アップロードしたファイルへのアクセスは非常に簡単です。ここでは、古い形式のファイルアップロード配列で提供されていたことと同じようにデータを取得する方法を説明します。

$name = $attachment->getClientFilename();
$type = $attachment->getClientMediaType();
$size = $attachment->getSize();
$tmpName = $attachment->getStream()->getMetadata('uri');
$error = $attachment->getError();

アップロードされたファイルを一時的な場所から目的の場所に移動させるのに手動で一時的なファイルにアクセスする必要はありません。代わりに moveTo() メソッドを使用することで簡単に行うことができます。

$attachment->moveTo($targetPath);

HTTP環境では、 moveTo() メソッドはファイルが実際にアップロードされたファイルであるかどうかを自動的に検証し、必要に応じて例外をスローします。アップロードされたファイルという概念自体がないCLI環境では、それに関係なくファイルを移動できるので、ファイルアップロードのテストが非常に簡単になります。

ファイルアップロード配列を使用するように戻すには、設定値 App.uploadedFilesAsObjects を false に設定してください。例えば、 config/app.php で以下のように設定します。

return [
    // ...
    'App' => [
        // ...
        'uploadedFilesAsObjects' => false,
    ],
    // ...
];

このオプションを無効にすると、ファイルのアップロードはリクエストデータの中で配列として表現されます。それは、ネストされた入力/名前があっても変わらない正規化された構造を持っています。これは PHP のスーパーグローバル変数 $_FILES で表現する方法とは異なります。 (詳細は PHPマニュアル [https://www.php.net/manual/en/features.file-upload.php] を参照してください)。つまり、 $attachment の値は次のようになります。

[
    'name' => 'attachment.txt',
    'type' => 'text/plain',
    'size' => 123,
    'tmp_name' => '/tmp/hfz6dbn.tmp'
    'error' => 0
]

Tip

アップロードされたファイルは、リクエストデータとは別のオブジェクトとして Cake\Http\ServerRequest::getUploadedFile() と Cake\Http\ServerRequest::getUploadedFiles() メソッドを使用しています。これらのメソッドは App.uploadedFilesAsObjects の設定に関係なく、常にオブジェクトを返します。

Cake\Http\ServerRequest::getUploadedFile($path)

アップロードされたファイルの特定のパスで返します。パスは Cake\Http\ServerRequest::getData() メソッドと同じドット構文を使用します。

$attachment = $this->request->getUploadedFile('attachment');

Cake\Http\ServerRequest::getData() と違って、 Cake\Http\ServerRequest::getUploadedFile() は、実際にアップロードされたファイルが指定されたパスに存在する場合にのみデータを返します。通常であれば、ファイルではないリクエストのbodyデータが指定されたパスに存在する場合、このメソッドは null を返します。

Cake\Http\ServerRequest::getUploadedFiles()

アップロードされたすべてのファイルを正規化された配列構造で返します。上の例では、ファイルの入力名が attachment の場合、構造は次のようになります。

[
      'attachment' => object(Laminas\Diactoros\UploadedFile) {
          // ...
      }
]

Cake\Http\ServerRequest::withUploadedFiles(array $files)

このメソッドは、リクエストオブジェクトのアップロードファイルを設定します。これは \Psr\Http\Message\UploadedFileInterface [https://www.php-fig.org/psr/psr-7/#16-uploaded-files]. を実装したオブジェクトの配列を受け付けます。これは、既存のアップロードされている可能性のあるすべてのファイルを置き換えます。

$files = [
    'MyModel' => [
        'attachment' => new \Laminas\Diactoros\UploadedFile(
            $streamOrFile,
            $size,
            $errorStatus,
            $clientFilename,
            $clientMediaType
        ),
        'anotherAttachment' => new \Laminas\Diactoros\UploadedFile(
            '/tmp/hfz6dbn.tmp',
            123,
            \UPLOAD_ERR_OK,
            'attachment.txt',
            'text/plain'
        ),
    ],
];

$this->request = $this->request->withUploadedFiles($files);

注釈

このメソッドでリクエストに追加したアップロードファイルは、リクエスト本文では利用 できません。すなわち、 Cake\Http\ServerRequest::getData() を経由して受け取ることはできません！リクエストデータに（も）必要な場合は、 Cake\Http\ServerRequest::withData() か Cake\Http\ServerRequest::withParsedBody() を経由して設定する必要があります。

PUT、PATCH または DELETE データ

Cake\Http\ServerRequest::input($callback[, $options])

REST サービスを構築しているとき PUT と DELETE リクエストのデータを受付けることがよくあります。 application/x-www-form-urlencoded リクエストボディーのデータは PUT と DELETE リクエストでは自動的に構文解析され $this->data に設定されます。もし JSON や XML データを受け付けている場合、どうやってリクエストボディーにアクセスすればいいのかについては以下の説明を見て下さい。

入力されたデータにアクセスする場合、オプション機能でそれをデコードすることができます。 XML や JSON のリクエストボディーのコンテンツと対話するときに便利です。デコード機能のための追加のパラメーターは、 input() の引数として渡すことができます。

$jsonData = $this->request->input('json_decode');

環境変数 ($ _SERVER と $ _ENV より)

Cake\Http\ServerRequest::env($key, $value = null)

ServerRequest::env() は、 env() グローバル関数のラッパーで、グローバルな $_SERVER や $_ENV を変更することなくゲッター/セッターとして動作します。

// ホストの取得
$host = $this->request->env('HTTP_HOST');

// 値を設定。一般的にはテストに役立ちます。
$this->request->env('REQUEST_METHOD', 'POST');

getServerParams() を使用すると、全ての環境変数にアクセスできます。

$env = $this->request->getServerParams();

XML または JSON データ

REST を採用しているアプリケーションでは URL エンコードされていない post 形式でデータを交換することがしばしばあります。 input() を使用すると、任意の形式の入力データを読み込むことができます。デコード関数が提供されることでデシリアライズされたコンテンツを受け取ることができます。

// PUT/POST アクションで投稿されたデータを JSON 形式にエンコードで取得する
$jsonData = $this->request->input('json_decode');

json_decode で「結果を配列として受け取る」パラメーターのように、デシリアライズメソッドの中には呼び出し時に追加パラメーターが必要なものがあります。同様に、 Xml を DOMDocument オブジェクトに変換したい場合、 input() は、追加のパラメーターを渡すことができます。

// PUT/POST アクションで投稿されたデータを XML エンコードで取得する
$data = $this->request->input('Cake\Utility\Xml::build', ['return' => 'domdocument']);

パス情報

リクエストオブジェクトはまたアプリケーションのパスについての役立つ情報を提供しています。 base や webroot 属性は URL の生成や、 アプリケーションがサブディレクトリーにいるのかどうかの決定に役立ちます。様々な属性が使用できます。

// 現在のリクエスト URL が /subdir/articles/edit/1?page=1 であると仮定

// /subdir/articles/edit/1?page=1 を保持
$here = $request->getRequestTarget();

// /subdir を保持
$base = $request->getAttribute('base');

// /subdir/ を保持
$base = $request->getAttribute('webroot');

リクエストの状態をチェック

Cake\Http\ServerRequest::is($type, $args...)

リクエストオブジェクトは、特定のリクエストに一定の条件を検査する簡単な方法を提供します。 is() メソッドを使用することで、多くの一般的な条件を確認するだけでなく、他のアプリケーション固有の要求基準を検査することができます。

$isPost = $this->request->is('post');

新しい種類の検出器を作成するために Cake\Http\ServerRequest::addDetector() を使用することで利用可能なリクエスト検出器を拡張することができます。4種類の異なる検出器を作成できます。

環境変数の比較 - 環境変数の比較、 env() から取得された値と提供された値が等しいかどうかを比較します。

パターン値の比較 - パターン値の比較では env() から取得された値と正規表現を比較します。

オプションベースの比較 - オプションベースの比較では正規表現を作成するためにオプションのリストを使います。既に定義済みのオプション検出器を追加するための呼び出しはオプションをマージするでしょう。

コールバック検出器 - コールバック検出器はチェックをハンドリングするために 'callback' タイプを提供します。コールバックはパラメーターとしてだけリクエストオブジェクトを受け取ります。

Cake\Http\ServerRequest::addDetector($name, $options)

いくつかの例:

// environment detector の追加
$this->request->addDetector(
    'post',
    ['env' => 'REQUEST_METHOD', 'value' => 'POST']
);

// pattern value detector の追加
$this->request->addDetector(
    'iphone',
    ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i']
);

// option detector の追加
$this->request->addDetector('internalIp', [
    'env' => 'CLIENT_IP',
    'options' => ['192.168.0.101', '192.168.0.100']
]);

// header detector を value comparison 付きで追加
$this->request->addDetector('fancy', [
    'env' => 'CLIENT_IP',
    'header' => ['X-Fancy' => 1]
]);

// header detector を callable comparison 付きで追加
$this->request->addDetector('fancy', [
    'env' => 'CLIENT_IP',
    'header' => ['X-Fancy' => function ($value, $header) {
        return in_array($value, ['1', '0', 'yes', 'no'], true);
    }]
]);

// callback detector を追加。有効な callable 形式でなければなりません。
$this->request->addDetector(
    'awesome',
    function ($request) {
        return $request->getParam('awesome');
    }
);

// 追加の引数を使用する detector を追加
$this->request->addDetector(
    'csv',
    [
        'accept' => ['text/csv'],
        'param' => '_ext',
        'value' => 'csv',
    ]
);

利用可能な組み込みの検出器は以下の通りです。

is('get') 現在のリクエストが GET かどうかを調べます。

is('put') 現在のリクエストが PUT かどうかを調べます。

is('patch') 現在のリクエストが PATCH かどうかを調べます。

is('post') 現在のリクエストが POST かどうかを調べます。

is('delete') 現在のリクエストが DELETE かどうかを調べます。

is('head') 現在のリクエストが HEAD かどうかを調べます。

is('options') 現在のリクエストが OPTIONS かどうかを調べます。

is('ajax') 現在のリクエストが X-Requested-With = XMLHttpRequest に由来するものかどうかを調べます。

is('ssl') リクエストが SSL 経由かどうかを調べます。

is('flash') リクエストに Flash の User-Agent があるかどうかを調べます。

is('json') リクエストに 「json」 の拡張子を持ち 「application/json」 MIME タイプを受付けるかどうかを調べます。

is('xml') リクエストが 「xml」拡張子を持ち、「application/xml」または「text/xml」 MIME タイプを受付けるかどうかを調べます。

ServerRequest は、 Cake\Http\ServerRequest::domain() 、 Cake\Http\ServerRequest::subdomains() 、 Cake\Http\ServerRequest::host() のようにサブドメインでアプリケーションを助けるためのメソッドを含みます。

セッションデータ

特定のリクエストのセッションにアクセスするには、 getSession() メソッドか session 属性を使用します。

$session = $this->request->getSession();
$session = $this->request->getAttribute('session');

$userName = $session->read('Auth.User.name');

詳細については、セッションオブジェクトを使用する方法のための セッションドキュメントを参照してください。

ホストとドメイン名

Cake\Http\ServerRequest::domain($tldLength = 1)

アプリケーションが実行されているドメイン名を返します。

// 'example.org' を表示
echo $request->domain();

Cake\Http\ServerRequest::subdomains($tldLength = 1)

アプリケーションが実行されているサブドメインを配列で返します。

// 'my.dev.example.org' の場合、 ['my', 'dev'] を返す
$subdomains = $request->subdomains();

Cake\Http\ServerRequest::host()

アプリケーションのホスト名を返します。

// 'my.dev.example.org' を表示
echo $request->host();

HTTP メソッドの読み込み

Cake\Http\ServerRequest::getMethod()

リクエストの HTTP メソッドを返します。

// POST を出力
echo $request->getMethod();

アクションが受け入れる HTTP メソッドの制限

Cake\Http\ServerRequest::allowMethod($methods)

許可された HTTP メソッドを設定します。もしマッチしなかった場合、 MethodNotAllowedException を投げます。 405 レスポンスには、通過できるメソッドを持つ Allow ヘッダーが含まれます。

public function delete()
{
    // POST と DELETE のリクエストのみ受け入れます
    $this->request->allowMethod(['post', 'delete']);
    ...
}

HTTP ヘッダーの読み込み

リクエストで使われている HTTP_* ヘッダーにアクセスできます。例えば

// 文字列としてヘッダーを取得
$userAgent = $this->request->getHeaderLine('User-Agent');

// 全ての値を配列で取得
$acceptHeader = $this->request->getHeader('Accept');

// ヘッダーの存在を確認
$hasAcceptHeader = $this->request->hasHeader('Accept');

いくつかの apache インストール環境では、 Authorization ヘッダーにアクセスできませんが、 CakePHP は、必要に応じて apache 固有のメソッドを介して利用できるようにします。

Cake\Http\ServerRequest::referer($local = true)

リクエストのリファラーを返します。

Cake\Http\ServerRequest::clientIp()

現在アクセスしているクライアントの IP アドレスを返します。

プロキシーヘッダーの信頼

アプリケーションがロードバランサーの背後にあったり、クラウドサービス上で実行されている場合、しばしばリクエストでロードバランサーのホスト、ポートおよびスキームを取得します。多くの場合、ロードバランサーはまた、オリジナルの値として HTTP-X-Forwarded-* ヘッダーを送信します。転送されたヘッダーは、CakePHP によって使用されることはありません。リクエストオブジェクトでこれらのヘッダーを使用するには、 trustProxy プロパティーを true にを設定します。

$this->request->trustProxy = true;

// これらのメソッドがプロキシーのヘッダーを使用するようになります。
$port = $this->request->port();
$host = $this->request->host();
$scheme = $this->request->scheme();
$clientIp = $this->request->clientIp();

一度プロキシが信頼されると、 clientIp() メソッドは X-Forwarded-For ヘッダの中の 最後の IPドレスを使用します。もし、アプリケーションが複数のプロキシの背後にある場合は setTrustedProxies() を使ってコントロール内のプロキシのIPアドレスを定義することができます。

$request->setTrustedProxies(['127.1.1.1', '127.8.1.3']);

プロキシが信頼された後は clientIp() は X-Forwarded-For ヘッダの最初のIPアドレスを使用します。

Accept ヘッダーの確認

Cake\Http\ServerRequest::accepts($type = null)

クライアントがどのコンテンツタイプを受付けるかを調べます。また、特定のコンテンツタイプが受付られるかどうかを調べます。

すべてのタイプを取得:

$accepts = $this->request->accepts();

あるタイプについての確認:

$acceptsJson = $this->request->accepts('application/json');

Cake\Http\ServerRequest::acceptLanguage($language = null)

クライアントが受付けるすべての言語を取得します。また、特定の言語が受付られるかどうかを調べます。

受付られる言語のリストを取得:

$acceptsLanguages = $this->request->acceptLanguage();

特定の言語が受付られるかどうかの確認:

$acceptsSpanish = $this->request->acceptLanguage('es-es');

クッキーの読込み

リクエストのクッキーはいくつかのメソッドを介して読むことができます。

// クッキーの値、またはクッキーが存在しない場合 null を取得
$rememberMe = $this->request->getCookie('remember_me');

// 値の読み込み、またはデフォルトの 0 を取得
$rememberMe = $this->request->getCookie('remember_me', 0);

// ハッシュとして全てのクッキーを取得
$cookies = $this->request->getCookieParams();

// Get a CookieCollection instance
$cookies = $this->request->getCookieCollection()

クッキーコレクションの操作方法については、 Cake\Http\Cookie\CookieCollection のドキュメントをご覧ください。

アップロードされたファイル

リクエストはアップロードされたファイルのデータを getData() または getUploadedFiles() で UploadedFileInterface オブジェクトとして公開します。

// アップロードファイルオブジェクトのリストを取得
$files = $request->getUploadedFiles();

// ファイルデータの読み込み
$files[0]->getStream();
$files[0]->getSize();
$files[0]->getClientFileName();

// ファイルの移動
$files[0]->moveTo($targetPath);

URIの操作

リクエストは、リクエストされたURIと対話するためのメソッドを含むURIオブジェクトを含みます。

// URIの取得
$uri = $request->getUri();

// URIからデータを読み出す
$path = $uri->getPath();
$query = $uri->getQuery();
$host = $uri->getHost();

レスポンス

class Cake\Http\Response

Cake\Http\Response は、CakePHP のデフォルトのレスポンスクラスです。いくつかの機能と HTTP レスポンスの生成をカプセル化します。また送信予定のヘッダーを調べるためにモックやスタブとしてテストの手助けをします。 Cake\Http\ServerRequest と同様に、 Controller, RequestHandlerComponent 及び Dispatcher に以前あった多くのメソッドを Cake\Http\Response が統合します。古いメソッドは非推奨になり、 Cake\Http\Response の使用を推奨します。

Response は次のような共通のレスポンスをラップするためのインターフェイスを提供します。

リダイレクトのヘッダーを送信。

コンテンツタイプヘッダーの送信。

任意のヘッダーの送信。

レスポンスボディーの送信。

コンテンツタイプの扱い

Cake\Http\Response::withType($contentType = null)

Cake\Http\Response::withType() を使用して、アプリケーションのレスポンスのコンテンツタイプを制御することができます。アプリケーションが Response に組み込まれていないコンテンツの種類に対処する必要がある場合は、以下のように setTypeMap() を使って設定することができます。

// vCard タイプを追加
$this->response->setTypeMap('vcf', ['text/v-card']);

// レスポンスのコンテンツタイプを vcard に設定
$this->response = $this->response->withType('vcf');

大抵の場合、追加のコンテンツタイプはコントローラーの beforeFilter() コールバックの中で設定したいと思うので、 RequestHandlerComponent が提供するビューの自動切り替え機能を活用できます。

ファイルの送信

Cake\Http\Response::withFile($path, $options = [])

リクエストに対する応答としてファイルを送信する機会があります。 Cake\Http\Response::withFile() を使用してそれを達成することができます。

public function sendFile($id)
{
    $file = $this->Attachments->getFile($id);
    $response = $this->response->withFile($file['path']);
    // レスポンスオブジェクトを返すとコントローラーがビューの描画を中止します
    return $response;
}

上記の例のようにメソッドにファイルのパスを渡す必要があります。CakePHP は、 Cake\Http\Response::$_mimeTypes に登録された、よく知られるファイルタイプであれば正しいコンテンツタイプヘッダーを送ります。 Cake\Http\Response::withFile() を呼ぶ前に Cake\Http\Response::withType() メソッドを使って、新しいタイプを追加できます。

もし、あなたが望むなら、 オプションを明記することによって、ブラウザー上に表示する代わりにファイルをダウンロードさせることができます。

$response = $this->response->withFile(
    $file['path'],
    ['download' => true, 'name' => 'foo']
);

サポートされているオプションは次のとおりです。

name

name は、ユーザーに送信される代替ファイル名を指定することができます。

download

ヘッダーでダウンロードを強制するように設定する必要があるかどうかを示すブール値。

文字列をファイルとして送信

動的に生成された pdf や ics のようにディスク上に存在しないファイルを返すことができます。

public function sendIcs()
{
    $icsString = $this->Calendars->generateIcs();
    $response = $this->response;

    // レスポンスのボディーに文字列コンテンツを挿入する
    $response = $response->withStringBody($icsString);

    $response = $response->withType('ics');

    // 任意のダウンロードファイル名を指定できます
    $response = $response->withDownload('filename_for_download.ics');

    // レスポンスオブジェクトを返すとコントローラーがビューの描画を中止します
    return $response;
}

コールバックはボディーを文字列として返すこともできます。

$path = '/some/file.png';
$this->response->body(function () use ($path) {
    return file_get_contents($path);
});

ヘッダーの設定

Cake\Http\Response::withHeader($header, $value)

ヘッダーの設定は Cake\Http\Response::withHeader() メソッドで行われます。すべての PSR-7 インターフェイスのメソッドと同様に、このメソッドは新しいヘッダーを含む新しい インスタンスを返します。

// 一つのヘッダーを追加/置換
$response = $response->withHeader('X-Extra', 'My header');

// 一度に複数ヘッダーを設定
$response = $response->withHeader('X-Extra', 'My header')
    ->withHeader('Location', 'http://example.com');

// 既存のヘッダーに値を追加
$response = $response->withAddedHeader('Set-Cookie', 'remember_me=1');

セットされた際、ヘッダーは送られません。これらのヘッダーは、 Cake\Http\Server によってレスポンスが実際に送られるまで保持されます。

便利なメソッド Cake\Http\Response::withLocation() を使うと直接リダイレクトヘッダーの設定や取得ができます。

ボディーの設定

Cake\Http\Response::withStringBody($string)

レスポンスボディーとして文字列を設定するには、次のようにします。

// ボディーの中に文字列をセット
$response = $response->withStringBody('My Body');

// json レスポンスにしたい場合
$response = $response->withType('application/json')
    ->withStringBody(json_encode(['Foo' => 'bar']));

Cake\Http\Response::withBody($body)

withBody() を使って、 Laminas\Diactoros\MessageTrait によって提供されるレスポンスボディーを設定するには、

$response = $response->withBody($stream);

$stream が Psr\Http\Message\StreamInterface オブジェクトであることを確認してください。新しいストリームを作成する方法は、以下をご覧ください。

Laminas\Diactoros\Stream ストリームを使用して、ファイルからレスポンスをストリーム化することもできます。

// ファイルからのストリーム化
use Laminas\Diactoros\Stream;

$stream = new Stream('/path/to/file', 'rb');
$response = $response->withBody($stream);

また、 CallbackStream を使用してコールバックをストリーム化できます。クライアントへストリーム化する必要のある画像、CSV ファイル もしくは PDF のようなリソースがある場合に便利です。

// コールバックからのストリーム化
use Cake\Http\CallbackStream;

// 画像の作成
$img = imagecreate(100, 100);
// ...

$stream = new CallbackStream(function () use ($img) {
    imagepng($img);
});
$response = $response->withBody($stream);

文字コードの設定

Cake\Http\Response::withCharset($charset)

レスポンスの中で使われる文字コードの種類を設定します。

$this->response = $this->response->withCharset('UTF-8');

ブラウザーキャッシュとの対話

Cake\Http\Response::withDisableCache()

時々、コントローラーアクションの結果をキャッシュしないようにブラウザーに強制する必要がでてきます。 Cake\Http\Response::withDisableCache() はそういった目的で使われます。

public function index()
{
    // キャッシュの無効化
    $this->response = $this->response->withDisabledCache();
}

警告

Internet Explorer にファイルを送ろうとしている場合、SSL ドメインからのキャッシュを無効にすることで結果をエラーにすることができます。

Cake\Http\Response::withCache($since, $time = '+1 day')

クライアントにレスポンスをキャッシュして欲しいことを伝えられます。 Cake\Http\Response::withCache() を使って:

public function index()
{
    // キャッシュの有効化
    $this->response = $this->response->withCache('-1 minute', '+5 days');
}

上記の例では、訪問者の体感スピード向上のため、クライアントにレスポンス結果を 5日間キャッシュするように伝えています。 withCache() メソッドは、第一引数に Last-Modified ヘッダーの値を設定します。第二引数に Expires ヘッダーと max-age ディレクティブの値を設定します。 Cache-Control の public ディレクティブも設定されます。

HTTP キャッシュのチューニング

アプリケーションの速度を改善するための簡単で最善の方法の一つは HTTP キャッシュを使う事です。このキャッシュモデルの元では、modified time, response entity tag などいくつかのヘッダーを設定することでレスポンスのキャッシュコピーを使うべきかどうかをクライアントが決定できるように助ける事が求められます。

キャッシュやデータが変更されたときに無効化(更新)するロジックのコードを持つのではなく、 HTTP は二つのモデル、expiration と validation を使います。これらは大抵の場合、自身でキャッシュを管理するよりかなり単純です。

Cake\Http\Response::withCache() と独立して、HTTP キャッシュヘッダーをチューニングするための様々なメソッドが使えます。この点に関して、ブラウザーやリバースプロキシーのキャッシュよりも有利だと言えます。

Cache Control ヘッダー

Cake\Http\Response::withSharable($public, $time = null)

キャッシュ制御ヘッダーは expiration モデルの元で使われ、複数の指示を含んでいます。ブラウザーやプロキシーがどのようにキャッシュされたコンテンツを扱うのかをその指示で変更することができます。 Cache-Control ヘッダーは以下の通りです。

Cache-Control: private, max-age=3600, must-revalidate

Response のいくつかのユーティリティメソッドを用いることで、最終的に有効な Cache-Control ヘッダーを生成します。一つ目は、 withSharable() メソッドです。このメソッドは異なるユーザーやクライアントの間で共有出来ることを考慮されたレスポンスかどうかを示します。このメソッドは実際には、このヘッダーが public または private のどちらなのかを制御しています。 private としてレスポンスを設定することは、レスポンスのすべてまたはその一部が特定のユーザー用であることを示しています。共有キャッシュのメリットを活かすためにはコントロールディレクティブを public に設定する必要があります。

このメソッドの二番目のパラメーターはキャッシュの max-age を指定するために使われます。このパラメーターはレスポンスが古いと見なされる秒数を表しています。

public function view()
{
    // ...
    // Cache-Control を 3600 秒の間、public として設定
    $this->response = $this->response->withSharable(true, 3600);
}

public function my_data()
{
    // ...
    // Cache-Control を 3600 秒の間、private として設定
    $this->response = $this->response->withSharable(false, 3600);
}

Response は Cache-Control ヘッダーの中で各コンポーネントを設定するための分割されたメソッドを公開しています。

Expiration ヘッダー

Cake\Http\Response::withExpires($time)

Expires ヘッダーに、レスポンスが古いと見なされる日時を設定できます。このヘッダーは withExpires() メソッドを使って設定されます。

public function view()
{
    $this->response = $this->response->withExpires('+5 days');
}

またこのメソッドは、DateTime インスタンスや DateTime クラスによって構文解析可能な文字列を受け付けます。

Etag ヘッダー

Cake\Http\Response::withEtag($tag, $weak = false)

HTTP におけるキャッシュの検証はコンテンツが定期的に変化するような場合によく使われ、キャッシュが古いと見なせる場合にのみレスポンスコンテンツが生成されることをアプリケーションに求めます。このモデルのもとでは、クライアントはページを直接使う代わりにキャッシュの中に保存し続け、アプリケーションに毎回リソースが変更されたかどうかを尋ねます。これは画像や他のアセットといった静的なリソースに対して使われる場合が多いです。

withEtag() メソッド (entity tag と呼ばれる) は要求されたリソースを識別するための一意な文字列です。大抵の場合はファイルのチェックサムのようなもので、リソースが一致するかどうかを調べるためにキャッシュはチェックサムを比較するでしょう。

実際にこのヘッダーを使うメリットを得るためには、手動で checkNotModified() メソッドを呼び出すかコントローラーにリクエストハンドリング を読み込まなければなりません。

public function index()
{
    $articles = $this->Articles->find('all')->all();

    // 記事内容の単純なチェックサムです
    // 現実世界のアプリケーションでは、もっと効率的な実装を使用する必要があります
    $checksum = md5(json_encode($articles));

    $response = $this->response->withEtag($checksum);
    if ($response->checkNotModified($this->request)) {
        return $response;
    }

    $this->response = $response;
    // ...
}

注釈

ほとんどのプロキシーユーザーは、おそらくパフォーマンスと互換性の理由から、Etags の代わりに Last Modified ヘッダーの使用を検討してください。

Last Modified ヘッダー

Cake\Http\Response::withModified($time)

HTTP キャッシュの検証モデルのもとでは、リソースが最後に変更された日時を示すために Last-Modified ヘッダーを設定することができます。このヘッダーを設定すると CakePHP がキャッシュしているクライアントにレスポンスが変更されたのかどうかを返答する手助けとなります。

実際にこのヘッダーを使うメリットを得るためには、 checkNotModified() メソッドを呼び出すかコントローラーにリクエストハンドリング を読み込まなければなりません。

public function view()
{
    $article = $this->Articles->find()->first();
    $response = $this->response->withModified($article->modified);
    if ($response->checkNotModified($this->request)) {
        return $response;
    }
    $this->response;
    // ...
}

Vary ヘッダー

Cake\Http\Response::withVary($header)

時には同じ URL で異なるコンテンツを提供したいと思うかもしれません。これは多国語対応ページがある場合やブラウザーごとに異なる HTML を返すようなケースでしばしばおこります。そのような状況では Vary ヘッダーを使えます。

$response = $this->response->withVary('User-Agent');
$response = $this->response->withVary('Accept-Encoding', 'User-Agent');
$response = $this->response->withVary('Accept-Language');

Not-Modified レスポンスの送信

Cake\Http\Response::checkNotModified(Request $request)

リクエストオブジェクトとレスポンスのキャッシュヘッダーを比較し、まだキャッシュが有効かどうかを決定します。もしまだ有効な場合、レスポンスのコンテンツは削除され 304 Not Modified ヘッダーが送られます。

// コントローラーアクションの中で
if ($this->response->checkNotModified($this->request)) {
    return $this->response;
}

クッキーの設定

クッキーは、配列または Cake\Http\Cookie\Cookie オブジェクトを使ってレスポンスに追加することができます。

use Cake\Http\Cookie\Cookie;
use DateTime;

// クッキーを追加
$this->response = $this->response->withCookie(Cookie::create(
    'remember_me',
    'yes',
    // すべてのキーはオプションです
    [
        'expires' => new DateTime('+1 year'),
        'path' => '',
        'domain' => '',
        'secure' => false,
        'http' => false,
    ]
));

クッキーオブジェクトの使い方は クッキーの作成 セクションをご覧ください。 withExpiredCookie() を使ってレスポンスに期限切れのクッキーを送ることができます。これにより、ブラウザはローカルクッキーを削除します。

$this->response = $this->response->withExpiredCookie(new Cookie('remember_me'));

クロスオリジンリクエストヘッダー（CORS）の設定

HTTP アクセス制御 [https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS] 関連のヘッダーを定義するために、流れるようなインターフェイスの cors() メソッドが使用できます。

$this->response = $this->response->cors($this->request)
    ->allowOrigin(['*.cakephp.org'])
    ->allowMethods(['GET', 'POST'])
    ->allowHeaders(['X-CSRF-Token'])
    ->allowCredentials()
    ->exposeHeaders(['Link'])
    ->maxAge(300)
    ->build();

以下の基準が満たされた場合のみ、 CORS 関連ヘッダーはレスポンスに適用されます。

リクエストは Origin ヘッダーがあります。

リクエストの Origin 値が許可された Origin 値のいずれかと一致します。

不変レスポンスに伴うよくある失敗

レスポンスオブジェクトはレスポンスを不変オブジェクトとして扱ういくつかのメソッドを提供しています。不変オブジェクトは、偶発的な副作用の追跡を困難になるのを予防し、その変更順序のリファクタリングに起因するメソッド呼び出しに起因する間違いを減らします。それらは多くの利点を提供しますが、不変オブジェクトには慣れが必要です。 with で始まるメソッドは、レスポンスに対して不変な方法で動作し、常に 、 新しい インスタンスを返します。変更されたインスタンスを保持し忘れるのは、不変オブジェクトを扱うときに人々が最も頻繁にする失敗です。

$this->response->withHeader('X-CakePHP', 'yes!');

上記のコードでは、レスポンスは X-CakePHP ヘッダーがありません。 withHeader() メソッドの戻り値を保持していないためです。上記のコードを修正するには、次のように記述します。

$this->response = $this->response->withHeader('X-CakePHP', 'yes!');

クッキーコレクション

class Cake\Http\Cookie\CookieCollection

CookieCollection オブジェクトは、リクエストオブジェクトとレスポンスオブジェクトからアクセス可能です。イミュータブルパターンを使ってクッキーのグループとやり取りすることができ、リクエストとレスポンスの不変性が維持されます。

クッキーの作成

class Cake\Http\Cookie\Cookie

Cookie オブジェクトは、コンストラクタオブジェクトを介して、またはイミュータブルパターンに従って流れるようなインターフェースを使用することによって定義することができます。

use Cake\Http\Cookie\Cookie;

// コンストラクタの中の全ての引数
$cookie = new Cookie(
    'remember_me', // 名前
    1, // 値
    new DateTime('+1 year'), // 有効期限、適用する場合
    '/', // パス、該当する場合
    'example.com', // ドメイン名、適用する場合
    false, // secure のみ?
    true // http のみ ?
);

// ビルダーメソッドを使用
$cookie = (new Cookie('remember_me'))
    ->withValue('1')
    ->withExpiry(new DateTime('+1 year'))
    ->withPath('/')
    ->withDomain('example.com')
    ->withSecure(false)
    ->withHttpOnly(true);

クッキーを作成したら、新規または既存の CookieCollection に追加することができます。

use Cake\Http\Cookie\CookieCollection;

// 新規のコレクションを作成
$cookies = new CookieCollection([$cookie]);

// 既存のコレクションに追加
$cookies = $cookies->add($cookie);

// 名前でクッキーを削除
$cookies = $cookies->remove('remember_me');

注釈

コレクションは不変であり、クッキーを追加したりコレクションからクッキーを削除すると、新規に コレクションが作成されることに注意してください。

レスポンスにクッキーオブジェクトを追加することができます。

// クッキーを1つ追加
$response = $this->response->withCookie($cookie);

// クッキーコレクション全体を置き換える
$response = $this->response->withCookieCollection($cookies);

レスポンスにセットするクッキーは クッキー暗号化ミドルウェア を使って暗号化することができます。

クッキーの読込み

CookieCollection インスタンスを取得すると、それに含まれるクッキーにアクセスできます。

// クッキーが存在するかどうかをチェック
$cookies->has('remember_me');

// コレクション内のクッキーの数を取得
count($cookies);

// クッキーインスタンスを取得
$cookie = $cookies->get('remember_me');

Cookie オブジェクトを取得すると、その状態をやりとりしたり変更したりできます。クッキーは不変なので、クッキーを変更した場合にコレクションを更新する必要があることに注意してください。

// 値の取得
$value = $cookie->getValue()

// JSON 値の中のデータにアクセス
$id = $cookie->read('User.id');

// 状態のチェック
$cookie->isHttpOnly();
$cookie->isSecure();

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コントローラー

class Cake\Controller\Controller

コントローラー (Controller) は MVC の 'C' です。ルーティングが適用されて適切なコントローラーが見つかった後、コントローラーのアクションが呼ばれます。コントローラーはリクエストを解釈して、適切なモデルが呼ばれるのを確認して、正しいレスポンスまたはビューを書き出します。コントローラーはモデルとビューの中間層とみなすことができます。コントローラーは薄くシンプルに、モデルを大きくしましょう。そうすれば、あなたの書いたコードはより簡単に再利用できるようになり、そしてより簡単にテストできるでしょう。

一般的に、コントローラーはひとつのモデルのロジックを管理するために使われます。たとえば、オンラインベーカリーのサイトを構築しようとしている場合、レシピやその材料を管理する RecipesController と IngredientsController を作るでしょう。コントローラーは複数のモデルを扱う場合でも問題なく動作しますが、CakePHP では主に操作するモデルにちなんで、コントローラーの名前が付けられます。

アプリケーションのコントローラーは AppController クラスを継承し、そしてそれは Controller クラスを継承しています。 AppController クラスは src/Controller/AppController.php に定義し、アプリケーションのコントローラー全体で共有されるメソッドを含めるようにしましょう。

コントローラーは、リクエストを操作するための アクション と呼ばれるいくつかのメソッドを提供します。デフォルトで、コントローラー上のすべての public メソッドはアクションとなり、URL からアクセスができます。アクションはリクエストを解釈してレスポンスを返す役割があります。普通、レスポンスは描画されたビューの形式となっていますが、同様のレスポンスを作成する方法は他にもあります。

AppController

冒頭で述べたように、 AppController クラスはアプリケーションのすべてのコントローラーの親クラスとなります。 AppController はそれ自身、CakePHP のコアライブラリーに含まれる Cake\Controller\Controller クラスを継承しています。 AppController は src/Controller/AppController.php に次のように定義されます。

namespace App\Controller;

use Cake\Controller\Controller;

class AppController extends Controller
{
}

AppController で作られたクラス変数とメソッドはそれを継承するすべてのコントローラーで有効となります。コンポーネント (あとで学びます) は多くのコントローラー (必ずしもすべてのコントローラーとは限りません) で使われるコードをまとめるのに使われます。

アプリケーション中のすべてのコントローラーで使われるコンポーネントを読み込むために AppController を使うことができます。CakePHP はこうした用途のために、 Controller のコンストラクターの最後で呼び出される initialize() メソッドを提供します。

namespace App\Controller;

use Cake\Controller\Controller;

class AppController extends Controller
{
    public function initialize(): void
    {
        // CSRF コンポーネントを常に有効にします。
        $this->loadComponent('Csrf');
    }
}

リクエストの流れ

CakePHP アプリケーションへのリクエストが生じると、 CakePHP の Cake\Routing\Router と Cake\Routing\Dispatcher クラスは正しいコントローラーを見つけて、インスタンスを作成するために ルートを接続 を使用します。リクエストデータはリクエストオブジェトの中にカプセル化されています。 CakePHP はすべての重要なリクエスト情報を $this->request プロパティーの中に格納します。 CakePHP のリクエストオブジェクトについてのより詳しい情報は リクエスト の章を参照してください。

コントローラーのアクション

コントローラーのアクションは、リクエストパラメーターを、要求を送ってきたブラウザーやユーザーに対するレスポンスに変換する役割があります。CakePHP は規約に則ることで、このプロセスを自動化し、本来であればあなたが書かなければならなかったコードを省いてくれます。

CakePHP は規約に従って、アクション名のビューを描画します。オンラインベーカリーの例に戻りますが、 RecipesController は view() ・ share() ・ search() アクションを持つかもしれません。このコントローラーは src/Controller/RecipesController.php にあり、次のようなコードになっています。

// src/Controller/RecipesController.php

class RecipesController extends AppController
{
    public function view($id)
    {
        // アクションの処理をここで行います。
    }

    public function share($customerId, $recipeId)
    {
        // アクションの処理をここで行います。
    }

    public function search($query)
    {
        // アクションの処理をここで行います。
    }
}

これらのアクションのテンプレートファイルは templates/Recipes/view.php 、 templates/Recipes/share.php 、そして templates/Recipes/search.php になります。規約に従ったビューのファイル名は、アクション名を小文字にしてアンダースコアーでつないだものです。

通常、コントローラーのアクションは View クラスがビュー層の描画で使うコンテキストを作るために set() を使います。 CakePHP の規約に従うと、手動でビューを描画したり生成したりする必要はありません。代わりに、コントローラーのアクションが完了すると、CakePHP はビューの描画と送信をします。

もし何らかの理由でデフォルトの動作をスキップさせたければ、完全にレスポンスを作成して、アクションから Cake\Http\Response オブジェクトを返すこともできます。

アプリケーションでコントローラーを効率的に使うために、CakePHP のコントローラーから提供されるいくつかのコアな属性やメソッドを説明しましょう。

ビューとの相互作用

コントローラーはビューといくつかの方法でお互いに作用しあっています。最初に、コントローラーは Controller::set() を使って、ビューにデータを渡すことができます。コントローラーからどのビュークラスを使うか、どのビューを描画すべきか、を決めることもできます。

ビュー変数の設定

Cake\Controller\Controller::set(string $var, mixed $value)

Controller::set() メソッドはコントローラーからビューへデータを渡すための主な方法です。 Controller::set() を使った後は、その変数はビュー内でアクセスできるようになります。

// まずコントローラーからデータを渡します

$this->set('color', 'pink');

// すると、ビューでそのデータを利用できます
?>

You have selected <?= h($color) ?> icing for the cake.

Controller::set() メソッドは最初のパラメーターに連想配列も指定できます。この方法はデータのかたまりを簡単にビューに割り当てる方法としてよく使われます。

$data = [
    'color' => 'pink',
    'type' => 'sugar',
    'base_price' => 23.95
];

// $color・$type および $base_price を作成し
// ビューで使用できるようになります

$this->set($data);

ビューオプションの設定

もしビュークラスや、レイアウト／テンプレートのパス、ビューの描画時に使われるヘルパーやテーマをカスタマイズしたければ、ビルダーを得るために viewBuilder() メソッドを使います。このビルダーは、ビューが作成される前にビューのプロパティーを設定するために使われます。

$this->viewBuilder()
    ->addHelper('MyCustom')
    ->setTheme('Modern')
    ->setClassName('Modern.Admin');

上記は、どのようにしてカスタムヘルパーを読み込み、テーマを設定し、カスタムビュークラスを使用できるかを示しています。

ビューの描画

Cake\Controller\Controller::render(string $view, string $layout)

Controller::render() メソッドは各アクションの最後に自動的に呼ばれます。このメソッドは (Controller::set() で渡したデータを使って) すべてのビューロジックを実行し、ビューを View::$layout 内に配置し、エンドユーザーに表示します。

render に使用されるデフォルトのビューファイルは、規約によって決定されます。 RecipesController の search() アクションがリクエストされたら、 templates/Recipes/search.php のビューファイルが描画されます。

namespace App\Controller;

class RecipesController extends AppController
{
// ...
    public function search()
    {
        // templates/Recipes/search.php のビューを描画します
        return $this->render();
    }
// ...
}

CakePHP は ($this->autoRender に false をセットしない限り) アクションの後に自動的に描画メソッドを呼び出しますが、 Controller::render() メソッドの第一引数にビュー名を指定することで、別のビューファイルを指定することができます。

$view が '/' で始まっていれば、ビューまたはエレメントのファイルが src/Template フォルダーからの相対パスであると見なします。これはエレメントを直接描画することができ、 AJAX 呼び出しではとても有用です。

// templates/element/ajaxreturn.php のエレメントを描画します
$this->render('/element/ajaxreturn');

Controller::render() の第二引数 $layout はビューが描画されるレイアウトを指定することができます。

特定のテンプレートの描画

コントローラーで、規約に従ったものではなく、別のビューを描画したいことがあるかもしれません。これは Controller::render() を直接呼び出すことで可能です。一度 Controller::render() を呼び出すと、CakePHP は再度ビューを描画することはありません。

namespace App\Controller;

class PostsController extends AppController
{
    public function my_action()
    {
        $this->render('custom_file');
    }
}

これは templates/Posts/my_action.php の代わりに templates/Posts/custom_file.php を描画します。

また、次のような書式で、プラグイン内のビューを描画することもできます。 $this->render('PluginName.PluginController/custom_file') 。例:

namespace App\Controller;

class PostsController extends AppController
{
    public function myAction()
    {
        $this->render('Users.UserDetails/custom_file');
    }
}

これは plugins/Users/templates/UserDetails/custom_file.php を描画します。

コンテンツタイプのネゴシエーション

Cake\Controller\Controller::viewClasses()

コントローラは、サポートするビュークラスの一覧を定義することができます。コントローラのアクションが完了すると、 CakePHP はビューリストを使用して content-typeのネゴシエーション（コンテントタイプの取り決め）を実行します。これにより、アプリケーションは同じコントローラアクションを再利用してHTML ビューをレンダリングしたり JSONやXML のレスポンスをレンダリングしたりすることができるようになります。コントローラにサポートするビュークラスのリストを定義するには、 viewClasses() メソッドを使用します。:

namespace App\Controller;

use Cake\View\JsonView;
use Cake\View\XmlView;

class PostsController extends AppController
{
    public function viewClasses()
    {
        return [JsonView::class, XmlView::class];
    }
}

アプリケーションの View クラスは、リクエストの Accept ヘッダーやルーティング拡張機能に基づいて、他のビューを選択できない場合に自動的にフォールバックとして使用されます。もしアプリケーションが異なるレスポンスフォーマットに対して異なるロジックを実行する必要がある場合は、 $this->request->is() を使用して、必要な条件ロジックを構築することができます。

注釈

ビュークラスがcontent-typeのネゴシエーションに参加するには、静的な contentType() フックメソッドを実装する必要があります。

コンテンツタイプネゴシエーションのフォールバック

リクエストのコンテントタイプの設定にマッチする View がない場合、 CakePHP はベースとなる View クラスを使用します。もし、content-typeのネゴシエーションを要求したい場合は、 406ステータスコードを設定する NegotiationRequiredView を使用します。:

public function viewClasses()
{
    // Acceptヘッダーのネゴシエーションを要求するか、406のレスポンスを返します。
    return [JsonView::class, NegotiationRequiredView::class];
}

TYPE_MATCH_ALL のコンテンツタイプ値を使用して、独自のフォールバックビューロジックを構築することができます:

namespace App\View;

use Cake\View\View;

class CustomFallbackView extends View
{
    public static function contentType(): string
    {
        return static::TYPE_MATCH_ALL;
    }

}

match-allビューは、content-typeのネゴシエーションが試みられた 後でのみ適用されることを覚えておくことが重要です。

バージョン 4.4.0 で追加: 4.4 より前のバージョンでは、 viewClasses() の代わりにリクエストハンドリング を使用する必要があります。

他のページへのリダイレクト

Cake\Controller\Controller::redirect(string|array $url, integer $status)

もっともよく使う、フロー制御のメソッドは Controller::redirect() です。このメソッドは最初の引数に、CakePHP の相対 URL を受け取ります。ユーザーが正常に注文を出した時、レシート画面にリダイレクトさせたいかもしれません。

public function place_order()
{
    // 注文終了のためのロジック
    if ($success) {
        return $this->redirect(
            ['controller' => 'Orders', 'action' => 'thanks']
        );
    }
    return $this->redirect(
        ['controller' => 'Orders', 'action' => 'confirm']
    );
}

このメソッドは適切なヘッダーが設定されたレスポンスのインスタンスを返します。ビューの描画を抑制し、ディスパッチャーが実際にリダイレクトを行えるようにするためにこのレスポンスのインスタンスを return すべきです。

$url 引数に相対 URL または絶対 URL を指定することもできます。

return $this->redirect('/orders/thanks');
return $this->redirect('http://www.example.com');

アクションにデータを渡すこともできます。

return $this->redirect(['action' => 'edit', $id]);

Controller::redirect() の第二引数では、リダイレクトに伴う HTTP ステータスコードを定義することができます。リダイレクトの性質によっては、 301 (moved parmanently) または 303 (see other) を使ったほうが良いでしょう。

リファラーのページにリダイレクトする必要があれば、次のようにできます。

return $this->redirect($this->referer());

クエリー文字列とハッシュを使う例は次のようになります。

return $this->redirect([
    'controller' => 'Orders',
    'action' => 'confirm',
    $order->id,
    '?' => [
        'product' => 'pizza',
        'quantity' => 5
    ],
    '#' => 'top'
]);

生成される URL はこのようになります。

http://www.example.com/orders/confirm?product=pizza&quantity=5#top

同じコントローラーの他のアクションへの転送

Cake\Controller\Controller::setAction($action, $args...)

もし、現在のアクションを 同じ コントローラーの異なるアクションにフォワードする必要があれば、リクエストオブジェクトを更新し、描画されるビューテンプレートを変更し、そして指定のアクションに実行をフォワードするために Controller::setAction() を使用します。

// delete アクションから、更新後の一覧ページを描画することができます。
$this->setAction('index');

追加のモデル読み込み

Cake\Controller\Controller::loadModel(string $modelClass, string $type)

loadModel 関数は、コントローラーのデフォルト以外のモデルを使う必要がある時に便利です。

// コントローラーのメソッドの中で。
$this->loadModel('Articles');
$recentArticles = $this->Articles->find('all', [
    'limit' => 5,
    'order' => 'Articles.created DESC'
]);

もしも組み込み ORM 以外のテーブルプロバイダーを使いたければ、ファクトリーメソッドに接続することで、そのテーブルシステムを CakePHP のコントローラーに紐づけることができます。

// コントローラーのメソッドの中で。
$this->modelFactory(
    'ElasticIndex',
    ['ElasticIndexes', 'factory']
);

テーブルファクトリーを登録した後は、インスタンスを読み出すために loadModel を使うことができます。

// コントローラーのメソッドの中で。
$this->loadModel('Locations', 'ElasticIndex');

注釈

組み込みの ORM の TableRegistry は既定では 'Table' プロバイダーとして接続されています。

モデルのページネーション

Cake\Controller\Controller::paginate()

このメソッドはモデルから取得した結果をページ分けするために使われます。ページサイズやモデルの検索条件などを指定できます。 paginate() のより詳しい使い方は ページネーションの章を参照してください。

$paginate 属性は paginate() がどうふるまうかを簡単にカスタマイズする方法を提供します。

class ArticlesController extends AppController
{
    public $paginate = [
        'Articles' => [
            'conditions' => ['published' => 1]
        ]
    ];
}

コンポーネント読み込みの設定

Cake\Controller\Controller::loadComponent($name, $config = [])

コントローラーの initialize() メソッドの中で、読み込みたいコンポーネントや、その設定データを定義することができます。

public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('Csrf');
    $this->loadComponent('Comments', Configure::read('Comments'));
}

property Cake\Controller\Controller::$components

コントローラーの $components プロパティーでは、コンポーネントの設定ができます。設定されたコンポーネントとそれに依存するコンポーネントは CakePHP によって構築されます。より詳しい情報は コンポーネントの設定 の章を参照してください。先に述べたように、 $components プロパティーはコントローラーの各親クラスで定義されたプロパティーとマージされます。

ヘルパー読み込みの設定

property Cake\Controller\Controller::$helpers

追加で利用する MVC クラスをどうやって CakePHP のコントローラーに伝えるのかを見てみましょう。

class RecipesController extends AppController
{
    public $helpers = ['Form'];
}

これらの変数はそれぞれ、継承された値とマージされます。したがって、たとえば FormHelper を、あるいは AppController で宣言されている他のクラスを、再度宣言する必要はありません。

バージョン 3.0 で非推奨: コントローラーからのヘルパーの読み込みは後方互換のために提供しています。ヘルパーをどう読み込むかについては ヘルパーの設定 を参照してください。

リクエストライフサイクルコールバック

CakePHP のコントローラーはリクエストのライフサイクル周りにロジックを挿入できるいくつかのイベント／コールバックを呼び出します。

イベント一覧

Controller.initialize

Controller.startup

Controller.beforeRedirect

Controller.beforeRender

Controller.shutdown

コントローラーのコールバックメソッド

コントローラーでメソッドが実装されていれば、既定では以下のコールバックメソッドが関連するイベントに接続されます。

Cake\Controller\Controller::beforeFilter(EventInterface $event)

コントローラーの各アクションの前に発動する Controller.initialize イベント中に呼ばれます。アクティブなセッションのチェックや、ユーザーの権限の調査に適した場所です。

注釈

beforeFilter() メソッドはアクションが存在しない場合でも呼ばれます。

beforeFilter メソッドからレスポンスを返した場合であっても、同イベントの他のリスナ―が呼ばれるのを抑制することはしません。明示的に イベントを停止 しなければなりません。

Cake\Controller\Controller::beforeRender(EventInterface $event)

コントローラーのアクションロジックの後、ビューが描画される前に発動する Controller.beforeRender イベント中に呼ばれます。このコールバックはそれほど使われませんが、もしアクション終了前に render() を手動で呼んでいれば必要になるかもしれません。

Cake\Controller\Controller::afterFilter(EventInterface $event)

コントローラーの各アクションの後、ビューの描画が完了した後に発動される Controller.shutdown イベント中に呼ばれます。これが最後に走るコントローラーのメソッドです。

コントローラーのライフサイクルのコールバックに加えて、 コンポーネントも似たようなコールバックの一式を提供します。

最良の結果を得るために、子コントローラーのコールバック中で AppController のコールバックを呼ぶのを忘れないでください。

//use Cake\Event\EventInterface;
public function beforeFilter(EventInterface $event)
{
    parent::beforeFilter($event);
}

Controller Middleware

Cake\Controller\Controller::middleware($middleware, array $options = [])

Middleware can be defined globally, in a routing scope or within a controller. To define middleware for a specific controller use the middleware() method from your controller's initialize() method:

public function initialize(): void
{
    parent::initialize();

    $this->middleware(function ($request, $handler) {
        // Do middleware logic.

        // Make sure you return a response or call handle()
        return $handler->handle($request);
    });
}

Middleware defined by a controller will be called before beforeFilter() and action methods are called.

バージョン 4.3.0 で追加: Controller::middleware() was added.

コントローラーのより詳細

ページコントローラー

コンポーネント

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ページコントローラー

CakePHP の公式スケルトンアプリは、デフォルトコントローラー PagesController.php を提供しています。これは静的なコンテンツを提供するためのシンプルで補助的なコントローラーです。インストール後のホームページはこのコントローラーとビューファイル templates/Pages/home.php を利用して作成されました。もし templates/Pages/about_us.php というビューファイルを作成すれば、この URL http://example.com/pages/about_us でアクセスできます。ページコントローラーは必要に応じて気軽に修正できます。

コンポーザを利用してアプリを "bake" すると、ページコントローラーが src/Controller/ フォルダー以下に作成されます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コンポーネント

コンポーネントはコントローラー間で共有されるロジックのパッケージです。 CakePHP には、様々な共通のタスクを支援するための素晴らしいコアコンポーネントが用意されています。あなた独自のコンポーネントも作成できます。 もしコントローラー間でコピー＆ペーストしたい箇所があった場合、その機能を含むコンポーネントの作成を検討しましょう。コンポーネントを作成することで、コントローラーのコードを綺麗に保ち、プロジェクト間のコードの再利用につながります。

CakePHP の中に含まれるコンポーネントの詳細については、各コンポーネントの章をチェックしてください。

AuthComponent

フラッシュ

セキュリティ

ページネーション

リクエストハンドリング

フォームの保護

HTTPキャッシュの確認

コンポーネントの設定

コアコンポーネントの多くは設定を必要としています。コンポーネントが設定を必要としている例は、 AuthComponent やなどにあります。これらのコンポーネントや一般的なコンポーネントの設定は、通常、お使いのコントローラーの initialize() メソッド内で loadComponent() を使用するか、 $components 配列を介して行われます。

class PostsController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('RequestHandler', [
            'viewClassMap' => ['json' => 'AppJsonView'],
        ]);
        $this->loadComponent('Security', ['blackholeCallback' => 'blackhole']);
    }

}

config() メソッドを使用して、実行時にコンポーネントを設定することができます。しばしば、コントローラーの beforeFilter() メソッドで行われます。上記は、次のように表現することもできます。

public function beforeFilter(EventInterface $event)
{
    $this->RequestHandler->setConfig('viewClassMap', ['rss' => 'MyRssView']);
}

コンポーネントは、ヘルパーと同じように、設定データを取得および設定するために使用されている getConfig() と setConfig() メソッドを実装しています。

// 設定データの読み込み
$this->RequestHandler->getConfig('viewClassMap');

// 設定をセット
$this->Csrf->setConfig('cookieName', 'token');

コンポーネントは、ヘルパーと同じように、config() でアクセス可能な $_config プロパティーを作成するためにコンストラクターの設定で自分の $_defaultConfig プロパティーを自動的にマージします。

コンポーネントの別名

共通設定の一つに className オプションがあります。このオプションを使うとコンポーネントに別名をつけられます。この機能は $this->Auth や他のコンポーネントの参照を独自実装に置き換えたい時に便利です。

// src/Controller/PostsController.php
class PostsController extends AppController
{
    public function initialize(): void
    {
        $this->loadComponent('Auth', [
            'className' => 'MyAuth'
        ]);
    }
}

// src/Controller/Component/MyAuthComponent.php
use Cake\Controller\Component\AuthComponent;

class MyAuthComponent extends AuthComponent
{
    // コア AuthComponent を上書きするコードを追加
}

上記の例ではコントローラーにて MyAuthComponent に $this->Auth という別名 をつけています。

注釈

別名を付けられたコンポーネントはコンポーネントが使われるあらゆる場所のインスタンスを置き換えます。これは、他のコンポーネントの内部を含みます。

コンポーネントの動的ロード

すべてのコントローラーアクションで全コンポーネントを使えるようにする必要はないかもしれません。このような状況では、お使いのコントローラーで loadComponent() メソッドを使用して、実行時にコンポーネントをロードすることができます。

// コントローラーのアクションの中で
$this->loadComponent('OneTimer');
$time = $this->OneTimer->getTime();

注釈

動的にロードされたコンポーネントはコールバックされないことに注意してください。もし、 beforeFilter または startup コールバックに依存している場合、あなたのコンポーネントをロードするときに手動でそれらを呼び出す必要があります。

コンポーネントの使用

一旦、コンポーネントをコントローラーに読込んでしまえば、使うのは非常に簡単です。使用中の各コンポーネントはコントローラーのプロパティーのように見えます。もし、 Cake\Controller\Component\FlashComponent をコントローラーに読込んだ場合、以下のようにアクセスすることができます。

class PostsController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Flash');
    }

    public function delete()
    {
        if ($this->Post->delete($this->request->getData('Post.id')) {
            $this->Flash->success('Post deleted.');
            return $this->redirect(['action' => 'index']);
        }
    }

注釈

モデルとコンポーネントの両方がプロパティーとしてコントローラーに追加されているので、それらは同じ「名前空間」を共有しています。コンポーネントとモデルに同じ名前を付けないように注意してください。

コンポーネントの作成

アプリケーションの様々な箇所で複雑な数学的処理を必要としているオンラインアプリケーションを仮定して下さい。これから、コントローラーの様々な箇所で使うための共有ロジックを集約するためのコンポーネントを作成します。

はじめに、新しいコンポーネントファイルとクラスを作成します。 src/Controller/Component/MathComponent.php にファイルを作成します 。コンポーネントのための基本的な構造は次のようになります。

namespace App\Controller\Component;

use Cake\Controller\Component;

class MathComponent extends Component
{
    public function doComplexOperation($amount1, $amount2)
    {
        return $amount1 + $amount2;
    }
}

注釈

すべてのコンポーネントは Cake\Controller\Component を継承しなければなりません。継承されていない場合、例外が発生します。

コントローラーの中にコンポーネントを読み込む

一旦コンポーネントが完成してしまえば、コントローラーの initialize() メソッド中でそれをロードすることによって、アプリケーションのコントローラーで使用することができます。ロードされた後、コントローラーはそのコンポーネントに由来する名前の新しいプロパティーを与えられ、そのプロパティーを通してコンポーネントのインスタンスにアクセスできます。

// コントローラーの中で
// 標準の $this->Csrf と同様に
// 新しいコンポーネントを $this->Math として利用可能にします。
public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('Math');
    $this->loadComponent('Csrf');
}

コントローラーの中でコンポーネントを読み込む時、コンポーネントのコンストラクターに渡すバラメータを宣言することもできます。このパラメーターはコンポーネントによって処理することができます。

// コントローラーの中で
public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('Math', [
        'precision' => 2,
        'randomGenerator' => 'srand'
    ]);
    $this->loadComponent('Csrf');
}

上記の例では precision と randomGenerator を含む配列が MathComponent::initialize() の $config パラメーターに渡されます。

コンポーネントの中で他のコンポーネントを使用する

作成しているコンポーネントから他のコンポーネントを使いたい時がたまにあります。その場合、作成中のコンポーネントから他のコンポーネントを読み込むことができ、その方法はコントローラーから $components 変数を使って読み込む場合と同じです。

// src/Controller/Component/CustomComponent.php
namespace App\Controller\Component;

use Cake\Controller\Component;

class CustomComponent extends Component
{
    // あなたのコンポーネントが使っている他のコンポーネント
    public $components = ['Existing'];

    // あなたのコンポーネントに必要な、その他の追加のセットアップを実行
    public function initialize(array $config): void
    {
        $this->Existing->foo();
    }

    public function bar()
    {
        // ...
    }
}

// src/Controller/Component/ExistingComponent.php
namespace App\Controller\Component;

use Cake\Controller\Component;

class ExistingComponent extends Component
{
    public function foo()
    {
        // ...
    }
}

注釈

コントローラーから読み込んだコンポーネントと違い、コンポーネントからコンポーネントを読み込んだ場合は、コールバックが呼ばれないことに注意して下さい。

コンポーネントのコントローラーへのアクセス

コンポーネント内から、_registry を介して現在のコントローラーにアクセスすることができます。

$controller = $this->getController();

コンポーネントのコールバック

また、コンポーネントは、リクエストサイクルを増強することができる、いくつかのリクエストライフサイクルコールバックを提供しています。

beforeFilter(EventInterface $event)

コントローラーの beforeFilter メソッドの前に呼び出されますが、コントローラーのinitialize() メソッドの 後 です。

startup(EventInterface $event)

コントローラーの beforeFilter メソッドの後、コントローラーの現在のアクションハンドラの前に呼び出されます。

beforeRender(EventInterface $event)

コントローラーがリクエストされたアクションのロジックを実行した後、ビューとレイアウトが描画される前に呼び出されます。

shutdown(EventInterface $event)

出力結果がブラウザーに送信される前に呼び出されます。

beforeRedirect(EventInterface $event, $url, Response $response)

コントローラーの redirect メソッドが呼び出された時に、他のアクションより先に呼びだされます。このメソッドが false を返す時、コントローラーはリクエストのリダイレクトを中断します。 $url と $response パラメーターを使用すると、リダイレクト先やレスポンスの任意の他のヘッダーを検査や変更することができます。

コンポーネントイベントでのリダイレクトの使用

コンポーネントのコールバックメソッド内からリダイレクトするには、次のようにします。

public function beforeFilter(EventInterface $event)
{
    $event->stopPropagation();
    return $this->getController()->redirect('/');
}

イベントを停止することで、CakePHPに他のコンポーネントのコールバックを実行させたくないこと、そしてコントローラがこれ以上アクションを処理してはいけないことを知らせます。 As of 4.1.0 you can raise a RedirectException to signal a redirect:

use Cake\Http\Exception\RedirectException;
use Cake\Routing\Router;

public function beforeFilter(EventInterface $event)
{
    throw new RedirectException(Router::url('/'))
}

Raising an exception will halt all other event listeners and create a new response that doesn't retain or inherit any of the current response's headers. When raising a RedirectException you can include additional headers:

throw new RedirectException(Router::url('/'), 302, [
    'Header-Key' => 'value',
]);

バージョン 4.1.0 で追加.

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

AuthComponent

class AuthComponent(ComponentCollection $collection, array $config = [])

ユーザーを識別し、認証し、権限を付与することは、ほとんどすべてのウェブアプリケーションに共通の機能です。CakePHP の AuthComponent ではそういったタスクを実行するためのプラガブルな方法を提供します。AuthComponent により、認証オブジェクトと、ユーザーの権限を識別・判定する柔軟な仕組みを作るための権限判定オブジェクトを組み合わせることができるようになります。

バージョン 4.0.0 で非推奨: AuthComponent は 4.0.0 では非推奨になり、 authorization [https://book.cakephp.org/authorization/] と authentication [https://book.cakephp.org/authentication/] プラグインに置き換えられます。

以降を読む前に

認証の設定には、ユーザーテーブルの定義、モデルやコントローラーやビューの作成など、いくつかのステップが必要です。

CMS チュートリアルの中で順を追って説明しています。

もし、CakePHP のための既存の認証や認可の解決方法を探しているなら、Awesome CakePHP リストの Authentication and Authorization [https://github.com/FriendsOfCake/awesome-cakephp/blob/master/README.md#authentication-and-authorization] セクションを見てください。

認証

認証とは、与えられた認証情報によりユーザーを識別し、そのユーザーが言うとおりの人物であることを確実なものにする処理のことです。たいていの場合、これはユーザー名とパスワードにより行われ、それと既知のユーザーリストを照らし合わせます。CakePHP には、あなたのアプリケーション内に保管されているユーザーを認証するための組み込み済みの方法がいくつか存在します。

FormAuthenticate では、POST されたデータをもとに認証を行うことが可能です。通常これは、ユーザーが情報を入力するログインフォームです。

BasicAuthenticate では、Basic HTTP 認証を使った認証を行うことが可能です。

DigestAuthenticate では、ダイジェスト HTTP 認証を使った認証を行うことが可能です。

デフォルトで AuthComponent は FormAuthenticate を使用します。

認証タイプの選択

たいていの場合は、フォームベースの認証を提供したいと思うでしょう。これはウェブブラウザーを使うユーザーにとってはもっとも簡単な方法です。もし、API やウェブサービスを構築しているなら、 Basic 認証やダイジェスト認証も考慮したくなるかもしれません。ダイジェスト認証と Basic 認証の重要な違いは、どのようにパスワードを扱うかということにあります。Basic 認証では、ユーザー名とパスワードは平文のテキストとしてサーバーに送信されます。そのため Basic 認証は SSL を使わないアプリケーションには向いていません。これは、慎重に扱うべきパスワードが露出してしまうことになるためです。ダイジェスト認証はユーザー名やパスワード、そのほかのいくつかの詳細情報のダイジェストハッシュを使います。そのため ダイジェスト認証は SSL 暗号化しないアプリケーションにもふさわしいものです。

また、OpenID のような認証システムを使うことも可能です。ただし、OpenID は CakePHP のコアには含まれません。

認証ハンドラーの設定

認証ハンドラーは authenticate を使用して設定します。認証に使うハンドラーを１つもしくは複数設定することができます。複数のハンドラーを設定することで、複数のログインの仕組みをサポートすることが可能です。ユーザーがログインする際、認証ハンドラーは宣言されている順に判定されます。あるハンドラーでユーザーを識別ができたら、それ以降のハンドラーでは判定されません。逆に、例外を投げることですべての認証を失敗にすることもできます。投げられた任意の例外をキャッチし、必要に応じて処理する必要があります。

コントローラーの beforeFilter() もしくは initialize() メソッドの中に、認証ハンドラーを設定することができます。配列を使用して各認証オブジェクトに設定情報を渡すことができます。

// シンプルな設定
$this->Auth->setConfig('authenticate', ['Form']);

// 設定を記述
$this->Auth->setConfig('authenticate', [
    'Basic' => ['userModel' => 'Members'],
    'Form' => ['userModel' => 'Members']
]);

２番目の例では、 userModel キーを２回宣言しなければならないということに気づいたでしょう。コードを DRY に保ちたいなら、 all キーを使うことができます。この特別なキーを使うことで、列挙したオブジェクトすべてに設定が渡されることになります。 all キーは AuthComponent::ALL と記述することもできます。

// 'all' を使って設定を記述
$this->Auth->setConfig('authenticate', [
    AuthComponent::ALL => ['userModel' => 'Members'],
    'Basic',
    'Form'
]);

上記の例では、 Form と Basic の両方ともが 'all' キーで宣言された設定を取得することになります。特定の認証オブジェクトに個別に書いた設定は 'all' キーの同名のキーの情報をオーバーライドします。コアの認証オブジェクトでは次の設定キーをサポートしています。

fields ユーザーを識別するのに使うフィールド(配列)。ユーザー名とパスワードのフィールドを指定するキー username と password を使用することができます。

userModel ユーザーテーブルのモデル名。デフォルトは Users。

finder ユーザーレコードを取得するために使用するファインダーメソッド。デフォルトは 'all'。

passwordHasher パスワード処理のクラス。デフォルトは　Default。

scope と contain オプションは 3.1 で非推奨になりました。ユーザーレコードを取得するクエリーを変更する代わりにカスタムファインダーを使用してください。

userFields オプションは 3.1 で非推奨になりました。カスタムファインダーの select() を使用してください。

initialize() メソッドの中でユーザーの特定の列名を設定するには:

public function initialize()
{
    parent::initialize();
    $this->loadComponent('Auth', [
        'authenticate' => [
            'Form' => [
                'fields' => ['username' => 'email', 'password' => 'passwd']
            ]
        ]
    ]);
}

Auth の他の設定キー (authError や loginAction など) を authenticate や Form の要素として書いてはいけません。それらは authenticate キーと同じレベルであるべきです。上記の例を他の Auth 設定を使って書いた場合は次のようになります。

public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('Auth', [
        'loginAction' => [
            'controller' => 'Users',
            'action' => 'login',
            'plugin' => 'Users'
        ],
        'authError' => 'Did you really think you are allowed to see that?',
        'authenticate' => [
            'Form' => [
                'fields' => ['username' => 'email']
            ]
        ],
        'storage' => 'Session'
    ]);
}

共通の設定に加えて、Basic 認証では次のキーも利用できます:

realm 認証される realm。デフォルトでは env('SERVER_NAME') 。

共通の設定に加えて、ダイジェスト認証では次のキーも利用できます:

realm realm 認証の認証先。デフォルトはサーバー名。

nonce 認証で使われる nonce。デフォルトは uniqid() 。

qop デフォルトは auth。現時点では他の値はサポートされていません。

opaque クライアントから変更されることなく戻されるべき文字列。デフォルトは md5($settings['realm']) 。

注釈

ユーザーレコードを見つけるために、データベースには username のみを使用して問い合わせます。パスワードのチェックは、PHP で行われます。これは、(デフォルトで使用される) bcrypt のようなハッシュ化アルゴリズムは、毎回新しいハッシュを生成するからです。同じ文字列であっても、パスワードが合致するかどうかをチェックするために SQL で単純に文字列を比較することはできません。

検索クエリーのカスタマイズ

認証クラスの設定で finder オプションを使用して、ユーザーレコードを取得するために使用されるクエリーをカスタマイズすることができます。

public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('Auth', [
        'authenticate' => [
            'Form' => [
                'finder' => 'auth'
            ]
        ],
    ]);
}

この場合、 UsersTable がファインダーメソッドの findAuth() を持っていなければなりません。下に示した例では、クエリーは、必要なフィールドのみを取得し条件を追加するために変更されます。 username と password のようにユーザーを認証する必要があるフィールドを、選択することを保証する必要があります。

public function findAuth(\Cake\ORM\Query $query, array $options)
{
    $query
        ->select(['id', 'username', 'password'])
        ->where(['Users.active' => 1]);

    return $query;
}

注釈

finder オプションは 3.1 以降で使用可能です。そのバージョンより前なら、クエリーを変更するために scope と contain オプションを使用することができます。

ユーザーの識別とログイン

AuthComponent::identify()

リクエスト中の認証情報を使用してユーザーを識別するために、 $this->Auth->identify() を手動で呼ぶ必要があります。その後、セッションにユーザー情報を保存する、すなわち、ユーザーをログインするために $this->Auth->setUser() メソッドを使用します。

ユーザーを認証する際には、設定されている認証オブジェクトを設定された順にチェックしていきます。あるオブジェクトでユーザーが識別できたら、以降のオブジェクトはチェックされません。ログインフォームで動作する login 関数のサンプルは次のようになります。

public function login()
{
    if ($this->request->is('post')) {
        $user = $this->Auth->identify();
        if ($user) {
            $this->Auth->setUser($user);
            return $this->redirect($this->Auth->redirectUrl());
        } else {
            $this->Flash->error(__('Username or password is incorrect'));
        }
    }
}

上記のコードでは、はじめに POST データを使用して、ユーザーを識別しようと試みます。成功した場合、ログインがリクエストの間で持続するようにセッションにユーザー情報を設定し、彼らが訪れていた最後のページまたは loginRedirect の設定で指定された URL のどちらかにリダイレクトします。ログインに失敗した場合は、フラッシュメッセージが設定されます。

警告

$this->Auth->setUser($data) は、そのメソッドに渡されたデータを持つユーザーとしてログインします。これは、実際には認証クラスに対する認証情報をチェックしません。

ログイン後のリダイレクト

AuthComponent::redirectUrl()

ユーザーがログインした後、一般的に彼らが来たところに戻すのにリダイレクトすることになるでしょう。ユーザーがログインした後にリダイレクトされるべき目的地を設定するために、URL を渡してください。

パラメーターが渡されない場合、返される URL は、次の規則に従います。

redirect クエリー文字列が存在していて、同じドメインの現在のアプリが実行されている場合は、正規化された URL を返します。 3.4.0 より前は、セッションの Auth.redirect の値が使用されていました。

クエリー文字列やセッション値がなく loginRedirect の設定がある場合は、 loginRedirect の値を返します。

セッションがなく loginRedirect がない場合は、 / を返します。

ステートレス認証システムの作成

Basic およびダイジェストはステートレス認証スキームであり、初回の POST またはフォームを必要としません。もし Basic／ダイジェストオーセンティケーターだけを使っているならコントローラーにログインアクションは必要ありません。ステートレス認証はリクエストごとにユーザーの認証情報を再検証し、これは追加のオーバーヘッドが多少生じますが、クライアントは、クッキーを使用せずにログインすることができますし、AuthComponent で API を構築するのに、より適しています。

AuthComponent がユーザーレコードの格納にセッションを使用しないように、ステートレスなオーセンティケーターのための strage 設定は、 Memory に設定する必要があります。また、unauthorizedRedirect に false を設定すると、 AuthComponent が、デフォルトの動作であるリファラーへのリダイレクトの代わりに ForbiddenException をスローすることもできます。

unauthorizedRedirect オプションは、認証されたユーザーに対してのみ適用します。ユーザーがまだ認証されていなかったり、ユーザーをリダイレクトさせたくない場合、 Basic や Digest のような１つまたは複数のステートレスなオーセンティケーターを読み込む必要があります。

認証オブジェクトはクッキーに依存しないユーザーログインのシステムをサポートするために使われる getUser() メソッドを実装することができます。典型的な getUser メソッドはリクエストや環境を見て、ユーザーを識別するためにその情報を使います。HTTP Basic 認証の例を挙げると、ユーザー名とパスワードの値として $_SERVER['PHP_AUTH_USER'] と $_SERVER['PHP_AUTH_PW'] を使います。

注釈

期待通りに認証が動作しない場合、クエリーが全く実行されていないかどうかをチェックしてください (BaseAuthenticate::_query($username) をご覧ください)。クエリーが実行されない場合、 $_SERVER['PHP_AUTH_USER'] と $_SERVER['PHP_AUTH_PW'] がウェブサーバーによって読み込まれたかどうかをチェックしてください。もし Apache で FastCGI-PHP を使用している場合は、 webroot 内の .htaccess ファイルに次の行を追加する必要があるかもしれません。

RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

リクエストごとに、それらの値 (PHP_AUTH_USER と PHP_AUTH_PW) を再度ユーザーを識別するために使い、正規のユーザーであることを確認します。認証オブジェクトの authenticate() メソッドと同様に、 getUser() メソッドも成功ならユーザー情報の配列を、失敗なら false を返すようにしてください。

public function getUser(ServerRequest $request)
{
    $username = env('PHP_AUTH_USER');
    $pass = env('PHP_AUTH_PW');

    if (empty($username) || empty($pass)) {
        return false;
    }
    return $this->_findUser($username, $pass);
}

上記は HTTP Basic 認証用の getUser メソッドをどのように実行できるのかを示しています。 _findUser() メソッドは BaseAuthenticate の一部でユーザー名、パスワードをもとにユーザーを識別します。

Basic 認証の使用

Basic 認証を使用すると、イントラネットアプリケーションや単純な API のシナリオに使用できるステートレス認証を作成することができます。Basic 認証の認証情報は、リクエストごとに再チェックされます。

警告

Basic 認証では、プレーンテキストで認証情報を送信します。 Basic 認証を使用するときは、HTTPS を使用する必要があります。

Basic 認証を使用するには、AuthComponent を設定する必要があります。

$this->loadComponent('Auth', [
    'authenticate' => [
        'Basic' => [
            'fields' => ['username' => 'username', 'password' => 'api_key'],
            'userModel' => 'Users'
        ],
    ],
    'storage' => 'Memory',
    'unauthorizedRedirect' => false
]);

ここでは、フィールドとして、ユーザー名 + API キーを使用し、Users モデルを使用しています。

Basic 認証のための API キーの作成

基本的な HTTP はプレーンテキストで認証情報を送信するので、ユーザーに自分のログインパスワードを送信させることは賢明ではありません。代わりに、不透明な API キーが、一般的に使用されます。 CakePHP のライブラリーを使用してランダムにこれらの API トークンを生成することができます。

namespace App\Model\Table;

use Cake\Auth\DefaultPasswordHasher;
use Cake\Utility\Text;
use Cake\Event\EventInterface;
use Cake\ORM\Table;
use Cake\Utility\Security;

class UsersTable extends Table
{
    public function beforeSave(EventInterface $event)
    {
        $entity = $event->getData('entity');

        if ($entity->isNew()) {
            $hasher = new DefaultPasswordHasher();

            // API の 'トークン' を生成
            $entity->api_key_plain = Security::hash(Security::randomBytes(32), 'sha256', false);

            // ログインの際に BasicAuthenticate がチェックする
            // トークンを Bcrypt で暗号化
            $entity->api_key = $hasher->hash($entity->api_key_plain);
        }
        return true;
    }
}

上記は、ユーザーが保存されるごとにランダムなハッシュを生成します。上記のコードでは、 api_key (ハッシュ化された API キーを格納) と api_key_plain (API キーの平文バージョン) の２つのカラムを持つと仮定しており、後でそれをユーザーに表示することができます。プレーン HTTP を介する場合でも、パスワードの代わりにキーを使用することで、ユーザーが自分の元のパスワードの代わりに、不透明なトークンを使用できます。ユーザーのリクエストに応じて、API キーを再生成するロジックを含めることも賢明です。

ダイジェスト認証の使用

ダイジェスト認証は、Basic 認証よりも改善されたセキュリティモデルを提供しています。ユーザーの認証情報がリクエストヘッダーに送信されないからです。代わりにハッシュが送信されます。

ダイジェスト認証を使用するには、 AuthComponent を設定する必要があります。

$this->loadComponent('Auth', [
    'authenticate' => [
        'Digest' => [
            'fields' => ['username' => 'username', 'password' => 'digest_hash'],
            'userModel' => 'Users'
        ],
    ],
    'storage' => 'Memory',
    'unauthorizedRedirect' => false
]);

ここでは、フィールドとして、ユーザー名 + digest_hash を使用し、 Users モデルを使用しています。

ダイジェスト認証のパスワードのハッシュ化

ダイジェスト認証は RFC で定義されたフォーマットでハッシュ化されたパスワードが必要です。パスワードをダイジェスト認証で使用できるよう正しくハッシュ化するために、特別なパスワードハッシュ化の関数 DigestAuthenticate を使ってください。ダイジェスト認証とその他の認証戦略を合わせて利用する場合には、通常のハッシュ化パスワードとは別のカラムでダイジェストパスワードを保管するのをお勧めします。

namespace App\Model\Table;

use Cake\Auth\DigestAuthenticate;
use Cake\Event\EventInterface;
use Cake\ORM\Table;

class UsersTable extends Table
{
    public function beforeSave(EventInterface $event)
    {
        $entity = $event->getData('entity');

        // ダイジェスト認証のためのパスワードを作成。
        $entity->digest_hash = DigestAuthenticate::password(
            $entity->username,
            $entity->plain_password,
            env('SERVER_NAME')
        );
        return true;
    }
}

ダイジェスト認証のパスワードは、ダイジェスト認証の RFC に基づいており、他のハッシュ化パスワードよりもやや多くの情報が必要です。

注釈

AuthComponent::$authenticate 内で DigestAuthentication が設定された場合、 DigestAuthenticate::password() の第３パラメーターは定義した 'realm' の設定値と一致する必要があります。このデフォルトは env('SCRIPT_NAME') です。複数の環境で一貫したハッシュが欲しい場合に static な文字列を使用することができます。

カスタム認証オブジェクトの作成

認証オブジェクトはプラガブルなので、カスタム認証オブジェクトを自分のアプリケーション内にでも、プラグインとしてでも作成が可能です。もし例えば、OpenID 認証オブジェクトを作成したいのだとしたら、 src/Auth/OpenidAuthenticate.php の中で次のように記述することができます。

namespace App\Auth;

use Cake\Auth\BaseAuthenticate;
use Cake\Http\ServerRequest;
use Cake\Http\Response;

class OpenidAuthenticate extends BaseAuthenticate
{
    public function authenticate(ServerRequest $request, Response $response)
    {
        // OpenID 用の処理をここに記述します。
        // ユーザー認証が通った場合は、user の配列を返します。
        // 通らなかった場合は false を返します。
    }
}

ユーザーとユーザー情報の配列とを識別できない場合、認証オブジェクトは false を返す必要があります。必ずしも BaseAuthenticate を継承する必要はなく、認証オブジェクトが Cake\Event\EventListenerInterface を実装することだけが必要です。 BaseAuthenticate クラスは、一般的に使用されている便利な多くのメソッドを提供します。認証オブジェクトがステートレスまたはクッキーレスの認証をサポートする必要がある場合には、 getUser() メソッドを実装することができます。詳しくは、Basic 認証とダイジェスト認証のセクションを参照してください。

AuthComponent は、ユーザーを識別した後と、ユーザーがログアウトする前に、２つのイベント Auth.afterIdentify と Auth.logout をトリガーします。あなたの認証クラスの implementedEvents() メソッドからマッピング配列を返すことによって、これらのイベントのコールバック関数を設定することができます。

public function implementedEvents()
{
    return [
        'Auth.afterIdentify' => 'afterIdentify',
        'Auth.logout' => 'logout'
    ];
}

カスタム認証オブジェクトの利用

カスタム認証オブジェクトを作成したら、 AuthComponent の authenticate 配列内にそれを含めることで利用することができます。

$this->Auth->setConfig('authenticate', [
    'Openid', // app 内の認証オブジェクト
    'AuthBag.Openid', // プラグインの認証オブジェクト
]);

注釈

単純な表記を使用する場合、認証オブジェクトの初期化の際「Authenticate」の文字は必要ありませんので注意してください。代わりに、名前空間を使用する場合は「Authenticate」の文字を含むクラスの完全な名前空間を設定する必要があります。

認証されていないリクエストの処理

認証されていないユーザーが最初に保護されたページにアクセスしようとすると、 チェーンの最後のオーセンティケーターの unauthenticated() メソッドが呼び出されます。認証オブジェクトが適切に応答またはリダイレクトを送信処理し、それ以上のアクションは必要ないということを示すためにレスポンスオブジェクトを返すことができます。 authenticate 設定で認証オブジェクトを指定する順序を設定できます。

オーセンティケーターが null を返した場合、 AuthComponent は、ユーザーをログインアクションにリダイレクトします。Ajax リクエストでかつ ajaxLogin 設定が指定されていた場合、その要素は描画され、そうでなければ HTTP ステータスコード 403 が返されます。

認証関連のフラッシュメッセージの表示

Auth が生成するセッションエラーメッセージを表示するためには、あなたのレイアウトに次のコードを加えなければなりません。 templates/layout/default.php の body 部に次の2行を追加してください。

// 3.4.0 以降は、これだけが必要です。
echo $this->Flash->render();

// 3.4.0 より前は、これも同様に必要です。
echo $this->Flash->render('auth');

AuthComponent の flash 設定を使うことでエラーメッセージをカスタマイズすることができます。 flash 設定を使うことで、 AuthComponent がフラッシュメッセージのために使うパラメーターを設定することができます。利用可能なキーは次のとおりです。

key - 使用されるキー。デフォルトは 'default'。 3.4.0 より前の key のデフォルトは 'auth'。

element - 描画に使用するエレメント名。デフォルトは null。

params - 使用される追加の params 配列。デフォルトは [] 。

フラッシュメッセージの設定だけでなく、 AuthComponent が使用する他のエラーメッセージをカスタマイズすることもできます。コントローラーの beforeFilter() の中や component の設定で、認証が失敗した際に使われるエラーをカスタマイズするのに authError を使うことができます。

$this->Auth->setConfig('authError', 'Woopsie, you are not authorized to access this area.');

ユーザーがすでにログインしていた後にのみ、認可エラーを表示したいということもあると思います。その場合は false を設定することにより、このメッセージを表示しないようにすることができます。

コントローラーの beforeFilter() 、またはコンポーネントの設定で:

if (!$this->Auth->user()) {
    $this->Auth->setConfig('authError', false);
}

パスワードのハッシュ化

データベースに永続化される前に、パスワードをハッシュ化する責任があり、最も簡単な方法は、 User エンティティーでセッター機能を使用することです。

namespace App\Model\Entity;

use Cake\Auth\DefaultPasswordHasher;
use Cake\ORM\Entity;

class User extends Entity
{
    // ...

    protected function _setPassword($password)
    {
        if (strlen($password) > 0) {
          return (new DefaultPasswordHasher)->hash($password);
        }
    }

    // ...
}

AuthComponent は、ユーザーの認証情報を検証するときに、 DefaultPasswordHasher を使用するようにデフォルトで設定されています。ユーザーを認証するために追加の設定は必要とされません。

DefaultPasswordHasher は、業界内で使用される強力なパスワードハッシュソリューションの 1つである bcrypt ハッシュアルゴリズムを内部で使用しています。このパスワードハッシュ化クラスを使用することが推奨されていますが、パスワードが異なるアルゴリズムでハッシュ化されたユーザーのデータベースを管理しているケースも考慮しています。

カスタムパスワードハッシュ化クラスの作成

異なるパスワードハッシュ化クラスを使用するためには、src/Auth/LegacyPasswordHasher.php クラスを作成し、 hash() と check() メソッドを実装する必要があります。このクラスは AbstractPasswordHasher クラスを継承する必要があります。

namespace App\Auth;

use Cake\Auth\AbstractPasswordHasher;

class LegacyPasswordHasher extends AbstractPasswordHasher
{
    public function hash($password)
    {
        return sha1($password);
    }

    public function check($password, $hashedPassword)
    {
        return sha1($password) === $hashedPassword;
    }
}

その後、独自のパスワードハッシュ化クラスを使用するために AuthComponent の設定が必要です。

public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('Auth', [
        'authenticate' => [
            'Form' => [
                'passwordHasher' => [
                    'className' => 'Legacy',
                ]
            ]
        ]
    ]);
}

レガシーシステムをサポートすることは良いアイデアですが、最新のセキュリティの進歩を使用してデータベースを保つことはさらに良いです。次のセクションでは、あるハッシュアルゴリズムから CakePHP のデフォルトに移行する方法を説明します。

ハッシュ化アルゴリズムの変更

CakePHP は、1つのアルゴリズムから別のユーザーのパスワードを移行するためのクリーンな方法を提供します。これは FallbackPasswordHasher クラスによって実現されます。 sha1 パスワードハッシュを使用している CakePHP 2.x のアプリを移行していると仮定すると、次のように AuthComponent を設定することができます。

public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('Auth', [
        'authenticate' => [
            'Form' => [
                'passwordHasher' => [
                    'className' => 'Fallback',
                    'hashers' => [
                        'Default',
                        'Weak' => ['hashType' => 'sha1']
                    ]
                ]
            ]
        ]
    ]);
}

hashers キーに指定された最初の名前が好ましいクラスですが、チェックが失敗した場合には、リスト内の他のクラスに切り替わります。

WeakPasswordHasher を使用している場合、パスワードにソルトを付与することを保証するために Security.salt 値を設定する必要があります。

ユーザーの古いパスワードをその場で更新するために、ログイン機能を変更することができます。

public function login()
{
    if ($this->request->is('post')) {
        $user = $this->Auth->identify();
        if ($user) {
            $this->Auth->setUser($user);
            if ($this->Auth->authenticationProvider()->needsPasswordRehash()) {
                $user = $this->Users->get($this->Auth->user('id'));
                $user->password = $this->request->getData('password');
                $this->Users->save($user);
            }
            return $this->redirect($this->Auth->redirectUrl());
        }
        ...
    }
}

エンティティーのセッター関数に再びプレーンなパスワードを設定することで、 上記の例に示すように、パスワードをハッシュ化してからエンティティーを保存します。

手動でのユーザーログイン

AuthComponent::setUser(array $user)

独自のアプリケーションを登録した直後など、時には手動によるログインが必要になる事態が発生することもあるでしょう。ログインさせたいユーザーデータを引数に $this->Auth->setUser() を呼び出すことで、これを実現することができます。

public function register()
{
    $user = $this->Users->newEntity($this->request->getData());
    if ($this->Users->save($user)) {
        $this->Auth->setUser($user->toArray());
        return $this->redirect([
            'controller' => 'Users',
            'action' => 'home'
        ]);
    }
}

警告

setUser() メソッドに渡される配列に新たなユーザー ID が追加されていることを必ず確認してください。そうでなければ、そのユーザー ID が利用できなくなってしまいます。

ログインしているユーザーへのアクセス

AuthComponent::user($key = null)

ユーザーがログインしたあと、現状のそのユーザーについての特定の情報が必要になることもあるでしょう。 AuthComponent::user() を使うことで、現在ログインしているそのユーザーにアクセスすることができます。

// コントローラーや他のコンポーネントの中から
$this->Auth->user('id');

現在のユーザーがログインしていない、もしくはキーが存在しないなら、null を返します。

ログアウト

AuthComponent::logout()

最終的には認証を解除し、適切な場所へとリダイレクトするためのてっとり早い方法がほしくなるでしょう。このメソッドはあなたのアプリケーション内のメンバーページに「ログアウト」リンクを入れたい場合にも便利です。

public function logout()
{
    return $this->redirect($this->Auth->logout());
}

すべてのクライアントで、ダイジェスト認証や Basic 認証でログインしたユーザーのログアウトを達成すること難しいものです。多くのブラウザーは開いている間だけ継続する認証情報を保有しています。一部のクライアントは 401 のステータスコードを送信して強制的にログアウトすることができます。認証 realm の変更は、一部のクライアントで機能させるためのもう１つの解決法です。

認証実行時の判定

いくつかのケースでは、 beforeFilter(Event $event) メソッドの中で $this->Auth->user() を使用したいこともあるでしょう。これは、 checkAuthIn 設定キーを使用して達成可能です。最初に認証チェックを行いたいイベントの指定は次のように変更します。

// initialize() の中で認証するために AuthComponent を設定
$this->Auth->setConfig('checkAuthIn', 'Controller.initialize');

checkAuthIn のデフォルト値は 'Controller.startup' ですが、 'Controller.initialize' を使用することによって、 beforeFilter() メソッドの前に初めて認証が行われます。

認可

認可は識別され認証されたユーザーが、要求するリソースへのアクセスを許可されていることを保証するプロセスです。有効にした場合、 AuthComponent は自動的に認可ハンドラーをチェックして、要求しているリソースへのアクセスを許可されたユーザーがログインしていることを確認することができます。いくつかの組み込みの認可ハンドラーがありますが、あなたのアプリケーションまたはプラグインの一部として独自のものを作成することができます。

ControllerAuthorize アクティブなコントローラーの isAuthorized() を呼び出し、ユーザーの認可のために、その戻り値を使用します。これはユーザーの認可をもっともシンプルに行う方法です。

注釈

CakePHP 2.x で利用可能な ActionsAuthorize と CrudAuthorize アダプタは現在、独立したプラグインの cakephp/acl [https://github.com/cakephp/acl] に移されました。

認可ハンドラーの設定

認可ハンドラーは authorize キーを使用して設定します。認可に使うハンドラーを１つもしくは複数設定することができます。複数のハンドラーを使うことで、さまざまな認可の方法をサポートできます。認可ハンドラーがチェックされる際には、宣言された順に呼び出されます。もし、認可を確認することができない場合やチェックが失敗した場合、ハンドラーは false を返す必要があります。もし、正常に認可を確認することができた場合、ハンドラーは true を返す必要があります。いずれかのハンドラーを通過できるまで、順番に呼び出されます。すべてのチェックが失敗した場合、ユーザーは元いたページへとリダイレクトされます。また、例外をスローすることによって、すべての認可を失敗にすることもできます。投げられた任意の例外をキャッチし、それらを処理する必要があります。

コントローラーの beforeFilter() もしくは initialize() メソッドの中に、認可ハンドラーを設定することができます。配列を使用して各認可オブジェクトに設定情報を渡すことができます。

// 基本的な設定
$this->Auth->setConfig('authorize', ['Controller']);

// 設定を記述
$this->Auth->setConfig('authorize', [
    'Actions' => ['actionPath' => 'controllers/'],
    'Controller'
]);

authorize も authenticate とほぼ同様で、 all キーを使うことでコードを DRY に保ちやすくなります。この特別なキーを使うことで、列挙したオブジェクトすべてに設定が渡されることになります。 all キーは AuthComponent::ALL と記述することもできます。

// 'all' を使って設定を記述
$this->Auth->setConfig('authorize', [
    AuthComponent::ALL => ['actionPath' => 'controllers/'],
    'Actions',
    'Controller'
]);

上記の例では、 Actions と Controller の両方ともが 'all' キーで宣言された設定を取得することになります。特定の認可オブジェクトに個別に書いた設定は 'all' キーの同名のキーの情報をオーバーライドします。

認証されたユーザーが、アクセスを許可されていない URL にアクセスしようとすると、リファラーにリダイレクトされてしまいます。このようなリダイレクトをしたくない場合 (主にステートレス認証アダプタを使用する際に必要)、設定オプション unauthorizedRedirect に false を設定することができます。これは、 AuthComponent がリダイレクトする代わりに ForbiddenException を投げるようになります。

カスタム認可オブジェクトの作成

認可オブジェクトはプラガブルなので、カスタム認可オブジェクトを自分のアプリケーション内にでも、プラグインとしてでも作成が可能です。もし例えば、LDAP 認可オブジェクトを作成したいのだとしたら、 src/Auth/LdapAuthorize.php の中で次のように記述することができます。

namespace App\Auth;

use Cake\Auth\BaseAuthorize;
use Cake\Http\ServerRequest;

class LdapAuthorize extends BaseAuthorize
{
    public function authorize($user, ServerRequest $request)
    {
        // ldap 用の処理をここに記述します。
    }
}

認可オブジェクトは該当ユーザーがアクセスを拒否されたり、該当オブジェクトでのチェックができなかった場合には false を返してください。認可オブジェクトがユーザーのアクセスが妥当だと判定したなら true を返してください。 BaseAuthorize を継承する必要はありませんが、独自の認可オブジェクトは必ず authorize() メソッドを実装してください。 BaseAuthorize クラスではよく使われる強力なメソッドが多数提供されます。

カスタム認可オブジェクトの利用

カスタム認可オブジェクトを作成したら、AuthComponent の authorize 配列にそれらを含めることで使うことができます。

$this->Auth->setConfig('authorize', [
    'Ldap', // app 内の認可オブジェクト
    'AuthBag.Combo', // プラグインの認可オブジェクト
]);

認可を使用しない

どの組み込み認可オブジェクトも使いたくなくて、 AuthComponent の外側で完全に権限を扱いたい場合は、 $this->Auth->setConfig('authorize', false); を設定することが可能です。デフォルトでは、 AuthComponent は、 authorize に false をセットした状態で始まります。認可スキームを使用しない場合は、独自にコントローラーの beforeFilter() または別のコンポーネントで認可を確認してください。

公開するアクションの作成

AuthComponent::allow($actions = null)

コントローラーのアクションが完全に公開すべきものであったり、ユーザーのログインが不要であったりという場合があります。 AuthComponent は悲観的であり、デフォルトではアクセスを拒否します。 AuthComponent::allow() を使うことで、公開すべきアクションに印をつけることができます。アクションに公開の印をつけることで、 AuthComponent は該当のユーザーがログインしているかのチェックも、認可オブジェクトによるチェックも行わなくなります。

// すべてのアクションを許可。
$this->Auth->allow();

// index アクションのみ許可。
$this->Auth->allow('index');

// view と index アクションのみ許可
$this->Auth->allow(['view', 'index']);

引数を空で呼びだすと、すべてのアクションを公開することを許可します。単一のアクションの場合、文字列としてアクション名を指定することができます。それ以外の場合は配列を使用します。

注釈

UsersController の「ログイン」アクションを許可するリストに追加してはいけません。そうすることで、 AuthComponent の正常な機能に問題を引き起こします。

認可が必要なアクションの作成

AuthComponent::deny($actions = null)

デフォルトでは、全てのアクションは認可を必要とします。一方、アクションに公開の印を付けた後、その公開アクションを取り消したくなるかもしれません。そのために AuthComponent::deny() を使うことができます。

// 全てのアクションを拒否。
$this->Auth->deny();

// １つのアクションを拒否。
$this->Auth->deny('add');

// アクションのグループを拒否。
$this->Auth->deny(['add', 'edit']);

引数を空で呼びだすと、すべてのアクションを拒否します。単一のアクションの場合、文字列としてアクション名を指定することができます。それ以外の場合は配列を使用します。

ControllerAuthorize の利用

ControllerAuthorize では、コントローラーのコールバックで認可チェックを処理することができます。非常にシンプルな認可を行う場合や、認可を行うのにモデルとコンポーネントを合わせて利用する必要がある場合、しかしカスタム認可オブジェクトを作成したくない場合に、これは理想的です。

コールバックでは必ず isAuthorized() を呼んでください。これは該当ユーザーがリクエスト内でリソースにアクセスすることが許可されるかを boolean で返します。コールバックにはアクティブなユーザーが渡されますので、チェックが可能です。

class AppController extends Controller
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Auth', [
            'authorize' => 'Controller',
        ]);
    }

    public function isAuthorized($user = null)
    {
        // 登録済みユーザーなら誰でも公開機能にアクセス可能です。
        if (!$this->request->getParam('prefix')) {
            return true;
        }

        // admin ユーザーだけが管理機能にアクセス可能です。
        if ($this->request->getParam('prefix') === 'Admin') {
            return (bool)($user['role'] === 'admin');
        }

        // デフォルトは拒否
        return false;
    }
}

上記のコールバックは非常にシンプルな認可システムとなっており、role = admin のユーザーだけが admin に設定されたアクションにアクセスすることができます。

設定オプション

以下の設定は、コントローラーの initialize() メソッドもしくは、 $this->Auth->setConfig() を使用するかのどちらかで定義することができます。

ajaxLogin

不正な／期限切れのセッションを伴った Ajax リクエストの場合に render すべき任意のビューエレメントの名前。

allowedActions

ユーザーの妥当性チェックが必要ないコントローラーのアクションの配列。

authenticate

ユーザーのログインに使いたい認証オブジェクトの配列を設定してください。コアの認証オブジェクトがいくつか存在します。 以降を読む前に を参照してください。

authError

ユーザーがアクセス権の無いオブジェクトやアクションにアクセスした場合に表示されるエラー。

false を設定することにより、authError メッセージを表示しないようにできます。

authorize

各リクエストでユーザーの認可に使いたい認可オブジェクトの配列を設定してください。認可 を参照してください。

flash

Auth が FlashComponent::set() でフラッシュメッセージを行う必要があるときに使用します。利用可能なキーは以下のとおりです。

element - 使用されるエレメント。デフォルトは 'default' 。

key - 使用されるキー。デフォルトは 'auth' 。

params - 使用される追加の params 配列。デフォルトは '[]' 。

loginAction

ログインを扱うコントローラーとアクションを表す (文字列や配列で定義した) URL。デフォルトは /users/login 。

loginRedirect

ログイン後のリダイレクト先のコントローラーとアクションを表す (文字列や配列で定義した) URL。この値はユーザーが Auth.redirect をセッション内に持っている場合には無視されます。

logoutRedirect

ユーザーがログアウトした後のリダイレクト先となるデフォルトのアクション。 AuthComponent は post-logout のリダイレクトを扱いませんが、リダイレクト先の URL は AuthComponent::logout() から返されるものとなります。デフォルトは loginAction 。

unauthorizedRedirect

許可されていないアクセスに対する処理を制御します。デフォルトでは、許可されていないユーザーはリファラーの URL か loginRedirect か、もしくは '/' にリダイレクトされます。 false をセットした場合は、リダイレクトする代わりに ForbiddenException が送出されます。

storage

ユーザーレコードを永続化するために使用するストレージクラス。ステートレスオーセンティケーターを使用する場合には、 Memory に設定する必要があります。デフォルトは Session 。配列を使用して、ストレージクラスに設定オプションを渡すことができます。例えば、独自のセッションキーを使用するには、 storage に ['className' => 'Session', 'key' => 'Auth.Admin'] を設定します。

checkAuthIn

最初の認証チェックが行われるべきイベントの名前。デフォルトは Controller.startup 。コントローラーの beforeFilter() メソッドが実行される前にチェックしたい場合は、 Controller.initialize に設定することができます。

$this->Auth->getConfig() を呼ぶことで、現在の設定の値を取得できます。

$this->Auth->getConfig('loginAction');

return $this->redirect($this->Auth->getConfig('loginAction'));

例えば login ルートにユーザーをリダイレクトしたい場合に便利です。パラメーターを指定せずに、完全な設定が返されます。

AuthComponent で保護されたアクションのテスト

AuthComponent で保護されたコントローラーのアクションをテストする方法のコツは、認証が必要なアクションのテスト セクションをご覧ください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

フラッシュ

class Cake\Controller\Component\FlashComponent(ComponentCollection $collection, array $config = [])

FlashComponent は、フォームの処理後やデータの確認のために表示する一回限りのメッセージ通知に使用します。このような通知を CakePHP では「フラッシュメッセージ」と呼んでいます。 FlashComponent はフラッシュメッセージを $_SESSION に書き込みます。そして FlashHelper を使ってビューで表示します。

フラッシュメッセージの設定

FlashComponent は、フラッシュメッセージの設定に２つの方法を用意しています。ひとつは、 __call() マジックメソッドで、もうひとつは set() メソッドです。アプリケーションをより様々な表現を用いて利用するためには、 FlashComponent の __call() マジックメソッドを利用することで templates/element/flash ディレクトリー以下に配置されたエレメントをマッピングしたメソッド名を使用することができます。規約により、キャメルケース形式のメソッドは、小文字でアンダースコアー区切りのエレメント名に置き換えられます。

// templates/element/flash/success.php を使用
$this->Flash->success('This was successful');

// templates/element/flash/great_success.php を使用
$this->Flash->greatSuccess('This was greatly successful');

あるいは、エレメントをレンダリングしないで平文メッセージを設定するためには、 set() メソッドを用いることができます。

$this->Flash->set('This is a message');

FlashComponent の __call() および set() メソッドは任意に第2引数をとります、その配列のオプションは以下の通りです。

key デフォルトは ‘flash’。セッション内の ‘Flash’ キー配下の配列キー。

element デフォルトは null ですが、 __call() マジックメソッドの使用時には、自動的に設定されます。表示に使用されるエレメント名。

params キーバリューの任意の配列です。エレメントの中で変数として利用する配列。

オプションの使用例:

// コントローラーの中で
$this->Flash->success('The user has been saved', [
    'key' => 'positive',
    'clear' => true,
    'params' => [
        'name' => $user->name,
        'email' => $user->email
    ]
]);

// ビューの中で
<?= $this->Flash->render('positive') ?>

<!-- templates/element/flash/success.php の中で -->
<div id="flash-<?= h($key) ?>" class="message-info success">
    <?= h($message) ?>: <?= h($params['name']) ?>, <?= h($params['email']) ?>.
</div>

__call() マジックメソッドを使用している時、 element オプションは、常に置き換えられます。プラグインから指定したエレメントを取得するためには、 plugin パラメーターをセットしてください。例:

// コントローラーの中で
$this->Flash->warning('My message', ['plugin' => 'PluginName']);

上記のコードは plugins/PluginName/templates/element/flash 配下の warning.php エレメントを使用しています。

注釈

デフォルトでは、CakePHP は、クロスサイトスクリプティング防止のためにフラッシュメッセージ中の内容はエスケープします。フラッシュメセージ中のユーザーデータは、HTML エンコードされ、安全に表示されます。もし、フラッシュメッセージの中に HTML を含めたい場合、 escape オプションをセットする必要があり、escape オプションがセットされた際に、エスケープを無効にするようにフラッシュメッセージのテンプレートを調整してください。

フラッシュメッセージ内の HTML

'escape' オプションキーを使用することでフラッシュメッセージ中に HTML を出力することができます。

$this->Flash->info(sprintf('<b>%s</b> %s', h($highlight), h($message)), ['escape' => false]);

手動で入力をエスケープされることを確かめてください。上記の例では、 $highlight と $message は、非 HTML な入力で、エスケープされます。

フラッシュメッセージの表示に関する詳しい情報は、 FlashHelper セクションをご覧ください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

セキュリティ

class SecurityComponent(ComponentCollection $collection, array $config = [])

Security コンポーネントを使うと、アプリケーションにさらに堅牢なセキュリティを導入できます。このコンポーネントは、以下の様々なタスクのためのメソッドを提供します。

アプリケーションが受付ける HTTP メソッドの限定

フォーム改ざん防止

SSL の利用を要求

コントローラー間の通信制限

全てのコンポーネントと同様に、いくつかの設定できるパラメーターがあり、これら全てのプロパティーは、直接設定したり、コントローラーの beforeFilter() の中で、プロパティーと同じ名前のセッターメソッドで設定できます。

Security コンポーネントを使用することで、自動的にフォーム改ざんを 防止します。 Security コンポーネントによって、hidden トークンフィールドがフォームに追加され、チェックされます。

Security コンポーネントのフォーム保護機能と、 startup() コールバックでフォームデータを処理する他のコンポーネントを使用している場合、 initialize() メソッドの中で、それらのコンポーネントの前に Security コンポーネントを配置するようにしてください。

注釈

Security コンポーネントを使用している時、フォームの作成に FormHelper を使わなければなりません 。また、フィールドの "name" 属性を上書きしてはいけません 。Security コンポーネントは、FormHelper で作成され、管理されるインジケーターを確認します。(これらは create() と end() の中で作成されます。) 例えば JavaScript で無効化・ 削除・新規作成するなどして POST リクエスト中の投稿されたフィールドを動的に差し替えることは、リクエストが破棄されるきっかけになります。

副作用を避けるために、実行前に使用されている HTTP メソッドを常に確認する必要があります。正しい HTTP メソッドが使用されていることを確認するために、 HTTP メソッドをチェックする か、 Cake\Http\ServerRequest::allowMethod() を使用してください。

ブラックホールコールバックの処理

SecurityComponent::blackHole(Controller $controller, string $error = '', ?SecurityException $exception = null)

あるアクションが Security コンポーネントによって制限されている時、デフォルトでは、不正なリクエストとして 400 エラーを返し破棄します。コントローラー中のコールバック関数を blackHoleCallback 設定オプションに設定することによってこの振る舞いを変更できます。

コールバックメソッドを設定することによって、ブラックホール処理がどのように機能するかをカスタマイズすることができます。

public function beforeFilter(EventInterface $event)
{
    parent::beforeFilter($event);

    $this->Security->setConfig('blackHoleCallback', 'blackhole');
}

public function blackhole($type, SecurityException $exception)
{
    if ($exception->getMessage() === 'リクエストがSSLではありません。アクションは安全である必要があります。') {
        // 例外メッセージを翻訳可能な文字列に書き換えます。
        $exception->setMessage(__('要求されたページにHTTPSでアクセスしてください'));
    }

    // 条件付きでリワードされた例外を再度スローします。
    throw $exception;

    // または、フラッシュメッセージを設定して、
    // 要求されたページの HTTPS バージョンにリダイレクトするなどして
    // エラーを処理することもできます。
}

$type パラメーターは、以下の値を指定できます。

'auth' は、フォームバリデーションエラー、もしくはコントローラー/アクションの不適合エラーを示します。

'secure' は、SSL メソッド制限の失敗を示します。

アクションを SSL 通信に限定

この機能は HTTPSエンフォーサミドルウェア の登場に伴い削除されました。

フォーム改ざん防止

デフォルトでは、 SecurityComponent は、ユーザーが特定の方法でフォームを改変することを防ぎます。 SecurityComponent は、以下のことを防止します。

フォームに新規フィールドを追加することはできません。

フォームからフィールドを削除することはできません。

hidden フィールドの値を更新することはできません。

これらの改変を防ぐには、 FormHelper を使って、フォーム内のフィールドを追跡することで実現します。その際、 hidden フィールドの値も追跡対象になります。このデータの全てが結合され、ハッシュに変換されます。フォームが投稿された時、 SecurityComponent は、 POST データをもとに同じ構造を構築し、ハッシュと比較します。

注釈

SecurityComponent は、セレクトオプションの追加や変更を防ぐことは できません 。また、ラジオオプションも追加や変更を防ぐことはできません。

unlockedFields

POST バリデーションを解除したいフォームフィールドの一覧をセットします。このコンポーネントの他にも、 FormHelper::unlockField() でも解除できます。制限が解除されたフィールドは、POST 時に必須ではなくなり、 hidden フィールドの値もチェックされません。

validatePost

false をセットすると、POST リクエストのバリデーションを完全にスキップし、実質フォームバリデーションを無効化します。

使い方

Security コンポーネントは、一般的にコントローラーの initialize または beforeFilter() のコールバックの中で行われます。

namespace App\Controller;

use App\Controller\AppController;
use Cake\Event\EventInterface;

class WidgetsController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Security');
    }

    public function beforeFilter(EventInterface $event)
    {
        parent::beforeFilter($event);

        if ($this->request->getParam('prefix') === 'Admin') {
            $this->Security->setConfig('validatePost', false);
        }
    }
}

上記の例では、 管理者用ルーティングの全てのアクションは、セキュアな SSL 通信のみを許可します。

CSRF 防御

CSRF つまり、クロスサイトリクエストフォージェリ (Cross Site Request Forgery) は、アプリケーションの一般的な脆弱性です。攻撃者が、直前のリクエストを記録し再生することを許し、他のドメイン上の画像タグやリソースを使用してデータを送信します。 CSRF 保護機能を有効にするには、CSRFミドルウェア（クロスサイトリクエストフォージェリミドルウェア） を利用してください。

指定したアクションの Security コンポーネントの無効化

例えば AJAX リクエストなど、あるアクションで全てのセキュリティチェックを無効化したい場合があります。 beforeFilter() 内で $this->Security->unlockedActions にリストアップすることでこれらのアクションを「アンロック」できます。

namespace App\Controller;

use App\Controller\AppController;
use Cake\Event\EventInterface;

class WidgetController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Security');
    }

    public function beforeFilter(EventInterface $event)
    {
        parent::beforeFilter($event);

        $this->Security->setConfig('unlockedActions', ['edit']);
    }
}

この例では、edit アクションのすべてのセキュリティチェックが無効になります。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ページネーション

class Cake\Controller\Component\PaginatorComponent

フレキシブルでかつユーザーフレンドリーなウェブアプリケーションを作成する際の主たる障害の一つとなるのが、直感的なユーザーインターフェイスです。多くのアプリケーションはすぐに巨大となりかつ複雑になり、デザイナーやプログラマーは、何百件、何千件ものレコードが表示されることに対応しきれなくなってきます。リファクタリングするには時間がかかり、パフォーマンスやユーザー満足度が犠牲になることが多いです。

１ページあたりに表示されるレコードの数を一定数に抑えることは、すべてのアプリケーションにとって重大な課題であり、開発者にとって頭の痛い問題でした。CakePHP は素早く、かつ簡単に、データをページ分けする方法を提供することで、開発者への負担を軽減します。

CakePHP におけるページネーションは、コントローラーにおけるコンポーネントによって提供され、ページ分けされたクエリーをより簡単にビルドできるようにします。ビューの中の PaginatorHelper は、ページネーションのリンクやボタンを作り出すことを容易にすることに使われます。

基本的な使用方法

クエリをページ分割するには、まず PaginatorComponent をロードする必要があります。

class ArticlesController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Paginator');
    }
}

一度ロードされれば、ORMテーブルクラスや Query オブジェクトをページ分割することができます。

public function index()
{
    // ORM テーブルのページ分割
    $this->set('articles', $this->paginate($this->Articles));

    // 部分的に完了したクエリをページ分割する
    $query = $this->Articles->find('published');
    $this->set('articles', $this->paginate($query));
}

高度な使用方法

PaginatorComponent は、 $paginate のコントローラプロパティや paginate() の引数 $settings として設定することで、より複雑なユースケースをサポートしています。これらの条件はページ分割クエリの基礎となります。これらの条件は URLから渡される sort, direction, limit, page のパラメータによって拡張されます。　:

class ArticlesController extends AppController
{
    public $paginate = [
        'limit' => 25,
        'order' => [
            'Articles.title' => 'asc'
        ]
    ];
}

Tip

デフォルトの order オプションは配列として定義されていなければなりません。

find() でサポートされているオプションのいずれかをページ分割の設定に含めることができます。ページネーションオプションを カスタム Finder メソッド にバンドルする方がすっきりしていてシンプルです。 finder オプションを使用することで、ページ分割の際にファインダーを使用することができます。

class ArticlesController extends AppController
{
    public $paginate = [
        'finder' => 'published',
    ];
}

ファインダーメソッドに追加のオプションが必要な場合は、これらの値を finder: に渡すことができます。

class ArticlesController extends AppController
{
    // タグごとに記事を検索する
    public function tags()
    {
        $tags = $this->request->getParam('pass');

        $customFinderOptions = [
            'tags' => $tags
        ];

        // カスタム Finder メソッドは、ArticlesTable.php の中で "findTagged" と呼ばれる
        // 以下のような構文となっている
        // public function findTagged(Query $query, array $options) {
        // そのため、taggedをキーとして使用する
        $settings = [
            'finder' => [
                'tagged' => $customFinderOptions
            ]
        ];
        $articles = $this->paginate($this->Articles, $settings);
        $this->set(compact('articles', 'tags'));
    }
}

一般的なページネーションの値を定義することに加え、コントローラーには１セット以上のページネーションに関するデフォルト設定を定義することができます。そのためには、設定を加えたいモデルの後に、配列におけるキー名称を加えるだけです。

class ArticlesController extends AppController
{
    public $paginate = [
        'Articles' => [],
        'Authors' => [],
    ];
}

Articles や Authors のキーの値は、基本的な $paginate 配列に含まれるすべてのプロパティを含めることができます。

一度 paginate() を使って結果を作成した後は コントローラのリクエストはページングパラメータで更新されます。ページングのメタデータは $this->request->getParam('paging') で取得できます。

シンプルなページネーション

デフォルトではページネーションは count() クエリを使って結果セットのサイズを計算し、ページ番号のリンクを表示できるようにしています。非常に大きなデータセットでは、このcountクエリは非常に高価になります。 'Next' と 'Previous' リンクだけを表示したい場合は、カウントクエリを行わない 'simple' paginator を使うことができます。

public function initialize(): void
{
    parent::initialize();

    // Load the paginator component with the simple paginator strategy.
    $this->loadComponent('Paginator', [
        'paginator' => new \Cake\Datasource\SimplePaginator(),
    ]);
}

SimplePaginator を使っている場合、ページ番号やカウンターデータ、最後のページへのリンク、総レコード数のコントロールを生成することはできません。

PaginatorComponent を直接使用する

他のコンポーネントからデータをページ分割する必要がある場合は PaginatorComponent を直接使うと良いでしょう。 PaginatorComponent はコントローラメソッドと似たようなAPIを持っています。　:

$articles = $this->Paginator->paginate($articleTable->find(), $config);

// または
$articles = $this->Paginator->paginate($articleTable, $config);

最初のパラメータは、ページ分割したいテーブルオブジェクトの検索結果からのクエリオブジェクトでなければなりません。オプションで、テーブルオブジェクトを渡してクエリを作成することもできます。 2番目のパラメータは、ページ分割に使用する設定の配列です。この配列はコントローラの $paginate プロパティと同じ構造でなければなりません。 Query オブジェクトをページ分割する際には、 finder オプションは無視されます。これは、ページ分割したいクエリを渡していることを前提としています。

複数のクエリのページ分割

コントローラの $paginate プロパティと paginate() メソッドを呼び出す際に scope オプションを使うことで、1つのコントローラのアクションの中で複数のモデルをページ分割することができます。

// ページ分割するプロパティ
public $paginate = [
    'Articles' => ['scope' => 'article'],
    'Tags' => ['scope' => 'tag']
];

// コントローラーアクションにおいて
$articles = $this->paginate($this->Articles, ['scope' => 'article']);
$tags = $this->paginate($this->Tags, ['scope' => 'tag']);
$this->set(compact('articles', 'tags'));

scope オプションを指定すると、 PaginatorComponent がスコープされたクエリ文字列パラメータを検索するようになります。例えば、以下のURLはタグと記事を同時にページ分割するのに使えます。

/dashboard?article[page]=1&tag[page]=3

スコープされたHTML要素やページネーション用のURLを生成する方法については複数の結果の改ページ のセクションを参照してください。

同じモデルを複数回ページ分割する

1つのコントローラアクション内で同じモデルを複数回ページ分割するには、モデルのエイリアスを定義する必要があります。テーブルレジストリの使用方法の詳細については、 TableLocator の利用 を参照してください。

// コントローラーアクションにおいて
$this->paginate = [
    'ArticlesTable' => [
        'scope' => 'published_articles',
        'limit' => 10,
        'order' => [
            'id' => 'desc',
        ],
    ],
    'UnpublishedArticlesTable' => [
        'scope' => 'unpublished_articles',
        'limit' => 10,
        'order' => [
            'id' => 'desc',
        ],
    ],
];

$publishedArticles = $this->paginate(
    $this->Articles->find('all', [
        'scope' => 'published_articles'
    ])->where(['published' => true])
);

// ページ分割コンポーネントで差別化できるようにテーブルオブジェクトを追加登録します。
$unpublishedArticlesTable = $this->fetchTable('UnpublishedArticles', [
    'className' => 'App\Model\Table\ArticlesTable',
    'table' => 'articles',
    'entityClass' => 'App\Model\Entity\Article',
]);

$unpublishedArticles = $this->paginate(
    $unpublishedArticlesTable->find('all', [
        'scope' => 'unpublished_articles'
    ])->where(['published' => false])
);

ソート時に使用するフィールドの制御

デフォルトでは、テーブルが持つ非仮想カラムに対してソートを行うことができます。これはインデックス化されていないカラムをソートしてしまうことになり、ソートするのにコストがかかるため、望ましくないこともあります。ソートできるフィールドのホワイトリストを sortableFields オプションを使って設定することができます。このオプションは関連するデータやページ分割クエリの一部である計算フィールドをソートしたい場合に必要です。

public $paginate = [
    'sortableFields' => [
        'id', 'title', 'Users.username', 'created'
    ]
];

ホワイトリストにないフィールドでソートしようとするリクエストは無視されます。

1ページあたりの最大行数を制限する

ページごとに取得される結果の数は limit パラメータとしてユーザに公開されます。一般的に、ユーザがページ分割されたセットのすべての行を取得できるようにすることは望ましくありません。オプションの maxLimit は、外部からこの制限値を高く設定することはできないことを保証します。デフォルトでは、CakePHPはフェッチできる行の最大数を100に制限しています。もしこのデフォルト値がアプリケーションにとって適切でない場合は、ページ分割オプションの一部として調整することができます。

public $paginate = [
    // 他のキーはこちら
    'maxLimit' => 10
];

リクエストのリミットパラメータがこの値よりも大きければ、 maxLimit の値まで減らされます。

追加の関連付けへのジョイン

追加の関連付けをページ分割されたテーブルにロードするには、 contain パラメータを使用します。

public function index()
{
    $this->paginate = [
        'contain' => ['Authors', 'Comments']
    ];

    $this->set('articles', $this->paginate($this->Articles));
}

範囲外のページ要求

PaginatorComponent は、存在しないページにアクセスしようとすると NotFoundException` をスローします。

そのため、通常のエラーページをレンダリングさせるか、 try catch ブロックを使用して NotFoundException が発生した場合に適切な処理を行うことができます。

use Cake\Http\Exception\NotFoundException;

public function index()
{
    try {
        $this->paginate();
    } catch (NotFoundException $e) {
        // 最初のページや最後のページにリダイレクトするようにします。
        // $this->request->getAttribute('page')を指定すると、必要な情報が得られます。
    }
}

ビューのページネーション

ページネーションナビゲーションのリンクの作り方は、 HelperPaginatorHelper のドキュメントを確認してください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

リクエストハンドリング

class RequestHandlerComponent(ComponentCollection $collection, array $config = [])

RequestHandler コンポーネントは、 アプリケーションに対する HTTP リクエストについての追加情報を取得するために CakePHP で使用されています。それは、クライアントが好むコンテンツタイプが何かを知り、自動的にリクエストの入力を解析し、コンテンツタイプをビュークラスやテンプレートのパスとマップする方法を定義することができます。

RequestHandler は初期状態で、多くの JavaScript ライブラリーが使用している X-Requested-With ヘッダーに基づいた AJAX リクエストを自動的に判定します。 Cake\Routing\Router::extensions() と組み合わせて使用することで、 RequestHandler は、自動的に HTML 以外のメディアタイプに対応したレイアウトとテンプレートファイルを切り替えます。さらに、リクエストの拡張子と同じ名前のヘルパーが存在する場合、コントローラーのヘルパーの設定をする配列に加えます。最後に、 XML/JSON データをコントローラーへ POST した場合、自動的に解析され $this->request->getData() 配列に割り当てられ、モデルデータとして保存可能です。 RequestHandler を利用するためには initialize() メソッドに含めてください。

class WidgetsController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('RequestHandler');
    }

    // 以下省略
}

リクエスト情報の取得

RequestHandler はクライアントやリクエストについての情報を提供するいくつかのメソッドがあります。

RequestHandlerComponent::accepts($type = null)

$type は、文字列・配列・null のいずれかです。文字列の場合、そのコンテンツタイプをクライアントが受付ける場合、 accepts() は true を返します。配列の場合、そのコンテンツタイプのいずれかを受付ける場合、 accepts() は true を返します。 null の場合、クライアントが受付けるコンテンツタイプの配列を返します。例:

class ArticlesController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('RequestHandler');
    }

    public function beforeFilter(EventInterface $event)
    {
        if ($this->RequestHandler->accepts('html')) {
            // クライアントが HTML (text/html) のレスポンスを
            // 受付ける場合のみコードが実行されます。
        } elseif ($this->RequestHandler->accepts('xml')) {
            // XML のみ実行するコード
        }
        if ($this->RequestHandler->accepts(['xml', 'rss', 'atom'])) {
            // クライアントが XML か RSS か Atom のいずれかを
            // 受付ける場合にコードが実行されます。
        }
    }
}

リクエスト「型」を検出する他のメソッドは、次のとおりです。

RequestHandlerComponent::isXml()

現在のリクエストが XML のレスポンスを受付ける場合は true を返します。

RequestHandlerComponent::isRss()

現在のリクエストが RSS のレスポンスを受付ける場合は true を返します。

RequestHandlerComponent::isAtom()

現在の呼び出しが Atom のレスポンスを受付ける場合は true を返します。そうでなければ false を返します。

RequestHandlerComponent::isMobile()

ユーザーエージェントにモバイルブラウザーの文字列を含む場合、もしくはクライアントが WAP コンテンツを受け入れる場合は true を返します。サポートされているモバイルユーザーエージェント文字列は次のとおりです。

Android

AvantGo

BlackBerry

DoCoMo

Fennec

iPad

iPhone

iPod

J2ME

MIDP

NetFront

Nokia

Opera Mini

Opera Mobi

PalmOS

PalmSource

portalmmm

Plucker

ReqwirelessWeb

SonyEricsson

Symbian

UP.Browser

webOS

Windows CE

Windows Phone OS

Xiino

RequestHandlerComponent::isWap()

クライアントが WAP コンテンツを受付ける場合は true を返します。

上記の全ての検出メソッドは、特定のコンテンツタイプを対象にしたフィルター機能と同様の方法で使用できます。例えば、 AJAX のリクエストに応答するときには、ブラウザーのキャッシュを無効にして、デバッグレベルを変更したいでしょう。ただし、非 AJAX リクエストのときは反対にキャッシュを許可したいと思います。そのようなときは次のようにします。

if ($this->request->is('ajax')) {
    $this->response->disableCache();
}
// コントローラーのアクションの続き

リクエストデータの自動デコード

この機能はバージョン4.0において RequestHandlerComponent から削除されました。代わりに ボディパーサミドルウェア を使用してください。

コンテンツタイプの設定を確認

RequestHandlerComponent::prefers($type = null)

クライアントが好むコンテンツタイプを判定します。パラメーターを省略した場合は、最も可能性の高いコンテンツタイプが返されます。 $type を配列で渡した場合、クライアントが受け付けるものとマッチした最初の値が返されます。設定はまず、もし Router で解析されたファイルの拡張子により確定されます。次に、 HTTP_ACCEPT にあるコンテンツタイプのリストから選ばれます。

$this->RequestHandler->prefers('json');

リクエストへの応答

RequestHandlerComponent::renderAs($controller, $type)

指定した型に、コントローラーの出力モードを変更します。適切なヘルパーが存在し、それがコントローラー中のヘルパー配列で指定されていなければ、これを追加します。

// コントローラーに xml レスポンスの出力を強制。
$this->RequestHandler->renderAs($this, 'xml');

このメソッドは、現在のコンテンツタイプに一致するヘルパーを追加しようとします。例えば、 rss として出力する場合、 RssHelper が追加されます。

RequestHandlerComponent::respondAs($type, $options)

コンテンツタイプにマップした名前に基づき、応答するヘッダーをセットします。このメソッドは、一度に多くのレスポンスプロパティーを設定することができます。

$this->RequestHandler->respondAs('xml', [
    // ダウンロードを強制
    'attachment' => true,
    'charset' => 'UTF-8'
]);

RequestHandlerComponent::responseType()

現在の応答するコンテンツタイプのヘッダーをの型を返します。もしセットされていなければ null を返します。

HTTP キャッシュバリデーションの活用

HTTP キャッシュバリデーションモデルは、クライアントへのレスポンスにコピーを使用するかどうかを判断する（リバースプロキシーとして知られる）キャッシュゲートウェイを使用する処理です。このモデルでは、主に帯域幅を節約しますが、正しく使用することで応答時間の短縮や、いくつかの CPU の処理を節約することができます。

コントローラーで RequestHandlerComponent を有効化すると、ビューが描画される前に、自動的にチェックを行います。このチェックでは、前回クライアントが要求してからレスポンスに変更がないかを判断するため、レスポンスオブジェクトと元のリクエストを比較します。

レスポンスが変更無いと見なされる場合、ビューの描画処理は行われず、クライアントには何も返さず処理時間を短縮、帯域幅を節約します。レスポンスステータスコードは 304 Not Modified にセットされます。

自動的なチェックは、 checkHttpCache を false にすることで行わないようにすることができます。

public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('RequestHandler', [
        'checkHttpCache' => false
    ]);
}

カスタムビュークラスの利用

JsonView/XmlView を利用する場合、カスタムビュークラスでデフォルトのシリアライズ方法を上書きしたり、独自のカスタムクラスを追加したい場合があるでしょう。

その場合、既存のタイプや新規タイプのクラスをマッピングすることができます。また、 viewClassMap 設定を使用して、これを自動的に設定することができます。

public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('RequestHandler', [
        'viewClassMap' => [
            'json' => 'ApiKit.MyJson',
            'xml' => 'ApiKit.MyXml',
            'csv' => 'ApiKit.Csv'
        ]
    ]);
}

バージョン 3.1.0 で非推奨: 3.1.0 から viewClassMap() メソッドは非推奨です。実行時に viewClassMap を変更するには、 config() を使用してください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

フォームの保護

class FormProtection(ComponentCollection $collection, array $config = [])

フォーム保護コンポーネントは、フォームデータの改ざんから保護する仕組みを提供します。

すべてのコンポーネントと同様に、いくつかの設定可能なパラメータを使用して設定します。これらのプロパティはすべて直接設定するか、コントローラの initialize() メソッドや beforeFilter() メソッドの中にある同じ名前のセッターメソッドを使って設定することができます。

もし、startup() コールバックでフォームデータを処理する他のコンポーネントを使用している場合は、 initialize() メソッドの中で、必ず FormProtection Component をそれらのコンポーネントの前に置いてください。

注釈

FormProtectionコンポーネントを使用するときは、必ず FormHelperを使用してフォームを作成してください。また、フィールドの「名前」属性を上書き してはいけません 。 FormProtection コンポーネントは、FormHelper によって作成・管理される特定の指標を探します。（特に create() と end() で作成されたもの）

POST リクエストで送信される動的なフィールド変更 (JavaScriptによる無効化、削除、新規フィールドの作成など) を使用すると、フォームトークンのバリデーションに失敗する可能性が高くなります。

フォームの改ざん防止

デフォルトでは　FormProtectionComponent はユーザが特定の方法でフォームを改ざんすることを防ぎます。例えば、次のようなことを防ぐことができます。

フォームのアクション(URL)は変更できません。

未知のフィールドをフォームに追加することはできません。

フォームからフィールドを削除することはできません。

隠し入力の値を変更することはできません。

このようなタイプの改ざんを防ぐには FormHelper を使ってどのフィールドがフォームにあるかを追跡することで可能になります。隠されたフィールドの値も追跡されます。これらのデータはすべて結合されてハッシュ化され、隠されたトークンフィールドは自動的にフォームに挿入されます。フォームが送信されると、FormProtectionComponent は POSTデータを使って同じ構造を作り、ハッシュを比較します。

注釈

FormProtectionComponent はセレクトボックスのオプションの追加や変更を防ぎ *ません* 。また、ラジオボタンのオプションの追加や変更も防ぎません。

使用方法

セキュリティコンポーネントの設定は通常コントローラの initialize() または beforeFilter() コールバックで行われます。

利用可能なオプションは以下の通りです。 :

validate

false に設定すると、POSTリクエストのバリデーションを完全にスキップし、本質的にフォームのバリデーションをオフにします。

unlockedFields

POSTのバリデーションから除外するフォームフィールドのリストを設定します。フィールドのロックを解除するには、コンポーネントで行うか FormHelper::unlockField() を使用します。ロックが解除されたフィールドは POST の一部である必要はありませんし、ロックが解除されていない非表示のフィールドの値はチェックされません。

unlockedActions

POSTのバリデーションチェックから除外するアクションを設定します。

validationFailureCallback

バリデーションに失敗した場合に呼び出すコールバックを指定します。有効なClosureでなければなりません。デフォルトでは未設定で、検証に失敗した場合は例外がスローされます。

フォームの改ざんチェックを無効にする

namespace App\Controller;

use App\Controller\AppController;
use Cake\Event\EventInterface;

class WidgetsController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();

        $this->loadComponent('FormProtection');
    }

    public function beforeFilter(EventInterface $event)
    {
        parent::beforeFilter($event);

        if ($this->request->getParam('prefix') === 'Admin') {
            $this->FormProtection->setConfig('validate', false);
        }
    }
}

上記の例では、管理者用プレフィックス付きルートのフォーム改ざん防止機能を無効にしています。

特定のアクションのためにフォームの改ざんを無効にする

アクションに対してフォームの改ざん防止を無効にしたい場合があるかもしれません。 (例えばAJAXリクエストの場合) これらのアクションを beforeFilter() 内の $this->Security->unlockedActions にリストアップすることで「ロックを解除」することができます。

namespace App\Controller;

use App\Controller\AppController;
use Cake\Event\EventInterface;

class WidgetController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('FormProtection');
    }

    public function beforeFilter(EventInterface $event)
    {
        parent::beforeFilter($event);

        $this->FormProtection->setConfig('unlockedActions', ['edit']);
    }
}

この例では、編集アクションのすべてのセキュリティチェックを無効にします。

コールバックによる検証失敗の処理

フォーム保護の検証に失敗した場合、デフォルトでは400エラーになります。この動作は validationFailureCallback 設定オプションをコントローラのコールバック関数に設定することで設定できます。

コールバックメソッドを設定することで、失敗処理の動作をカスタマイズすることができます。

public function beforeFilter(EventInterface $event)
{
    parent::beforeFilter($event);

    $this->FormProtection->setConfig(
        'validationFailureCallback',
        function (BadRequestException $exception) {
            // You can either return a response instance or throw the exception
            // received as argument.
        }
    );
}

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

HTTPキャッシュの確認

class CheckHttpCacheComponent(ComponentCollection $collection, array $config = [])

バージョン 4.4.0 で追加: CheckHttpCacheComponent が追加されました。

HTTPキャッシュ検証モデルは、リバースプロキシとしても知られるキャッシュゲートウェイが、保存されたレスポンスのコピーをクライアントに提供できるかどうかを判断するための処理の1つです。このモデルでは、主に帯域幅を節約することができますが、正しく使用すればCPU処理も節約でき、レスポンスタイムを短縮することができます。:

// in a Controller
public function initialize(): void
{
    parent::initialize();

    $this->addComponent('CheckHttpCache');
}

コントローラで CheckHttpCacheComponent を有効にすると、自動的に beforeRender チェックが有効になります。このチェックでは、レスポンスオブジェクトに設定されたキャッシュヘッダと、リクエストで送信されたキャッシュヘッダを比較し、クライアントが最後にレスポンスを要求したときから変更されていないかどうかを判断します。以下のリクエストヘッダが使用されます。:

If-None-Match はレスポンスの Etag ヘッダーと比較されます。

If-Modified-Since はレスポンスの Last-Modified ヘッダーと比較されます。

レスポンスヘッダがリクエストヘッダの基準と一致する場合、 ビューのレンダリングはスキップされます。これにより、アプリケーションがビューを生成する手間が省け、帯域幅と時間を節約することができます。レスポンスヘッダが一致した場合、空のレスポンスが 304 Not Modified というステータスコードとともに返されます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ビュー

class Cake\View\View

ビュー (View) は MVC の V です。ビューはリクエストに対する出力を生成する役割を担います。大抵の場合、これは HTML フォームや XML、JSON などですが、ファイルのストリーミングや、ユーザーがダウンロード可能な PDF の生成もビューレイヤーの役割となります。

CakePHP では下記の典型的な描画シナリオに対応するためのいくつかの組み込みのビュークラスを用意しています。

XML や JSON ウェブサービスを作成するには JSON と XML ビュー を利用できます。

保護されたファイルや動的に生成されたファイルを提供するにはファイルの送信 を利用できます。

複数テーマのビューを作成するには テーマ を利用できます。

App ビュー

AppView はアプリケーションの既定のビュークラスです。 AppView は自身が CakePHP に含まれる Cake\View\View クラスを継承していて、 src/View/AppView.php の中で次のように定義されています。

<?php
namespace App\View;

use Cake\View\View;

class AppView extends View
{
}

アプリケーション中で描画されるすべてのビューで使われるヘルパーを読み込むために AppView を使用することができます。こうした用途のために、CakePHP は View のコンストラクターの最後で呼び出される initialize() メソッドを提供します。

<?php
namespace App\View;

use Cake\View\View;

class AppView extends View
{
    public function initialize(): void
    {
        // MyUtils ヘルパーをいつでも有効にする
        $this->loadHelper('MyUtils');
    }
}

ビューテンプレート

CakePHP のビューレイヤーは、どのようにユーザーに伝えるかです。ほとんどの場合、ビューは HTML/XHTML ドキュメントをブラウザーに返しますが、JSON を介してリモートアプリケーションに返答したり、CSV ファイルを出力する必要もあるかもしれません。

CakePHP のテンプレートファイルは既定の拡張子を .php (CakePHP Template) としており、制御構造や出力のために PHP 別の構文 [https://php.net/manual/ja/control-structures.alternative-syntax.php] を利用することができます。これらのファイルにはコントローラーから受け取ったデータを、閲覧者のために用意した表示形式に整形するのに必要なロジックを入れます。

別の echo

テンプレート中の変数を echo または print します。

<?php echo $variable; ?>

短いタグにも対応しています。

<?= $variable ?>

別の制御構文

if 、 for 、 foreach 、 switch 、そして while のような制御構文は単純な形式で書くことができます。括弧は必要ないことに注意してください。代わりに、 foreach の閉じ括弧は endforeach に置き換えられています。上記の各制御構造は endif 、 endfor 、 endforeach 、 endwhile のような閉じ構文を持っています。各制御構造の後（最後の一つを除いて）に セミコロン (;) ではなく、コロン (:) があることにも注意してください。

以下は foreach の使用例です。

<ul>
<?php foreach ($todo as $item): ?>
  <li><?= $item ?></li>
<?php endforeach; ?>
</ul>

別の例として、 if/elseif/else の用法です。コロンに注意してください。

<?php if ($username === 'sally'): ?>
   <h3>やあ、Sally</h3>
<?php elseif ($username === 'joe'): ?>
   <h3>やあ、Joe</h3>
<?php else: ?>
   <h3>やあ、そこの誰かさん！</h3>
<?php endif; ?>

もしも、 Twig [https://twig.sensiolabs.org] のようなテンプレート言語を使いたいのであれば、ビューのサブクラスがテンプレート言語と CakePHP の橋渡しをしてくれるでしょう。

テンプレートファイルは /templates/ の中の、ファイルを使用するコントローラーにちなんで名付けられたフォルダーに置かれ、その対応するアクション名にちなんで名付けられます。例えば、 Products コントローラーの view() アクションのビューファイルは通常、 /templates/Products/view.php となります。

CakePHP のビューレイヤーはいくつかの異なるパーツによって作り上げられています。各パーツは異なる役割を持っており、この章で説明していきます。

テンプレート: テンプレートは実行中のアクション固有のページの一部分です。アプリケーションの応答の中心となります。

エレメント: 小さな、再利用可能なちょっとしたコードです。エレメントは通常、ビューの中で描画されます。

レイアウト: アプリケーションの多くのインターフェイスをくるむ表示コードを入れるテンプレートファイルです。ほとんどのビューはレイアウトの中に描画されます。

ヘルパー: これらのクラスはビューレイヤーの様々な場所で必要とされるロジックをカプセル化します。とりわけ、CakePHP のヘルパーはフォームの構築や AJAX 機能の構築、モデルデータのページ切替、RSS フィードの提供などの手助けをしてくれます。

cells: これらのクラスは、自己完結型のUI部品を作成する小さなコントローラー風の機能を提供します。より詳しい情報は ビューセル を参照してください。

ビュー変数

コントローラーの中で set() で設定した変数は、アクションが描画するビューやレイアウトの双方で使用可能です。加えて、設定した変数はエレメントの中でも使用可能です。もしも、追加の変数をビューからレイアウトに渡す必要があれば、ビューテンプレートの中から set() を呼ぶか、 ビューブロックの使用 が利用できます。

CakePHP は自動では出力をエスケープしませんので、ユーザーデータを出力する前にはいつも エスケープしなければならないことを覚えておいてください。 h() 関数でユーザーコンテンツをエスケープできます。

<?= h($user->bio); ?>

ビュー変数の設定

Cake\View\View::set(string $var, mixed $value)

ビューにはコントローラーオブジェクトの set() と類似の set() メソッドがあります。ビューファイルから set() を使うと後で描画されるレイアウトやエレメントに変数を追加できます。 set() の使い方のより詳しい情報は ビュー変数の設定 を参照してください。

ビューファイルでは、次のように記述できます。

$this->set('activeMenuButton', 'posts');

そしてレイアウトでは、 $activeMenuButton 変数が使用でき、 'posts' という値を持ちます。

ビューの継承

ビューの継承によって、あるビューを他のビューでくるむことができるようになります。ビューブロック と組み合わせることでビューを DRY に保つための強力な方法が得られます。例えば、あなたが作成しているアプリケーションの特定のビューで、サイドバーの描画を変える必要があるとします。共通のビューファイルを継承することで、サイドバーのマークアップの繰り返しを避けられ、そしてただ変化する場所を定義するだけです。

<!-- templates/Common/view.php -->
<h1><?= h($this->fetch('title')) ?></h1>
<?= $this->fetch('content') ?>

<div class="actions">
    <h3>関連アクション</h3>
    <ul>
    <?= $this->fetch('sidebar') ?>
    </ul>
</div>

上記のビューファイルを親ビューとして使うことができます。これは sidebar と title ブロックを定義しているビューが継承することを期待しています。 content ブロックは CakePHP が作る特別なブロックで、継承しているビューの捕捉されなかったすべての内容が入ります。ビューファイルに投稿のデータが格納されている $post という変数があるとすれば、ビューは次のようになります。

<!-- templates/Posts/view.php -->
<?php
$this->extend('/Common/view');

$this->assign('title', $post->title);

$this->start('sidebar');
?>
<li>
<?php
echo $this->Html->link('edit', [
    'action' => 'edit',
    $post->id,
]);
?>
</li>
<?php $this->end(); ?>

// 残りの内容は親ビューの 'content' ブロックとして使用可能です。
<?= h($post->body) ?>

上記の例はどのようにビューを継承できるかを示しており、ブロック一式を生成しています。定義されたブロックの中に入っていないあらゆるコンテンツは捕捉され、 content という特別な名前のブロックに配置されます。ビューに extend() の呼び出しがある場合、現在のビューファイルは最後まで実行されます。実行完了後、継承されたビューが描画されます。一つのビューファイルで二回以上 extend() が呼び出される場合、次に処理される親ビューを上書きします。

$this->extend('/Common/view');
$this->extend('/Common/index');

上記は /Common/index.php が現在のビューの親ビューとして描画される結果になります。

継承されたビューは好きなだけ入れ子にすることができます。必要に応じて各ビューは他のビューを継承することができます。各親ビューは一つ前のビューの内容を content ブロックとして取得できます。

注釈

content をブロック名として使うことは避けるべきです。 CakePHP は継承されたビューの中で捕捉されていないコンテンツとして扱ってしまいます。

blocks() メソッドを使って、存在するブロックを列挙することができます。

$list = $this->blocks();

ビューブロックの使用

ビューブロックはビュー/レイアウトの中のどこかで定義されるであろう、スロットやブロックを定義できるようにする柔軟な API を提供します。例えばブロックは、サイドバーや、レイアウトの末尾や先頭のアセット読込領域の実装に理想的です。ブロックを実装するには二つの方法があります。捕捉するブロックとするか、直接割り当てるかです。 start() 、 append() 、 prepend() 、 assign() 、 fetch() 、そして end() メソッドは捕捉するブロックと共に動作します。

// sidebar ブロックを作成する。
$this->start('sidebar');
echo $this->element('sidebar/recent_topics');
echo $this->element('sidebar/recent_comments');
$this->end();

// sidebar の末尾に追加する。
$this->start('sidebar');
echo $this->fetch('sidebar');
echo $this->element('sidebar/popular_topics');
$this->end();

append() を使ってブロックに追記することもできます。

$this->append('sidebar');
echo $this->element('sidebar/popular_topics');
$this->end();

// 上と同じです。
$this->append('sidebar', $this->element('sidebar/popular_topics'));

もしあるブロックを消去または上書きする必要がある場合、二つほど選択肢があります。 reset() メソッドはいつでもブロックを消去または上書きします。空文字での assign() メソッドも特定のブロックを消去するために使えます。

// sidebar ブロックの以前の中身を消去します。
$this->reset('sidebar');

// 空文字を代入も sidebar ブロックを消去します。.
$this->assign('sidebar', '');

ブロックのコンテンツへの代入は、ビュー変数をブロックに変換したい時にしばしば役に立ちます。例えば、ページタイトルのためにブロックを使いたいかもしれませんし、時にはコントローラーの中で、ビュー変数としてタイトルを代入するかもしれません。

// ビューやレイアウトの中で $this->fetch('title') に
$this->assign('title', $title);

prepend() メソッドは既存のブロックの先頭にコンテンツを追記することができます。

// sidebar に前置します
$this->prepend('sidebar', 'このコンテンツはサイドバーの先頭に来ます');

ブロックの表示

ブロックの表示には fetch() メソッドを使います。fetch() はもしもブロックが存在しなかった場合に '' を返してブロックを出力します。

<?= $this->fetch('sidebar') ?>

fetch を使うとブロックが存在するかどうかによってブロックに囲まれたコンテンツの表示を切り替えることができます。これはレイアウトや継承されたビューで見出しや他のマークアップ表示を切り替えたい場合に役に立ちます。

// templates/layout/default.php の中で
<?php if ($this->fetch('menu')): ?>
<div class="menu">
    <h3>Menu options</h3>
    <?= $this->fetch('menu') ?>
</div>
<?php endif; ?>

ブロックが存在しない場合の既定の値を指定することもできます。これによって、ブロックが存在しない場合のプレースホルダーを追加することでききます。既定値は第二引数で指定します。

<div class="shopping-cart">
    <h3>買い物かご</h3>
    <?= $this->fetch('cart', '買い物かごが空です') ?>
</div>

スクリプトと CSS ファイルのためのブロック使用

HtmlHelper はビューブロックと結び付いており、その script() 、 css() 、そして meta() メソッドは block = true のオプションで使われると同名のブロックをそれぞれ更新します。

<?php
// ビューファイルの中で
$this->Html->script('carousel', ['block' => true]);
$this->Html->css('carousel', ['block' => true]);
?>

// レイアウトファイルの中で
<!DOCTYPE html>
<html lang="en">
    <head>
    <title><?= $this->fetch('title') ?></title>
    <?= $this->fetch('script') ?>
    <?= $this->fetch('css') ?>
    </head>
    // 残りのレイアウトが続きます

Cake\View\Helper\HtmlHelper はスクリプトや CSS が入るブロックを制御することもできます。

// ビューファイルの中で
$this->Html->script('carousel', ['block' => 'scriptBottom']);

// レイアウトの中で
<?= $this->fetch('scriptBottom') ?>

レイアウト

レイアウトにはビューをくるむ表示コードが入ります。すべてのビューから見えて欲しいものはレイアウトに配置されるべきです。

CakePHP の既定のレイアウトは templates/layout/default.php に置かれます。もし、アプリケーションの見栄え全体を変更したい場合、これが手始めとなり、なぜならページが描画される時には、コントローラーによって描画されるビューのコードは、既定のレイアウトの中に置かれるからです。

他のレイアウトファイルは templates/layout に配置されるべきです。レイアウトを作るとき、ビューの出力がどこに配置されるかを CakePHP に伝える必要があります。そのために、レイアウトには $this->fetch('content') を入れるようにしてください。これは既定のレイアウトがどのようなものかの一例です。

<!DOCTYPE html>
<html lang="en">
<head>
<title><?= h($this->fetch('title')) ?></title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<!-- 外部ファイルとスクリプトファイルがここに入れます (詳しくは HTML ヘルパーを参照。) -->
<?php
echo $this->fetch('meta');
echo $this->fetch('css');
echo $this->fetch('script');
?>
</head>
<body>

<!-- もしすべてのビューでメニューを表示したい場合、ここに入れます -->
<div id="header">
    <div id="menu">...</div>
</div>

<!-- ここがビューで表示されるようにしたい場所です -->
<?= $this->fetch('content') ?>

<!-- 表示される各ページにフッターを追加します -->
<div id="footer">...</div>

</body>
</html>

script 、 css 、 meta ブロックには、組み込みの HTML ヘルパーを使ってビューで定義されたすべての内容が入ります。ビューからの JavaScript と CSS ファイルを読み込むのに便利です。

注釈

HtmlHelper::css() や HtmlHelper::script() をテンプレートファイルで使うとき、HTML ソースを同じ名前でブロックの中に配置するには 'block' => true を指定してください。 (詳しい使い方は API を参照してください)

content ブロックは描画されたビューの内容が入ります。

title ブロックの内容をビューファイルから設定することができます。

$this->assign('title', 'アクティブユーザー表示');

title ブロックが空の値の場合、自動的に 'Admin/Articles' のような現在のテンプレートパスの表現に置き換えられます。

好きなだけレイアウトを作ることができます。 templates/layout ディレクトリーに置いて、コントローラーのアクションの中か、ビューの $layout プロパティーで切り替えるだけです。

// コントローラーから
public function view()
{
    // レイアウトの設定
    $this->viewBuilder()->setLayout('admin');
}

// ビューファイルから
$this->layout = 'loggedin';

例えば、サイトに小さな広告バナー枠があるとすれば、小さな広告枠のある新しいレイアウトを作って、以下のように全コントローラーのアクションで指定するかもしれません。

namespace App\Controller;

class UsersController extends AppController
{
    public function viewActive()
    {
        $this->set('title', 'アクティブユーザー表示');
        $this->viewBuilder()->setLayout('default_small_ad');
    }

    public function viewImage()
    {
        $this->viewBuilder()->setLayout('image');

        // ユーザー画像の出力
    }
}

既定のレイアウトの他に CakePHP の公式スケルトン app は 'ajax' レイアウトも持っています。 Ajax レイアウトは AJAX のレスポンスを組み立てるのに便利で、これは空のレイアウトです。 (ほとんどの AJAX 呼び出しは、完全に描画されたインターフェイスというよりも、返却にちょっとしたマークアップを必要とするだけです。)

スケルトン app は RSS を生成するのを手助けする既定のレイアウトも持っています。

プラグインのレイアウト使用

もしプラグインに存在するレイアウトを使いたい場合、 プラグイン記法を使うことが出来ます。例えば、 Contacts プラグインの contact レイアウトを使うには次のようにします。

namespace App\Controller;

class UsersController extends AppController
{
    public function viewActive()
    {
        $this->viewBuilder()->setLayout('Contacts.contact');
    }
}

エレメント

Cake\View\View::element(string $elementPath, array $data, array $options = [])

多くのアプリケーションは、様々なページで、時には異なるレイアウトのページで、繰り返し必要とされる表示コードの小さなブロックを持っています。CakePHP は再利用が必要なウェブサイトのパーツを繰り返し使う手助けをします。この再利用可能なパーツはエレメントと呼ばれます。広告、ヘルプボックス、ナビゲーションコントロール、追加のメニュー、ログインフォーム、吹き出しは CakePHP ではしばしばエレメントとして実装されます。エレメントは基本的に他のビュー、レイアウト、そして他のエレメントにさえも入れることができる小さなビューです。エレメントは、繰り返し使う要素の描画を個別のファイルに収めるので、ビューの可読性を高めるのに使えます。アプリケーション内のコンテンツの一部を再利用する手助けにもなります。

エレメントは templates/element/ フォルダーの中に .php の拡張子を持つ名前で配置されます。次の例はビューの element メソッドを使って出力しています。

echo $this->element('helpbox');

エレメントに変数を渡す

element メソッドの第二引数を通してエレメントにデータを渡すことができます。

echo $this->element('helpbox', [
    "helptext" => "おお、このテキストはとても役に立つ。"
]);

エレメントファイルの内部では、引数で渡されたすべての変数をパラメーター配列のメンバーとして利用できます。(テンプレートファイルにおけるコントローラーの Controller::set() メソッドと同様の動作です。) 上記の例では templates/element/helpbox.php の中で $helptext 変数が使えます。

// templates/element/helpbox.php の中で
echo $helptext; // 出力 "おお、このテキストはとても役に立つ。"

View::element() メソッドは、エレメントのためのオプションもサポートしています。サポートされるオプションは、 'cache' と 'callbacks' です。例えば:

echo $this->element('helpbox', [
        "helptext" => "これはエレメントに $helptext として渡されます",
        "foobar" => "これはエレメントに $foobar として渡されます",
    ],
    [
        // "long_view" のキャッシュ設定を使います
        "cache" => "long_view",
        // エレメントから before/afterRender が呼び出されるには true に設定してください
        "callbacks" => true
    ]
);

エレメントは Cache クラスを通じてキャッシュされます。設定済みのキャッシュのいずれかにエレメントが保存されるように設定できます。その結果、何処にいつまでエレメントを保存しておくのかを非常に柔軟に制御することができます。あるアプリケーションの中で同じエレメントの異なるバージョンをキャッシュするためには、次の書式を使って一意のキャッシュキーを提供してください。

$this->element('helpbox', [], [
        'cache' => ['config' => 'short', 'key' => 'unique value'],
    ]
);

もしもエレメント中で、データソースからの動的なデータのような、さらなるロジックが必要であれば、エレメントの代わりにビューセルを使用することを検討してください。詳細は ビューセルについて 参照してください。

エレメントのキャッシュ

キャッシュパラメーターを渡すだけで CakePHP のビューキャッシュの恩恵を得られます。 true を渡した場合、 'default' のキャッシュ設定に基づいてエレメントがキャッシュされます。あるいは、どのキャッシュ設定を使うかを設定することができます。 Cache の設定についての詳細は キャッシュ を参照してください。エレメントキャッシュの単純な例は以下のようになります。

echo $this->element('helpbox', [], ['cache' => true]);

あるビューの中で同じエレメントを二回以上描画し、かつキャッシュが有効であれば、毎回異なる名前の 'key' パラメーターを設定するようにしてください。後続の呼び出しが、その前の element() の呼び出しでキャッシュされた結果を上書してしまうのを避けることができるでしょう。例えば:

echo $this->element(
    'helpbox',
    ['var' => $var],
    ['cache' => ['key' => 'first_use', 'config' => 'view_long']]
);

echo $this->element(
    'helpbox',
    ['var' => $differenVar],
    ['cache' => ['key' => 'second_use', 'config' => 'view_long']]
);

上の例は、双方の element() の結果が別々にキャッシュされるようにします。もしすべてのエレメントのキャッシュで同じ設定を使いたい場合、 View::$elementCache を設定することで繰り返しを避けられます。 CakePHP は element() に何も渡されなかった場合、この設定を使います。

プラグインのエレメントの要求

もしプラグインを使っていて、そのプラグインのエレメントを使いたければ、慣れ親しんだプラグイン記法 を使うだけでよいです。もしもビューがプラグインのコントローラー/アクションで描画される場合、他のプラグイン名が渡されなければ、使用されるすべてのエレメントに対して自動的にそのプラグイン名が接頭されます。もしエレメントがプラグインに存在しない場合、メインの APP フォルダーの中を探します。

echo $this->element('Contacts.helpbox');

もしビューがプラグインの一部であれば、プラグイン名を省略できます。例えば、Contacts プラグインの ContactsController の中にいる場合、次の:

echo $this->element('helpbox');
// と
echo $this->element('Contacts.helpbox');

は等価で、描画されるエレメントは同じになります。

プラグインのサブフォルダー中のエレメント (例: plugins/Contacts/Template/element/sidebar/helpbox.php) については、以下を使ってください。

echo $this->element('Contacts.sidebar/helpbox');

ルーティングプレフィックスとエレメント

ルーティングプレフィックスを設定している場合、レイアウトやビューがそうであるように、エレメントのパスの解決は、プレフィックスの場所に切り替わります。 "Admin" プレフィックスが設定されていて、次のように呼ぶとすれば:

echo $this->element('my_element');

エレメントはまず templates/Admin/Element/ から探されます。もしもファイルが存在しなければ、既定の場所から探されます。

ビューの断片のキャッシュ

Cake\View\View::cache(callable $block, array $options = [])

時には、ビュー出力の断片を生成するのに時間がかかることがあります。たとえば、 ビューセル の描画や、時間のかかるヘルパーの操作など。アプリケーションの実行を速くする手助けのために、 CakePHP はビューの断片をキャッシュする方法を提供します。

// ローカル変数を想定しています
echo $this->cache(function () use ($user, $article) {
    echo $this->cell('UserProfile', [$user]);
    echo $this->cell('ArticleFull', [$article]);
}, ['key' => 'my_view_key']);

既定ではキャッシュされたビューの内容は View::$elementCache のキャッシュ設定になりますが、これを変更するために config オプションを使用できます。

ビューイベント

コントローラーと同様、ビューは、描画のライフサイクル回りのロジックを入れるために、いくつかのイベント/コールバックを呼び出すことができます。

イベント一覧

View.beforeRender

View.beforeRenderFile

View.afterRenderFile

View.afterRender

View.beforeLayout

View.afterLayout

アプリケーションの イベントリスナー をこれらのイベントにアタッチする、または ヘルパーコールバック を使うことができます。

独自のビュークラス作成

データビューの新しいタイプを有効にしたり、付加的なカスタムのビュー描画ロジックをアプリケーションに追加するために、カスタムビュークラスを作成する必要があるかもしれません。 CakePHP のほとんどの構成要素と同様に、ビュークラスにはいくつかの規約があります。

ビュークラスは src/View に配置してください。例: src/View/PdfView.php

ビュークラス名には View をサフィックスとしてつけてください。 例: PdfView

ビュークラス名を参照するときは View サフィックスを省略する必要があります。例: $this->viewBuilder()->setClassName('Pdf');.

また、正しく動作するように、 View を継承しましょう。

// src/View/PdfView.php の中で
namespace App\View;

use Cake\View\View;

class PdfView extends View
{
    public function render($view = null, $layout = null)
    {
        // カスタムロジックをここに。
    }
}

render メソッドを置き換えると、コンテンツがレンダリングされる方法を完全に制御できます。

ビューのより詳細

ビューセル

テーマ

JSON と XML ビュー

ヘルパー

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ビューセル

ビューセルはビュー処理やテンプレート出力を実行できる小さな小さなコントローラーです。セルのアイディアは Ruby のセル [https://github.com/trailblazer/cells] から拝借したもので、同様の役割と目的を果たします。

セルを使う時

セルは、モデルとの対話、ビュー処理、および出力処理を必要とする、再利用可能なページ部品を構築するのに理想的です。単純な例はオンライン店舗の買い物かご、あるいは CMS のデータ駆動型のナビゲーションメニューなどになるでしょう。

セルの作成

セルを作成するには、 src/View/Cell にクラスを、 templates/cell/ にテンプレートを定義します。この例では、ユーザーの通知受信トレイにいくつかのメッセージを表示するためにセルを作ることにします。まず、クラスファイルを作ります。中身はこのようになります。

namespace App\View\Cell;

use Cake\View\Cell;

class InboxCell extends Cell
{
    public function display()
    {
    }
}

このファイルを src/View/Cell/InboxCell.php に保存します。ご覧の通り、 CakePHP の他のクラスと同様に、セルはいくつかの規約を持っています。

セルは App\View\Cell 名前空間に置かれます。もしプラグインの中でセルを作るのであれば、その名前空間は PluginName\View\Cell になります。

クラス名は Cell で終わらなければなりません。

クラスは Cake\View\Cell を継承しなければなりません。

私たちのセルには空の display() メソッドを追加しましたが、これはセルを描画する時の規約上の既定のメソッドです。この文書の後ろで、他のメソッドの使い方についても扱います。さて、ファイル templates/cell/Inbox/display.php を作成しましょう。これは、私たちの新しいセルのためのテンプレートになります。

bake を使って手早くこのスタブコードを生成することもできます。

bin/cake bake cell Inbox

これは上記で作成したコードを生成してくれるでしょう。

セルの実装

ユーザー同士で互いにメッセージを送りあえるアプリケーションを開発しているとしましょう。私たちは Messages モデルを持っていて、そして AppController を汚さずに未読メッセージの数を表示したいとします。これはまさにセルを使用するケースです。先ほど作ったクラスに、以下を追加します。

namespace App\View\Cell;

use Cake\View\Cell;

class InboxCell extends Cell
{
    public function display()
    {
        $unread = $this->fetchTable('Messages')->find('unread');
        $this->set('unread_count', $unread->count());
    }
}

セルは ModelAwareTrait と ViewVarsTrait を使用しているため、コントローラーにとてもよく似たふるまいをします。コントローラー中とまったく同じように、 loadModel() や set() メソッドを使うことができます。テンプレートファイルの中に、以下を追加します。

<!-- templates/cell/Inbox/display.php -->
<div class="notification-icon">
    未読メッセージが <?= $unread_count ?> 件あります。
</div>

注釈

セルのテンプレートは分離したスコープを持っていて、現在のコントローラーのアクションまたは他のセル用のテンプレートやレイアウトを描画するのに使われたビューのインスタンスを共有しません。したがって、アクションのテンプレートやレイアウト中でのヘルパーの呼び出しあるいはブロックのセットなどには気付きません。

セルの呼び出し

セルは cell() メソッドを使ってビューから呼び出すことができて、下のいずれのコンテキストでも同様に動きます。

// アプリケーションのセルの呼び出し
$cell = $this->cell('Inbox');

// プラグインのセルの呼び出し
$cell = $this->cell('Messaging.Inbox');

上記は当該の名前のセルを呼び出して、その display() メソッドを実行します。以下のようにして、他のメソッドを実行することもできます。

// Inbox セル上の expanded() メソッドを実行します
$cell = $this->cell('Inbox::expanded');

もしもリクエスト中でどのセルを呼び出すかを決定するためのコントローラー処理が必要な場合、コントローラー中で cell() メソッドを有効にするために CellTrait を使用することができます。

namespace App\Controller;

use App\Controller\AppController;
use Cake\View\CellTrait;

class DashboardsController extends AppController
{
    use CellTrait;

    // 他のコード。
}

セルに引数を渡す

セルをより柔軟にするために、パラメーター付きのセルのメソッドが必要になることも多いでしょう。添字付きの配列として cell() の第二、第三引数を使用することで、アクションのパラメーターや、追加のオプションをセルクラスに渡すことができます。

$cell = $this->cell('Inbox::recent', ['-3 days']);

上記は以下のような関数の定義になるでしょう。

public function recent($since)
{
}

ビューの描画

セルが呼び出されて実行された後は、おそらくそれを描画したいはずです。セルを描画するための最も簡単な方法はそれをエコーすることです。

<?= $cell ?>

これはアクション名を小文字にしてアンダースコアー区切りにしたものに一致するテンプレートを描画します。例えば display.php です。

セルはテンプレートを描画するために View を使用しますので、もし必要であればセルのテンプレートの中で追加のセルを呼び出すこともできます。

注釈

セルをエコーすると PHP のマジックメソッド __toString() を使用するため致命的なエラーが発生した際にファイル名や行番号を表示するのを抑制してしまいます。意味のあるエラーメッセージを得るためには、例えば <?= $cell->render() ?> のようにして、 Cell::render() メソッドを使用することを推奨します。

別のテンプレートの描画

規約ではセルは実行するアクションに一致するテンプレートを描画します。もし、異なるビューテンプレートを描画する必要があれば、セルを描画する時に使用するテンプレートを指定することができます。

// 明示的に render() を呼び出します
echo $this->cell('Inbox::recent', ['-3 days'])->render('messages');

// セルをエコーする前にテンプレートを設定します。
$cell = $this->cell('Inbox');
$cell->viewBuilder()->setTemplate('messages');
// 3.4 より前
$cell->viewBuilder()->template('messages');
// 3.1 より前
$cell->template = 'messages';
echo $cell;

セルの出力のキャッシュ

もしも出力内容が頻繁には変わらない、あるいはアプリケーションのパフォーマンス向上のために、セルを描画する際にその出力をキャッシュしたいかもしれません。キャッシュを有効にする、あるいは設定するために、セルを作成する時に cache オプションを定義することができます。

// 既定の設定と生成キーを使用してキャッシュします
$cell = $this->cell('Inbox', [], ['cache' => true]);

// 特定のキャッシュ設定と生成キーでキャッシュします
$cell = $this->cell('Inbox', [], ['cache' => ['config' => 'cell_cache']]);

// 使用するキーと設定を指定します。
$cell = $this->cell('Inbox', [], [
    'cache' => ['config' => 'cell_cache', 'key' => 'inbox_' . $user->id]
]);

キーが生成される場合には、そのクラスとテンプレートの名前をアンダースコアー区切りにしたものが使用されます。

注釈

各セルを描画するために新しい View インスタンスが作成され、それらの新しいオブジェクトはメインのテンプレート／レイアウトとはコンテキストを共有しません。各セルは内包されていて、 View::cell() の呼び出しの引数として渡された変数にのみアクセスが可能です。

セル内のデータのページ制御

ページ制御された結果セットを描画するセルを作成するには、ORM の Paginator クラスを利用します。ユーザーのお気に入りメッセージをページ制御する例は次のようになります。

namespace App\View\Cell;

use Cake\View\Cell;
use Cake\Datasource\Paginator;

class FavoritesCell extends Cell
{
    public function display($user)
    {
        $this->loadModel('Messages');

        // paginator の作成
        $paginator = new Paginator();

        // モデルをページ制御
        $results = $paginator->paginate(
            $this->Messages,
            $this->request->getQueryParams(),
            [
                // パラメーター付きカスタムファインダーを使用
                'finder' => ['favorites' => [$user]],

                // スコープ指定のクエリー文字列パラメーターを使用
                'scope' => 'favorites',
            ]
        );

        $paging = $paginator->getPagingParams() + (array)$this->request->getAttribute('paging');
        $this->request = $this->request->withAttribute('paging', $paging);

        $this->set('favorites', $results);
    }
}

上記のセルは、 スコープ指定のページ制御パラメーターを使用して Messages モデルをページ制御します。

セルのオプション

セルは、セルオブジェクトの作成時にプロパティーに変換されるコンストラクターオプションを宣言できます。

namespace App\View\Cell;

use Cake\View\Cell;
use Cake\Datasource\Paginator;

class FavoritesCell extends Cell
{
    protected $_validCellOptions = ['limit'];

    protected $limit = 3;

    public function display($userId)
    {
        $result = $this->fetchTable('Users')->find('friends', ['for' => $userId])->all();
        $this->set('favorites', $result);
    }
}

ここでは、 $limit プロパティーを定義し、 limit をセルのオプションとして追加しました。これにより、セルの作成時にオプションを定義することができます。

$cell = $this->cell('Favorites', [$user->id], ['limit' => 10])

セルのオプションは、データをプロパティーとして使用してデフォルト値をオーバーライドできるようにする場合に便利です。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

テーマ

CakePHP のテーマは、テンプレートファイルを供給することに集中したシンプルなプラグインです。独自プラグインの作成 のセクションをご覧ください。テーマには、ページの見た目や雰囲気を簡単に素早く切り替えられるようになるという利点があります。もし必要であれば、テンプレートファイルに追加してヘルパーやセルもまた供給することができます。ヘルパーやセルをテーマで使うときは、 プラグイン記法 を使い続ける必要があります。

テーマを使うためには、コントローラーのアクションをテーマ名にするか beforeRender() をコールバックしてください。

class ExamplesController extends AppController
{
    public function beforeRender(\Cake\Event\EventInterface $event)
    {
        $this->viewBuilder()->setTheme('Modern');
    }
}

テーマのテンプレートファイルは、同じ名前のプラグイン内にある必要があります。例えば、上記のテーマは plugins/Modern/src/Template で見つかるでしょう。 CakePHP は plugin/theme の名前にキャメルケースを期待しています。これは大切なことなので覚えておいてください。さらに plugins/Modern/src/Template の中のフォルダー構造は templates/ と全く同じにしてください。

例えば、Posts コントローラーの edit アクションのためのビューファイルは plugins/Modern/templates/Posts/edit.php に配置されて、レイアウトファイルは plugins/Modern/templates/layout/ に配置されます。カスタマイズされたテンプレートにプラグイン・テーマを供給することもできます。 TagsController を含んだ 'Cms' と名付けられたプラグインがある場合、テーマ Modern は edit テンプレートを plugins/Modern/templates/plugin/Cms/Tags/edit.php に置き換えます。

テーマの中にビューファイルを見つけられなかった場合、CakePHP はビューファイルを templates/ に配置しようとします。この方法によって、マスターテンプレートファイルを作成して、テーマフォルダーには上書きが必要なファイルだけを配置すればよくなります。

テーマがプラグインとしても機能する場合は、 Application の bootstrap メソッドの中で読み込ませることを忘れないようにしてください。例:

// /plugins/Modern フォルダーにあるプラグインテーマを読み込みます
$this->addPlugin('Modern');

テーマアセット

テーマは CakePHP の標準のプラグインなので、必要なアセットを webroot ディレクトリーに含めることができます。これは、テーマの配布と容易なパッケージングを可能にさせます。開発中、テーマのアセットへのリクエストは Cake\Routing\Dispatcher によってハンドリングされます。プロダクション環境でパフォーマンスを改善するためには、アプリケーションのパフォーマンス改善 を推奨します。

CakePHP の全ての組み込みヘルパーはテーマを認識しており、正確なパスを自動的に生成します。テンプレートファイル同様に、ファイルがテーマフォルダーの中に無かったらメインの webroot フォルダーをデフォルトにします。

// 'purple_cupcake'という名前のテーマの時
$this->Html->css('main.css');

// 以下のパスを生成する
/purple_cupcake/css/main.css

// 以下をリンクする
plugins/PurpleCupcake/webroot/css/main.css

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

JSON と XML ビュー

JsonView と XmlView を使用することで JSON と XML レスポンスを作成できるようになり、それらは Cake\Controller\Component\RequestHandlerComponent と統合されます。

あなたのアプリケーションで RequestHandlerComponent を有効にして、 json と xml またはいずれかの拡張子のサポートを有効にすることで、自動的に新しいビュークラスを使用することが出来るようになります。以降、このページでは JsonView と XmlView をデータビューと呼びます。

データビューを生成する方法は2つあります。一つ目は serialize オプションを使用する方法、二つ目は通常のテンプレートファイルを使用する方法です。

あなたのアプリケーションでデータビューを有効化する

データビュークラスを使用する前に、まず Cake\Controller\Component\RequestHandlerComponent をコントローラーでロードする必要があります。

public function initialize(): void
{
    ...
    $this->loadComponent('RequestHandler');
}

AppController で上記のようにすることで、コンテンツタイプによる自動切り替えが有効化されます。 viewClassMap 設定でコンポーネットを設定することで、カスタムクラスをマップしたり他のデータ型をマップするも出来ます。

任意で ファイル拡張子のルーティング で json と xml 、もしくはどちらかの拡張子を有効化することが出来ます。 http://example.com/articles.json の様に、URL の末尾でファイルの拡張子としてレスポンスタイプの名前を指定することで、 JSON 、 XML もしくはそれ以外の特定のフォーマットビューにアクセスすることが出来ます。

デフォルトでは ファイル拡張子のルーティング が無効の場合、リクエストの Accept ヘッダーにより、ユーザーにどのフォーマットをレンダリングするべきかが判断されます。例として、 JSON レスポンスをレンダリングするために使用される Accept の設定値は application/json です。

シリアライズキーをデータビューで使用する

serialize オプションは、データビューを使用する時に、どのビュー変数がシリアライズされるべきかを示します。 json/xml に変換する前に独自のフォーマット処理が不要な場合、これはコントローラーのアクションでテンプレートファイルを定義する手間を省いてくれます。

レスポンスを生成する前にビュー変数へ独自のフォーマット処理や操作が必要な場合は、テンプレートファイルを使用する必要があります。 serialize の値は、文字列かシリアライズしたいビュー変数の配列のどちらかになります。

namespace App\Controller;

class ArticlesController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('RequestHandler');
    }

    public function index()
    {
        // シリアライズする必要があるビュー変数をセットする
        $this->set('articles', $this->paginate());
        // JsonView がシリアライズするべきビュー変数を指定する
        $this->viewBuilder()->setOption('serialize', 'articles');
    }
}

serialize をビュー変数の配列として定義し結合することも出来ます。

namespace App\Controller;

class ArticlesController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('RequestHandler');
    }

    public function index()
    {
        // $articles と $comments を生成する何かのコード

        // シリアライズする必要があるビュー変数をセットする
        $this->set(compact('articles', 'comments'));

        // JsonView がシリアライズするべきビュー変数を指定する
        $this->viewBuilder()->setOption('serialize', ['articles', 'comments']);
    }
}

XmlView を使用するケースでは、配列として serialize を定義することには最上位に <response> 要素が自動的に付与されるという利点があります。 serialize と XmlView に文字列を使用する時は、ビュー変数が最上位要素を一つ持つことに確認してください。一つも最上位要素を持たない場合、XML の生成に失敗するでしょう。

テンプレートファイルをデータビューで使用する

最終的な出力の前にビュー変数に何かの処理を施したいケースでは、テンプレートファイルを使用する必要があります。例えば、生成された HTML を要素として持つ記事があり、 JSON レスポンスからそれを取り除きたいとします。こういった状況ではビューファイルが役に立ちます。

// コントローラーのコード
class ArticlesController extends AppController
{
    public function index()
    {
        $articles = $this->paginate('Articles');
        $this->set(compact('articles'));
    }
}

// ビューのコード - templates/Articles/json/index.php
foreach ($articles as &$article) {
    unset($article->generated_html);
}
echo json_encode(compact('articles'));

より複雑な操作を行ったり、ヘルパーを整形に使用することも出来ます。データビュークラスは、ビューファイルはシリアライズされたコンテンツを出力することを前提としているため、レイアウトをサポートしません。

XML ビューの作成

class XmlView

デフォルトでは serialize を使用する時、XmlView は <response> ノードでシリアル化されるビュー変数をラップします。 rootNode オプションを使用することで、このノードに別の名前を設定することが出来ます。

XmlView クラスは、XML の生成に使用するオプション（例: tags vs attributes ）を変更するための xmlOptions オプションをサポートしています。

XmlView の使用例は sitemap.xml [https://www.sitemaps.org/protocol.html] を生成することです。このドキュメントタイプでは rootNode を変更し属性を設定する必要があります。属性は @ プレフィックスを使用して定義されます。

public function sitemap()
{
    $pages = $this->Pages->find()->all();
    $urls = [];
    foreach ($pages as $page) {
        $urls[] = [
            'loc' => Router::url(['controller' => 'Pages', 'action' => 'view', $page->slug, '_full' => true]),
            'lastmod' => $page->modified->format('Y-m-d'),
            'changefreq' => 'daily',
            'priority' => '0.5'
        ];
    }

    // 生成されたドキュメントにカスタムルートノードを定義します。
    $this->viewBuilder()
        ->setOption('rootNode', 'urlset')
        ->setOption('serialize', ['@xmlns', 'url']);
    $this->set([
        // ルートノードで属性を定義します。
        '@xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9',
        'url' => $urls
    ]);
}

JSON ビューの作成

class JsonView

JsonView クラスは、JSON の生成に使用するビットマスクを変更するためための jsonOptions オプションをサポートします。このオプションの有効な値は json_encode [https://php.net/json_encode] を参照してください。

例えば、一貫した JSON 形式で CakePHP エンティティーの検証エラーをシリアライズするには:

// コントローラーのアクションの中で、保存に失敗した時
$this->set('errors', $articles->errors());
$this->viewBuilder()
    ->setOption('serialize', ['errors'])
    ->setOption('jsonOptions', JSON_FORCE_OBJECT);

JSONP レスポンス

JsonView を使用する時は、特別なビュー変数 _jsonp を使用することで JSONP レスポンスの返すことが出来ます。これに true を設定することで、ビュークラスに "callback" という名前のクエリー文字列パラメーターがセットされているかをチェックさせ、それ同時に提供された関数名で JSON レスポンスをラップさせることが出来ます。 "callback" の代わりにカスタムクエリー文字列パラメーターを使用したい場合は、 _jsonp に true の代わりの名前を指定してください。

使用例

リクエストのコンテンツタイプまたは拡張子によって、 RequestHandlerComponent が自動的にビューをセットするのに対して、あなたも同様にコントローラーのなかでビューマッピングを操作することが出来ます。

// src/Controller/VideosController.php
namespace App\Controller;

use App\Controller\AppController;
use Cake\Http\Exception\NotFoundException;

class VideosController extends AppController
{
    public function export($format = '')
    {
        $format = strtolower($format);

        // ビューマッピングの形式
        $formats = [
          'xml' => 'Xml',
          'json' => 'Json',
        ];

        // 未知の形式の時はエラー
        if (!isset($formats[$format])) {
            throw new NotFoundException(__('Unknown format.'));
        }

        // ビューに出力形式をセット
        $this->viewBuilder()->setClassName($formats[$format]);

        // データを取得
        $videos = $this->Videos->find('latest')->all();

        // ビューにデータをセット
        $this->set(compact('videos'));
        $this->viewBuilder()->setOption('serialize', ['videos']);

        // ダウンロードを指定
        return $this->response->withDownload('report-' . date('YmdHis') . '.' . $format);
    }
}

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ヘルパー

ヘルパーはアプリケーションのプレゼンテーション層のためのコンポーネントのようなクラスです。多くのビューやエレメント、レイアウトで共有される表示ロジックを含んでいます。この章では、ヘルパーを設定する方法を紹介します。ヘルパーの読み込み方法、それらのヘルパーの使い方、独自のヘルパーを作成するための簡単な手順を概説します。

CakePHP にはビューの作成に役立つ多くのヘルパーがあります。それらは、整形式のマークアップ (フォーム含む)、テキスト、時間、数値の整形に役立ったり、 Ajax 機能 をスピードアップさせたりします。 CakePHP のヘルパーに関する詳細については、各ヘルパーの章をご覧ください。

Breadcrumbs (パンくず)

Flash

Form

Html

Number

Paginator

Text

Time

Url

ヘルパーの設定

CakePHP でヘルパーを読み込むには、ビュークラスでヘルパーを宣言します。 AppView クラスは、すべての CakePHP アプリケーションが付属し、ヘルパーを読み込むための理想的な場所です。

class AppView extends View
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadHelper('Html');
        $this->loadHelper('Form');
        $this->loadHelper('Flash');
    }
}

プラグインのヘルパーを読み込むには、CakePHP の他の場所でも使われているプラグイン記法 を使います。

$this->loadHelper('Blog.Comment');

CakePHP やアプリケーションにあるヘルパーを明示的に読み込む必要はありません。これらのヘルパーは、初回の使用時に遅延ロードされます。例えば:

// まだ読み込まれていなければ FormHelper を読み込みます。
$this->Form->create($article);

プラグインのビュー内から、プラグインヘルパーを遅延ロードすることもできます。例えば、'Blog' プラグインのビューテンプレートは、同じプラグインからヘルパーを遅延ロードすることができます。

条件付きヘルパーの読み込み

現在のアクション名を使用して、条件付きでヘルパーを読み込むことができます。

class AppView extends View
{
    public function initialize(): void
    {
        parent::initialize();
        if ($this->request->getParam('action') === 'index') {
            $this->loadHelper('ListPage');
        }
    }
}

また、コントローラーの beforeRender メソッドを使用して、ヘルパーを読み込むことができます。

class ArticlesController extends AppController
{
    public function beforeRender(EventInterface $event)
    {
        parent::beforeRender($event);
        $this->viewBuilder()->addHelper('MyHelper');
    }
}

設定オプション

ヘルパーに設定オプションを渡すことができます。これらのオプションは、属性値を設定したり、ヘルパーの振る舞いを変更するために使用することができます。

namespace App\View\Helper;

use Cake\View\Helper;
use Cake\View\View;

class AwesomeHelper extends Helper
{
    public function initialize(array $config): void
    {
        debug($config);
    }
}

デフォルトでは、すべての設定オプションは、 $_defaultConfig プロパティーとマージされます。このプロパティーは、ヘルパーが必要とする設定のデフォルト値を定義する必要があります。例えば:

namespace App\View\Helper;

use Cake\View\Helper;
use Cake\View\StringTemplateTrait;

class AwesomeHelper extends Helper
{
    use StringTemplateTrait;

    protected $_defaultConfig = [
        'errorClass' => 'error',
        'templates' => [
            'label' => '<label for="{{for}}">{{content}}</label>',
        ],
    ];
}

ヘルパーのコンストラクターに提供される任意の設定は、構築時にデフォルト値とマージされ、マージされたデータは、 _config に設定されます。実行時設定を読み取るために config() メソッドを使用することができます。

// errorClass 設定オプションを読み込み
$class = $this->Awesome->config('errorClass');

ヘルパー設定を使用すると、宣言的にヘルパーを設定し、コントローラーロジックから設定ロジックを削除することができます。クラス宣言の一部として組み込むことができない設定オプションがある場合は、コントローラーの beforeRender コールバックで設定します。

class PostsController extends AppController
{
    public function beforeRender(EventInterface $event)
    {
        parent::beforeRender($event);
        $builder = $this->viewBuilder();
        $builder->helpers([
            'CustomStuff' => $this->_getCustomStuffConfig(),
        ]);
    }
}

ヘルパーの別名

共通設定の一つに className オプションがあります。このオプションを使うとビュー内に別名のヘルパーを作成することができます。この機能は $this->Html や他のヘルパーの参照を独自実装に置き換えたい時に便利です。

// src/View/AppView.php
class AppView extends View
{
    public function initialize(): void
    {
        $this->loadHelper('Html', [
            'className' => 'MyHtml'
        ]);
    }
}

// src/View/Helper/MyHtmlHelper.php
namespace App\View\Helper;

use Cake\View\Helper\HtmlHelper;

class MyHtmlHelper extends HtmlHelper
{
    // コア HtmlHelper を上書きするコードを追加
}

上記の例ではビューにて MyHtmlHelper に $this->Html という 別名 をつけています。

注釈

別名をつけられたヘルパーは、ヘルパーが使われるあらゆる場所のインスタンスを置き換えます。これは、他のヘルパーの内部を含みます。

ヘルパーの使用

コントローラーで使用するヘルパーを設定すると、各ヘルパーはビューのパブリックプロパティーとして公開されます。例えば、 HtmlHelper を使用していた場合、次を実行することによってアクセスすることができます。

echo $this->Html->css('styles');

上記は HtmlHelper の css() メソッドを呼び出します。 $this->{$helperName} を使用して任意の読み込まれたヘルパーにアクセスすることができます。

ヘルパーの動的ロード

ビュー内から動的にヘルパーを読み込む必要がある状況があるかもしれません。これを行うには、 ビューの Cake\View\HelperRegistry を使用することができます。

// どちらか１つが動作
$mediaHelper = $this->loadHelper('Media', $mediaConfig);
$mediaHelper = $this->helpers()->load('Media', $mediaConfig);

HelperRegistry は レジストリー です。そして、 CakePHP の他の場所で使用されるレジストリー API をサポートしています。

コールバックメソッド

ヘルパーは、ビューレンダリングプロセスを強化するためのコールバックをいくつか備えています。詳細は Helper クラス と イベントシステム ドキュメントをご覧ください。

ヘルパーの作成

アプリケーションやプラグインで使用するための独自のヘルパークラスを作成することができます。 CakePHP のコンポーネントと同様に、ヘルパークラスは、いくつかの規約があります。

ヘルパークラスファイルは、 src/View/Helper に置かれます。例: src/View/Helper/LinkHelper.php

ヘルパークラスは、末尾に Helper を付ける必要があります。例: LinkHelper 。

ヘルパークラス名を参照するときは、 末尾の Helper を省略しなければなりません。例: $this->loadHelper('Link'); 。

また、正しく動作させるために Helper を継承します。

/* src/View/Helper/LinkHelper.php */
namespace App\View\Helper;

use Cake\View\Helper;

class LinkHelper extends Helper
{
    public function makeEdit($title, $url)
    {
        // 特別な形式のリンクを作成するロジックをここに...
    }
}

他のヘルパーの読み込み

別のヘルパーに既に存在する機能を使用したい場合があります。その場合、 $helpers 配列に使いたいヘルパーを明示することで実現出来ます。フォーマットは、コントローラーで指定する場合と同じようにして下さい。

/* src/View/Helper/LinkHelper.php (他のヘルパーを使用) */

namespace App\View\Helper;

use Cake\View\Helper;

class LinkHelper extends Helper
{
    public $helpers = ['Html'];

    public function makeEdit($title, $url)
    {
        // 出力に HTML ヘルパーを使用
        // 整形されたデータ:

        $link = $this->Html->link($title, $url, ['class' => 'edit']);

        return '<div class="editOuter">' . $link . '</div>';
    }
}

独自のヘルパーを使用

ヘルパーを作成して src/View/Helper/ に配置すると、ビューに読み込めます。

class AppView extends View
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadHelper('Link');
    }
}

ヘルパーが読み込まれたら、一致するビュープロパティーにアクセスしてビュー内で使用できます。

<!-- 新しいヘルパーを使用してリンクを作成 -->
<?= $this->Link->makeEdit('レシピの変更', '/recipes/edit/5') ?>

注釈

HelperRegistry は、 コントローラー で明示されていないヘルパーを遅延ロードしようとします。

ヘルパー内部でビュー変数にアクセス

ヘルパー内部でビュー変数にアクセスしたい場合は、次のように $this->getView()->get() を使用することができます。

class AwesomeHelper extends Helper
{
    public $helpers = ['Html'];

    public function someMethod()
    {
        // meta description の設定
        return $this->Html->meta(
            'description', $this->getView()->get('metaDescription'), ['block' => 'meta']
        );
    }
}

ヘルパー内部でビューエレメントの描画

ヘルパー内部でエレメントを描画したい場合は、次のように $this->getView()->element() を使用することができます。

class AwesomeHelper extends Helper
{
    public function someFunction()
    {
        return $this->getView()->element(
            '/path/to/element',
            ['foo'=>'bar','bar'=>'foo']
        );
    }
}

Helper クラス

class Helper

コールバック

ヘルパーにコールバックメソッドを実装することで、CakePHP は関連するイベントにヘルパーを自動的に登録します。以前のバージョンの CakePHP とは異なり、ヘルパークラスはコールバックメソッドを実装していないので、あなたのコールバックでは parent を コールしてはいけません 。

Helper::beforeRenderFile(EventInterface $event, $viewFile)

各ビューファイルが描画される前に呼び出されます。これにはエレメント、ビュー、親ビュー、レイアウトを含みます。

Helper::afterRenderFile(EventInterface $event, $viewFile, $content)

各ビューファイルが描画された後に呼び出されます。これにはエレメント、ビュー、親ビュー、レイアウトを含みます。コールバックは描画されたコンテンツがブラウザーにどのように描画されるかを変えるために $content を変更して返すことができます。

Helper::beforeRender(EventInterface $event, $viewFile)

beforeRender メソッドはコントローラーの beforeRender メソッドの後に呼び出されます。しかし、コントローラーがビューとレイアウトを描画する前です。描画されるファイルを引数として受け取ります。

Helper::afterRender(EventInterface $event, $viewFile)

ビューが描画された後に呼び出されます。しかし、レイアウトの描画開始前です。

Helper::beforeLayout(EventInterface $event, $layoutFile)

レイアウトの描画開始前に呼び出されます。レイアウトファイル名を引数として受け取ります。

Helper::afterLayout(EventInterface $event, $layoutFile)

レイアウトの描画が完了した時に呼び出されます。レイアウトファイル名を引数として受け取ります。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Breadcrumbs (パンくず)

class Cake\View\Helper\BreadcrumbsHelper(View $view, array $config = [])

BreadcrumbsHelper は簡単にアプリのパンくずリストの作成と描画に対処する方法を提供します。

パンくずリストを作成

add() メソッドを使用して、リストにパンくずを追加することができます。これは 3 つの引数を持ちます。

title パンくずのタイトルとして表示する文字列。

url Url に与えられるパラメーターの配列や文字列。

options item と itemWithoutLink テンプレートの属性の配列。詳細は アイテムの属性を定義 のセクションをご覧ください。

リストの最後に追加するのに加えて、さまざまな操作を行うことができます。

// リストの最後に追加
$this->Breadcrumbs->add(
    'Products',
    ['controller' => 'products', 'action' => 'index']
);

// 複数のパンくずを最後に追加
$this->Breadcrumbs->add([
    ['title' => 'Products', 'url' => ['controller' => 'products', 'action' => 'index']],
    ['title' => 'Product name', 'url' => ['controller' => 'products', 'action' => 'view', 1234]]
]);

// パンくずをリストの一番先頭に追加
$this->Breadcrumbs->prepend(
    'Products',
    ['controller' => 'products', 'action' => 'index']
);

// 複数のパンくずをリストの先頭に順番に追加
$this->Breadcrumbs->prepend([
    ['title' => 'Products', 'url' => ['controller' => 'products', 'action' => 'index']],
    ['title' => 'Product name', 'url' => ['controller' => 'products', 'action' => 'view', 1234]]
]);

// 指定したスロットに挿入
// スロットが範囲外の場合は、例外が発生
$this->Breadcrumbs->insertAt(
    2,
    'Products',
    ['controller' => 'products', 'action' => 'index']
);

// タイトルを元に別のパンくずの前に挿入
// もし、パンくずのタイトルが見つからない場合、
// 例外が発生します。
$this->Breadcrumbs->insertBefore(
    'A product name', // 手前に挿入するパンくずのタイトル
    'Products',
    ['controller' => 'products', 'action' => 'index']
);

// タイトルを元に別のパンくずの後に挿入
// もし、パンくずのタイトルが見つからない場合、
// 例外が発生します。
$this->Breadcrumbs->insertAfter(
    'A product name', // 後ろに挿入するパンくずのタイトル
    'Products',
    ['controller' => 'products', 'action' => 'index']
);

これらのメソッドを使用すると、CakePHP の2段階の描画プロセスで動作することが可能になります。テンプレートやレイアウトは内側から描画される (つまり、インクルードされたエレメントが最初に描画される) ので、パンくずを追加したい場所を正確に定義することができます。

パンくずリストを描画

リストにパンくずを追加した後、 render() メソッドを使用して、簡単に描画することができます。このメソッドは、2つの配列引数を受け付けます。

$attributes: wrapper テンプレートに適用される属性の配列。これは、HTML タグに属性を追加することができます。テンプレート内に独自のテンプレート変数の挿入を可能にする特別な templateVars キーを受け入れます。

$separator: separator テンプレートの属性の配列。可能なプロパティーは次の通りです。

separator セパレーターとして表示する文字列。

innerAttrs セパレーターが２つの要素に分割された場合に属性を提供します。

templateVars テンプレートに独自のテンプレート変数の挿入を可能にします。

他のすべてのプロパティーは、HTML 属性として変換されます。そして、テンプレート内の attrs キーを置換します。もし、デフォルト設定 (このオプションが空) を使用する場合、セパレーターを描画しません。

以下は、パンくずリストを描画する例です。

echo $this->Breadcrumbs->render(
    ['class' => 'breadcrumbs-trail'],
    ['separator' => '<i class="fa fa-angle-right"></i>']
);

出力のカスタマイズ

BreadcrumbsHelper は内部で StringTemplateTrait を使用しています。これは、簡単に様々な HTML 文字列の出力をカスタマイズすることができます。次のデフォルトの定義では、4つのテンプレートが含まれます。

[
    'wrapper' => '<ul{{attrs}}>{{content}}</ul>',
    'item' => '<li{{attrs}}><a href="{{url}}"{{innerAttrs}}>{{title}}</a></li>{{separator}}',
    'itemWithoutLink' => '<li{{attrs}}><span{{innerAttrs}}>{{title}}</span></li>{{separator}}',
    'separator' => '<li{{attrs}}><span{{innerAttrs}}>{{separator}}</span></li>'
]

StringTemplateTrait の setTemplates() メソッドを使用すると簡単にカスタマイズすることができます。

$this->Breadcrumbs->setTemplates([
    'wrapper' => '<nav class="breadcrumbs"><ul{{attrs}}>{{content}}</ul></nav>',
]);

テンプレートを描画するとき、 templateVars オプションを使用すると、様々なテンプレートで、独自のテンプレート変数を追加することができます。

$this->Breadcrumbs->setTemplates([
    'item' => '<li{{attrs}}>{{icon}}<a href="{{url}}"{{innerAttrs}}>{{title}}</a></li>{{separator}}'
]);

また、 {{icon}} パラメーターを定義するには、リストにパンくずを追加する際にそれを指定するだけです。

$this->Breadcrumbs->add(
    'Products',
    ['controller' => 'products', 'action' => 'index'],
    [
        'templateVars' => [
            'icon' => '<i class="fa fa-money"></i>'
        ]
    ]
);

アイテムの属性を定義

アイテムとそのサブアイテムの両方に特定の HTML 属性を適用したい場合は、 $options 引数が提供する innerAttrs キーを活用することができます。 innerAttrs と templateVars 以外の全ては、HTML 属性として描画されます。

$this->Breadcrumbs->add(
    'Products',
    ['controller' => 'products', 'action' => 'index'],
    [
        'class' => 'products-crumb',
        'data-foo' => 'bar',
        'innerAttrs' => [
            'class' => 'inner-products-crumb',
            'id' => 'the-products-crumb'
        ]
    ]
);

// デフォルトのテンプレートに基づいて、次の HTML を描画:
<li class="products-crumb" data-foo="bar">
    <a href="/products/index" class="inner-products-crumb" id="the-products-crumb">Products</a>
</li>

パンくずの消去

reset() メソッドを使用してパンくずを消去することができます。これは、パンくずを変換してリストを上書きしたいときに便利です。

$crumbs = $this->Breadcrumbs->getCrumbs();
$crumbs = collection($crumbs)->map(function ($crumb) {
    $crumb['options']['class'] = 'breadcrumb-item';
    return $crumb;
})->toArray();

$this->Breadcrumbs->reset()->add($crumbs);

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Flash

class Cake\View\Helper\FlashHelper(View $view, array $config = [])

FlashHelperは、 FlashComponent によって $_SESSION にセットされたフラッシュメッセージを描画する方法を提供しています。 FlashComponent および FlashHelper はフラッシュメッセージを描画するためのエレメントを使用します。フラッシュエレメントは templates/element/flash ディレクトリー以下に存在します。 CakePHP の App テンプレートには、 success.php 、 default.php と error.php の 3つのフラッシュエレメントが付属しています。

フラッシュメッセージの描画

フラッシュメッセージを描画するためには、テンプレートファイルの中で FlashHelper の render() メソッドを使用します。

<?= $this->Flash->render() ?>

デフォルトでは、CakePHP は、フラッシュメッセージのためにセッション中の "flash" キーを使用します。しかし、 FlashComponent の中でフラッシュメッセージを設定した時にキーを指定した場合、そのキーを指定して描画します。

<?= $this->Flash->render('other') ?>

FlashComponent の中で設定したオプションを上書きすることもできます。

// コントローラーの中で
$this->Flash->set('The user has been saved.', [
    'element' => 'success'
]);

// テンプレートファイルの中で、 success.php の代わりに great_success.php を使用
<?= $this->Flash->render('flash', [
    'element' => 'great_success'
]);

注釈

フラッシュメッセージの独自テンプレートを作成する場合、全てのユーザーデータを適切に HTML エンコードしてください。CakePHP は、フラッシュメッセージのパラメーターをエスケープしません。

使用できる配列オプションについてもっと知りたい場合は、 FlashComponent セクションをご覧ください。

ルーティングのプレフィックスとフラッシュメッセージ

設定したルーティングのプレフィックスがある場合、フラッシュエレメントを templates/{Prefix}/element/flash に置きます。これにより、アプリケーションの各部分に特定のメッセージレイアウトを設定できます。例えば、フロントエンドと管理者のセクションで異なるレイアウトを使用する場合です。

フラッシュメッセージとテーマ

FlashHelper は、メッセージを描画するために標準のエレメントを使用し、指定したテーマに従います。そのため、テーマが templates/element/flash/error.php ファイルを持つ場合、エレメントやビューと同様に使用されます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Form

class Cake\View\Helper\FormHelper(View $view, array $config = [])

FormHelper は、フォーム作成時の力作業のほとんどを代行してくれます。 FormHelper は、フォームを素早く作成し、検証、再配置、レイアウトを効率化します。 FormHelper は、柔軟でもあります。通常は規約に沿ってほとんどのことをやってくれますが、特定のメソッドを使って必要な機能だけを使うこともできます。

フォームの開始

Cake\View\Helper\FormHelper::create(mixed $context = null, array $options = [])

$context - フォームが定義されているコンテキスト。 ORM エンティティー、 ORM 結果セット、メタデータの配列もしくは false/null (モデルのないフォームを作成するため) です。

$options - オプションまたは HTML 属性の配列。

FormHelper を活用するために最初に使うメソッドは create() です。このメソッドは、フォームの開始タグを出力します。

パラメーターはすべてオプションです。 create() が、パラメーターなしで呼ばれた場合、現在の URL を元に現在のコントローラーに送信するためのフォームを作ろうとしているものとみなします。フォーム送信のためのデフォルトのメソッドは POST です。 UsersController::add() のビューの中で create() を呼んだ場合、描画されたビューの中で次のような出力が表示されます。

<form method="post" action="/users/add">

$context 引数は、フォームの「コンテキスト」として使用されます。いくつかの組み込みフォームのコンテキストがあり、独自に追加することができます。次のセクションで説明します。組み込みのプロバイダーは、 $context の次の値とマップします。

Entity インスタンスまたはイテレーターは、 EntityContext [https://api.cakephp.org/4.x/class-Cake.View.Form.EntityContext.html] にマップされます。このコンテキストは、FormHelper が組み込みの ORM の結果を処理できるようにします。

schema キーを含む配列は、 ArrayContext [https://api.cakephp.org/4.x/class-Cake.View.Form.ArrayContext.html] にマップされます。これは単純なデータ構造を作成してフォームを構築することができます。

null と false は、 NullContext [https://api.cakephp.org/4.x/class-Cake.View.Form.NullContext.html] にマップされます。このコンテキストクラスは、FormHelper が必要とするインターフェイスを単に満たすだけです。このコンテキストは、ORM の永続性を必要としない短いフォームを作成したい場合に便利です。

すべてのコンテキストクラスは、リクエストデータにアクセスできるため、フォームを簡単に作成できます。

コンテキストを持ったフォームが作成されると、作成したすべてのコントロールはアクティブなコンテキストを使用します。 ORM バックエンドフォームの場合、FormHelper は関連データ、検証エラー、およびスキーマメタデータにアクセスできます。 end() メソッドを使用したり、再度 create() を呼び出すことによって、アクティブなコンテキストを閉じることができます。

エンティティーのフォームを作成するには、次の手順を実行します。

// /articles/add において
// $article は、空の Article エンティティーである必要があります。
echo $this->Form->create($article);

出力結果:

<form method="post" action="/articles/add">

これは ArticlesController の add() アクションにフォームデータを POST します。また、編集フォームを作成するために、同じロジックを使用することができます。 FormHelper は、 追加 または 編集 のフォームを作成するかどうかを自動的に検出するために、 Entity オブジェクトを使用します。提供されたエンティティーが「新しくない」場合は、 編集 フォームとして作成されます。

例えば、 http://example.org/articles/edit/5 を閲覧すると、次のことができます。

// src/Controller/ArticlesController.php:
public function edit($id = null)
{
    if (empty($id)) {
        throw new NotFoundException;
    }
    $article = $this->Articles->get($id);
    // 保存ロジックがここに入ります
    $this->set('article', $article);
}

// View/Articles/edit.php:
// $article->isNew() は false なので、編集フォームが得られます
<?= $this->Form->create($article) ?>

出力結果:

<form method="post" action="/articles/edit/5">
<input type="hidden" name="_method" value="PUT" />

注釈

これは 編集 フォームなので、デフォルトの HTTP メソッドを上書きするために hidden input フィールドが生成されます。

場合によっては、フォームの action の URL の最後にエンティティーの ID が自動的に付加されます。 URL に ID が付加されることを避けたい場合、 $options['url'] に '/my-acount' や \Cake\Routing\Router::url(['controller' => 'Users', 'action' => 'myAccount']) のように文字列を渡すことができます。

フォーム作成のためのオプション

$options 配列は、ほとんどのフォーム設定が行われる場所です。この特別な配列には、 form タグの生成方法に影響を与えるさまざまなキーと値のペアが含まれます。有効な値:

'type' - 作成するフォームの種類を選択できます。type が未指定の場合、フォームコンテキストに基づいて自動的に決まります。有効な値:

'get' - フォームの method に HTTP GET を設定します。

'file' - フォームの method に POST を設定し、 enctype に "multipart/form-data" を設定します。

'post' - method に POST を設定します。

'put', 'delete', 'patch' - フォームの送信時に、HTTP メソッドを PUT、 DELETE もしくは PATCH に上書きします。

'method' - 有効な値は、上記と同じです。フォームの method を明示的に上書きできます。

'url' - フォームを送信する URL を指定します。文字列および URL 配列を指定できます。

'encoding' - フォームに accept-charset エンコーディングをセットします。デフォルトは、 Configure::read('App.encoding') です。

'enctype' - 明示的にフォームのエンコーディングをセットできます。

'templates' - このフォームで使用したいテンプレート。指定したテンプレートは、既に読み込まれたテンプレートの上にマージされます。 /config のファイル名 (拡張子を除く) か、使用したいテンプレートの配列のいずれかを指定します。

'context' - フォームコンテキストクラスの追加オプション。(例えば、 EntityContext は、フォームのベースとなる特定の Table クラスを設定するための 'table' オプションを受け付けます。)

'idPrefix' - 生成された ID 属性のプレフィックス。

'templateVars' - formStart テンプレートのためのテンプレート変数を提供することができます。

autoSetCustomValidity - コントロールの HTML5 検証メッセージでカスタム必須および notBlank 検証メッセージを使用するには、 true を設定します。デフォルトは false です。

Tip

上記のオプションの他に、 $options 引数の中で、 作成した form 要素に渡したい有効な HTML 属性を指定できます。

クエリー文字列からフォームの値を取得

FormHelper の値ソースは、input タグなどの描画される要素がどこから値を受け取るかを定義します。

デフォルトでは、FormHelper は、「コンテキスト」をもとにその値を描画します。 EntityContext などのデフォルトのコンテキストは、現在のエンティティーや $request->getData() からデータを取得します。

しかし、クエリー文字列から読み込む必要があるフォームを構築している場合は、 FormHelper の valueSource() を使って、どこから入力データを読み込むかを変更できます。

// コンテキストでクエリー文字列の優先順位をつける
echo $this->Form->create($article, [
    'type' => 'get',
    'valueSources' => ['query', 'context']
]);

// 同じ効果:
echo $this->Form
    ->setValueSources(['query', 'context'])
    ->create($articles, ['type' => 'get']);

When input data has to be processed by the entity, i.e. marshal transformations, table query result or entity computations, and displayed after one or multiple form submissions where request data is retained, you need to put context first:

// Prioritize context over request data:
echo $this->Form->create($article,
    'valueSources' => ['context', 'data']
]);

サポートするソースは、 context, data そして query です。単一または複数のソースを使用できます。 FormHelper によって生成されたウィジェットは設定した順序でソースから値を集めます。

end() が呼ばれた時、値ソースはデフォルト (['context']) にリセットされます。

フォームの HTTP メソッドを変更

type オプションを使用することにより、フォームが使用する HTTP メソッドを変更することができます。

echo $this->Form->create($article, ['type' => 'get']);

出力結果:

<form method="get" action="/articles/edit/5">

type の値に 'file' を指定すると、フォームの送信方法は、'POST' に変更し、form タグに "multipart/form-data" の enctype が含まれます。これは、フォーム内部に file 要素がある場合に使用されます。適切な enctype 属性が存在しない場合は、ファイルのアップロードが機能しない原因となります。

例:

echo $this->Form->create($article, ['type' => 'file']);

出力結果:

<form enctype="multipart/form-data" method="post" action="/articles/add">

'type' の値として 'put' 、 'patch' または 'delete' を使用すると、フォームは機能的に 'post' フォームに相当しますが、送信されると、HTTP リクエストメソッドは、それぞれ 'PUT'、 'PATCH' または 'DELETE' で上書きされます。これで、CakePHP は、ウェブブラウザーで適切な REST サポートをエミュレートすることができます。

フォームの URL を設定

url オプションを使うと、フォームを現在のコントローラーやアプリケーションの別のコントローラーの特定のアクションに向けることができます。

例えば、フォームを現在のコントローラーの publish() アクションに向けるには、次のような $options 配列を与えます。

echo $this->Form->create($article, ['url' => ['action' => 'publish']]);

出力結果:

<form method="post" action="/articles/publish">

目的のフォームアクションが現在のコントローラーにない場合は、フォームアクションの完全な URL を指定できます。出力される URL は CakePHP アプリケーションに対する相対になります。

echo $this->Form->create(null, [
    'url' => [
        'controller' => 'Articles',
        'action' => 'publish'
    ]
]);

出力結果:

<form method="post" action="/articles/publish">

または外部ドメインを指定することができます。

echo $this->Form->create(null, [
    'url' => 'https://www.google.com/search',
    'type' => 'get'
]);

出力結果:

<form method="get" action="https://www.google.com/search">

フォームアクションに URL を出力したくない場合、 'url' => false を使用してください。

カスタムバリデーターの利用

多くの場合、モデルには複数の検証セットがあり、コントローラーアクションが適用される特定の検証ルールに基づいて必要なフィールドに FormHelper を設定する必要があります。たとえば、Users テーブルには、アカウントの登録時にのみ適用される特定の検証ルールがあります。

echo $this->Form->create($user, [
    'context' => ['validator' => 'register']
]);

上記では UsersTable::validationRegister() で定義されている register バリデーターの中で定義されたルールを $user と関連するすべてのアソシエーションに使用します。関連付けられたエンティティーのフォームを作成する場合は、配列を使用して各アソシエーションの検証ルールを定義できます。

echo $this->Form->create($user, [
    'context' => [
        'validator' => [
            'Users' => 'register',
            'Comments' => 'default'
        ]
    ]
]);

上記は、ユーザーには register 、そしてユーザーのコメントには default を使用します。

コンテキストクラスの作成

組み込みのコンテキストクラスは基本的なケースをカバーすることを目的としていますが、異なる ORM を使用している場合は新しいコンテキストクラスを作成する必要があります。このような状況では、 Cake\View\Form\ContextInterface [https://api.cakephp.org/4.x/class-Cake.View.Form.ContextInterface.html] を実装する必要があります。このインターフェイスを実装すると、新しいコンテキストを FormHelper に追加することができます。 View.beforeRender イベントリスナーやアプリケーションビュークラスで行うのが最善の方法です。

$this->Form->addContextProvider('myprovider', function ($request, $data) {
    if ($data['entity'] instanceof MyOrmClass) {
        return new MyProvider($request, $data);
    }
});

コンテキストのファクトリー関数では、正しいエンティティータイプのフォームオプションを確認するためのロジックを追加できます。一致する入力データが見つかった場合は、オブジェクトを返すことができます。一致するものがない場合は null を返します。

フォームコントロールの作成

Cake\View\Helper\FormHelper::control(string $fieldName, array $options = [])

$fieldName - 'Modelname.fieldname' の形式のフィールド名。

$options - コントロールのオプション や (様々な HTML 要素を生成するために control() が内部的に使用する) 他のメソッドのオプション、および有効な HTML 属性を含むオプション配列。

control() メソッドを使うと完全なフォームコントロールを生成できます。これらのコントロールには、必要に応じて、囲い込む div 、 label 、コントロールウィジェット、および検証エラーが含まれます。フォームコンテキストでメタデータを使用することにより、このメソッドは各フィールドに適切なコントロールタイプを選択します。内部的に control() は FormHelper の他のメソッドを使います。

Tip

control() メソッドによって生成されたフィールドは、このページでは一般的に "入力" と呼ばれますが、技術的にいえば、 control() メソッドは、 HTML の input 型の要素だけでなく、他の HTML フォーム要素 (select 、 button 、 textarea など) も生成できることに注意してください。

デフォルトでは、 control() メソッドは、次のウィジェットテンプレートを使用します。

'inputContainer' => '<div class="input {{type}}{{required}}">{{content}}</div>'
'input' => '<input type="{{type}}" name="{{name}}"{{attrs}}/>'

検証エラーが発生した場合は、以下も使われます。

'inputContainerError' => '<div class="input {{type}}{{required}} error">{{content}}{{error}}</div>'

作成されたコントロールの型（生成された要素タイプを指定する追加のオプションを指定しない場合）は、モデルの内部で推測され、列のデータ型に依存します。

作成されるコントロールの型は、カラムのデータ型に依存します。

カラムの型

得られたフォームのフィールド

string, uuid (char, varchar, その他)

text

boolean, tinyint(1)

checkbox

decimal

number

float

number

integer

number

text

textarea

text で、名前が password, passwd

password

text で、名前が email

email

text で、名前が tel, telephone, または phone

tel

date

day, month, および year の select

datetime, timestamp

day, month, year, hour, minute, および meridian の select

time

hour, minute, および meridian の select

binary

file

$options パラメーターを使うと、必要な場合に特定のコントロールタイプを選択することができます。

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

Tip

とても些細なことですが、 control() フォームメソッドを使用して特定の要素を生成すると、デフォルトでは div の囲い込みが常に生成されます。特定のフォームメソッド（例えば $this->Form->checkbox('published'); ）を使用して同じタイプの要素を生成すると、ほとんどの場合、 div の囲い込みが生成されません。あなたのニーズに応じて、どちらかを使うことができます。

モデルのフィールドの検証ルールで入力が必須であり、空を許可しない場合は、囲い込む div は、クラス名に required が追加されます。 required オプションを使用して自動的に必須フラグを無効にすることができます。

echo $this->Form->control('title', ['required' => false]);

フォーム全体のブラウザー検証トリガーをスキップするには、 submit() を使って生成する入力ボタンに対して 'formnovalidate' => true オプションを設定したり、 create() のオプションで 'novalidate' => true を設定できます。

たとえば、Users モデルに username (varchar), password (varchar), approved (datetime) および quote (text) のフィールドがあるとします。FormHelper の control() メソッドを使用すると、これらのフォームフィールドすべてに適切なコントロールを作成できます。

echo $this->Form->create($user);
// 以下は、テキスト入力を生成します
echo $this->Form->control('username');
// 以下は、パスワード入力を生成します
echo $this->Form->control('password');
// 'approved' を datetime か timestamp フィールドとみなし、
// 以下は、日・月・年・時・分を生成します
echo $this->Form->control('approved');
// 以下は、テキストエリア要素を生成します
echo $this->Form->control('quote');

echo $this->Form->button('Add');
echo $this->Form->end();

日付フィールドのいくつかのオプションを示すより広範な例:

echo $this->Form->control('birth_dt', [
    'label' => '生年月日',
    'min' => date('Y') - 70,
    'max' => date('Y') - 18,
]);

特定の コントロールのオプション に加えて、選択された (または CakePHP によって推論された) コントロールタイプや HTML 属性 (例えば onfocus) に対応する特定のメソッドによって受け入れられるオプションを指定することができます。

belongsTo または hasOne を使用していて select フィールドを作成する場合は、 Users コントローラーに次のものを追加できます（User belongsTo Group を前提とします）。

$this->set('groups', $this->Users->Groups->find('list'));

その後、ビューテンプレートに以下を追加します。

echo $this->Form->control('group_id', ['options' => $groups]);

belongsToMany で関連付く Groups の select ボックスを作成するには、 UsersController に以下を追加します。

$this->set('groups', $this->Users->Groups->find('list'));

その後、ビューテンプレートに以下を追加します。

echo $this->Form->control('groups._ids', ['options' => $groups]);

モデル名が2つ以上の単語 (たとえば "UserGroup") で構成されている場合、 set() を使用してデータを渡すときは、データを次のように複数形とローワーキャメルケース [https://en.wikipedia.org/wiki/Camel_case#Variations_and_synonyms] で名前を付ける必要があります。

$this->set('userGroups', $this->UserGroups->find('list')->all());

注釈

送信ボタンを生成するために FormHelper::control() を使用しないでください。代わりに submit() を使用してください。

フィールドの命名規則

コントロールウィジェットを作成するときは、フィールドの名前をフォームのエンティティーに一致する属性の後に指定する必要があります。たとえば、 $article エンティティーのフォームを作成した場合、そのプロパティーの名前を付けたフィールドを作成します。例えば title 、 body と published 。

association.fieldname を最初のパラメーターとして渡すことで、関連するモデルや任意のモデルのコントロールを作成できます。

echo $this->Form->control('association.fieldname');

フィールド名のドットは、ネストされたリクエストデータに変換されます。たとえば、 0.comments.body という名前のフィールドを作成した場合、 0[comments][body] のような名前属性が得られます。この規則により、ORM でデータを簡単に保存できます。さまざまなアソシエーションタイプの詳細は、 関連データの入力を作成 セクションにあります。

datetime に関連するコントロールを作成する場合、FormHelper はフィールドのサフィックスを追加します。 year 、 month 、 day 、 hour 、 minute 、または meridian というフィールドが追加されていることがあります。エンティティーがマーシャリングされると、これらのフィールドは自動的に DateTime オブジェクトに変換されます。

コントロールのオプション

FormHelper::control() は、その $options 引数を通して、多数のオプションをサポートしています。 control() 自身のオプションに加えて、生成されたコントロールタイプに対するオプションと HTML 属性を受け付けます。以下は FormHelper::control() で特有のオプションについて説明します。

$options['type'] - 生成するためのウィジェットタイプを指定する文字列。フォームコントロールの作成 にあるフィールド型に加えて、 'file' 、 'password' 、および HTML5 でサポートされているすべてのタイプを作成することもできます。 'type' を指定することで、モデルの設定を上書きして、コントロールのタイプを強制することができます。デフォルトは null 。

例:

echo $this->Form->control('field', ['type' => 'file']);
echo $this->Form->control('email', ['type' => 'email']);

出力結果:

<div class="input file">
    <label for="field">Field</label>
    <input type="file" name="field" value="" id="field" />
</div>
<div class="input email">
    <label for="email">Email</label>
    <input type="email" name="email" value="" id="email" />
</div>

$options['label'] - 文字列の見出しや ラベルのオプション の配列。このキーは、通常は input HTML 要素に付随するラベル内に表示したい文字列に設定することができます。デフォルトは null です。

例:

echo $this->Form->control('name', [
    'label' => 'The User Alias'
]);

出力結果:

<div class="input">
    <label for="name">The User Alias</label>
    <input name="name" type="text" value="" id="name" />
</div>

あるいは、 label 要素の出力を無効にするには、このキーに false を設定します。

例:

echo $this->Form->control('name', ['label' => false]);

出力結果:

<div class="input">
    <input name="name" type="text" value="" id="name" />
</div>

これに配列を設定すると、 label 要素の追加オプションが提供されます。これを行う場合、配列中の text キーを使ってラベルテキストをカスタマイズすることができます。

例:

echo $this->Form->control('name', [
    'label' => [
        'class' => 'thingy',
        'text' => 'The User Alias'
    ]
]);

出力結果:

<div class="input">
    <label for="name" class="thingy">The User Alias</label>
    <input name="name" type="text" value="" id="name" />
</div>

$options['options'] - ここには、アイテムの配列を引数として必要とする radio や select のようなウィジェットのために、生成される要素を含む配列を提供することができます (詳細は、 ラジオボタンの作成 と 選択ピッカーの作成 をご覧ください)。デフォルトは、 null です。

$options['error'] - このキーを使用すると、デフォルトのモデルエラーメッセージを無効にすることができ、たとえば国際化メッセージを設定するために使用できます。エラーメッセージの出力とフィールドクラスを無効にするには、 'error' キーを false に設定してください。デフォルトは null 。

例:

echo $this->Form->control('name', ['error' => false]);

モデルのエラーメッセージを上書きするには、元の検証エラーメッセージと一致するキーを持つ配列を使用します。

例:

$this->Form->control('name', [
    'error' => ['Not long enough' => __('This is not long enough')]
]);

上記のように、モデルにある各検証ルールに対してエラーメッセージを設定することができます。さらに、フォームに国際化メッセージを提供することもできます。

$options['nestedInput'] - チェックボックスとラジオボタンで使用。 input 要素を label 要素の内側か外側に生成するかどうかを制御します。 control() がチェックボックスやラジオボタンを生成する時、これに false を設定して、 label 要素の外側に HTML の input 要素を強制的に生成することができます。

一方、任意のコントロールタイプに対して、これを true に設定することで、生成された input 要素をラベルの中に強制的に入れることができます。これをラジオボタンで変更する場合は、デフォルトの radioWrapper テンプレートも変更する必要があります。生成されるコントロールタイプによっては、デフォルトが true や false になります。

$options['templates'] - この入力に使用するテンプレート。指定したテンプレートは、既に読み込まれたテンプレートの上にマージされます。このオプションは、ロードするテンプレートを含む /config のファイル名 (拡張子を除く) か、使用するテンプレートの配列のいずれかです。

$options['labelOptions'] - これを false に設定すると nestedWidgets の周りのラベルを無効にします。または、 label タグに提供される属性の配列を設定します。

$options['readOnly'] - フォームにて、フィールドを readOnly に設定します。

例:

echo $this->Form->control('name', ['readonly' => true]);

コントロールの特定のタイプを生成

汎用的な control() メソッドに加えて、 FormHelper には様々な種類のコントロールタイプを生成するために個別のメソッドがあります。これらは、コントロールウィジェットそのものを生成するのに使えますが、完全に独自のフォームレイアウトを生成するために label() や error() といった他のメソッドを組み合わせることができます。

特定のコントロールのための共通オプション

さまざまなコントロール要素メソッドは、共通のオプションをサポートしており、使用されるフォームメソッドに応じて、 $options または $attributes 配列の引数の中に指定する必要があります。これらのオプションはすべて、 control() でもサポートされています。繰り返しを減らすために、すべてのコントロールメソッドで共有される共通オプションは次の通りです。

'id' - このキーを設定すると、コントロールの DOM id の値が強制的に設定されます。これにより、設定可能な 'idPrefix' が上書きされます。

'default' コントロールフィールドのデフォルト値を設定します。この値は、フォームに渡されるデータにそのフィールドに関する値が含まれていない場合 (または、一切データが渡されない場合) に使われます。明示的なデフォルト値は、スキーマで定義されたデフォルト値を上書きします。

使用例:

echo $this->Form->text('ingredient', ['default' => 'Sugar']);

select フィールドを持つ例（"Medium" サイズがデフォルトで選択されます）

$sizes = ['s' => 'Small', 'm' => 'Medium', 'l' => 'Large'];
echo $this->Form->select('size', $sizes, ['default' => 'm']);

注釈

checkbox をチェックする目的では default は使えません。その代わり、コントローラーで $this->request->getData() の中の値をセットするか、またはコントロールオプションの checked を true にします。

デフォルト値への代入の際 false を使うのは注意が必要です。 false 値はコントロールフィールドのオプションを無効または除外するために使われます。そのため 'default' => false では値を全く設定しません。代わりに 'default' => 0 を使用してください。

'value' - コントロールフィールドに特定の値を設定するために使用します。これは、Form、Entity、 request->getData() などのコンテキストから注入される可能性のある値を上書きします。

注釈

コンテキストや valuesSource から値を取得しないようにフィールドを設定したい場合、 'value' を '' に設定する必要があります (もしくは null に設定) 。

上記のオプションに加えて、任意の HTML 属性を混在させることができます。特に規定のないオプション名は HTML 属性として扱われ、生成された HTML のコントロール要素に反映されます。

バージョン 3.3.0 で変更: 3.3.0 では、FormHelper は、自動的にデータベーススキーマで定義されたデフォルト値を使用します。 schemaDefault オプションを false に設定することで、この動作を無効にすることができます。

input 要素の作成

FormHelper で利用可能なメソッドには、さらに特定のフォーム要素を作成するためのものがあります。これらのメソッドの多くでは、特別な $options や $attributes パラメーターを指定できます。ただし、この場合、このパラメーターは主に (フォーム要素の DOM id の値のような) HTML タグの属性を指定するために使われます。

テキスト入力の作成

Cake\View\Helper\FormHelper::text(string $name, array $options)

$name - 'Modelname.fieldname' の形式のフィールド名。

$options - 特定のコントロールのための共通オプション や有効な HTML 属性を含むオプション配列。

シンプルな text 型の input HTML 要素を作成します。

例:

echo $this->Form->text('username', ['class' => 'users']);

出力結果:

<input name="username" type="text" class="users">

パスワード入力の作成

Cake\View\Helper\FormHelper::password(string $fieldName, array $options)

$fieldName - 'Modelname.fieldname' の形式のフィールド名。

$options - 特定のコントロールのための共通オプション や有効な HTML 属性を含むオプション配列。

シンプルな password 型の input 要素を作成します。

例:

echo $this->Form->password('password');

出力結果:

<input name="password" value="" type="password">

非表示入力の作成

Cake\View\Helper\FormHelper::hidden(string $fieldName, array $options)

$fieldName - 'Modelname.fieldname' の形式のフィールド名。

$options - 特定のコントロールのための共通オプション や有効な HTML 属性を含むオプション配列。

非表示のフォーム入力を作成します。

例:

echo $this->Form->hidden('id');

出力結果:

<input name="id" type="hidden" />

テキストエリアの作成

Cake\View\Helper\FormHelper::textarea(string $fieldName, array $options)

$fieldName - 'Modelname.fieldname' の形式のフィールド名。

$options - 特定のコントロールのための共通オプション やテキストエリア特有のオプション (下記参照) と、有効な HTML 属性を含むオプション配列。

textarea コントロールフィールドを作成します。使用されるデフォルトのウィジェットテンプレートは、

'textarea' => '<textarea name="{{name}}"{{attrs}}>{{value}}</textarea>'

例:

echo $this->Form->textarea('notes');

出力結果:

<textarea name="notes"></textarea>

フォームが編集されると（すなわち、配列 $this->request->getData() に User モデルに渡すために保存された情報が含まれている場合）、生成される HTML には notes フィールドに対応する値が自動的に含まれます。

例:

<textarea name="notes" id="notes">
このテキストは編集されます。
</textarea>

テキストエリアのオプション

特定のコントロールのための共通オプション に加えて、 textarea() はいくつかの固有のオプションをサポートします。

'escape' - テキストエリアの内容をエスケープするかどうかを指定します。デフォルトは true です。

例:

echo $this->Form->textarea('notes', ['escape' => false]);
// もしくは....
echo $this->Form->control('notes', ['type' => 'textarea', 'escape' => false]);

'rows', 'cols' - これらの2つのキーを使用して、 textarea フィールドの行数と列数を指定する HTML 属性を設定することができます。

例:

echo $this->Form->textarea('comment', ['rows' => '5', 'cols' => '5']);

出力結果:

<textarea name="comment" cols="5" rows="5">
</textarea>

セレクト、チェックボックス、ラジオコントロールの作成

これらのコントロールは、いくつかの共通点といくつかのオプションを共有し、それらは簡単に参照するために、このサブセクションで全てグループ化します。

セレクト、チェックボックス、ラジオに関するオプション

select() 、 checkbox() そして radio() によって共有されるオプションは次の通りです。 (各メソッドの独自のセクションには、そのメソッド特有のオプションが記述されています。)

'value' - 影響を受ける要素の値を設定または選択します。

チェックボックスの場合、 input 要素に割り当てられた HTML の 'value' 属性を、値として提供するものに設定します。

ラジオボタンまたは選択ピッカーの場合は、フォームが描画されるときに選択される要素を定義します (この場合、 'value' は有効で存在する要素の値を割り当てなければなりません)。 date() 、 time() 、 dateTime() のようなセレクト型コントロールと組み合わせて使用することもできます。

echo $this->Form->time('close_time', [
    'value' => '13:30:00'
]);

注釈

date() および dateTime() コントロールの 'value' キーには、 UNIX タイムスタンプまたは DateTime オブジェクトを使用することもできます。

multiple 属性を true に設定した select コントロールでは、デフォルトで選択したい値の配列を使うことができます。

// 値に 1 と 3 を持つ HTML <option> 要素が事前選択されて描画されます。
echo $this->Form->select(
    'rooms',
    [1, 2, 3, 4, 5],
    [
        'multiple' => true,
        'value' => [1, 3]
    ]
);

'empty' - radio() と select() に適用します。デフォルトは false です。

radio() に渡して true を設定すると、最初のラジオボタンとして追加の入力要素を作成し、値を '' に、ラベルを 'empty' にします。ラベルキャプションを制御する場合は、このオプションを文字列に設定します。

select メソッドに渡されると、ドロップダウンリストに空の値を持つ空白の HTML option 要素が作成されます。空の値を空の option の代わりに表示させたい場合は、 'empty' に文字列を渡します。

echo $this->Form->select(
    'field',
    [1, 2, 3, 4, 5],
    ['empty' => '(一つ選ぶ)']
);

出力結果:

<select name="field">
    <option value="">(一つ選ぶ)</option>
    <option value="0">1</option>
    <option value="1">2</option>
    <option value="2">3</option>
    <option value="3">4</option>
    <option value="4">5</option>
</select>

'hiddenField' - チェックボックスとラジオボタンの場合、デフォルトでは、メインの要素とともに hidden input 要素も作成されます。そのため、値の指定がなくても、 $this->request->getData() の中のキーは必ず存在します。その値のデフォルトは、チェックボックスの場合は 0 、ラジオボタンの場合は '' です。

デフォルト出力の例:

<input type="hidden" name="published" value="0" />
<input type="checkbox" name="published" value="1" />

これは 'hiddenField' を false とすることで無効にできます。

echo $this->Form->checkbox('published', ['hiddenField' => false]);

出力結果:

<input type="checkbox" name="published" value="1">

フォーム上に複数のコントロールブロックを作成してグループ化する場合は、最初のコントロールを除くすべての入力で、このパラメーターを false に設定する必要があります。 hidden 入力がページ上の複数の場所にある場合は、入力値の最後のグループだけが保存されます。

この例では Tertiary Colors だけが渡され、Primary Colors は上書きされます。

<h2>Primary Colors</h2>
<input type="hidden" name="color" value="0" />
<label for="color-red">
    <input type="checkbox" name="color[]" value="5" id="color-red" />
    Red
</label>

<label for="color-blue">
    <input type="checkbox" name="color[]" value="5" id="color-blue" />
    Blue
</label>

<label for="color-yellow">
    <input type="checkbox" name="color[]" value="5" id="color-yellow" />
    Yellow
</label>

<h2>Tertiary Colors</h2>
<input type="hidden" name="color" value="0" />
<label for="color-green">
    <input type="checkbox" name="color[]" value="5" id="color-green" />
    Green
</label>
<label for="color-purple">
    <input type="checkbox" name="color[]" value="5" id="color-purple" />
    Purple
</label>
<label for="color-orange">
    <input type="checkbox" name="color[]" value="5" id="color-orange" />
    Orange
</label>

2番目の入力グループで 'hiddenField' を無効にすると、この動作を防ぐことができます。

'N' のように 0 以外の別の hidden フィールド値を設定することができます。

echo $this->Form->checkbox('published', [
    'value' => 'Y',
    'hiddenField' => 'N',
]);

オプションの作成にコレクションを使用

オプション配列の作成に Collection クラスを使うことができます。この方法は、すでにエンティティーのコレクションを持っていて、そこから select 要素を作成したい場合に理想的です。

combine メソッドを使って、基本的なオプションの配列を作ることができます。

$options = $examples->combine('id', 'name');

配列を拡張して特別な属性を追加することもできます。以下は、 コレクションの map メソッドを使って option 要素にデータ属性を作成します。

$options = $examples->map(function ($value, $key) {
    return [
        'value' => $value->id,
        'text' => $value->name,
        'data-created' => $value->created
    ];
});

チェックボックスの作成

Cake\View\Helper\FormHelper::checkbox(string $fieldName, array $options)

$fieldName - 'Modelname.fieldname' の形式のフィールド名。

$options - 上記の 特定のコントロールのための共通オプション またはセレクト、チェックボックス、ラジオに関するオプション 、チェックボックス特有のオプション (下記参照) と有効な HTML 属性を含むオプション配列。

checkbox フォーム要素を作成します。使用されるウィジェットテンプレートは、

'checkbox' => '<input type="checkbox" name="{{name}}" value="{{value}}"{{attrs}}>'

チェックボックスのオプション

'checked' - このチェックボックスをオンにするかどうかを示すブール値。デフォルトは false です。

'disabled' - 入力不可のチェックボックスを作成。

このメソッドは、関連する隠しフォームの input 要素も生成し、指定されたフィールドのデータの送信を強制します。

例:

echo $this->Form->checkbox('done');

出力結果:

<input type="hidden" name="done" value="0">
<input type="checkbox" name="done" value="1">

$options 配列を使って checkbox の値を指定することもできます。

例:

echo $this->Form->checkbox('done', ['value' => 555]);

出力結果:

<input type="hidden" name="done" value="0">
<input type="checkbox" name="done" value="555">

FormHelper で hidden 入力を作成したくない場合は、 'hiddenField' を使います。

例:

echo $this->Form->checkbox('done', ['hiddenField' => false]);

出力結果:

<input type="checkbox" name="done" value="1">

ラジオボタンの作成

Cake\View\Helper\FormHelper::radio(string $fieldName, array $options, array $attributes)

$fieldName - 'Modelname.fieldname' の形式のフィールド名。

$options - 少なくともラジオボタンのラベルを含むオプション配列。値や HTML 属性を含むこともできます。この配列がない場合、メソッドは hidden 入力を生成する ('hiddenField' が true の場合)、または全く要素がない ('hiddenField' が false の場合) かのいずれかです。

$attributes - 特定のコントロールのための共通オプション またはセレクト、チェックボックス、ラジオに関するオプション 、ラジオボタン特有の属性 (下記参照)と有効な HTML 属性を含むオプション配列。

radio ボタン入力を作成します。使用されるデフォルトのウィジェットテンプレートは、

'radio' => '<input type="radio" name="{{name}}" value="{{value}}"{{attrs}}>'
'radioWrapper' => '{{label}}'

ラジオボタンの属性

'label' - ウィジェットのラベルを表示するかどうかを示すブール値、または、全てのラベルに適用する属性の配列。 class 属性が定義されている場合、チェックされたボタンの class 属性に selected が追加されます。デフォルトは true 。

'hiddenField' - true に設定すると、 '' の値を持つ非表示の入力がインクルードされます。これは、非連続的なラジオセットを作成する場合に便利です。デフォルトは true 。

'disabled' - すべてのラジオボタンを無効にするには true または 'disabled' に設定します。デフォルトは false 。

$options 引数を通してラジオボタンのラベルの見出しを指定してください。

例:

$this->Form->radio('gender', ['Masculine','Feminine','Neuter']);

出力結果:

<input name="gender" value="" type="hidden">
<label for="gender-0">
    <input name="gender" value="0" id="gender-0" type="radio">
    Masculine
</label>
<label for="gender-1">
    <input name="gender" value="1" id="gender-1" type="radio">
    Feminine
</label>
<label for="gender-2">
    <input name="gender" value="2" id="gender-2" type="radio">
    Neuter
</label>

一般に $options は単純な キー => 値 のペアです。ただし、カスタム属性をラジオボタンに配置する必要がある場合は、拡張形式を使用することができます。

例:

echo $this->Form->radio(
    'favorite_color',
    [
        ['value' => 'r', 'text' => 'Red', 'style' => 'color:red;'],
        ['value' => 'u', 'text' => 'Blue', 'style' => 'color:blue;'],
        ['value' => 'g', 'text' => 'Green', 'style' => 'color:green;'],
    ]
);

出力結果:

<input type="hidden" name="favorite_color" value="">
<label for="favorite-color-r">
    <input type="radio" name="favorite_color" value="r" style="color:red;" id="favorite-color-r">
    Red
</label>
<label for="favorite-color-u">
    <input type="radio" name="favorite_color" value="u" style="color:blue;" id="favorite-color-u">
    Blue
</label>
<label for="favorite-color-g">
    <input type="radio" name="favorite_color" value="g" style="color:green;" id="favorite-color-g">
    Green
</label>

選択ピッカーの作成

Cake\View\Helper\FormHelper::select(string $fieldName, array $options, array $attributes)

$fieldName - 'Modelname.fieldname' の形式のフィールド名。これは、 select 要素の name 属性を提供します。

$options - 選択ピッカーの項目のリストを含むオプション配列。この配列がない場合、メソッドは、中に option 要素が一つもない空の select HTML 要素のみを生成します。

$attributes - 特定のコントロールのための共通オプション またはセレクト、チェックボックス、ラジオに関するオプション または select 特有のオプション (下記参照)と、有効な HTML 属性を含むオプション配列。

$options 配列の項目で設定された select 要素を作成します。 $attributes['value'] が提供された場合、指定された値を持つ HTML option 要素が選択ピッカーが描画される際に選択されて表示されます。

デフォルトでは、 select は次のウィジェットテンプレートを使用します。

'select' => '<select name="{{name}}"{{attrs}}>{{content}}</select>'
'option' => '<option value="{{value}}"{{attrs}}>{{text}}</option>'

以下も使用します。

'optgroup' => '<optgroup label="{{label}}"{{attrs}}>{{content}}</optgroup>'
'selectMultiple' => '<select name="{{name}}[]" multiple="multiple"{{attrs}}>{{content}}</select>'

選択ピッカーの属性

'multiple' - true をセットすると、選択ピッカー内で複数選択ができます。 'checkbox' をセットすると、複数チェックボックスが代わりに作成されます。デフォルトは null です。

'escape' - ブール値。 true の場合、選択ピッカー内の option 要素の内容はエンコードされた HTML エンティティーになります。デフォルトは true です。

'val' - 選択ピッカーで値を事前に選択できるようにします。

'disabled' - disabled 属性を制御します。 true をセットした場合、選択ピッカー全体を無効にします。配列をセットした場合、配列に含まれている値を持つ特定の option のみ無効にします。

$options 引数は、 select コントロールの option 要素の内容を手動で指定できます。

例:

echo $this->Form->select('field', [1, 2, 3, 4, 5]);

出力結果:

<select name="field">
    <option value="0">1</option>
    <option value="1">2</option>
    <option value="2">3</option>
    <option value="3">4</option>
    <option value="4">5</option>
</select>

$options の配列はキーと値のペアとしても指定することができます。

例:

echo $this->Form->select('field', [
    'Value 1' => 'Label 1',
    'Value 2' => 'Label 2',
    'Value 3' => 'Label 3'
]);

出力結果:

<select name="field">
    <option value="Value 1">Label 1</option>
    <option value="Value 2">Label 2</option>
    <option value="Value 3">Label 3</option>
</select>

optgroup 付きで select を生成したい場合は、データを階層形式 (ネストした配列) で渡すだけです。これは複数のチェックボックスとラジオボタンでも機能しますが、 optgroup の代わりに fieldset 要素で囲みます。

例:

$options = [
    'Group 1' => [
        'Value 1' => 'Label 1',
        'Value 2' => 'Label 2'
    ],
    'Group 2' => [
        'Value 3' => 'Label 3'
    ]
];
echo $this->Form->select('field', $options);

出力結果:

<select name="field">
    <optgroup label="Group 1">
        <option value="Value 1">Label 1</option>
        <option value="Value 2">Label 2</option>
    </optgroup>
    <optgroup label="Group 2">
        <option value="Value 3">Label 3</option>
    </optgroup>
</select>

option タグ内で HTML 属性を生成するには:

$options = [
    [ 'text' => 'Description 1', 'value' => 'value 1', 'attr_name' => 'attr_value 1' ],
    [ 'text' => 'Description 2', 'value' => 'value 2', 'attr_name' => 'attr_value 2' ],
    [ 'text' => 'Description 3', 'value' => 'value 3', 'other_attr_name' => 'other_attr_value' ],
];
echo $this->Form->select('field', $options);

出力結果:

<select name="field">
    <option value="value 1" attr_name="attr_value 1">Description 1</option>
    <option value="value 2" attr_name="attr_value 2">Description 2</option>
    <option value="value 3" other_attr_name="other_attr_value">Description 3</option>
</select>

属性による選択ピッカーの制御

$attributes パラメーター内の特定のオプションを使用することにより、 select() メソッドの特定の振る舞いを制御することができます。

'empty' - $attributes 引数の中で 'empty' キーを true にセットすると (デフォルトの値は false)、ドロップダウンリストの先頭に空の値の空白オプションを追加できます。

例:

$options = ['M' => 'Male', 'F' => 'Female'];
echo $this->Form->select('gender', $options, ['empty' => true]);

出力結果:

<select name="gender">
    <option value=""></option>
    <option value="M">Male</option>
    <option value="F">Female</option>
</select>

'escape' - select() メソッドは 'escape' という属性が使用でき、ブール値を受け取り、HTML エンティティーに select オプションの内容をエンコードするかどうかを決定します。

例:

// これで、各オプション要素の内容の HTML エンコードを止められます
$options = ['M' => 'Male', 'F' => 'Female'];
echo $this->Form->select('gender', $options, ['escape' => false]);

'multiple' - true にセットすると、選択ピッカーは複数選択ができます。

例:

echo $this->Form->select('field', $options, ['multiple' => true]);

または、関連するチェックボックスのリストを出力するために 'multiple' を 'checkbox' に設定します。

$options = [
    'Value 1' => 'Label 1',
    'Value 2' => 'Label 2'
];
echo $this->Form->select('field', $options, [
    'multiple' => 'checkbox'
]);

出力結果:

<input name="field" value="" type="hidden">
<div class="checkbox">
    <label for="field-1">
        <input name="field[]" value="Value 1" id="field-1" type="checkbox">
        Label 1
    </label>
</div>
<div class="checkbox">
    <label for="field-2">
        <input name="field[]" value="Value 2" id="field-2" type="checkbox">
        Label 2
    </label>
</div>

'disabled' - このオプションを設定して、すべてまたは一部の select の option 項目を無効にすることができます。すべての項目を無効にするには、 'disabled' に true を設定します。特定の項目のみを無効にするには、無効にする項目をキーに含む配列を 'disabled' に設定してください。

すべてのチェックボックスを無効にするには disabled を true にします。

例:

$options = [
    'M' => 'Masculine',
    'F' => 'Feminine',
    'N' => 'Neuter'
];
echo $this->Form->select('gender', $options, [
    'disabled' => ['M', 'N']
]);

出力結果:

<select name="gender">
    <option value="M" disabled="disabled">Masculine</option>
    <option value="F">Feminine</option>
    <option value="N" disabled="disabled">Neuter</option>
</select>

このオプションは 'multiple' が 'checkbox' に設定されている場合にも有効です。

$options = [
    'Value 1' => 'Label 1',
    'Value 2' => 'Label 2'
];
echo $this->Form->select('field', $options, [
    'multiple' => 'checkbox',
    'disabled' => ['Value 1']
]);

出力結果:

<input name="field" value="" type="hidden">
<div class="checkbox">
    <label for="field-1">
        <input name="field[]" disabled="disabled" value="Value 1" type="checkbox">
        Label 1
    </label>
</div>
<div class="checkbox">
    <label for="field-2">
        <input name="field[]" value="Value 2" id="field-2" type="checkbox">
        Label 2
    </label>
</div>

ファイル入力の作成

Cake\View\Helper\FormHelper::file(string $fieldName, array $options)

$fieldName - 'Modelname.fieldname' の形式のフィールド名。

$options - 特定のコントロールのための共通オプション や有効な HTML 属性を含むオプション配列。

フォームの中にファイルアップロードフィールドを作成します。デフォルトで使用されるウィジェットテンプレートは:

'file' => '<input type="file" name="{{name}}"{{attrs}}>'

フォームにファイルアップロードフィールドを追加するためには、まずフォームの enctype に 'multipart/form-data' がセットされていることを確認してください。

まずは、次のように create() メソッドを使用してください。

echo $this->Form->create($document, ['enctype' => 'multipart/form-data']);
// または
echo $this->Form->create($document, ['type' => 'file']);

次にフォームのビューファイルに以下のいずれかを追加します。

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

// または
echo $this->Form->file('submittedfile');

注釈

HTML 自体の制限により、'file' タイプの入力フィールドにデフォルト値を設定することはできません。フォームを表示するたびに、内部の値は空に設定されます。

フォームの送信に際して file フィールドは、フォームを受信しようとしているスクリプトに対して拡張された data 配列を提供します。

CakePHP が Windows サーバー上にインストールされている場合、上記の例について、送信されるデータ配列内の値は次のように構成されます (Unix 環境では 'tmp_name' が異なったパスになります)。

$this->request->data['submittedfile']

// 次の配列を含みます:
[
    'name' => 'conference_schedule.pdf',
    'type' => 'application/pdf',
    'tmp_name' => 'C:/WINDOWS/TEMP/php1EE.tmp',
    'error' => 0, // Windows の場合、文字列になります。
    'size' => 41737,
];

この配列は PHP 自身によって生成されます。PHP が file フィールドを通してデータをどう処理しているのかについては、 PHP マニュアルのファイルアップロードのセクションをご覧ください [https://secure.php.net/features.file-upload] 。

注釈

$this->Form->file() を使う場合、 $this->Form->create() の中の 'type' オプションを 'file' に設定することで、フォームのエンコーディングのタイプを設定できます。

日付と時刻に関するコントロールの作成

日時関連の方法は、多くの共通の特性とオプションを共有しているため、このサブセクションにまとめられています。

日付と時刻のコントロールの共通オプション

これらのオプションは、日付と時刻に関するコントロールに共通します。

'empty' - true の場合、余分の空の option HTML 要素が、 select の中のリストの先頭に追加されます。文字列の場合、その文字列は空の要素として表示されます。デフォルトは true です。

'default' | value - 2つのいずれかを使用して、フィールドに表示されるデフォルト値を設定します。フィールド名と一致する $this->request->getData() の値は、この値を上書きします。デフォルトが指定されていない場合、 time() が使用されます。

'year', 'month', 'day', 'hour', 'minute', 'second', 'meridian' - これらのオプションを使用すると、コントロール要素が生成されるかどうか制御できます。これらのオプションを false にセットすることにより、特定の選択ピッカーの生成を無効にすることができます (デフォルトでは、使用されたメソッドの中で描画されます) 。さらに、各オプションでは、HTML 属性を指定した select 要素に渡すことができます。

日付関連コントロールのオプション

これらのオプションは、日付関連のメソッド、つまり year() 、 month() 、 day() 、 dateTime() そして date() に関連しています。

'monthNames' - false の場合は、選択ピッカーの月の表示でテキストの代わりに2桁の数字が使用されます。配列をセットした場合 (例 ['01' => 'Jan', '02' => 'Feb', ...])、指定された配列が使用されます。

'min' - 年の select フィールドで使用される最小の年。

'max' - 年の select フィールドで使用される最大の年。

'order' - 年選択ピッカー内の年の値の順序。利用可能な値は 'asc' と 'desc' 。デフォルトは 'desc' です。

'year', 'month', 'day' - これらのオプションを使用すると、コントロール要素が生成されるかどうか制御できます。これらのオプションを false にセットすることにより、特定の選択ピッカーの生成を無効にすることができます (デフォルトでは、使用されたメソッドの中で描画されます) 。さらに、各オプションでは、HTML 属性を指定した select 要素に渡すことができます。

時刻関連コントロールのオプション

これらのオプションは、時刻関連のメソッド、 hour() 、 minute() 、 second() 、 dateTime() そして time() に関連しています。

'interval' - 分選択ピッカーの option 要素の中に表示される分の値の間隔。デフォルトは 1 です。

'round' - 値が一定の間隔にきちんと収まらないときに、いずれかの方向に丸めるようにしたい場合は、 up または down に設定します。デフォルトは null です。

timeFormat - dateTime() と time() に適用されます。選択ピッカーで使用する時刻の書式は、 12 または 24 のいずれかです。このオプションに 24 以外の何かをセットした場合、書式は自動的に 12 がセットされ、秒選択ピッカーの右側に meridian 選択ピッカーが自動的に表示されます。デフォルトは 24 です。

format - hour() に適用されます。選択ピッカーで使用する時刻の書式は、 12 または 24 のいずれかです。 12 をセットした場合、 meridian 選択ピッカーは自動的に表示されません。それを追加するか、フォームコンテキストから適切な期間を推論する方法を提供するかは、あなた次第です。デフォルトは 24 です。

second - dateTime() と time() に適用されます。秒を有効にするために true に設定します。デフォルトは false です。

'hour', 'minute', 'second', 'meridian' - これらのオプションを使用すると、コントロール要素が生成されるかどうか制御できます。これらのオプションを false にセットすることにより、特定の選択ピッカーの生成を無効にすることができます (デフォルトでは、使用されたメソッドの中で描画されます) 。さらに、各オプションでは、HTML 属性を指定した select 要素に渡すことができます。

日時入力の作成

Cake\View\Helper\FormHelper::dateTime($fieldName, $options = [])

$fieldName - select 要素の HTML name 属性のプレフィックスとして使用される文字列。

$options - 特定のコントロールのための共通オプション または日時特有のオプション (下記参照)、そして有効な HTML 属性を含むオプション配列。

日付と時刻の select 要素のセットを生成します。

コントロールの順序、およびコントロール間の要素/内容を制御するには、 dateWidget テンプレートを上書きします。デフォルトで dateWidget テンプレートは:

{{year}}{{month}}{{day}}{{hour}}{{minute}}{{second}}{{meridian}}

オプションを指定せずにメソッドを呼び出すと、デフォルトでは、年（4桁）、月（英語の完全名）、曜日（数値）、時間（数値）、分（数値）の5つの選択ピッカーが生成されます。

例:

<?= $this->form->dateTime('registered') ?>

出力結果:

<select name="registered[year]">
    <option value="" selected="selected"></option>
    <option value="2022">2022</option>
    ...
    <option value="2012">2012</option>
</select>
<select name="registered[month]">
    <option value="" selected="selected"></option>
    <option value="01">January</option>
    ...
    <option value="12">December</option>
</select>
<select name="registered[day]">
    <option value="" selected="selected"></option>
    <option value="01">1</option>
    ...
    <option value="31">31</option>
</select>
<select name="registered[hour]">
    <option value="" selected="selected"></option>
    <option value="00">0</option>
    ...
    <option value="23">23</option>
</select>
<select name="registered[minute]">
    <option value="" selected="selected"></option>
    <option value="00">00</option>
    ...
    <option value="59">59</option>
</select>

特定の select ボックスにカスタムクラス/属性を含む datetime コントロールを作成するには、 $options 引数の中で各コンポーネントのオプションの配列として指定します。

例:

<?= $this->form->dateTime('registered', ['value' => new DateTime()]) ?>

これは、次の2つの選択ピッカーを作成します。

<input type="datetime-local" name="registered" value="2019-02-08T18:20:10" />

日付コントロールの作成

Cake\View\Helper\FormHelper::date($fieldName, $options = [])

$fieldName - select 要素の HTML name 属性のプレフィックスとして使用される文字列。

$options - 特定のコントロールのための共通オプション 、 日付と時刻のコントロールの共通オプション 、適用可能な 時刻関連コントロールのオプション 、そして有効な HTML 属性を含むオプション配列。

デフォルトでは、年（4桁）、月（英語の完全名）、日（数値）の値が設定された 3つの選択ピッカーを作成します。

生成された select 要素をさらに制御するには、オプションを追加します。

例:

// 今年が 2017 年だと仮定すると、これは日ピッカーを無効にし、年ピッカーの空の
// オプションを削除し、最低年を制限し、年の HTML 属性を追加し、
// 月の文字列の 'empty' オプションを追加し、月を数値に変更します。
<?php
    echo $this->Form->date('registered', [
        'minYear' => 2018,
        'monthNames' => false, // 月は数字で表示されます。
        'empty' => [
            'year' => false, // 年選択コントロールは空の値のオプションを持ちません。
            'month' => 'Choose month...' // しかしながら、月選択コントロールは持ちます。
        ],
        'day' => false, // 日付選択コントールを表示しない。
        'year' => [
            'class' => 'cool-years',
            'title' => 'Registration Year'
        ]
    ]);
?>

出力結果:

<input type="date" name="registered" />

時間コントロールの作成

Cake\View\Helper\FormHelper::time($fieldName, $options = [])

$fieldName - select 要素の HTML name 属性のプレフィックスとして使用される文字列。

$options - 特定のコントロールのための共通オプション 、 日付と時刻のコントロールの共通オプション 、適用可能な 時刻関連コントロールのオプション 、そして有効な HTML 属性を含むオプション配列。

デフォルトでは、 24 時間と 60 分の値が入力された２つの select 要素 (hour と minute) を生成します。さらに、HTML 属性は、特定のコンポーネントごとに $options で指定することができます。 $options['empty'] が false の場合、選択ピッカーは空のデフォルトオプションを含みません。

たとえば、15 分単位で選択できる時間範囲を作成し、各 select ボックスにクラスを適用するには、次のようにします。

echo $this->Form->time('released', [
    'interval' => 15,
    'hour' => [
        'class' => 'foo-class',
    ],
    'minute' => [
        'class' => 'bar-class',
    ],
]);

これは、次の2つの選択ピッカーを作成します。

echo $this->Form->time('released');

年コントロールの作成

Cake\View\Helper\FormHelper::year(string $fieldName, array $options = [])

$fieldName - select 要素の HTML name 属性のプレフィックスとして使用される文字列。

$options - 特定のコントロールのための共通オプション 、 日付と時刻のコントロールの共通オプション 、適用可能な 時刻関連コントロールのオプション 、そして有効な HTML 属性を含むオプション配列。

min から max （これらのオプションが提供されているとき）、または今日から数えて-5年から+5年までの値を持つ select 要素を作成します。さらに、HTML 属性は、 $options で指定することができます。 $options ['empty'] が false の場合、選択ピッカーはリスト内に空の項目を含みません。

たとえば、2000 年から今年までの年を作成するには、次のようにします。

echo $this->Form->year('purchased', [
    'min' => 2000,
    'max' => date('Y')
]);

2009 年だった場合は、次のようになるでしょう。

<input type="time" name="released" />

月コントロールの作成

Cake\View\Helper\FormHelper::month(string $fieldName, array $attributes)

$fieldName - select 要素の HTML name 属性のプレフィックスとして使用される文字列。

$attributes - 特定のコントロールのための共通オプション 、 日付と時刻のコントロールの共通オプション 、適用可能な 時刻関連コントロールのオプション 、そして有効な HTML 属性を含むオプション配列。

月の名前を列挙した select 要素を作成します。

例:

echo $this->Form->month('mob');

Will output:

<input type="month" name="mob" />

時間コントロールの作成

Cake\View\Helper\FormHelper::year(string $fieldName, array $options = [])

$fieldName - select 要素の HTML name 属性のプレフィックスとして使用される文字列。

$attributes - 特定のコントロールのための共通オプション 、 日付と時刻のコントロールの共通オプション 、適用可能な 時刻関連コントロールのオプション 、そして有効な HTML 属性を含むオプション配列。

時を列挙した select 要素を作成します。

format オプションを使用して、12 時間または 24 時間のピッカーを作成することができます。

echo $this->Form->hour('created', [
    'format' => 12
]);
echo $this->Form->hour('created', [
    'format' => 24
]);

分コントロールの作成

Cake\View\Helper\FormHelper::minute(string $fieldName, array $attributes)

$fieldName - select 要素の HTML name 属性のプレフィックスとして使用される文字列。

$attributes - 特定のコントロールのための共通オプション 、 日付と時刻のコントロールの共通オプション 、適用可能な 時刻関連コントロールのオプション 、そして有効な HTML 属性を含むオプション配列。

分の値を列挙した select 要素を作成します。 interval オプションを使用して特定の値のみを含む選択ピッカーを作成することができます。

たとえば、10 分ずつ増やしたい場合は、次のようにします。

// ビューテンプレートファイルの中で
echo $this->Form->minute('arrival', [
    'interval' => 10
]);

これは、以下を出力します。

<select name="purchased">
    <option value=""></option>
    <option value="2009">2009</option>
    <option value="2008">2008</option>
    <option value="2007">2007</option>
    <option value="2006">2006</option>
    <option value="2005">2005</option>
    <option value="2004">2004</option>
    <option value="2003">2003</option>
    <option value="2002">2002</option>
    <option value="2001">2001</option>
    <option value="2000">2000</option>
</select>

ラベルの作成

Cake\View\Helper\FormHelper::label(string $fieldName, string $text, array $options)

$fieldName - 'Modelname.fieldname' の形式のフィールド名。

$text - ラベルの見出しテキストを指定するためのオプション文字列。

$options - オプション。特定のコントロールのための共通オプション と有効な HTML 属性を含む配列。

label 要素を作成します。引数の $fieldName は、要素の HTML for 属性を生成するために使われます。 $text が未定義の場合、 $fieldName はラベルの text 属性を変えるために使われます。

例:

echo $this->Form->label('name');
echo $this->Form->label('name', 'Your username');

出力結果:

<label for="name">Name</label>
<label for="name">Your username</label>

第３パラメーター $options に id や class を設定できます。

echo $this->Form->label('name', null, ['id' => 'user-label']);
echo $this->Form->label('name', 'Your username', ['class' => 'highlight']);

出力結果:

<label for="name" id="user-label">Name</label>
<label for="name" class="highlight">Your username</label>

エラーの表示と確認

FormHelper は、フィールドエラーを簡単にチェックしたり、必要に応じてカスタマイズされたエラーメッセージを表示できる、いくつかのメソッドを公開しています。

エラーの表示

Cake\View\Helper\FormHelper::error(string $fieldName, mixed $text, array $options)

$fieldName - 'Modelname.fieldname' の形式のフィールド名。

$text - オプション。エラーメッセージを提供する文字列または配列。配列の場合、 キー名 => メッセージのハッシュになります。デフォルトは null 。

$options - 'escape' キーのブール値のみを含みます。これは、エラーメッセージの内容を HTML エスケープするかどうかを定義します。デフォルトは true です。

検証エラーが発生した際に、与えられたフィールドの $text で指定された、検証エラーメッセージを表示します。フィールドの $text がない場合、そのフィールドのデフォルトの検証エラーメッセージが使用されます。

次のテンプレートウィジェットを使います。

'error' => '<div class="error-message">{{content}}</div>'
'errorList' => '<ul>{{content}}</ul>'
'errorItem' => '<li>{{text}}</li>'

'errorList' と 'errorItem' テンプレートは、１つのフィールドに複数のエラーメッセージを書式化するために使用されます。

例:

// TicketsTable に 'notEmpty' 検証ルールがある場合:
public function validationDefault(Validator $validator)
{
    $validator
        ->requirePresence('ticket', 'create')
        ->notEmpty('ticket');
}

// そして、 templates/Tickets/add.php の中が次のような場合:
echo $this->Form->text('ticket');

if ($this->Form->isFieldError('ticket')) {
    echo $this->Form->error('ticket', 'Completely custom error message!');
}

もし、 Ticket フィールドの値を指定せずにフォームの Submit ボタンをクリックした場合、フォームは次のように出力されます。

<input name="ticket" class="form-error" required="required" value="" type="text">
<div class="error-message">Completely custom error message!</div>

注釈

control() を使用している時、デフォルトではエラーは描画されますので、 isFieldError() を使用したり、手動で error() を呼び出す必要はありません。

Tip

あるモデルのフィールドを使用して、 control() で複数のフォームフィールドを生成し、それぞれ同じ検証エラーメッセージを表示させたい場合、それぞれの検証ルール の中でカスタムエラーメッセージを定義する方が良いでしょう。

エラーの確認

Cake\View\Helper\FormHelper::isFieldError(string $fieldName)

$fieldName - 'Modelname.fieldname' の形式のフィールド名。

指定された $fieldName に有効な検証エラーがある場合は true を返します。そうでなければ fales を返します。

例:

if ($this->Form->isFieldError('gender')) {
    echo $this->Form->error('gender');
}

HTML5 検証メッセージにバリデーションメッセージを表示

autoSetCustomValidity FormHelper オプションが true に設定されている場合、デフォルトのブラウザーの HTML5 必須メッセージの代わりに、フィールドの必須および notBlank バリデーションメッセージに対するエラーメッセージが使用されます。このオプションを有効にすると、フィールドに onvalid と oninvalid イベント属性が追加されます。例えば、

<input type="text" name="field" required onvalid="this.setCustomValidity('')" oninvalid="this.setCustomValidity('Custom notBlank message')" />

カスタム Javascript を使用してこれらのイベントを手動で設定したい場合は、 autoSetCustomValidity オプションを false に設定して、代わりに 特別な customValidityMessage テンプレート変数を使用することができます。このテンプレート変数はフィールドが必須の場合に追加されます。

// テンプレート例
[
    'input' => '<input type="{{type}}" name="{{name}}" data-error-message="{{customValidityMessage}}" {{attrs}}/>',
]

// このような input が作成されます
<input type="text" name="field" required data-error-message="Custom notBlank message" />

それから Javascript を使って onvalid と oninvalid イベントを好きなように設定できます。

ボタンと submit 要素の作成

Submit 要素の作成

Cake\View\Helper\FormHelper::submit(string $caption, array $options)

$caption - ボタンのテキスト見出しまたは画像へのパスを提供するオプション文字列。デフォルトは、 'Submit' です。

$options - 特定のコントロールのための共通オプション 、または submit 特有のオプション (下記参照) 。

$caption を値としてもつ submit タイプの input 要素を作成します。指定された $caption が画像の URL である場合 (つまり、 '://' を含む文字列または、拡張子 '.jpg, .jpe, .jpeg, .gif' を含む場合)、画像の送信ボタンが生成され、指定された画像が存在する場合は、それを使用します。最初の文字が '/' の場合、画像は webroot からの相対パスになり、最初の文字が '/' ではない場合、画像は webroot/img からの相対パスになります。

デフォルトで次のウィジェットテンプレートを使用します。

'inputSubmit' => '<input type="{{type}}"{{attrs}}/>'
'submitContainer' => '<div class="submit">{{content}}</div>'

Submit のオプション

'type' - リセットボタンを生成するためにこのオプションに 'reset' を設定します。デフォルトは 'submit' です。

'templateVars' - input 要素や、そのコンテナーにテンプレート変数を追加するために、この配列を設定します。

その他の指定された属性は input 要素に割り当てられます。

例:

echo $this->Form->submit('Click me');

出力結果:

<div class="submit"><input value="Click me" type="submit"></div>

見出しテキストの代わりに見出しパラメーターとして画像への相対 URL または絶対 URL を渡すことができます。

echo $this->Form->submit('ok.png');

出力結果:

<div class="submit"><input type="image" src="/img/ok.png"></div>

submit 入力は、基本的なテキストやイメージが必要な場合に便利です。より複雑なボタンの内容が必要な場合は、 button() を使用してください。

ボタン要素の作成

Cake\View\Helper\FormHelper::button(string $title, array $options = [])

$title - ボタンの見出しテキストを提供する必須の文字列。

$options - 特定のコントロールのための共通オプション やボタン特有のオプション (下記参照)と有効な HTML 属性を含むオプション配列。

指定されたタイトルと 'button' のデフォルトタイプの HTML ボタンを作成します。

ボタンのオプション

'type' - これを設定すると、次の3つの button タイプのどれかが出力されます。

'submit' - $this->Form->submit() と同様に送信ボタンを作成します。しかしながら、 submit() のように div の囲い込みは生成しません。これがデフォルトのタイプです。

'reset' - フォームのリセットボタンを作成します。

'button' - 標準の押しボタンを作成します。

'escapeTitle' - ブール値。 true をセットした場合、 $title で指定された値を HTML エンコードします。デフォルトは true です。

'escape' - ブール値。 true に設定すると、ボタンに対して生成されたすべてのHTML属性をHTMLエンコードします。 デフォルトは true です。

'confirm' - クリック時に表示される確認メッセージ。デフォルトは null です。

例:

echo $this->Form->button('ボタン');
echo $this->Form->button('別のボタン', ['type' => 'button']);
echo $this->Form->button('フォームのリセット', ['type' => 'reset']);
echo $this->Form->button('フォームの送信', ['type' => 'submit']);

出力結果:

<button type="submit">ボタン</button>
<button type="button">別のボタン</button>
<button type="reset">フォームのリセット</button>
<button type="submit">フォームの送信</button>

'escapeTitle' オプションの使用例:

// エスケープされていないHTMLをレンダリングします。
echo $this->Form->button('<em>Submit Form</em>', [
    'type' => 'submit',
    'escapeTitle' => false,
]);

フォームを閉じる

Cake\View\Helper\FormHelper::end($secureAttributes = [])

$secureAttributes - オプション。SecurityComponent 用に生成された非表示の input 要素に HTML 属性として渡されるセキュアな属性を提供できます。

end() は、フォームを閉じて完成します。多くの場合、 end() は終了タグだけを出力しますが、 end() を使うと、 FormHelper が Cake\Controller\Component\SecurityComponent に必要な hidden フォーム要素を挿入できるようになります。

<?= $this->Form->create(); ?>

<!-- フォーム要素はここにあります -->

<?= $this->Form->end(); ?>

生成された hidden 入力に属性を追加する必要がある場合は、 $secureAttributes 引数を使用できます。

echo $this->Form->end(['data-type' => 'hidden']);

出力結果:

<div style="display:none;">
    <input type="hidden" name="_Token[fields]" data-type="hidden"
        value="2981c38990f3f6ba935e6561dc77277966fabd6d%3AAddresses.id">
    <input type="hidden" name="_Token[unlocked]" data-type="hidden"
        value="address%7Cfirst_name">
</div>

注釈

アプリケーションで Cake\Controller\Component\SecurityComponent を使用している場合は、必ずフォームを end() で終わらせてください。

単独のボタンと POST リンクの作成

POST ボタンの作成

Cake\View\Helper\FormHelper::postButton(string $title, mixed $url, array $options = [])

$title - ボタンの見出しテキストを提供する必須の文字列。デフォルトでは HTML エンコードされません。

$url - 文字列や配列として提供される URL。

$options - 特定のコントロールのための共通オプション 、特定のオプション（下記参照）と有効な HTML 属性を含むオプション配列。

デフォルトでは、POST で送信する <form> 要素で囲まれた <button> タグを作成します。また、デフォルトでは、SecurityComponent のために非表示入力フィールドも生成します。

POST ボタンのオプション

'data' - hidden 入力に渡すキーと値の配列。

'method' - 使用するリクエスト方法。例えば、 'delete' をセットすると、 HTTP/1.1 DELETE リクエストをシミュレートします。デフォルトは 'post' です。

'form' - FormHelper::create() に渡す任意のオプションの配列。

また、 postButton() メソッドは、 button() メソッドで有効なオプションも受け付けます。

例:

// templates/Tickets/index.php の中で
<?= $this->Form->postButton('Delete Record', ['controller' => 'Tickets', 'action' => 'delete', 5]) ?>

出力結果:

<form method="post" accept-charset="utf-8" action="/Rtools/tickets/delete/5">
    <div style="display:none;">
        <input name="_method" value="POST" type="hidden">
    </div>
    <button type="submit">Delete Record</button>
    <div style="display:none;">
        <input name="_Token[fields]" value="186cfbfc6f519622e19d1e688633c4028229081f%3A" type="hidden">
        <input name="_Token[unlocked]" value="" type="hidden">
        <input name="_Token[debug]" value="%5B%22%5C%2FRtools%5C%2Ftickets%5C%2Fdelete%5C%2F1%22%2C%5B%5D%2C%5B%5D%5D" type="hidden">
    </div>
</form>

このメソッドは form 要素を作成します。なので、開かれたフォームの中でこのメソッドを使用しないでください。代わりに Cake\View\Helper\FormHelper::submit() または Cake\View\Helper\FormHelper::button() を使用して、開かれたフォームの中でボタンを作成してください。

POST リンクの作成

Cake\View\Helper\FormHelper::postLink(string $title, mixed $url = null, array $options = [])

$title - <a> タグに囲まれたテキストを提供する必須の文字列。

$url - オプション。フォームの URL (相対 URL 、または http:// で始まる外部 URL) を含む文字列または配列。

$options - 特定のコントロールのための共通オプション 、特有のオプション（下記参照）と有効な HTML 属性を含むオプション配列。

HTML リンクを作成しますが、指定した方法 (デフォルトは POST)で URL にアクセスします。ブラウザーで有効にするには JavaScript が必要です。

POST リンクのオプション

'data' - hidden 入力に渡すキーと値の配列。

'method' - 使用するリクエスト方法。例えば、 'delete' をセットすると、 HTTP/1.1 DELETE リクエストをシミュレートします。デフォルトは 'post' です。

'confirm' - クリック時に表示される確認メッセージ。デフォルトは null です。

'block' - ビューブロック 'postLink' へフォームの追加するためにこのオプションに true をセットしたり、カスタムブロック名を指定します。デフォルトは null です。

また、 postLink メソッドは、 link() メソッドの有効なオプションを受け付けます。

このメソッドは <form> 要素を作成します。このメソッドを既存のフォームの中で使いたい場合は、 block オプションを使用して、新しいフォームがメインフォームの外部でレンダリング可能なビューブロック に設定されるようにする必要があります。

あなたが探しているものがフォームを送信するボタンであれば、代わりに Cake\View\Helper\FormHelper::button() または Cake\View\Helper\FormHelper::submit() を使用してください。

注釈

開いているフォームの中に postLink を入れないように注意してください。代わりに、 block オプションを使ってフォームをビューブロック にバッファリングしてください。

FormHelper で使用するテンプレートのカスタマイズ

CakePHP の多くのヘルパーと同じように、FormHelper は、作成する HTML をフォーマットするための文字列テンプレートを使用しています。既定のテンプレートは、合理的な既定値のセットを意図していますが、アプリケーションに合わせてテンプレートをカスタマイズする必要があるかもしれません。

ヘルパーが読み込まれたときにテンプレートを変更するには、コントローラーにヘルパーを含めるときに templates オプションを設定することができます。

// View クラスの中で
$this->loadHelper('Form', [
    'templates' => 'app_form',
]);

これは、 config/app_form.php の中のタグを読み込みます。このファイルには、名前で索引付けされたテンプレートの配列が含まれている必要があります。

// config/app_form.php の中で
return [
    'inputContainer' => '<div class="form-control">{{content}}</div>',
];

定義したテンプレートは、ヘルパーに含まれるデフォルトのテンプレートを置き換えます。置き換えられていないテンプレートは引き続きデフォルト値を使用します。 setTemplates() メソッドを使って実行時にテンプレートを変更することもできます。

$myTemplates = [
    'inputContainer' => '<div class="form-control">{{content}}</div>',
];
$this->Form->setTemplates($myTemplates);
// 3.4 より前
$this->Form->templates($myTemplates);

警告

パーセント記号 (%) を含むテンプレート文字列には特別な注意が必要です。この文字の先頭に %% のようにもう一つパーセンテージを付ける必要があります。なぜなら、内部的なテンプレートは sprintf() で使用されるためにコンパイルされているからです。例: '<div style="width:{{size}}%%">{{content}}</div>'

テンプレート一覧

デフォルトのテンプレートのリスト、それらのデフォルトのフォーマット、そして期待される変数は FormHelper API ドキュメント [https://api.cakephp.org/4.x/class-Cake.View.Helper.FormHelper.html#%24_defaultConfig] で見つけることができます。

異なるカスタムコントロールコンテナーの使用

これらのテンプレートに加えて、 control() メソッドはコントロールコンテナーごとに異なるテンプレートを使用しようとします。たとえば、datetime コントロールを作成する場合、 datetimeContainer が存在する場合にはそれが使用されます。そのコンテナーがない場合、 inputContainer テンプレートが使用されます。例えば:

// 独自の HTML で囲まれた radio を追加
$this->Form->templates([
    'radioContainer' => '<div class="form-radio">{{content}}</div>'
]);

// 独自の div で囲まれた radio セットを作成
echo $this->Form->control('email_notifications', [
    'options' => ['y', 'n'],
    'type' => 'radio'
]);

異なるカスタムフォームグループの使用

コンテナーの制御と同様に、 control() メソッドはフォームグループごとに異なるテンプレートを使用しようとします。フォームグループは、ラベルとコントロールの組み合わせです。例えば、radio 入力を作成する時、 radioFormGroup が存在する場合、それが使用されます。そのテンプレートが存在しない場合、デフォルトでは、ラベル＆入力の各セットは、 formGroup テンプレートを使用して描画されます。

例:

// 独自の radio フォームグループを追加
$this->Form->setTemplates([
    'radioFormGroup' => '<div class="radio">{{label}}{{input}}</div>'
]);

テンプレートにテンプレート変数を追加

カスタムテンプレートにテンプレートプレースホルダを追加し、コントロールを生成するときにプレースホルダを設定することができます。

例:

// help プレースホルダ付きでテンプレートを追加
$this->Form->setTemplates([
    'inputContainer' => '<div class="input {{type}}{{required}}">
        {{content}} <span class="help">{{help}}</span></div>'
]);

// help 変数を設定し入力を生成
echo $this->Form->control('password', [
    'templateVars' => ['help' => '少なくとも 8 文字の長さ。']
]);

出力結果:

<div class="input password">
    <label for="password">
        Password
    </label>
    <input name="password" id="password" type="password">
    <span class="help">少なくとも 8 文字の長さ。</span>
</div>

チェックボックスとラジオのラベル外への移動

デフォルトでは、CakePHP は、 control() で作成されたチェックボックスと control() と radio() の両方で作成されたラジオボタンをラベル要素内でネストします。これにより、人気の CSS フレームワークとの統合に役立ちます。ラベルの外に checkbox/radio 入力を配置する必要がある場合は、テンプレートを変更することで行うことができます。

$this->Form->setTemplates([
    'nestingLabel' => '{{hidden}}{{input}}<label{{attrs}}>{{text}}</label>',
    'formGroup' => '{{input}}{{label}}',
]);

これにより、ラジオボタンとチェックボックスがラベルの外側に描画されます。

フォーム全体の生成

複数のコントロールの作成

Cake\View\Helper\FormHelper::controls(array $fields = [], $options = [])

$fields - 生成するフィールドの配列。指定した各フィールドのカスタムタイプ、ラベル、その他のオプションを設定できます。

$options - オプション。オプションの配列。有効なキーは:

'fieldset' - filedset を無効にするために false を設定してください。空の配列を渡すと、fieldset は有効になります。 HTML 属性として適用するパラメーターの配列を fieldset タグに渡すこともできます。

legend - legend のテキストをカスタマイズするための文字列。生成された入力セットの legend を無効にするために false を設定してください。

fieldset で囲まれた指定された一連のコントロールセットを生成します。生成されたフィールドを含めることで指定できます。

echo $this->Form->controls([
    'name',
    'email'
]);

オプションを使用して legend のテキストをカスタマイズすることができます。

echo $this->Form->controls($fields, ['legend' => 'Update news post']);

$fields パラメーターで追加のオプションを定義することによって、生成されたコントロールをカスタマイズすることができます。

echo $this->Form->controls([
    'name' => ['label' => 'カスタムラベル']
]);

$fields をカスタマイズする場合、生成された legend/fieldset を制御するために $options パラメーターを使用することができます。

例えば:

echo $this->Form->controls(
    [
        'name' => ['label' => 'カスタムラベル']
    ],
    ['legend' => 'Update your post']
);

fieldset を無効にすると、 legend は出力されません。

エンティティー全体のコントロールを作成

Cake\View\Helper\FormHelper::allControls(array $fields, $options = [])

$fields - オプション。生成するフィールドのカスタマイズ配列。カスタムタイプ、ラベル、その他のオプションを設定できます。

$options - オプション。オプションの配列。有効なキーは:

'fieldset' - これに false を設定すると fieldset が無効になります。空の場合、fieldset は有効になります。パラメーターの配列を渡すと、 fieldset の HTML 属性として適用されます。

legend - legend テキストをカスタマイズするための文字列。これに false を設定すると、生成されたコントロールセットの legend が無効になります。

このメソッドは controls() と密接に関係していますが、 $fields 引数は現在のトップレベルエンティティーの 全ての フィールドにデフォルト設定されています。生成されたコントロールから特定のフィールドを除外するには、 $fields パラメーターで false を設定します。

echo $this->Form->allControls(['password' => false]);
// 3.4.0 より前の場合:
echo $this->Form->allInputs(['password' => false]);

関連データの入力を作成

関連するデータのフォームを作成するのは簡単で、エンティティーのデータ内のパスに密接に関連しています。次のテーブルリレーションを仮定します。

Authors HasOne Profiles

Authors HasMany Articles

Articles HasMany Comments

Articles BelongsTo Authors

Articles BelongsToMany Tags

アソシエーション付きで読み込まれた記事を編集していた場合、次のコントロールを作成できます。

$this->Form->create($article);

// Article コントロール
echo $this->Form->control('title');

// Author コントロール (belongsTo)
echo $this->Form->control('author.id');
echo $this->Form->control('author.first_name');
echo $this->Form->control('author.last_name');

// Author の profile (belongsTo + hasOne)
echo $this->Form->control('author.profile.id');
echo $this->Form->control('author.profile.username');

// 別々の入力として、
// Tags コントロール (belongsToMany)
echo $this->Form->control('tags.0.id');
echo $this->Form->control('tags.0.name');
echo $this->Form->control('tags.1.id');
echo $this->Form->control('tags.1.name');

// 結合テーブルの入力 (articles_tags)
echo $this->Form->control('tags.0._joinData.starred');
echo $this->Form->control('tags.1._joinData.starred');

// Comments コントロール (hasMany)
echo $this->Form->control('comments.0.id');
echo $this->Form->control('comments.0.comment');
echo $this->Form->control('comments.1.id');
echo $this->Form->control('comments.1.comment');

上記のコントロールは、コントローラー内の次のコードを使用して完成したエンティティーグラフにマーシャリングすることができます。

$article = $this->Articles->patchEntity($article, $this->request->getData(), [
    'associated' => [
        'Authors',
        'Authors.Profiles',
        'Tags',
        'Comments'
    ]
]);

上記の例は、各エンティティーと結合データレコードに対して別々の入力を持つ、多数の関連するデータセットの拡張された例を示しています。また、多数の関連に属する複数選択入力を作成することもできます。

// belongsToMany の複数選択要素
// _joinData をサポートしません
echo $this->Form->control('tags._ids', [
    'type' => 'select',
    'multiple' => true,
    'options' => $tagList,
]);

独自ウィジェットの追加

CakePHP を使うと、アプリケーションに独自のコントロールウィジェットを簡単に追加でき、他のコントロールタイプと同様に使用することができます。すべてのコアコントロールタイプはウィジェットとして実装されています。つまり、独自の実装でコアウィジェットを上書きすることができます。

Widget クラスの構築

Widget クラスは、とても単純で必須のインターフェイスを持っています。これらは Cake\View\Widget\WidgetInterface を実装しなければなりません。このインターフェイスを実装するには、 render(array $data) メソッドと secureFields(array $data) メソッドが必要です。 render() メソッドは、ウィジェットを構築するためのデータ配列を受け取り、ウィジェットの HTML 文字列を返すことが期待されています。 secureFields() メソッドは、同様にデータ配列を受け取り、このウィジェットで保護するフィールドのリストを含む配列を返すことが期待されています。 CakePHP がウィジェットを構築している場合、最初の引数として Cake\View\StringTemplate インスタンスを取得し、その後にあなたが定義した依存関係が続くことが期待できます。 autocomplete ウィジェットを作成したい場合、以下を実行できます。

namespace App\View\Widget;

use Cake\View\Form\ContextInterface;
use Cake\View\Widget\WidgetInterface;

class AutocompleteWidget implements WidgetInterface
{

    protected $_templates;

    public function __construct($templates)
    {
        $this->_templates = $templates;
    }

    /**
     * Methods that render the widget.
     *
     * @param array $data The data to build an input with.
     * @param \Cake\View\Form\ContextInterface $context The current form context.
     *
     * @return string
     */
    public function render(array $data, ContextInterface $context): string
    {
        $data += [
            'name' => '',
        ];
        return $this->_templates->format('autocomplete', [
            'name' => $data['name'],
            'attrs' => $this->_templates->formatAttributes($data, ['name'])
        ]);
    }

    public function secureFields(array $data): array
    {
        return [$data['name']];
    }
}

明らかに、これは非常に簡単な例ですが、独自ウィジェットの構築方法を示しています。

ウィジェットの使用

FormHelper を読み込むときや、 addWidget() メソッドを使って独自のウィジェットを読み込むことができます。 FormHelper を読み込むとき、ウィジェットは設定として定義されます。

// View クラスの中で
$this->loadHelper('Form', [
    'widgets' => [
        'autocomplete' => ['Autocomplete']
    ]
]);

あなたのウィジェットが他のウィジェットを必要とする場合は、それらの依存関係を宣言することによって FormHelper に取り込ませることができます。

$this->loadHelper('Form', [
    'widgets' => [
        'autocomplete' => [
            'App\View\Widget\AutocompleteWidget',
            'text',
            'label'
        ]
    ]
]);

上記の例では、 autocomplete ウィジェットは text と label ウィジェットに依存します。ウィジェットがビューにアクセスする必要がある場合は、 _view 'ウィジェット' を使用してください。 autocomplete ウィジェットが作成されると、 text と label の名前に関連するウィジェットオブジェクトが渡されます。 addWidget() メソッドを使ってウィジェットを追加すると、次のようになります。

// classname の使用。
$this->Form->addWidget(
    'autocomplete',
    ['Autocomplete', 'text', 'label']
);

// インスタンスの使用 - 依存関係を解決する必要があります。
// 3.6.0 より前は、ウィジェットの取得に widgetRegistry() を使用。
$autocomplete = new AutocompleteWidget(
    $this->Form->getTemplater(),
    $this->Form->getWidgetLocator()->get('text'),
    $this->Form->getWidgetLocator()->get('label'),
);
$this->Form->addWidget('autocomplete', $autocomplete);

追加/置換されると、ウィジェットはコントロールの 'type' として使用できます。

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

これは、 controls() とまったく同じように label と囲い込む div を持つ独自ウィジェットを作成します。あるいは、マジックメソッドを使用してコントロールウィジェットだけを作成することもできます。

echo $this->Form->autocomplete('search', $options);

SecurityComponent との連携

Cake\Controller\Component\SecurityComponent には、フォームをより安全で安全にするためのいくつかの機能があります。コントローラーに SecurityComponent を含めるだけで、フォームの改ざん防止機能が自動的に有効になります。

SecurityComponent を利用する際は、前述のようにフォームを閉じる際は、必ず end() を使う必要があります。これにより特別な _Token 入力が生成されます。

Cake\View\Helper\FormHelper::unlockField($name)

$name - オプション。ドット区切りのフィールド名。

SecurityComponent によるフィールドのハッシュ化が行われないようにフィールドのロックを解除します。またこれにより、そのフィールドを JavaScript で操作できるようになります。 $name には入力のためのエンティティーのプロパティー名を指定します。

$this->Form->unlockField('id');

Cake\View\Helper\FormHelper::secure(array $fields = [], array $secureAttributes = [])

$fields - オプション。ハッシュの生成に使用するフィールドの一覧を含む配列。指定がない場合、 $this->fields が使用されます。

$secureAttributes - オプション。生成される hidden 入力要素の中に渡す HTML 属性の配列。

フォームで使用されるフィールドに基づくセキュリティーハッシュをもつ非表示の input フィールドを生成し、または、保護されたフォームが使用されていない場合は空の文字列を生成します。 $secureAttributes を設定した場合、これらの HTML 属性は、 SecurityCompnent によって生成された非表示の input タグの中にマージされます。これは、 'form' のような HTML5 属性を設定するのに特に便利です。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Html

class Cake\View\Helper\HtmlHelper(View $view, array $config = [])

CakePHP における HtmlHelper の役割は、 HTML に関連するオプションを より簡単、高速に作成し、より弾力的なものに変えることです。このヘルパーを使うことで、アプリケーションの足どりはより軽くなり、そしてドメインのルートが置かれている場所に関して、よりフレキシブルなものになるでしょう。

HtmlHelper にある多くのメソッドは $attributes という引数を持っています。これにより、いかなる追加属性もタグに付け加えることができます。ここでは、 $attributes パラメーターを使用する方法の例をいくつか紹介します。

欲しい属性: <tag class="someClass" />
配列パラメーター: ['class' => 'someClass']

欲しい属性: <tag name="foo" value="bar" />
配列パラメーター:  ['name' => 'foo', 'value' => 'bar']

整形式の要素を挿入

HtmlHelper の果たすもっとも重要なタスクは、適切に整形されたマークアップの生成です。このセクションでは、いくつかの HtmlHelper のメソッドと、その使用方法について説明します。

文字セットのタグを作成

Cake\View\Helper\HtmlHelper::charset($charset=null)

文書の文字セットを指定する meta タグを作成するために使います。デフォルト値は UTF-8 です。使用例:

echo $this->Html->charset();

出力結果:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

または、

echo $this->Html->charset('ISO-8859-1');

出力結果:

<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />

CSS ファイルへのリンク

Cake\View\Helper\HtmlHelper::css(mixed $path, array $options = [])

CSS スタイルシートへのリンク（複数可）を作成します。 block オプションが true に設定されている場合、link タグは css ブロックに追加されます。このブロックはドキュメントの head タグの中に出力することができます。

block オプションを使うと、link 要素をどのブロックに追加するかを制御することができます。デフォルトでは、 css ブロックに追加されます。

$options 配列のキー 'rel' が 'import' に設定されていると、スタイルシートがインポートされます。

パスが '/' で始まらない場合、CSS をインクルードするこのメソッドは、指定された CSS ファイルが webroot/css ディレクトリー内にあることを前提としています。

echo $this->Html->css('forms');

出力結果:

<link rel="stylesheet" href="/css/forms.css" />

最初のパラメーターは、複数のファイルを含むように配列することができます。

echo $this->Html->css(['forms', 'tables', 'menu']);

出力結果:

<link rel="stylesheet" href="/css/forms.css" />
<link rel="stylesheet" href="/css/tables.css" />
<link rel="stylesheet" href="/css/menu.css" />

プラグイン記法 を使用して、すべての読み込まれたプラグインの CSS ファイルをインクルードすることができます。 plugins/DebugKit/webroot/css/toolbar.css を含めるために、以下を使用することができます。

echo $this->Html->css('DebugKit.toolbar.css');

読み込まれたプラグインと名前を共有する CSS ファイルをインクルードするには、次の操作を実行します。例えば、 Blog プラグインを持っていて、 webroot/css/Blog.common.css をインクルードしたければ、

echo $this->Html->css('Blog.common.css', ['plugin' => false]);

プログラムによる CSS の作成

Cake\View\Helper\HtmlHelper::style(array $data, boolean $oneline = true)

メソッドに渡した配列のキーと値から CSS のスタイル定義を作成します。特に動的な CSS の作成に便利です。

echo $this->Html->style([
    'background' => '#633',
    'border-bottom' => '1px solid #000',
    'padding' => '10px'
]);

出力結果:

background:#633; border-bottom:1px solid #000; padding:10px;

meta タグの作成

Cake\View\Helper\HtmlHelper::meta(string|array $type, string $url = null, array $options = [])

このメソッドは、 RSS または Atom フィードや、 favicon といった外部リソースとリンクする際に便利です。 css() と同様に、 ['block' => true] のように $attributes パラメーターの 'block' キーを true に設定することで、このタグをインラインで表示するか meta ブロックに追加するかどうかを指定することができます。

$attributes のパラメーターを使って "type" 属性を設定するとき、 CakePHP では、いくつかのショートカットを用意しています。

type

変換後の値

html

text/html

rss

application/rss+xml

atom

application/atom+xml

icon

image/x-icon

<?= $this->Html->meta(
    'favicon.ico',
    '/favicon.ico',
    ['type' => 'icon']
);
?>
// 出力結果 (改行を追加しています)
// 注意: このヘルパーのコードは、異なる rel 属性値を必要とする
// 新旧両方のブラウザーでアイコンをダウンロードさせるための
// ２つのタグを作成します。
<link
    href="/subdir/favicon.ico"
    type="image/x-icon"
    rel="icon"
/>
<link
    href="/subdir/favicon.ico"
    type="image/x-icon"
    rel="shortcut icon"
/>

<?= $this->Html->meta(
    'Comments',
    '/comments/index.rss',
    ['type' => 'rss']
);
?>
// 出力結果 (改行を追加しています)
<link
    href="http://example.com/comments/index.rss"
    title="Comments"
    type="application/rss+xml"
    rel="alternate"
/>

ここのメソッドを使用して、meta keywords と description を追加することもできます。例:

<?= $this->Html->meta(
    'keywords',
    'ここに meta キーワードを書き込む'
);
?>
// 出力結果
<meta name="keywords" content="ここに meta キーワードを書き込む" />

<?= $this->Html->meta(
    'description',
    'ここに何か説明を書き込む'
);
?>
// 出力結果
<meta name="description" content="ここに何か説明を書き込む" />

定義済みの meta タグを作成するだけでなく、link 要素を作成することもできます。

<?= $this->Html->meta([
    'link' => 'http://example.com/manifest',
    'rel' => 'manifest'
]);
?>
// 出力結果
<link href="http://example.com/manifest" rel="manifest"/>

このように呼び出されたときに meta() に提供された属性は、生成された link タグに追加されます。

DOCTYPE の作成

Cake\View\Helper\HtmlHelper::docType(string $type = 'html5')

(X)HTML の DOCTYPE (文書型宣言) を返します。次の表に従って文書型を指定してください。

type

変換された値

html4-strict

HTML 4.01 Strict

html4-trans

HTML 4.01 Transitional

html4-frame

HTML 4.01 Frameset

html5 (default)

HTML5

xhtml-strict

XHTML 1.0 Strict

xhtml-trans

XHTML 1.0 Transitional

xhtml-frame

XHTML 1.0 Frameset

xhtml11

XHTML 1.1

echo $this->Html->docType();
// 出力結果: <!DOCTYPE html>

echo $this->Html->docType('html4-trans');
// 出力結果:
// <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
//    "http://www.w3.org/TR/html4/loose.dtd">

画像のリンク

Cake\View\Helper\HtmlHelper::image(string $path, array $options = [])

整形された画像タグを作成します。指定されたパスは webroot/img/ と相対的でなければなりません。

echo $this->Html->image('cake_logo.png', ['alt' => 'CakePHP']);

出力結果:

<img src="/img/cake_logo.png" alt="CakePHP" />

画像リンクを作成するには、 $attributes の url オプションを使ってリンク先を指定します。

echo $this->Html->image("recipes/6.jpg", [
    "alt" => "Brownies",
    'url' => ['controller' => 'Recipes', 'action' => 'view', 6]
]);

出力結果:

<a href="/recipes/view/6">
    <img src="/img/recipes/6.jpg" alt="Brownies" />
</a>

電子メールの中で画像を作成したり、画像への絶対パスが必要な場合は、 fullBase オプションを使用することができます。

echo $this->Html->image("logo.png", ['fullBase' => true]);

出力結果:

<img src="http://example.com/img/logo.jpg" alt="" />

読み込まれたプラグインからの画像ファイルを プラグイン記法 を使って組み込むことができます。 plugins/DebugKit/webroot/img/icon.png を組み込むために、次のように使用することができます。

echo $this->Html->image('DebugKit.icon.png');

読み込まれたプラグインと名前を共有する画像ファイルを組み込むには、次のようにしてできます。例えば、 Blog プラグインを持っていて、webroot/img/Blog.icon.png を組み込みたければ、

echo $this->Html->image('Blog.icon.png', ['plugin' => false]);

リンクの作成

Cake\View\Helper\HtmlHelper::link(string $title, mixed $url = null, array $options = [])

HTML リンクを作成するための多目的なメソッドです。要素の属性や $title をエスケープするかどうかを指定するには $options を使用してください。

echo $this->Html->link(
    'Enter',
    '/pages/home',
    ['class' => 'button', 'target' => '_blank']
);

出力結果:

<a href="/pages/home" class="button" target="_blank">Enter</a>

絶対URLにするためには '_full'=>true オプションを使用してください。

echo $this->Html->link(
    'Dashboard',
    ['controller' => 'Dashboards', 'action' => 'index', '_full' => true]
);

出力結果:

<a href="http://www.yourdomain.com/dashboards/index">Dashboard</a>

オプションで confirm キーを指定すると、JavaScript の confirm() ダイアログを表示できます。

echo $this->Html->link(
    '削除',
    ['controller' => 'Recipes', 'action' => 'delete', 6],
    ['confirm' => 'このレシピを削除してよろしいですか?']
);

出力結果:

<a href="/recipes/delete/6"
    onclick="return confirm(
        'このレシピを削除してよろしいですか?'
    );">
    削除
</a>

link() でクエリー文字列を作成することもできます。

echo $this->Html->link('View image', [
    'controller' => 'Images',
    'action' => 'view',
    1,
    '?' => ['height' => 400, 'width' => 500]
]);

出力結果:

<a href="/images/view/1?height=400&width=500">View image</a>

$title の HTML 特殊文字は HTML エンティティーに変換されます。この変換を無効にするには、 $options 配列の escape オプションを false に設定します。

echo $this->Html->link(
    $this->Html->image("recipes/6.jpg", ["alt" => "Brownies"]),
    "recipes/view/6",
    ['escape' => false]
);

出力結果:

<a href="/recipes/view/6">
    <img src="/img/recipes/6.jpg" alt="Brownies" />
</a>

escape を false に設定すると、リンクの属性のエスケープも無効になります。 escapeTitle オプションを使うと、属性ではなくタイトルのエスケープだけを無効にすることができます。

echo $this->Html->link(
    $this->Html->image('recipes/6.jpg', ['alt' => 'Brownies']),
    'recipes/view/6',
    ['escapeTitle' => false, 'title' => 'hi "howdy"']
);

出力結果:

<a href="/recipes/view/6" title="hi &quot;howdy&quot;">
    <img src="/img/recipes/6.jpg" alt="Brownies" />
</a>

また、さまざまな種類の URL の例については、 Cake\View\Helper\UrlHelper::build() メソッドをチェックしてください。

動画と音声ファイルのリンク

Cake\View\Helper\HtmlHelper::media(string|array $path, array $options)

オプション:

type 生成するメディア要素のタイプ。有効な値は "audio" または "video" です。 type が指定されていない場合、メディアの種類はファイルの MIME タイプに基づいて推測されます。

text video タグ内に含めるテキスト。

pathPrefix 相対 URL に使用するパスのプレフィックス。デフォルトは 'files/' です。

fullBase 指定されている場合、src 属性はドメイン名を含む完全なアドレスを取得します。

整形された audio/video タグを返します。

 <?= $this->Html->media('audio.mp3') ?>

 // 出力結果
 <audio src="/files/audio.mp3"></audio>

 <?= $this->Html->media('video.mp4', [
     'fullBase' => true,
     'text' => 'Fallback text'
 ]) ?>

 // 出力結果
 <video src="http://www.somehost.com/files/video.mp4">Fallback text</video>

<?= $this->Html->media(
     ['video.mp4', ['src' => 'video.ogg', 'type' => "video/ogg; codecs='theora, vorbis'"]],
     ['autoplay']
 ) ?>

 // 出力結果
 <video autoplay="autoplay">
     <source src="/files/video.mp4" type="video/mp4"/>
     <source src="/files/video.ogg" type="video/ogg;
         codecs='theora, vorbis'"/>
 </video>

JavaScript ファイルへのリンク

Cake\View\Helper\HtmlHelper::script(mixed $url, mixed $options)

ローカルファイルまたはリモート URL のいずれかのスクリプトファイルをインクルードします。

デフォルトでは、script タグは、文書のインラインに追加されます。 $options['block'] を true に設定することで、これを上書きする場合は、 script タグは代わりに文書内の他の場所で出力できる script ブロックに追加されます。どのブロック名が使用されているかを書き換えたい場合は、 $options['block'] を設定することで可能になります。

$options['once'] は、このスクリプトをリクエストごとに1回または複数回含めるかどうかを制御します。デフォルトは true です。

$options を使用して、生成された script タグに追加のプロパティーを設定することができます。 script タグの配列を使用すると、生成されたすべての script タグに属性が適用されます。

この JavaScript ファイルをインクルードするメソッドは、指定された JavaScript ファイルが webroot/js ディレクトリー内にあることを前提としています。

echo $this->Html->script('scripts');

出力結果:

<script src="/js/scripts.js"></script>

webroot/js にないファイルを絶対パスでリンクすることもできます。

echo $this->Html->script('/otherdir/script_file');

また、リモートの URL にリンクすることができます。

echo $this->Html->script('https://code.jquery.com/jquery.min.js');

出力結果:

<script src="https://code.jquery.com/jquery.min.js"></script>

最初のパラメーターは、複数のファイルをインクルードするために配列することができます。

echo $this->Html->script(['jquery', 'wysiwyg', 'scripts']);

出力結果:

<script src="/js/jquery.js"></script>
<script src="/js/wysiwyg.js"></script>
<script src="/js/scripts.js"></script>

block オプションを使って特定のブロックに script タグを追加することができます。

echo $this->Html->script('wysiwyg', ['block' => 'scriptBottom']);

レイアウトの中で 'scriptBottom' に追加されたすべての script タグを出力することができます。

echo $this->fetch('scriptBottom');

プラグイン記法 を使用して、すべての読み込まれたプラグインの script ファイルをインクルードすることができます。 plugins/DebugKit/webroot/js/toolbar.js をインクルードするために、次を使用できます。

echo $this->Html->script('DebugKit.toolbar.js');

読み込まれたプラグインと名前を共有するスクリプトファイルをインクルードするには、次の操作を実行します。例えば、 Blog プラグインを持っていて、 webroot/js/Blog.plugins.js をインクルードしたければ、

echo $this->Html->script('Blog.plugins.js', ['plugin' => false]);

インライン Javascript ブロックの作成

Cake\View\Helper\HtmlHelper::scriptBlock($code, $options = [])

PHP ビューコードから Javascript ブロックを生成するには、スクリプトブロックメソッドの1つを使用できます。スクリプトは、その場所で出力することも、ブロックにバッファリングすることもできます。

// defer 属性付きで、一度に全てのスクリプトブロックを定義
$this->Html->scriptBlock('alert("hi")', ['defer' => true]);

// 後で出力するスクリプトブロックをバッファリング
$this->Html->scriptBlock('alert("hi")', ['block' => true]);

Cake\View\Helper\HtmlHelper::scriptStart($options = [])

Cake\View\Helper\HtmlHelper::scriptEnd()

scriptStart() メソッドを使って、 <script> タグに出力するキャプチャーブロックを作成することができます。保存されたスクリプトスニペットをインラインで出力することも、ブロックにバッファリングすることもできます。

// 'script' ブロックに追加
$this->Html->scriptStart(['block' => true]);
echo "alert('I am in the JavaScript');";
$this->Html->scriptEnd();

Javascript をバッファリングした後、他の ビューブロック と同じように出力することができます。

// レイアウトの中で
echo $this->fetch('script');

ネストされたリストの作成

Cake\View\Helper\HtmlHelper::nestedList(array $list, array $options = [], array $itemOptions = [])

連想配列からネストされたリスト (UL / OL) を構築:

$list = [
    'Languages' => [
        'English' => [
            'American',
            'Canadian',
            'British',
        ],
        'Spanish',
        'German',
    ]
];
echo $this->Html->nestedList($list);

出力結果:

// 出力結果 (空白は省かれます)
<ul>
    <li>Languages
        <ul>
            <li>English
                <ul>
                    <li>American</li>
                    <li>Canadian</li>
                    <li>British</li>
                </ul>
            </li>
            <li>Spanish</li>
            <li>German</li>
        </ul>
    </li>
</ul>

テーブルヘッダーを作成

Cake\View\Helper\HtmlHelper::tableHeaders(array $names, array $trOptions = null, array $thOptions = null)

<table> タグの内側に配置されるテーブルのヘッダーセルの行を作成します。

echo $this->Html->tableHeaders(['Date', 'Title', 'Active']);

出力結果:

<tr>
    <th>Date</th>
    <th>Title</th>
    <th>Active</th>
</tr>

echo $this->Html->tableHeaders(
    ['Date','Title','Active'],
    ['class' => 'status'],
    ['class' => 'product_table']
);

出力結果:

<tr class="status">
     <th class="product_table">Date</th>
     <th class="product_table">Title</th>
     <th class="product_table">Active</th>
</tr>

列ごとに属性を設定することができます。これは、 $thOptions で提供されるデフォルトの代わりに使用されます。

echo $this->Html->tableHeaders([
    'id',
    ['Name' => ['class' => 'highlight']],
    ['Date' => ['class' => 'sortable']]
]);

出力結果:

<tr>
    <th>id</th>
    <th class="highlight">Name</th>
    <th class="sortable">Date</th>
</tr>

テーブルのセルを作成

Cake\View\Helper\HtmlHelper::tableCells(array $data, array $oddTrOptions = null, array $evenTrOptions = null, $useCount = false, $continueOddEven = true)

奇数行と偶数行に対して <tr> 属性を異なる方法で割り当てる表セルを行内に作成します。特定の <td> 属性の [] 内に単一のテーブルセルをラップします。

echo $this->Html->tableCells([
    ['Jul 7th, 2007', 'Best Brownies', 'Yes'],
    ['Jun 21st, 2007', 'Smart Cookies', 'Yes'],
    ['Aug 1st, 2006', 'Anti-Java Cake', 'No'],
]);

出力結果:

<tr><td>Jul 7th, 2007</td><td>Best Brownies</td><td>Yes</td></tr>
<tr><td>Jun 21st, 2007</td><td>Smart Cookies</td><td>Yes</td></tr>
<tr><td>Aug 1st, 2006</td><td>Anti-Java Cake</td><td>No</td></tr>

echo $this->Html->tableCells([
    ['Jul 7th, 2007', ['Best Brownies', ['class' => 'highlight']] , 'Yes'],
    ['Jun 21st, 2007', 'Smart Cookies', 'Yes'],
    ['Aug 1st, 2006', 'Anti-Java Cake', ['No', ['id' => 'special']]],
]);

出力結果:

<tr>
    <td>
        Jul 7th, 2007
    </td>
    <td class="highlight">
        Best Brownies
    </td>
    <td>
        Yes
    </td>
</tr>
<tr>
    <td>
        Jun 21st, 2007
    </td>
    <td>
        Smart Cookies
    </td>
    <td>
        Yes
    </td>
</tr>
<tr>
    <td>
        Aug 1st, 2006
    </td>
    <td>
        Anti-Java Cake
    </td>
    <td id="special">
        No
    </td>
</tr>

echo $this->Html->tableCells(
    [
        ['Red', 'Apple'],
        ['Orange', 'Orange'],
        ['Yellow', 'Banana'],
    ],
    ['class' => 'darker']
);

出力結果:

<tr class="darker"><td>Red</td><td>Apple</td></tr>
<tr><td>Orange</td><td>Orange</td></tr>
<tr class="darker"><td>Yellow</td><td>Banana</td></tr>

HtmlHelper によるタグ出力の変更

Cake\View\Helper\HtmlHelper::setTemplates(array $templates)

テンプレートを追加や置換をするためのテンプレートの配列を読み込みます。

// 指定したテンプレートを読み込む。
$this->Html->setTemplates([
    'javascriptlink' => '<script src="{{url}}" type="text/javascript"{{attrs}}></script>'
]);

直接 templater を使うことでテンプレートを含む設定ファイルを読み込むことができます。

// テンプレートを持つ設定ファイルを読み込む。
$this->Html->templater()->load('my_tags');

テンプレートのファイルを読み込む場合、ファイルは次のようになります。

<?php
return [
    'javascriptlink' => '<script src="{{url}}" type="text/javascript"{{attrs}}></script>'
];

警告

パーセント記号 (%) を含むテンプレート文字列には特別な注意が必要です。この文字の先頭に %% のようにもう一つパーセンテージを付ける必要があります。なぜなら、内部的なテンプレートは sprintf() で使用されるためにコンパイルされているからです。例: <div style="width:{{size}}%%">{{content}}</div>

HtmlHelper でパンくずリストを作成

Cake\View\Helper\HtmlHelper::addCrumb(string $name, string $link = null, mixed $options = null)

Cake\View\Helper\HtmlHelper::getCrumbs(string $separator = '&raquo;', string $startText = false)

Cake\View\Helper\HtmlHelper::getCrumbList(array $options = [], $startText = false)

多くのアプリケーションでは、エンドユーザーのナビゲーションを容易にするためのパンくずリストがあります。 HtmlHelper の助けを借りて、アプリでパンくずリストを作成することができます。パンくずを作るには、まずレイアウトテンプレートで次のようにします。

echo $this->Html->getCrumbs(' > ', 'Home');

$startText のオプションは配列も受け付けます。これにより、生成された最初のリンクへのさらなる制御を可能にします。

echo $this->Html->getCrumbs(' > ', [
    'text' => $this->Html->image('home.png'),
    'url' => ['controller' => 'Pages', 'action' => 'display', 'home'],
    'escape' => false
]);

text や url 以外のキーは $options パラメーターとして link() に渡されます。

今、ビューでは、各ページ上のパンくずリストを開始するために以下を追加しようと考えています:

$this->Html->addCrumb('Users', '/users');
$this->Html->addCrumb('Add User', ['controller' => 'Users', 'action' => 'add']);

これは、 getCrumbs が追加されたレイアウトで、 "Home > Users > Add User" の出力を追加します。

また、HTML リストの中で整形されたパンくずを取得することもできます。

echo $this->Html->getCrumbList();

オプションとして、 <ul> (順不同リスト) に収まる class のような通常の HTML パラメーターを使用することができ、 特別なオプションを持ちます。 (li 要素の間にある） separator 、 irstClass 及び lastClass などです。

echo $this->Html->getCrumbList(
    [
        'firstClass' => false,
        'lastClass' => 'active',
        'class' => 'breadcrumb'
    ],
    'Home'
);

このメソッドはリストとその要素を生成するために Cake\View\Helper\HtmlHelper::tag() を使います。 getCrumbs() と同様に動作するので、全てのパンくずに追加されたオプションを使用できます。 $startText パラメーターを使って、最初のパンくずのリンクやテキストを提供することができます。これは、常にルートのリンクを含めたい場合に便利です。このオプションは getCrumbs() の $startText オプションと同じ働きをします。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Number

class Cake\View\Helper\NumberHelper(View $view, array $config = [])

NumberHelper は、ビューの中で一般的なフォーマットを使用して数値の表示を可能にする、便利なメソッドで構成されています。これらのメソッドは、通貨、パーセンテージ、データサイズを整形したり、数値を特定精度へ整形する方法と、そして数値を整形するさらなる柔軟性も提供します。

以下の全ての機能は、整形された数値を返します。これらは自動的にビューに出力を表示しません。

通貨フォーマット

Cake\View\Helper\NumberHelper::currency(mixed $value, string $currency = null, array $options = [])

このメソッドは、共通通貨フォーマット（ユーロ、英ポンド、米ドル）で数値を表示するために使用されます。ビュー内で次のように使います。

// NumberHelper としてコール
echo $this->Number->currency($value, $currency);

// Number としてコール
echo Number::currency($value, $currency);

1つ目のパラメーター $value は、合計金額をあらわす浮動小数点数でなければいけません。 2つ目のパラメーターは、あらかじめ定義された通貨フォーマット方式を選択するための文字列です。

$currency

通貨の種類によってフォーマットされた 1234.56

EUR

€1.234,56

GBP

£1,234.56

USD

$1,234.56

3つ目のパラメーターは、出力を定義するためのオプションの配列です。次のオプションが用意されています。

オプション

説明

before

レンダリングされた数値の前に表示されるテキスト。

after

レンダリングされた数値の後に表示されるテキスト。

zero

ゼロ値の場合に使用するテキストで、文字列か数値で指定できます。例. 0, '無料!'

places

小数点以下の桁数を指定します。例. 2

precision

小数点以下の最大桁数を指定します。例. 2

locale

数値フォーマットに使用するロケール名。例. "fr_FR".

fractionSymbol

小数に使用する文字列。例. ' cents'

fractionPosition

fractionSymbol で指定した文字列を、小数の 'before' または 'after' のどちらに配置するか。

pattern

数値のフォーマットに使用する ICU 数値パターン。例. #,###.00

useIntlCode

通貨記号を国際通貨コードに置き換えるために true を指定する。

$currency の値が null の場合、デフォルト通貨は Cake\I18n\Number::defaultCurrency() によって設定されます。

デフォルト通貨の設定

Cake\View\Helper\NumberHelper::defaultCurrency($currency)

デフォルト通貨のための setter/getter です。これによって、常に Cake\I18n\Number::currency() に通貨を渡したり、他のデフォルトを設定することによって全ての通貨の出力を変更したりする必要がなくなります。 $currency に false が設定された場合、現在格納されている値をクリアします。デフォルトでは、設定されていれば intl.default_locale を取得し、そうでない場合は 'en_US' を設定します。

浮動小数点数フォーマット

Cake\View\Helper\NumberHelper::precision(float $value, int $precision = 3, array $options = [])

このメソッドは指定された精度(小数点以下)で数値を表示します。定義された精度のレベルを維持するために丸めます。

// NumberHelper としてコール
echo $this->Number->precision(456.91873645, 2);

// 出力
456.92

// Number としてコール
echo Number::precision(456.91873645, 2);

パーセンテージフォーマット

Cake\View\Helper\NumberHelper::toPercentage(mixed $value, int $precision = 2, array $options = [])

オプション

説明

multiply

値を 100 で乗算しなければならないかどうかを示す Boolean 値です。少数のパーセンテージに便利です。

このメソッドは Cake\I18n\Number::precision() のように、与えられた精度に応じて(精度を満たすように丸めて)数値をフォーマットします。このメソッドはパーセンテージとして数値を表現し、パーセント記号を追加して出力します。

// NumberHelper としてコール。 出力: 45.69%
echo $this->Number->toPercentage(45.691873645);

// Number としてコール。 出力: 45.69%
echo Number::toPercentage(45.691873645);

// multiply オプションとともにコール。 出力: 45.7%
echo Number::toPercentage(0.45691, 1, [
    'multiply' => true
]);

人が読める形式の値との相互作用

Cake\View\Helper\NumberHelper::toReadableSize(string $size)

このメソッドはデータサイズを人が読める形式に整形します。これは、バイト数を KB、MB、GB、および TB へ変換するための近道を提供します。サイズは、データのサイズに応じて小数点以下二桁の精度で表示されます。(例 大きいサイズの表現):

// NumberHelper としてコール
echo $this->Number->toReadableSize(0); // 0 Byte
echo $this->Number->toReadableSize(1024); // 1 KB
echo $this->Number->toReadableSize(1321205.76); // 1.26 MB
echo $this->Number->toReadableSize(5368709120); // 5 GB

// Number としてコール
echo Number::toReadableSize(0); // 0 Byte
echo Number::toReadableSize(1024); // 1 KB
echo Number::toReadableSize(1321205.76); // 1.26 MB
echo Number::toReadableSize(5368709120); // 5 GB

数字の整形

Cake\View\Helper\NumberHelper::format(mixed $value, array $options = [])

このメソッドは、ビューで使うための数値の整形をより制御しやすくします。 (および、メインのメソッドとして、NumberHelper のその他のほとんどのメソッドから使用されます。) このメソッドは以下のように使用します。

// NumberHelper としてコール
$this->Number->format($value, $options);

// Number としてコール
Number::format($value, $options);

$value パラメーターは、出力のために整形しようとしている数値です。 $options が未指定の場合、1236.334 という数値は 1,236 として出力されるでしょう。デフォルトの制度は1の位であることに注意してください。

$options パラメーターはこのメソッドに存在している手品のタネの在りかです。

もし整数を渡した場合、精度もしくは小数点以下の桁数になります。

もし連想配列を渡した場合、以下のキーが使用できます。

オプション

説明

places

小数点以下の桁数を指定します。例. 2

precision

小数点以下の最大桁数を指定します。例. 2

pattern

数値のフォーマットに使用する ICU 数値パターン。例. #,###.00

locale

数値フォーマットに使用するロケール名。例. "fr_FR".

before

レンダリングされた数値の前に表示されるテキスト。

after

レンダリングされた数値の後に表示されるテキスト。

例:

// NumberHelper としてコール
echo $this->Number->format('123456.7890', [
    'places' => 2,
    'before' => '¥ ',
    'after' => ' !'
]);
// 出力 '¥ 123,456.79 !'

echo $this->Number->format('123456.7890', [
    'locale' => 'fr_FR'
]);
// 出力 '123 456,79 !'

// Number としてコール
echo Number::format('123456.7890', [
    'places' => 2,
    'before' => '¥ ',
    'after' => ' !'
]);
// 出力 '¥ 123,456.79 !'

echo Number::format('123456.7890', [
    'locale' => 'fr_FR'
]);
// 出力 '123 456,79 !'

Cake\View\Helper\NumberHelper::ordinal(mixed $value, array $options = [])

このメソッドは序数を出力します。

例:

echo Number::ordinal(1);
// 出力 '1st'

echo Number::ordinal(2);
// 出力 '2nd'

echo Number::ordinal(2, [
    'locale' => 'fr_FR'
]);
// 出力 '2e'

echo Number::ordinal(410);
// 出力 '410th'

差分フォーマット

Cake\View\Helper\NumberHelper::formatDelta(mixed $value, array $options = [])

このメソッドは、符号付きの数として値の差分を表示します。

// NumberHelper としてコール
$this->Number->formatDelta($value, $options);

// Number としてコール
Number::formatDelta($value, $options);

$value パラメーターは、出力のために整形しようとしている数値です。 $options が未指定の場合、1236.334 という数値は 1,236 として出力されるでしょう。デフォルトの制度は1の位であることに注意してください。

$options パラメーターは Number::format() と同じキーを取ります。

オプション

説明

places

小数点以下の桁数を指定します。例. 2

precision

小数点以下の最大桁数を指定します。例. 2

locale

数値フォーマットに使用するロケール名。例. "fr_FR".

before

レンダリングされた数値の前に表示されるテキスト。

after

レンダリングされた数値の後に表示されるテキスト。

例:

// NumberHelper としてコール
echo $this->Number->formatDelta('123456.7890', [
    'places' => 2,
    'before' => '[',
    'after' => ']'
]);
// 出力 '[+123,456.79]'

// Number としてコール
echo Number::formatDelta('123456.7890', [
    'places' => 2,
    'before' => '[',
    'after' => ']'
]);
// 出力 '[+123,456.79]'

警告

全てのシンボルは UTF-8 です。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Paginator

class Cake\View\Helper\PaginatorHelper(View $view, array $config = [])

PaginatorHelper は、ページ番号や次ページへ／前ページへのリンクといった、ページ制御関連の出力をするために使用されます。これは PaginatorComponent と連携して機能します。

ページ制御を組み込んだデータセットの作成や、ページ制御関連のクエリーについての詳細はページネーション を参照してください。

PaginatorHelper テンプレート

内部的に PaginatorHelper は一連のシンプルな HTML テンプレートを使用してマークアップを生成します。これらのテンプレートを変更して、PaginatorHelper によって生成された HTML をカスタマイズすることができます。

テンプレートは {{var}} スタイルのプレースホルダを使います。 {{}} のまわりに空白を入れないことが重要です。そうしないと置き換えが機能しません。

ファイルからテンプレートをロードする

コントローラーに PaginatorHelper を追加するとき、'templates' 設定を定義して読み込むテンプレートファイルを定義することができます。これにより、複数のテンプレートをカスタマイズし、コードを DRY に保つことができます。

// AppView.php の中で
public function initialize()
{
    ...
    $this->loadHelper('Paginator', ['templates' => 'paginator-templates']);
}

これは config/paginator-templates.php にあるファイルをロードします。ファイルの外観は以下の例を参照してください。 プラグイン記法を使ってプラグインからテンプレートをロードすることもできます。

// AppView.php の中で
public function initialize(): void
{
    ...
    $this->loadHelper('Paginator', ['templates' => 'MyPlugin.paginator-templates']);
}

テンプレートが、主となるアプリケーションのものでもプラグインのものでも、テンプレートファイルは次のようになります。

return [
    'number' => '<a href="{{url}}">{{text}}</a>',
];

実行時にテンプレートを変更する

Cake\View\Helper\PaginatorHelper::setTemplates($templates)

このメソッドを使用すると、実行時に PaginatorHelper で使用されるテンプレートを変更できます。これは、特定のメソッド呼び出しのテンプレートをカスタマイズする場合に便利です。

// 現在のテンプレート値を読み込みます
$result = $this->Paginator->getTemplates('number');

// テンプレートを変更します
$this->Paginator->setTemplates([
    'number' => '<em><a href="{{url}}">{{text}}</a></em>'
]);

警告

パーセント記号 (%) を含むテンプレート文字列には特別な注意が必要です。この文字の前に %% のように別のパーセンテージを付ける必要があります。なぜなら、内部的なテンプレートは sprintf() で使われるように変換されているからです。例: '<div style="width:{{size}}%%">{{content}}</div>'

テンプレート名

PaginatorHelper は次のテンプレートを使用します。

nextActive next() によって生成されたリンクの有効な状態。

nextDisabled next() の無効な状態。

prevActive prev() によって生成されたリンクの有効な状態。

prevDisabled prev() の無効な状態。

counterRange counter() で format == range の場合のテンプレート。

counterPages counter() で format == pages の場合のテンプレート。

first first() によって生成されたリンクに使用されるテンプレート。

last last() によって生成されたリンクに使用されるテンプレート。

number numbers() によって生成されたリンクに使用されるテンプレート。

current 現在のページで使用されているテンプレート。

ellipsis numbers() によって生成された省略記号に使用されるテンプレート。

sort 方向のないソートリンクのテンプレート。

sortAsc 昇順のソートリンクのテンプレート。

sortDesc 降順のソートリンクのテンプレート。

ソートリンクの作成

Cake\View\Helper\PaginatorHelper::sort($key, $title = null, $options = [])

パラメータ:

$key (string) -- ソートしたい結果セットのキーの名前。

$title (string) -- リンクのタイトル。$title が null の場合、 $key の変化 (inflection) したものがタイトル用として使われます。

$options (array) -- ソートリンク用のオプション。

ソート用のリンクを作成します。並べ替えと方向のクエリー文字列パラメーターをセットします。リンクはデフォルトでは昇順にソートされます。 sort() で生成されたリンクは最初のクリックの後、 自動的に方向を切り替えます。結果セットが指定されたキーにより ‘asc’ にソートされている場合、返されたリンクは ‘desc’ でソートします。

$options で使えるキー:

escape コンテンツ内の HTML エンティティーをエンコードするかどうか。 デフォルトは true 。

model 使用するモデル。デフォルトは PaginatorHelper::defaultModel() 。

direction リンクが非アクティブの時に適用するデフォルトのソート順。

lock ソート順をロック (固定) するかどうか。 デフォルトのソート順にのみ適用されます。デフォルトは false 。

ここで複数の投稿 (post) をページ制御していて、今１ページ目にいるとすると:

echo $this->Paginator->sort('user_id');

出力結果:

<a href="/posts/index?page=1&amp;sort=user_id&amp;direction=asc">User Id</a>

title パラメーターを使って、リンクに付けるカスタムテキストを作ることもできます。

echo $this->Paginator->sort('user_id', 'User account');

出力結果:

<a href="/posts/index?page=1&amp;sort=user_id&amp;direction=asc">User account</a>

リンクに対して HTML のような画像を使っている場合は、エスケープを off にする必要があります。

echo $this->Paginator->sort(
  'user_id',
  '<em>User account</em>',
  ['escape' => false]
);

出力結果:

<a href="/posts/index?page=1&amp;sort=user_id&amp;direction=asc"><em>User account</em></a>

direction オプションでリンクのデフォルトのソート順を設定できます。一度リンクがアクティブになると、通常のように自動的にソート順が切り替わります。

echo $this->Paginator->sort('user_id', null, ['direction' => 'desc']);

出力結果:

<a href="/posts/index?page=1&amp;sort=user_id&amp;direction=desc">User Id</a>

lock オプションでソート順を指定された順に固定できます。

echo $this->Paginator->sort('user_id', null, ['direction' => 'asc', 'lock' => true]);

Cake\View\Helper\PaginatorHelper::sortDir(string $model = null, mixed $options = [])

ソートされている結果セットのソート順を取得します。

Cake\View\Helper\PaginatorHelper::sortKey(string $model = null, mixed $options = [])

ソートされている結果セットのソートキーを取得します。

ページ番号リンクの作成

Cake\View\Helper\PaginatorHelper::numbers($options = [])

ページ番号の並びを返します。モジュールを使って、現在のページの前後 何ページまでを表示するのかを決めます。デフォルトでは、 現在のページのいずれかの側で最大８個までのリンクが作られます。ただし存在しないページは作られません。現在のページもリンクにはなりません。

サポートされているオプションは以下の通りです。

before 数字の前に挿入されるコンテンツ

after 数字の後に挿入されるコンテンツ

model その番号を作る元になるモデル。デフォルトは PaginatorHelper::defaultModel()

modulus 現在のページの両側に含める数字の数。デフォルトは 8。

first 先頭ページへのリンクを生成したい場合、先頭から何ページ分を生成するかを整数で指定します。デフォルトは false です。文字列を指定すると、その文字列をタイトルの値として先頭ページへのリンクを生成します。

echo $this->Paginator->numbers(['first' => 'First page']);

last 最終ページヘのリンクを生成したい場合、最後から何ページ分を生成するかを整数で定義します。デフォルトは false です。 first オプションと 同じロジックに従います。 last() メソッドを使って別々に定義することも可能です。

このメソッドを使えば出力の多くをカスタマイズできますが、一切パラメーターを指定せずにコールしても問題ありません。

echo $this->Paginator->numbers();

first と last オプションを使って先頭ページと最終ページへのリンクを作れます。以下の例ではページ制御された結果セットの中の、先頭から２ページと末尾から２ページのリンクを含むページリンクの並びを生成します。

echo $this->Paginator->numbers(['first' => 2, 'last' => 2]);

ジャンプ用リンクの作成

特定のページ番号に直接行けるリンクを作れるだけでなく、現在の直前や直後、および先頭や末尾へのリンクを作りたくなる場合もあるでしょう。

Cake\View\Helper\PaginatorHelper::prev($title = '<< Previous', $options = [])

パラメータ:

$title (string) -- リンクのタイトル

$options (mixed) -- ページ制御用リンクのオプション

ページ制御されたレコードセットの中で、１つ前のページへのリンクを作ります。

$options 以下のキーをサポートしています。

escape コンテンツの HTML エンティティーをエンコードするかどうか。デフォルトは true です。

model 使用するモデル。デフォルトは PaginatorHelper::defaultModel() 。

disabledTitle リンクが無効な場合に使う文字列。デフォルトは $title パラメーター。

単純な例を以下に示します。

echo $this->Paginator->prev(' << ' . __('previous'));

もし投稿の２ページ目にいる場合は、以下のような出力になります。

<li class="prev">
    <a rel="prev" href="/posts/index?page=1&amp;sort=title&amp;order=desc">
        &lt;&lt; previous
    </a>
</li>

これより前のページがない場合は、以下のようになります。

<li class="prev disabled"><a href="" onclick="return false;">&lt;&lt; previous</a></li>

このメソッドで使用するテンプレートを変更するには、 PaginatorHelper テンプレート を参照してください。

Cake\View\Helper\PaginatorHelper::next($title = 'Next >>', $options = [])

このメソッドは prev() と全く同じですが、いくつか例外があります。これは直前のページではなく直後のページヘのリンクを作ります。また rel 属性には prev の代わりに next を使います。

Cake\View\Helper\PaginatorHelper::first($first = '<< first', $options = [])

先頭ページまたは先頭ページまでの一連の数字を返します。文字列が渡されると、その文字列をラベルとする先頭ページへのリンクのみが生成されます。

echo $this->Paginator->first('< first');

上記の例は先頭ページヘの単一のリンクを作成します。最初のページにいる場合は何も出力しません。先頭から何ページ分の並びを生成したいかを、整数で指定することもできます。

echo $this->Paginator->first(3);

上記の例では、３ページ目またはそれより先にいる場合、先頭から３ページ目までのリンクを生成します。それ以降の分は生成されません。

options パラメーターには以下の設定が可能です。

model 使用するモデル。デフォルトは PaginatorHelper::defaultModel() 。

escape テキストをエスケープするかどうか。コンテンツに HTML が含まれている場合は false に設定します。

Cake\View\Helper\PaginatorHelper::last($last = 'last >>', $options = [])

このメソッドはちょうど first() メソッドのような動きをしますが、少し異なるところがあります。もし $last の文字列値が表す最終ページにいる場合は何も生成しません。 $last が整数値の場合、ユーザーが最後から last ページ以内に範囲内に入った場合はリンクを生成しません。

ヘッダーリンクタグの作成

PaginatorHelper を使用すると、ページの <head> 要素に改行タグを作成できます。

// 現在のモデルの次ページと前ページのリンクを作成する。
echo $this->Paginator->meta();

// 現在のモデルの次ページと前ページ、先頭ページと最終ページのリンクを作成する。
echo $this->Paginator->meta(['first' => true, 'last' => true]);

ページ制御状態の確認

Cake\View\Helper\PaginatorHelper::current(string $model = null)

与えられたモデルについて、レコードセットの現在ページを返します。

// 現在の場所: http://example.com/comments/view/page:3
echo $this->Paginator->current('Comment');
// 出力は 3

Cake\View\Helper\PaginatorHelper::hasNext(string $model = null)

与えられた結果セットが最終ページでない場合に true を返します。

Cake\View\Helper\PaginatorHelper::hasPrev(string $model = null)

与えられた結果セットが先頭ページでない場合に true を返します。

Cake\View\Helper\PaginatorHelper::hasPage(int $page = 1, string $model = null)

与えられた結果セットが $page が示すページ番号を含む場合に true を返します。

Cake\View\Helper\PaginatorHelper::total(string $model = null)

与えられたモデルの総ページ数を返します。

ページカウンターの生成

Cake\View\Helper\PaginatorHelper::counter($options = [])

ページ制御された結果セットのためのカウンター文字列を返します。与えられた書式文字列と多くのオプションを使って、ページ制御された 結果セットの中の位置を表す、ローカライズされたアプリケーション固有の文字列を生成することができます。

counter() には多くのオプションがあります。 サポートされているのは以下のものです。

format カウンターの書式。サポートされている書式は 'range', 'pages' およびカスタムです。 pages のデフォルトは '1 of 10' のような出力です。カスタムモードでは与えられた文字列がパースされ、トークンが実際の値に置き換えられます。利用できるトークンは以下の通りです。

{{page}} - 表示された現在のページ

{{pages}} - 総ページ数

{{current}} - 表示されようとしている現在のレコード数

{{count}} - 結果セットの中の全レコード数

{{start}} - 表示されようとしている先頭のレコード数

{{end}} - 表示されようとしている最終のレコード数

{{model}} - モデル名を複数系にして読みやすい書式にしたもの。あなたのモデルが 'RecipePage' であれば、 {{model}} は 'recipe pages' になります。

counter メソッドに対して利用できるトークンを使って、文字列だけを与えることもできます。たとえば以下のようにできます。

echo $this->Paginator->counter(
    '{{page}} / {{pages}} ページ, {{current}} 件目 / 全 {{count}} 件,
    開始レコード番号 {{start}}, 終了レコード番号 {{end}}'
);

'format' を range に設定すると '1 - 3 of 13' のように出力します。

echo $this->Paginator->counter([
    'format' => 'range'
]);

model ページ制御する対象のモデル。デフォルトは PaginatorHelper::defaultModel() 。これは 'format' オプションのカスタム文字列と組み合わせて使われます。

ページ制御 URL の生成

Cake\View\Helper\PaginatorHelper::generateUrl(array $options = [], $model = null, $full = false)

デフォルトでは、非標準的なコンテキスト（JavaScript など）で使用する完全なページ制御 URL 文字列を返します。

echo $this->Paginator->generateUrl(['sort' => 'title']);

制限セレクトボックスコントロールの作成

Cake\View\Helper\PaginatorHelper::limitControl(array $limits = [], $default = null, array $options = [])

limit クエリーパラメーターを変更するドロップダウンコントロールを作成します。

// デフォルトを使用
echo $this->Paginator->limitControl();

// 必要な limit オプションを定義
echo $this->Paginator->limitControl([25 => 25, 50 => 50]);

// カスタム制限と選択されたオプションの設定
echo $this->Paginator->limitControl([25 => 25, 50 => 50], $user->perPage);

生成されたフォームやコントロールは、変更時に自動的に送信されます。

ページ制御オプションの設定

Cake\View\Helper\PaginatorHelper::options($options = [])

PaginatorHelperのすべてのオプションを設定します。サポートされているオプションは以下の通りです。:

url ページ制御アクションの URL 。 ‘url’ にはサブオプションがいくつかあります。:

sort レコードをソートする際のキー。

direction ソート順。デフォルトは ‘ASC’ です。

page 表示するページ番号。

上記の例で出てきたオプションは、特定のページやソート順を強制するのに使用できます。このヘルパーで生成された URL に対して、追加的な URL コンテンツを追加できます。

$this->Paginator->options([
    'url' => [
        'sort' => 'email',
        'direction' => 'desc',
        'page' => 6,
        'lang' => 'en'
    ]
]);

上記の例では、ヘルパーが生成するリンク全てに経路パラメーター en を追加します。また、指定されたソートキー、ソート順、ページ番号で リンクを生成します。デフォルトでは、 PaginatorHelper は現在のパスとクエリーパラメーターすべてをマージします。

escape リンクの title フィールドを HTML エスケープするかどうかを指定します。デフォルトは true です。

model ページ制御対象のモデル名。デフォルトは PaginatorHelper::defaultModel() です。

使用例

ユーザーに対してどのようにレコードを表示するのかは自由に決められますが、一般的には、HTML テーブルにより行われます。以下の例ではテーブルレイアウトを前提としていますが、ビューの中で利用可能な PaginatorHelper が、そのように機能を制限されているわけではありません。

詳細は API の中の PaginatorHelper [https://api.cakephp.org/4.x/class-Cake.View.Helper.PaginatorHelper.html] を参照してください。なお、前述のように、PaginatorHelper には、テーブルの列ヘッダーに統合できるソート機能もあります。

<!-- templates/Posts/index.php -->
<table>
    <tr>
        <th><?= $this->Paginator->sort('id', 'ID') ?></th>
        <th><?= $this->Paginator->sort('title', 'Title') ?></th>
    </tr>
       <?php foreach ($recipes as $recipe): ?>
    <tr>
        <td><?= $recipe->id ?> </td>
        <td><?= h($recipe->title) ?> </td>
    </tr>
    <?php endforeach; ?>
</table>

PaginatorHelper の sort() メソッドから出力されたリンクにより、ユーザーはテーブルのヘッダーをクリックして、指定されたフィールドによるデータのソートを切り替えることができます。

アソシエーションに基づいて列をソートすることもできます。

<table>
    <tr>
        <th><?= $this->Paginator->sort('title', 'Title') ?></th>
        <th><?= $this->Paginator->sort('Authors.name', 'Author') ?></th>
    </tr>
       <?php foreach ($recipes as $recipe): ?>
    <tr>
        <td><?= h($recipe->title) ?> </td>
        <td><?= h($recipe->name) ?> </td>
    </tr>
    <?php endforeach; ?>
</table>

注釈

関連するモデルでカラムをソートするには、 PaginationComponent::paginate プロパティーで設定する必要があります。上記の例を使用すると、ページ制御を処理するコントローラーは、次のように sortableFields キーを設定する必要があります。

$this->paginate = [
    'sortableFields' => [
        'Posts.title',
        'Authors.name',
    ],
];

sortableFields オプションの使い方の詳細については、ソート時に使用するフィールドの制御 をご覧ください。

ビューにおけるページ制御の表示に関する最後のネタは、 PaginationHelper によって提供されるページナビゲーションの追加です。

// ページ番号を表示する
<?= $this->Paginator->numbers() ?>

// 次ページと前ページのリンクを表示する
<?= $this->Paginator->prev('« Previous') ?>
<?= $this->Paginator->next('Next »') ?>

// 現在のページ番号 / 全ページ数 を表示する
<?= $this->Paginator->counter() ?>

counter() メソッドによる文章出力は、特殊なマーカーを使用してカスタマイズできます。

<?= $this->Paginator->counter([
    'format' => 'ページ {{page}} / {{pages}}, 全 {{count}} レコード中の
             {{current}} レコードを表示中, 先頭レコード {{start}}, 末尾 {{end}}'
]) ?>

複数の結果の改ページ

複数のクエリーをページ制御する 場合は、ページ設定関連要素を生成するときに model オプションを設定する必要があります。 PaginatorHelper のすべてのメソッド呼び出しで model オプションを使用するか、 options() を使用してデフォルトモデルを設定することができます。

// モデルオプションを渡す
echo $this->Paginator->sort('title', ['model' => 'Articles']);

// デフォルトモデルを設定する
$this->Paginator->options(['defaultModel' => 'Articles']);
echo $this->Paginator->sort('title');

model オプションを使用すると、 PaginatorHelper はクエリーがページ制御されたときに定義された scope を自動的に使用します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Text

class Cake\View\Helper\TextHelper(View $view, array $config = [])

TextHelper には、ビュー内のテキストをより使いやすく見やすくするためのメソッドが含まれています。リンクの有効化、URLのフォーマット、選択した単語やフレーズの周囲のテキストの抜粋を作成、テキストブロック内のキーワードのハイライト、 長いテキストの綺麗な切り詰めなどを支援します。

メールアドレスのリンク化

Cake\View\Helper\TextHelper::autoLinkEmails(string $text, array $options = [])

$options で定義されたオプションに従い、 $text に整形されたメールアドレスのリンクを追加します。( HtmlHelper::link() を参照)

$myText = 'For more information regarding our world-famous ' .
    'pastries and desserts, contact info@example.com';
$linkedText = $this->Text->autoLinkEmails($myText);

出力:

For more information regarding our world-famous pastries and desserts,
contact <a href="mailto:info@example.com">info@example.com</a>

このメソッドは自動的に入力をエスケープします。これを無効にする必要がある場合には、 escape オプションを使用します。

URLのリンク化

Cake\View\Helper\TextHelper::autoLinkUrls(string $text, array $options = [])

autoLinkEmails() と同様ですが、https, http, ftp, nntp から始まる文字列を見つけ、適切にリンクします。

このメソッドは自動的に入力をエスケープします。これを無効にする必要がある場合には、 escape オプションを使用します。

URLとメールアドレス両方のリンク化

Cake\View\Helper\TextHelper::autoLink(string $text, array $options = [])

与えられた $text に対して、 autoLinkUrls() と autoLinkEmails() 両方の機能を実行します。全てのURLとメールアドレスは与えられた $options を考慮して適切にリンクされます。

このメソッドは自動的に入力をエスケープします。これを無効にする必要がある場合には、 escape オプションを使用します。

テキストの段落化

Cake\View\Helper\TextHelper::autoParagraph(string $text)

2行改行されている場合は適切にテキストを<p>で囲み、 1行改行には<br>を追加します。:

$myText = 'For more information
regarding our world-famous pastries and desserts.

contact info@example.com';
$formattedText = $this->Text->autoParagraph($myText);

出力:

<p>For more information<br />
regarding our world-famous pastries and desserts.</p>
<p>contact info@example.com</p>

文字列の一部をハイライトする

Cake\View\Helper\TextHelper::highlight(string $haystack, string $needle, array $options = [])

$options['format'] で指定された文字列か、デフォルトの文字列を使って $haystack 中の $needle をハイライトします。

オプション:

format string - ハイライトするフレーズに適用する HTML パーツ

html bool - true ならすべての HTML タグを無視して、正確にテキストのみをハイライトするよう保証します。

例:

// TextHelper として呼ぶ
echo $this->Text->highlight(
    $lastSentence,
    '使って',
    ['format' => '<span class="highlight">\1</span>']
);

// Text として呼ぶ
use Cake\Utility\Text;

echo Text::highlight(
    $lastSentence,
    '使って',
    ['format' => '<span class="highlight">\1</span>']
);

出力:

$options['format'] で指定された文字列か、デフォルトの文字列を<span class="highlight">使って</span>
$haystack 中の $needle をハイライトします。

リンク除去

Cake\View\Helper\TextHelper::stripLinks($text)

渡された $text から HTML リンクを取り除きます。

テキストの切り詰め

Cake\View\Helper\TextHelper::truncate(string $text, int $length = 100, array $options)

$text が $length より長い場合、このメソッドはそれを $length の長さに切り詰め、 'ellipsis' が定義されているなら末尾にその文字列を追加します。もし 'exact' に false が渡されたなら、 $length を超えた最初の空白で切り詰められます。もし 'html' に true が渡されたなら、HTML タグは尊重され、削除されなくなります。

$options はすべての追加パラメーターを渡すのに使われ、下記のようなキーがデフォルトになっており、すべてが省略可能です。

[
    'ellipsis' => '...',
    'exact' => true,
    'html' => false
]

例:

// TextHelper として呼ぶ
echo $this->Text->truncate(
    'The killer crept forward and tripped on the rug.',
    22,
    [
        'ellipsis' => '...',
        'exact' => false
    ]
);

// Text として呼ぶ
use Cake\Utility\Text;

echo Text::truncate(
    'The killer crept forward and tripped on the rug.',
    22,
    [
        'ellipsis' => '...',
        'exact' => false
    ]
);

出力:

The killer crept...

文字列の末尾を切り詰める

Cake\View\Helper\TextHelper::tail(string $text, int $length = 100, array $options)

$text が $length より長い場合、このメソッドは先頭から差となる長さの文字列を取り除き、 'ellipsis' が定義されているなら先頭にその文字列を追加します。もし 'exact' に false が渡されたなら、切り詰めが本来発生したであろう場所の前にある最初の空白で切り詰められます。

$options はすべての追加パラメーターを渡すのに使われ、下記のようなキーがデフォルトになっており、すべてが省略可能です。

[
    'ellipsis' => '...',
    'exact' => true
]

例:

$sampleText = 'I packed my bag and in it I put a PSP, a PS3, a TV, ' .
    'a C# program that can divide by zero, death metal t-shirts'

// TextHelper として呼ぶ
echo $this->Text->tail(
    $sampleText,
    70,
    [
        'ellipsis' => '...',
        'exact' => false
    ]
);

// Text として呼ぶ
use Cake\Utility\Text;

echo Text::tail(
    $sampleText,
    70,
    [
        'ellipsis' => '...',
        'exact' => false
    ]
);

出力:

...a TV, a C# program that can divide by zero, death metal t-shirts

抜粋の抽出

Cake\View\Helper\TextHelper::excerpt(string $haystack, string $needle, integer $radius=100, string $ellipsis="...")

$haystack から、 $needle の前後 $radius で指定された文字数分を含む文字列を抜粋として抽出し、その先頭と末尾に $ellipsis の文字列を追加します。このメソッドは検索結果には特に便利でしょう。クエリー文字列やキーワードを結果の文章中とともに表示することができます。

// TextHelper として呼ぶ
echo $this->Text->excerpt($lastParagraph, 'method', 50, '...');

// Text として呼ぶ
use Cake\Utility\Text;

echo Text::excerpt($lastParagraph, 'method', 50, '...');

出力:

... by $radius, and prefix/suffix with $ellipsis. This method is especially
handy for search results. The query...

配列を文章的なものに変換する

Cake\View\Helper\TextHelper::toList(array $list, $and='and', $separator=', ')

最後の２要素が 'and' で繋がっている、カンマ区切りのリストを生成します。

$colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];

// TextHelper として呼ぶ
echo $this->Text->toList($colors);

// Text として呼ぶ
use Cake\Utility\Text;

echo Text::toList($colors);

出力:

red, orange, yellow, green, blue, indigo and violet

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Time

class Cake\View\Helper\TimeHelper(View $view, array $config = [])

TimeHelper には時間に関係のある情報を手早く処理するための2つの役割があります。:

時間文字列のフォーマットを整えることができます。

時間の判定ができます。

ヘルパーの使い方

TimeHelper の一般的な使い道は、ユーザーのタイムゾーンに合わせて日付と時刻を補正することです。それでは掲示板を例にとりましょう。あなたの掲示板には多くのユーザーがいて、世界中のどの地域からでもいつでもメッセージを投稿できます。時間を管理する簡単な方法は、すべての日付と時刻を GMT+0 または UTC として保存することです。アプリケーションのタイムゾーンを確実に GMT+0 に設定するために config/bootstrap.php の date_default_timezone_set('UTC'); という記述のコメントアウトを解除してください。

次にタイムゾーンのフィールドをユーザーのテーブルに追加して、ユーザーがタイムゾーンを設定できるように 必要な修正を加えます。これでログインしているユーザーのタイムゾーンが分かるようになり、 TimeHelper を使って投稿日時を補正することができます。

echo $this->Time->format(
  $post->created,
  \IntlDateFormatter::FULL,
  null,
  $user->time_zone
);
// GMT+0 のユーザーには 'Saturday, August 22, 2011 at 11:53:00 PM GMT' と
// 表示されます。
// GMT-8 のユーザーには 'Saturday, August 22, 2011 at 03:53 PM GMT-8:00' と
// 表示されます。

TimeHelper のほとんどの機能は、古いバージョンの CakePHP からアップグレードしているアプリケーションのための下位互換性のあるインターフェースとして意図されています。なぜなら、 ORM は timestamp 型と datetime 型の列ごとに Cake\I18n\Time インスタンスを返すので、そのメソッドを使用してほとんどのタスクを実行できるからです。例えば、受け入れられた書式指定文字列について読むには、 Cake\I18n\Time::i18nFormat() [https://api.cakephp.org/4.x/class-Cake.I18n.Time.html#i18nFormat()] メソッドを見てください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Url

class Cake\View\Helper\UrlHelper(View $view, array $config = [])

UrlHelper は他のヘルパーから URL を生成することが容易になります。アプリケーションのヘルパーでコアヘルパーをオーバーライドすることによって URL の生成方法をカスタマイズする単一の場所を提供します。このやり方は ヘルパーの別名セクションをご覧ください。

URL の生成

Cake\View\Helper\UrlHelper::build(mixed $url = null, array $options = [])

コントローラーとアクションの組み合わせを指定することで URL を生成して返します。 $url が空の場合、 REQUEST_URI を返します。そうでない場合、コントローラーとアクションの組み合わせで URL を生成します。 full が true の場合、結果がフルベース URL で返されます。

echo $this->Url->build([
    'controller' => 'Posts',
    'action' => 'view',
    'bar',
]);

// 出力結果
/posts/view/bar

さらにいつかの使用例は、こちら:

拡張子つきの URL:

echo $this->Url->build([
    'controller' => 'Posts',
    'action' => 'list',
    '_ext' => 'rss',
]);

// 出力結果
/posts/list.rss

フルベース URL を前につけた ('/' から始まる) URL:

echo $this->Url->build('/posts', ['fullBase' => true]);

// 出力結果
http://somedomain.com/posts

GET パラメーターとフラグメントアンカーの URL:

echo $this->Url->build([
    'controller' => 'Posts',
    'action' => 'search',
    '?' => ['foo' => 'bar'],
    '#' => 'first',
]);

// 出力結果
/posts/search?foo=bar#first

上記の例で使用している ? キーは、あなたが使用しているクエリー文字列パラメーターを明示したい場合やクエリー文字列パラメーターをあなたのルートプレースホルダーの一つと名前を共有したい場合に便利です。

名前付きルートの URL:

echo $this->Url->build(['_name' => 'product-page', 'slug' => 'i-m-slug']);

// 以下のようにルートをセットアップすることを仮定:
// $router->connect(
//     '/products/{slug}',
//     [
//         'controller' => 'Products',
//         'action' => 'view',
//     ],
//     [
//         '_name' => 'product-page',
//     ]
// );

echo $this->Url->build(['_name' => 'product-page', 'slug' => 'i-m-slug']);
// Will result in:
/products/i-m-slug

第２パラメーターは、HTML エスケープやベースパスを追加するかどうかを制御するオプションを定義できます。

$this->Url->build('/posts', [
    'escape' => false,
    'fullBase' => true,
]);

以下は、アセットタイムスタンプ付きの URL が <link rel="preload"/> で囲まれており、フォントをプリロードしています。注意: ファイルは存在していなければならず、 Configure::read('Asset.timestamp') は、タイムスタンプを追加するために true または 'force' を返さなければなりません。

echo $this->Html->meta([
    'rel' => 'preload',
    'href' => $this->Url->assetUrl(
        '/assets/fonts/yout-font-pack/your-font-name.woff2'
    ),
    'as' => 'font',
]);

CSS や JavaScript、または画像ファイルの URL を生成する場合、これらのアセットタイプのためのヘルパーメソッドがあります。

// 出力結果 /img/icon.png
$this->Url->image('icon.png');

// 出力結果 /js/app.js
$this->Url->script('app.js');

// 出力結果 /css/app.css
$this->Url->css('app.css');

// メソッドの呼び出し時にタイムスタンプを強制
$this->Url->css('app.css', ['timestamp' => 'force']);

// または、メソッド呼び出し時にタイムスタンプを無効化
$this->Url->css('app.css', ['timestamp' => false]);

詳細は API の Router::url [https://api.cakephp.org/4.x/class-Cake.Routing.Router.html#_url] を確認してください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

データベースアクセス & ORM

CakePHP では、2種類の主要なオブジェクトを使ってデータベースのデータを操作します。 1種類目は リポジトリー や テーブルオブジェクト です。これらのオブジェクトを利用して、データのコレクションへアクセスします。これらを利用することで、新しいレコードを保存したり、既存データの編集/削除、リレーションの定義、そして一括処理ができます。 2種類目は エンティティー です。エンティティーは、個々のレコードを意味し、行/レコードレベルの振る舞いや機能の定義を可能にします。

これら2つのクラスは、原則、あなたのデータ、正当性、相互作用や展開に関して発生するほぼすべてのことを管理する役割を担います。

CakePHP の組み込み ORM はリレーショナルデータベースに特化していますが、別のデータソースを選択するように拡張することも可能です。

CakePHP の ORM はアクティブレコードやデータマッパーパターンのアイデアやコンセプトを拝借しています。その目的は、早く作成し、シンプルに ORM を利用するという2つの利点を混成させるためです。

ORM の調査を始める前に あなたのデータベース接続の設定をご確認ください。

簡単な例

始めるにあたり、何もコードを書く必要はありません。もし、あなたのデータベーステーブルが CakePHP の規約 に準拠している場合、すぐに ORM の利用を開始できます。例えば articles からいくつかデータをロードしたい場合、 src/Model/Table/ArticlesTable.php を作成し、下記のように記述できます。

<?php
namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
}

Then in a controller or command we can have CakePHP create an instance for us:

public function someMethod()
{
    $resultset = $this->fetchTable('Articles')->find()->all();

    foreach ($resultset as $row) {
        echo $row->title;
    }
}

In other contexts, you can use the LocatorAwareTrait which add accessor methods for ORM tables:

use Cake\ORM\Locator\LocatorAwareTrait;

public function someMethod()
{
    $articles = $this->getTableLocator()->get('Articles');
    // more code.
}

Within a static method you can use the FactoryLocator to get the table locator:

// $articles は、 ArticlesTable クラスのインスタンスです。
$articles = TableRegistry::getTableLocator()->get('Articles');

具象テーブルクラスがあると、具象化エンティティークラスが欲しくなります。エンティティークラスはアクセッサーとミューテーターメソッドを定義でき、個別や複数レコードにカスタムロジックを定義できます。下記を src/Model/Entity/Article.php 内、 <?php の開始タグの後に追加します。

namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
}

エンティティーはデフォルトで単数形キャメルケースのテーブル名を利用します。前の手順でエンティティークラスはすでに作ったので、データベースからエンティティーをロードした時に、新しい Article クラスのインスタンスが生成されます。

use Cake\ORM\Locator\LocatorAwareTrait;

// ArticlesTable のインスタンス取得
$articles = $this->getTableLocator()->get('Articles');
$resultset = $articles->find()->all();

foreach ($resultset as $row) {
    // 各 row は、 Article クラスのインスタンスです。
    echo $row->title;
}

CakePHP は命名規則でテーブルクラスとエンティティークラスを関連づけます。もしテーブルにどのエンティティーを利用するかカスタマイズする必要があれば、 entityClass() のメソッドを特定のクラス名にセットします。

テーブルオブジェクト と エンティティー の章に、テーブルオブジェクトとエンティティーの使い方が詳しく記述されています。

詳細

データベースの基本

クイックツアー

設定

コネクションの管理

データの型

Connection クラス

ステートメントとの対話

クエリーロギング

引用識別子

メタデータ・キャッシング

データベースの作成

クエリービルダー

Query

クエリーオブジェクト

データを select する

高度な条件

結果を取得する

関連付くデータをロードする

データを insert する

データを update する

データを delete する

SQL インジェクションを防止する

より複雑なクエリー

テーブルオブジェクト

基本的な使い方

コールバックのライフサイクル

ビヘイビアー

接続設定

TableLocator の利用

エンティティー

Entity

エンティティークラスの生成

エンティティー生成

エンティティーのデータへのアクセス

アクセサーとミューテーター

エンティティーが変更されたかチェックする

バリデーションエラー

一括代入 (Mass Assignment)

アソシエーションの Lazy ローディング

トレイトを使った再利用可能なコードの生成

配列や JSON への変換

複合型の保存

データの取り出しと結果セット

Table

クエリーのデバッグと結果セット

主キーで単一のエンティティーを取得する

データのロードに Finder を使う

１つ目の結果を取得する

結果の件数を取得する

キー/値のペアを検索する

スレッド状のデータを検索する

カスタム Finder メソッド

動的な Finder

関連付いたデータを取得する

contain を用いた関連データのイーガーロード

フェッチの戦略の変更する

関連をレイジーロード(Lazy Load)する

結果セットを使いこなす

結果を Map/Reduce で変更する

データの検証

エンティティー構築前のデータ検証

既定のバリデーションセットの作成

異なるバリデーションセットの使用

バリデータの組み合わせ

バリデーションプロバイダー

テーブルからのバリデータ取得

既定のバリデータクラス

アプリケーションルールの適用

バリデーション対アプリケーションルール

データの保存

データ保存の概要

リクエストデータのエンティティーへの変換

エンティティーの保存

厳密な保存

複数のエンティティーの保存

一括更新

データの削除

Table::delete()

連鎖削除

一括削除

厳密な削除

アソシエーション - モデル同士を繋ぐ

hasOne アソシエーション

belongsTo アソシエーション

hasMany アソシエーション

belongsToMany アソシエーション

ビヘイビアー

ビヘイビアーの利用

コアビヘイビアー

ビヘイビアーの生成

リクエストデータをエンティティープロパティーに変換する

ロードされたビヘイビアーの削除

ロードされたビヘイビアーへのアクセス

スキーマシステム

Schema\TableSchema オブジェクト

スキーマコレクション

スキーマキャッシュツール

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

データベースの基本

CakePHP データベースアクセス層は、リレーショナルデータベースを扱うほとんどの面を抽象化して、サーバーコネクションの保持、クエリーの生成、SQL インジェクションの防止、スキーマチェックと変更、デバッグとデータベースに送信したクエリーのプロファイリングなどの支援を提供します。

クイックツアー

この章で説明する機能は、下位レベルのデータベースアクセス API でできることを説明します。もし ORM についてもっと詳細に知りたい場合は、 クエリービルダーや テーブルオブジェクト のセクションを参照してください。

データベース接続を作る一番簡単な方法は、 DSN 文字列を使います。

use Cake\Datasource\ConnectionManager;

$dsn = 'mysql://root:password@localhost/my_database';
ConnectionManager::setConfig('default', ['url' => $dsn]);

作成したら、コネクションオブジェクトにアクセスして使えるようになります。

$connection = ConnectionManager::get('default');

サポートしているデータベース

CakePHP は下記のリレーショナルデータベースをサポートしています。

MySQL 5.1+

SQLite 3

PostgreSQL 8.3+

SQLServer 2008+

Oracle (コミュニティープラグイン経由)

上記のデータベースそれぞれについて、適切な PDO 拡張がインストールされている必要があります。プロシージャー型 API はサポートされていません。

Oracle データベースは、 Driver for Oracle Database [https://github.com/CakeDC/cakephp-oracle-driver] コミュニティープラグインを経由してサポートされます。

Select 文の実行

生の SQL クエリーを実行するのは非常に簡単です。

use Cake\Datasource\ConnectionManager;

$connection = ConnectionManager::get('default');
$results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc');

パラメーターを追加するには、プリペアードステートメントを使います。

$results = $connection
    ->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1])
    ->fetchAll('assoc');

これは引数として複合データ型を使用することも可能です。

use Cake\Datasource\ConnectionManager;
use DateTime;

$connection = ConnectionManager::get('default');
$results = $connection
    ->execute(
        'SELECT * FROM articles WHERE created >= :created',
        ['created' => new DateTime('1 day ago')],
        ['created' => 'datetime']
    )
    ->fetchAll('assoc');

SQL 文を手で書く代わりに、クエリービルダーを使うこともできます。

$results = $connection
    ->newQuery()
    ->select('*')
    ->from('articles')
    ->where(['created >' => new DateTime('1 day ago')], ['created' => 'datetime'])
    ->order(['title' => 'DESC'])
    ->execute()
    ->fetchAll('assoc');

Insert 文の実行

データベースに行を追加するのは、通常は数行の話しです。

use Cake\Datasource\ConnectionManager;
use DateTime;

$connection = ConnectionManager::get('default');
$connection->insert('articles', [
    'title' => 'A New Article',
    'created' => new DateTime('now')
], ['created' => 'datetime']);

Update 文の実行

データベースの行の更新も同様に直感的に可能で、下記の例では article の id が 10 のデータを更新しています。

use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$connection->update('articles', ['title' => 'New title'], ['id' => 10]);

Delete 文の実行

同様に delete() メソッドはデータベースから行を削除するために使われ、下記の例では article の id が 10 の行を削除しています。

use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$connection->delete('articles', ['id' => 10]);

設定

慣例として、データベース接続は config/app.php に設定します。このファイルに定義された接続情報は、アプリケーションが使用する接続構成を生成する Cake\Datasource\ConnectionManager に引き渡します。サンプルとなる接続情報が config/app.default.php にあります。サンプルの接続設定は、次のようになります。

'Datasources' => [
    'default' => [
        'className' => 'Cake\Database\Connection',
        'driver' => 'Cake\Database\Driver\Mysql',
        'persistent' => false,
        'host' => 'localhost',
        'username' => 'my_app',
        'password' => 'secret',
        'database' => 'my_app',
        'encoding' => 'utf8mb4',
        'timezone' => 'UTC',
        'cacheMetadata' => true,
    ]
],

上記は指定されたパラメーターを持つ 'default' 接続を生成します。あなたは設定ファイルに必要な数だけ接続を定義することができます。また、 Cake\Datasource\ConnectionManager::config() を使って実行時に追加の設定をおこなうこともできます。その一例は次のようになります。

use Cake\Datasource\ConnectionManager;

ConnectionManager::setConfig('default', [
    'className' => 'Cake\Database\Connection',
    'driver' => 'Cake\Database\Driver\Mysql',
    'persistent' => false,
    'host' => 'localhost',
    'username' => 'my_app',
    'password' => 'secret',
    'database' => 'my_app',
    'encoding' => 'utf8mb4',
    'timezone' => 'UTC',
    'cacheMetadata' => true,
]);

設定オプションは DSN 文字列形式で設定することもできます。これは、環境変数や PaaS 環境で作業する時に便利です。:

ConnectionManager::setConfig('default', [
    'url' => 'mysql://my_app:sekret@localhost/my_app?encoding=utf8&timezone=UTC&cacheMetadata=true',
]);

DSN 文字列を使用するときには、クエリー文字列引数として追加のパラメーターやオプションを定義することができます。

デフォルトでは、すべてのテーブルオブジェクトは default の接続を使用します。デフォルト以外の接続を使用するには、 接続設定 を参照してください。

データベース設定ではいくつかのキーがサポートされています。使用可能なキーは下記の通りです。:

className

データベースサーバーへの接続を行うクラスの名前空間名付きの完全クラス名。このクラスは、データベースドライバーをロードし、SQL トランザクションメカニズムを提供し、 SQL を生成したりといったことを担当しています。

driver

ドライバークラス名は、データベースエンジンのすべての特異性を実装するために使われます。これは プラグイン記法 を用いた短いクラス名でも、完全名前空間名でも、どちらでもドライバーインスタンスを生成することが可能です。短いクラス名の例は、 Mysql, Sqlite, Postgres, Sqlserver などです。

persistent

データベースへの持続的接続を使うかどうか。このオプションは、 SqlServer ではサポートされません。 CakePHP バージョン 3.4.13 以降、SqlServer で persistent を true にセットすると例外が投げられます。

host

データベースサーバーのホスト名 （または IP アドレス）。

username

アカウントのユーザー名。

password

アカウントのパスワード。

database

接続するデータベース名。データベース名に . の使用は避けてください。識別子を引用符で囲むことが難しくするため、CakePHP は、データベース名の . をサポートしません。相対パスに起因する不正なパスを避けるため、 SQLite データベースへのパスは、絶対パス (ROOT . DS . 'my_app.db' など) にしてください。

port (optional)

サーバーに接続する際に使用する TCP ポート または Unix ソケット。

encoding

サーバーに SQL 文を送信する際に使用する文字セットを示します。 DB2 以外のデータベースでは、データベースのデフォルトエンコーディングがデフォルト設定されます。もし MySQL で UTF-8 で接続したいのなら、ハイフンなしで 'utf8' と指定してください。

timezone

サーバーのタイムゾーンがセットされます。

schema

PostgreSQL データベースで特定のスキーマを使う時に設定します。

unix_socket

Unix ソケットファイルを経由して接続することをサポートしているドライバーによって使用されます。PostgreSQL を使用していて、Unix ソケットを使用する場合は、 host を空白のままにします。

ssl_key

SSL キー・ファイルへのファイルパス。 （MySQL のみでサポートされています）。

ssl_cert

SSL 証明書ファイルへのファイルパス。 （MySQL のみでサポートされています）。

ssl_ca

SSL 証明書の認証局へのファイルパス。 （MySQL のみでサポートされています）。

init

接続が作成されたときに、データベースサーバーに送信されるクエリーのリスト。

log

クエリーログを有効にするには true をセットします。有効なクエリーで debug レベルの時に、 queriesLog スコープでログ出力されます。

quoteIdentifiers

あなたがテーブルやカラム名に予約語や特殊文字を使用している場合は true に設定します。この設定を有効にすると、SQL を生成する際に クエリービルダーによって引用符で囲まれたクエリーが生成されます。これはクエリーを実行する前に横断的に処理を行う必要があるため、パフォーマンスを低下させることに注意してください。

flags

ベースになる PDO のインスタンスに引き継がれる、 PDO 定数の連想配列。 flags がサポートしている内容については、お使いの PDO ドライバーのマニュアルをご覧ください。

cacheMetadata

boolean 型の true か、メタデータを格納するキャッシュ設定の文字列のどちらか。メタデータのキャッシュをオフにする事はお勧めしませんし、パフォーマンスがとても悪化します。詳細は メタデータ・キャッシング のセクションを参照してください。

mask

生成されたデータベースファイルのパーミッションをセットします。 (SQLite のみでサポートされています)

この時点で、あなたは CakePHP の規約 を見たいと思うかもしれません。正しいテーブル名（といくつかのカラムの追加）によって、いくつかの機能を獲得して、設定を回避することができます。例えば、もしデータベースのテーブル名が big_boxes でしたら、 テーブルクラス BigBoxesTable と、コントローラー BigBoxesController は、全て自動的に一緒に動作します。慣例としてデータベースのテーブル名は、例えば bakers, pastry_stores, savory_cakes といった具合に、アンダースコアー区切り・小文字・複数形とします。

コネクションの管理

class Cake\Datasource\ConnectionManager

ConnectionManager クラスは、あなたのアプリケーションがデータベース接続にアクセスするためのレジストリーとして機能します。これは他のオブジェクトが既存のコネクションへの参照を取得するための場所を提供します。

コネクションへのアクセス

static Cake\Datasource\ConnectionManager::get($name)

一度設定した接続は、 Cake\Datasource\ConnectionManager::get() を使って取り出すことができます。このメソッドはすでに確立しているコネクションを返すか、もしまだ接続していないのであればロードして接続してから返します。

use Cake\Datasource\ConnectionManager;

$connection = ConnectionManager::get('default');

存在しない接続をロードしようとしたら、例外を throw します。

実行時にコネクションを生成する

config() や get() を使用して、実行時に設定ファイルに定義されていないコネクションを生成することができます。

ConnectionManager::setConfig('my_connection', $config);
$connection = ConnectionManager::get('my_connection');

コネクション生成時の設定についての詳細は 設定 を参照してください。

データの型

class Cake\Database\TypeFactory

各ベンダーのデータベースは全て同じデータ型を持つわけではなく、似たようなデータ型が同じ名前になっているわけでもありませんので、 CakePHP ではデータベース層で使用するために基本的なデータ型のセットを提供しています。CakePHP がサポートしている型は、

string

一般的に CHAR または VARCHAR のカラムが指定されます。 fixed オプションを使うと、強制的に CHAR カラムとなります。 SQL Server では、NCHAR と NVARCHAR 型になります。

text

TEXT 型に変換します。

uuid

データベースがサポートするなら UUID 型に、さもなければ CHAR(36) に変換します。

integer

データベースがサポートする INTEGER 型に変換します。現時点では、 BIT はサポートしていません。

smallinteger

データベースによって提供される SMALLINT 型に変換します。

tinyinteger

データベースによって提供される TINYINT 型か SMALLINT 型に変換します。 MySQL の TINYINT(1) は、 boolean として扱われます。

biginteger

データベースによって提供される BIGINT 型に変換します。

float

データベースに応じて DOUBLE 型か FLOAT 型に変換されます。精度（小数点以下桁数）を指定するために precision オプションを使うことができます。

decimal

DECIMAL 型に変換されます。 length と precision オプションをサポートします。

boolean

BOOLEAN に変換します。MySQL の場合は TINYINT(1) になります。現時点では、 BIT(1) はサポートしていません。

binary

データベースによって提供される BLOB 型または BYTEA 型に変換します。

date

タイムゾーン情報を持たない DATE 型に変換されます。この型の戻り値は、ネイティブな DateTime クラスを拡張した Cake\I18n\Date です。

datetime

タイムゾーン情報を持たない DATETIME 型に変換されます。 PostgreSQL と SQL Server では、TIMESTAMP 型に変換されます。この型のデフォルトの戻り値は、組込みの DateTime クラスと Chronos [https://github.com/cakephp/chronos] を拡張した Cake\I18n\Time クラスになります。

timestamp

TIMESTAMP 型に変換します。

time

全てのデータベースで TIME 型に変換します。

json

可能であれば、JSON 型に変換し、そうでなければ TEXT 型に変換します。 'json' 型は 3.3.0 で追加されました。

これらの型は、テストフィクスデャーを使用している時に、CakePHP が提供するスキーマリフレクション機能とスキーマ生成機能の両方で使用されます。

また、各型は PHP と SQL の表現の変換を行う機能も提供します。これらのメソッドはクエリー実行時に型のヒントに基づいて呼び出されます。例えば、 'datetime' という名前の項目なら、入力パラメーターを自動的に DateTime から timestamp か 整形した日付文字列に変換します。同様に 'binary' という名前の項目ならファイルハンドラを受け入れ、データを読み込むときにはファイルハンドラを生成します。

DateTime タイプ

class Cake\Database\DateTimeType

ネイティブの DATETIME カラムタイプにマッピングします。 PostgreSQL や SQL Server では、これは TIMESTAMP 型になります。このカラム型のデフォルトの戻り値は Cake\I18n\FrozenTime で、これは組み込みの DateTimeImmutable クラスと Chronos [https://github.com/cakephp/chronos] を拡張したものです。

Cake\Database\DateTimeType::setTimezone(string|\DateTimeZone|null $timezone)

データベースサーバーのタイムゾーンとアプリケーションの PHP のタイムゾーンが一致しない場合は、このメソッドを使用してデータベースのタイムゾーンを指定することができます。このタイムゾーンは、PHP のオブジェクトをデータベースの日付文字列に変換する際に使用されます（その逆も同様です）。

class Cake\Database\DateTimeFractionalType

MySQLの DATETIME(6) のようなマイクロ秒を含むdatetimeカラムのマッピングに使用できます。このタイプを使用するには、マッピングされたタイプとして追加する必要があります。:

// config/bootstrap.php の中で
use Cake\Database\TypeFactory;
use Cake\Database\Type\DateTimeFractionalType;

// デフォルトのdatetime型を、より正確なものに上書きします。
TypeFactory::map('datetime', DateTimeFractionalType::class);

class Cake\Database\DateTimeTimezoneType

PostgreSQLの TIMESTAMPTZ のような、タイムゾーンを含むdatetimeカラムをマッピングするために使用できます。このタイプを使用するには、マッピングされたタイプとして追加する必要があります。:

// config/bootstrap.php の中で
use Cake\Database\TypeFactory;
use Cake\Database\Type\DateTimeTimezoneType;

// デフォルトのdatetime型を、より正確なものに上書きします。
TypeFactory::map('datetime', DateTimeTimezoneType::class);

独自の型を作成する

class Cake\Database\TypeFactory

static Cake\Database\TypeFactory::map($name, $class)

もしあなたが CakePHP に実装されていない、データベース独自の型が必要な場合、 CakePHP の型システムに新たな型を追加することができます。 Type クラスは次のメソッドを実装することが期待されます。

toPHP: 与えられた値をデータベース型から PHP で等価な値にキャストします。

toDatabase: 与えられた値を PHP 型からデータベースで受け入れ可能な値にキャストします。

toStatement: 与えられた値をステートメントの型にキャストします。

marshal: フラットデータを PHP オブジェクトに変換します。

基本的なインターフェイスを満たす簡単な方法は、 Cake\Database\Type を拡張することです。例えば、もしあなたが JSON 型を追加したいなら、下記のような型クラスを作成します。

// src/Database/Type/JsonType.php の中で

namespace App\Database\Type;

use Cake\Database\DriverInterface;
use Cake\Database\Type\BaseType;
use PDO;

class JsonType extends BaseType
{
    public function toPHP($value, DriverInterface $driver)
    {
        if ($value === null) {
            return null;
        }
        return json_decode($value, true);
    }

    public function marshal($value)
    {
        if (is_array($value) || $value === null) {
            return $value;
        }
        return json_decode($value, true);
    }

    public function toDatabase($value, DriverInterface $driver)
    {
        return json_encode($value);
    }

    public function toStatement($value, DriverInterface $driver)
    {
        if ($value === null) {
            return PDO::PARAM_NULL;
        }
        return PDO::PARAM_STR;
    }

}

デフォルトでは toStatement() メソッドは新しい型の値を文字列として扱います。私たちは新しい型を作成したら、型マッピングに追加しなければなりません。アプリケーションの bootstrap 時に、次の事を行います。

use Cake\Database\TypeFactory;

TypeFactory::map('json', 'App\Database\Type\JsonType');

こうすればスキーマ情報は新しい型で上書きされ、CakePHP のデータベース層は自動的に JSON データを変換してクエリーを作成します。あなたは Table の getSchema() メソッド で、新たに作った型のマッピングをすることができます。

use Cake\Database\Schema\TableSchemaInterface;

class WidgetsTable extends Table
{

    public function getSchema(): TableSchemaInterface
    {
        $schema = parent::getSchema();
        $schema->columnType('widget_prefs', 'json');

        return $schema;
    }

}

独自データ型から SQL 表現への変換

前の例は、SQL 文の文字列として表現しやすい 'json' カラム型のための独自データ型に変換します。複雑な SQL データ型は、SQL クエリーの文字列や整数として表現することはできません。これらのデータ型を動作させる際、あなたの Type クラスは、 Cake\Database\Type\ExpressionTypeInterface インスタンスを実装する必要があります。例として、MySQL の POINT 型データのためのシンプルな Type クラスを作成します。最初に、PHP の POINT データを表現するために使用する「値」オブジェクトを定義します。

// src/Database/Point.php の中で
namespace App\Database;

// 値オブジェクトはイミュータブルです。
class Point
{
    protected $_lat;
    protected $_long;

    // ファクトリーメソッド
    public static function parse($value)
    {
        // MySQLからWKBデータを解析します。
        $unpacked = unpack('x4/corder/Ltype/dlat/dlong', $value);

        return new static($unpacked['lat'], $unpacked['long']);
    }

    public function __construct($lat, $long)
    {
        $this->_lat = $lat;
        $this->_long = $long;
    }

    public function lat()
    {
        return $this->_lat;
    }

    public function long()
    {
        return $this->_long;
    }
}

値オブジェクトを作成することで、この値オブジェクトや SQL 表現にデータを変換する Type クラスが必要になります。

namespace App\Database\Type;

use App\Database\Point;
use Cake\Database\DriverInterface;
use Cake\Database\Expression\FunctionExpression;
use Cake\Database\ExpressionInterface;
use Cake\Database\Type\BaseType;
use Cake\Database\Type\ExpressionTypeInterface;

class PointType extends BaseType implements ExpressionTypeInterface
{
    public function toPHP($value, DriverInterface $d)
    {
        return Point::parse($value);
    }

    public function marshal($value)
    {
        if (is_string($value)) {
            $value = explode(',', $value);
        }
        if (is_array($value)) {
            return new Point($value[0], $value[1]);
        }
        return null;
    }

    public function toExpression($value): ExpressionInterface
    {
        if ($value instanceof Point) {
            return new FunctionExpression(
                'POINT',
                [
                    $value->lat(),
                    $value->long()
                ]
            );
        }
        if (is_array($value)) {
            return new FunctionExpression('POINT', [$value[0], $value[1]]);
        }
        // その他のケースを処理
    }

    public function toDatabase($value, DriverInterface $driver)
    {
        return $value;
    }
}

上記のクラスは、いくつかの興味深い特徴があります。

toPHP メソッドは、SQL クエリーの結果を値オブジェクトにパースします。

marchal メソッドは、例えばリクエストデータで与えられたデータから値オブジェクトへ変換します。 '10.24,12.34 のような文字列や配列を受け取れるようにしています。

toExpression メソッドは、値オブジェクトから同等の SQL 表現へ変換します。例えば、結果の SQL は、 POINT(10.24, 12.34) のようになります。

一度独自の型を作成したら、 独自の型をテーブルクラスと関連づける必要があります。

イミュータブル DateTime オブジェクトの有効化

Date/Time オブジェクトは容易に変更されてしまうため、CakePHP はイミュータブルなオブジェクトを利用できるようなっています。以下の設定は、 あなたのアプリケーションの config/bootstrap.php ファイル内で行うのが最適です。

TypeFactory::build('datetime')->useImmutable();
TypeFactory::build('date')->useImmutable();
TypeFactory::build('time')->useImmutable();
TypeFactory::build('timestamp')->useImmutable();

注釈

新しいアプリケーションは、デフォルトでイミュータブルオブジェクトが有効になります。

Connection クラス

class Cake\Database\Connection

Connection クラスは、一貫性のある方法でデータベースコネクションと対話するためのシンプルなインターフェイスを提供します。これはドライバー層への基底インターフェイスであり、クエリーの実行、クエリーのロギング、トランザクション処理といった機能を提供するためのものです。

クエリーの実行

Cake\Database\Connection::query($sql)

あなたがコネクションオブジェクトを取得したら、恐らく何らかのクエリーを発行したくなるでしょう。 CakePHP のデータベース抽象化レイヤは、PDO とネイティブドライバー上にラッパー機能を提供します。これらのラッパーは PDO と似たようなインターフェイスを提供します。クエリーを実行する方法は、あなたが実行したいクエリーと取得したい結果の種類に応じていくつかあります。もっとも基本的な方法は、完全な SQL クエリーの実行を可能にする query() です。

$statement = $connection->query('UPDATE articles SET published = 1 WHERE id = 2');

Cake\Database\Connection::execute($sql, $params, $types)

query() メソッドは追加パラメーターを受け付けません。もし追加パラメーターが必要なら、プレースホルダーを使用可能な execute() メソッドを使用します。

$statement = $connection->execute(
    'UPDATE articles SET published = ? WHERE id = ?',
    [1, 2]
);

型に関する情報がない場合は、 execute は全てのプレースホルダーを文字列とみなします。もし特定の型にバインドする必要があるなら、クエリーを生成する時に型名を指定することができます。

$statement = $connection->execute(
    'UPDATE articles SET published_date = ? WHERE id = ?',
    [new DateTime('now'), 2],
    ['date', 'integer']
);

Cake\Database\Connection::newQuery()

これはあなたのアプリケーションで豊富なデータ型を使用し、適切に SQL 文に変換することができます。クエリーを作成する最後の、そして最も柔軟な方法は、 クエリービルダー を使用することです。この方法では、プラットフォーム固有の SQL を使用することなく、複雑で表現力豊かなクエリーを構築することができます。

$query = $connection->newQuery();
$query->update('articles')
    ->set(['published' => true])
    ->where(['id' => 2]);
$statement = $query->execute();

クエリービルダーを使用する場合は、 execute() メソッドを呼ぶまではサーバーに SQL は送信されず、メソッド呼び出し後に順次処理されます。最初に送信してから、順次結果セットを作成します。

$query = $connection->newQuery();
$query->select('*')
    ->from('articles')
    ->where(['published' => true]);

foreach ($query as $row) {
    // Do something with the row.
}

注釈

もし Cake\ORM\Query のインスタンスを生成しているのなら、 SELECT クエリーの結果セットを取得するのに all() を使用できます。

トランザクションを使う

コネクションオブジェクトは、データベーストランザクションを行うためのいくつかの簡単な方法を提供します。トランザクション操作の最も基本的な方法は、SQL構文と同じような begin() , commit() , rollback() を使用するものです。

$connection->begin();
$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]);
$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]);
$connection->commit();

Cake\Database\Connection::transactional(callable $callback)

このコネクションインスタンスへのインターフェースに加えて、さらに begin/commit/rollback を簡単にハンドリングする transactional() メソッドが提供されています。

$connection->transactional(function ($connection) {
    $connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]);
    $connection->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]);
});

基本的なクエリーに加えて、 クエリービルダー または テーブルオブジェクト のいずれかを使用してより複雑なクエリーを実行することができます。トランザクションメソッドは下記のことを実行します。

begin を呼び出します。

引数で渡されたクロージャーを実行します。

もしクロージャー内で例外が発生したら、ロールバックを発行して例外を再度 throw します。

クロージャーが false を返したら、ロールバックを発行して false を返します。

クロージャーが正常終了したら、トランザクションをコミットします。

ステートメントとの対話

基底レベルのデータベース API を使用していると、ステートメントオブジェクトがよく出てきます。これらのオブジェクトで、ドライバーから基になるプリペアードステートメントを操作できるようになります。クエリーオブジェクトを生成し実行するか execute() を実行した後、あなたは StatementDecorator インスタンスを持つ事になります。これはベースとなる基本的なステートメントオブジェクトをラップして、追加の機能を提供します。

ステートメントを準備する

あなたは execute() か prepare() でステートメントオブジェクトを生成できます。 execute() メソッドは引き継いだ値をバインドしたステートメントを返します。それに対して prepare() は不完全なステートメントを返します。

// execute は指定された値でバインドして SQL ステートメントを実行します。
$statement = $connection->execute(
    'SELECT * FROM articles WHERE published = ?',
    [true]
);

// prepare はプレースホルダーのための準備をします。
// 実行する前にパラメーターをバインドする必要があります。
$statement = $connection->prepare('SELECT * FROM articles WHERE published = ?');

SQL 文を準備したら、あなたは追加のデータをバインドし、それを実行することができます。

値をバインドする

プリペアードステートメントを作成したら、追加のデータをバインドする必要があります。あなたは bind() メソッドを使って一度に複数の値をバインドする事も、 bindValue を使って１項目ずつバインドする事もできます。

$statement = $connection->prepare(
    'SELECT * FROM articles WHERE published = ? AND created > ?'
);

// 複数項目のバインド
$statement->bind(
    [true, new DateTime('2013-01-01')],
    ['boolean', 'date']
);

// １項目ずつのバインド
$statement->bindValue(1, true, 'boolean');
$statement->bindValue(2, new DateTime('2013-01-01'), 'date');

ステートメントを作成する時には、項目の通し番号ではなく、項目名の配列をキーに使用することもできます。

$statement = $connection->prepare(
    'SELECT * FROM articles WHERE published = :published AND created > :created'
);

// 複数項目のバインド
$statement->bind(
    ['published' => true, 'created' => new DateTime('2013-01-01')],
    ['published' => 'boolean', 'created' => 'date']
);

// １項目ずつのバインド
$statement->bindValue('published', true, 'boolean');
$statement->bindValue('created', new DateTime('2013-01-01'), 'date');

警告

同じステートメント内で、項目の通し番号と項目名のキーを混在させることはできません。

実行と結果行の取得

プリペアードステートメントを作成してデータをバインドしたら、実行して行フェッチすることができます。ステートメントは execute() メソッドで実行します。一度実行したら、結果は fetch() か fetchAll() を使ってフェッチします。

$statement->execute();

// １行読み込む
$row = $statement->fetch('assoc');

// 全行を読み込む
$rows = $statement->fetchAll('assoc');

// 全行読み込んだ結果を順次処理する
foreach ($statement as $row) {
    // Do work
}

注釈

読み込んだフェッチする時には、２つのモードを使用することができます。結果配列のキーを項目の通番にする場合 (num) と、項目名をキーにする場合 (assoc) です。

行数を取得する

ステートメントを実行したら、下記のように対象行数を取得することができます。

$rowCount = count($statement);
$rowCount = $statement->rowCount();

エラーコードをチェックする

あなたのクエリーが成功しなかった場合は、エラー関連情報を errorCode() と errorInfo() メソッドによって取得することができます。このメソッドは PDO で提供されているものと同じように動作します。

$code = $statement->errorCode();
$info = $statement->errorInfo();

クエリーロギング

あなたのコネクションを設定する時に、 log オプションに true をセットするとクエリーのログを有効にすることができます。また、 logQueries を使って実行中にクエリーログを切り替えることができます。

// クエリーログを有効
$connection->enableQueryLogging(true);

// クエリーログを停止
$connection->enableQueryLogging(false);

クエリーログを有効にしていると、 'debug' レベルで 'queriesLog' スコープで Cake\Log\Log にクエリーをログ出力します。あなたはこのレベル・スコープを出力するようにログ設定をする必要があります。 stderr にログ出力するのはユニットテストの時に便利で、files/syslog に出力するのは Web リクエストの時に便利です。

use Cake\Log\Log;

// Console logging
Log::setConfig('queries', [
    'className' => 'Console',
    'stream' => 'php://stderr',
    'scopes' => ['queriesLog']
]);

// File logging
Log::setConfig('queries', [
    'className' => 'File',
    'path' => LOGS,
    'file' => 'queries.log',
    'scopes' => ['queriesLog']
]);

注釈

クエリーログは、デバッグまたは開発用途での利用を想定しています。アプリケーションのパフォーマンスに悪影響を及ぼしますので、公開サイトでは利用すべきではありません。

引用識別子

デフォルトの CakePHP では、生成される SQL 文は引用符で囲まれて いません 。その理由は、引用識別子はいくつかの問題があるためです。

パフォーマンスへの負荷 - 引用符を使うと、使わない時よりずっと遅く、複雑になります。

ほとんどの場合に不要 - CakePHP の規約に従う新しいデータベースでは、引用符で囲む必要はありません。

もしあなたが引用符が必要な古いスキーマを使用しているなら、データベースの設定 で quoteIdentifiers を設定すると引用符を使うことができます。また、実行時にこの機能を有効にすることもできます。

$connection->getDriver()->enableAutoQuoting();

有効にすると、引用識別子は 全ての識別子を IdentifierExpression オブジェクトに変換するトラバーサルが発生する原因になります。

注釈

QueryExpression オブジェクトに含まれる SQL スニペットは変換されません。

メタデータ・キャッシング

CakePHP の ORM は、あなたのアプリケーションのスキーマ、インデックス、外部キーを決定するために、データベースリフレクションを使用します。このメタデータは頻繁に変更され、アクセスにコストがかかるため、一般的にキャッシュされます。デフォルトでは、メタデータは _cake_model_ キャッシュ設定に保存されます。あなたはデータベース設定の cacheMetatdata オプションを使ってカスタムキャッシュ設定を定義することができます。

'Datasources' => [
    'default' => [
        // Other keys go here.

        // Use the 'orm_metadata' cache config for metadata.
        'cacheMetadata' => 'orm_metadata',
    ]
],

実行時に cacheMetadata() メソッドを使ってメタデータのキャッシュを設定することもできます。

// キャッシュを無効化
$connection->cacheMetadata(false);

// キャッシュを有効化
$connection->cacheMetadata(true);

// カスタムキャッシュ設定を利用
$connection->cacheMetadata('orm_metadata');

CakePHP にはメタデータキャッシュを管理するための CLI ツールも同梱しています。詳細については スキーマキャッシュツール を参照してください。

データベースの作成

もし、データベースを選択せずに接続したい場合、データベース名を省略してください。

$dsn = 'mysql://root:password@localhost/';

これでデータベースの作成や変更のクエリーを実行するためにコネクションオブジェクトが使えます。

$connection->query("CREATE DATABASE IF NOT EXISTS my_database");

注釈

データベースを作成する場合、文字コードや照合順序をセットすることをお勧めします。もしこれらの値がなかった場合、データベースはシステムのデフォルト値をセットします。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

クエリービルダー

class Cake\ORM\Query

ORM のクエリービルダーにより簡単に流れるようなインターフェイスを使ってクエリーを作り、実行することができます。クエリーを組み立てることで、 union やサブクエリーを使った高度なクエリーも簡単に作成することができます。

クエリービルダーは裏側で PDO プリペアードステートメント（prepared statement）を使うことで、 SQL インジェクション攻撃から守っています。

クエリーオブジェクト

Query オブジェクトを作成するもっとも簡単な方法は Table オブジェクトから find() を使うことです。このメソッドは完結していない状態のクエリーを返し、このクエリーは変更可能です。必要なら、テーブルのコネクションオブジェクトも使うことで、ORM 機能を含まない、より低い層のクエリービルダーにアクセスすることもできます。この詳細は クエリーの実行のセクションを参照してください。

use Cake\ORM\TableRegistry;

$articles = TableRegistry::getTableLocator()->get('Articles');

// 新しいクエリーを始めます。
$query = $articles->find();

コントローラーの中では自動的に慣習的な機能を使って作成されるテーブル変数を使うことができます。

// ArticlesController.php の中で

$query = $this->Articles->find();

テーブルから行を取得する

use Cake\ORM\Locator\LocatorAwareTrait;

$query = $this->getTableLocator()->get('Articles')->find();

foreach ($query->all() as $article) {
    debug($article->title);
}

以降の例では $articles は Table であると想定します。なお、コントローラーの中では $articles の代わりに $this->Articles を使うことができます。

Query オブジェクトのほとんどのメソッドが自分自身のクエリーオブジェクトを返します。これは Query が遅延評価される(lazy)ことを意味し、必要になるまで実行されないことを意味します。

$query->where(['id' => 1]); // 自分自身のクエリーオブジェクトを返します
$query->order(['title' => 'DESC']); // 自分自身を返し、SQL はまだ実行されません。

もちろん Query オブジェクトの呼び出しではメソッドをチェーンすることもできます。

$query = $articles
    ->find()
    ->select(['id', 'name'])
    ->where(['id !=' => 1])
    ->order(['created' => 'DESC']);

foreach ($query->all() as $article) {
    debug($article->created);
}

Query オブジェクトを debug() で使うと、内部の状態とデータベースで実行されることになる SQL が出力されます。

debug($articles->find()->where(['id' => 1]));

// 出力
// ...
// 'sql' => 'SELECT * FROM articles where id = ?'
// ...

foreach を使わずに、クエリーを直接実行することができます。もっとも簡単なのは all() メソッドか toList() メソッドのどちらかを呼ぶ方法です。

$resultsIteratorObject = $articles
    ->find()
    ->where(['id >' => 1])
    ->all();

foreach ($resultsIteratorObject as $article) {
    debug($article->id);
}

$resultsArray = $articles
    ->find()
    ->where(['id >' => 1])
    ->toList();

foreach ($resultsArray as $article) {
    debug($article->id);
}

debug($resultsArray[0]->title);

上記の例では、 $resultsIteratorObject は Cake\ORM\ResultSet のインスタンスです。このインスタンスはイテレートすることができ、それが持つメソッドで部分取り出しなどができます。

多くの場合、 all() を呼ぶ必要はなく、単に Query オブジェクトをイテレートすることで、結果を得ることができます。Query オブジェクトはまた、結果オブジェクトとして直接使うこともできます。クエリーをイテレートしたり、 toList() を呼んだり、 Collection から継承したメソッドを呼んだりすると、クエリーは実行され、結果が返ります。

テーブルから単一行を取得する

first() メソッドを使うことでクエリーの最初の結果を取得することができます。

$article = $articles
    ->find()
    ->where(['id' => 1])
    ->first();

debug($article->title);

カラムから値リストを取得する

// Collection ライブラリーの extract() メソッドを使います
// これもクエリーを実行します
$allTitles = $articles->find()->all()->extract('title');

foreach ($allTitles as $title) {
    echo $title;
}

クエリーの結果から key-value リストを得ることもできます。

$list = $articles->find('list');

$list = $articles->find('list')->all();
foreach ($list as $id => $title) {
    echo "$id : $title"
}

リストを生成するために使用するフィールドのカスタマイズの方法の詳しい情報は、キー/値のペアを検索する セクションを参照してください。

クエリーは Collection オブジェクトである

Query オブジェクトのメソッドに慣れたら、 Collection を見て、効果的にデータを横断するスキルを磨くことを強くお勧めします。つまり、 Collection オブジェクトで呼ぶことができるものは、 Query オブジェクトでも呼ぶことができることを、知っておくことは重要です。

// Collection ライブラリーの combine() メソッドを使います
// これは find('list') と等価です
$keyValueList = $articles->find()->combine('id', 'title');

// 上級な例
$results = $articles->find()
    ->where(['id >' => 1])
    ->order(['title' => 'DESC'])
    ->all()
    ->map(function ($row) {
    // map() は Collection のメソッドで、クエリーを実行します
        $row->trimmedTitle = trim($row->title);
        return $row;
    })
    ->combine('id', 'trimmedTitle') // combine() も Collection のメソッドです
    ->toArray(); // これも Collection のメソッドです

foreach ($results as $id => $trimmedTitle) {
    echo "$id : $trimmedTitle";
}

クエリーの遅延評価

Query オブジェクトは遅延評価されます。これはクエリーが次のいずれかが起こるまで実行されないということを意味します。

クエリーが foreach() でイテレートされる。

クエリーの execute() メソッドが呼ばれる。これは下層の statement オブジェクトを返し、 insert/update/delete クエリーで使うことができます。

クエリーの first() メソッドが呼ばれる。 SELECT (それがクエリーに LIMIT 1 を加えます) で構築された結果セットの最初の結果が返ります。

クエリーの all() メソッドが呼ばれる。結果セットが返り、 SELECT ステートメントでのみ使うことができます。

クエリーの toList() や toArray() メソッドが呼ばれる。

このような条件が合致するまでは、 SQL をデータベースへ送らずに、クエリーを変更することができます。つまり、 Query が評価されないかぎり、SQL はデータベースへ送信されないのです。クエリーが実行された後に、クエリーを変更・再評価したら、追加で SQL が走ることになります。

CakePHP が生成している SQL がどんなものか見たいなら、クエリーロギング を on にしてください。

データを select する

CakePHP では SELECT クエリーを簡単につくれます。取得するフィールドを制限するのには、 select() メソッドを使います。

$query = $articles->find();
$query->select(['id', 'title', 'body']);
foreach ($query as $row) {
    debug($row->title);
}

連想配列でフィールドを渡すことでフィールドのエイリアス (別名) をセットすることができます。

// SELECT id AS pk, title AS aliased_title, body ... になる
$query = $articles->find();
$query->select(['pk' => 'id', 'aliased_title' => 'title', 'body']);

フィールドを select distinct するために、 distinct() メソッドを使うことができます。

// SELECT DISTINCT country FROM ... になる
$query = $articles->find();
$query->select(['country'])
    ->distinct(['country']);

基本の条件をセットするには、 where() メソッドを使うことができます。

// 条件は AND で連結されます
$query = $articles->find();
$query->where(['title' => 'First Post', 'published' => true]);

// where() を複数回呼んでもかまいません
$query = $articles->find();
$query->where(['title' => 'First Post'])
    ->where(['published' => true]);

無名関数を where() メソッドに渡すこともできます。渡された無名関数は、第一引数として \Cake\Database\Expression\QueryExpression のインスタンス、第二引数として \Cake\ORM\Query を受け取ります。

$query = $articles->find();
$query->where(function (QueryExpression $exp, Query $q) {
    return $exp->eq('published', true);
});

さらに複雑な WHERE 条件の作り方を知りたい場合は 高度な条件のセクションをご覧ください。ソート順を適用するために、 order メソッドが使用できます。

$query = $articles->find()
    ->order(['title' => 'ASC', 'id' => 'ASC']);

一つのクエリーで order() を複数回呼ぶと、複数の句が追加されます。しかし、ファインダーを使用する時、 ORDER BY を上書きする必要が生じることもあります。 order() (orderAsc() や orderDesc() も同様に) の第２パラメーターに Query::OVERWRITE または true をセットしてください。

$query = $articles->find()
    ->order(['title' => 'ASC']);
// あとで、ORDER BY 句を追加の代わりに上書きします。
$query = $articles->find()
    ->order(['created' => 'DESC'], Query::OVERWRITE);

複合的な式でソートする必要があるなら order に加えて、 orderAsc と orderDesc メソッドが使えます。

$query = $articles->find();
$concat = $query->func()->concat([
    'title' => 'identifier',
    'synopsis' => 'identifier'
]);
$query->orderAsc($concat);

行の数を制限したり、行のオフセットをセットするためには、 limit() と page() メソッドを使うことができます。

// 50 から 100 行目をフェッチする
$query = $articles->find()
    ->limit(50)
    ->page(2);

上記の例にあるように、クエリーを変更するすべてのメソッドは流暢 (fluent) なインターフェイスを提供しますので、クエリーを構築する際にチェーンメソッドの形で呼び出すことができます。

特定のフィールドを選択

クエリーはデフォルトでテーブルのすべてのフィールドを選択します。例外となるのは select() 関数をあえて呼び、特定のフィールドを指定した場合だけです。

// articles テーブルから id と title だけが選択される
$articles->find()->select(['id', 'title']);

select($fields) を呼んで、なおもテーブルのすべてのフィールドを選択したいなら、次の方法でテーブルインスタンスを select() に渡すことができます。

// 計算された slug フィールドを含む、 articles テーブルのすべてのフィールドを取得
$query = $articlesTable->find();
$query
    ->select(['slug' => $query->func()->concat(['title' => 'identifier', '-', 'id' => 'identifier'])])
    ->select($articlesTable); // articles のすべてのフィールドを選択する

テーブル上のいくつかのフィールド以外のすべてのフィールドを選択したい場合には selectAllExcept() を使用できます。

$query = $articlesTable->find();

// published フィールドを除く全てのフィールドを取得
$query->selectAllExcept($articlesTable, ['published']);

アソシエーションが含まれる場合、 Association オブジェクトを渡すこともできます。

SQL 関数を使う

CakePHP の ORM では抽象化された馴染み深い SQL 関数をいくつか使えるようになっています。抽象化により ORM は、プラットフォーム固有の実装を選んで関数を実行できるようになっています。たとえば、 concat は MySQL、PostgreSQL、SQL Server で異なる実装がされています。抽象化によりあなたのコードが移植しやすいものになります。

// SELECT COUNT(*) count FROM ... になる
$query = $articles->find();
$query->select(['count' => $query->func()->count('*')]);

多くのおなじみの関数が func() メソッドとともに作成できます:

rand()

0から1の間の乱数をSQLで生成します。

sum()

合計を算出します。 引数はリテラル値として扱われます。

avg()

平均値を算出します。 引数はリテラル値として扱われます。

min()

カラムの最小値を算出します。 引数はリテラル値として扱われます。

max()

カラムの最大値を算出します。 引数はリテラル値として扱われます。

count()

件数を算出します。 引数はリテラル値として扱われます。

concat()

２つの値を結合します。 引数はバインドパラメーターとして扱われます。

coalesce()

Coalesce を算出します。 引数はバインドパラメーターとして扱われます。

dateDiff()

２つの日にち/時間の差を取得します。 引数はバインドパラメーターとして扱われます。

now()

デフォルトでは日付と時刻を返しますが、 'time' または 'date' を指定してこれらの値のみを返すこともできます。

extract()

SQL 式から特定の日付部分(年など)を返します。

dateAdd()

日付式に単位時間を加算します。

dayOfWeek()

SQL の WEEKDAY 関数を呼ぶ FunctionExpression を返します。

SQL 関数に渡す引数には、リテラルの引数と、バインドパラメーターの２種類がありえます。識別子やリテラルのパラメーターにより、カラムや他の SQL リテラルを参照できます。バインドパラメーターにより、ユーザーデータを SQL 関数へと安全に渡すことができます。たとえば

$query = $articles->find()->innerJoinWith('Categories');
$concat = $query->func()->concat([
    'Articles.title' => 'identifier',
    ' - CAT: ',
    'Categories.name' => 'identifier',
    ' - Age: ',
    $query->func()->dateDiff(
        'NOW()' => 'literal',
        'Articles.created' => 'identifier'
    )
]);
$query->select(['link_title' => $concat]);

literal の値を伴う引数を作ることで、 ORM はそのキーをリテラルな SQL 値として扱うべきであると知ることになります。 identifier の値を伴う引数を作ることで、ORM は、そのキーがフィールドの識別子として扱うべきであると知ることになります。

上記では MySQL にて下記の SQL が生成されます。

SELECT CONCAT(
    Articles.title,
    :c0,
    Categories.name,
    :c1,
    (DATEDIFF(NOW(), Articles.created))
) FROM articles;

クエリーが実行される際には、 :c0 という値に ' - CAT' というテキストがバインドされることになります。 dateDiff 式は適切なSQLに変換されます。

カスタム関数

もし func() が必要なSQL関数をラップしていない場合は、 func() を使って直接呼び出すことができ、引数やユーザデータを安全に渡すことができます。カスタム関数には適切な型の引数を渡すようにしてください。

$query = $articles->find();
$year = $query->func()->year([
    'created' => 'identifier'
]);
$time = $query->func()->date_format([
    'created' => 'identifier',
    "'%H:%i'" => 'literal'
]);
$query->select([
    'yearCreated' => $year,
    'timeCreated' => $time
]);

これらのカスタム関数は、 MySQL にて下記の SQL を生成します。

SELECT YEAR(created) as yearCreated,
       DATE_FORMAT(created, '%H:%i') as timeCreated
FROM articles;

注釈

信頼されていないユーザのデータを任意のSQL関数に渡すには func() を使ってください。

集約 - Group と Having

count や sum のような集約関数を使う際には、 group by や having 句を使いたいことでしょう。

$query = $articles->find();
$query->select([
    'count' => $query->func()->count('view_count'),
    'published_date' => 'DATE(created)'
])
->group('published_date')
->having(['count >' => 3]);

Case 文

ORM ではまた、 SQL の case 式も使えます。 case 式により if ... then ... else のロジックを SQL の中に実装することができます。これは条件付きで sum や count をしなければならない状況や、条件に基いてデータを特定しなければならない状況で、データを出力するのに便利です。

公開済みの記事（published articles）がデータベース内にいくつあるのか知りたい場合、次の SQL を使用することができます。

SELECT
COUNT(CASE WHEN published = 'Y' THEN 1 END) AS number_published,
COUNT(CASE WHEN published = 'N' THEN 1 END) AS number_unpublished
FROM articles

これはクエリービルダーでは次のようなコードになります。

$query = $articles->find();
$publishedCase = $query->newExpr()
    ->addCase(
        $query->newExpr()->add(['published' => 'Y']),
        1,
        'integer'
    );
$unpublishedCase = $query->newExpr()
    ->addCase(
        $query->newExpr()->add(['published' => 'N']),
        1,
        'integer'
    );

$query->select([
    'number_published' => $query->func()->count($publishedCase),
    'number_unpublished' => $query->func()->count($unpublishedCase)
]);

addCase 関数は SQL 内で if .. then .. [elseif .. then .. ] [ .. else ] ロジックを構築するために複数の文を一まとめに書くことができます。

町 (city) を人口 (population size) に基いて SMALL、MEDIUM、LARGE に分類するなら、次のようになります。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->addCase(
            [
                $q->newExpr()->lt('population', 100000),
                $q->newExpr()->between('population', 100000, 999000),
                $q->newExpr()->gte('population', 999001),
            ],
            ['SMALL',  'MEDIUM', 'LARGE'], # 条件に合致したときの値
            ['string', 'string', 'string'] # それぞれの値の型
        );
    });
# WHERE CASE
#   WHEN population < 100000 THEN 'SMALL'
#   WHEN population BETWEEN 100000 AND 999000 THEN 'MEDIUM'
#   WHEN population >= 999001 THEN 'LARGE'
#   END

値リストよりも case 条件リストの方が少ない場合はいつでも、 addCase は自動的に if .. then .. else 文を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->addCase(
            [
                $q->newExpr()->eq('population', 0),
            ],
            ['DESERTED', 'INHABITED'], # 条件に合致したときの値
            ['string', 'string'] # それぞれの値の型
        );
    });
# WHERE CASE
#   WHEN population = 0 THEN 'DESERTED' ELSE 'INHABITED' END

エンティティーの代わりに配列を取得

ORM とオブジェクトの結果セットは強力である一方で、エンティティーの作成が不要なときもあります。たとえば、集約されたデータにアクセスする場合、Entity を構築するのは無意味です。データベースの結果をエンティティーに変換する処理は、ハイドレーション (hydration) と呼ばれます。もし、この処理を無効化したい場合、このようにします。

$query = $articles->find();
$query->enableHydration(false); // エンティティーの代わりに配列を返す
$result = $query->toList(); // クエリーを実行し、配列を返す

これらの行を実行した後、結果はこのようになります。

[
    ['id' => 1, 'title' => 'First Article', 'body' => 'Article 1 body' ...],
    ['id' => 2, 'title' => 'Second Article', 'body' => 'Article 2 body' ...],
    ...
]

計算フィールドを追加する

クエリー後に何か後処理をする必要があるかもしれません。計算フィールドや生成された (derived) データをいくつか追加する必要があるなら、 formatResults() メソッドを使うことができます。これにより軽い負荷で、結果セットを map することができます。この処理をこれ以上に制御する必要があるなら、もしくは、結果セットを reduce する必要があるなら、 Map/Reduce 機能を代わりに使ってください。人々のリストを問い合わせる際に、formatResults を使って年齢 (age) を算出するなら次のようになります。

// フィールド、条件、関連が構築済であると仮定します。
$query->formatResults(function (\Cake\Collection\CollectionInterface $results) {
    return $results->map(function ($row) {
        $row['age'] = $row['birth_date']->diff(new \DateTime)->y;
        return $row;
    });
});

上記の例にあるように、フォーマッタ関数(フォーマットするコールバック)の第１引数に ResultSetDecorator が渡されています。第２引数にはフォーマッタ関数がセットされる Query インスタンスが渡されます。引数の $results は必要に応じて、取り出しでも変換でもすることができます。

フォーマッタ関数は、クエリーが値を返せるようにするために、イテレータオブジェクトを返す必要があります。フォーマッタ関数はすべての Map/Reduce が実行し終わった後、適用されます。 contain された関連の中からでも同じようにフォーマッタ関数を適用することができます。 CakePHP はフォーマッタ関数が適切なスコープになるよう保証します。たとえば、下記のようにした場合でも、期待どおりに動きます。

// Articles テーブル内のメソッドで
$query->contain(['Authors' => function ($q) {
    return $q->formatResults(function (\Cake\Collection\CollectionInterface $authors) {
        return $authors->map(function ($author) {
            $author['age'] = $author['birth_date']->diff(new \DateTime)->y;
            return $author;
        });
    });
}]);

// 結果を取得する
$results = $query->all();

// 29 が出力される
echo $results->first()->author->age;

上記にあるように、関連付いたクエリービルダーに設置されたフォーマッタ関数は、関連付いたデータの中だけの操作にスコープが限定されます。 CakePHP は計算された値が正しい Entity にセットされることを保証します。

高度な条件

クエリービルダーは複雑な where 句の構築を簡単にします。グループ化された条件は、 where() と Expression オブジェクトを組み合わせることで表現できます。単純なクエリーの場合、条件の配列を使用して条件を作成できます。

$query = $articles->find()
    ->where([
        'author_id' => 3,
        'OR' => [['view_count' => 2], ['view_count' => 3]],
    ]);

上記は次のような SQL を生成します。

SELECT * FROM articles WHERE author_id = 3 AND (view_count = 2 OR view_count = 3)

深くネストされた配列を避けたい場合は、 where() のコールバック形式を使用してクエリーを構築することができます。コールバック形式を使用すると、式ビルダーを使用して配列なしでより複雑な条件を作成できます。例:

$query = $articles->find()->where(function (QueryExpression $exp, Query $query) {
    // 同一フィールドに複数条件を追加するために add() を使用
    $author = $query->newExpr()->or(['author_id' => 3])->add(['author_id' => 2]);
    $published = $query->newExpr()->and(['published' => true, 'view_count' => 10]);

    return $exp->or([
        'promoted' => true,
        $query->newExpr()->and([$author, $published])
    ]);
});

上記は次のような SQL を出力します。

SELECT *
FROM articles
WHERE (
    (
        (author_id = 2 OR author_id = 3)
        AND
        (published = 1 AND view_count = 10)
    )
    OR promoted = 1
)

コールバックに渡される QueryExpression は完全な式を構築するために、 combinators と conditions を使用します。

Combinators

これらは新しい QueryExpression オブジェクトを作成し、その式に追加された条件をどのように結合するかを設定します。

and() は、すべての条件を AND で結合する新しい式オブジェクトを作成します。

or() `` は、すべての条件を ``OR で結合する新しい式オブジェクトを作成します。

Conditions

これらは式に追加され、どの組み合わせが使用されたかに応じて自動的に結合されます。

コールバック関数に渡される QueryExpression のデフォルトは and() です。

$query = $articles->find()
    ->where(function (QueryExpression $exp) {
        return $exp
            ->eq('author_id', 2)
            ->eq('published', true)
            ->notEq('spam', true)
            ->gt('view_count', 10);
    });

where() を使い始めた場合、 and() は暗黙的に選ばれているため、それを呼ぶ必要はありません。上記の例では新たな条件がいくつか AND で結合されています。結果の SQL は次のようになります。

SELECT *
FROM articles
WHERE (
author_id = 2
AND published = 1
AND spam != 1
AND view_count > 10)

ただし、 AND と OR の両方を使いたいなら、次のようにすることもできます。

$query = $articles->find()
    ->where(function (QueryExpression $exp) {
        $orConditions = $exp->or(['author_id' => 2])
            ->eq('author_id', 5);
        return $exp
            ->add($orConditions)
            ->eq('published', true)
            ->gte('view_count', 10);
    });

これは下記のような SQL を生成します。

SELECT *
FROM articles
WHERE (
    (author_id = 2 OR author_id = 5)
    AND published = 1
    AND view_count >= 10
)

combinators では、メソッドのチェーンを分離したい場合に、新しい式オブジェクトをパラメータとして受け取るコールバックを渡すこともできます。

$query = $articles->find()
    ->where(function (QueryExpression $exp) {
        $orConditions = $exp->or(function (QueryExpression $or) {
            return $or->eq('author_id', 2)
                ->eq('author_id', 5);
        });
        return $exp
            ->not($orConditions)
            ->lte('view_count', 10);
    });

not() を使って式を否定することができます。

$query = $articles->find()
    ->where(function (QueryExpression $exp) {
        $orConditions = $exp->or(['author_id' => 2])
            ->eq('author_id', 5);
        return $exp
            ->not($orConditions)
            ->lte('view_count', 10);
    });

これは下記のような SQL を生成します。

SELECT *
FROM articles
WHERE (
    NOT (author_id = 2 OR author_id = 5)
    AND view_count <= 10
)

SQL 関数を使った式を構築することも可能です。

$query = $articles->find()
    ->where(function (QueryExpression $exp, Query $q) {
        $year = $q->func()->year([
            'created' => 'identifier'
        ]);
        return $exp
            ->gte($year, 2014)
            ->eq('published', true);
    });

これは下記のような SQL を生成します。

SELECT *
FROM articles
WHERE (
    YEAR(created) >= 2014
    AND published = 1
)

Expression オブジェクトを使う際、下記のメソッド使って条件を作成できます:

eq() 等号の条件を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->eq('population', '10000');
    });
# WHERE population = 10000

notEq() 不等号の条件を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->notEq('population', '10000');
    });
# WHERE population != 10000

like() LIKE 演算子を使った条件を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->like('name', '%A%');
    });
# WHERE name LIKE "%A%"

notLike() LIKE 条件の否定を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->notLike('name', '%A%');
    });
# WHERE name NOT LIKE "%A%"

in() IN を使った条件を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->in('country_id', ['AFG', 'USA', 'EST']);
    });
# WHERE country_id IN ('AFG', 'USA', 'EST')

notIn() IN を使った条件の否定を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->notIn('country_id', ['AFG', 'USA', 'EST']);
    });
# WHERE country_id NOT IN ('AFG', 'USA', 'EST')

gt() > の条件を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->gt('population', '10000');
    });
# WHERE population > 10000

gte() >= の条件を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->gte('population', '10000');
    });
# WHERE population >= 10000

lt() < の条件を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->lt('population', '10000');
    });
# WHERE population < 10000

lte() <= の条件を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->lte('population', '10000');
    });
# WHERE population <= 10000

isNull() IS NULL の条件を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->isNull('population');
    });
# WHERE (population) IS NULL

isNotNull() IS NULL の条件の否定を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->isNotNull('population');
    });
# WHERE (population) IS NOT NULL

between() BETWEEN の条件を作成します。

$query = $cities->find()
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->between('population', 999, 5000000);
    });
# WHERE population BETWEEN 999 AND 5000000,

exists() EXISTS を使用した条件を作成します。

$subquery = $cities->find()
    ->select(['id'])
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->equalFields('countries.id', 'cities.country_id');
    })
    ->andWhere(['population >' => 5000000]);

$query = $countries->find()
    ->where(function (QueryExpression $exp, Query $q) use ($subquery) {
        return $exp->exists($subquery);
    });
# WHERE EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND population > 5000000)

notExists() EXISTS を使用した条件の否定を作成します。

$subquery = $cities->find()
    ->select(['id'])
    ->where(function (QueryExpression $exp, Query $q) {
        return $exp->equalFields('countries.id', 'cities.country_id');
    })
    ->andWhere(['population >' => 5000000]);

$query = $countries->find()
    ->where(function ($exp, $q) use ($subquery) {
        return $exp->notExists($subquery);
    });
# WHERE NOT EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND population > 5000000)

式オブジェクトは、多くの一般的に使用される関数や式をカバーしている必要があります。式を使って必要な条件を作ることができない場合は、 bind() を使ってパラメータを条件に手動でバインドすることができます。

$query = $cities->find()
    ->where([
        'start_date BETWEEN :start AND :end'
    ])
    ->bind(':start', '2014-01-01', 'date')
    ->bind(':end',   '2014-12-31', 'date');

あなたの望む条件を作成するビルダーメソッドが取得できなかったり利用したくない場合、 WHERE 句の中で SQL スニペットを使えるようにもできます。

// ２つのフィールドをお互いに比較
$query->where(['Categories.parent_id != Parents.id']);

警告

式 (expression) の中で使われる列名や SQL スニペットには安全性が確実でない内容を絶対に含めてはいけません 。関数の呼び出しで、安全でないデータを安全に渡す方法については SQL 関数を使う のセクションを参照してください。

式の中で識別子を使用する

クエリーの中でカラムや SQL 識別子を参照する必要がある場合、 identifier() メソッドが使用できます。

$query = $countries->find();
$query->select([
        'year' => $query->func()->year([$query->identifier('created')])
    ])
    ->where(function ($exp, $query) {
        return $exp->gt('population', 100000);
    });

警告

SQL インジェクションを防ぐため、識別子の式には信頼できないデータを渡すべきではありません。

IN 句を自動生成する

ORM を使ってクエリーをビルドする際、大抵の場合、利用するカラムのデータ型を指定する必要はありません。なぜなら CakePHP はスキーマデータに基いて推測することができるためです。もしクエリーの中で CakePHP に自動的に等号を IN に変えさせたいなら、カラムのデータ型を明示する必要があります。

$query = $articles->find()
    ->where(['id' => $ids], ['id' => 'integer[]']);

// もしくは、自動的に配列へとキャストさせるために IN を含めます。
$query = $articles->find()
    ->where(['id IN' => $ids]);

上記では自動的に id = ? ではなく id IN (...) が作成されます。これは、パラメーターが単数か配列か判らない場合に便利です。データ型名の末尾に付く [] という接尾辞は、扱いたいデータが配列であることをクエリービルダーに知らせます。もしもデータが配列でなかったなら、まず、配列へとキャストされることになります。その後、配列の各値は type system を使ってキャストされることになります。これは複合型であっても同様に動きます。たとえば、DateTime オブジェクトのリストも使うことができます。

$query = $articles->find()
    ->where(['post_date' => $dates], ['post_date' => 'date[]']);

IS NULL を自動生成する

条件の値が null かもしれないし、他の値かもしれない場合、 IS 演算子を使うことで自動的に正しい式が作成されます。

$query = $categories->find()
    ->where(['parent_id IS' => $parentId]);

上記は $parentId の型に応じて parent_id` = :c1 もしくは parent_id IS NULL が自動的に作成されます。

IS NOT NULL を自動生成する

条件として非 null もしくは、他の値でないことを期待する場合、 IS NOT 演算子を使うことで自動的に正しい式が作成されます。

$query = $categories->find()
    ->where(['parent_id IS NOT' => $parentId]);

上記は $parentId の型に応じて parent_id` != :c1 もしくは parent_id IS NOT NULL が自動的に作成されます。

未加工の式

クエリービルダーでは目的の SQL が構築できない場合、Expression オブジェクトを使って、 SQL の断片をクエリーに追加することができます。

$query = $articles->find();
$expr = $query->newExpr()->add('1 + 1');
$query->select(['two' => $expr]);

Expression オブジェクトは where() 、 limit() 。 group() 、 select() 等のようなクエリービルダーのメソッドで使うことができます。

警告

Expression オブジェクトを使うと SQL インジェクションに対して脆弱になります。式の中で信頼できないデータを使用しないでください。

結果を取得する

クエリーができたら、それから行を受け取りたいでしょう。これにはいくつかの方法があります。

// クエリーをイテレートする
foreach ($query as $row) {
    // なにかする
}

// 結果を取得する
$results = $query->all();

Query オブジェクトでは Collection のメソッドがどれでも使えます。それらで結果を前処理したり、変換したりすることができます。

// コレクションのメソッドを使う
$ids = $query->map(function ($row) {
    return $row->id;
});

$maxAge = $query->max(function ($max) {
    return $max->age;
});

first や firstOrFail を使って、単一のレコードを受け取ることができます。これらのメソッドはクエリーに LIMIT 1 句を付加した形に変更します。

// 最初の行だけを取得する
$row = $query->first();

// 最初の行を取得する。できないなら例外とする。
$row = $query->firstOrFail();

レコードの合計数を返す

Query オブジェクトを使って、条件の結果見つかった行の合計数を取得することができます。

$total = $articles->find()->where(['is_active' => true])->count();

count() メソッドは limit、 offset、 page 句を無視します。それゆえ、下記でも同じ結果を返すことになります。

$total = $articles->find()->where(['is_active' => true])->limit(10)->count();

これは、別の Query オブジェクトを構築する必要なく、結果セットの合計数を前もって知ることができるので便利です。同様に、結果のフォーマット (result formatting)、Map/Reduce 処理は count() を使う際には無視されます。

加えて言うと、group by 句を含んだクエリーの合計数を、クエリーを少しも書き直すことなく、取得することが可能です。たとえば、記事 (article) の id と、そのコメント (comment) 件数を取得するクエリーを考えてみましょう。

$query = $articles->find();
$query->select(['Articles.id', $query->func()->count('Comments.id')])
    ->matching('Comments')
    ->group(['Articles.id']);
$total = $query->count();

カウント後でも、結びついたレコードをフェッチするのにクエリーを使うことができます。

$list = $query->all();

ときには、クエリーの合計件数を返すメソッドをカスタマイズしたくなることもあるでしょう。たとえば、値をキャッシュしたり、合計行数を見積もったり、あるいは、left join のような高負荷な部分を取り除くようにクエリーを変更したりなどです。 CakePHP のページネーション (pagination) システムでは count() メソッドを呼び出しますので、これは特に有用です。

$query = $query->where(['is_active' => true])->counter(function ($query) {
    return 100000;
});
$query->count(); // 100000 を返す

上記の例では、PaginatorComponent が count メソッドを呼ぶ際には、ハードコーディングされた行数を受け取ることになります。

ロードされた結果をキャッシュする

変更されることのない Entity をフェッチする際、結果をキャッシュしたいと思うかもしれません。 Query クラスでは簡単にこれを実現できます。

$query->cache('recent_articles');

これでクエリーの結果セットのキャッシュが有効になります。もし cache() に１つだけの引数を渡した場合は、 'default' のキャッシュ・コンフィグレーションが使われることになります。第２引数でどのキャッシュ・コンフィグレーションを使うのかを制御できます。

// 文字列で Config 名
$query->cache('recent_articles', 'dbResults');

// CacheEngine のインスタンス
$query->cache('recent_articles', $memcache);

cache() メソッドは静的なキーだけでなく、 キーを生成する関数も受け取れます。渡す関数は引数でクエリーを受け取りますので、クエリーの内容を読んで動的にキャッシュキーを生成することができます。

// クエリーの where 句の単純なチェックサムに基づくキーを生成します
$query->cache(function ($q) {
    return 'articles-' . md5(serialize($q->clause('where')));
});

キャッシュメソッドはキャッシュされた結果をカスタム finder に渡したり、イベントリスナで使ったりするのを簡単にします。

キャッシュ設定されたクエリーが結果を返すときには次のようなことが起こります:

クエリーが結果セットを保持しているなら、それを返します。

キャッシュキーを解決して、キャッシュデータを読みす。キャッシュデータが空でなければ、その結果を返します。

キャッシュが見つからない場合、クエリが実行され、 Model.beforeFind イベントがトリガーされ、新しい ResultSet が作成されます。この ResultSet はキャッシュに書き込まれ、返されます。

注釈

ストリーミングクエリー (streaming query) の結果をキャッシュすることはできません。

関連付くデータをロードする

クエリービルダーは同時に複数テーブルからデータを取り出す際に、できるだけ最少のクエリーで取り出せるようにしてくれます。関連付くデータをフェッチする際には、まず、アソシエーション - モデル同士を繋ぐ のセクションにあるようにテーブル間の関連をセットアップしてください。他のテーブルから関連するデータをフェッチするためにクエリーを合成する技術を イーガーロード (eager load) といいます。

イーガーロードは、ORM のレイジーロード (lazy load) 周辺に潜むパフォーマンス問題の多くを避けるのに役立ちます。イーガーロードで生成されたクエリーは JOIN に影響を与えて、効率的なクエリーが作られるようになります。 CakePHP では 'contain' メソッドを使って関連データのイーガーロードを定義します。

// コントローラーやテーブルのメソッド内で

// find() のオプションとして
$query = $articles->find('all', ['contain' => ['Authors', 'Comments']]);

// クエリーオブジェクトのメソッドとして
$query = $articles->find('all');
$query->contain(['Authors', 'Comments']);

上記では関連する author と comment を結果セットの article ごとにロードします。ロードする関連データを定義するためのネストされた配列を使って、ネストされた関連データをロードすることができます。

$query = $articles->find()->contain([
    'Authors' => ['Addresses'], 'Comments' => ['Authors']
]);

または、ドット記法を使ってネストされた関連データを表現することもできます。

$query = $articles->find()->contain([
    'Authors.Addresses',
    'Comments.Authors'
]);

好きなだけ深く関連データをイーガーロードできます。

$query = $products->find()->contain([
    'Shops.Cities.Countries',
    'Shops.Managers'
]);

複数の簡単な contain() 文を使って全ての関連データからフィールドを選択できます。

$query = $this->find()->select([
    'Realestates.id',
    'Realestates.title',
    'Realestates.description'
])
->contain([
    'RealestateAttributes' => [
        'Attributes' => [
            'fields' => [
                // contain() の中で別名がつけられたフィールドは、
                // 正しくマップされたモデルのプレフィックスが含まれていなければなりません。
                'Attributes__name' => 'attr_name'
            ]
        ]
    ]
])
->contain([
    'RealestateAttributes' => [
        'fields' => [
            'RealestateAttributes.realestate_id',
            'RealestateAttributes.value'
        ]
    ]
])
->where($condition);

クエリー上の contain を再設定する必要があるなら、第２引数に true を指定することができます。

$query = $articles->find();
$query->contain(['Authors', 'Comments'], true);

contain に条件を渡す

contain() を使う際、関連によって返される列を限定し、条件によってフィルターすることができます。条件を指定するには、第１引数としてクエリーオブジェクト \Cake\ORM\Query を受け取る無名関数を渡します。

// コントローラーやテーブルのメソッド内で

$query = $articles->find()->contain('Comments', function (Query $q) {
    return $q
        ->select(['body', 'author_id'])
        ->where(['Comments.approved' => true]);
});

これは、またコントローラーレベルでページネーションが働きます。

$this->paginate['contain'] = [
    'Comments' => function (Query $query) {
        return $query->select(['body', 'author_id'])
        ->where(['Comments.approved' => true]);
    }
];

注釈

関連によってフェッチされるフィールドを限定する場合、外部キーの列が確実に select されなければなりません 。外部キーのカラムが select されない場合、関連データが最終的な結果の中に無いということがおこります。

ドット記法を使って、深くネストされた関連データを制限することも可能です。

$query = $articles->find()->contain([
    'Comments',
    'Authors.Profiles' => function (Query $q) {
        return $q->where(['Profiles.is_published' => true]);
    }
]);

上記の例では、公開されたプロファイル (profile) を持たなくても、著者 (author) は引き続き取得します。公開されたプロファイル (profile) を持つ著者のみを取得するには、 matching() を使用してください。関連にカスタム Finder メソッドをいくつか定義しているなら、 contain() の中でそれらを使うことができます。

// すべての article を取り出すが、承認され (approved)、人気のある (popular) ものだけに限定する
$query = $articles->find()->contain('Comments', function (Query $q) {
    return $q->find('approved')->find('popular');
});

注釈

BelongsTo と HasOne の関連で関連するレコードをロードする際には where 句と select 句だけが使用可能です。これ以外の関連タイプであれば、クエリーオブジェクトが提供するすべての句を使うことができます。

生成されたクエリー全体を完全にコントロールする必要があるなら、生成されたクエリーに contain() に foreignKey 制約を追加しないようにと指示を出すことができます。この場合、配列を使って foreignKey と queryBuilder を渡してください。

$query = $articles->find()->contain([
    'Authors' => [
        'foreignKey' => false,
        'queryBuilder' => function (Query $q) {
            return $q->where(...); // フィルターのための完全な条件
        }
    ]
]);

select() でロードするフィールドを限定しているが、contain している関連データのフィールドもまたロードしたいなら、 select() に関連オブジェクトを渡すこともできます。

// Articles から id と title を、 Users から全列を select する
$query = $articles->find()
    ->select(['id', 'title'])
    ->select($articles->Users)
    ->contain(['Users']);

別の方法として、複数の関連がある場合には、 enableAutoFields() を使うことができます。

// Articles から id と title を、 Users、Comments、Tags から全列を select する
$query->select(['id', 'title'])
    ->contain(['Comments', 'Tags'])
    ->enableAutoFields(true)
    ->contain(['Users' => function(Query $q) {
        return $q->autoFields(true);
    }]);

関連を含んだソート

関連を HasMany や BelongsToMany でロードした時、 sort オプションで、これら関連データをソートすることができます。

$query->contain([
    'Comments' => [
        'sort' => ['Comments.created' => 'DESC']
    ]
]);

関連付くデータでフィルターする

関連データに関するクエリーでよくあるのは、指定の関連データに「マッチする (matching)」レコードを見つけるものです。たとえば、 'Articles belongsToMany Tags' である場合、かなりの確率で、 CakePHP タグ (Tag) を持つ記事 (Article) を探したいはずです。これは CakePHP の ORM では極めてシンプルにできます。

// コントローラーやテーブルのメソッド内で

$query = $articles->find();
$query->matching('Tags', function ($q) {
    return $q->where(['Tags.name' => 'CakePHP']);
});

この戦略は HasMany の関連にも同様に適用できます。たとえば、'Authors HasMany Articles' である場合、下記のようにして、最近公開された記事 (Article) のすべての投稿者 (Author) を抽出したいかもしれません。

$query = $authors->find();
$query->matching('Articles', function ($q) {
    return $q->where(['Articles.created >=' => new DateTime('-10 days')]);
});

深い関連を使って抽出することも驚くほど簡単です。文法はすでによく知っているものです。

// コントローラーやテーブルのメソッド内で
$query = $products->find()->matching(
    'Shops.Cities.Countries', function ($q) {
        return $q->where(['Countries.name' => 'Japan']);
    }
);

// 渡された変数を使って 'markstory' によってコメントされた記事 (Article) をユニークに取り出す
// ドット区切りのマッチングパスは、ネストされた matching() 呼び出しでも使われます
$username = 'markstory';
$query = $articles->find()->matching('Comments.Users', function ($q) use ($username) {
    return $q->where(['username' => $username]);
});

注釈

この機能は INNER JOIN 句を生成しますので、条件によりすでに除外していない限り、取得した行が重複しているかもしれず、find クエリーでは distinct の呼び出しを考えたいことでしょう。これは、たとえば、同じユーザーが一つの記事 (Article) に複数回コメントした場合にありえます。

関連から「マッチ ('matched') した」ことで取得されるデータはエンティティーの _matchingData プロパティーで利用可能です。同一の関連を match かつ contain している場合、結果には _matchingData プロパティーと標準の関連系のプロパティーの両方があることになります。

innerJoinWith を使う

matching() 関数を使うことで、すでに見てきたように、特定の関連との INNER JOIN が作成され、結果セットにもフィールドがロードされます。

matching() を使いたいものの、結果セットにフィールドをロードしたくない状況もあるかもしれません。この目的で innerJoinWith() を使うことが出来ます。

$query = $articles->find();
$query->innerJoinWith('Tags', function ($q) {
    return $q->where(['Tags.name' => 'CakePHP']);
});

innerJoinWith() メソッドは matching() と同様に動きます。つまり、ドット記法を使うことで深くネストする関連を join できます。

$query = $products->find()->innerJoinWith(
    'Shops.Cities.Countries', function ($q) {
        return $q->where(['Countries.name' => 'Japan']);
    }
);

違いは結果セットに追加のカラムが追加されず、 _matchingData プロパティーがセットされないことだけです。

notMatching を使う

matching() の対義語となるのが notMatching() です。この関数は結果を、特定の関連に繋がっていないものだけにフィルターするようにクエリーを変更します。

// コントローラーやテーブルのメソッド内で

$query = $articlesTable
    ->find()
    ->notMatching('Tags', function ($q) {
        return $q->where(['Tags.name' => '退屈']);
    });

上記の例は 退屈 という単語でタグ付けされていない、すべての記事(Article)を検索します。このメソッドを HasMany の関連にも同様に使うことができます。たとえば、10日以内に公開 (published) されていない記事 (Article) のすべての作者 (Author) を検索することができます。

$query = $authorsTable
    ->find()
    ->notMatching('Articles', function ($q) {
        return $q->where(['Articles.created >=' => new \DateTime('-10 days')]);
    });

このメソッドを深い関連にマッチしないレコードだけにフィルターするために使うこともできます。例えば、特定のユーザーによるコメントが付かなかった記事を見つけることができます。

$query = $articlesTable
    ->find()
    ->notMatching('Comments.Users', function ($q) {
        return $q->where(['username' => 'jose']);
    });

コメント (Comment) がまったく付いていない記事 (Article) も上記の条件を満たしてしまいますので、 matching() と notMatching() を混ぜて使いたくなるかもしれません。下記の例は最低１件以上のコメント (Comment) を持つ記事 (Article) の中で特定ユーザーにコメントされているものを除外して検索したものです。

$query = $articlesTable
    ->find()
    ->notMatching('Comments.Users', function ($q) {
        return $q->where(['username' => 'jose']);
    })
    ->matching('Comments');

注釈

notMatching() は LEFT JOIN 句を生成しますので、条件により回避していない限り、取得した行が重複しているかもしれず、find クエリーでは distinct の呼び出しを考えたいことでしょう。

matching() 関数の正反対となる notMatching() ですが、いかなるデータも結果セットの _matchingData プロパティーに追加しないということを覚えておいてください。

leftJoinWith を使う

時には、すべての関連レコードをロードしたくはないが、関連に基いて結果を計算したいということがあるかもしれません。たとえば、記事 (Article) の全データと一緒に、記事ごとのコメント (Comment) 数をロードしたい場合には、 leftJoinWith() 関数が使えます。

$query = $articlesTable->find();
$query->select(['total_comments' => $query->func()->count('Comments.id')])
    ->leftJoinWith('Comments')
    ->group(['Articles.id'])
    ->enableAutoFields(true);

上記クエリーの結果は Article データの結果に加え、データごとに total_comments プロパティーが含まれます。

leftJoinWith() はまた深くネストした関連にも使うことができます。たとえばこれは、特定の単語でタグ (Tag) 付けされた記事 (Article) の数を投稿者 (Author) ごとに出したい場合に便利です。

$query = $authorsTable
    ->find()
    ->select(['total_articles' => $query->func()->count('Articles.id')])
    ->leftJoinWith('Articles.Tags', function ($q) {
        return $q->where(['Tags.name' => 'awesome']);
    })
    ->group(['Authors.id'])
    ->enableAutoFields(true);

この関数は指定した関連からいずれのカラムも結果セットへとロードしません。

Join を追加する

関連付くデータを contain() でロードすることもできますが、追加の join をクエリービルダーに加えることもできます。

$query = $articles->find()
    ->join([
        'table' => 'comments',
        'alias' => 'c',
        'type' => 'LEFT',
        'conditions' => 'c.article_id = articles.id',
    ]);

複数 join の連想配列を渡すことで、複数の join を一度に追加できます。

$query = $articles->find()
    ->join([
        'c' => [
            'table' => 'comments',
            'type' => 'LEFT',
            'conditions' => 'c.article_id = articles.id',
        ],
        'u' => [
            'table' => 'users',
            'type' => 'INNER',
            'conditions' => 'u.id = articles.user_id',
        ]
    ]);

上記にあるように、join を加える際に、エイリアス (別名) を配列のキーで渡すことができます。 join の条件も条件の配列と同じように表現できます。

$query = $articles->find()
    ->join([
        'c' => [
            'table' => 'comments',
            'type' => 'LEFT',
            'conditions' => [
                'c.created >' => new DateTime('-5 days'),
                'c.moderated' => true,
                'c.article_id = articles.id'
            ]
        ],
    ], ['c.created' => 'datetime', 'c.moderated' => 'boolean']);

手動で join を作成する際、配列による条件を使うなら、join 条件内の各列ごとにデータ型を渡す必要があります。 join 条件のデータ型を渡すことで、ORM はデータの型を SQL へと正しく変換できるのです。 join を作成する際には join() だけでなく、rightJoin()、 leftJoin()、innerJoin() を使うこともできます。

// エイリアスと文字列の条件で join する
$query = $articles->find();
$query->leftJoin(
    ['Authors' => 'authors'],
    ['Authors.id = Articles.author_id']);

// エイリアスと配列の条件・型で join する
$query = $articles->find();
$query->innerJoin(
    ['Authors' => 'authors'],
    [
    'Authors.promoted' => true,
    'Authors.created' => new DateTime('-5 days'),
    'Authors.id = Articles.author_id'
    ],
    ['Authors.promoted' => 'boolean', 'Authors.created' => 'datetime']);

注意しなければならないのは、 Connection を定義する際に quoteIdentifiers オプションが true の場合には、テーブルの列間の join 条件は次のようにしなければならないということです。

$query = $articles->find()
    ->join([
        'c' => [
            'table' => 'comments',
            'type' => 'LEFT',
            'conditions' => [
                'c.article_id' => new \Cake\Database\Expression\IdentifierExpression('articles.id')
            ]
        ],
    ]);

これは Query 内のすべての識別子に引用符が付くことを保証し、いくつかのデータベースドライバ（とくに PostgreSQL）でエラーが起こらないようにします。

データを insert する

前の例とは違って、insert するクエリーを作成するのに find() は使わないでください。代わりに、 query() を使って新たな Query オブジェクトを作成します。

$query = $articles->query();
$query->insert(['title', 'body'])
    ->values([
        'title' => 'First post',
        'body' => 'Some body text'
    ])
    ->execute();

１つのクエリーで複数の行を insert するために values() メソッドを、必要な回数分つなげることができます。

$query = $articles->query();
$query->insert(['title', 'body'])
    ->values([
        'title' => 'First post',
        'body' => 'Some body text'
    ])
    ->values([
        'title' => 'Second post',
        'body' => 'Another body text'
    ])
    ->execute();

通常は、エンティティーを使い、 save() でデータを insert するほうが簡単です。また、SELECT と INSERT を一緒に構築すれば、 INSERT INTO ... SELECT スタイルのクエリーを作成することができます。

$select = $articles->find()
    ->select(['title', 'body', 'published'])
    ->where(['id' => 3]);

$query = $articles->query()
    ->insert(['title', 'body', 'published'])
    ->values($select)
    ->execute();

注釈

クエリービルダーでレコードを insert すると、 Model.afterSave のようなイベントは発生しません。代わりに データ保存のための ORM が利用できます。

データを update する

クエリーの insert と同様に、update のクエリーを作成するのに find() を使わないでください。代わりに、 query() を使って新たな Query オブジェクトを作成します。

$query = $articles->query();
$query->update()
    ->set(['published' => true])
    ->where(['id' => $id])
    ->execute();

通常は、エンティティーを使い、 patchEntity() でデータを update するほうが簡単です。

注釈

クエリービルダーでレコードを update すると、 Model.afterSave のようなイベントは発生しません。代わりに データ保存のための ORM が利用できます。

データを delete する

クエリーの insert と同様に、delete のクエリーを作成するのに find() を使わないでください。代わりに、 query() を使って新たな クエリーオブジェクトを作成します:

$query = $articles->query();
$query->delete()
    ->where(['id' => $id])
    ->execute();

通常は、エンティティーを使い、 delete() でデータを delete するほうが簡単です。

SQL インジェクションを防止する

ORM とデータベースの抽象層では、ほとんどの SQL インジェクション問題を防止してはいますが、不適切な用法により危険な値が入り込む余地も依然としてありえます。

条件配列を使用している場合、キー/左辺および単一の値の入力は、ユーザーデータを含んではいけません。

$query->where([
    // キー/左辺のデータは、生成されたクエリーにそのまま挿入されるので、
    // 安全ではありません
    $userData => $value,

    // 単一の値の入力にも同じことが言えますが、
    // どの形式のユーザーデータでも安全に使用することはできません
    $userData,
    "MATCH (comment) AGAINST ($userData)",
    'created < NOW() - ' . $userData
]);

Expression ビルダーを使う際には、カラム名にユーザーデータを含めてはいけません。

$query->where(function (QueryExpression $exp) use ($userData, $values) {
    // いずれの式 (expression) の中であってもカラム名は安全ではありません
    return $exp->in($userData, $values);
});

関数式を構築する際、関数名にユーザーデータを含めてはいけません。

// 安全ではありません
$query->func()->{$userData}($arg1);

// 関数式の引数としてユーザーデータの配列を使うことも安全ではありません
$query->func()->coalesce($userData);

未加工 (raw) の式は安全ではありません。

$expr = $query->newExpr()->add($userData);
$query->select(['two' => $expr]);

値のバインディング

バインディングを使用することにより、多くの安全でない状況から保護することが可能です。プリペアードステートメントへの値のバインディングと同様に、 Cake\Database\Query::bind() メソッドを使用して、クエリーに値をバインドすることができます。

次の例は、上記の安全ではない SQL インジェクションが発生しやすい例を安全に変更したものです。

$query
    ->where([
        'MATCH (comment) AGAINST (:userData)',
        'created < NOW() - :moreUserData'
    ])
    ->bind(':userData', $userData, 'string')
    ->bind(':moreUserData', $moreUserData, 'datetime');

注釈

Cake\Database\StatementInterface::bindValue() と異なり、 Query::bind() は、コロンを含む名前付けされたプレースホルダーを渡す必要があります!

より複雑なクエリー

クエリービルダーでは UNION クエリーやサブクエリーのような複雑なクエリーも構築することができます。

UNION

UNION は１つ以上のクエリーを一緒に構築して作成します。

$inReview = $articles->find()
    ->where(['need_review' => true]);

$unpublished = $articles->find()
    ->where(['published' => false]);

$unpublished->union($inReview);

unionAll() メソッドを使うことで UNION ALL クエリーを作成することもできます。

$inReview = $articles->find()
    ->where(['need_review' => true]);

$unpublished = $articles->find()
    ->where(['published' => false]);

$unpublished->unionAll($inReview);

サブクエリー

サブクエリーはリレーショナル・データベースにおいて強力な機能であり、CakePHP ではそれを実に直感的に構築することができます。クエリーを一緒に構築することで、サブクエリーを作ることができます。

$matchingComment = $articles->getAssociation('Comments')->find()
    ->select(['article_id'])
    ->distinct()
    ->where(['comment LIKE' => '%CakePHP%']);

$query = $articles->find()
    ->where(['id IN' => $matchingComment]);

サブクエリーはクエリー式のどこにでも使うことができます。たとえば、 select() や join() メソッドの中でもです。

ステートメントのロックの追加

ほとんどのリレーショナル・データベース製品は、SELECT 操作を行う際のロックをサポートします。これは、 epilog() メソッドを使用することで可能です。

// MySQL の場合、
$query->epilog('FOR UPDATE');

epilog() メソッドは、クエリーの最後に生の SQL を追加することができます。決して生のユーザーデータを epilog() にセットしないでください。

複雑なクエリーを実行する

クエリービルダーはほとんどのクエリーを簡単に構築できるようにしてくれますが、あまりに複雑なクエリーだと、構築するにも退屈で入り組んだものになるかもしれません。望む SQL を直接実行 したいかもしれません。

SQL を直接実行するということは、走ることになるクエリーを微調整できることになります。ただし、そうしてしまうと、 contain や他の高レベルな ORM 機能は使えません。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

テーブルオブジェクト

class Cake\ORM\Table

テーブルオブジェクトは特定のテーブルに保存されたエンティティーのコレクションへのアクセスを提供します。それぞれのテーブルは、与えられたテーブルによって繋がれた関連付けられたテーブルクラスを持ちます。もし、与えられたテーブルの振る舞いをカスタマイズする必要ないなら、CakePHP はテーブルのインスタンスを作ります。

テーブルオブジェクトと ORM を作る前に　データベースへの接続がなされているか確かめましょう。

基本的な使い方

まずはじめにテーブルクラスを作ってください。これらのクラスは src/Model/Table に作ります。テーブルは、リレーショナルデータベースに特化したモデルコレクションです。そして、CakePHP の ORM の中で、あなたのデータベースへの主なインターフェースです。最も基本的なテーブルクラスは次のようになります。

// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
}

このクラスで使用するテーブル名を ORM に伝えていないことに注目してください。規約により、テーブルオブジェクトは、クラス名を小文字とアンダースコアー区切りにした名前のテーブルを使用します。上記の例では articles テーブルが使用されます。テーブルクラスが BlogPosts という名前の場合、テーブルは blog_posts と名付けてください。あなたは、 setTable() メソッドを使用することでテーブルを指定できます。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->setTable('my_table');
    }
}

テーブルを指定した時は、命名規則は適用されません。規約により、ORM はそれぞれのテーブルが id という名前の主キーを持っていることを前提としています。もし主キーの名前を変更する必要がある場合、 setPrimaryKey() メソッドが使用できます。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->setPrimaryKey('my_id');
    }
}

テーブルが使うエンティティークラスのカスタマイズ

デフォルトではテーブルオブジェクトは命名規則に従った Entity クラスを使います。たとえば、 ArticlesTable というテーブルクラスの名前だったらエンティティーは Article に、 PurchaseOrdersTable というテーブルクラスの名前だったらエンティティーは PurchaseOrder になります。もし命名規約に従わない場合は、 setEntityClass() メソッドで設定を変えられます。

class PurchaseOrdersTable extends Table
{
    public function initialize(array $config): void
    {
        $this->setEntityClass('App\Model\Entity\PO');
    }
}

上記の例では、テーブルオブジェクトはコンストラクターの最後に呼ばれる initialize() メソッドを持ちます。コンストラクターをオーバーライドする代わりに、このメソッドで初期化することを推奨します。

テーブルクラスのインスタンスを取得する

テーブルにクエリーを実行する前に、テーブルインスタンスを取得する必要があります。 TableLocator クラスを使用することで取得できます。

// コントローラー内で

$articles = $this->getTableLocator()->get('Articles');

TableLocator はテーブルを作るための様々な依存関係を提供します。そして、作成されたすべてのテーブルインスタンスの設定を維持し、リレーションの構築と ORM の設定を簡単にしてくれます。詳細は TableLocator の利用 をご覧ください。

テーブルクラスがプラグインの中にある場合、あなたのテーブルクラスのために正しい名前を必ず使用してください。それに失敗すると、デフォルトのクラスが正しいクラスの代わりに使われてしまい、バリデーションルールやコールバックが呼ばれないなどの結果を生じます。プラグインのテーブルクラスを正しくロードするために、次のように使用してください。

// プラグインの Table
$articlesTable = $this->getTableLocator()->get('PluginName.Articles');

// ベンダープレフィックス付きのプラグイン Table
$articlesTable = $this->getTableLocator()->get('VendorName/PluginName.Articles');

コールバックのライフサイクル

上記で示した通り、テーブルオブジェクトは、いろいろなイベントを起こします。イベントは、 ORM 内でフックしたり、サブクラス化やメソッドをオーバーライドせずにロジックを加えたい時に便利です。イベントリスナーはテーブルクラスやビヘイビアークラスで定義できます。また、テーブルのイベントマネージャーをリスナーをバインドするために使えます。

コールバックメソッドを使うとき、 initialize() メソッドで追加されたビヘイビアーは、テーブルコールバックメソッドが開始する 前に 呼ばれるリスナーを持ちます。これは、コントローラーやコンポーネントと同じ流れに従います。

イベントリスナーにテーブルクラスやビヘイビアーを追加するには、単純にメソッド名を以下の様に使います。イベントサブシステムの使い方の詳細は イベントシステム をご覧ください。:

// コントローラー内で
$articles->save($article, ['customVariable1' => 'yourValue1']);

// ArticlesTable.php内で
public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options)
{
    $customVariable = $options['customVariable1'];  // 'yourValue1'
    $options['customVariable2'] = 'yourValue2';
}

public function afterSaveCommit(Event $event, EntityInterface $entity, ArrayObject $options)
{
    $customVariable = $options['customVariable1'];  // 'yourValue1'
    $customVariable = $options['customVariable2'];  // 'yourValue2'
}

イベント一覧

Model.initialize

Model.beforeMarshal

Model.afterMarshal

Model.beforeFind

Model.buildValidator

Model.buildRules

Model.beforeRules

Model.afterRules

Model.beforeSave

Model.afterSave

Model.afterSaveCommit

Model.beforeDelete

Model.afterDelete

Model.afterDeleteCommit

initialize

Cake\ORM\Table::initialize(EventInterface $event, ArrayObject $data, ArrayObject $options)

Model.initialize イベントは、コンストラクターと initialize メソッドが呼ばれた後に発行されます。デフォルトでは、 Table クラスは、このイベントを購読しません。そして、代わりに initialize フックメソッドを使います。

Model.initialize イベントに応答するために、 EventListenerInterface を実装したリスナークラスを作成することができます。

use Cake\Event\EventListenerInterface;
class ModelInitializeListener implements EventListenerInterface
{
    public function implementedEvents()
    {
        return [
            'Model.initialize' => 'initializeEvent',
        ];
    }

    public function initializeEvent($event): void
    {
        $table = $event->getSubject();
        // ここで何かする
    }
}

そして、以下のように EventManager にリスナーを追加します。

use Cake\Event\EventManager;
$listener = new ModelInitializeListener();
EventManager::instance()->attach($listener);

これは、任意の Table クラスが構築されたとき、 initializeEvent を呼びます。

beforeMarshal

Cake\ORM\Table::beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)

Model.beforeMarshal イベントは、リクエストデータがエンティティーに変換される前に発行されます。詳細は エンティティー構築前のリクエストデータ変更 をご覧ください。

afterMarshal

Cake\ORM\Table::afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $data, ArrayObject $options)

The Model.afterMarshal イベントは、リクエストデータがエンティティーに変換された後に発行されます。イベントハンドラは、変換されたエンティティ、元のリクエストデータ、および patchEntity() または newEntity() 呼び出しに提供されたオプションを取得します。

バージョン 4.1.0 で追加.

beforeFind

Cake\ORM\Table::beforeFind(EventInterface $event, Query $query, ArrayObject $options, $primary)

Model.beforeFind イベントは、各検索操作の前に発生します。イベントを停止し、クエリにカスタム結果セットを渡すことにより、検索操作を完全にバイパスできます。:

public function beforeFind(EventInterface $event, Query $query, ArrayObject $options, $primary)
{
    if (/* ... */) {
        $event->stopPropagation();
        $query->setResult(new \Cake\Datasource\ResultSetDecorator([]));

        return;
    }
    // ...
}

この例では、関連するテーブルまたはそのアタッチされたビヘイビアーで beforeFind イベントはトリガーされません。ただし、振る舞いイベントは通常、デフォルトの優先順位が与えられているため、以前に呼び出されます。クエリは Query::setResult() を介して渡された空の結果セットを返します。

イベントを止めて戻り値を返すことで find を完全にバイパスできます。 $query インスタンスに対してなされた全ての変更は find 処理の間維持されます。 $primary パラメーターは、これがルートクエリーなのか、それともアソシエーションのクエリーなのかを示します。クエリーに含まれる全てのアソシエーションで Model.beforeFind イベントが呼ばれます。 JOIN を使うアソシエーションに対しては、ダミーのクエリーが渡されます。イベントリスナーでは、追加のフィールド、検索条件、 JOIN や結果のフォーマッターを設定出来ます。これらのオプションや機能はルートクエリーにコピーされます。

CakePHP の旧バージョンでは afterFind コールバックがありましたが、 結果を Map/Reduce で変更する機能とエンティティーのコンストラクターに置き換えられました。

buildValidator

Cake\ORM\Table::buildValidator(EventInterface $event, Validator $validator, $name)

Model.buildValidator イベントは $name バリデーターが作られた時に発行されます。ビヘイビアーは、バリデーションメソッドに追加するために、このフックが使用できます。

buildRules

Cake\ORM\Table::buildRules(EventInterface $event, RulesChecker $rules)

Model.buildRules イベントはルールインスタンスが作られた後と、 Table の beforeRules() メソッドが呼ばれた後に発行されます。

beforeRules

Cake\ORM\Table::beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, $operation)

Model.beforeRules イベントはエンティティーにルールが適用される前に発行されます。このイベントが止まると、チェックのためのルールを停止して、適用したルールの結果をセットすることができます。

afterRules

Cake\ORM\Table::afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, $result, $operation)

Model.afterRules イベントはルールがエンティティーに適用された後に発行されます。このイベントが止まると、操作をチェックするためのルールの結果の値を返すことができます。

beforeSave

Cake\ORM\Table::beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)

Model.beforeSave イベントはエンティティーが保存する前に発行されます。このイベントを止めることによって、保存を停止できます。イベントが停止すると、このイベントの結果が返されます。

afterSave

Cake\ORM\Table::afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)

Model.afterSave イベントはエンティティーを保存した後に発行されます。

afterSaveCommit

Cake\ORM\Table::afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)

Model.afterSaveCommit イベントは、保存処理がラップされたトランザクションがコミットされた後に発行されます。データベース操作が暗黙的にコミットされる非アトミックな保存でも引き起こされます。イベントは、 save() が直接呼ばれた最初のテーブルだけに引き起こされます。 save が呼ばれる前にトランザクションが始まっている場合、イベントは起こりません。

beforeDelete

Cake\ORM\Table::beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)

Model.beforeDelete イベントはエンティティーを削除する前に発行されます。このイベントを停止することによって、削除を中止できます。イベントが停止すると、このイベントの結果が返されます。

afterDelete

Cake\ORM\Table::afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)

Model.afterDelete イベントはエンティティーが削除された後に発行されます。

afterDeleteCommit

Cake\ORM\Table::afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)

Model.afterDeleteCommit イベントは、削除処理がラップされたトランザクションがコミットされた後に発行されます。データベース操作が暗黙的にコミットされる非アトミックな保存でも引き起こされます。イベントは、 delete() が直接呼ばれた最初のテーブルだけに引き起こされます。 delete が呼ばれる前にトランザクションが始まっている場合、イベントは起こりません。

Stopping Table Events

保存を継続しないようにするには、コールバックでイベントの伝搬を停止するだけです:

public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
    if (...) {
        $event->stopPropagation();
        $event->setResult(false);
        return;
    }
    ...
}

また、コールバックからfalseを返すこともできます。これはイベントの伝播を止めるのと同じ効果があります。

Callback priorities

テーブルやビヘイビアでイベントを使用する際には、優先順位とリスナーが付く順番に注意してください。ビヘイビアイベントは、テーブルイベントの前にアタッチされます。デフォルトの優先順位では、ビヘイビアのコールバックが同名のテーブルイベントの 前 にトリガーされます。

例えば、テーブルが TreeBehavior を使用している場合、 TreeBehavior::beforeDelete() メソッドは、テーブルの beforeDelete() メソッドよりも先に呼び出されてしまい、テーブルのメソッドで削除される子要素のレコードを操作することはできません。

イベントの優先順位を管理するには、いくつかの方法があります:

priority オプションを使って、ビヘイビアのリスナーの 優先度 を変更します。これは、ビヘイビアの すべての コールバックメソッドの優先度を変更します。

Behavior:

// In a Table initialize() method
$this->addBehavior('Tree', [
    // Default value is 10 and listeners are dispatched from the
    // lowest to highest priority.
    'priority' => 2,
]);

Model.implementedEvents() メソッドを使用し、 Table クラスの priority を変更します。これにより、コールバック関数ごとに異なる優先度を割り当てることができます:

// In a Table class.
public function implementedEvents()
{
    $events = parent::implementedEvents();
    $events['Model.beforeDelete'] = [
        'callable' => 'beforeDelete',
        'priority' => 3
    ];

    return $events;
}

ビヘイビアー

Cake\ORM\Table::addBehavior($name, array $options = [])

ビヘイビアーは、テーブルクラスにまたがって関連するロジックの再利用可能な部品を作成する簡単な方法を提供します。なぜビヘイビアーが通常のクラスで、トレイトではないのか不思議に思うかもしれません。第一の理由は、ビヘイビアーはイベントリスナーだからです。トレイトは再利用可能なロジックの部品になりえますが、イベントをバインドするのは厄介です。

ビヘイビアーをテーブルに追加するために addBehavior() メソッドが使えます。一般的に、これを initialize() でやるのがもっともよいです。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config)
    {
        $this->addBehavior('Timestamp');
    }
}

アソシエーションには プラグイン記法 と追加の設定オプションが使えます。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Timestamp', [
            'events' => [
                'Model.beforeSave' => [
                    'created_at' => 'new',
                    'modified_at' => 'always'
                ]
            ]
        ]);
    }
}

CakePHP によって提供されるビヘイビアーを含む、ビヘイビアーに関する詳細は ビヘイビアーの章をご覧ください。

接続設定

デフォルトでは、全てのテーブルインスタンスは default データベース接続を使用します。もし、複数のデータベース接続を使用している場合、どのコネクションを使用してテーブルを設定したくなるでしょう。これは、 defaultConnectionName() メソッドで出来ます。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public static function defaultConnectionName(): string {
        return 'replica_db';
    }
}

注釈

defaultConnectionName() メソッドはスタティックで なければなりません 。

TableLocator の利用

class Cake\ORM\TableLocator

これまで見てきたように、TableLocator クラスは　factory/registry をアプリケーションのテーブルインスタンスにアクセスするために使うことを簡単にします。これには他にも便利な機能があります。

テーブルオブジェクトの設定

static Cake\ORM\TableLocator::get($alias, $config)

テーブルをレジストリーからロードする時に、依存関係をカスタマイズするか、 $options 配列が用意するモックオブジェクトを使います。

$articles = FactoryLocator::get('Table')->get('Articles', [
    'className' => 'App\Custom\ArticlesTable',
    'table' => 'my_articles',
    'connection' => $connectionObject,
    'schema' => $schemaObject,
    'entityClass' => 'Custom\EntityClass',
    'eventManager' => $eventManager,
    'behaviors' => $behaviorRegistry
]);

接続とスキーマ設定に注意して下さい。それらは文字列変数ではなくオブジェクトです。この接続は Cake\Database\Connection のオブジェクトとスキーマの Cake\Database\Schema\Collection を扱います。

注釈

テーブルは initialize() メソッドで追加の設定を行う場合、それらの値はレジストリーの設定を上書きします。

また、事前にレジストリーを setConfig() メソッドを使って設定できます。設定データは エイリアスごと に保存され、オブジェクトの initialize() メソッドで上書きできます。

FactoryLocator::get('Table')->setConfig('Users', ['table' => 'my_users']);

注釈

そのエイリアスにアクセスする前か、最初 のアクセス時だけテーブルの設定が可能です。レジストリーが投入された後に設定しても効果がありません。

レジストリーの初期化

Cake\ORM\TableLocator::clear()

テストケースで、レジストリーをフラッシュしたいこともあるでしょう。モックオブジェクトを使う時やテーブルの依存関係を設定する時に便利です。

FactoryLocator::get('Table')->clear();

ORM クラスを配置する名前空間の設定

もし、規約に従わない場合、おそらくテーブルやエンティティークラスは CakePHP によって検知されません。これを修正するために、 Cake\Core\Configure::write メソッドで名前空間をセットできます。例えば、

/src
    /App
        /My
            /Namespace
                /Model
                    /Entity
                    /Table

は、次のように設定されます。

Cake\Core\Configure::write('App.namespace', 'App\My\Namespace');

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

エンティティー

class Cake\ORM\Entity

テーブルオブジェクト がオブジェクトのコレクションへのアクセスを表し、提供するのに対し、エンティティーは個々の行やドメインオブジェクトを表します。エンティティーは保持するデータにアクセスして操作するための永続的なプロパティーとメソッドを保有しています。

CakePHP でテーブルオブジェクトの find() を使うたびにエンティティーが作られます。

エンティティークラスの生成

CakePHP の ORM を使うためにエンティティークラスを生成する必要はありません。しかし、使用するエンティティーでロジックをカスタマイズしたいなら、クラスを作る必要があります。慣例通りなら src/Model/Entity/ にクラスが存在しています。もし、 articles テーブルが存在するなら、以下のエンティティーが作れます。

// src/Model/Entity/Article.php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
}

今のところ、このエンティティーは何も出来ません。とはいえ、データを article テーブルからロードすれば、このクラスのインスタンスを得られます。

注釈

もし、エンティティークラスが定義されていないなら、CakePHP はデフォルトのエンティティークラスを使います。

エンティティー生成

直接、エンティティーのインスタンスを生成できます。

use App\Model\Entity\Article;

$article = new Article();

エンティティーからインスタンスを生成する時、インスタンスに必要なプロパティーを渡すことができます。

use App\Model\Entity\Article;

$article = new Article([
    'id' => 1,
    'title' => 'New Article',
    'created' => new DateTime('now')
]);

エンティティーを生成するお勧めの方法は、 Table オブジェクトの newEntity() メソッドです。

use Cake\ORM\TableRegistry;

$article = TableRegistry::getTableLocator()->get('Articles')->newEmptyEntity();

$article = TableRegistry::getTableLocator()->get('Articles')->newEntity([
    'id' => 1,
    'title' => 'New Article',
    'created' => new DateTime('now')
]);

$article は、 App\Model\Entity\Article のインスタンスになります。もしくは Article クラスが作成しなかった場合は、代替として Cake\ORM\Entity のインスタンスになります。

エンティティーのデータへのアクセス

エンティティーは保有するデータにアクセスするいくつかの方法を提供します。最も一般的なのは、オブジェクトの表記を使ってエンティティー内のデータにアクセスすることです。

use App\Model\Entity\Article;

$article = new Article;
$article->title = 'This is my first post';
echo $article->title;

また、 get() と set() メソッドも使えます。

$article->set('title', 'This is my first post');
echo $article->get('title');

set() を使う時、一つの配列で複数のプロパティーを一度に更新できます。

$article->set([
    'title' => 'My first post',
    'body' => 'It is the best ever!'
]);

警告

エンティティーをリクエストデータでアップデートするときには、一度の代入でどのフィールドにセットできるかホワイトリストで制限するべきです。

has() を使ってエンティティーにプロパティが定義されているかどうかを確認できます。

$article = new Article([
    'title' => 'First post',
    'user_id' => null
]);
$article->has('title'); // true
$article->has('user_id'); // false
$article->has('undefined'); // false.

has() メソッドは、プロパティが定義されていてヌル以外の値を持つ場合、 true を返します。 isEmpty() と hasValue() を使って、プロパティに '空でない' 値が含まれているかどうかを調べることができます。

$article = new Article([
    'title' => 'First post',
    'user_id' => null
]);
$article->isEmpty('title');  // false
$article->hasValue('title'); // true

$article->isEmpty('user_id');  // true
$article->hasValue('user_id'); // false

アクセサーとミューテーター

シンプルな get/set インターフェイスに加えて、エンティティーはアクセサーメソッドとミューテーターメソッドを提供できるようになっています。これらのメソッドは、プロパティーがどうやってセットされたり、読まれたりするかをカスタマイズするために使えます。

アクセサーは _get + フィールド名のキャメルケースという命名ルールを使います。

Cake\ORM\Entity::get($field)

このメソッドは唯一の引数として _fields 配列内にある基本の値を受け取ります。アクセサーはエンティティーを保存する際に使われますので、データをフォーマットするメソッドを定義する場合は注意が必要です。データはフォーマットされた状態で保存されることになります。例えば、

namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
    protected function _getTitle($title)
    {
        return ucwords($title);
    }
}

アクセサーは以下の２つの方法でプロパティーを取得する際に実行されます。

echo $article->title;
echo $article->get('title');

注釈

フィールドを参照するたびに、アクセサーのコードが実行されます。アクセサーでリソース集約的な操作を実行している場合は、 $myEntityProp = $entity->my_property のように、ローカル変数を使用してキャッシュすることができます。

ミューテーターを定義することによって、プロパティーの設定方法をカスタマイズできます。

Cake\ORM\Entity::set($field = null, $value = null)

ミューテーターは常にプロパティーに保存すべき値を返すようにしてください。上の例のように、ミューテーターを使って他の計算されたプロパティーを設定することもできます。これをする際に、呼び出しがループしてしまわないように注意して下さい。CakePHP はミューテーターの無限ループを防ぐことが出来ません。

ミューテーターによりセットされるプロパティーを変換したり、計算されたデータを作成したりすることができるようになります。ミューテーターとアクセサーはオブジェクト表記や、 get() や set() を使ってプロパティーが読まれた場合に適用されます。例えば、

namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\Utility\Text;

class Article extends Entity
{
    protected function _setTitle($title)
    {
        return Text::slug($title);
    }

}

ミューテーターは、以下の２つの方法でプロパティーを設定するときに実行されます。

$user->title = 'foo'; // 同時に slug が設定されます。
$user->set('title', 'foo'); // 同時に slug が設定されます。

警告

アクセサーは、エンティティーがデータベースに永続化される前にも実行されます。フィールドを変換したいけれど、変換したものを永続化したくない場合、永続化されない仮想プロパティーの使用をお勧めします。

仮想プロパティーの生成

アクセサーを定義することによって、現在存在しないフィールド・プロパティーへのアクセスを提供できます。例えば、users テーブルが first_name と last_name 列を持っていたとして、フルネームのためのメソッドを作れるということです。

namespace App\Model\Entity;

use Cake\ORM\Entity;

class User extends Entity
{
    protected function _getFullName()
    {
        return $this->first_name . '  ' . $this->last_name;
    }
}

仮想プロパティーは、エンティティーに存在するかのようにアクセスできます。プロパティー名はメソッド( full_name )の小文字とアンダースコア付きのバージョンになります。

echo $user->full_name;

仮想プロパティーは find で使えないということを覚えておいてください。もし、仮想プロパティーを、エンティティーを表す JSON や配列の一部にしたい場合、仮想プロパティーが含まれるようにする をご覧ください。

エンティティーが変更されたかチェックする

Cake\ORM\Entity::dirty($field = null, $dirty = null)

エンティティーのプロパティーが変更されたかどうかに応じるコードを作りたいと思うことがあるかもしれません。例えば、フィールドが変更された時にだけバリデートしたい場合です。

// タイトルが変更された時に
$article->isDirty('title');

フィールドに変更されたという印をつける事もできます。これは配列のプロパティーに追加した場合に便利です。

// コメントを追加して、フィールドが変更されたと印をつけます。
$article->comments[] = $newComment;
$article->setDirty('comments', true);

加えて、 getOriginal() メソッドを使うことで元のプロパティー値に応じたコードを書くこともできます。このメソッドは値が変更されているなら元の値を返し、そうでなければ実際の値を返します。

また、エンティティー内のプロパティーのいずれかが変化したかをチェックすることもできます。

// エンティティーが変更されたか確かめる
$article->isDirty();

clean() メソッドで不必要な印をエンティティーのフィールドから除去できます。

$article->clean();

オプションを追加で渡すことで、フィールドに印が付くのを避けることができます。

$article = new Article(['title' => 'New Article'], ['markClean' => true]);

Entity の全ての変更されたプロパティーの一覧を取得するには、次のように呼ぶことができます。

$dirtyFields = $entity->getDirty();

バリデーションエラー

エンティティーの保存 がされた後、どんなバリデーションエラーもエンティティー自身に保存されます。バリデーションエラーには getErrors() や getError() 、 hasErrors() メソッドを使ってアクセスできます。

// エラーの取得
$errors = $user->getErrors();

// １つのフィールドのエラーを取得
$errors = $user->getError('password');

// エンティティーまたはネストされたエンティティーにエラーがあるか
$user->hasErrors();

// ルートエンティティーのみにエラーがあるか
$user->hasErrors(false);

setErrors() や setError() はまたエンティティーにエラーをセットするために使うこともできます。これにより、エラーメッセージで動くコードのテストが簡単になります。

$user->setError('password', ['Password is required']);
$user->setErrors([
    'password' => ['Password is required'],
    'username' => ['Username is required']
]);

一括代入 (Mass Assignment)

一括でエンティティーのプロパティーを設定するのは単純で便利ですが、これには重大なセキュリティ問題が伴います。リクエストからユーザーデータをエンティティーへと一括代入してしまうと、ユーザーはどの列でも変更できるようになってしまいます。匿名のエンティティークラスを使ったり、 Bake コンソール でエンティティーを生成すると、 CakePHP は一括代入から保護しません。

_accessible プロパティーにより、プロパティーと一括代入できるかどうかのマップを提供できるようになります。 true と false の値はそれぞれ、その列が一括代入できるか、できないかを示しています。

namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
    protected $_accessible = [
        'title' => true,
        'body' => true
    ];
}

具体的なフィールドに加え、名前が指定されなかった場合の受け皿となる * という特別なフィールドが存在します。

namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
    protected $_accessible = [
        'title' => true,
        'body' => true,
        '*' => false,
    ];
}

注釈

* プロパティーが定義されない場合、デフォルトは false になります。

一括代入に対する保護の回避

新しいエンティティーを new キーワードで作成する際、一括代入に対して保護しないように指示できます。

use App\Model\Entity\Article;

$article = new Article(['id' => 1, 'title' => 'Foo'], ['guard' => false]);

保護されたフィールドを実行時に変更する

setAccess() メソッドを使うことで保護されたフィールドのリストを実行時に変更できます。

// user_id にアクセスできるようにする
$article->setAccess('user_id', true);

// title を保護する。
$article->setAccess('title', false);

注釈

フィールドがアクセス可能かの変更は、そのメソッドを呼んだインスタンスのみに影響します。

Table オブジェクトの newEntity() と patchEntity() を使う際、オプションを使って一括代入からの保護をカスタマイズできます。アクセス可能なフィールドの変更 に詳細があります。

フィールドに対する保護を受け渡す

保護されたフィールドに対して一括代入を許可したい状況もあるでしょう。

$article->set($properties, ['guard' => false]);

guard オプションを false にすることで、今回の set() の呼び出しに限り、アクセス可能なフィールドリストを無視することが出来ます。

エンティティーが永続化されているかチェックする

エンティティーが示す行がデータベース上に既に存在しているかを知らなければならないことは良くあることです。こういった場合は isNew() メソッドを使って下さい。

if (!$article->isNew()) {
    echo '既に保存されました!';
}

既にエンティティーが永続化されているかどうかが解っているなら isNew() をセッターとして使えます。

$article->setNew(false);

$article->setNew(true);

アソシエーションの Lazy ローディング

アソシエーションの eager ローディングは大抵の場合において最も有効なアクセス法ではありますが、アソシエーションデータを lazy ロードしたいときもあるかもしれません。この方法を見ていく前に、 eager ローディングと lazy ローディングの違いを見てみましょう:

Eager ローディング

できるだけ 少ない クエリーでDBから情報を取得できるようにJOINを（可能なときは）使います。 HasMany アソシエーションを使うような分割したクエリーが必要なときは、1つのクエリーで、現在のオブジェクト一式に必要な 全て の関連データを取ってこようとします。

Lazy ローディング

絶対に必要になるまでアソシエーションのロードを遅延させます。これにより、不要なデータがオブジェクト化されないので CPU 時間を節約できますが、大量のクエリーがDBに送られることになるかもしれません。例えば、 複数の記事 (articles) とそれに属するコメント (comments) を舐めるループでは、イテレートされた記事の数だけクエリーが何度も送られることになります。

CakePHP の ORM には lazy ローディングは含まれませんが、実現するためにコミュニティープラグインの１つを使うことができます。私たちは LazyLoad プラグイン [https://github.com/jeremyharris/cakephp-lazyload] をお勧めします。

あなたのエンティティーにプラグインを追加した後、以下のようにできます。

$article = $this->Articles->findById($id);

// comments プロパティーは lazy ロードされます。
foreach ($article->comments as $comment) {
    echo $comment->body;
}

トレイトを使った再利用可能なコードの生成

いくつかのエンティティークラスで同じロジックを使わなければならないことに気づくことがあるでしょう。 PHP のトレイトはこういった場合に威力を発揮します。 src/Model/Entity に自作のトレイトを置くことができます。慣習的に CakePHP のトレイトは末尾に Trait が付いていますので、クラスやインターフェイスでないことが判るようになっています。トレイトは振る舞いを補完するもので、これを使うことで、テーブルオブジェクトやエンティティーオブジェクトに機能を提供できるようになっています。

例えば、 SoftDeletable プラグインを使っていたとして、これがトレイトを提供します。このトレイトは、エンティティーに 'deleted' マークを付けるためのメソッドを提供します。 softDelete メソッドがトレイトにより提供されるのです。

// SoftDelete/Model/Entity/SoftDeleteTrait.php

namespace SoftDelete\Model\Entity;

trait SoftDeleteTrait
{
    public function softDelete()
    {
        $this->set('deleted', true);
    }

}

そして、このトレイトをインポートし、インクルードすることで、独自のエンティティークラスで使えます。

namespace App\Model\Entity;

use Cake\ORM\Entity;
use SoftDelete\Model\Entity\SoftDeleteTrait;

class Article extends Entity
{
    use SoftDeleteTrait;
}

配列や JSON への変換

API を作る時、しばしば、エンティティーを配列や JSON に変換する必要があるでしょう。 CakePHP では以下のように簡単にできます。

// 配列を取得します。
// アソシエーションも toArray() で変換されます。
$array = $user->toArray();

// JSON に変換します。
// アソシエーションも jsonSerialize フックで変換されます。
$json = json_encode($user);

エンティティーを JSON へと変換する際に、仮想 (virtual) フィールドや隠し (hidden) フィールドのリストが適用されます。エンティティーは再帰的に JSON へと変換されます。これは、エンティティーとアソシエーションを eager ロードする場合、CakePHP は関連データを正しいフォーマットへと正しく変換できることを意味します。

仮想プロパティーが含まれるようにする

配列や JSON に変換した際、仮想フィールドはデフォルトでは含まれません。仮想プロパティーが含まれるようにするためには、そのように指定する必要があります。エンティティークラスを定義する際に、含まれるべき仮想プロパティーのリストを提供できます。

namespace App\Model\Entity;

use Cake\ORM\Entity;

class User extends Entity
{
    protected $_virtual = ['full_name'];
}

実行時に setVirtual() を使うことでこのリストを変更できます。

$user->setVirtual(['full_name', 'is_admin']);

フィールドを隠す

JSON/配列フォーマットで出力したくないフィールドがある場合があります。例えば、パスワードや ”秘密の質問” などです。エンティティークラスを定義する際、どのプロパティーを隠すか設定できます。

namespace App\Model\Entity;

use Cake\ORM\Entity;

class User extends Entity
{
    protected $_hidden = ['password'];
}

実行時に setHidden() を使うことでこのリストを変更できます。

$user->setHidden(['password', 'recovery_question']);

複合型の保存

DB の複合型のデータをシリアライズ/デシリアライズするためのロジックがエンティティーのアクセサーとミューテーターに含まれることは想定されていません。配列型やオブジェクト型のような複合的なデータ型をどうやって保存するのかを理解するには複雑な型の保存 を参照して下さい。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

データの取り出しと結果セット

class Cake\ORM\Table

テーブルオブジェクトが「リポジトリー」やオブジェクトのコレクション周りの抽象化を提供してくれますので、クエリーを実行した際には「エンティティー」オブジェクトとして個々のレコードを取得することができます。このセクションではエンティティーを検索したりロードしたりする様々な方法について説明します。詳細は エンティティー セクションをご覧ください。

クエリーのデバッグと結果セット

ORM はいまや、コレクションとエンティティーを返しますので、それらのオブジェクトをデバッグすることは以前の CakePHP よりも複雑になりえます。いまでは、様々な方法で ORM が返すデータを調査する方法が存在します。

debug($query) SQL とバインドパラメーターが表示されます。結果は表示されません。

sql($query) DebugKit がインストールされている場合に限り、最後に描画された SQL を表示します。

debug($query->all()) ResultSet のプロパティー (結果ではなく) が表示されます。

debug($query->toList()) 結果を個々に見る簡単な方法です。

debug(iterator_to_array($query)) クエリー結果を配列形式で表示します。

debug(json_encode($query, JSON_PRETTY_PRINT)) 人に読みやすい形で結果を表示します。

debug($query->first()) 単一のエンティティーのプロパティーを表示します。

debug((string)$query->first()) 単一のエンティティーのプロパティーを JSON として表示します。

主キーで単一のエンティティーを取得する

Cake\ORM\Table::get($id, $options = [])

エンティティーとそれに関連するデータを編集・閲覧する際に、データベースから単一のエンティティーをロードするというのは、もっともよく使う方法です。この場合は get() を使います。

// コントローラーやテーブルのメソッド内で

// 単一の article を取得する
$article = $articles->get($id);

// 単一の article と、それに関連する comment を取得する。
$article = $articles->get($id, [
    'contain' => ['Comments']
]);

get の操作がどの結果も見つけられない場合は、 Cake\Datasource\Exception\RecordNotFoundException が発生します。この例外を catch してもいいですし、CakePHP に 404 エラーへと変えさせてもかまいません。

find() のように get() もキャッシュ機能を持ってます。 get() を呼ぶ際にキャッシュを読ませるために cache オプションを使うことができます。

// コントローラーやテーブルのメソッド内で

// いずれかのキャッシュの config もしくは CacheEngine インスタンスと生成されたキーを使う
$article = $articles->get($id, [
    'cache' => 'custom',
]);

// いずれかのキャッシュの config もしくは CacheEngine インスタンスと指定したキーを使う
$article = $articles->get($id, [
    'cache' => 'custom', 'key' => 'mykey'
]);

// キャッシュを使わないと明示する
$article = $articles->get($id, [
    'cache' => false
]);

選択肢として、カスタム Finder メソッド を使ってエンティティーを get() することもできます。たとえば、あるエンティティーの translations すべてを取得したいことがあるかもしれません。 finder オプションを使えば、それを獲得することができます。

$article = $articles->get($id, [
    'finder' => 'translations',
]);

データのロードに Finder を使う

Cake\ORM\Table::find($type, $options = [])

エンティティーを使うには、それらをロードする必要があります。これを最も簡単に行えるのが find() メソッドを使うことです。 find メソッドは、あなたが求めるデータを検索するための簡単で拡張性の高い方法を提供します。

// コントローラーやテーブルのメソッド内で

// すべての article を検索する
$query = $articles->find('all');

find() メソッドの戻り値は常に Cake\ORM\Query オブジェクトです。 Query クラスにより、それの生成後は、クエリーをより精錬することができるようになります。 Query オブジェクトは怠惰に評価され、行のフェッチ、配列への変換、もしくは all() メソッドの呼び出しをするまでは実行されません。

// コントローラーやテーブルのメソッド内で

// すべての article を検索
// この時点ではクエリーは走らない。
$query = $articles->find('all');

// イテレーションはクエリーを実行する
foreach ($query->all() as $row) {
}

// all() の呼び出しはクエリーを実行し、結果セットを返す
$results = $query->all();

// 結果セットがあれば すべての行を取得できる
$data = $results->toList();

// クエリーからキーと値の配列への変換はクエリーを実行する
$data = $query->toArray();

注釈

クエリーが開始されたら、 クエリービルダー インターフェースを使うことができ、この便利なインターフェースにより、条件、リミット、保持する関連の追加などが行えます。より複雑なクエリーを構築することができます。

// コントローラーやテーブルのメソッド内で
$query = $articles->find('all')
    ->where(['Articles.created >' => new DateTime('-10 days')])
    ->contain(['Comments', 'Authors'])
    ->limit(10);

find() に対するとても一般的なオプションも提供します。これがあればテストの際にモックするメソッドを少なくできます。

// コントローラーやテーブルのメソッド内で
$query = $articles->find('all', [
    'conditions' => ['Articles.created >' => new DateTime('-10 days')],
    'contain' => ['Authors', 'Comments'],
    'limit' => 10
]);

find() で使えるオプションは次の通りです:

conditions クエリーの WHERE 句に使う条件を提供します。

limit 欲しい行数をセットします。

offset 欲しいページオフセットをセットします。 page をあわせて使うことで計算を簡単にできます。

contain 関連をイーガーロード (eager load) するように定義します。

fields エンティティーへとロードされる列を制限します。いくつかの列だけがロードされることになるのでエンティティーが正しく動かないこともありえます。

group クエリーに GROUP BY 句を加えます。集約関数を使う際に便利です。

having クエリーに HAVING 句を加えます。

join カスタム JOIN を追加で定義します。

order 結果セットに並び順を設定します。

このリストに無いオプションはどれも beforeFind リスナに渡され、クエリーオブジェクトの変更に使われます。クエリーオブジェクトの getOptions() メソッドを使うことで、利用中のオプションを取得することができます。クエリーオブジェクトをコントローラーに渡すよりも、 カスタム Finder メソッド でクエリーをまとめることをお勧めします。カスタム finder メソッドを使うことでクエリーを再利用できるようになり、テストが簡単になります。

デフォルトでクエリーと結果セットは エンティティー オブジェクトを返します。変換 (hydrate) を無効化すれば、素となる配列を取得することができます。

$query->disableHydration();

// $data は配列のデータを含む ResultSet です。
$data = $query->all();

１つ目の結果を取得する

first() メソッドによりクエリーから１つ目の行だけをフェッチすることができます。クエリーがまだ実行されいないなら、 LIMIT 1 句が適用されます。

// コントローラーやテーブルのメソッド内で
$query = $articles->find('all', [
    'order' => ['Articles.created' => 'DESC']
]);
$row = $query->first();

このアプローチは CakePHP 旧バージョンの find('first') を置き換えるものです。また、主キーでエンティティーをロードするなら get() メソッドも使いたいかもしれません。

注釈

first() メソッドは、結果が見つからない場合、 null を返します。

結果の件数を取得する

クエリーオブジェクトを作成したら、 count() メソッドを使うことでクエリー結果の件数を取得することができます。

// コントローラーやテーブルのメソッド内で
$query = $articles->find('all', [
    'conditions' => ['Articles.title LIKE' => '%Ovens%']
]);
$number = $query->count();

count() メソッドのさらなる用法は レコードの合計数を返す を参照してください。

キー/値のペアを検索する

自分のアプリケーションのデータから関連する連想配列のデータを生成できると便利なときがよくありますたとえば、 <select> エレメントを生成する際にはとても便利です。 CakePHP ではデータの 'list' を生成するメソッドを使うことで簡単にできます。

// コントローラーやテーブルのメソッド内で
$query = $articles->find('list');
$data = $query->toArray();

// データは下記のようになっています
$data = [
    1 => '最初の投稿',
    2 => '私が書いた２つ目の記事',
];

追加のオプションがない場合、 $data のキーはテーブルの主キーになり、値はテーブルの 'displayField' になります。テーブルオブジェクトの setDisplayField() メソッドを使うことでテーブルの表示列を設定できます。

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->setDisplayField('label');
    }
}

list を呼び出す際、 keyField と valueField オプションを使うことで、それぞれキー、値に使われるフィールドを設定することができます。

// コントローラーやテーブルのメソッド内で
$query = $articles->find('list', [
    'keyField' => 'slug',
    'valueField' => 'label'
]);
$data = $query->toArray();

// データは下記のようになっています
$data = [
    'first-post' => '最初の投稿',
    'second-article-i-wrote' => '私が書いた２つ目の記事',
];

結果はネストされた配列へとグルーピングすることができます。これは bucket された set が欲しい時や FormHelper で <optgroup> エレメントを構築したいときに便利です。

// コントローラーやテーブルのメソッド内で
$query = $articles->find('list', [
    'keyField' => 'slug',
    'valueField' => 'label',
    'groupField' => 'author_id'
]);
$data = $query->toArray();

// データは下記のようになっています
$data = [
    1 => [
        'first-post' => '最初の投稿',
        'second-article-i-wrote' => '私が書いた２つ目の記事',
    ],
    2 => [
        // さらなるデータ
    ]
];

join でつながっている関連テーブルからリストのデータを生成することもできます。

$query = $articles->find('list', [
    'keyField' => 'id',
    'valueField' => 'author.name'
])->contain(['Authors']);

キーと値の出力をカスタマイズ

最後に、リストの find の中で、エンティティーのアクセッサーメソッドにアクセスするためにクロージャを使用することができます。

// Authors の中で、エンティティーは displayFild として使用するために仮想フィールドを作成
protected function _getLabel()
{
    return $this->_fields['first_name'] . ' ' . $this->_fields['last_name']
      . ' / ' . __('User ID %s', $this->_fields['user_id']);
}

この例は、Author エンティティーの _getLabel() アクセッサーメソッドを使うことを示しています。

// ファインダーやコントローラーの中で
$query = $articles->find('list', [
        'keyField' => 'id',
        'valueField' => function ($article) {
            return $article->author->get('label');
        }
    ])
    ->contain('Authors');

オプション指定なしで、ラベルを取得することもできます。

// AuthorsTable::initialize() の中で
$this->setDisplayField('label'); // Author::_getLabel() を利用します。
// ファインダーやコントローラーの中で
$query = $authors->find('list'); // AuthorsTable::getDisplayField() を利用します。

スレッド状のデータを検索する

find('threaded') finder はキーフィールドを通じて一つにネストされたエンティティーを返します。デフォルトで、このフィールドは、 parent_id です。この finder は、'隣接リスト' スタイルのテーブルに保存されたデータにアクセスすることができます。与えられた parent_id にマッチするすべてのエンティティーは、 children 属性の下に配置されます。

// コントローラーやテーブルのメソッド内で
$query = $comments->find('threaded');

// デフォルト値を拡張
$query = $comments->find('threaded', [
    'keyField' => $comments->primaryKey(),
    'parentField' => 'parent_id'
]);
$results = $query->toArray();

echo count($results[0]->children);
echo $results[0]->children[0]->comment;

parentField と keyField のキーを使うことでそれらのフィールドでスレッドとなるよう定義することができます。

Tip

より高度なツリー状のデータを扱う必要があるなら、代わりに Tree の利用を検討してください。

カスタム Finder メソッド

上記の例ではビルドインの all と list という finder の使い方を見てきました。しかしながら、独自の finder メソッドを実装することは可能ですし、お勧めです。 finder メソッドは共通で使うクエリーをパッケージ化する理想的な方法です。クエリーを抽象化できるようにすることで、メソッドは使いやすくなります。 finder メソッドは、あなたが作成したい finder の名前が Foo の場合、 findFoo というように規約に則ったメソッドを作成することで定義されます。例えば、公開された記事を見つけるために articles テーブルに finder を追加したい場合、次のようになります。

use Cake\ORM\Query;
use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function findOwnedBy(Query $query, array $options)
    {
        $user = $options['user'];
        return $query->where(['author_id' => $user->id]);
    }
}

// コントローラーやテーブルのメソッド内で
$articles = TableRegistry::getTableLocator()->get('Articles');
$query = $articles->find('ownedBy', ['user' => $userEntity]);

Finder メソッドはクエリーを必要に応じて変更したり、 $options を使うことで関連するアプリケーションのロジックにあわせて finder の操作をカスタマイズしたりすることができます。 Finder の 'stack' (重ね呼び) もまた、複雑なクエリーを難なく表現できるようにしてくれます。 'published' と 'recent' の両方の Finder を持っているとすると、次のようになります。

// コントローラーやテーブルのメソッド内で
$articles = TableRegistry::getTableLocator()->get('Articles');
$query = $articles->find('published')->find('recent');

ここまではいずれも、テーブルクラスの Finder メソッドを例に見てきましたが、Finder メソッドはビヘイビアー で定義することも可能です。

フェッチ後に結果を変更する必要があるなら、 結果を Map/Reduce で変更する 機能を使って結果を変更してください。 map reduce 機能は、旧バージョンの CakePHP にあった 'afterFind' コールバックに代わるものです。

注釈

config 配列に公開された引数を渡す $products->find('sizes', ['large', 'medium']) と、カスタム Finder をチェーンするときに予期しない結果が生じる可能性があります。常にオプションを連想配列として渡してください。 $products->find('sizes', ['values' => ['large', 'medium']])

動的な Finder

CakePHP の ORM は動的に構築する Finder メソッドを提供します。これにより追加コーディングなしで簡単なクエリーを表現できます。たとえば、 username でユーザーを検索したいなら、次のようにできます。

// コントローラーの中
// 下記の２つは同じ
$query = $this->Users->findByUsername('joebob');
$query = $this->Users->findAllByUsername('joebob');

// テーブルメソッドの中
$users = TableRegistry::getTableLocator()->get('Users');
// 下記の２つは同じ
$query = $users->findByUsername('joebob');
$query = $users->findAllByUsername('joebob');

動的 Finder を使う際には、複数フィールドを使うこともできます。

$query = $users->findAllByUsernameAndApproved('joebob', 1);

OR 条件を生成することもできます。

$query = $users->findAllByUsernameOrEmail('joebob', 'joe@example.com');

OR や AND 条件のどちらも使えますが、１つの動的 Finder の中に２つを混ぜて使うことはできません。 contain のような他のクエリーオプションも動的 Finder には対応していません。より複雑なクエリーを詰め込みたいなら カスタム Finder メソッド を使ってください。なお、動的 Finder とカスタム Finder を混ぜて使うことは可能です。

$query = $users->findTrollsByUsername('bro');

上記は下記のように読み替えられます。

$users->find('trolls', [
    'conditions' => ['username' => 'bro']
]);

動的クエリーからクエリーオブジェクトを得た後、１つ目の結果が欲しい場合、first() を呼ぶ必要があります。

注釈

動的 Finder はクエリーを簡単にしてくれますが、追加のオーバーヘッドが発生することになります。

関連付いたデータを取得する

関連付いたデータを取得したい、もしくは関連付いたデータを基に抽出したい場合、２つの方法があります:

contain() や matching() のような CakePHP ORM クエリー関数を使う

innerJoin() や leftJoin() 、 rightJoin() のような join 関数を使う

最初のモデルとそれに関連付くデータをロードしたいなら、 contain() を使ってください。 contain() により、ロードされる関連データには追加条件を適用することになりますが、関連データをベースに、最初のモデルを条件付けることはできません。

関連データをベースに最初のモデルを条件付けたいなら matching() を使ってください。たとえば、特定の tag を持つ article をすべてロードしたい場合などです。詳細は matching() にありますので matching と joins を用いた関連データによるフィルタリング を参照してください。

join 関数を使いたい場合の詳細は Join を追加する を参照してください。

contain を用いた関連データのイーガーロード

CakePHP は find() を使う際、デフォルトでは関連データを いずれも ロードしません。結果の中にロードしたい各関連データは 'contain' で指定するか、イーガーロード (eager load) する必要があります。

イーガーロードは、ORM のレイジーロード (lazy load) 周辺に潜むパフォーマンス問題の多くを避けるのに役立ちます。イーガーロードで生成されたクエリーは JOIN に影響を与えて、効率的なクエリーが作られるようになります。 CakePHP では 'contain' メソッドを使って関連データのイーガーロードを定義します。

// コントローラーやテーブルのメソッド内で

// find() のオプションとして
$query = $articles->find('all', ['contain' => ['Authors', 'Comments']]);

// クエリーオブジェクトのメソッドとして
$query = $articles->find('all');
$query->contain(['Authors', 'Comments']);

上記では関連する author と comment を結果セットの article ごとにロードします。ロードする関連データを定義するためのネストされた配列を使って、ネストされた関連データをロードすることができます。

$query = $articles->find()->contain([
    'Authors' => ['Addresses'], 'Comments' => ['Authors']
]);

または、ドット記法を使ってネストされた関連データを表現することもできます。

$query = $articles->find()->contain([
    'Authors.Addresses',
    'Comments.Authors'
]);

好きなだけ深く関連データをイーガーロードできます。

$query = $products->find()->contain([
    'Shops.Cities.Countries',
    'Shops.Managers'
]);

複数の簡単な contain() 文を使って全ての関連データからフィールドを選択できます。

$query = $this->find()->select([
    'Realestates.id',
    'Realestates.title',
    'Realestates.description'
])
->contain([
    'RealestateAttributes' => [
        'Attributes' => [
            'fields' => [
                // contain() の中で別名がつけられたフィールドは、
                // 正しくマップされたモデルのプレフィックスが含まれていなければなりません。
                'Attributes__name' => 'attr_name'
            ]
        ]
    ]
])
->contain([
    'RealestateAttributes' => [
        'fields' => [
            'RealestateAttributes.realestate_id',
            'RealestateAttributes.value'
        ]
    ]
])
->where($condition);

クエリー上の contain を再設定する必要があるなら、第２引数に true を指定することができます。

$query = $articles->find();
$query->contain(['Authors', 'Comments'], true);

contain に条件を渡す

contain() を使う際、関連によって返される列を限定し、条件によってフィルターすることができます。条件を指定するには、第１引数としてクエリーオブジェクト \Cake\ORM\Query を受け取る無名関数を渡します。

// コントローラーやテーブルのメソッド内で

$query = $articles->find()->contain('Comments', function (Query $q) {
    return $q
        ->select(['body', 'author_id'])
        ->where(['Comments.approved' => true]);
});

これは、またコントローラーレベルでページネーションが働きます。

$this->paginate['contain'] = [
    'Comments' => function (Query $query) {
        return $query->select(['body', 'author_id'])
        ->where(['Comments.approved' => true]);
    }
];

注釈

関連によってフェッチされるフィールドを限定する場合、外部キーの列が確実に select されなければなりません 。外部キーのカラムが select されない場合、関連データが最終的な結果の中に無いということがおこります。

ドット記法を使って、深くネストされた関連データを制限することも可能です。

$query = $articles->find()->contain([
    'Comments',
    'Authors.Profiles' => function (Query $q) {
        return $q->where(['Profiles.is_published' => true]);
    }
]);

上記の例では、公開されたプロファイル (profile) を持たなくても、著者 (author) は引き続き取得します。公開されたプロファイル (profile) を持つ著者のみを取得するには、 matching() を使用してください。関連にカスタム Finder メソッドをいくつか定義しているなら、 contain() の中でそれらを使うことができます。

// すべての article を取り出すが、承認され (approved)、人気のある (popular) ものだけに限定する
$query = $articles->find()->contain('Comments', function (Query $q) {
    return $q->find('approved')->find('popular');
});

注釈

BelongsTo と HasOne の関連で関連するレコードをロードする際には where 句と select 句だけが使用可能です。これ以外の関連タイプであれば、クエリーオブジェクトが提供するすべての句を使うことができます。

生成されたクエリー全体を完全にコントロールする必要があるなら、生成されたクエリーに contain() に foreignKey 制約を追加しないようにと指示を出すことができます。この場合、配列を使って foreignKey と queryBuilder を渡してください。

$query = $articles->find()->contain([
    'Authors' => [
        'foreignKey' => false,
        'queryBuilder' => function (Query $q) {
            return $q->where(...); // フィルターのための完全な条件
        }
    ]
]);

select() でロードするフィールドを限定しているが、contain している関連データのフィールドもまたロードしたいなら、 select() に関連オブジェクトを渡すこともできます。

// Articles から id と title を、 Users から全列を select する
$query = $articles->find()
    ->select(['id', 'title'])
    ->select($articles->Users)
    ->contain(['Users']);

別の方法として、複数の関連がある場合には、 enableAutoFields() を使うことができます。

// Articles から id と title を、 Users、Comments、Tags から全列を select する
$query->select(['id', 'title'])
    ->contain(['Comments', 'Tags'])
    ->enableAutoFields(true)
    ->contain(['Users' => function(Query $q) {
        return $q->autoFields(true);
    }]);

関連を含んだソート

関連を HasMany や BelongsToMany でロードした時、 sort オプションで、これら関連データをソートすることができます。

$query->contain([
    'Comments' => [
        'sort' => ['Comments.created' => 'DESC']
    ]
]);

matching と joins を用いた関連データによるフィルタリング

関連データに関するクエリーでよくあるのは、指定の関連データに「マッチする (matching)」レコードを見つけるものです。たとえば、 'Articles belongsToMany Tags' である場合、かなりの確率で、 CakePHP タグ (Tag) を持つ記事 (Article) を探したいはずです。これは CakePHP の ORM では極めてシンプルにできます。

// コントローラーやテーブルのメソッド内で

$query = $articles->find();
$query->matching('Tags', function ($q) {
    return $q->where(['Tags.name' => 'CakePHP']);
});

この戦略は HasMany の関連にも同様に適用できます。たとえば、'Authors HasMany Articles' である場合、下記のようにして、最近公開された記事 (Article) のすべての投稿者 (Author) を抽出したいかもしれません。

$query = $authors->find();
$query->matching('Articles', function ($q) {
    return $q->where(['Articles.created >=' => new DateTime('-10 days')]);
});

深い関連を使って抽出することも驚くほど簡単です。文法はすでによく知っているものです。

// コントローラーやテーブルのメソッド内で
$query = $products->find()->matching(
    'Shops.Cities.Countries', function ($q) {
        return $q->where(['Countries.name' => 'Japan']);
    }
);

// 渡された変数を使って 'markstory' によってコメントされた記事 (Article) をユニークに取り出す
// ドット区切りのマッチングパスは、ネストされた matching() 呼び出しでも使われます
$username = 'markstory';
$query = $articles->find()->matching('Comments.Users', function ($q) use ($username) {
    return $q->where(['username' => $username]);
});

注釈

この機能は INNER JOIN 句を生成しますので、条件によりすでに除外していない限り、取得した行が重複しているかもしれず、find クエリーでは distinct の呼び出しを考えたいことでしょう。これは、たとえば、同じユーザーが一つの記事 (Article) に複数回コメントした場合にありえます。

関連から「マッチ ('matched') した」ことで取得されるデータはエンティティーの _matchingData プロパティーで利用可能です。同一の関連を match かつ contain している場合、結果には _matchingData プロパティーと標準の関連系のプロパティーの両方があることになります。

innerJoinWith を使う

matching() 関数を使うことで、すでに見てきたように、特定の関連との INNER JOIN が作成され、結果セットにもフィールドがロードされます。

matching() を使いたいものの、結果セットにフィールドをロードしたくない状況もあるかもしれません。この目的で innerJoinWith() を使うことが出来ます。

$query = $articles->find();
$query->innerJoinWith('Tags', function ($q) {
    return $q->where(['Tags.name' => 'CakePHP']);
});

innerJoinWith() メソッドは matching() と同様に動きます。つまり、ドット記法を使うことで深くネストする関連を join できます。

$query = $products->find()->innerJoinWith(
    'Shops.Cities.Countries', function ($q) {
        return $q->where(['Countries.name' => 'Japan']);
    }
);

違いは結果セットに追加のカラムが追加されず、 _matchingData プロパティーがセットされないことだけです。

notMatching を使う

matching() の対義語となるのが notMatching() です。この関数は結果を、特定の関連に繋がっていないものだけにフィルターするようにクエリーを変更します。

// コントローラーやテーブルのメソッド内で

$query = $articlesTable
    ->find()
    ->notMatching('Tags', function ($q) {
        return $q->where(['Tags.name' => '退屈']);
    });

上記の例は 退屈 という単語でタグ付けされていない、すべての記事(Article)を検索します。このメソッドを HasMany の関連にも同様に使うことができます。たとえば、10日以内に公開 (published) されていない記事 (Article) のすべての作者 (Author) を検索することができます。

$query = $authorsTable
    ->find()
    ->notMatching('Articles', function ($q) {
        return $q->where(['Articles.created >=' => new \DateTime('-10 days')]);
    });

このメソッドを深い関連にマッチしないレコードだけにフィルターするために使うこともできます。例えば、特定のユーザーによるコメントが付かなかった記事を見つけることができます。

$query = $articlesTable
    ->find()
    ->notMatching('Comments.Users', function ($q) {
        return $q->where(['username' => 'jose']);
    });

コメント (Comment) がまったく付いていない記事 (Article) も上記の条件を満たしてしまいますので、 matching() と notMatching() を混ぜて使いたくなるかもしれません。下記の例は最低１件以上のコメント (Comment) を持つ記事 (Article) の中で特定ユーザーにコメントされているものを除外して検索したものです。

$query = $articlesTable
    ->find()
    ->notMatching('Comments.Users', function ($q) {
        return $q->where(['username' => 'jose']);
    })
    ->matching('Comments');

注釈

notMatching() は LEFT JOIN 句を生成しますので、条件により回避していない限り、取得した行が重複しているかもしれず、find クエリーでは distinct の呼び出しを考えたいことでしょう。

matching() 関数の正反対となる notMatching() ですが、いかなるデータも結果セットの _matchingData プロパティーに追加しないということを覚えておいてください。

leftJoinWith を使う

時には、すべての関連レコードをロードしたくはないが、関連に基いて結果を計算したいということがあるかもしれません。たとえば、記事 (Article) の全データと一緒に、記事ごとのコメント (Comment) 数をロードしたい場合には、 leftJoinWith() 関数が使えます。

$query = $articlesTable->find();
$query->select(['total_comments' => $query->func()->count('Comments.id')])
    ->leftJoinWith('Comments')
    ->group(['Articles.id'])
    ->enableAutoFields(true);

上記クエリーの結果は Article データの結果に加え、データごとに total_comments プロパティーが含まれます。

leftJoinWith() はまた深くネストした関連にも使うことができます。たとえばこれは、特定の単語でタグ (Tag) 付けされた記事 (Article) の数を投稿者 (Author) ごとに出したい場合に便利です。

$query = $authorsTable
    ->find()
    ->select(['total_articles' => $query->func()->count('Articles.id')])
    ->leftJoinWith('Articles.Tags', function ($q) {
        return $q->where(['Tags.name' => 'awesome']);
    })
    ->group(['Authors.id'])
    ->enableAutoFields(true);

この関数は指定した関連からいずれのカラムも結果セットへとロードしません。

フェッチの戦略の変更する

すでにご存知の通り、 belongsTo と hasOne の関連はメインとなる Finder クエリーの中で JOIN を使ってロードされます。これにより、データ取得の際には、クエリーとフェッチ速度が改善され、より表現力の高い条件文を作成できるようになります。ただ一方で、 order() や limit() など、関連に影響する特定の句を Finder クエリーに追加したい場合に、問題となりえます。

たとえば、記事 (Article) の最初のコメント (Comment) を関連データとして取得したい場合:

$articles->hasOne('FirstComment', [
     'className' => 'Comments',
     'foreignKey' => 'article_id'
]);

この関連からデータをただしくフェッチするには、特定のカラムで order by したいので、クエリーに select 戦略 (strategy) を使うのだと教える必要があります。

$query = $articles->find()->contain([
    'Comments' => [
        'strategy' => 'select',
    ]
]);

この方法での戦略 (strategy) の動的な変更は指定したクエリーのみに適用されます。もしも戦略の変更を永続的に行いたいなら次のようにできます。

$articles->FirstComment->setStrategy('select');

select 戦略の利用は、別データベースにあるテーブルとの関連を作るのに優れた方法です。なぜなら、その場合は joins を使ってレコードをフェッチできないためです。

サブクエリー戦略でフェッチする

テーブルのサイズが増えてくると、そのテーブルの関連のフェッチは遅くなっていきます。一度に大きなデータを扱うクエリーの場合には、なおのことです。 hasMany と belongsToMany の関連データをロードする際、関連を最適化する良い方法は、 subquery 戦略を使うことです。

$query = $articles->find()->contain([
    'Comments' => [
            'strategy' => 'subquery',
            'queryBuilder' => function ($q) {
                return $q->where(['Comments.approved' => true]);
            }
    ]
]);

結果はデフォルトの戦略と同じになりますが、データベースによってはクエリーとフェッチ時間が著しく改善されます。とりわけ、この戦略により、 Microsoft SQL Server などのようにクエリーごとのバインド変数のサイズに制限があるデータベースであっても、大きなデータの塊を一度に扱うことが可能になります。

関連データの戦略を永続的にしたいなら次のようにできます。

$articles->Comments->setStrategy('subquery');

関連をレイジーロード(Lazy Load)する

CakePHP は簡単に関連付くデータをイーガーロード (Eager Load) できますが、レイジーロード (Lazy Load) したいという場合もありえるでしょう。その場合は アソシエーションの Lazy ローディング と追加で関連をロードする を参照してください。

結果セットを使いこなす

all() を使ってクエリーが実行されたら、 Cake\ORM\ResultSet のインスタンスが得られます。このオブジェクトはクエリーから得られた結果のデータを強力に操作する方法を提供します。クエリーオブジェクトと同様に、ResultSets は Collection ですので、 ResultSet オブジェクトのコレクションメソッドをどれでも使うことができます。

ResultSet オブジェクトは基本となるプリペアードステートメント (prepared statement) から行をレイジーロード (Lazy Load) します。デフォルトでは、結果をメモリーにバッファしますので、ある結果セットを何度もイテレートしたり、結果をキャッシュしてイテレートしたりすることができます。もしメモリーに収まらないデータセットを扱う必要がある場合、クエリー上でバッファリングを無効にして結果をストリームすることができます。

$query->enableBufferedResults(false);

バッファを OFF に切り替える場合にはいくつか注意点があります:

結果セットを複数回イテレートできません。

結果をイテレートしてキャッシュすることもできません。

hasMany や belongsToMany の関連をイーガーロード (Eager Load) するクエリーでは、バッファを無効化できません。なぜなら、これら関連タイプでは、結果のすべてに依存してクエリーを生成しますので、全データのイーガーロードが必須となるのです。

警告

PostgreSQL や SQL Server を使った際の、ストリーミング結果 (Streaming results) であってもエンティティー結果 (entire results) 用にメモリーが割り当てられます。これは PDO の制約によるものです。

結果セットの結果は cache/serialize したり、API 用に JSON エンコードしたりすることができます。

// コントローラーやテーブルのメソッド内で
$results = $query->all();

// Serialized
$serialized = serialize($results);

// Json
$json = json_encode($results);

結果セットを serialize する場合も json_encode する場合も期待通りに動きます。 serialize されたデータは unserialized により結果セットに戻ります。 JSON への変換は、結果セット間のすべてのエンティティーオブジェクト上の、 hidden と virtual field の設定を考慮します。

serialize が簡単にできるだけでなく、結果セットは 'Collection' オブジェクトですので、 collection objects でサポートされるすべてのメソッドが使えます。たとえば、記事 (Article) のコレクションにあるタグ (Tag) をユニークに取り出すことができます。

// コントローラーやテーブルのメソッド内で
$articles = TableRegistry::getTableLocator()->get('Articles');
$query = $articles->find()->contain(['Tags']);

$reducer = function ($output, $value) {
    if (!in_array($value, $output)) {
        $output[] = $value;
    }
    return $output;
};

$uniqueTags = $query->all()
    ->extract('tags.name')
    ->reduce($reducer, []);

以下は、結果セットで使うコレクションメソッドの別の例です。

// 計算されたプロパティーにより行をフィルターします
$filtered = $results->filter(function ($row) {
    return $row->is_recent;
});

// 結果のプロパティーから連想配列を作成する
$articles = TableRegistry::getTableLocator()->get('Articles');
$results = $articles->find()->contain(['Authors'])->all();

$authorList = $results->combine('id', 'author.name');

コレクションの機能を使った結果セットの詳細は コレクション を参照してください。計算フィールドを追加する では計算列の追加方法や結果セットの置き換え方法が示されています。

ResultSet から最初/最後のレコードを取得する

first() と last() メソッドを使うことで、結果セットから該当のレコードを取得することができます。

$result = $articles->find('all')->all();

// 最初・最後の結果を取得します。
$row = $result->first();
$row = $result->last();

ResultSet から任意の場所を指定して取得する

skip() と first() を使うことで ResultSet から任意のレコードを取得できます。

$result = $articles->find('all')->all();

// ５番目のレコードを取得する
$row = $result->skip(4)->first();

Query や ResultSet が空かどうかをチェックする

Query や ResultSet オブジェクトの isEmpty() メソッドを使うことで１行以上あるかどうかを確認できます。 Query オブジェクトで isEmpty() メソッドを呼び出した場合はクエリーが評価されます。

// クエリーをチェックします
// CakePHP 4.3.0 からは非推奨
$query->isEmpty();

// 結果をチェックします
$results = $query->all();
$results->isEmpty();

追加で関連をロードする

結果セットを作成した後に、追加の関連をロードする必要があるかもしれません。これはレイジー (Lazy) にイーガーロード (Eager Load) する絶好のタイミングです。 loadInto() を使うことで追加の関連をロードできます。

$articles = $this->Articles->find()->all();
$withMore = $this->Articles->loadInto($articles, ['Comments', 'Users']);

エンティティーが単一であれ、コレクションであれ、追加のデータをその中にイーガーロード (Eager Load) することができます。

結果を Map/Reduce で変更する

大抵の場合、find 操作でデータベースから取り出したデータは事後処理が必要となります。エンティティーの getter 系メソッドは仮想プロパティーの生成や、特別なデータフォーマッティングの多くを面倒みてくれますが、ときには、より基本的な方法でデータ構造を変更する必要があることもあります。

このような場合に、データベースからフェッチした後で結果を処理する方法として、 Query オブジェクトは mapReduce() を提供します。

データ構造を変更するよくある事例は、結果をとある条件に基いて仕分けするものです。このために mapReduce() 関数を使うことができます。２つの callable な関数 $mapper と $reducer が必要となります。 $mapper callable は第１引数としてデータベースから現在の結果を受け取り、第２引数としてイテレーションのキーを受け取ります。最後の引数として、走っている MapReduce ルーチンのインスタンスを受け取ります。

$mapper = function ($article, $key, $mapReduce) {
    $status = 'published';
    if ($article->isDraft() || $article->isInReview()) {
        $status = 'unpublished';
    }
    $mapReduce->emitIntermediate($article, $status);
};

上記の例では $mapper が article の status を published にするか unpublished にするか計算しており、その後で MapReduce インスタンスの emitIntermediate() が呼ばれます。このメソッドは published か unpublished のどちらかでラベル付けされた article リストの中に現在の article を追加します。

MapReduce 処理の次のステップは最終的な結果を確定させるためのものです。 mapper の中で生成される各 status ごとに $reducer 関数が呼ばれ、追加の処理を何でも実行することができます。この関数は第１引数で該当の "bucket" の中にある article リストを受け取り、第２引数で処理対象の "bucket" 名を受け取り、第３引数で mapper() 関数と同じように MapReduce ルーチンのインスタンスを受け取ります。この例では何も追加の処理を行っていませんでしたが、最終的な結果に emit() だけを行っています。

$reducer = function ($articles, $status, $mapReduce) {
    $mapReduce->emit($articles, $status);
};

最終的には、２つの関数を配置することで仕分けすることができます。

$articlesByStatus = $articles->find()
    ->where(['author_id' => 1])
    ->mapReduce($mapper, $reducer);

foreach ($articlesByStatus as $status => $articles) {
    echo sprintf("%d 件の %s の article が存在します", count($articles), $status);
}

上記は下記のように出力されます。

4 件の published の article が存在します
5 件の unpublished の article が存在します

もちろん、これは、実際には MapReduce 処理を使わずとも別の方法で解決できるような、ごく単純な例です。次は、結果を emit する以上のことが求められるような reducer 関数を使った別の例を見てみましょう。

CakePHP についての情報を含む記事 (article) でもっともよく発言された単語を計算する場合、例によって mapper 関数が必要です。

$mapper = function ($article, $key, $mapReduce) {
    if (stripos($article['body'], 'cakephp') === false) {
        return;
    }

    $words = array_map('strtolower', explode(' ', $article['body']));
    foreach ($words as $word) {
        $mapReduce->emitIntermediate($article['id'], $word);
    }
};

まずは "cakephp" という単語が記事の本文中にあるかどうかをチェックし、次に本文を個々の単語に分解します。各単語ごとに bucket を生成し、その中に各記事の id を入れます。こうなればあとは結果を reduce して、カウントを取り出すだけです。

$reducer = function ($occurrences, $word, $mapReduce) {
    $mapReduce->emit(count($occurrences), $word);
}

最後に、すべてを一緒にします。

$wordCount = $articles->find()
    ->where(['published' => true])
    ->andWhere(['published_date >=' => new DateTime('2014-01-01')])
    ->enableHydrate(false)
    ->mapReduce($mapper, $reducer)
    ->toArray();

これは、ストップワードを除去しない場合、非常に大きな配列を返すこともありえますが、このようなものを返します。

[
    'cakephp' => 100,
    'awesome' => 39,
    'impressive' => 57,
    'outstanding' => 10,
    'mind-blowing' => 83
]

最後の例を見ればもはや MapReduce のエキスパートです。 friends (友人) テーブルが存在し、データベースから "嘘の友人" を検索することを想像してください。わかりやすく言えば、双方向でフォローしあっていない人たちのことです。 mapper() 関数を見てみましょう。

$mapper = function ($rel, $key, $mr) {
    $mr->emitIntermediate($rel['target_user_id'], $rel['source_user_id']);
    $mr->emitIntermediate(-$rel['source_user_id'], $rel['target_user_id']);
};

中間の配列は次のようになります。

[
    1 => [2, 3, 4, 5, -3, -5],
    2 => [-1],
    3 => [-1, 1, 6],
    4 => [-1],
    5 => [-1, 1],
    6 => [-3],
    ...
]

正の数は第１レベルのキーで示されたユーザーが彼らをフォローしていることを意味し、負の数はそのユーザーが彼らからフォローされていることを意味します。

それでは reduce しましょう。 reducer が呼ばれるごとに、reducer はユーザーごとのフォロワーのリストを受け取ります。

$reducer = function ($friends, $user, $mr) {
    $fakeFriends = [];

    foreach ($friends as $friend) {
        if ($friend > 0 && !in_array(-$friend, $friends)) {
            $fakeFriends[] = $friend;
        }
    }

    if ($fakeFriends) {
        $mr->emit($fakeFriends, $user);
    }
};

そして、クエリーにこの関数を渡します。

$fakeFriends = $friends->find()
    ->disableHydration()
    ->all()
    ->mapReduce($mapper, $reducer)
    ->toArray();

これは下記のような配列を返します。

[
    1 => [2, 4],
    3 => [6]
    ...
]

結果の配列は、たとえば、 id 1 のユーザーは 2 と 4 をフォローしていますが、彼らは 1 をフォローし返していないということを意味します。

複数の操作を stack する(重ねて呼ぶ)

クエリーの中で mapReduce を使用しても、すぐには実行されません。代わりに最初の結果をフェッチしようとしたらただちに実行されるように登録されます。これにより、別のメソッドやフィルターをチェーン (chain) 呼び出しでクエリーに加えたり、さらには、 MapReduce ルーチンを追加することもできるようになるのです。

$query = $articles->find()
    ->where(['published' => true])
    ->mapReduce($mapper, $reducer);

// 後工程で下記のようにします:
$query->where(['created >=' => new DateTime('1 day ago')]);

これは カスタム Finder メソッド セクションで説明しているように、カスタム Finder メソッドを構築するのに非常に便利です。

public function findPublished(Query $query, array $options)
{
    return $query->where(['published' => true]);
}

public function findRecent(Query $query, array $options)
{
    return $query->where(['created >=' => new DateTime('1 day ago')]);
}

public function findCommonWords(Query $query, array $options)
{
    // 前のセクションで説明した共通の単語の件と同じもの
    $mapper = ...;
    $reducer = ...;
    return $query->mapReduce($mapper, $reducer);
}

$commonWords = $articles
    ->find('commonWords')
    ->find('published')
    ->find('recent');

さらに、１回のクエリーで複数回の mapReduce 操作を stack する (重ねて呼ぶ) ことも可能です。たとえば、記事の中でもっとも頻出する単語が知りたいのに加え、記事すべての中で 20 回よりも多く発言された単語だけを返すようにフィルターもしたい場合は下記のようになります。

$mapper = function ($count, $word, $mr) {
    if ($count > 20) {
        $mr->emit($count, $word);
    }
};

$articles->find('commonWords')->all()->mapReduce($mapper);

stack されたすべての MapReduce 操作を取り除く

ときには mapReduce 操作をまったく実行させずに Query オブジェクトを更新したいという状況もあるかもしれません。これは両方の引数に null を指定し、第３引数 (overwrite) で true を呼び出すことで達成できます。

$query->mapReduce(null, null, true);

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

データの検証

データを保存する 前におそらくそのデータが正しく矛盾がないことを保証したいはずです。 CakePHP ではデータの検証には二つの段階があります:

リクエストデータがエンティティーにコンバートされる前、データ型や書式まわりのバリデーションルールが適用されます。

データが保存される前、ドメインまたはアプリケーションルールが適用されます。これらのルールはアプリケーションのデータの一貫性の保証に役立ちます。

エンティティー構築前のデータ検証

データからエンティティーを構築する時、データの検証 (バリデーション) ができます。データのバリデーションではデータの型、形状およびサイズなどを確認することができます。既定ではリクエストデータがエンティティーに変換される前に検証が行われます。もしも何らかのバリデーションルールが通らなかった場合、返されたエンティティーはエラーを含んだ状態になります。エラーのあるフィールドは返されたエンティティーには含まれません。

$article = $articles->newEntity($this->request->getData());
if ($article->getErrors()) {
    // エンティティー検証失敗。
}

バリデーションが有効になっている状態でエンティティーを構築すると、次のことが起こります:

バリデータオブジェクトが作成されます。

table および default バリデーションプロバイダーが追加されます。

命名に沿ったバリデーションメソッドが呼び出されます。たとえば validationDefault 。

Model.buildValidator イベントが発動します。

リクエストデータが検証されます。

リクエストデータがそのカラム型に対応する型に変換されます。

エラーがエンティティーにセットされます。

正しいデータはエンティティーに設定されますが、検証を通らなかったフィールドは除外されます。

もしもリクエストデータを変換する時にバリデーションを無効にしたければ、 validate オプションに偽を指定してください。

$article = $articles->newEntity(
    $this->request->getData(),
    ['validate' => false]
);

patchEntity() メソッドについても同じことが言えます。

$article = $articles->patchEntity($article, $newData, [
    'validate' => false
]);

既定のバリデーションセットの作成

バリデーションルールは規約ではテーブルクラス中で定義されます。何のデータが検証されるかとあわせてどこで保存されるかも定義します。

テーブル中で既定のバリデーションオブジェクトを作るには、 validationDefault() 関数を作成します。

use Cake\ORM\Table;
use Cake\Validation\Validator;

class ArticlesTable extends Table
{
    public function validationDefault(Validator $validator)
    {
        $validator
            ->requirePresence('title', 'create')
            ->notEmptyString('title');

        $validator
            ->allowEmptyString('link')
            ->add('link', 'valid-url', ['rule' => 'url']);

        ...

        return $validator;
    }
}

有効なバリデーションメソッドやルールは Validator クラスによって提供されバリデーターを作成する の節に記載されています。

注釈

バリデーションオブジェクトは主にユーザー入力の検証用に意図されています。たとえば、フォームやその他の投稿されたリクエストデータです。

異なるバリデーションセットの使用

バリデーションを無効にすることに加えて適用させたいバリデーションルールを選ぶこともできます。

$article = $articles->newEntity(
    $this->request->getData(),
    ['validate' => 'update']
);

上記は必要なルールを構築するために、そのテーブルインスタンスの validationUpdate() メソッドを呼び出します。既定では validationDefault() メソッドが使用されます。記事テーブル用のバリデータの一例はこのようになります。

class ArticlesTable extends Table
{
    public function validationUpdate($validator)
    {
        $validator
            ->notEmptyString('title', __('タイトルを設定してください'))
            ->notEmptyString('body', __('本文は必須です'));
        return $validator;
    }
}

必要に応じていくつものバリデーションセットを設けることができます。バリデーションルールセットの構築についてのより多くの情報は バリデーション を参照してください。

アソシエーションに異なるバリデーションセットを使用

バリデーションセットはアソシエーションごとに定義することもできます。 newEntity() または patchEntity() メソッドを使用する時、変換されるアソシエーション各々に追加のオプションを渡すことができます。

$data = [
     'title' => '私の肩書き',
     'body' => 'テキスト',
     'user_id' => 1,
     'user' => [
         'username' => 'マーク'
     ],
     'comments' => [
         ['body' => '一番目のコメント'],
         ['body' => '二番目のコメント'],
     ]
 ];

 $article = $articles->patchEntity($article, $data, [
     'validate' => 'update',
     'associated' => [
         'Users' => ['validate' => 'signup'],
         'Comments' => ['validate' => 'custom']
     ]
 ]);

バリデータの組み合わせ

バリデータオブジェクトはこのように構築されるので、その構築過程を複数の手順に分割することは簡単です。

// UsersTable.php

public function validationDefault(Validator $validator)
{
    $validator->notEmptyString('username');
    $validator->notEmptyString('password');
    $validator->add('email', 'valid-email', ['rule' => 'email']);
    ...

    return $validator;
}

public function validationHardened(Validator $validator): Validator
{
    $validator = $this->validationDefault($validator);

    $validator->add('password', 'length', ['rule' => ['lengthBetween', 8, 100]]);
    return $validator;
}

上の手順では、 hardened バリデーションセットを使う時には default セット中で定義されているバリデーションルールも含むことになります。

バリデーションプロバイダー

バリデーションルールは既知のあらゆるプロバイダーで定義されている関数を使うことができます。既定では CakePHP はいくつかのプロバイダーを設定します:

table プロバイダーではテーブルクラスまたはそのビヘイビアーのメソッドが有効です。

コアの Validation クラスが default プロバイダーとしてセットアップされます。

バリデーションルールを作る時に、そのルールのプロバイダー名を指定できます。たとえば、もしあなたのテーブルが isValidRole メソッドを持っているとすればそれをバリデーションルールとして使うことができます。

use Cake\ORM\Table;
use Cake\Validation\Validator;

class UsersTable extends Table
{
    public function validationDefault(Validator $validator): Validator
    {
        $validator
            ->add('role', 'validRole', [
                'rule' => 'isValidRole',
                'message' => __('有効な権限を指定する必要があります'),
                'provider' => 'table',
            ]);
        return $validator;
    }

    public function isValidRole($value, array $context): bool
    {
        return in_array($value, ['admin', 'editor', 'author'], true);
    }

}

バリデーションルールにはクロージャも使うことができます。

$validator->add('name', 'myRule', [
    'rule' => function ($value, array $context) {
        if ($value > 1) {
            return true;
        }
        return '適切な値ではありません。';
    }
]);

バリデーションメソッドは通らない時にエラーメッセージを返すことができます。これは渡された値に動的に基づくエラーメッセージを作るための簡単な方法です。

テーブルからのバリデータ取得

テーブルクラスにバリデーションセットを作成した後は、名前を指定して結果のオブジェクトを取得できるようになります。

$defaultValidator = $usersTable->getValidator('default');

$hardenedValidator = $usersTable->getValidator('hardened');

既定のバリデータクラス

上述の通り、既定ではバリデーションメソッドは Cake\Validation\Validator のインスタンスを受け取ります。そうではなくて、カスタムバリデータのインスタンスが毎回ほしいのであれば、テーブルの $_validatorClass プロパティーを使うことができます。

// あなたのテーブルクラスの中で
public function initialize(array $config)
{
    $this->_validatorClass = '\FullyNamespaced\Custom\Validator';
}

アプリケーションルールの適用

リクエストデータがエンティティーに変換される 時、基本的なデータ検証が行われますが、多くのアプリケーションは基本的な検証が完了した後にのみ適用されるもっと複雑な検証も設けています。

バリデーションはデータの形式や構文が正しいことを保証する一方、 ルールはあなたのアプリケーションやネットワークの既存の状態に対してデータを比較することに焦点を当てます。

この種のルールはしばしば「ドメインルール」や「アプリケーションルール」と言われます。 CakePHP は、エンティティーが保存される前に適用される「ルールチェッカー」を通してこれを行います。いくつかのドメインルールの例は次のようになります:

メールアドレスの一意性の保証。

ステータス遷移や業務フローの手順 (たとえば、請求書のステータス更新)。

論理削除されたアイテムの更新の抑制。

使用量／料金の上限の強制。

ドメインルールは save() および delete() メソッドを呼ぶとチェックされます。

ルールチェッカーの作成

ルールチェッカークラスは一般にテーブルクラスの buildRules() メソッドで定義されます。ビヘイビアーや他のイベントの受け手は与えられたテーブルクラスのルールチェッカーを受け取るために Model.buildRules イベントを使うことができます。

use Cake\ORM\RulesChecker;

// テーブルクラスの中で
public function buildRules(RulesChecker $rules)
{
    // 作成および更新操作に提供されるルールを追加
    $rules->add(function ($entity, $options) {
        // 失敗／成功を示す真偽値を返す
    }, 'ruleName');

    // 作成のルールを追加
    $rules->addCreate(function ($entity, $options) {
        // 失敗／成功を示す真偽値を返す
    }, 'ruleName');

    // 更新のルールを追加
    $rules->addUpdate(function ($entity, $options) {
        // 失敗／成功を示す真偽値を返す
    }, 'ruleName');

    // 削除のルールを追加
    $rules->addDelete(function ($entity, $options) {
        // 失敗／成功を示す真偽値を返す
    }, 'ruleName');

    return $rules;
}

ルールの関数はチェックされるエンティティーとオプションの配列を期待します。オプションの配列は errorField 、 message 、そして repository を含みます。 repository オプションはルールが追加されるテーブルクラスを含みます。ルールはあらゆる callable を受け取るので、インスタンス関数を使うこともできます。

$rules->addCreate([$this, 'uniqueEmail'], 'uniqueEmail');

または呼び出し可能なクラスも使えます。

$rules->addCreate(new IsUnique(['email']), 'uniqueEmail');

ルールを追加する時、任意でルールが適用されるフィールドやエラーメッセージを定義することができます。

$rules->add([$this, 'isValidState'], 'validState', [
    'errorField' => 'status',
    'message' => 'この請求書はそのステータスに遷移できません。'
]);

エンティティーの getErrors() メソッドを呼ぶとエラーを確認できます。

$entity->getErrors(); // ドメインルールのエラーメッセージを含んでいます

一意フィールドルールの作成

一意ルールは極めて一般的なので、CakePHP は一意フィールドの組み合わせを定義できる単純なルールクラスを内包しています。

use Cake\ORM\Rule\IsUnique;

// 一つのフィールド
$rules->add($rules->isUnique(['email']));

// フィールドのリスト
$rules->add($rules->isUnique(
    ['username', 'account_id'],
    'この username と account_id の組み合わせはすでに使用されています。'
));

外部キーフィールドのルールを設定する時には、ルールでは列挙したフィールドのみが使われるのを覚えておくことが重要です。これは $user->account->id を変更しても上記のルールは発動しないことを意味します。

外部キールール

制約を強制するためにデータベースエラーに頼ることもできますが、ルールのコードはより良いユーザーエクスペリエンスを提供するのに役立ちます。このために CakePHP は ExistsIn ルールクラスを内包しています。

// 一つのフィールド
$rules->add($rules->existsIn('article_id', 'Articles'));

// 複数キー。複合主キーに役立ちます。
$rules->add($rules->existsIn(['site_id', 'article_id'], 'Articles'));

存在をチェックするための関連テーブルのフィールドは主キーの一部でなければなりません。

複合外部キーの null が可能な部分が null の時、 existsIn が通るように強制することができます。

// 例: NodesTable の複合主キーは (parent_id, site_id) です。
// Node は、親 Node を参照しますが、必須ではありません。参照しない場合、parent_id が null になります。
// たとえ null が可能なフィールド (parent_id のような) が null であっても、このルールが通ることを許可します。
$rules->add($rules->existsIn(
    ['parent_id', 'site_id'], // Schema: parent_id NULL, site_id NOT NULL
    'ParentNodes',
    ['allowNullableNulls' => true]
));

// それに加えて Node は、常に Site を参照してください。
$rules->add($rules->existsIn(['site_id'], 'Sites'));

大部分の SQL データベースでは、複数カラムの UNIQUE インデックスは、 NULL は、それ自身と等しくないため、複数の null 値が存在することを許可します。複数の null 値を許可することは、CakePHP のデフォルトの振る舞いですが、 allowMultipleNulls を使用することでユニークチェックに null 値を含むことができます。

// null 値は `parent_id` と `site_id` の中に１つだけで存在できます。
$rules->add($rules->existsIn(
    ['parent_id', 'site_id'],
    'ParentNodes',
    ['allowMultipleNulls' => false]
));

アソシエーションカウントルール

プロパティーやアソシエーションが正しい件数かどうかの検証が必要な場合、 validCount() ルールが利用できます。

// ArticlesTable.php ファイルの中で
// 記事にタグは５つ以内。
$rules->add($rules->validCount('tags', 5, '<=', 'タグは 5 つまで持てます'));

ルールに基づく件数を定義する際、第３引数は、比較演算子を定義します。比較には ==, >=, <=, >, <, そして != が使えます。プロパティーの件数が範囲内であることを保証するために、２つのルールを使用してください。

// ArticlesTable.php ファイルの中で
// タグは３つ以上、５つ以内
$rules->add($rules->validCount('tags', 3, '>=', 'タグは 3 つ以上必要です'));
$rules->add($rules->validCount('tags', 5, '<=', 'タグは 5 つ以下です'));

もしプロパティーが数えられない場合や存在しない場合、 validCount は false を返すことに注意してください。

// もし tags が null の場合、保存操作は失敗します。
$rules->add($rules->validCount('tags', 0, '<=', 'タグを持つことはできません'));

アソシエーションリンクの制約ルール

LinkConstraint はデータベースがサポートしないSQL制約をエミュレートしたり、制約が失敗したときにユーザーフレンドリーなエラーメッセージを提供したい場合に使用できます。このルールを使用すると、アソシエーションが関連レコードを持っているかどうかを確認できます。　:

// 更新時に各コメントが記事にリンクされていることを確認してください。
$rules->addUpdate($rules->isLinkedTo(
    'Articles',
    'article',
    '記事が必要です。'
));

// 削除時に記事にリンクされたコメントがないことを確認してください。
$rules->addDelete($rules->isNotLinkedTo(
    'Comments',
    'comments',
    '削除する前にコメントがない状態にしてください。'
));

バージョン 4.0.0 で追加: 4.0.0 で追加されました。

エンティティーメソッドをルールとして使用

ドメインルールとしてエンティティーのメソッドを使いたいかもしれません。

$rules->add(function ($entity, $options) {
    return $entity->isOkLooking();
}, 'ruleName');

条件付きルールの使用

エンティティーデータに基づいて条件付きでルールを適用することができます。

$rules->add(function ($entity, $options) use($rules) {
    if ($entity->role == 'admin') {
        $rule = $rules->existsIn('user_id', 'Admins');

        return $rule($entity, $options);
    }
    if ($entity->role == 'user') {
        $rule = $rules->existsIn('user_id', 'Users');

        return $rule($entity, $options);
    }

    return false;
}, 'userExists');

条件付き/動的なエラーメッセージ

ルールは、 カスタムコールバック またはルールオブジェクト であり、パスするかどうかを示すブーリアン型を返すか、検証がパスしなかったことを意味する文字列を返すことができ、返された文字列はエラーメッセージとして使用されます。

message オプションで定義された既存のエラーメッセージは、ルールから返されたエラーメッセージによって上書きされます。

$rules->add(
    function ($entity, $options) {
        if (!$entity->length) {
            return false;
        }

        if ($entity->length < 10) {
            return '値が 10 より小さい場合のエラーメッセージ';
        }

        if ($entity->length > 20) {
            return '値が 20 より大きい場合のエラーメッセージ';
        }

        return true;
    },
    'ruleName',
    [
        'errorField' => 'length',
        'message' => '`false` が返された時に使われる一般的なエラーメッセージ'
    ]
 );

注釈

返されたメッセージを実際に使用するためには、 errorField オプションも指定しなければならない ことに注意してください。そうしなければ、ルールは単にパスしないだけで、エンティティーにエラーメッセージが設定されません。

再利用可能なカスタムルールの作成

カスタムドメインルールを再利用したい事もあるでしょう。それには、独自の呼び出し可能なルールを作成することによって行います。

use App\ORM\Rule\IsUniqueWithNulls;
// ...
public function buildRules(RulesChecker $rules)
{
    $rules->add(new IsUniqueWithNulls(['parent_id', 'instance_id', 'name']), 'uniqueNamePerParent', [
        'errorField' => 'name',
        'message' => 'Name must be unique per parent.'
    ]);
    return $rules;
}

そのようなルールを作成する方法の例として、コアのルールを確認してください。

カスタムルールオブジェクト作成

もしもアプリケーションがよく再利用されるルールを持っているのであれば、再利用可能なクラスにそうしたルールをまとめると役に立ちます。

// src/Model/Rule/CustomRule.php の中で
namespace App\Model\Rule;

use Cake\Datasource\EntityInterface;

class CustomRule
{
    public function __invoke(EntityInterface $entity, array $options)
    {
        // 何かする
        return false;
    }
}

// カスタムルールの追加
use App\Model\Rule\CustomRule;

$rules->add(new CustomRule(...), 'ruleName');

カスタムルールクラスを作ることでコードを 重複がない状態 (訳注：DRY = Don't Repeat Yourself の訳) に保つことができ、またドメインルールを簡単にテストできるようになります。

ルールの無効化

エンティティーを保存する時、必要であればルールを無効にできます。

$articles->save($article, ['checkRules' => false]);

バリデーション対アプリケーションルール

CakePHP の ORM は検証に二層のアプローチを使う点がユニークです。

一層目はバリデーションです。バリデーションルールは、ステートレスな方法の操作を意図しています。それらは、形状、データ型、データの書式が正しいことを保証するために最もよく作用します。

二層目は、アプリケーションルールです。アプリケーションルールは、あなたのエンティティーのステートフルなプロパティーのチェックに最もよく作用します。例えば、バリデーションルールは、メールアドレスが有効なことを保証することができますが、アプリケーションルールは、メールアドレスがユニークであることを保証できます。

すでに見てきた通りに、一層目は newEntity() か patchEntity() を呼ぶ時に Validator オブジェクトを通して行われます。

$validatedEntity = $articlesTable->newEntity(
    $unsafeData,
    ['validate' => 'customName']
);
$validatedEntity = $articlesTable->patchEntity(
    $entity,
    $unsafeData,
    ['validate' => 'customName']
);

上記の例では、 validationCustomName() メソッドを使って定義される「カスタム」バリデータを使用します。

public function validationCustomName($validator)
{
    $validator->add(...);
    return $validator;
}

バリデーションは文字列や配列を渡されることを想定しています。それらがリクエストから得られるものですので:

// src/Model/Table/UsersTable.php の中で
public function validatePasswords($validator)
{
    $validator->add('confirm_password', 'no-misspelling', [
        'rule' => ['compareWith', 'password'],
        'message' => 'パスワードが一致しません',
    ]);

    ...
    return $validator;
}

バリデーションはエンティティーのプロパティーを直接設定した時には起動 しません 。

$userEntity->email = 'not an email!!';
$usersTable->save($userEntity);

上記の例では、バリデーションは newEntity() と patchEntity() メソッドのためにのみ起動されるので、エンティティーは保存されてしまうことになります。検証の第二層がこの状況に対処します。

アプリケーションルールは上で説明したように save() か delete() が呼ばれるといつでもチェックされます。

// src/Model/Table/UsersTable.php の中で
public function buildRules(RulesChecker $rules)
{
    $rules->add($rules->isUnique(['email']));

    return $rules;
}

// アプリケーションのコード中のどこかで
$userEntity->email = 'a@duplicated.email';
$usersTable->save($userEntity); // 偽を返します

バリデーションは直接のユーザー入力を意図しており、アプリケーションルールはアプリケーション中で生成されたデータの変更に特化しています。

// src/Model/Table/OrdersTable.php の中で
public function buildRules(RulesChecker $rules)
{
    $check = function($order) {
        if ($order->shipping_mode !== 'free') {
            return true;
        }

        return $order->price >= 100;
    };
    $rules->add($check, [
        'errorField' => 'shipping_mode',
        'message' => '100ドル以下の注文を送料無料にはできません！'
    ]);

    return $rules;
}

// アプリケーションのコード中のどこかで
$order->price = 50;
$order->shipping_mode = 'free';
$ordersTable->save($order); // 偽を返します

バリデーションをアプリケーションルールとして使用

ある状況ではユーザーあるいはアプリケーションによって生成されたデータの両方に対して同じ検証の処理を走らせたいかもしれません。これは、エンティティーのプロパティーを直接設定するような CLI スクリプトを走らせる時に起こり得るでしょう。

// src/Model/Table/UsersTable.php の中で
public function validationDefault(Validator $validator)
{
    $validator->add('email', 'valid_email', [
        'rule' => 'email',
        'message' => '無効なメールアドレスです'
    ]);

    // ...

    return $validator;
}

public function buildRules(RulesChecker $rules): RulesChecker
{
    // アプリケーションルールの追加
    $rules->add(function($entity) {
        $data = $entity->extract($this->getSchema()->columns(), true);
        if (!$entity->isNew() && !empty($data)) {
            $data += $entity->extract((array)$this->getPrimaryKey());
        }
        $validator = $this->getValidator('default');
        $errors = $validator->validate($data, $entity->isNew());
        $entity->setErrors($errors);

        return empty($errors);
    });

    // ...

    return $rules;
}

保存が実行されると、追加された新しいアプリケーションのおかげで失敗します。

$userEntity->email = 'not an email!!!';
$usersTable->save($userEntity);
$userEntity->getError('email'); // 無効なメールアドレスです

同じ結果が newEntity() や patchEntity() を使う時にも期待できます。

$userEntity = $usersTable->newEntity(['email' => 'not an email!!']);
$userEntity->getError('email'); // 無効なメールアドレスです

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

データの保存

class Cake\ORM\Table

データを読み出した 後は、おそらく更新を行ってその変更を保存したいでしょう。

データ保存の概要

アプリケーションは通常、データが保存されるいくつかの方法を持っています。最初のひとつは言うまでもなくウェブフォームを通して、また他の方法としてはコード中で直接データを生成または変更して、データベースに送られるようにすることです。

データの挿入

データベースにデータを挿入する最も簡単な方法は、新しいエンティティーを作成してそれを Table クラスの save() に渡すことです。

use Cake\ORM\TableRegistry;

// Prior to 3.6 use TableRegistry::get('Articles')
$articlesTable = TableRegistry::getTableLocator()->get('Articles');
$article = $articlesTable->newEmptyEntity();

$article->title = '新しい記事';
$article->body = 'これは記事の本文です';

if ($articlesTable->save($article)) {
    // $article エンティティーは今や id を持っています
    $id = $article->id;
}

データの更新

データの更新は同じく簡単で、そして save() メソッドはこの目的でもまた使用されます。

use Cake\ORM\TableRegistry;

// Prior to 3.6 use TableRegistry::get('Articles')
$articlesTable = TableRegistry::getTableLocator()->get('Articles');
$article = $articlesTable->get(12); // id 12 の記事を返します

$article->title = 'CakePHP は最高のフレームワークです！';
$articlesTable->save($article);

CakePHP は挿入または更新のいずれの処理を行うかを isNew() メソッドの返値に基づいて知ります。 get() や find() で返されたエンティティーは、 isNew() が呼ばれた時には常に false を返します。

アソシエーションの保存

既定では save() メソッドはアソシエーションの一階層目も保存します。

// Prior to 3.6 use TableRegistry::get('Articles')
$articlesTable = TableRegistry::getTableLocator()->get('Articles');
$author = $articlesTable->Authors->findByUserName('mark')->first();

$article = $articlesTable->newEmptyEntity();
$article->title = 'mark の記事';
$article->author = $author;

if ($articlesTable->save($article)) {
    // 外部キー値は自動でセットされます。
    echo $article->author_id;
}

save() メソッドはアソシエーションのレコードも作成することができます。

$firstComment = $articlesTable->Comments->newEmptyEntity();
$firstComment->body = 'CakePHP の機能は傑出しています';

$secondComment = $articlesTable->Comments->newEmptyEntity();
$secondComment->body = 'CakePHP のパフォーマンスは素晴らしい！';

$tag1 = $articlesTable->Tags->findByName('cakephp')->first();
$tag2 = $articlesTable->Tags->newEmptyEntity();
$tag2->name = 'すごい';

$article = $articlesTable->get(12);
$article->comments = [$firstComment, $secondComment];
$article->tags = [$tag1, $tag2];

$articlesTable->save($article);

多対多レコードの関連付け

前の例は一つの記事といくつかのタグを関連付ける方法を例示しています。同じことをするための別の方法として、アソシエーション側で link() メソッドを使用する方法があります。

$tag1 = $articlesTable->Tags->findByName('cakephp')->first();
$tag2 = $articlesTable->Tags->newEmptyEntity();
$tag2->name = 'すごい';

$articlesTable->Tags->link($article, [$tag1, $tag2]);

多対多レコードの紐付け解除

多対多レコードの紐付け解除は unlink() メソッドを通して行われます。

$tags = $articlesTable
    ->Tags
    ->find()
    ->where(['name IN' => ['cakephp', 'すごい']])
    ->toList();

$articlesTable->Tags->unlink($article, $tags);

プロパティーを直接設定または変更してレコードを更新した時は、データ検証は行われませんので、フォームデータを受け取る時にはこれは問題になります。次のセクションでは、データが検証されて保存されるように、効果的にエンティティーに変換するための方法を例示します。

リクエストデータのエンティティーへの変換

データを変更してデータベースに保存して戻す前に、リクエストデータをリクエスト中の配列形式から変換する必要があります。ORM が使用するエンティティーです。 Table クラスは、リクエストデータを一つまたは複数のエンティティーに変換するための簡単で効果的な方法を提供します。単一のエンティティーの変換には次の方法を使います。

// コントローラーの中で

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = TableRegistry::getTableLocator()->get('Articles');

// 検証して Entity オブジェクトに変換します。
$entity = $articles->newEntity($this->request->getData());

注釈

もし newEntity() を使っていて、返されてきたエンティティーが渡したデータのいくつかまたはすべてを失っている場合は、設定したいカラムがそのエンティティーの $_accessible プロパティーに列挙されているかをもう一度確認してみてください。一括代入 (Mass Assignment) をご覧ください。

リクエストデータはあなたのエンティティーの構造に従っていなければなりません。例えば、もしも一つの記事が、一人のユーザーに属していて、複数のコメントを持っているなら、リクエストデータはこうなっているはずです。

$data = [
    'title' => '勝利のための CakePHP',
    'body' => 'CakePHP でのベーキングはウェブ開発を楽しくします！',
    'user_id' => 1,
    'user' => [
        'username' => 'mark'
    ],
    'comments' => [
        ['body' => 'CakePHP の機能は傑出しています'],
        ['body' => 'CakePHP のパフォーマンスは素晴らしい！'],
    ]
];

既定では、 エンティティー構築前のデータ検証 のセクションで説明している通り、 newEntity() メソッドは渡されたデータを検証します。もし、検証を回避したければ 'validate' => false オプションを渡してください。

$entity = $articles->newEntity($data, ['validate' => false]);

入れ子になったアソシエーションを保存するフォームを作る時は、どのアソシエーションが変換されるべきかを定義する必要があります。

// コントローラーの中で

$articles = $this->getTableLocator()->get('Articles');

// 入れ子になったアソシエーション付きの新しいエンティティー
$entity = $articles->newEntity($this->request->getData(), [
    'associated' => [
        'Tags', 'Comments' => ['associated' => ['Users']]
    ]
]);

上記は 'Tags' 、 'Comments' そして Comments 用の 'Users' が変換されるべきであることを示しています。代わりに、簡潔にするためにドット記法を使うことができます。

// コントローラーの中で

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = TableRegistry::getTableLocator()->get('Articles');

// ドット記法を用いた、入れ子になったアソシエーション付きの新しいエンティティー
$entity = $articles->newEntity($this->request->getData(), [
    'associated' => ['Tags', 'Comments.Users']
]);

入れ子になったアソシエーションの変換を無効にする時は、次のようになります。

$entity = $articles->newEntity($data, ['associated' => []]);
// または...
$entity = $articles->patchEntity($entity, $data, ['associated' => []]);

関連付けられたデータもまた、指定しない限り、既定では検証されます。アソシエーションごとに使われる検証セットを変更することもできます。

// コントローラーの中で

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = TableRegistry::getTableLocator()->get('Articles');

// Tags アソシエーションの検証を回避して
// Comments.Users 用に 'signup' の検証セットを指定します
$entity = $articles->newEntity($this->request->getData(), [
    'associated' => [
        'Tags' => ['validate' => false],
        'Comments.Users' => ['validate' => 'signup']
    ]
]);

関連付けられた変換のために、異なる検証を使用する方法に関する詳しい情報は、アソシエーションに異なるバリデーションセットを使用 の章をご覧ください。

以下の図表は newEntity() または patchEntity() メソッドの内部でどんなことが起きるのかの概要を示しています。

newEntity() からはいつでもエンティティーが返されることを当てにすることができます。もし検証に失敗した場合、エンティティーはエラーを含んでいる状態になり、無効なフィールドはどれも作成されたエンティティー上には設定されません。

BelongsToMany データの変換

もし belongsToMany アソシエーションを保存しようとしている場合は、エンティティーデータのリストまたは ID のリストを使うことができます。エンティティーデータのリストを使うときはリクエストデータはこうなるべきです。

$data = [
    'title' => '私のタイトル',
    'body' => '本文',
    'user_id' => 1,
    'tags' => [
        ['name' => 'CakePHP'],
        ['name' => 'インターネット'],
    ],
];

上記は二つの新しいタグを作成します。もし既存のタグをある記事に紐付けたいのであれば ID のリストを使うことができます。リクエストデータはこうなるべきです。

$data = [
    'title' => '私のタイトル',
    'body' => '本文',
    'user_id' => 1,
    'tags' => [
        '_ids' => [1, 2, 3, 4],
    ]
];

もし何らかの既存の belongsToMany レコードを紐付ける必要があって、かつ同時に新規のものも作成する必要があるのであれば、拡張の形式を使うことができます。

$data = [
    'title' => '私のタイトル',
    'body' => '本文',
    'user_id' => 1,
    'tags' => [
        ['name' => '新しいタグ'],
        ['name' => '別の新しいタグ'],
        ['id' => 5],
        ['id' => 21],
    ],
];

上記のデータがエンティティーに変換されるとき、四つのタグを持つことになります。最初の二つは新規オブジェクトで、次の二つは既存レコードを参照することになります。

belongsToMany データを変換するときは、 onlyIds オプションを使って、新しいエンティティーの作成を行わなくすることができます。有効にすると、このオプションは belongsToMany の変換を _ids キーの使用のみに制限して、他のすべてのデータを無視します。

HasMany データの変換

もし、既存の hasMeny アソシエーションを更新したり、それらのプロパティーを更新したい場合、エンティティーに hasMany アソシエーションが設定されていることを最初に確認する必要があります。そのとき、以下のようなリクエストデータが使えます。

$data = [
    'title' => 'My Title',
    'body' => 'The text',
    'comments' => [
        ['id' => 1, 'comment' => 'Update the first comment'],
        ['id' => 2, 'comment' => 'Update the second comment'],
        ['comment' => 'Create a new comment'],
    ],
];

もし hasMany アソシエーションを保存しようとしている場合で、既存のレコードを新しい親レコードに紐付けたいのであれば、 _ids 形式を使うことができます。

$data = [
    'title' => '私の新しい記事',
    'body' => '本文',
    'user_id' => 1,
    'comments' => [
        '_ids' => [1, 2, 3, 4]
    ],
];

hasMany データを変換するときは、 onlyIds オプションを使って、新しいエンティティーの作成を行わなくすることができます。有効にすると、このオプションは belongsToMany の変換を _ids キーの使用のみに制限して、他のすべてのデータを無視します。

複数レコードの変換

一度に複数のレコードを作成／更新するフォームを作るときは、 newEntities() を使うことができます。

// コントローラーの中で。

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = $this->getTableLocator()->get('Articles');
$entities = $articles->newEntities($this->request->getData());

この場合には、複数の記事用のリクエストデータはこうなるべきです。

$data = [
    [
        'title' => '一番目の投稿',
        'published' => 1
    ],
    [
        'title' => '二番目の投稿',
        'published' => 1,
    ],
];

リクエストデータをエンティティーに変換し終えたら、それらを save() または delete() できます。

// コントローラーの中で。
foreach ($entities as $entity) {
    // エンティティーを保存
    $articles->save($entity);

    // エンティティーを削除
    $articles->delete($entity);
}

上記は各エンティティーの保存で個別のトランザクションが走ります。もし単一のトランザクションですべてのエンティティーを処理したいのであれば、 transactional() を使うことができます。

// コントローラーの中で。
$articles->getConnection()->transactional(function () use ($articles, $entities) {
    foreach ($entities as $entity) {
        $articles->save($entity, ['atomic' => false]);
    }
});

アクセス可能なフィールドの変更

newEntity() に、アクセス不可能なフィールドに書き込ませることもできます。例えば id は通常は _accessible プロパティーから外れます。そうした場合には、 accessibleFields オプションを使うことができます。これは関連付けられたエンティティーの ID を維持するために便利かもしれません。

// コントローラーの中で

$articles = $this->getTableLocator()->get('Articles');
$entity = $articles->newEntity($this->request->getData(), [
    'associated' => [
        'Tags', 'Comments' => [
            'associated' => [
                'Users' => [
                    'accessibleFields' => ['id' => true],
                ],
            ],
        ],
    ],
]);

上記では、問題のエンティティーについては Comments と Users の間でのアソシエーションが変わらずに維持されます。

注釈

もし newEntity() を使っていて、返されてきたエンティティーが渡したデータのいくつかまたはすべてを失っている場合は、設定したいカラムがそのエンティティーの $_accessible プロパティーに列挙されているかをもう一度確認してみてください。一括代入 (Mass Assignment) をご覧ください。

リクエストデータをエンティティーにマージ

エンティティーを更新するためには、既存のエンティティーに対して直接リクエストデータを適用することを選んでもよいです。これは、データベースに保存するためにすべてのフィールドを送るのとは対照的に、実際に変更されたフィールドのみが保存されるようにできる利点があります。 patchEntity() を使って、生データの配列を既存のエンティティーにマージすることができます。

// コントローラーの中で。

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = $this->getTableLocator()->get('Articles');
$article = $articles->get(1);
$articles->patchEntity($article, $this->request->getData());
$articles->save($article);

検証と patchEntity

newEntity() と同じように、 patchEntity メソッドは、データがエンティティーにコピーされる前に検証を行います。このメカニズムは エンティティー構築前のデータ検証 のセクションで説明されています。エンティティーにパッチを当てる際に検証を無効にしたいのであれば、 validate オプションを渡してください。

// コントローラーの中で。

$articles = $this->getTableLocator()->get('Articles');
$article = $articles->get(1);
$articles->patchEntity($article, $data, ['validate' => false]);

当該のエンティティー、または何らかのアソシエーションに対して使われる検証セットを変更することもできます。

$articles->patchEntity($article, $this->request->getData(), [
    'validate' => 'custom',
    'associated' => ['Tags', 'Comments.Users' => ['validate' => 'signup']]
]);

HasMany と BelongsToMany へのパッチ

前のセクションで説明したように、リクエストデータはあなたのエンティティーの構造に従っていなければなりません。 patchEntity() メソッドはアソシエーションをマージする能力も同じく持っていて、既定ではアソシエーションの一階層目のみがマージされますが、マージされるアソシエーションを制御したい、または深い深い階層についてマージしたい場合、メソッドの第三引数を使うことができます。

// コントローラーの中で。
$associated = ['Tags', 'Comments.Users'];
$article = $articles->get(1, ['contain' => $associated]);
$articles->patchEntity($article, $this->request->getData(), [
    'associated' => $associated,
]);
$articles->save($article);

アソシエーションは、元のエンティティーの主キーフィールドを、データ配列中に含まれているフィールドにマッチさせることでマージを行います。もしもアソシエーションの対象のプロパティーに前のエンティティーがなければ、アソシエーションは新しくエンティティーを構築します。

例えば、次のような何らかのリクエストデータを与えます。

$data = [
    'title' => '私のタイトル',
    'user' => [
        'username' => 'mark',
    ],
];

user プロパティーの中にエンティティーない状態で、エンティティーへのパッチを試みると、新しい user エンティティーが作成されます。

// コントローラーの中で
$entity = $articles->patchEntity(new Article, $data);
echo $entity->user->username; // 'mark' を出力します

hasMany の belongsToMany アソシエーションについても同じことが言えますが、以下の注意点があります。

注釈

belongsToMany アソシエーションについては、関連付けられたエンティティー用のプロパティーがアクセス可能になっているようにしてください。

もし、 Product belongsToMany Tag であれば、こうなります。

// Product エンティティーの中で
protected $_accessible = [
    // .. 他のプロパティー
   'tags' => true,
];

注釈

hasMany と belongsToMany アソシエーションでは、もしデータ配列中のレコードと主キーがマッチしないエンティティーがあった場合、それらのレコードは結果のエンティティーから除かれてしまいます。

patchEntity() も patchEntities() もデータを保存するわけではないことを覚えていてください。単に与えられたエンティティーを変更（または作成）するだけです。エンティティーを保存するためには、そのテーブルの save() メソッドを呼ばなければなりません。

例えば、以下の場合を考えてみてください。

$data = [
    'title' => '私のタイトル',
    'body' => '本文',
    'comments' => [
        ['body' => '一番目のコメント', 'id' => 1],
        ['body' => '二番目のコメント', 'id' => 2],
    ]
];
$entity = $articles->newEntity($data);
$articles->save($entity);

$newData = [
    'comments' => [
        ['body' => '変更されたコメント', 'id' => 1],
        ['body' => '新しいコメント'],
    ]
];
$articles->patchEntity($entity, $newData);
$articles->save($entity);

最後に、もしエンティティーが配列に変換されて戻されたとしたら、以下のような結果を得ることになります。

[
    'title' => '私のタイトル',
    'body' => '本文',
    'comments' => [
        ['body' => '変更されたコメント', 'id' => 1],
        ['body' => '新しいコメント'],
    ]
];

ご覧のように、id が 2 のコメントはもはやなくなっています。 $newData 配列のどのレコードにもマッチしなかったためです。これは、 CakePHP がリクエストデータに示された、それが新規なのかどうかを反映させているために起こります。

この方法のいくつかの利点は、エンティティーを再び保存する際に実行される多くの操作を削減することにあります。

ただ、これは id 2 のコメントがデータベースから削除されたことを意味するものではない点に注意してください。もし当該の記事へのコメントで、当該のエンティティーの中にないものを削除したいのであれば、その主キーを集約してリストにないものの一括削除を実行してください。

// コントローラーの中で。

// Prior to 3.6 use TableRegistry::get('Comments')
$comments = TableRegistry::getTableLocator()->get('Comments');
$present = (new Collection($entity->comments))->extract('id')->filter()->toList();
$comments->deleteAll([
    'article_id' => $article->id,
    'id NOT IN' => $present
]);

ご覧のように、これはまたアソシエーションがシングルセットのように実装される必要がある場所で問題を解決するのを助けます。

また一回で複数のエンティティーに対してパッチをあてることもできます。 hasMany と belongsToMany アソシエーションに対してのパッチのために作られた考えでは、主キーフィールドの値でマッチさせ、元のエンティティー配列の内、マッチできなかったものは結果配列から取り除かれて現れない、というように複数のエンティティーにパッチをあてます。

// コントローラーの中で。

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = TableRegistry::getTableLocator()->get('Articles');
$list = $articles->find('popular')->toList();
$patched = $articles->patchEntities($list, $this->request->getData());
foreach ($patched as $entity) {
    $articles->save($entity);
}

patchEntity() を使うのに似ていて、配列中の各エンティティーにマージされることになるアソシエーションを制御するための第三引数を利用することができます。

// コントローラーの中で。
$patched = $articles->patchEntities(
    $list,
    $this->request->getData(),
    ['associated' => ['Tags', 'Comments.Users']]
);

エンティティー構築前のリクエストデータ変更

もしリクエストデータがエンティティーに変換される前にそれらを変更する必要がある時は、 Model.beforeMarshal イベントを利用することができます。このイベントはエンティティーが作成される直前に、リクエストデータを操作させてくれます。

// ファイルの先頭に use ステートメントを入れること。
use Cake\Event\Event;
use ArrayObject;

// テーブルまたはビヘイビアークラスの中で
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
{
    if (isset($data['username'])) {
        $data['username'] = mb_strtolower($data['username']);
    }
}

$data パラメーターは ArrayObject のインスタンスですので、エンティティーを作成するのに使われるデータを変更するために return する必要はありません。

beforeMarshal の主な目的は、単純な誤りを自動的に解決できる時や、データが正しいフィールドに入るように再構成される必要がある時に、検証プロセスを通過できるようにユーザーを支援することです。

Model.beforeMarshal イベントは検証プロセスの開始時に引き起こされますが、その理由は beforeMarshal では検証ルールや、フィールドのホワイトリストのような保存オプションを変更できるようになっているからです。検証はこのイベントが終了した直後に行われます。検証が行われる前にデータを変更をするありふれた例は、保存前に全フィールドをトリムすることです。

// ファイルの先頭に use ステートメントを入れること。
use Cake\Event\Event;
use ArrayObject;

// テーブルまたはビヘイビアークラスの中で
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
{
    foreach ($data as $key => $value) {
        if (is_string($value)) {
            $data[$key] = trim($value);
        }
    }
}

変換プロセスの動作の仕方の理由で、もしあるフィールドが検証に渡されない場合それは自動的にデータ配列から削除されてエンティティーにはコピーされません。これはエンティティーオブジェクトへの入力から整合性のないデータを防止するためです。

それから、 beforeMarshal 中のデータは渡されたデータのコピーです。これは、他のどこかで使われるかもしれない、元のユーザー入力を保持するために重要だからです。

エンティティー構築前のデータ検証

データの検証 の章には、データが正しく整合性を保ち続けられるようにするために CakePHP の検証機能をどう使うかについてより詳しい情報があります。

プロパティーのマスアサインメント攻撃の回避

リクエストデータからエンティティーを作成またはマージする時には、エンティティー中でユーザーに何を変更させるか、または追加させるかについて注意深くある必要があります。例えば、 user_id を含んでいるリクエスト中の配列を送ることで、攻撃者は記事の所有者を変更することができ、望まない影響を引き起こします。

// ['user_id' => 100, 'title' => 'ハックしました！'] を含んでいます。
$data = $this->request->data;
$entity = $this->patchEntity($entity, $data);
$this->save($entity);

この攻撃を防御するための二つの方法があります。最初の一つはエンティティーの一括代入 (Mass Assignment) 機能を使うリクエストに対して安全に設定されるように既定のカラムを設定することです。

二番目の方法はエンティティーを作成またはマージする時に fieldList オプションを利用することです。

// ['user_id' => 100, 'title' => 'ハックしました！'] を含んでいます。
$data = $this->request->data;

// タイトルのみ変更することを許します
$entity = $this->patchEntity($entity, $data, [
    'fieldList' => ['title']
]);
$this->save($entity);

アソシエーションにどのプロパティーが割り当てられるかを制御することもできます。

// タイトルとタグのみ変更することを許し、
// かつ、タグ名のみが設定可能なカラムです
$entity = $this->patchEntity($entity, $data, [
    'fieldList' => ['title', 'tags'],
    'associated' => ['Tags' => ['fieldList' => ['name']]]
]);
$this->save($entity);

この機能の利用は、多くの違った機能にユーザーがアクセス可能で、ユーザーに権限に基づいて異なるデータを編集できるようにしたい時に便利です。

fieldList オプションは newEntity() 、 newEntities() および patchEntities() メソッドでも受け入れられます。

バージョン 3.4.0 で非推奨: fieldList の代わりに fields を使用してください。

エンティティーの保存

Cake\ORM\Table::save(Entity $entity, array $options = [])

リクエストデータをデータベースに保存する時は、 save() に渡すために、まず最初に newEntity() を使って新しいエンティティーをハイドレートする必要があります。例えばこうです。

// コントローラーの中で

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = TableRegistry::getTableLocator()->get('Articles');
$article = $articles->newEntity($this->request->getData());
if ($articles->save($article)) {
    // ...
}

ORM は、挿入か更新のいずれが実行されるべきかを決定するために、エンティティーの isNew() メソッドを使用します。もし isNew() が真を返し、エンティティーが主キー値を持っていれば、 'exists' クエリーが発行されます。 'exists' クエリーは $options 引数に 'checkExisting' => false を渡すことで抑制することができます。

$articles->save($article, ['checkExisting' => false]);

いくつかのエンティティーを読み出した後は、おそらくそれらを変更して、データベースを更新したいでしょう。これは CakePHP では実に単純な課題です。

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = TableRegistry::getTableLocator()->get('Articles');
$article = $articles->find('all')->where(['id' => 2])->first();

$article->title = '私の新しいタイトル';
$articles->save($article);

保存する時は、 CakePHP は ルールを適用して 、データベーストランザクションの保存操作を巻き取ります。また、変更のあったプロパティーのみを更新します。上記の save() の呼び出しは、こんな SQL を生成します。

UPDATE articles SET title = '私の新しいタイトル' WHERE id = 2;

もし新しいエンティティーであれば、こんな SQL が生成されます。

INSERT INTO articles (title) VALUES ('私の新しいタイトル');

エンティティーが保存されると、いくつかのことが起こります。

もし無効になっていなければ、ルールのチェックが開始されます。

ルールのチェックが Model.beforeRules イベントを引き起こします。もしイベントが停止されると、保存操作は失敗し、 false を返します。

ルールがチェックされます。もしエンティティーが作成されようとしているのであれば、 create ルールが使われます。もしエンティティーが更新されようとしているのであれば、 update ルールが使われます。

Model.afterRules イベントが引き起こされます。

Model.beforeSave イベントが発動されます。もし停止されると、保存は中止され、 save() は false を返します。

親のアソシエーションが保存されます。例えば、列挙されたあらゆる belongsTo アソシエーションが保存されます。

エンティティーの modified フィールドが保存されます。

子のアソシエーションが保存されます。例えば、列挙されたあらゆる hasMany 、 hasOne 、または belongsToMany アソシエーションが保存されます。

Model.afterSave イベントが発動されます。

Model.afterSaveCommit イベントが発動されます。

以下の図表は上記の工程を図解しています。

作成および更新のルールについてのより詳しい情報は アプリケーションルールの適用 のセクションを参照してください。

警告

もしエンティティーが保存される時に何も変更が行われていなければ、コールバックは呼び出されません。なぜなら、保存が実行されないからです。

save() メソッドは成功時には変更されたエンティティーを返し、失敗時には false を返します。また save の $options 引数を使って、ルールやトランザクションを無効にすることができます。

// コントローラーまたはテーブルメソッドの中で
$articles->save($article, ['checkRules' => false, 'atomic' => false]);

アソシエーションの保存

エンティティーを保存する時には、いくつかの、またはすべての関連付けられたエンティティーを保存するように選択することもできます。既定では、すべての一階層目のエンティティーが保存されます。例えば、 Article の保存は、 articles テーブルに直接関連付けられているあらゆる dirty なエンティティーもまた自動的に更新します。

associated オプションを使うことで、どのエンティティーが保存されるかを、調整することができます。

// コントローラーの中で

// comments アソシエーションのみを保存します
$articles->save($entity, ['associated' => ['Comments']]);

ドット記法を使うことで、遠くの、または深い入れ子のアソシエーションを定義することができます。

// company 、その employees とそれぞれに関連する addresses を保存します。
$companies->save($entity, ['associated' => ['Employees.Addresses']]);

さらに、アソシエーションのドット記法はオプションの配列で組み合わせることができます。

$companies->save($entity, [
  'associated' => [
    'Employees',
    'Employees.Addresses'
  ]
]);

エンティティーはデータベースから読み出された時と同じ方法で構造化されていなければいけません。アソシエーションの入力をどう構築するか についてのフォームヘルパーのドキュメントを参照してください。

もしもエンティティーが構築された後で、アソシエーションのデータを構築または変更しようとしているなら、アソシエーションのプロパティーが変更されたことを dirty() で印さなければいけません。

$company->author->name = 'Master Chef';
$company->dirty('author', true);

BelongsTo アソシエーションの保存

belongsTo アソシエーションを保存する時は、 ORM は単一の入れ子のエンティティーを、単数形で命名された、アンダースコアー区切り のアソシエーション名で期待しています。例えばこうです。

// コントローラーの中で。
$data = [
    'title' => '一番目の投稿',
    'user' => [
        'id' => 1,
        'username' => 'mark'
    ]
];

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = TableRegistry::getTableLocator()->get('Articles');
$article = $articles->newEntity($data, [
    'associated' => ['Users']
]);

$articles->save($article);

HasOne アソシエーションの保存

hasOne アソシエーションを保存する時は、 ORM は単一の入れ子のエンティティーを、単数形で命名された、アンダースコアー区切り のアソシエーション名で期待しています。例えばこうです。

// コントローラーの中で。
$data = [
    'id' => 1,
    'username' => 'cakephp',
    'profile' => [
        'twitter' => '@cakephp'
    ]
];

// Prior to 3.6 use TableRegistry::get('Articles')
$users = TableRegistry::getTableLocator()->get('Users');
$user = $users->newEntity($data, [
    'associated' => ['Profiles']
]);
$users->save($user);

HasMany アソシエーションの保存

hasMany アソシエーションを保存する時は、 ORM はエンティティーの配列を、複数形で命名された、アンダースコアー区切り のアソシエーション名で期待しています。例えばこうです。

// コントローラーの中で。
$data = [
    'title' => '一番目の投稿',
    'comments' => [
        ['body' => 'これまでで最高の投稿'],
        ['body' => '私は実にこれが好きだ。']
    ]
];

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = TableRegistry::getTableLocator()->get('Articles');
$article = $articles->newEntity($data, [
    'associated' => ['Comments']
]);
$articles->save($article);

hasMany アソシエーションを保存する時は、 関連付けられたレコードは、更新されるか挿入されるかのいずれかになります。レコードがデータベース中ですでに関連付けられたレコードを持っている場合、二つの保存方法の選択肢があります。

append

関連付けられたレコードはデータベース中で更新されるか、もしくは既存のどのレコードにもマッチしなければ、挿入されます。

replace

与えられたレコードのいずれにもマッチしない既存のレコードはデータベースから削除され、与えられたレコードのみが残ります（もしくは挿入されます）。

既定では append 保存方法が使われます。 saveStrategy の定義に関する詳細は、 hasMany アソシエーション をご覧ください。

新しいレコードを既存のアソシエーションに追加する時はいつでも、そのアソシエーションのプロパティーを 'dirty' として印さなければいけません。これが ORM に、アソシエーションのプロパティーが保存されなければならないことを伝えます。

$article->comments[] = $comment;
$article->dirty('comments', true);

dirty() の呼び出しがないと、更新された comments は保存されません。

既存のレコードでアソシエーションの新しいエンティティを作成する場合、最初に対応するプロパティを初期化する必要があります。

$mentor->students = [];

初期化せずに $mentor->students[] = $student; を呼んでも、効果はありません。

BelongsToMany アソシエーションの保存

belongsToMany アソシエーションを保存する時は、 ORM はエンティティーの配列を、複数形で、アンダースコアー区切り のアソシエーション名で期待しています。例えばこうです。

// コントローラーの中で。
$data = [
    'title' => 'First Post',
    'tags' => [
        ['tag' => 'CakePHP'],
        ['tag' => 'Framework']
    ]
];

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = TableRegistry::getTableLocator()->get('Articles');
$article = $articles->newEntity($data, [
    'associated' => ['Tags']
]);
$articles->save($article);

リクエストデータをエンティティーに変換する時は、 newEntity() と newEntities() メソッドが、両方のプロパティーの配列や、 _ids キーでの ID のリストについても扱います。

_ids キーの使用は、belongs to many アソシエーション用に、フォームコントロール上のセレクトボックスやチェックボックスを構築するのを簡単にします。詳しくはリクエストデータのエンティティーへの変換 のセクションを参照してください。

belongsToMany アソシエーションを保存する時は、二つの保存方法の選択肢があります。

append

新しい紐付けのみが、このアソシエーションのそれぞれの側に作成されます。この方法は、既存の紐付けについては、保存されるエンティティーの配列に与えられなかったとしても解除しません。

replace

保存する時に、既存の紐付けは除去されて、新しい紐付けか結合テーブルに作成されます。もし、保存しようとしているエンティティーのいくつかが、データベース中に既存の紐付けとしてある場合、それらのリンクは削除ではなく更新されて、再保存されます。

saveStrategy の定義に関する詳細は、 belongsToMany アソシエーション をご覧ください。

既定は replace の方法が使われます。新しいレコードを既存のアソシエーションに追加する時はいつでも、そのアソシエーションのプロパティーを 'dirty' として印さなければいけません。これが ORM に、アソシエーションのプロパティーが保存されなければならないことを伝えます。

$article->tags[] = $tag;
$article->dirty('tags', true);

dirty() の呼び出しがないと、更新された tags は保存されません。

二つの既存のエンティティー間でアソシエーションを作りたいことがしばしばあるかもしれません。例えば、ユーザーがある記事を共同で編集するなど。これは link メソッドを使って、次のようにします。

$article = $this->Articles->get($articleId);
$user = $this->Users->get($userId);

$this->Articles->Users->link($article, [$user]);

belongsToMany アソシエーションを保存する時に、いくつかの追加データを結合テーブルに保存することはありそうなことです。前のタグの例では、記事に投票した人の vote_type になるかもしれません。 vote_type は upvote や downvote で、文字列で表現されます。関係は Users と Articles の間になります。

アソシエーションと vote_type の保存は、まず _joinData に何らかのデータを追加して、そして link() でそのアソシエーションを保存します。例はこうです。

$article = $this->Articles->get($articleId);
$user = $this->Users->get($userId);

$user->_joinData = new Entity(['vote_type' => $voteType], ['markNew' => true]);
$this->Articles->Users->link($article, [$user]);

結合テーブルへの追加データの保存

いくつかの状況では、BelongsToMany アソシエーションを結合するテーブルは、追加のカラムを持ちます。 CakePHP はこれらのプロパティーをカラムに保存することを簡単にします。 belongsToMany アソシエーションのそれぞれのエンティティーは、 _joinData プロパティーを持っていて、これは結合テーブル上の追加のカラムを含んでいます。このデータは配列か Entity インターフェイスになります。例えば、もしも Students BelongsToMany Courses であれば、こんな結合テーブルになるかもしれません。

id | student_id | course_id | days_attended | grade

データを保存する時、データを _joinData プロパティーに設定することで、結合テーブル上の追加のカラムに投入することができます。

$student->courses[0]->_joinData->grade = 80.12;
$student->courses[0]->_joinData->days_attended = 30;

$studentsTable->save($student);

_joinData プロパティーはエンティティーになるか、もしリクエストデータからエンティティーを構築したならデータの配列になります。リクエストデータで結合テーブルのデータを保存する場合、 POST データはこのようになります。

$data = [
    'first_name' => 'Sally',
    'last_name' => 'Parker',
    'courses' => [
        [
            'id' => 10,
            '_joinData' => [
                'grade' => 80.12,
                'days_attended' => 30
            ]
        ],
        // 他のコース
    ]
];
$student = $this->Students->newEntity($data, [
    'associated' => ['Courses._joinData']
]);

FormHelper で入力を正しく構築する方法については 関連データの入力を作成のドキュメントを参照してください。

複雑な型の保存

テーブルは、文字列、整数、浮動小数、真偽などの基本的な型で表現されたデータを格納することができます。しかし、配列やオブジェクトのようなより複雑な型を受け入れるように拡張することができ、こうしたデータをデータベースに保存できるような単純な型にシリアライズします。

この機能は、カスタム型システムを使って行われます。カスタムカラム型をどう構築するかについては独自の型を作成する のセクションを参照してください。

// 3.6 より前では、 config/bootstrap.php で型マップを追加する必要があります。

use Cake\Database\TypeFactory;

TypeFactory::map('json', 'Cake\Database\Type\JsonType');

// src/Model/Table/UsersTable.php の中で
use Cake\Database\Schema\TableSchemaInterface;

class UsersTable extends Table
{
    public function getSchema(): TableSchemaInterface
    {
        $schema = parent::getSchema();
        $schema->setColumnType('preferences', 'json');

        // 3.6 より前では、 ``setColumnType`` の代わりに ``columnType`` を使用してください。
        $schema->columnType('preferences', 'json');

        return $schema;
    }
}

上記のコードは preferences カラムを json カスタムタイプにマップします。これは、このカラムのデータを取得する時には、 JSON 文字列がアンシリアライズされて、エンティティーの中に配列として置かれることを意味します。

同様に、保存された時は、配列は JSON の表現に変換されて戻されます。

$user = new User([
    'preferences' => [
        'sports' => ['サッカー', '野球'],
        'books' => ['マスタリング PHP', 'ハムレット']
    ]
]);
$usersTable->save($user);

複雑なデータ型を使用する時、エンドユーザーから受け取ったデータが正しい型かを検証することは重要です。複雑なデータを正しく処理するのに失敗することは、悪意のあるユーザーが通常ではできないデータを保存できてしまう結果になります。

厳密な保存

Cake\ORM\Table::saveOrFail($entity, $options = [])

このメソッドを使用すると、次の条件で Cake\ORM\Exception\PersistenceFailedException を投げます。

アプリケーションルールのチェックに失敗した場合

エンティティーにエラーが含まれている場合

保存がコールバックによって中断された場合

これを使用することで、例えば、Shell のタスクの中で複雑なデータベースの操作を実行する際に役に立ちます。

注釈

このメソッドをコントローラー内で使用する場合、発生する可能性がある PersistenceFailedException を必ず捕まえてください。

保存に失敗したエンティティーを追跡する場合、 Cake\ORMException\PersistenceFailedException::getEntity() メソッドを使用できます。

try {
    $table->saveOrFail($entity);
} catch (\Cake\ORM\Exception\PersistenceFailedException $e) {
    echo $e->getEntity();
}

これは内部的に Cake\ORM\Table::save() コールを実行するので、対応するすべての保存イベントはトリガーされます。

複数のエンティティーの保存

Cake\ORM\Table::saveMany($entities, $options = [])

このメソッドを使うと、複数のエンティティーを自動で保存することができます。 $entities は newEntities() / patchEntities() で作成されたエンティティーの配列です。 $options は save() で受け入れるいくつかのオプションを持っています。

$data = [
    [
        'title' => '一番目の投稿',
        'published' => 1
    ],
    [
        'title' => '二番目の投稿',
        'published' => 1
    ],
];

// Prior to 3.6 use TableRegistry::get('Articles')
$articles = TableRegistry::getTableLocator()->get('Articles');
$entities = $articles->newEntities($data);
$result = $articles->saveMany($entities);

結果は成功時には更新されたエンティティーを、失敗時には false を返します。

一括更新

Cake\ORM\Table::updateAll($fields, $conditions)

時には行を個別に更新するのが効率的ではない、または必要でないことがあるかもしれません。このような場合は、一括して多くの行を変更するために、一括更新で新しいフィールド値と更新条件を割り当てる方が効率的です。

// すべての公開されていない記事を公開します。
function publishAllUnpublished()
{
    $this->updateAll(
        [  // フィールド
            'published' => true,
            'publish_date' => FrozenTime::now()
        ],
        [  // 条件
            'published' => false
        ]
    );
}

もし 一括更新をしつつ、かつ SQL 式を使う必要がある場合、内部的に updateAll() がプリペアードステートメントを使うので、式オブジェクトを使う必要があります。

use Cake\Database\Expression\QueryExpression;

...

function incrementCounters()
{
    $expression = new QueryExpression('view_count = view_count + 1');
    $this->updateAll([$expression], ['published' => true]);
}

一括更新は一行またはそれ以上の行が更新されると成功したとみなされます。

警告

updateAll は beforeSave/afterSave イベントを 引き起こしません 。もしこれらが必要であれば、まずレコードのコレクションを読み出して、そして、それらを更新してください。

updateAll() は利便性のためだけにあります。次のような、より柔軟なインターフェイスを使うこともできます。

// すべての公開されていない記事を公開します。
function publishAllUnpublished()
{
    $this->query()
        ->update()
        ->set(['published' => true])
        ->where(['published' => false])
        ->execute();
}

データを update する も参照してください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

データの削除

class Cake\ORM\Table

Cake\ORM\Table::delete(Entity $entity, $options = [])

読み込んだエンティティーは、テーブル本来の削除メソッドを呼びだすことによって削除することが出来ます。

// コントローラーの中
$entity = $this->Articles->get(2);
$result = $this->Articles->delete($entity);

エンティティー削除時には、いくつかのことが起こります。

削除ルール が適用されます。 ルールのチェックに失敗した場合、削除は中止されます。

Model.beforeDelete イベントが起動されます。このイベントが停止した場合、削除は失敗し、イベントの戻り値が返されます。

エンティティーが削除されます。

全ての依存関係先が削除されます。依存関係先がエンティティーとして削除されるとき、追加のイベントが起動されます。

BelongsToMany アソシエーション用の全ての結合テーブルのレコードが削除されます。

Model.afterDelete イベントが起動されます。

デフォルトでは、一回のトランザクションの中で全ての削除が行われますが、 atomic オプションで無効化することも出来ます。

$result = $this->Articles->delete($entity, ['atomic' => false]);

連鎖削除

エンティティーを削除するとき関連データを削除することもできます。HasOne や HasMany が dependent として設定されている場合、削除処理はそれらのエンティティーにも連鎖適用されます。デフォルトでは、関連テーブル内のエンティティーの削除には Cake\ORM\Table::deleteAll() が使用されます。 cascadeCallbacks オプションを true に設定することにより、関連するエンティティーを ORM に読み出させ、それらを個別に削除させるように選択できます。上記２つのオプションを有効にした HasMany のサンプルは、このようになります。

// テーブル内の初期化メソッド
$this->hasMany('Comments', [
    'dependent' => true,
    'cascadeCallbacks' => true,
]);

注釈

cascadeCallbacks が true の時、一括削除に比較して削除処理はだいぶ遅くなります。 cascadeCallbacks オプションは、あなたのアプリケーションがイベントリスナーによって処理される重要な仕事を持っている場合のみ有効にされるべきです。

一括削除

Cake\ORM\Table::deleteAll($conditions)

一行ずつ削除することが効率的でなかったり有用ではない時があります。そういったケースでは、一回で複数行を削除するために、一括削除を使うことが効率的です。

// 全てのスパムを削除する
function destroySpam()
{
    return $this->deleteAll(['is_spam' => true]);
}

一括削除では、１つ以上の行が削除されると成功したとみなされます。

警告

deleteAll は、beforeDelete/afterDelete イベントを 呼び出しません 。それらのイベントを呼び出したい場合、それぞれのレコードを読み込んで削除する必要があります。

厳密な削除

Cake\ORM\Table::deleteOrFail($entity, $options = [])

このメソッドを使用すると、次の条件で Cake\ORM\Exception\PersistenceFailedException を投げます。

エンティティーが新しい場合

エンティティーが主キーの値を持たない場合

アプリケーションルールのチェックが失敗した場合

削除コールバックによって中断された場合

保存に失敗したエンティティーを追跡する場合、 Cake\ORMException\PersistenceFailedException::getEntity() メソッドを使用できます。

try {
    $table->deleteOrFail($entity);
} catch (\Cake\ORM\Exception\PersistenceFailedException $e) {
    echo $e->getEntity();
}

これは内部的に Cake\ORM\Table::delete() コールを実行するので、対応するすべての削除イベントがトリガーされます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

アソシエーション - モデル同士を繋ぐ

アプリケーション中で、異なるオブジェクト同士の関連を定義することは よくあることです。例えば、記事は多くのコメントを持っていて著者に属しています。著者は多くの記事とコメントを持っています。 CakePHP はこうしたアソシエーションの管理を簡単にします。CakePHP には４つのアソシエーションがあります。 hasOne 、 hasMany 、 belongsTo 、そして belongsToMany です。

関係

アソシエーション種別

例

１ 対 １

hasOne

ユーザーは１つのプロフィールを持っている。

１ 対 多

hasMany

ユーザーは複数の記事を持つことができる。

多 対 １

belongsTo

多くの記事がユーザーに属している。

多 対 多

belongsToMany

タグは多くの記事に属している。

アソシエーションはテーブルオブジェクトの initialize() の中で定義されます。アソシエーション種別に合ったメソッドでアプリケーション中のアソシエーションを定義することができます。例えば、 ArticlesTable 中で belongsTo アソシエーションを定義したいのであれば次にようにします。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->belongsTo('Authors');
    }
}

アソシエーションの設定の最も単純な形式では、関連付けたいテーブルのエイリアスを受け取ります。既定ではアソシエーションの細目は CakePHP の規約に従います。もしアソシエーションの扱われ方をカスタマイズしたい場合には、セッターでそれを変更することができます。

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->belongsTo('Authors', [
                'className' => 'Publishing.Authors'
            ])
            ->setForeignKey('author_id')
            ->setProperty('author');
    }
}

アソシエーションをカスタマイズするために配列も使用できます。

$this->belongsTo('Authors', [
    'className' => 'Publishing.Authors',
    'foreignKey' => 'author_id',
    'propertyName' => 'author'
]);

しかし、配列は、流れるようなインターフェイスから得られるタイプヒントや自動補完を提供しません。

同一のテーブルを、異なるアソシエーションの種別を定義するために複数回使うこともできます。例えば、承認されたコメントとまだ検閲されていないものを分けたい場合を考えてみましょう。

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasMany('Comments')
            ->setFinder('approved');

        $this->hasMany('UnapprovedComments', [
                'className' => 'Comments'
            ])
            ->setFinder('unapproved')
            ->setProperty('unapproved_comments');
    }
}

ご覧のとおり、 className キーを指定することで、同一のテーブルの異なるアソシエーションのために同一のテーブルを使うことができます。親子関係を作成するために自己結合のテーブルを作成することもできます。

class CategoriesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasMany('SubCategories', [
            'className' => 'Categories'
        ]);

        $this->belongsTo('ParentCategories', [
            'className' => 'Categories'
        ]);
    }
}

アソシエーション種別で索引されたテーブル名のセットを含む配列を引数として受け取る Table::addAssociations() を一度呼ぶことで、まとめてアソシエーションを設定することもできます。

class PostsTable extends Table
{
    public function initialize(array $config): void
    {
       $this->addAssociations([
           'belongsTo' => [
               'Users' => ['className' => 'App\Model\Table\UsersTable']
           ],
           'hasMany' => ['Comments'],
           'belongsToMany' => ['Tags']
       ]);
    }
}

各アソシエーション種別は、そのエイリアスがキーで、値がアソシエーション設定データになった複数のアソシエーションを受け取ることができます。もし数値キーが使用された場合は値がアソシエーションのエイリアスとして扱われます。

hasOne アソシエーション

Users テーブルを Addresses テーブルが hasOne の関係になるように設定してみましょう。

まず、データベースのテーブルに正しくキーを付ける必要があります。 hasOne の関係を築くには、一方のテーブルが他方のテーブルのレコードを参照する外部キーを持つ必要があります。この場合では addresses テーブルが user_id というフィールドを持ちます。基本的なパターンは次の通りです。

hasOne: 相手側の モデルが外部キーを持ちます。

関係

スキーマ

Users hasOne Addresses

addresses.user_id

Doctors hasOne Mentors

mentors.doctor_id

注釈

CakePHP の規約に従うことは必須ではなく、アソシエーションの定義では任意の外部キーを使用するように上書きすることできます。それでも規約に従うとコードの繰り返しを少なくし、読みやすく、そしてメンテナンスしやすくすることができます。

UsersTable と AddressesTable クラスを作成したら、次のコードでアソシエーションを作ることができます。

class UsersTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasOne('Addresses');
    }
}

もしさらなる制御が必要であれば、セッターを使ってアソシエーションを定義することができます。例えば、特定のレコードのみを含むようにアソシエーションを制限したい場合は次のようにします。

class UsersTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasOne('Addresses')
            ->setName('Addresses')
            ->setFinder('primary')
            ->setDependent(true);
    }
}

異なる Addresses を複数のアソシエーションに分割したい場合は、次のようにすることができます。

class UsersTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasOne('HomeAddress', [
                'className' => 'Addresses'
            ])
            ->setProperty('home_address')
            ->setConditions(['HomeAddress.label' => 'Home'])
            ->setDependent(true);

        $this->hasOne('WorkAddress', [
                'className' => 'Addresses'
            ])
            ->setProperty('work_address')
            ->setConditions(['WorkAddress.label' => 'Work'])
            ->setDependent(true);
    }
}

注釈

条件の中に label のような同じカラムを持つ複数の hasOne アソシエーションがある場合は、上記のようにカラム名の前にテーブルの別名を使用する必要があります。

hasOne アソシエーションの配列で可能なキーは以下の通りです。

className: 当該のモデルに関連付けられるモデルのクラス名。 'User hasOne Address' の関係を定義したい場合、 className キーは 'Addresses' になるはずです。

foreignKey: 相手側のテーブル上の外部キーの名前。これは複数の hasOne の関係を定義する必要がある場合に特に便利です。このキーの既定値は当該のモデルの名前をアンダースコアーで区切り、単数形にして '_id' を末尾に付けたものです。上の例では 'user_id' が既定になります。

bindingKey: foreignKey での紐付けに使用される、当該のテーブルのカラム名。指定されなかった場合、主キー（例えば Users テーブルの id カラム）が使われます。

conditions: ['Addresses.primary' => true] のような find() 互換の条件の配列です。

joinType: SQL クエリーで使われる結合の種別で、既定は LEFT です。もし hasOne アソシエーションが常にあれば INNER を使うことができます。

dependent: dependent キーが true に設定され、そしてエンティティーが削除された場合、関連付けられたモデルのレコードも削除されます。この例では User を削除した時に関連付けられた Address も削除されるようにしたければ true にします。

cascadeCallbacks: これと dependent が true の時には、カスケード削除はコールバックが正しく呼ばれるように、エンティティーを読み出して削除します。 false の時には、関連付けられたデータを削除するために deleteAll() が使われコールバックは呼ばれません。

propertyName: 関連付けられたテーブルからソースのテーブルの結果にデータを埋める際のプロパティー名。既定は、アソシエーションの名前をアンダースコアーで区切り、単数形にしたもので、よって例では address です。

strategy: クエリーで使うためのストラテジーを定義します。既定は 'join' です。他の有効な値は 'select' で、これは代わりに別のクエリーを使用します。

finder: 関連付けられたレコードを読み込む時に使われるファインダーメソッドです。

このアソシエーションが定義された後は、 Users テーブルの検索操作で、もし Address のレコードが存在すればそれを含むことができます。

// コントローラーまたはテーブルのメソッドの中で
$query = $users->find('all')->contain(['Addresses'])->all();
foreach ($query as $user) {
    echo $user->address->street;
}

上記は次のような SQL を実行します。

SELECT * FROM users INNER JOIN addresses ON addresses.user_id = users.id;

belongsTo アソシエーション

ここまでで、 User テーブルから Address データにアクセスできるようになりました。次は Address テーブルから関連する User データにアクセスできるように、 belongsTo アソシエーションを定義しましょう。belongsTo アソシエーションは hasOne や hasMany の自然な補完です。つまり、他の方向からの関連データを見ることができます。

データベースのテーブルに belongsTo の関係のためにキーを作る時には、次の規約に従ってください。

belongsTo: 当該の モデルが外部キーを持ちます。

関係

スキーマ

Addresses belongsTo Users

addresses.user_id

Mentors belongsTo Doctors

mentors.doctor_id

Tip

あるテーブルが外部キーを持っている場合、それは他のテーブルに属しています。

次のようにして Addresses テーブルに belongsTo アソシエーションを定義することができます。

class AddressesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->belongsTo('Users');
    }
}

セッターを使って、より詳細な関係を定義することができます。

class AddressesTable extends Table
{
    public function initialize(array $config): void
    {
        // バージョン 3.4 より前は、 foreignKey() と joinType() を使用してください
        $this->belongsTo('Users')
            ->setForeignKey('user_id')
            ->setJoinType('INNER');
    }
}

belongsTo アソシエーションの配列で可能なキーは以下の通りです。

className: 当該のモデルに関連付けられるモデルのクラス名。 'Profile belongsTo User' の関係を定義したい場合、 className キーは 'Users' になるはずです。

foreignKey: 当該のテーブル上の外部キーの名前。これは同一のモデルに対して複数の belongsTo 関係を定義する必要がある場合に特に便利です。このキーの既定値は相手側のモデルの名前をアンダースコアーで区切り、単数形にして _id を末尾に付けたものです。

bindingKey: foreignKey での紐付けで使用される、相手側のテーブルのカラム名。指定されなかった場合、主キー（例えば Users テーブルの id カラム）が使われます。

conditions: ['Users.active' => true] のような find() 互換の条件の配列、または SQL 文字列です。

joinType: SQL クエリーで使われる結合の種別で、既定は LEFT であり、これはすべての状況で要求を満たすとは限らず、メインおよび関連付けられたモデル一式を返すかあるいは何も返さないようにしたい場合には INNER が便利です。

propertyName: 関連付けられたテーブルからソースのテーブルの結果にデータを埋める際のプロパティー名。既定は、アソシエーションの名前をアンダースコアーで区切り、単数形にしたもので、よって例では user です。

strategy: クエリーで使うためのストラテジーを定義します。既定は 'join' です。他の有効な値は 'select' で、これは代わりに別のクエリーを使用します。

finder: 関連付けられたレコードを読み込む時に使われるファインダーメソッドです。

このアソシエーションが定義された後は、 Addresses テーブルの検索操作で、もし User のレコードが存在すればそれを含むことができます。

// コントローラーまたはテーブルのメソッドの中で
$query = $addresses->find('all')->contain(['Users'])->all();
foreach ($query as $address) {
    echo $address->user->username;
}

上記は次のような SQL を実行します。

SELECT * FROM addresses LEFT JOIN users ON addresses.user_id = users.id;

hasMany アソシエーション

hasMany アソシエーションの一例は "Article hasMany Comments" （記事が多くのコメントを持つ）です。このアソシエーションを定義することで、記事が読み出される時にそのコメントと一緒に記事を取得することができるようになります。

hasMany の関係のためにテーブルを作成する場合には、この規約に従ってください。

hasMany: 相手側の モデルが外部キーを持つ。

関係

スキーマ

Article hasMany Comment

Comment.article_id

Product hasMany Option

Option.product_id

Doctor hasMany Patient

Patient.doctor_id

Articles モデルの中で、 hasMany アソシエーションを次のように定義することができます。

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasMany('Comments');
    }
}

セッターを使って、より詳細な関係を定義することができます。

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasMany('Comments')
            ->setForeignKey('article_id')
            ->setDependent(true);
    }
}

時にはアソシエーションで複合キーを設定したいかもしれません。

// ArticlesTable::initialize() の呼び出しの中で
$this->hasMany('Comments')
    ->setForeignKey([
        'article_id',
        'article_hash'
    ]);

上記の例の通りに、必要な複合キーを含む配列を setForeignKey() に渡しました。既定では、 bindingKey は id および hash としてそれぞれ自動的に定義されますが、既定とは異なる紐付けフィールドを指定する必要があれば、次のようにして setBindingKeys() を手動で設定することができます。

// ArticlesTable::initialize() の呼び出しの中で
$this->hasMany('Comments')
    ->setForeignKey([
        'article_id',
        'article_hash'
    ])
    ->setBindingKey([
        'whatever_id',
        'whatever_hash'
    ]);

foreignKey の値が reviews テーブルを参照し bindingKey の値が articles テーブルを参照することに注意することは大切です。

hasMany アソシエーションの配列で可能なキーは以下の通りです。

className: 当該のモデルに関連付けられるモデルのクラス名。 'User hasMany Comment' の関係を定義したい場合、 className キーは 'Comments' になるはずです。

foreignKey: 相手側のテーブル上の外部キーの名前。これは複数の hasMany の関係を定義する必要がある場合に特に便利です。このキーの既定値は当該のモデルの名前をアンダースコアーで区切り、単数形にして '_id' を末尾に付けたものです。

bindingKey: foreignKey での紐付けに使用される、当該のテーブルのカラム名。指定されなかった場合、主キー（例えば Articles テーブルの id カラム）が使われます。

conditions: ['Comments.visible' => true] のような find() 互換の条件の配列、または SQL 文字列です。

sort: ['Comments.created' => 'ASC'] のような find() 互換の order 句の配列、または SQL 文字列です。

dependent: dependent が true に設定されている場合、再帰的なモデル削除が可能です。この例では Article レコードを削除した時に Comment レコードが削除されます。

cascadeCallbacks: これと dependent が true の時には、カスケード削除はコールバックが正しく呼ばれるように、エンティティーを読み出して削除します。 false の時には、関連付けられたデータを削除するために deleteAll() が使われコールバックは呼ばれません。

propertyName: 関連付けられたテーブルからソースのテーブルの結果にデータを埋める際のプロパティー名。既定は、アソシエーションの名前をアンダースコアーで区切り、複数形にしたもので、よって例では comments です。

strategy: クエリーで使うためのストラテジーを定義します。既定は 'select' です。他の有効な値は 'subquery' で、これは IN のリストを等価のサブクエリーに置き換えます。

saveStrategy: 'append' または 'replace' のいずれかです。デフォルトは 'append' です。 'append' の場合、当該のレコードがデータベース中のレコードに追加されます。 'replace' の場合、関連付けられたレコードで当該のセットにないものは削除されます。もし外部キーが null になれるカラムの場合、または dependent が真の場合、レコードは親を持たなくなります。

finder: 関連付けられたレコードを読み込む時に使われるファインダーメソッドです。

このアソシエーションが定義された後は、 Articles テーブルの検索操作で、もし Comment のレコードが存在すればそれを含むことができます。

// コントローラーまたはテーブルのメソッドの中で
$query = $articles->find('all')->contain(['Comments']);
foreach ($query as $article) {
    echo $article->comments[0]->text;
}

上記は次のような SQL を実行します。

SELECT * FROM articles;
SELECT * FROM comments WHERE article_id IN (1, 2, 3, 4, 5);

サブクエリーのストラテジーが使われた時は、次のような SQL が生成されます。

SELECT * FROM articles;
SELECT * FROM comments WHERE article_id IN (SELECT id FROM articles);

hasMany アソシエーションにおいて件数をキャッシュしたいかもしれません。これは関連付けられたレコードの数をしばしば表示する必要があるものの、それらを数えるためだけに全レコードを読み出したくはない時に便利です。例えば、何らかの記事についてのコメント数は、記事の一覧をより効率に生成できるようにするためにしばしばキャッシュされます。関連付けられたレコードの数をキャッシュするには CounterCacheBehavior を使用することができます。

データベースには、アソシエーションのプロパティー名と一致するカラムを持たせないようにすべきです。もし例えば、アソシエーションのプロパティー名と衝突する件数フィールドを持っている場合、アソシエーションのプロパティー、またはカラム名のいずれかの名前を変更しなければなりません。

belongsToMany アソシエーション

注釈

3.0 以降では、 hasAndBelongsToMany / HABTM は、 belongsToMany / BTM に名前が変更されました。

belongsToMany アソシエーションの一例は "Article belongsToMany Tags" (記事が多くのタグに属する) で、一つの記事のタグがほかの記事によって共有される場合です。 belongsToMany はしばしば "has and belongs to many" （多くを持ち、多くに属する）とも呼ばれ、これは多対多アソシエーションの典型です。

hasMany と belongsToMany の主な違いは belonsToMany アソシエーションでのモデル間の紐付けが排他的ではないことです。例えば、 Articles テーブルに Tags テーブルを結合するとします。 '笑える' を Article の Tag にすることは、そのタグを使い果たしません。次に書く記事にもそれを使うことができます。

belongsToMany アソシエーションでは三つのデータベーステーブルが必要です。上記の例では、 articles 、 tags および articles_tags が必要です。 articles_tags テーブルは tags と articles を紐付けるデータを一緒に持っています。結合テーブルは、関連する二つのテーブルの名前に基づいており、規約によってアンダースコアーで区切られています。その最も単純な形式では、このテーブルは article_id と tag_id で構成されます。

belongsToMany は両方の モデル の名前を持つ別のテーブルが必要です。

関係

結合テーブルのフィールド

Article belongsToMany Tag

articles_tags.id, articles_tags.tag_id, articles_tags.article_id

Patient belongsToMany Doctor

doctors_patients.id, doctors_patients.doctor_id, doctors_patients.patient_id.

次のようにして 両方のモデルの中で belongsTo アソシエーションを定義することができます。

// src/Model/Table/ArticlesTable.php の中で
class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->belongsToMany('Tags');
    }
}

// src/Model/Table/TagsTable.php の中で
class TagsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->belongsToMany('Articles');
    }
}

設定を使って、より詳細な関係を定義することができます。

// src/Model/Table/TagsTable.php の中で
class TagsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->belongsToMany('Articles', [
            'joinTable' => 'articles_tags',
        ]);
    }
}

belongsToMany アソシエーションの配列で可能なキーは以下の通りです。

className: 当該のモデルに関連付けられるモデルのクラス名。 'Article belongsToMany Tag' の関係を定義したい場合、 className キーは 'Tags' になるはずです。

joinTable: このアソシエーションで使われる結合テーブルの名前（当該のテーブルが belongsToMany 結合テーブルの命名規約に準拠していない場合）。既定では、結合テーブル用の Table インスタンスを読み出すためにこの名前が使われます。

foreignKey: 結合テーブル上の当該のモデルを参照する外部キーの名前、または複合外部キーの場合はリスト。これは複数の belongsToMany の関係を定義する必要がある場合に特に便利です。このキーの既定値は当該のモデルの名前をアンダースコアーで区切り、単数形にして '_id' を末尾に付けたものです。

bindingKey: foreignKey での紐付けに使用される、当該のテーブルのカラム名。既定ではその主キーです。

targetForeignKey: 結合モデル上の対象モデルを参照する外部キーの名前、または複合外部キーの場合はリスト。このキーの既定値は当該のモデルの名前をアンダースコアーで区切り、単数形にして '_id' を末尾に付けたものです。

conditions: find() 互換の条件の配列、または SQL 文字列です。関連付けられたテーブル上に条件を持つには、 'through' モデルを使用し、それに必要な belongsTo アソシエーションを定義してください。

sort: find() 互換の order 句の配列。

dependent: dependent キーが false に設定され、そしてエンティティーが削除された場合、結合テーブルのデータは削除されません。

through: 結合テーブルで使用する Table インスタンスのエイリアス、またはインスタンス自体のいずれかを指定できます。これにより、結合テーブルのキーのカスタマイズが可能になり、そして結合テーブルの動作をカスタマイズすることができます。

cascadeCallbacks: これが true の時には、カスケード削除は結合テーブル上のコールバックが正しく呼ばれるように、エンティティーを読み出して削除します。 false の時には、関連付けられたデータを削除するために deleteAll() が使われコールバックは呼ばれません。これはオーバーヘッドの削減を助けるために既定では false になります。

propertyName: 関連付けられたテーブルからソースのテーブルの結果にデータを埋める際のプロパティー名。既定は、アソシエーションの名前をアンダースコアーで区切り、複数形にしたもので、よって例では tags です。

strategy: クエリーで使うためのストラテジーを定義します。既定は 'select' です。他の有効な値は 'subquery' で、これは IN のリストを等価のサブクエリーに置き換えます。

saveStrategy: 'append' または 'replace' のいずれかです。 既定は 'replace' です。関連するエンティティーの保存に使用するモードを示します。前者はリレーションの両側の間に新しい紐付けを作成するだけで、後者は保存する時に渡されたエンティティーの間に紐付けを作成するために消去と置換を行います。

finder: 関連付けられたレコードを読み込む時に使われるファインダーメソッドです。

このアソシエーションが定義された後は、 Articles テーブルの検索操作で、もし Tag のレコードが存在すればそれを含むことができます。

// コントローラーまたはテーブルのメソッドの中で
$query = $articles->find('all')->contain(['Tags'])->all();
foreach ($query as $article) {
    echo $article->tags[0]->text;
}

上記は次のような SQL を実行します。

SELECT * FROM articles;
SELECT * FROM tags
INNER JOIN articles_tags ON (
  tags.id = article_tags.tag_id
  AND article_id IN (1, 2, 3, 4, 5)
);

サブクエリーのストラテジーが使われた時は、次のような SQL が生成されます。

SELECT * FROM articles;
SELECT * FROM tags
INNER JOIN articles_tags ON (
  tags.id = article_tags.tag_id
  AND article_id IN (SELECT id FROM articles)
);

'through' オプションの使用

もし結合テーブルに追加の情報を持たせようとしている場合、あるいはもし規約から外れる結合カラムを使用する必要がある場合、 through オプションを定義する必要があります。 through オプションは belongsToMany アソシエーションがどのように作られるかを完全に制御できるようにします。

時には多対多アソシエーションで追加のデータを保存するのが望ましいことがあります。以下を考えてみてください。

Student BelongsToMany Course
Course BelongsToMany Student

Student は多くの Courses を取っていて、 Course は多くの Student に取られています。これは単純な多対多のアソシエーションです。次のようなテーブルがあれば事足ります。

id | student_id | course_id

では、生徒が授業に出席した日数や成績を保存したい場合はどうでしょう？欲しいテーブルは次のようになります。

id | student_id | course_id | days_attended | grade

この要件を実装する方法は モデルの結合 、もしくは hasMany through アソシエーションを使うことです。これは、このアソシエーション自身がモデルになります。つまり、新しい CoursesMemberships モデルを作ればよいのです。以下のモデルを見てください。

class StudentsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->belongsToMany('Courses', [
            'through' => 'CoursesMemberships',
        ]);
    }
}

class CoursesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->belongsToMany('Students', [
            'through' => 'CoursesMemberships',
        ]);
    }
}

class CoursesMembershipsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->belongsTo('Students');
        $this->belongsTo('Courses');
    }
}

CoursesMemberships 結合テーブルは、追加のメタ情報に加えて、与えられた Student が Course に参加しているかどうかを一意に識別します。

既定のアソシエーションの条件

finder オプションは、関連付けられたレコードのデータを読み出すためにカスタムファインダー を使えるようにします。これはクエリーをよりカプセル化し、コードをより DRY にします。 join (belongsTo/hasOne) を使って読み出されるアソシエーションのデータを読み出すためにファインダーを使う場合、いくつかの制限があります。クエリーの次の部分だけがルートクエリーに適用されます。

WHERE 条件

追加の join

contain されたアソシエーション

他のクエリーの部分、例えば select されるカラム、 order 、 group by 、 having そして、その他のサブステートメントについては、ルートクエリーには適用されません。 join によって 読み出されない アソシエーション (hasMany/belongsToMany) には、上記の制約は持たず、結果のフォーマッターや map/reduce 機能を使うこともできます。

アソシエーションの読み出し

アソシエーションを定義したら、結果を取得する時に アソシエーションのイーガーロード ができるようになります。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ビヘイビアー

ビヘイビアーは、モデル層ロジックの水平再利用を整理して有効にする方法です。概念的にはトレイトに似ています。ただし、ビヘイビアーは別個のクラスとして実装されます。これにより、モデルが発行するライフサイクルコールバックにフックして、トレイトのような機能を提供することができます。

ビヘイビアーは、多くのモデルで共通の振る舞いをまとめる便利な方法を提供します。たとえば、CakePHP には TimestampBehavior が含まれています。多くのモデルはタイムスタンプフィールドを必要とし、これらのフィールドを管理するロジックはいずれのモデルにも固有ではありません。 ビヘイビアーの利用が最適なのはこの種のシナリオです。

ビヘイビアーの利用

ビヘイビアーは、テーブルクラスにまたがって関連するロジックの再利用可能な部品を作成する簡単な方法を提供します。なぜビヘイビアーが通常のクラスで、トレイトではないのか不思議に思うかもしれません。第一の理由は、ビヘイビアーはイベントリスナーだからです。トレイトは再利用可能なロジックの部品になりえますが、イベントをバインドするのは厄介です。

ビヘイビアーをテーブルに追加するために addBehavior() メソッドが使えます。一般的に、これを initialize() でやるのがもっともよいです。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config)
    {
        $this->addBehavior('Timestamp');
    }
}

アソシエーションには プラグイン記法 と追加の設定オプションが使えます。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Timestamp', [
            'events' => [
                'Model.beforeSave' => [
                    'created_at' => 'new',
                    'modified_at' => 'always'
                ]
            ]
        ]);
    }
}

コアビヘイビアー

CounterCache

Timestamp

Translate

Tree

ビヘイビアーの生成

次の例では、非常に単純な SluggableBehavior を作成します。 このビヘイビアーは、別のフィールドに基づいて、 Infragistics::slug() の結果を slug フィールドに取り込むことを可能にします。

ビヘイビアーを作成する前に、ビヘイビアーの規約を理解する必要があります。

ビヘイビアーファイルは src/Model/Behavior 、または MyPlugin\Model\Behavior に配置する。

ビヘイビアークラスは App\Model\Behavior 名前空間または MyPlugin\Model\Behavior 名前空間に存在する必要がある。

ビヘイビアークラスの名前は Behavior で終了する。

ビヘイビアーは Cake\ORM\Behavior を継承する。

sluggable behavior を作成してみます。 src/Model/Behavior/SluggableBehavior.php に以下を挿入します。

namespace App\Model\Behavior;

use Cake\ORM\Behavior;

class SluggableBehavior extends Behavior
{
}

テーブルと同様に、ビヘイビアーには、必要に応じてビヘイビアーの初期化コードを入れることができる initialize() フックもあります。

public function initialize(array $config): void
{
    // 何らかの初期化処理
}

このビヘイビアーをテーブルクラスの1つに追加できるようになりました。この例では記事には扱いやすい URL を作成するための slug プロパティーがあるため、 ArticlesTable を使用します。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Sluggable');
    }
}

この新しいビヘイビアーは、今は何もしません。次に、ミックスインメソッドとイベントリスナーを追加して、エンティティーを保存するときにフィールドを自動的に slug させることができるようにします。

ミックスインメソッドの定義

ビヘイビアーに定義されたパブリックメソッドは、それが追加されたテーブルオブジェクトに「ミックスイン」メソッドとして追加されます。同じメソッドを提供する2つのビヘイビアーを追加すると、例外が発生します。ビヘイビアーがテーブルクラスと同じメソッドを提供する場合、ビヘイビアーメソッドはテーブルから呼び出すことはできません。ビヘイビアーミックスインメソッドは、テーブルに提供されるものとまったく同じ引数を受け取ります。たとえば、SluggableBehavior が次のメソッドを定義しているとします。

public function slug($value)
{
    return Text::slug($value, $this->_config['replacement']);
}

これは以下を使用して呼び出すことができます。

$slug = $articles->slug('My article name');

公開されたミックスインメソッドの制限または名前の変更

ビヘイビアーを作成するときに、ミックスインメソッドとしてパブリックメソッドを公開したくない場合があります。このような場合、 implementMethods 設定キーを使用してミックスインメソッドの名前を変更したり除外したりすることができます。たとえば、slug() メソッドに接頭辞を付ける場合は、次のようにします。

protected $_defaultConfig = [
    'implementedMethods' => [
        'superSlug' => 'slug',
    ]
];

この設定を適用すると slug() は呼び出し可能になりませんが、 superSlug() のミックスインメソッドがテーブルに追加されます。特に、ビヘイビアーが他のパブリックメソッドを実装していた場合、上記の設定ではミックスインメソッドとしては 利用できません 。

公開されたメソッドは設定によって決まるので、テーブルにビヘイビアーを追加するときに、ミックスインメソッドの名前を変更/削除することもできます。例えば以下のようにします。

// テーブルのinitialize()メソッド内で
$this->addBehavior('Sluggable', [
    'implementedMethods' => [
        'superSlug' => 'slug',
    ]
]);

イベントリスナーの定義

私たちのビヘイビアーには、フィールドを slug するためのミックスインメソッドが用意されているので、エンティティーを保存するときにフィールドを自動的にスラッグするコールバックリスナを実装できます。また、slug メソッドを変更して、単純な値ではなくエンティティーを受け入れるようにします。ビヘイビアーは次のようになります。

namespace App\Model\Behavior;

use ArrayObject;
use Cake\Datasource\EntityInterface;
use Cake\Event\EventInterface;
use Cake\ORM\Behavior;
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\Utility\Text;

class SluggableBehavior extends Behavior
{
    protected $_defaultConfig = [
        'field' => 'title',
        'slug' => 'slug',
        'replacement' => '-',
    ];

    public function slug(EntityInterface $entity)
    {
        $config = $this->getConfig();
        $value = $entity->get($config['field']);
        $entity->set($config['slug'], Text::slug($value, $config['replacement']));
    }

    public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
    {
        $this->slug($entity);
    }

}

上記のコードは、ビヘイビアーの興味深い機能をいくつか示しています。

ビヘイビアーでは、コールバックのライフサイクル に従うメソッドを定義することで、コールバックメソッドを定義できます。

ビヘイビアーでは、デフォルトのコンフィグレーションプロパティーを定義できます。ビヘイビアーがテーブルに追加されている場合、このプロパティーはオーバーライドとマージされます。

保存が続行しないようにするには、コールバック内のイベント伝播を停止するだけです。

public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
    if (...) {
        $event->stopPropagation();
        $event->setResult(false);
        return;
    }
    $this->slug($entity);
}

ファインダーの定義

slug 値を持つ記事を保存できるようになったので、slug で記事を取得できるようにファインダーメソッドを実装する必要があります。ビヘイビアーファインダーメソッドは、 カスタム Finder メソッド と同じ規約を使用します。 find('slug') メソッドは以下のようになります。

public function findSlug(Query $query, array $options)
{
    return $query->where(['slug' => $options['slug']]);
}

ビヘイビアーに上記のメソッドがあれば、呼び出しが可能です。

$article = $articles->find('slug', ['slug' => $value])->first();

公開されたファインダーメソッドの制限または名前の変更

ビヘイビアーを作成するときに、ファインダーメソッドを公開したくない場合や、メソッドの重複を避けるためにファインダーの名前を変更する必要がある場合があります。このような場合は、 implementedFinders 設定キーを使用してファインダーメソッドの名前を変更したり除外したりできます。たとえば、 find(slug) メソッドの名前を変更したい場合は、次のようにします。

protected $_defaultConfig = [
    'implementedFinders' => [
        'slugged' => 'findSlug',
    ]
];

この設定を適用すると、 find('slug') がエラーを引き起こします。しかし、 find('slugged') が利用可能になります。特に、ビヘイビアーが他のファインダーメソッドを実装していた場合、それらは設定に含まれていないため 利用できません 。

公開されたメソッドは設定によって決まるので、ビヘイビアーをテーブルに追加するときに、ファインダーメソッドの名前を変更/削除することもできます。例えば以下のようにします。

// テーブルの initialize() メソッド内で
$this->addBehavior('Sluggable', [
    'implementedFinders' => [
        'slugged' => 'findSlug',
    ]
]);

リクエストデータをエンティティープロパティーに変換する

ビヘイビアーは、 Cake\ORM\PropertyMarshalInterface を実装することによって、カスタムフィールドがどのように変換されるかについてのロジックを定義できます。このインターフェイスでは、1つのメソッドを実装する必要があります。

public function buildMarshalMap($marshaller, $map, $options)
{
    return [
        'custom_behavior_field' => function ($value, $entity) {
            // 必要であれば値を変換
            return $value . '123';
        }
    ];
}

TranslateBehavior には、参照される可能性のあるこのインターフェースの重要な実装があります。

ロードされたビヘイビアーの削除

テーブルからビヘイビアーを削除するには、 removeBehavior() メソッドを呼び出します。

// 読み込まれたビヘイビアーを削除
$this->removeBehavior('Sluggable');

ロードされたビヘイビアーへのアクセス

ビヘイビアーをテーブルインスタンスに追加したら、読み込まれたビヘイビアーの情報を確認(introspect)したり、 BehaviorRegistry を使用して特定のビヘイビアーにアクセスしたりできます。

// どのビヘイビアーが読み込まれたかを調べる
$table->behaviors()->loaded();

// 特定のビヘイビアーが読み込まれたかどうかを調べる
// プラグインプレフィックスを含めないことに注意
$table->behaviors()->has('CounterCache');

// 読み込まれたビヘイビアーを取得する
// プラグインプレフィックスを含めないことに注意
$table->behaviors()->get('CounterCache');

ロードされたビヘイビアーの再構成

既にロードされているビヘイビアーの設定を変更するには、 BehaviorRegistry::get コマンドを InstanceConfigTrait トレイトによって提供される config コマンドと組み合わせることができます。

たとえば、親( AppTable など)のクラスに Timestamp ビヘイビアーがロードされている場合は、ビヘイビアーの設定を追加、変更、または削除するために、次の操作を行うことができます。この場合、Timestamp が応答するイベントを追加します。

namespace App\Model\Table;

use App\Model\Table\AppTable; // similar to AppController

class UsersTable extends AppTable
{
    public function initialize(array $options): void
    {
        parent::initialize($options);

        // 例：親クラスが $this->addBehavior('Timestamp'); を呼び出していて、さらにイベントを追加したい場合
        if ($this->behaviors()->has('Timestamp')) {
            $this->behaviors()->get('Timestamp')->setConfig([
                'events' => [
                    'Users.login' => [
                        'last_login' => 'always'
                    ],
                ],
            ]);
        }
    }
}

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CounterCache

class Cake\ORM\Behavior\CounterCacheBehavior

多くの場合、ウェブアプリケーションは関連するオブジェクトの数を表示する必要があります。たとえば、記事のリストを表示するときに、記事のコメントの数を表示することができます。または、ユーザーを表示するときに、彼女が持っている友人/フォロワーの数を表示することもできます。 CounterCache ビヘイビアーは、これらの状況を想定しています。 CounterCache は、呼び出されたときにオプションで割り当てられた関連モデルのフィールドを更新します。フィールドはデータベースに存在し、INT 型でなければなりません。

基本的な使用方法

他のビヘイビアーと同様に CounterCache ビヘイビアーを有効にしますが、いくつかのリレーションとそれらのそれぞれに格納されるフィールド数を設定するまでは何も行いません。以下の例を使用して、各記事のコメント数を次のようにキャッシュすることができます。

class CommentsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('CounterCache', [
            'Articles' => ['comment_count']
        ]);
    }
}

CounterCache の設定は、リレーション名のマップとそのリレーションに対する具体的な設定でなければなりません。

カウンターの値は、エンティティーが保存または削除されるたびに更新されます。 updateAll() または deleteAll() を使用するか、作成した SQL を実行すると、カウンターは更新 されません。

高度な使用方法

キャッシュされたカウンターを関連するすべてのレコードより少なく保つ必要がある場合は、カウンター値を生成するために追加の条件またはファインダーメソッドを指定できます。

// 特定の find メソッドを使用する。
// find(published) の場合
$this->addBehavior('CounterCache', [
    'Articles' => [
        'comment_count' => [
            'finder' => 'published'
        ]
    ]
]);

カスタムファインダーメソッドがない場合は、代わりにレコードを検索するための条件の配列を指定できます。

$this->addBehavior('CounterCache', [
    'Articles' => [
        'comment_count' => [
            'conditions' => ['Comments.spam' => false]
        ]
    ]
]);

CounterCache で複数のフィールドを更新する場合（条件付きカウントと基本カウントの両方を表示するなど）、これらのフィールドを配列に追加できます。

$this->addBehavior('CounterCache', [
    'Articles' => ['comment_count',
        'published_comment_count' => [
            'finder' => 'published'
        ]
    ]
]);

独自の CounterCache フィールド値を計算する場合は、 ignoreDirty オプションを true に設定します。これにより、前に dirty を設定した場合、フィールドが再計算されなくなります。

$this->addBehavior('CounterCache', [
    'Articles' => [
        'comment_count' => [
            'ignoreDirty' => true
        ]
    ]
]);

最後に、カスタムファインダーと条件が適切でない場合は、コールバックメソッドを提供することができます。この呼び出し可能オブジェクトは、格納するカウント値を返さなければなりません。

$this->addBehavior('CounterCache', [
    'Articles' => [
        'rating_avg' => function ($event, $entity, $table, $original) {
            return 4.5;
        }
    ]
]);

あなたの関数は、カウンター列の更新をスキップするために false を返したり、カウント値を生成した Query オブジェクトを返すことができます。 Query オブジェクトを返すと、そのクエリーは update 文のサブクエリーとして使われます。 $table パラメーターは、便宜上、ビヘイビアーを保持している (ターゲット関係ではない) テーブルオブジェクトを参照します。コールバックは、 $original に false が設定されて少なくとも1回呼び出されます。 entity-update がアソシエーションを変更した場合、コールバックは true で 2回 呼び出され、戻り値は 以前 に関連付けられたアイテムのカウンターを更新します。

注釈

CounterCache ビヘイビアーは、 belongsTo アソシエーションに対してのみ機能します。たとえば、 "Comments belongsTo Articles" の場合、Article テーブルの comment_count を生成するために、 CommentsCache ビヘイビアーを CommentsTable に追加する必要があります。

これを belongsToMany アソシエーションに対して機能させることは可能ですが、アソシエーションオプションで設定されたカスタム through テーブルで CounterCache ビヘイビアーを有効にして cascadeCallbacks 設定オプションを true にする必要があります。カスタム JOIN テーブルを設定する方法は 'through' オプションの使用 を参照してください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Timestamp

class Cake\ORM\Behavior\TimestampBehavior

Timestamp ビヘイビアーは、モデルのイベントのたびにテーブルオブジェクトのタイムスタンプを更新します。これは、主に created や modified フィールドにデータを投入するために使用されます。けれども、設定を追加すると、任意の timestamp と datatime カラムを任意のイベントで更新することができます。

一般的な使い方

あなたは他のビヘイビアーと同様に、timestamp ビヘイビアーを以下の様に有効にできます。

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Timestamp');
    }
}

デフォルトの設定は以下のようになっています:

新しく Entity を保存するとき、 created と modified に現在の日時を設定します。

Entity を更新したとき、 modified に現在の日時を設定します。

使い方と設定方法

もし別の名前でフィールドを更新する必要があるときや、カスタムイベントでさらなるタイムスタンプフィールドを更新したい場合、次のような追加設定を使うことができます。

class OrdersTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Timestamp', [
            'events' => [
                'Model.beforeSave' => [
                    'created_at' => 'new',
                    'updated_at' => 'always',
                ],
                'Orders.completed' => [
                    'completed_at' => 'always'
                ]
            ]
        ]);
    }
}

上記の例では、標準の Model.beforeSave イベントに加えて、注文完了時に completed_at カラムを更新しています。

Entity での Timestamp 更新

しばしば、他のプロパティーを変更せずに、エンティティーのタイムスタンプのみ更新したいこともあるでしょう。これは、レコードに「touch する」と呼ばれます。まさにこれをするために CakePHP では touch() メソッドを使うことができます。

// Model.beforeSave イベントに基づいて touch します
$articles->touch($article);

// 指定したイベントに基づいて touch します
$orders->touch($order, 'Orders.completed');

Entity を保存後、フィールドが更新されます。

レコードの touch は、子リソースが作成や更新されたときに親リソースを変更するためのシグナルがほしい際に便利です。例えば、新しくコメントが追加されたときに記事を更新するといったことです。

編集のタイムスタンプ無しで更新の保存

エンティティーを保存する際の updated タイムスタンプカラムの自動更新を無効化するには、その属性を 'dirty' としてマークします。

// modified カラムを dirty としてマークして、更新時に現在の値がセットされるようにします。
$order->setDirty('modified', true);

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Translate

class Cake\ORM\Behavior\TranslateBehavior

Translate ビヘイビアーは、エンティティーの複数の言語で翻訳されたコピーを作成し、取得することができます。これは、独立した i18n テーブルを使って行います。このテーブルと結び付けられている任意の Table オブジェクトの各フィールドの翻訳を格納します。

警告

TranslateBehavior は、現時点では複合主キーをサポートしていません。

クイックツアー

データベース内に i18n テーブルを作成した後、翻訳できるようにしたい Table オブジェクトにビヘイビアーを追加してください。

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Translate', [
            'defaultLocale' => 'en_GB',
        ]);
    }
}

次に、アプリケーションの言語を変更して、エンティティーを取得するために使用する言語を選択します。これはすべての翻訳に影響します。

// コントロールの中。例えば、スペイン語にロケールを変更。
I18n::setLocale('es');
$this->loadModel('Articles');

次に、既存のエンティティーを取得します。

$article = $this->Articles->get(12);
echo $article->title; // 'A title' と出力、まだ未翻訳。

次に、エンティティーを翻訳します。

$article->title = 'Un Artículo';
$this->Articles->save($article);

再びエンティティーを取得してみます。

$article = $this->Articles->get(12);
echo $article->title; // 'Un Artículo' と出力、超簡単！

複数の翻訳での動作は、Entity クラスに特別なトレイトを使用して行うことができます。

use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;

class Article extends Entity
{
    use TranslateTrait;
}

これで、単一のエンティティーのすべての翻訳を見つけることができます。

$article = $this->Articles->find('translations')->first();
echo $article->translation('es')->title; // 'Un Artículo'

echo $article->translation('en')->title; // 'An Article';

同様に複数の翻訳を一度に保存することも簡単です。

$article->translation('es')->title = 'Otro Título';
$article->translation('fr')->title = 'Un autre Titre';
$this->Articles->save($article);

動作の仕方や必要に応じたビヘイビアーの調整方法について詳しく知りたい場合は、この章の残りの部分を読んでください。

i18n データベーステーブルの初期化

ビヘイビアーを使用するためには、正しいスキーマで i18n テーブルを作成する必要があります。現在、 i18n テーブルを読み込む唯一の方法は、手動でデータベースに次の SQL スクリプトを実行することです。

CREATE TABLE i18n (
    id int NOT NULL auto_increment,
    locale varchar(6) NOT NULL,
    model varchar(255) NOT NULL,
    foreign_key int(10) NOT NULL,
    field varchar(255) NOT NULL,
    content text,
    PRIMARY KEY (id),
    UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field),
    INDEX I18N_FIELD(model, foreign_key, field)
);

スキーマは、 /config/schema/i18n.sql の中で sql ファイルとして提供されています。

言語省略形の注意: Translate ビヘイビアーは言語識別子に制限はありませんが、可能な値は、 locale カラムの型とサイズによってのみ制限されています。 es-419 (ラテンアメリカのスペイン語、言語省略形と地域コード UN M.49 [https://en.wikipedia.org/wiki/UN_M.49]) のように省略形を使用したい場合に備えて、 locale は varchar(6) として定義されます。

Tip

国際化と地域化で必要なものと同じ言語省略形を使用するのが賢明です。そうすると、一貫性があり、言語を切り替えることは、 Translate ビヘイビアー と 国際化と地域化 の両方が等しく動作します。

したがって、 en 、 fr 、 de のような 2 文字の ISO コードまたは、言語とそれが話される国の両方が含まれている fr_FR 、 es_AR 、 da_DK のような完全なロケール名のいずれかを使用することをお勧めします。

Table への Translate ビヘイビアーの追加

ビヘイビアーの追加は、Table クラスの initialize() メソッドで行うことができます。

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Translate', ['fields' => ['title', 'body']]);
    }
}

最初に注意しなければならないのは、設定配列に fields キーを渡す必要があるということです。このフィールドのリストは、どのカラムが翻訳を格納できるかをビヘイビアーに伝えるために必要です。

別の翻訳テーブルの利用

特定のリポジトリーを翻訳するために i18n 以外のテーブルを使用する場合は、ビヘイビアーの設定で指定することができます。これは、翻訳するテーブルが複数あり、それぞれのテーブルごとに格納されているデータをより明確に分離したい場合によく使用されます。

class ArticlesTable extends Table
{

    public function initialize(array $config)
    {
        $this->addBehavior('Translate', [
            'fields' => ['title', 'body'],
            'translationTable' => 'ArticlesI18n'
        ]);
    }
}

使用する独自のテーブルに、 field 、 foreign_key 、 locale 、 model というカラムがあることを確認する必要があります。

翻訳された内容の読み込み

上記に示したように、読み込まれたエンティティーの有効な翻訳を選ぶために setLocale() メソッドを使用することができます。

// コントローラーの先頭で I18n コア機能をロード
use Cake\I18n\I18n;

// 次に、アクションの中で言語を変更することができます。
I18n::setLocale('es');
$this->loadModel('Articles');

// 結果のすべてのエンティティーは、スペイン語の翻訳が含まれています。
$results = $this->Articles->find()->all();

このメソッドは、Table 内の任意のファインダーで動作します。たとえば、 find('list') で TranslateBehavior を使用することができます。

I18n::setLocale('es');
$data = $this->Articles->find('list')->toArray();

// データが含まれます。
[1 => 'Mi primer artículo', 2 => 'El segundo artículo', 15 => 'Otro articulo' ...]

エンティティーのすべての翻訳を取得

翻訳されたコンテンツを更新するためのインターフェイスを構築するときに、同時に1つまたは複数の翻訳を表示すると便利です。このために translations ファインダーを使用することができます。

// すべての対応する翻訳を持つ最初の記事を検索
$article = $this->Articles->find('translations')->first();

上記の例では、 _translations プロパティーが設定されたエンティティーのリストを取得します。このプロパティーは、翻訳データエンティティーのリストが含まれます。たとえば、次のプロパティーがアクセス可能になります。

// 出力結果 'en'
echo $article->_translations['en']->locale;

// 出力結果 'title'
echo $article->_translations['en']->field;

// 出力結果 'My awesome post!'
echo $article->_translations['en']->body;

このデータを扱うためのよりエレガントな方法は、テーブルに使用されるエンティティークラスにトレイトを追加することです。

use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;

class Article extends Entity
{
    use TranslateTrait;
}

このトレイトには translation というメソッドが含まれています。これにより、新しい翻訳エンティティーにその場でアクセスしたり作成することができます。

// 出力結果 'title'
echo $article->translation('en')->title;

// 新しい翻訳データエンティティーを article に追加
$article->translation('de')->title = 'Wunderbar';

取得する翻訳を制限

特定のレコードセットのためにデータベースから取得される言語を制限することができます。

$results = $this->Articles->find('translations', [
    'locales' => ['en', 'es']
]);
$article = $results->first();
$spanishTranslation = $article->translation('es');
$englishTranslation = $article->translation('en');

空の翻訳の取得を防止

翻訳レコードには任意の文字列を含めることができますが、もしレコードが翻訳されて空文字列（''）として格納されている場合、 translate ビヘイビアーは元のフィールド値を上書きします。

これが望ましくない場合は、 allowEmptyTranslations 設定キーを使用して、空である翻訳を無視することができます。

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Translate', [
            'fields' => ['title', 'body'],
            'allowEmptyTranslations' => false
        ]);
    }
}

上記は、内容のある翻訳されたデータのみを読み込みます。

アソシエーションのすべての翻訳を取得

単一の検索操作で任意のアソシエーションの翻訳を見つけることもできます。

$article = $this->Articles->find('translations')->contain([
    'Categories' => function ($query) {
        return $query->find('translations');
    }
])->first();

// 出力結果 'Programación'
echo $article->categories[0]->translation('es')->name;

これは、 Categories に TranslateBehavior が追加されていることを前提としています。アソシエーションで translations カスタムファインダーを使用するには、クエリービルダー関数の contain 句を単に使用するだけです。

I18n::setLocale を使用せずに一つの言語の取得

I18n::setLocale('es'); を呼び出すと、翻訳されたすべての検索のデフォルトロケールが変更されますが、アプリケーションの状態を変更せずに翻訳されたコンテンツを取得したいことがあります。これらの状況には、ビヘイビアーの setLocale() メソッドを使用してください。

I18n::setLocale('en'); // 説明のためにリセット

$this->loadModel('Articles');
// 特定のロケール。3.6 より前は locale() メソッドを使用してください。
$this->Articles->setLocale('es');

$article = $this->Articles->get(12);
echo $article->title; // 'Un Artículo' と出力、超簡単！

これは、Articles テーブルのみのロケールを変更し、関連するデータの言語に影響を与えないことに注意してください。関連するデータに影響を与えるためには、各テーブルでメソッドを呼び出すことが必要です。例えば、

I18n::setLocale('en'); // 説明のためにリセット

$this->loadModel('Articles');
// 3.6 より前は locale() メソッドを使用してください。
$this->Articles->setLocale('es');
$this->Articles->Categories->setLocale('es');

$data = $this->Articles->find('all', ['contain' => ['Categories']]);

この例では、 Categories も TranslateBehavior が追加されていることを前提としています。

翻訳されたフィールドのクエリー

TranslateBehavior は、デフォルトでは検索条件を置換しません。翻訳されたフィールドの検索条件を作成するには translationField() メソッドを使用します。

// 3.6 より前は locale() メソッドを使用してください。
$this->Articles->setLocale('es');
$data = $this->Articles->find()->where([
    $this->Articles->translationField('title') => 'Otro Título'
]);

別の言語で保存

TranslateBehavior の背後にある哲学は、デフォルトの言語を表すエンティティー、およびそのエンティティー内の特定のフィールドを上書きできる複数の翻訳を持っているということです。これを踏まえて、直感的に任意のエンティティーの翻訳を保存することができます。たとえば、次のような設定になります。

// src/Model/Table/ArticlesTable.php の中で
class ArticlesTable extends Table
{
    public function initialize(array $config)
    {
        $this->addBehavior('Translate', ['fields' => ['title', 'body']]);
    }
}

// src/Model/Entity/Article.php の中で
class Article extends Entity
{
    use TranslateTrait;
}

// コントローラーの中で
$this->loadModel('Articles');
$article = new Article([
    'title' => 'My First Article',
    'body' => 'This is the content',
    'footnote' => 'Some afterwords'
]);

$this->Articles->save($article);

最初の記事を保存した後、その翻訳を保存することができる２通りの方法があります。１つ目、エンティティーに直接言語を設定します。

$article->_locale = 'es';
$article->title = 'Mi primer Artículo';

$this->Articles->save($article);

エンティティーが保存された後、翻訳されたフィールドも同様に永続化されますが、もう１つの注意点は、デフォルトの言語の値は上書きされずに保存されることです。

// 出力結果 'This is the content'
echo $article->body;

// 出力結果 'Mi primer Artículo'
echo $article->title;

一度、値を上書きすると、そのフィールドの翻訳が保存され、通常通りに取得することができます。

$article->body = 'El contendio';
$this->Articles->save($article);

別の言語でエンティティーを保存するために使用する２つ目の方法は、直接テーブルにデフォルトの言語を設定することです。

$article->title = 'Mi Primer Artículo';

// 3.6 より前は locale() メソッドを使用してください。
$this->Articles->setLocale('es');
$this->Articles->save($article);

同じ言語でエンティティーを取得や保存の両方が必要な時や、複数のエンティティーを一括で保存する時に、テーブルに直接言語を設定すると便利です。

複数の翻訳を保存

任意のデータベースのレコードに複数の翻訳を同時に追加したり編集したりできるようにすることは、一般的な要件です。これは、 TranslateTrait を使用して行うことができます。

use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;

class Article extends Entity
{
    use TranslateTrait;
}

次に、それらを保存する前に翻訳を取り込むことができます。

$translations = [
    'fr' => ['title' => "Un article"],
    'es' => ['title' => 'Un artículo']
];

foreach ($translations as $lang => $data) {
    $article->translation($lang)->set($data, ['guard' => false]);
}

$this->Articles->save($article);

3.3.0 では、複数の翻訳での動作は簡素化されました。翻訳されたフィールドのフォームコントロールを作成することができます。

// ビューテンプレートの中で
<?= $this->Form->create($article); ?>
<fieldset>
    <legend>French</legend>
    <?= $this->Form->control('_translations.fr.title'); ?>
    <?= $this->Form->control('_translations.fr.body'); ?>
</fieldset>
<fieldset>
    <legend>Spanish</legend>
    <?= $this->Form->control('_translations.es.title'); ?>
    <?= $this->Form->control('_translations.es.body'); ?>
</fieldset>

コントローラーの中では、通常通りにデータをマーシャリングできます。

$article = $this->Articles->newEntity($this->request->getData());
$this->Articles->save($article);

これは、すべてが永続化されたフランス語とスペイン語の翻訳の記事になります。同様に、エンティティーの $_accessible フィールドの中に _translations を追加することを忘れないでください。

翻訳されたエンティティーの検証

モデルに TranslateBehavior を追加した場合、 newEntity() や patchEntity() で、ビヘイビアーによって翻訳レコードが作成・更新される際に使用するバリデータを定義できます。

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Translate', [
            'fields' => ['title'],
            'validator' => 'translated'
        ]);
    }
}

上記は、翻訳されたエンティティーを検証するための validationTranslated によって作成されるバリデータを使用します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Tree

class Cake\ORM\Behavior\TreeBehavior

データベーステーブル内に階層データを格納することは、かなり一般的です。そのようなデータの例としては、無制限のサブカテゴリを有するカテゴリ、マルチレベルメニューシステムに関連するデータ、または企業内の部門などの階層のリテラル表現があります。

リレーショナル・データベースは、通常、このタイプのデータを格納および検索するのには適していませんが、複数レベルの情報を扱うために効果的な方法がいくつかあります。

TreeBehavior は、オーバーヘッドをほとんどかけることなく照会できる階層型のデータ構造をデータベースに保持し、検索や表示の処理のためのツリーデータを再構築するのに役立ちます。

必要条件

このビヘイビアーは、テーブル内の以下のカラムが必要です。

parent_id (null も可能) 親の行の ID を保持するカラム

lft (整数、符号付き) ツリー構造を維持するために使用

rght (整数、符号付き) ツリー構造を維持するために使用

カスタマイズする必要がある場合は、これらのフィールドの名前を設定できます。フィールドの意味とその使用方法の詳細については、 MPTT ロジック [https://www.sitepoint.com/hierarchical-data-database-2/] に書いてある記事を参照してください。

警告

TreeBehavior は、現時点では複合主キーをサポートしていません。

クイックツアー

階層データを格納したい Table に Tree ビヘイビアーを追加して有効にします。

class CategoriesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Tree');
    }
}

テーブルがすでにいくつかの行を保持している場合、一度追加すると CakePHP は内部構造を構築することができます。

$categories = $this->getTableLocator()->get('Categories');
$categories->recover();

テーブルから行を取得し、その行が持つ子孫の数を調べることで動作することを確認できます。

$node = $categories->get(1);
echo $categories->childCount($node);

同様に、ノードの子孫のフラットなリストを取得することは簡単です。

$descendants = $categories->find('children', ['for' => 1]);

foreach ($descendants as $category) {
    echo $category->name . "\n";
}

条件を渡す必要がある場合は、通常通り:

$descendants = $categories
    ->find('children', ['for' => 1])
    ->where(['name LIKE' => '%Foo%'])
    ->all();

foreach ($descendants as $category) {
    echo $category->name . "\n";
}

代わりに、各ノードの子が階層内にネストされているスレッドリストが必要な場合は、 'threaded' ファインダを積み重ねられます。

$children = $categories
    ->find('children', ['for' => 1])
    ->find('threaded')
    ->toArray();

foreach ($children as $child) {
    echo "{$child->name} は、直下の子が " . count($child->children) . " あります。";
}

スレッド化された結果を取得するには通常、再帰関数が必要ですが、HTML を選択するなど、リストを表示できるように各レベルの単一のフィールドを含む結果セットのみが必要な場合は、 'treeList' ファインダを使用する方が良いです。

$list = $categories->find('treeList');

// CakePHP テンプレートファイルの中で
echo $this->Form->control('categories', ['options' => $list]);

// もしくは、CLI スクリプトなどでプレーンテキストで出力できます
foreach ($list as $categoryName) {
    echo $categoryName . "\n";
}

出力は次のようになります。

My Categories
_Fun
__Sport
___Surfing
___Skating
_Trips
__National
__International

treeList ファインダはいくつかのオプションを持ちます。

keyPath: 配列キーに使用するフィールドを取得するためのドット区切りパス、または指定された行からキーを返すためのクロージャ。

valuePath: 配列値に使用するフィールドを取得するドット区切りパス、または指定された行から値を返すクロージャ。

spacer: 各項目のツリーの深さを表すプレフィックスとして使用される文字列

使用できるすべてのオプションの例です。

$query = $categories->find('treeList', [
    'keyPath' => 'url',
    'valuePath' => 'id',
    'spacer' => ' '
]);

クロージャーを使用する例です。

$query = $categories->find('treeList', [
    'keyPath' => 'url',
    'valuePath' => function($entity){
        return $entity->url . ' ' . $entity->id
     },
    'spacer' => ' '
]);

とても一般的な作業の1つは、特定のノードからツリーのルートまでのツリーパスを見つけることです。例えば、メニュー構造を表すパンくずリストを追加するのに便利です。

$nodeId = 5;
$crumbs = $categories->find('path', ['for' => $nodeId]);

foreach ($crumbs as $crumb) {
    echo $crumb->name . ' > ';
}

TreeBehavior で構築されたツリーは lft 以外のカラムでソートすることはできません。ツリーの内部表現はこのソートに依存するからです。幸いなことに、自分の親を変更することなく、同じレベルの内部ノードを並べ替えることができます。

$node = $categories->get(5);

// 子どもをリストアップするときに1つ上の位置に表示されるようにノードを移動します。
$categories->moveUp($node);

// 同じレベルの中でリストの先頭にノードを移動します。
$categories->moveUp($node, true);

// 一番下にノードを移動します。
$categories->moveDown($node, true);

設定

このビヘイビアーによって使用されるデフォルトのカラム名が、スキーマと一致しない場合、それらの別名を提供することができます。

public function initialize(array $config)
{
    $this->addBehavior('Tree', [
        'parent' => 'ancestor_id', // parent_id の代わりに使用
        'left' => 'tree_left', // lft の代わりに使用
        'right' => 'tree_right' // rght の代わりに使用
    ]);
}

ノードレベル (深さ)

ツリーノードの深さを知ることは、例えばメニューを生成するときなど、一定のレベルまでノードを検索したい時に役に立ちます。 level オプションを使うことで、各ノードのレベルを保存するフィールドを指定することができます。

$this->addBehavior('Tree', [
    'level' => 'level', // デフォルトは null で、レベルは保存しません
]);

db フィールドを使用してレベルをキャッシュしたくない場合、ノードのレベルを取得するために TreeBehavior::getLevel() メソッドが使用できます。

スコープとマルチツリー

時には、同じテーブルの中に複数のツリー構造を保持したい場合は、 'scope' 設定を使用して達成できます。たとえば、locations テーブルでは、国ごとに1つのツリーを作成することができます。

class LocationsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Tree', [
            'scope' => ['country_name' => 'Brazil']
        ]);
    }
}

前の例では、すべてのツリーの操作は、 country_name カラムに 'Brazil' がセットされている行のみに限定されます。 'config' 関数を使って、その場でスコープを変更することができます。

$this->behaviors()->Tree->config('scope', ['country_name' => 'France']);

必要に応じて、スコープとしてクロージャを渡すことで、スコープのより細かい制御ができます。

$this->behaviors()->Tree->config('scope', function ($query) {
    $country = $this->getConfigureContry(); // 作成した関数
    return $query->where(['country_name' => $country]);
});

独自のソートフィールドでのリカバリ

デフォルトでは、recover() は、主キーを使用して項目を並べ替えます。これは数字の（自動インクリメント）カラムであればうまくいきますが、 UUID を使用すると奇妙な結果につながる可能性があります。

リカバリのための独自のソートが必要な場合は、設定で独自の order 句を設定できます。

$this->addBehavior('Tree', [
    'recoverOrder' => ['country_name' => 'DESC'],
]);

階層データの保存

Tree ビヘイビアーを使用しているときは、通常、階層構造の内部表現を心配する必要はありません。ツリーに配置されているノードの位置は、各エンティティーの 'parent_id' カラムから推定されます。

$aCategory = $categoriesTable->get(10);
$aCategory->parent_id = 5;
$categoriesTable->save($aCategory);

ツリー内にループ（そのノード自身を子ノードにする）を作成または保存しようとする場合、存在しない親 ID を提供すると例外がスローされます。

'parent_id' カラムを null に設定すると、ツリー内のノードをルートにすることができます。

$aCategory = $categoriesTable->get(10);
$aCategory->parent_id = null;
$categoriesTable->save($aCategory);

新しいルートノードの子供は保存されます。

ノードの削除

ノードとそのすべてのサブツリー（ツリーの任意の深さにある子孫）を削除することは簡単です。

$aCategory = $categoriesTable->get(10);
$categoriesTable->delete($aCategory);

TreeBehavior は、内部のすべての削除操作を処理します。また、1つのノードを削除し、ツリー内のすぐ上位の親ノードにすべての子を再割り当てすることもできます。

$aCategory = $categoriesTable->get(10);
$categoriesTable->removeFromTree($aCategory);
$categoriesTable->delete($aCategory);

すべての子ノードが保持され、新しい親が割り当てられます。

ノードの削除は、エンティティーの lft と rght の値に基づいて行われます。これは条件付き削除のためにノードのさまざまな子をループするときに注意することは重要です。

$descendants = $teams->find('children', ['for' => 1])->all();

foreach ($descendants as $descendant) {
    $team = $teams->get($descendant->id); // 最新のエンティティーオブジェクトを検索
    if ($team->expired) {
        $teams->delete($team); // 削除して、データベースに登録された lft と rght を並び替えます
    }
}

TreeBehavior は、ノードが削除されたときに、テーブル内のレコードの lft と rght の値を並べ替えます。したがって、(削除操作の前に保存された) $descendants 内のエンティティーの lft と rght の値は不正確になります。テーブルの不一致を防ぐために、エンティティーは、その場で読み込みおよび変更する必要があります。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

スキーマシステム

CakePHP は、SQL データストア内のテーブルのスキーマ情報を反映し、生成することができるスキーマシステムを備えています。スキーマシステムは、CakePHP がサポートする全ての SQL プラットフォーム用のスキーマを生成・反映することができます。

スキーマシステムの主要部分は Cake\Database\Schema\Collection と Cake\Database\Schema\TableSchema です。これらのクラスを使用すると、それぞれデータベース全体と個々の Table オブジェクトの機能にアクセスできます。

スキーマシステムの主な用途は、フィクスチャー のためのものです。しかしながら、必要な場合は、アプリケーションでそれを使用することができます。

Schema\TableSchema オブジェクト

class Cake\Database\Schema\TableSchema

スキーマ・サブシステムは、データベース内のテーブルに関するデータを保持するための簡単な Table オブジェクトを提供します。このオブジェクトは、スキーマのリフレクション機能によって返されます。

use Cake\Database\Schema\TableSchema;

// 一度にテーブルの１カラムを作成
$schema = new TableSchema('posts');
$schema->addColumn('id', [
  'type' => 'integer',
  'length' => 11,
  'null' => false,
  'default' => null,
])->addColumn('title', [
  'type' => 'string',
  'length' => 255,
  // 固定長 (char フィールド) の作成
  'fixed' => true
])->addConstraint('primary', [
  'type' => 'primary',
  'columns' => ['id']
]);

// Schema\TableSchema は、配列データを使用して作成できます
$schema = new TableSchema('posts', $columns);

Schema\TableSchema オブジェクトを使用すると、テーブルのスキーマに関する情報を構築することを可能にします。それは、テーブルを記述するために使用されるデータを正規化し、検証するのに役立ちます。たとえば、次の2つの形式は等価です。

$schema->addColumn('title', 'string');
// そして
$schema->addColumn('title', [
  'type' => 'string'
]);

等価ではありますが、２番目の形式は、より詳細かつ制御することができます。これは、2.x の Schema ファイルとフィクスチャースキーマで使用可能な既存の機能をエミュレートします。

カラムデータへのアクセス

カラムは、コンストラクターの引数として、または addColumn() を経由して追加されます。情報が追加された一つのフィールドは、 column() または columns() を使用して取得できます。

// カラムに関するデータの配列を取得
$c = $schema->column('title');

// 全てのカラムの一覧を取得
$cols = $schema->columns();

インデックスと制約

インデックスは addIndex() メソッドを使用して追加されます。制約は addConstraint() を使用して追加されます。無効な状態になるため、存在しない列に対してはインデックスや制約を追加できません。インデックスは制約とは異なり、メソッド間で型を混合しようとすると例外が発生します。両方のメソッドの例です。

$schema = new TableSchema('posts');
$schema->addColumn('id', 'integer')
  ->addColumn('author_id', 'integer')
  ->addColumn('title', 'string')
  ->addColumn('slug', 'string');

// 主キーの追加
$schema->addConstraint('primary', [
  'type' => 'primary',
  'columns' => ['id']
]);
// ユニークキーの追加
$schema->addConstraint('slug_idx', [
  'columns' => ['slug'],
  'type' => 'unique',
]);
// インデックスの追加
$schema->addIndex('slug_title', [
  'columns' => ['slug', 'title'],
  'type' => 'index'
]);
// 外部キーの追加
$schema->addConstraint('author_id_idx', [
  'columns' => ['author_id'],
  'type' => 'foreign',
  'references' => ['authors', 'id'],
  'update' => 'cascade',
  'delete' => 'cascade'
]);

単一の整数カラムに主キー制約を追加する場合、自動的にデータベースのプラットフォームに応じて auto-increment/serial カラムに変換されます。

$schema = new TableSchema('posts');
$schema->addColumn('id', 'integer')
->addConstraint('primary', [
    'type' => 'primary',
    'columns' => ['id']
]);

上記の例では、 id カラムは、MySQL の次のような SQL を生成します。

CREATE TABLE `posts` (
    `id` INTEGER AUTO_INCREMENT,
    PRIMARY KEY (`id`)
)

主キーに複数のカラムが含まれている場合、どのカラムも自動的には自動インクリメント値に変換されません。代わりに、複合キーのどのカラムを自動インクリメントしたいのかをテーブルオブジェクトに伝える必要があります。

$schema = new TableSchema('posts');
$schema->addColumn('id', [
    'type' => 'integer',
    'autoIncrement' => true,
])
->addColumn('account_id', 'integer')
->addConstraint('primary', [
    'type' => 'primary',
    'columns' => ['id', 'account_id']
]);

autoIncrement オプションは、 integer と biginteger カラムで動作します。

インデックスと制約の読み込み

インデックスと制約は、アクセサーメソッドを使用して、テーブルオブジェクトから読み取ることができます。 $schema が作成された TableSchema インスタンスであると仮定すると、以下を行うことができます。

// 制約の取得
// 全ての制約の名前を返します。
$constraints = $schema->constraints()

// 単一の制約に関するデータを取得
$constraint = $schema->constraint('author_id_idx')

// インデックスの取得
// 全てのインデックス名を返します。
$indexes = $schema->indexes()

// 単一のインデックスに関するデータを取得
$index = $schema->index('author_id_idx')

テーブルオプションの追加

一部のドライバー（主に MySQL）は、追加のテーブルのメタデータをサポートし、必要とします。 MySQL の場合には CHARSET、 COLLATE と ENGINE プロパティーは、 MySQL でテーブルの構造を維持するために必要とされます。テーブルオプションを追加するために、以下を使用することができます。

$schema->options([
  'engine' => 'InnoDB',
  'collate' => 'utf8_unicode_ci',
]);

プラットフォームの方言は、関係するキーだけを処理し、残りは無視します。すべてのオプションは、すべてのプラットフォームでサポートされるわけではありません。

Table を SQL に変換

createSql() や dropSql() を使用すると、特定のテーブルを作成または削除するためのプラットフォーム固有の SQL を取得することができます。

$db = ConnectionManager::get('default');
$schema = new TableSchema('posts', $fields, $indexes);

// テーブルの作成
$queries = $schema->createSql($db);
foreach ($queries as $sql) {
  $db->execute($sql);
}

// テーブルの削除
$sql = $schema->dropSql($db);
$db->execute($sql);

接続のドライバーを使用することにより、スキーマデータをプラットフォーム固有の SQL に変換することができます。 createSql と dropSql の戻り値は、テーブルと必要なインデックスを作成するために必要な SQL クエリーのリストです。いくつかのプラットフォームでは、コメントやインデックスのあるテーブルを作成するために複数のステートメントが必要な場合があります。クエリーの配列は常に返されます。

スキーマコレクション

class Cake\Database\Schema\Collection

Collection は、接続中に利用可能なさまざまなテーブルへのアクセスを提供します。これを使用すると、テーブルのリストを取得したり、テーブルを TableSchema オブジェクトに反映させることができます。クラスの基本的な使い方は次のようになります。

$db = ConnectionManager::get('default');

// スキーマコレクションの作成
// Prior to 3.4 use $db->schemaCollection()
$collection = $db->getSchemaCollection();

// テーブル名の取得
$tables = $collection->listTables();

// 単一テーブル (Schema\TableSchema インスタンス) の取得
$tableSchema = $collection->describe('posts');

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

スキーマキャッシュツール

SchemaCacheShell は単純なアプリケーションのメタデータキャッシュを管理する CLI ツールを提供します。開発環境ではこれは正しくメタデータキャッシュを既存のデータを消去することなく再構築する助けになります。以下のコマンドで可能です。

bin/cake schema_cache build --connection default

これで default 設定で接続されている全てのテーブルのメタデータを再構築します。一つのテーブルだけ再構築したければ名前を指定して出来ます。

bin/cake schema_cache build --connection default articles

加えて、キャッシュデータを作るために、SchemaCacheShell をキャッシュされたメタデータを削除するために使えます。:

# 全メタデータの消去
bin/cake schema_cache clear

# 一つのテーブルだけ消去
bin/cake schema_cache clear articles

注釈

3.6 より前は、 schema_cache の代わりに orm_cache を使ってください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

キャッシュ

class Cake\Cache\Cache

速くより近くのストレージシステムに必要なデータの二次コピーを持つことにより、高価なリソースや低速なリソースの読み込みを行うためにキャッシュを使用できます。たとえば、高価なクエリの結果や頻繁に変更されないリモートウェブサービスへのアクセスをキャッシュに格納することができます。キャッシュに保存されると、キャッシュからの読み込みは、リモートリソースにアクセスよりもはるかに高速です。

CakePHP のキャッシュは、 Cache クラスを使用します。このクラスは、様々なキャッシュの実装を扱うための静的なインターフェイスや統一された API を提供します。 CakePHP には、いくつかのキャッシュエンジンが用意されていて、独自のバックエンドを構築する必要な場合、シンプルなインターフェイスを提供します。以下が、組み込みのキャッシュエンジンです。

File File キャッシュはローカルファイルを使用するシンプルなキャッシュです。最も遅いキャッシュエンジンで、アトミックな操作のための多くの機能を持ちません。しかし、ディスクストレージは非常に安価なので、頻繁に書き込みが行なわれない大きなオブジェクトや要素の保存はファイルに適しています。

Memcached Memcached [https://php.net/memcached] 拡張を使います。

Redis phpredis [https://github.com/phpredis/phpredis] 拡張を使います。 Redis は高速で、Memcached と同様の永続キャッシュシステム、アトミックな操作を提供します。

Apcu APCu キャッシュは、PHP の APCu [https://php.net/apcu] 拡張を使用します。この拡張はオブジェクトを保存するためにウェブサーバー上の共有メモリーを使います。これはとても高速で、かつアトミックな読み込み/書き込みの機能を提供することが可能になります。

Wincache Wincache は Wincache [https://php.net/wincache] 拡張を使います。 Wincache は APC と同様の機能とパフォーマンスを持ちますが、Windows と IIS に最適化されています。

Array はすべてのデータを配列に保存します。永続的なストレージを提供せず、test suites内での使用を想定しています。

Null nullエンジンは実質何も保存せず、すべての読み込み操作を失敗させます。

あなたが選択したキャッシュエンジンに関わらず、アプリケーションは Cake\Cache\Cache とやり取りします。

Cache エンジンの設定

static Cake\Cache\Cache::setConfig($key, $config = null)

アプリケーションは、ブート処理中に任意の数の「エンジン」を設定できます。キャッシュエンジンの設定は、 config/app.php で定義されています。

最適なパフォーマンスを得るには、CakePHP では2つのキャッシュエンジンを定義する必要があります。

_cake_core_ は、ファイル構成の保存と、国際化と地域化ファイルの結果のパースに使用されます。

_cake_model_ は、アプリケーション上のモデルに対するスキーマの説明を保存するために使用されます。

複数のエンジン設定を使用することで、必要に応じてストレージを段階的に変更できます。例えば、以下のように config/app.php に設定できます。

// ...
'Cache' => [
    'short' => [
        'className' => 'File',
        'duration' => '+1 hours',
        'path' => CACHE,
        'prefix' => 'cake_short_'
    ],
    // 完全な名前空間つきの名前を使用。
    'long' => [
        'className' => 'Cake\Cache\Engine\FileEngine',
        'duration' => '+1 week',
        'probability' => 100,
        'path' => CACHE . 'long' . DS,
    ]
]
// ...

オプション設定は DSN を指定することもできます。これは環境変数や PaaS プロバイダーと一緒に動作するときに便利です。

Cache::setConfig('short', [
    'url' => 'memcached://user:password@cache-host/?timeout=3600&prefix=myapp_',
]);

DSN を使用するとき、追加のクエリー文字列要素としてパラメーターやオプションが定義できます。

実行時におけるキャッシュエンジンの設定もできます。

// 短い名前で
Cache::setConfig('short', [
    'className' => 'File',
    'duration' => '+1 hours',
    'path' => CACHE,
    'prefix' => 'cake_short_'
]);

// 完全な名前空間つきの名前を使用。
Cache::setConfig('long', [
    'className' => 'Cake\Cache\Engine\FileEngine',
    'duration' => '+1 week',
    'probability' => 100,
    'path' => CACHE . 'long' . DS,
]);

// オブジェクトで
$object = new FileEngine($config);
Cache::setConfig('other', $object);

これらのエンジン設定の名前 ('short' や 'long') は Cake\Cache\Cache::write() と Cake\Cache\Cache::read() の $config パラメーターとして使われます。キャッシュエンジンを設定する場合は、次の構文を使用してクラス名を参照することができます。

// 省略名 (App\ または Cake 名前空間の中)
Cache::setConfig('long', ['className' => 'File']);

// プラグインの省略名
Cache::setConfig('long', ['className' => 'MyPlugin.SuperCache']);

// 完全な名前空間
Cache::setConfig('long', ['className' => 'Cake\Cache\Engine\FileEngine']);

// CacheEngineInterface を実装したオブジェクト
Cache::setConfig('long', ['className' => $myCache]);

注釈

FileEngine 使用時に、正しいパーミッションでのキャッシュファイルを指定して作成するには、 mask オプションの設定が必要です。

エンジンのオプション

各エンジンは次のオプションを受け入れます。

duration このキャッシュ設定内のアイテムの存続期間を指定します。 strototime() 互換表現として指定されます。

groups この設定に格納されているすべてのキーに関連付けられているグループまたは「タグ」のリスト。キャッシュから完全なグループを削除するのに便利です。

prefix すべてのエントリーの先頭に追加されます。キースペースを別のキャッシュ設定または別のアプリケーションと共有する必要がある場合に適しています。

probability キャッシュ GC クリーンアップの可能性。 0 に設定すると、 Cache::gc() が自動的に呼び出されなくなります。

FileEngine オプション

FileEngine は次のエンジン固有オプションを使用します。

isWindows ホストがウインドウズであるかどうかで自動的に設定されます。

lock ファイルを書き込む前にロックする必要があるかどうか。

mask 作成されたファイルに使用されるマスク。

path キャッシュファイルを保存する場所へのパス。 デフォルトはシステムの一時ディレクトリです。

RedisEngine オプション

RedisEngine は次のエンジン固有オプションを使用します。

port Redis サーバーが動作しているポート。

host Redis サーバーが動作しているホスト。

database 接続に使用するデータベース番号。

password Redis サーバーのパスワード。

persistent Redis への永続的な接続を行うかどうか。

timeout Redis の接続タイムアウト。

unix_socket Redis の UNIX ソケットへのパス。

MemcacheEngine オプション

compress データを圧縮するかどうか。

username Memcache サーバーにアクセスするためのログイン名。

password Memcache サーバーにアクセスするためのパスワード。

persistent 永続的な接続の名前。同じ永続的な値を使用するすべての設定は、単一の基本接続を共有します。

serialize データをシリアライズするために使用されるシリアライザエンジン。利用可能なエンジンは php、igbinary、json です。 php のほかに、memcached 拡張は適切なシリアライザのサポートでコンパイルする必要があります。

servers memcached サーバーの文字列または配列。配列の場合、MemcacheEngine はそれらをプールとして使用します。

options memcached クライアントの追加のオプション。オプション => 値 の配列でなければなりません。 \Memcached::OPT_* 定数をキーとして使用してください。

キャッシュフォールバックの設定

書き込み不可能なフォルダーに書き込む FileEngine や、 RedisEngine が Redis に接続できないなど、エンジンが利用できない場合、エンジンは NullEngine にフォールバックし、ログ可能なエラーを引き起こします。これにより、キャッシュ障害のためにアプリケーションがキャッチされない例外をスローするのを防ぎます。

fallback 設定キーを使ってキャッシュ設定を指定された設定にフォールバックするよう設定できます。

Cache::setConfig('redis', [
    'className' => 'Redis',
    'duration' => '+1 hours',
    'prefix' => 'cake_redis_',
    'host' => '127.0.0.1',
    'port' => 6379,
    'fallback' => 'default',
]);

Redis サーバーが予期せず失敗した場合、 redis キャッシュ設定への書き込みは、 default キャッシュ設定への書き込みにフォールバックします。このシナリオで default キャッシュ設定への書き込みも失敗した場合、 NullEngine へ再び縮退運転し、キャッチされない例外をスローするのを防ぎます。

false でキャッシュフォールバックを無効にすることができます。

Cache::setConfig('redis', [
    'className' => 'Redis',
    'duration' => '+1 hours',
    'prefix' => 'cake_redis_',
    'host' => '127.0.0.1',
    'port' => 6379,
    'fallback' => false
]);

フォールバックがない場合、キャッシュ障害は例外として発生します。

設定されたキャッシュエンジンを削除する

static Cake\Cache\Cache::drop($key)

一度設定が作成されたら、変更することはできません。代わりに、 Cake\Cache\Cache::drop() と Cake\Cache\Cache::config() を使用して、設定を削除して再作成する必要があります。キャッシュエンジンを削除すると、設定が削除され、アダプターが構築されていれば破棄されます。

キャッシュへの書き込み

static Cake\Cache\Cache::write($key, $value, $config = 'default')

Cache::write() はキャッシュに $value を書き込みます。この値は後で $key で参照したり、削除したりすることができます。オプションの設定を指定して、キャッシュを保存することもできます。 $config を指定しない場合、デフォルトが使用されます。 Cache::write() はあらゆるタイプのオブジェクトを格納することができ、以下のようにモデルの結果を格納するのに理想的です。

$posts = Cache::read('posts');
if ($posts === null) {
    $posts = $someService->getAllPosts();
    Cache::write('posts', $posts);
}

Cache::write() と Cache::read() を使用して、データベースへのアクセスを減らし、 posts を取得しています。

注釈

CakePHP ORM で作成したクエリーの結果をキャッシュする場合は、 ロードされた結果をキャッシュするセクションで説明しているように、Query オブジェクトのビルトインキャッシュ機能を使用する方が良いです。

一度に複数のキーを書き込む

static Cake\Cache\Cache::writeMany($data, $config = 'default')

一度に複数のキャッシュキーを書き込む必要が出るかもしれません。 write() を複数回呼び出すこともできますが、 writeMany() は CakePHP がより効率的なストレージ API を使用できるようにします。例えば Memcached を使用する場合、 writeMany() を使用して、複数回のネットワーク接続を節約できます。

$result = Cache::writeMany([
    'article-' . $slug => $article,
    'article-' . $slug . '-comments' => $comments
]);

// $result は以下を含みます
['article-first-post' => true, 'article-first-post-comments' => true]

アトミックな書き込み

static Cake\Cache\Cache::add($key, $value $config = 'default')

Cache::add() を使用すると、キーがキャッシュに存在しない場合に、アトミックにキーを値に設定することができます。もし、キーがすでにキャッシュに存在する場合や、書き込みに失敗した場合は、 add() は false を返します:

// キーをロックとして機能するように設定する
$result = Cache::add($lockKey, true);
if (!$result) {
    return;
}
// 一度に1つのプロセスしかアクティブにできないアクションを行う。

// ロックキーを外す。
Cache::delete($lockKey);

警告

ファイルベースのキャッシュは、アトミックライトをサポートしていません。

Read-through キャッシュ

static Cake\Cache\Cache::remember($key, $callable, $config = 'default')

Cache を使用すると、Read-through キャッシュを簡単に行うことができます。指定されたキャッシュキーが存在する場合、それが返されます。キーが存在しない場合、呼び出し可能オブジェクトが呼び出され、結果がキャッシュに格納されます。

たとえば、リモートサービスコールの結果をキャッシュすることがよくあります。あなたはこれをシンプルにするために remember() を使うことができます。

class IssueService
{
    public function allIssues($repo)
    {
        return Cache::remember($repo . '-issues', function () use ($repo) {
            return $this->fetchAll($repo);
        });
    }
}

キャッシュからの読み込み

static Cake\Cache\Cache::read($key, $config = 'default')

Cache::read() は、 $key 配下に格納されたキャッシュされた値を $config から読み込むために使用されます。 $config が null の場合、デフォルトの設定が使用されます。 Cache::read() は、有効なキャッシュであればキャッシュされた値を返し、キャッシュが期限切れになっているか存在しない場合は null を返します。キャッシュの内容は false と評価される可能性があるので、必ず厳密な比較演算子 === または !== を使用してください。

例:

$cloud = Cache::read('cloud');
if ($cloud !== null) {
    return $cloud;
}

// クラウドデータを生成する
// ...

// キャッシュにデータを保存する
Cache::write('cloud', $cloud);

return $cloud;

short という別のキャッシュ設定を使っている場合、下記のように Cache::read() と Cache::write() に明記してください。

// デフォルトの代わりにshort からキー"cloud" を読み込む
$cloud = Cache::read('cloud', 'short');
if ($cloud === null) {
    // cloudデータ生成
    // ...

    // デフォルトの代わりにshort でデータをキャッシュ保存
    Cache::write('cloud', $cloud, 'short');
}

return $cloud;

一度に複数のキーを読み込む

static Cake\Cache\Cache::readMany($keys, $config = 'default')

一度に複数のキーを書き込んだ後、あなたは恐らくそれらを同様に読み込みたいでしょう。 read() を複数回呼び出すこともできますが、 readMany() は CakePHP がより効率的なストレージ API を使用できるようにします。例えば Memcached を使用している場合、 readMany() を使用して、複数回のネットワーク接続を節約できます。

$result = Cache::readMany([
    'article-' . $slug,
    'article-' . $slug . '-comments'
]);
// $result は以下を含みます
['article-first-post' => '...', 'article-first-post-comments' => '...']

キャッシュからの削除

static Cake\Cache\Cache::delete($key, $config = 'default')

Cache::delete() を使うと、キャッシュされたオブジェクトをストアから完全に削除できます。

// キーの削除
Cache::delete('my_key');

4.4.0 以降、 RedisEngine は deleteAsync() メソッドも提供し、 UNLINK オペレーションを使用してキャッシュキーを削除します:

Cache::pool('redis')->deleteAsync('my_key');

一度に複数のキーの削除

static Cake\Cache\Cache::deleteMany($keys, $config = 'default')

一度に複数のキーを書き込んだら、それらを削除したいかもしれません。 delete() を複数回呼び出すこともできますが、 deleteMany() は CakePHP がより効率的なストレージ API を使用できるようにします。例えば Memcached を使用している場合、 deleteMany() を使用して、複数回のネットワーク接続を節約できます。

$result = Cache::deleteMany([
    'article-' . $slug,
    'article-' . $slug . '-comments'
]);
// $result は以下を含みます
['article-first-post' => true, 'article-first-post-comments' => true]

キャッシュデータのクリア

static Cake\Cache\Cache::clear($config = 'default')

キャッシュ設定から、すべてのキャッシュされた値を破棄します。Apcu、Memcached、Wincache などのエンジンでは、キャッシュ設定のプレフィックスを使用してキャッシュエントリーを削除します。異なるキャッシュ設定には異なる接頭辞が付いていることを確認してください。

// すべてのキーをクリアする。
Cache::clear();

4.4.0 以降では、 RedisEngine は clearBlocking() メソッドも提供し、 UNLINK オペレーションを使ってキャッシュキーを削除します:

Cache::pool('redis')->clearBlocking();

注釈

APCu と Wincache は、ウェブサーバーと CLI 用に分離されたキャッシュを使用するため、別々にクリアする必要があります。（CLI ではウェブサーバーのキャッシュをクリアできません）

キャッシュを使用してカウンターを保存する

static Cake\Cache\Cache::increment($key, $offset = 1, $config = 'default')

static Cake\Cache\Cache::decrement($key, $offset = 1, $config = 'default')

アプリケーション内のカウンターは、キャッシュに保存するのに適しています。例として、コンテストの残りの「枠」の単純なカウントダウンをキャッシュに格納することができます。 Cache クラスは簡単な方法でカウンター値をインクリメント/デクリメントするアトミックな方法を公開しています。競合のリスクを軽減し、同時に2人のユーザーが値を1つ下げて誤った値にする可能性があるため、これらの値にはアトミック操作が重要です。

整数値を設定した後、 increment() および decrement() を使用して整数値を操作できます。

Cache::write('initial_count', 10);

// 設定した後に
Cache::decrement('initial_count');

// または
Cache::increment('initial_count');

注釈

インクリメントとデクリメントは FileEngine では機能しません。代わりに、APCu、Wincache、Redis または Memcached を使用する必要があります。

キャッシュを使用して共通のクエリー結果を格納する

まれにしか変更されない、またはキャッシュに大量の読み込みが行われるような結果をキャッシュすることによって、アプリケーションのパフォーマンスを大幅に向上させることができます。この完璧な例は、 Cake\ORM\Table::find() の結果です。この Query オブジェクトを使用すると、 cache() メソッドを使用して結果をキャッシュできます。詳細は、 ロードされた結果をキャッシュする セクションを参照してください。

グループの使用

たまに、複数のキャッシュエントリーを特定のグループまたは名前空間に属するようにマークしたい場合があります。同じグループ内のすべてのエントリーで共有される情報が変更されるたびに、キーを大量に無効化したいというのは一般的な要件です。これは、キャッシュ設定でグループを宣言することで可能です。

Cache::setConfig('site_home', [
    'className' => 'Redis',
    'duration' => '+999 days',
    'groups' => ['comment', 'article']
]);

Cake\Cache\Cache::clearGroup($group, $config = 'default')

ホームページに生成された HTML をキャッシュに保存したいが、コメントや投稿がデータベースに追加されるたびにこのキャッシュを自動的に無効にしたいとします。 comment と article グループを追加することで、このキャッシュ設定に保存されているキーに、両方のグループ名で効果的にタグを付けできます。

たとえば、新しい投稿が追加されるたびに、 article グループに関連付けられたすべてのエントリーを削除するように Cache エンジンに指示できます。

// src/Model/Table/ArticlesTable.php
public function afterSave($event, $entity, $options = [])
{
    if ($entity->isNew()) {
        Cache::clearGroup('article', 'site_home');
    }
}

static Cake\Cache\Cache::groupConfigs($group = null)

groupConfigs() を使用すると、グループと設定の間のマッピングを取得できます。つまり、同じグループを持ちます。

// src/Model/Table/ArticlesTable.php

/**
 * すべてのキャッシュ設定をクリアする前述の例のバリエーション
 * 同じグループを持つ
 */
public function afterSave($event, $entity, $options = [])
{
    if ($entity->isNew()) {
        $configs = Cache::groupConfigs('article');
        foreach ($configs['article'] as $config) {
            Cache::clearGroup('article', $config);
        }
    }
}

グループは、同じエンジンと同じ接頭辞を使用して、すべてのキャッシュ設定で共有されます。グループを使用していて、グループの削除を使用する場合は、すべての設定の共通プレフィックスを選択します。

全体的にキャッシュを有効または無効にする

static Cake\Cache\Cache::disable

キャッシュの有効期限に関連する問題を把握しようとするときに、キャッシュの読み込みと書き込みをすべて無効にする必要があります。 enable() と disable() を使ってこれを行うことができます。

// すべてのキャッシュ読み取りとキャッシュ書き込みを無効にする。
Cache::disable();

無効にすると、すべての読み込みと書き込みは null を返却します。

static Cake\Cache\Cache::enable

無効にすると、 enable() を使用してキャッシュを再び有効にすることができます。

// すべてのキャッシュの読み込みと書き込みを再び有効にする。
Cache::enable();

static Cake\Cache\Cache::enabled

もしキャッシュの状態を確認する必要がある場合は、 enabled() を使用してください。

キャッシュエンジンの作成

独自の Cache エンジンは App\Cache\Engine やプラグインの $plugin\Cache\Engine の中に提供することができます。キャッシュエンジンはキャッシュディレクトリー内になければなりません。 MyCustomCacheEngine という名前のキャッシュエンジンがあれば、 src/Cache/Engine/MyCustomCacheEngine.php に置かれます。また、プラグインの一部として、 plugins/MyPlugin/src/Cache/Engine/MyCustomCacheEngine.php に置かれます。プラグインのキャッシュ設定は、プラグインのドット構文を使用する必要があります。

Cache::setConfig('custom', [
    'className' => 'MyPlugin.MyCustomCache',
    // ...
]);

カスタムキャッシュエンジンは、いくつかの抽象メソッドを定義するだけでなく、いくつかの初期化メソッドを提供する Cake\Cache\CacheEngine を拡張する必要があります。

キャッシュエンジンに必要な API は次のとおりです。

class Cake\Cache\CacheEngine

Cache で使用されるすべてのキャッシュエンジンの基本クラス。

Cake\Cache\CacheEngine::write($key, $value)

戻り値:

成功時に boolean

キーの値をキャッシュに書き込み、データが正常にキャッシュされた場合は true を返し、失敗した場合は false を返します。

Cake\Cache\CacheEngine::read($key)

戻り値:

キャッシュ値または失敗時に false 。

キャッシュからキーを読み取ります。エントリーが期限切れまたは存在しないことを示す場合は false を返します。

Cake\Cache\CacheEngine::delete($key)

戻り値:

Boolean 成功時に true

キャッシュからキーを削除します。エントリーが存在しなかったか、削除できなかったことを示す場合は false を返します。

Cake\Cache\CacheEngine::clear($check)

戻り値:

Boolean 成功時に true

キャッシュからすべてのキーを削除します。 $check が true の場合、各値が実際に期限切れであることを検証する必要があります。

Cake\Cache\CacheEngine::clearGroup($group)

戻り値:

Boolean 成功時に true

同じグループに属するキャッシュからすべてのキーを削除します。

Cake\Cache\CacheEngine::decrement($key, $offset = 1)

戻り値:

Boolean 成功時に true

キー配下の数字をデクリメントし、デクリメントされた値を返します。

Cake\Cache\CacheEngine::increment($key, $offset = 1)

戻り値:

Boolean 成功時に true

キー配下の数字をインクリメントし、インクリメントされた値を返します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Bake コンソール

このページは 移動しました [https://book.cakephp.org/bake/2.x/ja/index.html] 。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コンソールコマンド

CakePHP はウェブフレームワークとしてだけではなく、コンソールアプリケーションを開発するためのコンソールフレームワークとしての機能を合わせ持っています。コンソールアプリケーションはメンテナンスといった様々なバックグラウンド タスクを行ったり、リクエスト－レスポンスのサイクルの外側で何かを実行するための仕組みです。CakePHP のコンソールアプリケーションでは、コマンドラインからあなたが作成したアプリケーションクラスを再利用できます。

CakePHP には元々たくさんのコンソールアプリケーションが備わっています。これらの中には（i18n のように）他の CakePHP の機能と組合せて使うものもあれば、仕事をより早く片付けるための、より一般的なものもあります。

CakePHP のコンソール

このセクションでは、コマンドラインにおける CakePHP をご紹介します。 CakePHPのコンソールツールは、ディスパッチタイプのセットアップを使って シェルやタスクをロードし、それらへパラメーターを渡します。以下の例ではbashを使用していますが、 CakePHPコンソールは多くの *nix シェルおよびウィンドウと互換性があります。

CakePHPアプリケーションには、シェルとタスクを含む src/Command 、 src/Shell 、および src/Shell/Task ディレクトリが含まれています。また、binディレクトリには実行ファイルが付属しています。

$ cd /path/to/app
$ bin/cake

注釈

Windowsのコマンドプロンプト(cmd.exe)の場合、 bin\cake としないと動作しません（スラッシュではなくバックスラッシュ）。

引数なしでコンソールを実行すると、使用可能なコマンドが一覧表示されます。そのコマンド名を使用してコマンドを実行できます。

# server シェルを実行
bin/cake server

# マイグレーションシェルを実行
bin/cake migrations -h

# bake コマンドを実行 (プラグインプレフィックスを先頭に付ける例)
bin/cake bake.bake -h

コマンドの名前がアプリケーションやフレームワークのコマンドと重複しない場合、プラグインのコマンドはプラグインプレフィックスなしで呼び出すことができます。 2つのプラグインが同じ名前のコマンドを提供する場合、最初にロードされたプラグインが短いエイリアス名を取得します。この plugin.command 形式を使用して、コマンドを呼び分けることができます。

コンソールアプリケーション

デフォルトでは、CakePHP はアプリケーションやプラグインの全てのコマンドを自動的に検出します。独立したコンソールアプリケーションを構築する場合は、公開されるコマンドの数を減らすことをお勧めします。 Application の console() フックを使って、公開されるコマンドの制限やコマンド名の整理が可能です。:

// src/Application.php
namespace App;

use App\Command\UserCommand;
use App\Command\VersionCommand;
use Cake\Console\CommandCollection;
use Cake\Http\BaseApplication;

class Application extends BaseApplication
{
    public function console(CommandCollection $commands): CommandCollection
    {
        // Add by classname
        $commands->add('user', UserCommand::class);

        // Add instance
        $commands->add('version', new VersionCommand());

        return $commands;
    }
}

上記の例では、利用できるコマンドは help ・ version ・ user のみです。プラグインにコマンドを追加する方法については、 コマンド セクションをご覧ください。

注釈

同じ Command クラスを使用する複数のコマンドを追加する場合、 help コマンドは最短のオプションを表示します。

コマンド名を変更

コマンドの名前を変更したり、ネストされたコマンドやサブコマンドを作成する場合があります。コマンドのデフォルトの自動検出ではこれは行われませんが、コマンドを登録して任意の名前をつけることができます。

プラグインの中で各コマンドを定義することにより、コマンド名をカスタマイズできます。:

public function console(CommandCollection $commands): CommandCollection
{
    // ネストされた名前のコマンドを追加
    $commands->add('user dump', UserDumpCommand::class)
    $commands->add('user:show', UserShowCommand::class)

    // コマンドの名前を全体的に変更
    $commands->add('lazer', UserDeleteCommand::class)

    return $commands;
}

アプリケーションの console() フックを上書きする場合、 $commands->autoDiscover() を呼び出して、CakePHP、アプリケーション、およびプラグインからコマンドを追加することを忘れないでください。

添付されているコマンドの名前を変更したい・削除する必要がある場合は、アプリケーションイベントマネージャーで Console.buildCommands イベントを使用して、使用可能なコマンドを変更できます。

コマンド

最初のコマンドを作成する方法については、 コマンドオブジェクト の章を参照してください。次に、コマンドについて詳しく説明します。

コマンドオブジェクト

コマンドの入力と出力

オプションパーサー

cron ジョブに登録してシェルを実行する

CakePHPが提供するコマンド

キャッシュツール

Completion ツール

I18N ツール

Plugin シェル

スキーマキャッシュツール

Routes ツール

Server ツール

インタラクティブ・コンソール (REPL)

シェル

コンソール環境におけるルーティング

コマンドラインインターフェイス(CLI)、特にシェルとタスクでは、 env('HTTP_HOST') およびその他のWebブラウザー固有の環境変数が設定されていません。

Router::url() を使ってレポートを生成したり、メールを送信したりする場合、デフォルトホスト http://localhost/ が含まれるため、結果として無効なURLになってしまいます。こういったケースでは、ドメインを手動で設定する必要があります。たとえば、ブートストラップまたは Config の App.fullBaseUrl を使って設定できます。

メールを送信する場合は、メールを送信する際のホストを Email クラスで指定する必要があります:

use Cake\Mailer\Email;

$email = new Email();
$email->setDomain('www.example.org');

これにより生成されたメッセージIDはは有効で、メール送信元のドメインに適合していることが保証されます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コマンドオブジェクト

class Cake\Console\Command

CakePHP には、開発のスピードアップと日常的なタスクの自動化を目的とした多数の組み込みコマンドが用意されています。これらの同じライブラリを使用して、アプリケーションとプラグイン用のコマンドを作成することができます。

コマンドの作成

最初のコマンドを作ってみましょう。この例では、単純な Hello world コマンドを作成します。アプリケーションの src/Command ディレクトリの中で、 HelloCommand.php を作成してください。その中に次のコードを書いてください。

<?php
namespace App\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;

class HelloCommand extends Command
{
    public function execute(Arguments $args, ConsoleIo $io)
    {
        $io->out('Hello world.');
    }
}

上記のように、Command クラスでは、さまざまな処理まとめて行うための execute() メソッドを実装する必要があります。コマンドが呼び出されたときに、このメソッドが呼び出されます。それでは、このコマンド実行してみましょう。

bin/cake hello

次の出力が表示されます。

Hello world.

無事に表示されましたか？次に、パラメータを与えられるようにしてみます。 buildOptionParser() を使用します。

<?php
namespace App\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;

class HelloCommand extends Command
{
    protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
    {
        $parser->addArgument('name', [
            'help' => 'What is your name'
        ]);
        return $parser;
    }

    public function execute(Arguments $args, ConsoleIo $io)
    {
        $name = $args->getArgument('name');
        $io->out("Hello {$name}.");
    }
}

このファイルを保存した後、次のようにコマンドを実行できます。

bin/cake hello jillian

# 出力結果
Hello jillian

デフォルトのコマンド名の変更

コマンド名は原則的にCakePHPの規約に則ったものになります。このコマンド名を上書きする場合は、コマンドの defaultName() メソッドを用います。:

public static function defaultName(): string
{
    return 'oh_hi';
}

上記のように書くことで、 HelloCommand コマンドは cake hello ではなく cake oh_hi として実行できるようになります。

引数やオプションの定義

すでに說明したように、 buildOptionParser() フックメソッドを使って引数を定義することができます。また、オプションも定義できます。 たとえば、 HelloCommand に yell オプションを追加することができます。

// ...
protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
    $parser
        ->addArgument('name', [
            'help' => 'What is your name'
        ])
        ->addOption('yell', [
            'help' => 'Shout the name',
            'boolean' => true
        ]);

    return $parser;
}

public function execute(Arguments $args, ConsoleIo $io)
{
    $name = $args->getArgument('name');
    if ($args->getOption('yell')) {
        $name = mb_strtoupper($name);
    }
    $io->out("Hello {$name}.");
}

詳しくは、 オプションパーサー をご覧ください。

ファイルに出力

コマンドは、実行されると ConsoleIo インスタンスが提供されます。このオブジェクトは stdout 、 stderr とのハンドシェイクによりファイルを生成することができます。詳しくは、 コマンドの入力と出力 セクションをご覧ください。

コマンド内でのモデルの使用

コンソールコマンドでアプリケーションのビジネスロジックにアクセスしたいケースが、しばしばあると思います。コントローラと同じように、コマンドでは LocatorAwareTrait を通じてモデルをロードできます。そのために、 $this->fetchTable() を使用します。:

<?php
declare(strict_types=1);

namespace App\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;

class UserCommand extends Command
{
    // デフォルトのテーブルを定義します。これにより、引数なしで  `fetchTable()` を使用できます。
    protected $defaultTable = 'Users';

    protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
    {
        $parser
            ->addArgument('name', [
                'help' => 'What is your name'
            ]);

        return $parser;
    }

    public function execute(Arguments $args, ConsoleIo $io): ?int
    {
        $name = $args->getArgument('name');
        $user = $this->fetchTable()->findByUsername($name)->first();

        $io->out(print_r($user, true));

        return null;
    }
}

上記のコマンドは、ユーザー名でユーザを取得し、データベースに格納されている情報を表示します。

終了コードと実行停止

コマンドが回復不能なエラーに遭遇したら、 abort() メソッドを使って実行を終了することができます。

// ...
public function execute(Arguments $args, ConsoleIo $io)
{
    $name = $args->getArgument('name');
    if (strlen($name) < 5) {
        // 実行を停止し、標準エラーに出力し、終了コードを 1 に設定
        $io->error('Name must be at least 4 characters long.');
        $this->abort();
    }
}

$io->abort() の引数を使用して、任意のメッセージと終了コードを渡すこともできます:

public function execute(Arguments $args, ConsoleIo $io)
{
    $name = $args->getArgument('name');
    if (strlen($name) < 5) {
        // 実行を停止しstderrに出力し、終了コードを99に設定します
        $io->abort('名前は4文字以上にする必要があります。', 99);
    }
}

Tip

終了コードの 64 から 78 は避けてください。それらは sysexits.h で記述された特定の意味を持っています。また、127 以上も避けてください。それらは、 SIGKILL や SIGSEGV のようなシグナルによるプロセスの終了を示すために使用されるからです。

OS既存の終了コードの詳細については、Unixシステム の sysexit マニュアルページ (man sysexits)、または Windows の System Error Codes ヘルプページを参照してください。

他のコマンドの呼び出し

コマンド内から他のコマンドを呼び出す必要がある場合があります。そのために executeCommand() を用いることができます。:

// コマンドのオプションと引数は配列で渡します。
$this->executeCommand(OtherCommand::class, ['--verbose', 'deploy']);

// コンストラクターに引数がある場合はインスタンスを生成して渡します。
$command = new OtherCommand($otherArgs);
$this->executeCommand($command, ['--verbose', 'deploy']);

コマンド説明文の設定

以下のようにコマンドの説明文を設定することができます。:

class UserCommand extends Command
{
    public static function getDescription(): string
    {
        return 'カスタムの説明文';
    }
}

これにより、Cake CLIに説明文が表示されます。:

bin/cake

App:
  - user
  └─── カスタムの説明文

コマンドのヘルプセクションと同様です。:

cake user --help
カスタムの説明文

Usage:
cake user [-h] [-q] [-v]

コマンドのテスト

コンソールアプリケーションをより簡単にテストするため、CakePHP には、コンソールアプリケーションをテストし、結果をアサートするための ConsoleIntegrationTestTrait トレイトが備わっています。このトレイトは、コマンドを実行するために使用する exec() メソッドが定義されており、このメソッドに、 CLI で使用するのと同じ文字列を渡すことができます。

src/Command/UpdateTableCommand.php に置かれた、とてもシンプルなシェルで始めましょう。

namespace App\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;

class UpdateTableCommand extends Command
{
    protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
    {
        $parser->setDescription('My cool console app');

        return $parser;
    }
}

このシェルの統合テストを書くために、 tests/TestCase/Command/UpdateTableTest.php に Cake\TestSuite\ConsoleIntegrationTestTrait を使用したテストケースを作成します。このシェルの説明が stdout に出力されることをテストしましょう。

namespace App\Test\TestCase\Command;

use Cake\TestSuite\ConsoleIntegrationTestTrait;
use Cake\TestSuite\TestCase;

class UpdateTableCommandTest extends TestCase
{
    use ConsoleIntegrationTestTrait;

    public function setUp(): void
    {
        parent::setUp();
        $this->useCommandRunner();
    }

    public function testDescriptionOutput()
    {
        $this->exec('update_table --help');
        $this->assertOutputContains('My cool console app');
    }
}

テストが成立しました。これは簡単な例ですが、コンソールアプリケーションの統合テストケースを作成することは基本的に簡単です。このコマンドにさらに多くのロジックを追加してみましょう。

namespace App\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\I18n\FrozenTime;

class UpdateTableCommand extends Command
{
    protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
    {
        $parser
            ->setDescription('My cool console app')
            ->addArgument('table', [
                'help' => 'Table to update',
                'required' => true
            ]);

        return $parser;
    }

    public function execute(Arguments $args, ConsoleIo $io)
    {
        $table = $args->getArgument('table');
        $this->fetchTable($table)->query()
            ->update()
            ->set([
                'modified' => new FrozenTime()
            ])
            ->execute();
    }
}

これはオプションと関連するロジックを必要とする、より完成度が高いシェルです。テストケースを次のコードスニペットに変更してみましょう。

namespace Cake\Test\TestCase\Command;

use Cake\Command\Command;
use Cake\I18n\FrozenTime;
use Cake\TestSuite\ConsoleIntegrationTestTrait;
use Cake\TestSuite\TestCase;

class UpdateTableCommandTest extends TestCase
{
    use ConsoleIntegrationTestTrait;

    protected $fixtures = [
        // assumes you have a UsersFixture
        'app.Users'
    ];

    public function testDescriptionOutput()
    {
        $this->exec('update_table --help');
        $this->assertOutputContains('My cool console app');
    }

    public function testUpdateModified()
    {
        $now = new FrozenTime('2021-12-12 00:00:00');
        FrozenTime::setTestNow($now);

        $this->loadFixtures('Users');

        $this->exec('update_table Users');
        $this->assertExitCode(Command::CODE_SUCCESS);

        $user = $this->getTableLocator()->get('Users')->get(1);
        $this->assertSame($user->modified->timestamp, $now->timestamp);

        FrozenTime::setTestNow(null);
    }
}

testUpdateModified() メソッドを見ると分かると思いますが、コマンドが最初の引数として渡すテーブルを更新することをテストします。まず最初に、コマンドが適切なステータスコード 0 で終了したことを assertExitCode() によってアサートします。

次に、このコマンドが意図どおりに動作したことを確認します。つまり、 modified カラムが現在の時刻に更新されたことを assertSame() で確認します。

ちなみに、 exec() はCLIに入力したのと同じ文字列を使用するため、コマンド文字列としてオプションと引数を含めることができます。

対話的なシェルのテスト

コンソールは対話的に用いることも多いインターフェイスです。 Cake\TestSuite\ConsoleIntegrationTestTrait トレイトで対話的なシェルをテストするには、期待する入力を exec() の２番目のパラメーターとして渡すだけです。それらは、期待どおりの順序で配列として含める必要があります。

引き続き、対話的な確認フローを追加してみましょう。テスト元のコマンドクラスを次のように更新します。

namespace App\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\I18n\FrozenTime;

class UpdateTableCommand extends Command
{
    protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
    {
        $parser
            ->setDescription('My cool console app')
            ->addArgument('table', [
                'help' => 'Table to update',
                'required' => true
            ]);

        return $parser;
    }

    public function execute(Arguments $args, ConsoleIo $io)
    {
        $table = $args->getArgument('table');
        if ($io->ask('Are you sure?', 'n', ['y', 'n']) === 'n') {
            $io->error('You need to be sure.');
            $this->abort();
        }
        $this->fetchTable($table)->query()
            ->update()
            ->set([
                'modified' => new FrozenTime()
            ])
            ->execute();
    }
}

対話的なサブコマンドができました。次に、適切な応答を受け取るかどうかをテストするテストケースと、誤った応答を受け取るかどうかをテストするケースを追加しましょう。 testUpdateModified メソッドを削除し、 tests/TestCase/Command/UpdateTableCommandTest.php に以下のメソッドを追加してください。

public function testUpdateModifiedSure()
{
    $now = new FrozenTime('2017-01-01 00:00:00');
    FrozenTime::setTestNow($now);

    $this->loadFixtures('Users');

    $this->exec('update_table Users', ['y']);
    $this->assertExitCode(Command::CODE_SUCCESS);

    $user = $this->getTableLocator()->get('Users')->get(1);
    $this->assertSame($user->modified->timestamp, $now->timestamp);

    FrozenTime::setTestNow(null);
}

public function testUpdateModifiedUnsure()
{
    $user = $this->getTableLocator()->get('Users')->get(1);
    $original = $user->modified->timestamp;

    $this->exec('my_console best_framework', ['n']);
    $this->assertExitCode(Command::CODE_ERROR);
    $this->assertErrorContains('You need to be sure.');

    $user = $this->getTableLocator()->get('Users')->get(1);
    $this->assertSame($original, $user->timestamp);
}

最初のテストケースでは、質問を確認し、レコードが更新されます。 2番目のテストでは確認していませんし、レコードが更新されていないので、エラーメッセージが stderr に書き込まれていることを確認できます。

CommandRunner のテスト

CommandRunner クラスを使ってディスパッチされたシェルをテストするには、次のメソッドを使ってテストケースでそれを有効にしてください。

$this->useCommandRunner();

アサーションメソッド

Cake\TestSuite\ConsoleIntegrationTestTrait トレイトは、コンソールの出力に対して容易にアサートできるようにするいくつかのアサーションメソッドを提供します。

// シェルがsuccessステータスで終了したことをアサート
$this->assertExitSuccess();

// シェルがerrorステータスで終了したことをアサート
$this->assertExitError();

// シェルが期待したコードで終了したことをアサート
$this->assertExitCode($expected);

// 標準出力が文字列を含むことをアサート
$this->assertOutputContains($expected);

// 標準エラーが文字列を含むことをアサート
$this->assertErrorContains($expected);

// 標準出力を正規表現にマッチするかをアサート
$this->assertOutputRegExp($expected);

// 標準エラーが正規表現にマッチするかをアサート
$this->assertErrorRegExp($expected);

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コマンドの入力と出力

class Cake\Console\ConsoleIo

CakePHP は ConsoleIo オブジェクトをコマンドに提供するので、ユーザー入力と出力情報を対話的にユーザーに読み取らせることができます。

コマンドヘルパー

コマンドヘルパーは、コマンド、シェルまたはタスクからアクセスして使用できます。

// 表としてデータを出力
$io->helper('Table')->output($data);

// プラグインからヘルパーを取得
$io->helper('Plugin.HelperName')->output($data);

また、ヘルパーのインスタンスを取得し、それらのパブリックメソッドを呼び出すこともできます。

// Progress ヘルパーを取得して利用
$progress = $io->helper('Progress');
$progress->increment(10);
$progress->draw();

ヘルパーの作成

CakePHPにはいくつかのコマンドヘルパーが付属していますが、アプリケーションやプラグインでもっと多くのコマンドヘルパーを作成できます。例として、装飾的な見出しを生成するための簡単なヘルパーを作成してみましょう。まず src/Command/Helper/HeadingHelper.php を作成し、以下を記述してください。

<?php
namespace App\Command\Helper;

use Cake\Console\Helper;

class HeadingHelper extends Helper
{
    public function output($args)
    {
        $args += ['', '#', 3];
        $marker = str_repeat($args[1], $args[2]);
        $this->_io->out($marker . ' ' . $args[0] . ' ' . $marker);
    }
}

この新しいヘルパーは、シェルコマンドの中で呼び出すことで使用できます。

// 両側に ### を付加
$this->helper('Heading')->output(['It works!']);

// 両側に ~~~~ を付加
$this->helper('Heading')->output(['It works!', '~', 4]);

ヘルパーは一般的に、配列のパラメーターを受けとる output() メソッドを実装しています。しかし、コンソールヘルパーは任意の形式の引数を受け取る追加のメソッドを実装できる普通のクラスです。

注釈

下位互換のため、ヘルパーは、 src/Shell/Helper にも配置できます。

組み込みヘルパー

Table ヘルパー

TableHelper は、成形されたアスキーアートの表の作成を支援します。使い方はとてもシンプルです。

$data = [
    ['Header 1', 'Header', 'Long Header'],
    ['short', 'Longish thing', 'short'],
    ['Longer thing', 'short', 'Longest Value'],
];
$io->helper('Table')->output($data);

// 出力結果
+--------------+---------------+---------------+
| Header 1     | Header        | Long Header   |
+--------------+---------------+---------------+
| short        | Longish thing | short         |
| Longer thing | short         | Longest Value |
+--------------+---------------+---------------+

Progress ヘルパー

ProgressHelper は２つの異なる方法で利用されます。シンプルなモードは、処理が完了するまでに呼び出されるコールバックを渡すことができます。

$io->helper('Progress')->output(['callback' => function ($progress) {
    // ここで作業します
    $progress->increment(20);
    $progress->draw();
}]);

追加のオプションを渡すことで、プログレスバーの制御ができます。

total プログレスバーの全アイテム数。デフォルトは 100 です。

width プログレスバーの幅。デフォルトは 80 です。

callback プログレスバーを更新するループ中で呼ばれるコールバック。

全てのオプションを使用した例です。

$io->helper('Progress')->output([
    'total' => 10,
    'width' => 20,
    'callback' => function ($progress) {
        $progress->increment(2);
        $progress->draw();
    }
]);

Progress ヘルパーは、必要であればプログレスバーの増加や再描画を手動で行うことができます。

$progress = $io->helper('Progress');
$progress->init([
    'total' => 10,
    'width' => 20,
]);

$progress->increment(4);
$progress->draw();

ユーザー入力の取得

Cake\Console\ConsoleIo::ask($question, $choices = null, $default = null)

対話的なコンソールアプリケーションを構築する際には、ユーザー入力を取得する必要があります。 CakePHP は、このための簡単な方法を提供します。

// ユーザーから任意のテキストを取得
$color = $io->ask('What color do you like?');

// ユーザーの選択を取得
$selection = $io->askChoice('Red or Green?', ['R', 'G'], 'R');

選択のバリデーションは大文字と小文字を区別しません。

ファイルの作成

Cake\Console\ConsoleIo::createFile($path, $contents)

ファイルを作成することは、多くの場合、開発とデプロイの自動化に役立つ多くのコンソールコマンドの重要な部分です。 createFile() メソッドは、対話的な確認でファイルを作成するためのシンプルなインターフェイスを提供します。

// 上書きの確認を含むファイルを作成します
$io->createFile('bower.json', $stuff);

// 尋ねることなく強制的に上書きします
$io->createFile('bower.json', $stuff, true);

出力の作成

stdout や stderr への書き込みは、CakePHP が簡単にできる別のルーチン操作です。

// 標準出力に出力
$io->out('Normal message');

// 標準エラーに出力
$io->err('Error message');

通常の出力メソッドに加え、CakePHP は適切な ANSI カラーで出力をスタイルするラッパーメソッドを提供します。

// 標準出力に緑色テキスト
$io->success('Success message');

// 標準出力に水色テキスト
$io->info('Informational text');

// 標準出力に青色テキスト
$io->comment('Additional context');

// 標準エラーに赤色テキスト
$io->error('Error text');

// 照準エラーに黄色テキスト
$io->warning('Warning text');

また、出力レベルに関する2つの便利なメソッドを提供します。

// 詳細出力が有効の時のみ (-v)
$io->verbose('Verbose message');

// すべてのレベルで表示
$io->quiet('Quiet message');

シェルはまた、画面のクリア、空白行の作成、または横棒線を描くためのメソッドを含みます。

// ２行の改行を出力
$io->out($io->nl(2));

// 横棒線を描画
$io->hr();

最後に、画面上の現在のテキスト行を更新することができます。

$io->out('Counting down');
$io->out('10', 0);
for ($i = 9; $i > 0; $i--) {
    sleep(1);
    $io->overwrite($i, 0, 2);
}

注釈

新しい行が出力されたら、テキストを上書きすることができないことに注意してください。

出力のレベル

コンソールアプリケーションには、詳細なレベルの出力が必要なことがよくあります。たとえば、cron ジョブとして実行する場合、ほとんどの出力は不要です。出力レベルを使用して、出力に適切なフラグを付けることができます。シェルの利用者は、コマンドを呼び出すときに正しいフラグを設定することで、関心のあるレベルを決定することができます。次の3つのレベルがあります。

QUIET - 必須のメッセージであり、静かな（必要最小限の）出力モードでも表示。

NORMAL - 通常利用におけるデフォルトのレベル。

VERBOSE - 毎日利用には冗長すぎるメッセージを表示、しかしデバッグ時には有用。

以下のように出力を指定できます。

// すべてのレベルで表示されます。
$io->out('Quiet message', 1, ConsoleIo::QUIET);
$io->quiet('Quiet message');

// QUIET 出力時には表示されません。
$io->out('normal message', 1, ConsoleIo::NORMAL);
$io->out('loud message', 1, ConsoleIo::VERBOSE);
$io->verbose('Verbose output');

// VERBOSE 出力時のみ表示されます。
$io->out('extra message', 1, ConsoleIo::VERBOSE);
$io->verbose('Verbose output');

シェルの実行時に --quiet や --verbose を使うことで出力を制御できます。これらのオプションはデフォルトで組み込まれていて、いつでも CakePHP コマンド内部の出力レベルを制御できるように考慮されています。

また、 --quiet と --verbose オプションは、ログデータの標準出力/標準エラーへの出力方法を制御します。通常の情報とそれ以上のレベルのログメッセージは標準出力/標準エラーに出力されます。 --verbose を使用する場合は、デバッグログは標準出力に出力されます。 --quiet を使用する場合は、警告とそれ以上のレベルのログメッセージのみ標準エラーに出力されます。

出力のスタイル

ちょうど HTML のようなタグを埋め込むことで、出力のスタイルを変更することができます。 ConsoleOutput はこれらのタグを正しい ansi コードシーケンスに変換したり、ansi コードをサポートしないコンソールではタグを除去します。スタイルはいくつかビルトインされたものがありますが、自分で作成することも 可能です。ビルトインされたものは以下の通りです。

success 成功メッセージ。緑色のテキスト。

error エラーメッセージ。赤色のテキスト。

warning 警告メッセージ。黄色のテキスト。

info 情報メッセージ。水色のテキスト。

comment 追加情報。青色のテキスト。

question 質問事項。シェルが自動的に追加する。

$io->styles() を使ってさらに多くのスタイルを追加できます。新しいスタイルを追加するには以下のようにします。

$io->styles('flashy', ['text' => 'magenta', 'blink' => true]);

これで <flashy> というタグが有効になり、ansi カラーが有効な端末であれば、 $this->out('<flashy>うわ！</flashy> 何か変になった'); の場合の表示は色がマゼンタでブリンクになります。スタイルを定義する際は text と background 属性として以下の色が指定できます。

black

blue

cyan

green

magenta

red

white

yellow

さらに以下のオプションをブール型のスイッチとして指定できます。 値が真の場合に有効になります。

blink

bold

reverse

underline

スタイルを追加すると ConsoleOutput のすべてのインスタンスでも有効になります。ですので stdout と stderr 両方のオブジェクトでこれらを再定義する必要はありません。

カラー表示の無効化

カラー表示はなかなか綺麗ですが、オフにしたい場合や強制的にオンにしたい場合もあるでしょう。

$io->outputAs(ConsoleOutput::RAW);

これは RAW（生の）出力モードにします。 RAW 出力モードではすべてのスタイルが無効になります。モードには３種類あります。

ConsoleOutput::COLOR - カラーエスケープコードを出力します。

ConsoleOutput::PLAIN - プレーンテキスト出力。既知のスタイルタグが出力から取り除かれます。

ConsoleOutput::RAW - RAW 出力、スタイルや書式設定は行われない。これは XML を出力する場合や、スタイルのデバッグを行う際に役立ちます。

デフォルトでは *nix システムにおける ConsoleOutput のデフォルトはカラー出力モードです。 Windows では ANSICON 環境変数がセットされている場合を除き、プレーンテキストモードがデフォルトです。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

オプションパーサー

class Cake\Console\ConsoleOptionParser

コンソールアプリケーションは通常、端末からコマンドに情報を得るための主要な手段としてオプションと引数を受け取ります。

OptionParser の定義

コマンドとシェルは buildOptionParser($parser) フックメソッドを提供します。このメソッドを使用して、コマンドのオプションと引数を定義できます。

protected function buildOptionParser($parser)
{
    // オプションと引数を定義

    // 完成したパーサーを返します
    return $parser;
}

シェルクラスは getOptionParser() フックメソッドを使ってオプションパーサーを定義します。

public function getOptionParser()
{
    // フレームワークから空のパーサーを取得
    $parser = parent::getOptionParser();

    // オプションと引数を定義

    // 完成したパーサーを返します
    return $parser;
}

引数の使用

Cake\Console\ConsoleOptionParser::addArgument($name, $params = [])

コマンドラインツールにおいて、位置引数 (指定順序が意味を持つ引数) はよく使われます。 ConsoleOptionParser では位置引数を要求するだけでなく定義することもできます。指定する際は $parser->addArgument(); で一度にひとつずつ設定するか、 $parser->addArguments(); で複数個を同時に指定するかを選べます。

$parser->addArgument('model', ['help' => 'bake するモデル']);

引数を作成する際は、以下のオプションが指定できます。

help この引数に対して表示するヘルプ。

required この引数が必須かどうか。

index 引数のインデックス。設定されない場合は引数リストの末尾に位置づけられます。同じインデックスを２回指定すると、最初に指定したオプションは上書きされます。

choices この引数について有効な選択肢。指定しない場合はすべての値が有効となります。 parse() が無効な値を検出すると、例外が発生します。

必須であると指定された引数が省略された場合、コマンドのパースにおいて例外が発生します。これにより、引数のチェックをシェルの中で行う必要がなくなります。

複数の引数の追加

Cake\Console\ConsoleOptionParser::addArguments(array $args)

複数の引数を１個の配列で持つ場合、 $parser->addArguments() により一度に複数の引数を追加できます。

$parser->addArguments([
    'node' => ['help' => 'The node to create', 'required' => true],
    'parent' => ['help' => 'The parent node', 'required' => true]
]);

ConsoleOptionParser 上のすべてのビルダーメソッドと同様に、 addArguments も強力なメソッドチェーンの一部として使えます。

引数の検証

位置引数を作成する場合、 required フラグを使用して、シェルが呼び出されたときに引数が存在しなければならないことを示すことができます。さらに choices を使うことで、その引数が取りうる有効な値の選択肢を制限できます。

$parser->addArgument('type', [
    'help' => 'これとやり取りするノードの型。',
    'required' => true,
    'choices' => ['aro', 'aco']
]);

この例では、必須でかつ入力時に値の正当性チェックが行われるような引数を作成します。引数が指定されないか、または無効な値が指定された場合は例外が発生してシェルが停止します。

オプションの利用

Cake\Console\ConsoleOptionParser::addOption($name, $options = [])

オプションまたはフラグは、コマンドラインツールで使用され、コマンドの順序付けられていないキーと値の引数を提供します。オプションは、長い名前と短い別名の両方を定義できます。値を受け取ったり (例えば --connection=default)、ブール値のオプション (-verbose など) を使うことができます。オプションは、 addOption() メソッドで定義されます。

$parser->addOption('connection', [
    'short' => 'c',
    'help' => 'connection',
    'default' => 'default',
]);

この例の場合、シェルを起動する際に cake myshell --connection=other, cake myshell --connection other, cake myshell -c other のいずれかで引数を指定できます。

またブール型のスイッチも作れますが、これらのスイッチは値を消費せず、またその存在はパースされた引数の中だけとなります。

$parser->addOption('no-commit', ['boolean' => true]);

このオプション指定の場合、 cake myshell --no-commit something のようにコールされると no-commit 引数が true になり、'something' は位置引数と見なされます。

オプションを作成する場合、オプションの振る舞いを定義するのに以下が指定できます。

short - このオプションを表す１文字の別名。未定義の場合はなしになります。

help - このオプションのヘルプ文字列。オプションのヘルプを生成する際に参照されます。

default - このオプションのデフォルト値。未定義の場合、デフォルト値は true となります。

boolean - 値を持たない単なるブール型のスイッチ。デフォルト値は false です。

choices - このオプションで取りうる有効な選択肢。指定しない場合はすべての値が有効となります。 parse() が無効な値を検出すると、例外が発生します。

複数オプションの追加

Cake\Console\ConsoleOptionParser::addOptions(array $options)

複数の引数を１個の配列で持つ場合、 $parser->addOptions() により一度に複数のオプションを追加できます。

$parser->addOptions([
    'node' => ['short' => 'n', 'help' => 'The node to create'],
    'parent' => ['short' => 'p', 'help' => 'The parent node']
]);

ConsoleOptionParser 上のビルダーメソッドと同様に、addOptions も強力なメソッドチェーンの一部として使えます。

オプション値は、 $this->params 配列に格納されます。また、存在しないオプションにアクセスした時のエラーを回避するために便利なメソッド $this->param() を使用することができます。

オプションの検証

オプションでは位置引数と同様に、値の選択肢を指定できます。オプションに choices が指定されている場合、それらがそのオプションで取りうる有効な値です。これ以外の値が指定されると InvalidArgumentException が発生します。

$parser->addOption('accept', [
    'help' => 'What version to accept.',
    'choices' => ['working', 'theirs', 'mine']
]);

ブール型オプションの使用

フラグのオプションを作りたい場合、オプションをブール型として指定できます。デフォルト値を持つオプションのように、ブール型のオプションもパース済み引数の中に常に自分自身を含んでいます。フラグが存在する場合それらは true にセットされ、存在しない場合は false になります。

$parser->addOption('verbose', [
    'help' => 'Enable verbose output.',
    'boolean' => true
]);

次のオプションは、解析されたパラメータに常に値を持ちます。その値が含まれていない場合、デフォルト値は false になり、定義されていれば true になります。

配列から ConsoleOptionParser の構築

Cake\Console\ConsoleOptionParser::buildFromArray($spec)

前述のように、サブコマンドのオプションパーサーを作成する際は、そのメソッドに対するパーサーの仕様を配列として定義できます。これによりすべてが配列として扱えるので、サブコマンドパーサーの構築が容易になります。

$parser->addSubcommand('check', [
    'help' => __('Check the permissions between an ACO and ARO.'),
    'parser' => [
        'description' => [
            __("Use this command to grant ACL permissions. Once executed, the "),
            __("ARO specified (and its children, if any) will have ALLOW access "),
            __("to the specified ACO action (and the ACO's children, if any).")
        ],
        'arguments' => [
            'aro' => ['help' => __('ARO to check.'), 'required' => true],
            'aco' => ['help' => __('ACO to check.'), 'required' => true],
            'action' => ['help' => __('Action to check')]
        ]
    ]
]);

パーサーの仕様の中では arguments, options, description そして epilog のためのキーを定義できます。配列形式ビルダーの内部には subcommands は定義できません。引数とオプションの値は、 Cake\Console\ConsoleOptionParser::addArguments() や Cake\Console\ConsoleOptionParser::addOptions() が利用する書式に従ってください。 buildFromArray を単独で使ってオプションパーサーを構築することも可能です。

public function getOptionParser()
{
    return ConsoleOptionParser::buildFromArray([
        'description' => [
            __("Use this command to grant ACL permissions. Once executed, the "),
            __("ARO specified (and its children, if any) will have ALLOW access "),
            __("to the specified ACO action (and the ACO's children, if any).")
        ],
        'arguments' => [
            'aro' => ['help' => __('ARO to check.'), 'required' => true],
            'aco' => ['help' => __('ACO to check.'), 'required' => true],
            'action' => ['help' => __('Action to check')]
        ]
    ]);
}

オプションパーサーのマージ

Cake\Console\ConsoleOptionParser::merge($spec)

group コマンドを構築する場合、おそらく、いくつかのパーサーを組み合わせたいでしょう。

$parser->merge($anotherParser);

各パーサーの引数の順序が同じでなければならないこと、およびオプションは、動作するために互換性がなければならないことに注意してください。ですので、別のキーを使用しないでください。

シェルからヘルプを取得

オプションパーサーでオプションと引数を定義することで、CakePHP は基本的なヘルプ情報を自動的に生成し、それぞれのコマンドに --help と -h を追加することができます。これらのオプションのいずれかを使用すると、生成されたヘルプの内容を見ることができます。

bin/cake bake --help
bin/cake bake -h

このいずれでも bake のヘルプを生成します。ネストされたコマンドのヘルプを表示することもできます。

bin/cake bake model --help
bin/cake bake model -h

これは bake の model コマンドに関するヘルプを表示します。

ヘルプを XML で取得

自動ツールや開発ツールをビルドするのに CakePHP のシェルとの対話処理を必要とする場合、ヘルプを機械がパースできる形式で取得できると便利です。 ConsoleOptionParser に以下の引数を追加することで、ヘルプを xml で出力できます。

cake bake --help xml
cake bake -h xml

この例は生成されたヘルプ、オプション、引数そして選択されたシェルのサブコマンドに関するドキュメントを XML で返します。XML ドキュメントの例としては以下のようになります。

<?xml version="1.0"?>
<shell>
    <command>bake fixture</command>
    <description>Generate fixtures for use with the test suite. You can use
        `bake fixture all` to bake all fixtures.</description>
    <epilog>
        Omitting all arguments and options will enter into an interactive
        mode.
    </epilog>
    <options>
        <option name="--help" short="-h" boolean="1">
            <default/>
            <choices/>
        </option>
        <option name="--verbose" short="-v" boolean="1">
            <default/>
            <choices/>
        </option>
        <option name="--quiet" short="-q" boolean="1">
            <default/>
            <choices/>
        </option>
        <option name="--count" short="-n" boolean="">
            <default>10</default>
            <choices/>
        </option>
        <option name="--connection" short="-c" boolean="">
            <default>default</default>
            <choices/>
        </option>
        <option name="--plugin" short="-p" boolean="">
            <default/>
            <choices/>
        </option>
        <option name="--records" short="-r" boolean="1">
            <default/>
            <choices/>
        </option>
    </options>
    <arguments>
        <argument name="name" help="Name of the fixture to bake.
            Can use Plugin.name to bake plugin fixtures." required="">
            <choices/>
        </argument>
    </arguments>
</shell>

ヘルプの出力をカスタマイズ

説明文とエピローグを追加することで、生成されたヘルプの内容をさらに充実させることができます。

説明文の設定

Cake\Console\ConsoleOptionParser::setDescription($text)

オプションパーサーの説明文を取得または設定します。説明文は引数やオプションの上に表示されます。配列または文字列を渡すことで説明文の値を設定できます。引数がない場合は現在の値を返します。

// 一度に複数行を設定
$parser->setDescription(['１行目', '２行目']);
// 3.4 より前
$parser->description(['１行目', '２行目']);

// 現在の値を取得する
$parser->getDescription();

エピローグの設定

Cake\Console\ConsoleOptionParser::setEpilog($text)

オプションパーサーのエピローグを取得または設定します。エピローグは、引数とオプションの情報の後に表示されます。配列または文字列を渡すことで、エピローグの値を設定することができます。引数がない場合は現在の値を返します。

// 一度に複数行を設定
$parser->setEpilog(['１行目', '２行目']);
// 3.4 より前
$parser->epilog(['１行目', '２行目']);

// 現在の値を取得する
$parser->getEpilog();

サブコマンドの追加

Cake\Console\ConsoleOptionParser::addSubcommand($name, $options = [])

コンソールアプリケーションはサブコマンドから構成されることも多いのですが、サブコマンド側で特別なオプション解析や独自ヘルプを持ちたいこともあります。この完全な例が bake です。 Bake は多くの別々のタスクから構成されますが、各タスクはそれぞれ独自のヘルプとオプションを持っています。 ConsoleOptionParser を使ってサブコマンドを定義し、それらに固有のオプションパーサーを提供できるので、シェルはそれぞれのタスクについてコマンドをどう解析すればよいのかを知ることができます。

$parser->addSubcommand('model', [
    'help' => 'Bake a model',
    'parser' => $this->Model->getOptionParser()
]);

上の例では、シェルのタスクに対してヘルプやそれに特化したオプションパーサーの提供方法を示しています。タスクの getOptionParser() を呼ぶことで、オプションパーサーの複製をしたり、シェル内の関係を調整する必要がなくなります。この方法でサブコマンドを追加することには２つの利点があります。まず生成されたヘルプの中で簡単にサブコマンドを文書化できること、そしてサブコマンドのヘルプに簡単にアクセスできることです。前述のやり方で生成したサブコマンドを使って cake myshell --help とやると、サブコマンドの一覧が出ます。また cake myshell model --help とやると、model タスクだけのヘルプが表示されます。

注釈

シェルはサブコマンドを定義すると、すべてのサブコマンドは、明示的に定義する必要があります。

サブコマンドを定義する際は、以下のオプションが使えます。

help - サブコマンドのヘルプテキスト。

parser - サブコマンドの ConsoleOptionParser。これによりメソッド固有のオプションパーサーを生成します。サブコマンドに関するヘルプが生成される際、もしパーサーが存在すればそれが使われます。 Cake\Console\ConsoleOptionParser::buildFromArray() と互換性のある配列としてパーサーを指定することができます。

サブコマンドの追加は、強力なメソッドチェーンの一部として使えます。

バージョン 3.5.0 で変更: 複数語のサブコマンドを追加する際、キャメルバック (camelBacked) 形式に加えてスネークケース (snake_cake) を使ってこれらのコマンドを呼び出すことができます。

バージョン 3.6.0 で非推奨: サブコマンドは非推奨です。代わりに ネストされたコマンドを使用してください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

cron ジョブに登録してシェルを実行する

通常シェルは、ニュースレターを送ったり、たまにデータベースをクリーンアップしたりすることを、 cron ジョブとして実行します。

このように簡単な設定で行えます。

  */5  *    *    *    *  cd /full/path/to/root && bin/cake myshell myparam
# *    *    *    *    *  実行するコマンド
# │    │    │    │    │
# │    │    │    │    │
# │    │    │    │    \───── 曜日 (0 - 6) (0 から 6 が日曜日から土曜日、
# |    |    |    |           もしくは曜日名)
# │    │    │    \────────── 月 (1 - 12)
# │    │    \─────────────── 日 (1 - 31)
# │    \──────────────────── 時 (0 - 23)
# \───────────────────────── 分 (0 - 59)

詳しくはこちら: https://ja.wikipedia.org/wiki/Crontab

Tip

cron ジョブで画面出力を非表示にするために -q (または --quiet) を使用してください。

共有ホスティング上の cron ジョブ

いくつかの共有ホスティング上で cd /full/path/to/root && bin/cake mycommand myparam は動作しません。代わりに php /full/path/to/root/bin/cake.php mycommand myparam が使用できます。

注釈

php.ini の中で register_argc_argv = 1 を含めることによって、 register_argc_argv を有効にしなければなりません。グローバルに register_argc_argv を変更できない場合、 -d register_argc_argv=1 パラメーターをつけることで、 cron ジョブに独自の設定ファイル (php.ini) を指定することができます。例: php -d register_argc_argv=1 /full/path/to/root/bin/cake.php myshell myparam

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

キャッシュツール

CLI 環境でキャッシュデータをよりよく管理するために、コンソールコマンドで、あなたのアプリケーションが持つキャッシュデータの消去ができます。

// 特定の設定のキャッシュをクリア
bin/cake cache clear <configname>

// すべての設定のキャッシュをクリア
bin/cake cache clear_all

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Completion ツール

コンソールで作業する開発者は多くの可能性を提供しますが、コマンドを完全に覚えて記述することは退屈です。特に、異なるコマンドを緻密に繰り返しながら、新しいシェルを開発する時などです。Completion シェルは、bash ・ zsh ・ fish などのシェルの補完スクリプトを書くための API を提供することによって、この問題を支援します。

サブコマンド

Completion シェルは、開発者が補完スクリプトの作成を支援するためのいくつかのサブコマンドを含みます。それぞれ自動補完プロセスの中の異なるステップのためのものです。

commands

最初のステップは、利用可能なシェルコマンドをプラグイン名が必要な場合は付加して出力します。(出力結果は、このコマンド自身や他のサブコマンド全てがスペースで区切られています。) 例えば:

bin/cake Completion commands

実行結果:

acl api bake command_list completion console i18n schema server test testsuite upgrade

あなたの補完スクリプトは、一連のリストから関連するコマンドを選ぶことができます。 (このコマンドや以下のサブコマンドなど)

subCommands

いったん気に入ったコマンドが選ばれると、次の段階として subCommands に入ります。そして、与えられたシェルコマンドのために利用可能なサブコマンドを出力します。例えば:

bin/cake Completion subcommands bake

実行結果:

controller db_config fixture model plugin project test view

options

三番目で最後の options は getOptionParser で設定されるような与えられた (サブ) コマンドのためのオプションを出力します。 (Shell から継承されたデフォルトのオプションを含みます。) 例えば:

bin/cake Completion options bake

実行結果:

--help -h --verbose -v --quiet -q --everything --connection -c --force -f --plugin -p --prefix --theme -t

また、シェルのサブコマンドである追加の引数を渡すことができます。それが、このサブコマンドの特定のオプションを出力します。

CakePHP コンソール用の Bash 自動補完を有効にする方法

まず、bash-completion ライブラリーがインストールされていることを確認します。もし、ない場合は、次のコマンドでインストールします。

apt-get install bash-completion

/etc/bash_completion.d/ に cake という名前のファイルを作成し、配置します。そのファイル中に Bash 補完ファイルの内容 を記述してください。

ファイルを保存して、コンソールを再起動してください。

注釈

MacOS X を使用している場合は、homebrew で brew install bash-completion を使って bash-completion ライブラリーをインストールすることができます。 cake ファイルの対象ディレクトリーは、 /usr/local/etc/bash_completion.d/ になります。

Bash 補完ファイルの内容

これは、CakePHP コンソールを使用しているときに自動補完を得るために正しい位置の cake ファイルの内側に配置する必要があるコードです。

#
# Bash completion file for CakePHP console
#

_cake()
{
    local cur prev opts cake
    COMPREPLY=()
    cake="${COMP_WORDS[0]}"
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"

    if [[ "$cur" == -* ]] ; then
        if [[ ${COMP_CWORD} = 1 ]] ; then
            opts=$(${cake} Completion options)
        elif [[ ${COMP_CWORD} = 2 ]] ; then
            opts=$(${cake} Completion options "${COMP_WORDS[1]}")
        else
            opts=$(${cake} Completion options "${COMP_WORDS[1]}" "${COMP_WORDS[2]}")
        fi

        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    fi

    if [[ ${COMP_CWORD} = 1 ]] ; then
        opts=$(${cake} Completion commands)
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    fi

    if [[ ${COMP_CWORD} = 2 ]] ; then
        opts=$(${cake} Completion subcommands $prev)
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        if [[ $COMPREPLY = "" ]] ; then
            _filedir
            return 0
        fi
        return 0
    fi

    opts=$(${cake} Completion fuzzy "${COMP_WORDS[@]:1}")
    COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
    if [[ $COMPREPLY = "" ]] ; then
        _filedir
        return 0
    fi
    return 0;
}

complete -F _cake cake bin/cake

自動補完の利用

有効にすると、TAB キーを使用することで、他のビルトインコマンドと同じ方法で自動補完を使用することができます。自動補完の 3 つのタイプが用意されています。インストールしたばかりの CakePHP から次の出力があります。

コマンド

コマンドの自動補完のためのサンプル出力:

$ bin/cake <tab>
bake        i18n        orm_cache   routes
console     migrations  plugin      server

サブコマンド

サブコマンドの自動補完のためのサンプル出力:

$ bin/cake bake <tab>
behavior            helper              shell
cell                mailer              shell_helper
component           migration           template
controller          migration_snapshot  test
fixture             model
form                plugin

オプション

サブコマンドのオプションの自動補完のためのサンプル出力:

$ bin/cake bake -<tab>
-c            --everything  --force       --help        --plugin      -q            -t            -v
--connection  -f            -h            -p            --prefix      --quiet       --theme       --verbose

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

I18N ツール

CakePHP の国際化機能は、 po files [https://ja.wikipedia.org/wiki/Gettext] を翻訳のソースとして使います。POファイルは Poedit [https://poedit.net/] のようなツールで簡単に編集できます。

CakePHP の i18n コマンドは、手軽に potファイル(poテンプレートファイル)を生成します。翻訳者は、これらのテンプレートファイルを使ってアプリケーションの翻訳作業を行ないます。翻訳が更新された pot ファイルは既存の翻訳とマージされます。

POT ファイルの生成

extract コマンドを使って、既存のアプリケーションのために POT ファイルを生成することができます。このコマンドはアプリケーション全体から __() 形式の関数をスキャンし、メッセージ文字列を抽出します。アプリケーション中のユニークな文字列はそれぞれひとつの POT ファイルの中にマージされます。

.. code-block:: console

bin/cake i18n extract

上記は、抽出シェルを実行します。 このコマンドの結果は、 resources/locales/default.pot ファイルになります。 pot ファイルは、 po ファイルを作成するためのテンプレートとして使用します。もし、手動で pot ファイルから po ファイルを作成するなら、 Plural-Forms ヘッダー行を正しく設定してください。

プラグイン用 POT ファイルの生成

特定のプラグインで使用される POT ファイルを生成することができます。

bin/cake i18n extract --plugin <Plugin>

これにより、プラグインで使用される必要な POT ファイルが生成されます。

一括で複数のフォルダーを抽出

複数ディレクトリから文字列の抽出が必要なこともあるでしょう。例えば、アプリケーションの config ディレクトリー内のいくつかの文字列を定義している場合、 src ディレクトリと同様にこのディレクトリーからも文字列を抽出したくなるはずです。それには --paths オプションを使用することができます。そのオプションに抽出する絶対パスをカンマ区切りリストで渡します。

bin/cake i18n extract --paths /var/www/app/config,/var/www/app/src

特定フォルダーを除外

除外したいフォルダーをカンマ区切りで指定します。指定された値に含まれるパスは無視されます。

bin/cake i18n extract --exclude vendor,tests

既存の POT ファイルの上書き警告をスキップする

--overwrite を追加することで、 POT ファイルが存在しても警告されず、デフォルトで上書きされます。

bin/cake i18n extract --overwrite

CakePHP コアライブラリーからのメッセージ抽出

デフォルトで、抽出シェルスクリプトは CakePHP コアライブラリー中で使われているメッセージを抽出するかどうか訊ねます。 --extract-core に yes か no を設定することで、デフォルトの動作を指定できます。

bin/cake i18n extract --extract-core yes

// または

bin/cake i18n extract --extract-core no

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Plugin シェル

Plugin シェルを使用すると、コマンドプロンプトを経由してプラグインをロードおよびアンロードすることができます。ヘルプを表示するには、以下を実行します。

bin/cake plugin --help

プラグインのロード

Load タスクで、あなたの config/bootstrap.php の中にプラグインをロードすることができます。以下を実行することによって行います。

bin/cake plugin load MyPlugin

これはあなたの src/Application.php に以下を追加します。

// bootstrap メソッドの中に追加
$this->addPlugin('MyPlugin');

// 3.6 より前は、config/bootstrap.php に以下を追加
Plugin::load('MyPlugin');

bake のような CLI ツールのみ提供するプラグインをロードする場合、以下のように bootstrap_cli.php を更新することができます。

bin/cake plugin load --cli MyPlugin
bin/cake plugin unload --cli MyPlugin

プラグインのアンロード

プラグインの名前を指定することで、アンロードすることができます。

bin/cake plugin unload MyPlugin

これは src/Application.php から $this->addPlugin('MyPlugin',...) の行を削除します。

プラグインのアセット

CakePHP は、デフォルトで AssetMiddleware ミドルウェアを使用して、プラグインのアセットを提供しています。これはとても便利ですが、 PHP を呼び出すことなく、直接ウェブサーバーがサービスを提供することができるように、アプリの webroot 下のプラグインのアセットをシンボリックリンク／コピーすることをお勧めします。以下を実行することによって行います。

bin/cake plugin assets symlink

上記のコマンドを実行すると、アプリの webroot 下にすべてのプラグインのアセットをシンボリックリンクします。シンボリックリンクをサポートしていない Windows では、アセットをシンボリックリンクする代わりにそれぞれのフォルダーにコピーされます。

プラグインの名前を指定することにより、特定のプラグインのアセットをシンボリックリンクすることができます。

bin/cake plugin assets symlink MyPlugin

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Routes ツール

RoutesShell は、ルートのテストおよびデバッグのためにシンプルに利用する CLI インターフェイスを提供します。どのようにルートが解析されるか、どのような URL のルーティングパラメーターが生成されるかをテストするために使用することができます。

すべてのルートの一覧を取得

bin/cake routes

URL 解析のテスト

URL が check メソッドを使用して解析される方法をすぐに見ることができます。

bin/cake routes check /bookmarks/edit/1

ルートが任意のクエリー文字列パラメーターが含まれている場合は、引用符で URL を囲むことを忘れないでください。

bin/cake routes check "/bookmarks/?page=1&sort=title&direction=desc"

URL 生成のテスト

generate メソッドを使用して、 ルーティング配列 が、どのような URL を生成するかを確認できます。

bin/cake routes generate controller:Bookmarks action:edit 1

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Server ツール

ServerSコマンドhell は、ビルトイン PHP ウェブサーバーを使用して簡単なウェブサーバーを立ち上げることができます。このサーバーは製品利用するためのものではありません。すぐにアイデアを試してみたくて Apache や nginx を設定する時間を費やしたくない開発中に、手軽に行うことができます。以下のように、サーバーのシェルを起動することができます。

$ bin/cake server

サーバーの起動とポート 8765 へのアタッチを確認してください。ウェブブラウザーから http://localhost:8765 へ、 CLI サーバーにアクセスしてください。あなたの端末内で CTRL-C を押してサーバーを閉じることができます。

注釈

他のホストからサーバーに繋がらない場合、 bin/cake server -H 0.0.0.0 を試してください。

ポートおよびドキュメントルートの変更

オプションを使用してポートおよびドキュメントルートをカスタマイズすることができます。

$ bin/cake server --port 8080 --document_root path/to/app

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

インタラクティブ・コンソール (REPL)

REPL(Read Eval Print Loop) プラグイン [https://github.com/cakephp/repl] を使うことで CakePHP やアプリケーションがインタラクティブ・コンソール内で探索しやすくなります。

注釈

このプラグインは 4.3 以前では CakePHP の app スケルトンに同梱されていました。

以下のようにするとインタラクティブ・コンソールを使い始めることができます。

$ bin/cake console

これは、アプリケーションを自動実行し、インタラクティブコンソールを開始します。この時点で、アプリケーションコードを対話的に実行したり、アプリケーションのモデルを利用してクエリーを実行することができます。

bin/cake console

>>> $articles = Cake\Datasource\FactoryLocator::get('Table')->get('Articles');
// object(Cake\ORM\Table)(
//
// )
>>> $articles->find()->all();

アプリケーションが自動実行されたら、REPL を利用してルーティングを試すこともできます。

>>> Cake\Routing\Router::parse('/articles/view/1');
// [
//   'controller' => 'Articles',
//   'action' => 'view',
//   'pass' => [
//     0 => '1'
//   ],
//   'plugin' => NULL
// ]

URL 生成を試すこともできます。

>>> Cake\Routing\Router::url(['controller' => 'Articles', 'action' => 'edit', 99]);
// '/articles/edit/99'

REPL を終了するには、 CTRL-C を使用するか、あるいは exit と入力してください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

シェル

class Cake\Console\Shell

バージョン 3.6.0 で非推奨: Shell は 3.6.0 で非推奨ですが、 5.x までは削除されません。代わりに コマンドオブジェクト を使用してください。

シェルの作成

早速コンソールで動くシェルを作ってみましょう。この例ではシンプルな Hello world シェルを作ります。アプリケーションの src/Shell ディレクトリーに HelloShell.php を作成してください。その中に以下のコードを書きます。

namespace App\Shell;

use Cake\Console\Shell;

class HelloShell extends Shell
{
    public function main()
    {
        $this->out('Hello world.');
    }
}

シェルクラスの規約として、クラス名は Shell サフィックス（接尾辞）を 付け、ファイル名と一致する必要があります。上記のシェルでは、 main() メソッドを作成しました。シェルが 追加コマンド（引数）なしで起動された場合、このメソッドが呼ばれます。この後、 多少コマンドを追加していきますが、現時点では単にシェルを起動してみましょう。アプリケーションディレクトリーから、以下を実行してください。

bin/cake hello

次の出力が表示されるはずです。

Hello world.

すでに述べたように、シェルで main() メソッドは、シェルに与えられた他のコマンドや引数がない場合、いつでも呼ばれる特殊なメソッドです。main メソッドの使い方がある程度わかったら、次は以下のように別のコマンドを追加してみましょう。

namespace App\Shell;

use Cake\Console\Shell;

class HelloShell extends Shell
{
    public function main()
    {
        $this->out('Hello world.');
    }

    public function heyThere($name = 'Anonymous')
    {
        $this->out('Hey there ' . $name);
    }
}

このファイルを保存した後、次のコマンドを実行すると、あなたの名前が表示されるはずです。

bin/cake hello hey_there your-name

public メソッドのうち頭に _ が付かないものは、コマンドラインから呼び出せます。ご覧のように、コマンドラインから呼び出されるメソッドは、アンダースコアー形式のシェル引数からクラス内の正しいキャメルケース形式のメソッド名に変換されています。

heyThere() メソッドでは、位置引数が heyThere() 関数に提供されていることがわかります。位置引数は args プロパティーでも利用できます。 $this->params で利用できるシェルアプリケーション上のスイッチやオプションにアクセスできますが、それを少し使いやすくしています。

main() メソッドを使用するときは、位置引数を使用することはできません。最初の位置引数やオプションが、コマンド名として解釈されるためです。引数を使用したい場合、 main 以外のメソッド名を使用する必要があります。

シェルのタスク

より高度なコンソールアプリケーションを開発する場合には、多くのシェル間で共有される再利用可能なクラスとして機能を構成したいでしょう。タスクによりコマンドをクラスに展開できます。例えば bake は、そのほとんどがタスクにより作られています。 $tasks プロパティーを使ってシェルのタスクを定義できます。

class UserShell extends Shell
{
    public $tasks = ['Template'];
}

プラグインからタスクを使うには、標準の プラグイン記法 を使用します。タスクは Shell/Task/ に、クラスにちなんで名付けられたファイルに格納されます。たとえば新たに 'FileGenerator' タスクを作成したい場合は、 src/Shell/Task/FileGeneratorTask.php を作成することになります。

各タスクは、少なくとも main() メソッドを実装する必要があります。タスクが呼び出されたとき ShellDispatcher は、このメソッドを呼び出します。タスククラスは次のようになります。

namespace App\Shell\Task;

use Cake\Console\Shell;

class FileGeneratorTask extends Shell
{
    public function main()
    {

    }
}

シェルはプロパティーとしてもタスクにアクセスできますので、 コンポーネントと同様に再利用可能な部品としてタスクを利用できます。

// Found in src/Shell/SeaShell.php
class SeaShell extends Shell
{
    // Found in src/Shell/Task/SoundTask.php
    public $tasks = ['Sound'];

    public function main()
    {
        $this->Sound->main();
    }
}

また、コマンドラインからタスクに直接アクセスすることができます。

$ cake sea sound

注釈

コマンドラインからタスクを直接アクセスするには、タスクは 必ず シェルクラスの $tasks プロパティーに含まれている必要があります。

また、タスク名は、シェルの OptionParser にサブコマンドとして追加する必要があります。

public function getOptionParser()
{
    $parser = parent::getOptionParser();
    $parser->addSubcommand('sound', [
        // コマンド一覧のヘルプテキストを提供
        'help' => 'Execute The Sound Task.',
        // オプションパーサーを互いにリンク
        'parser' => $this->Sound->getOptionParser(),
    ]);
    return $parser;
}

TaskRegistry による動的なタスクのロード

タスクのレジストリーオブジェクトを使用して、その場でタスクをロードすることができます。以下のようにすると $tasks で宣言されなかったタスクをロードすることができます。

$project = $this->Tasks->load('Project');

ProjectTask インスタンスをロードして返します。プラグインからタスクをロードすることもできます。

$progressBar = $this->Tasks->load('ProgressBar.ProgressBar');

シェルの中でのモデルの使用

アプリケーションのビジネスロジックに、シェルユーティリティーの中からアクセスする必要があることもよくあるでしょう。 CakePHP はそれが超簡単にできます。コントローラーの中で loadModel() を使用するのと同じように、シェルの中でモデルを読み込むことができます。ロードされたモデルは、あなたのシェルに付属するプロパティーとして設定されます。

namespace App\Shell;

use Cake\Console\Shell;

class UserShell extends Shell
{

    public function initialize()
    {
        parent::initialize();
        $this->loadModel('Users');
    }

    public function show()
    {
        if (empty($this->args[0])) {
            // CakePHP 3.2 より前なら error() を利用
            return $this->abort('Please enter a username.');
        }
        $user = $this->Users->findByUsername($this->args[0])->first();
        $this->out(print_r($user, true));
    }
}

上記のシェルは、username によってユーザーを取得し、データベースに格納された情報が表示されます。

シェルヘルパー

複雑な出力生成ロジックの場合、再利用可能な方法で、このロジックをカプセル化するためにコマンドヘルパー を利用することができます。

シェルから他のシェルの呼び出し

Cake\Console\Shell::dispatchShell($args)

あるシェルから他のシェルを呼び出したいケースは多々あると思います。他のシェルを呼び出すには Shell::dispatchShell() を使います。サブシェル側では引数を受け取るための argv が使えます。引数やオプションは可変引数もしくは文字列として指定できます。

// 文字列として
$this->dispatchShell('schema create Blog --plugin Blog');

// 配列として
$this->dispatchShell('schema', 'create', 'Blog', '--plugin', 'Blog');

上記は、プラグインのシェルの中からプラグイン用のスキーマを作るために schema シェルを呼んでいます。

ディスパッチされたシェルへのパラメーター追加

ディスパッチされたシェルへの（シェルの引数にない）追加パラメーターを渡すことが有用な時がしばしばあります。これを行うために、 dispatchShell() に配列を渡すことができます。配列は、 command キーと共に extra キーを持つことが期待されています。

// コマンド文字列を使用
$this->dispatchShell([
   'command' => 'schema create Blog --plugin Blog',
   'extra' => [
        'foo' => 'bar'
    ]
]);

// コマンド配列を使用
$this->dispatchShell([
   'command' => ['schema', 'create', 'Blog', '--plugin', 'Blog'],
   'extra' => [
        'foo' => 'bar'
    ]
]);

extra で渡されたパラメーターは、 Shell::$params プロパティーにマージされ、 Shell::param() メソッドでアクセス可能になります。シェルで dispatchShell() を使用してディスパッチされた時、デフォルトで requested 追加パラメーターが自動的に追加されます。この requested パラメーターは、ディスパッチされたシェルに表示されている CakePHP のコンソールウェルカムメッセージを防ぎます。

CLI オプションのパース

シェルはオプション、引数を定義し、ヘルプの生成を自動化するためにオプションパーサー を使います。

入出力との対話

シェルでは、 getIo() メソッドを使って ConsoleIo インスタンスにアクセスすることができます。詳細は、 コマンドの入力と出力 をご覧ください。

ConsoleIo オブジェクトに加えて、シェルクラスは一連のショートカットメソッドを提供します。これらのメソッドは、 ConsoleIo にあるメソッドのショートカットやエイリアスです。

// ユーザーから任意のテキストを取得
$color = $this->in('What color do you like?');

// ユーザーの選択を取得
$selection = $this->in('Red or Green?', ['R', 'G'], 'R');

// ファイルの作成
$this->createFile('bower.json', $stuff);

// 標準出力に出力
$this->out('Normal message');

// 標準エラーに出力
$this->err('Error message');

// 標準エラーに出力し、停止例外を発生
$this->abort('Fatal error');

// CakePHP 3.2 より前。標準エラーに出力し exit()
$this->error('Fatal error');

また、出力レベルに関する2つの便利なメソッドを提供します。

// 詳細出力が有効の時のみ (-v)
$this->verbose('Verbose message');

// すべてのレベルで表示
$this->quiet('Quiet message');

シェルはまた、画面のクリア、空白行の作成、または横棒線を描くためのメソッドを含みます。

// ２行の改行を出力
$this->out($this->nl(2));

// ユーザーの画面をクリア
$this->clear();

// 横棒線を描画
$this->hr();

シェルの実行を停止

あなたのシェルコマンドを停止したい条件に達した時、プロセスを停止するための StopException を発生させるために abort() を使用することができます。

$user = $this->Users->get($this->args[0]);
if (!$user) {
    // エラーメッセージとエラーコードとともに停止
    $this->abort('ユーザーが見つかりません', 128);
}

ステータスとエラーコード

コマンドラインツールは、成功を示すために 0 を返し、エラー状態を示すために 0 以外を返すべきです。 PHP メソッドは、通常 true か false を返すため、 Cake Shell の dispatch 関数は、 null と true の戻り値を 0 へ、それ以外の値は 1 へと変換することによって、これらのセマンティクスとの橋渡しに役立ちます。

Cake Shell の dispatch 関数は、 StopException をキャッチし、その例外コードの値をシェルの終了コードとして使用します。上記のように、 abort() を使ってメッセージを出力して指定したコードで終了したり、例に示すように、直接 StopException を起こすことができます。

namespace App\Shell\Task;

use Cake\Console\Shell;

class ErroneousShell extends Shell
{
    public function main()
    {
        return true;
    }

    public function itFails()
    {
        return false;
    }

    public function itFailsSpecifically()
    {
        throw new StopException("", 2);
    }
}

上記の例では、コマンドライン上で実行された際、次の終了コードを返します。

$ bin/cake erroneousshell ; echo $?
0
$ bin/cake erroneousshell itFails ; echo $?
1
$ bin/cake erroneousshell itFailsSpecifically ; echo $?
2

Tip

終了コードの 64 から 78 は避けてください。それらは sysexits.h で記述された特定の意味を持っています。終了コードの 127 以上を避けてください。それらは、 SIGKILL や SIGSEGV のようなシグナルによるプロセスの終了を示すために使用されます。

注釈

従来の終了コードについての詳細は、ほとんどの Unix システムの sysexit マニュアルページ (man sysexits) 、または Windows の System Error Codes ヘルプページを参照してください。

フックメソッド

Cake\Console\Shell::initialize()

シェルを初期化し、サブクラスのコンストラクターとして動作します。またシェルの実行に先立って、タスクの設定を行います。

Cake\Console\Shell::startup()

シェルを起動して、ウェルカムメッセージを表示します。コマンドや main の実行に先立ってチェックや設定を可能とします。

Tip

ウェルカム情報を削除する場合やそれまでのコマンドの流れを変更する場合は、 startup() メソッドをオーバーライドします。

終了コードの 64 から 78 は避けてください。それらは sysexits.h で記述された特定の意味を持っています。終了コードの 127 以上を避けてください。それらは、 SIGKILL や SIGSEGV のようなシグナルによるプロセスの終了を示すために使用されます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

デバッグ

デバッグはいかなる開発サイクルにおいても避けることのできない、必要なものです。 CakePHP は IDE やエディターと直接連携するようなツールは提供しませんが、 CakePHP はデバッグ作業やあなたのアプリケーション内部で何が走っているのかを探る作業を助けるためのツールをいくつか提供します。

基本的なデバッグ

debug(mixed $var, boolean $showHtml = null, $showFrom = true)

debug() 関数は PHP 関数の print_r() と同様に、グローバルに利用可能な関数です。 debug() 関数により、さまざまな方法で変数の内容を出力することができます。データを HTML に優しい方法で表示させたいなら、第２引数を true にしてください。この関数はまた、デフォルトで呼ばれた場所となるファイルと行番号も出力します。

この関数からの出力は、core の $debug 変数が true の場合のみ行われます。

dd() 、 pr() 及び pj() もご確認ください。

stackTrace()

stackTrace() 関数はグローバルに使用でき、関数がどこで呼ばれたかのスタックトレースを出力することができます。

breakpoint()

もし Psysh [https://psysh.org/] をインストールしている場合、この関数を CLI 環境で使用することで現在のローカルスコープで対話型コンソールを開くことができます。

// 実行したいコード
eval(breakpoint());

開いた対話型コンソールでローカル変数のチェックや他のコードの実行をすることができます。対話型デバッガーを終了して元の処理に戻りたい時は quit か q を入力してください。

Debugger クラスの使用

class Cake\Error\Debugger

Debugger を使用する際にはまず、 Configure::read('debug') に true がセットされていることを確認してください。

値の出力

static Cake\Error\Debugger::dump($var, $depth = 3)

dump は変数の内容を出力します。渡された変数のすべてのプロパティーと（可能なら）メソッドを出力します。

$foo = [1,2,3];

Debugger::dump($foo);

// 出力
array(
    1,
    2,
    3
)

// シンプルなオブジェクト
$car = new Car();

Debugger::dump($car);

// 出力
object(Car) {
    color => 'red'
    make => 'Toyota'
    model => 'Camry'
    mileage => (int)15000
}

データのマスク

Debugger でデータをダンプしたり、エラーページを描画する際、パスワードや API キーなど機密キーを隠したくなるでしょう。 config/bootstrap.php の中で、指定したキーをマスクすることができます。

Debugger::setOutputMask([
    'password' => 'xxxxx',
    'awsKey' => 'yyyyy',
]);

スタックトレース付きのログ出力

static Cake\Error\Debugger::log($var, $level = 7, $depth = 3)

呼び出されたときに詳細なスタックトレースを生成します。 log() メソッドは Debugger::dump() によるものと似たデータを出力しますが、出力バッファにではなく、 debug.log に出力します。 log() が正常に動作するためには、あなたの tmp ディレクトリー（と、その中）はウェブサーバーにより書き込み可能でなければならないことに気をつけてください。

スタックトレースの生成

static Cake\Error\Debugger::trace($options)

現在のスタックトレースを返します。トレースの各行には、呼び出しているメソッド、どこから呼ばれたかというファイルと行番号が含まれています。

// PostsController::index() の中で
pr(Debugger::trace());

// 出力
PostsController::index() - APP/Controller/DownloadsController.php, line 48
Dispatcher::_invoke() - CORE/src/Routing/Dispatcher.php, line 265
Dispatcher::dispatch() - CORE/src/Routing/Dispatcher.php, line 237
[main] - APP/webroot/index.php, line 84

上記では、コントローラーのアクション内で Debugger::trace() を呼ぶことで、スタックトレースを生成しています。スタックトレースは下から上へと読み、現在走っている関数（スタックフレーム）の順になっています。

ファイルから抜粋を取得

static Cake\Error\Debugger::excerpt($file, $line, $context)

$path（絶対パス）にあるファイルからの抜粋を取得します。$line 行目をハイライトし、 $line 行目の前後 $context 行もあわせて取得します。

pr(Debugger::excerpt(ROOT . DS . LIBS . 'debugger.php', 321, 2));

// 下記のように出力されます
Array
(
    [0] => <code><span style="color: #000000"> * @access public</span></code>
    [1] => <code><span style="color: #000000"> */</span></code>
    [2] => <code><span style="color: #000000">    function excerpt($file, $line, $context = 2) {</span></code>

    [3] => <span class="code-highlight"><code><span style="color: #000000">        $data = $lines = array();</span></code></span>
    [4] => <code><span style="color: #000000">        $data = @explode("\n", file_get_contents($file));</span></code>
)

このメソッドは内部的に使われているものですが、あなたが独自のエラーメッセージを生成する場合や独自の状況でログ出力する場合にも使いやすいものです。

static Cake\Error\Debugger::getType($var)

変数の型を取得します。オブジェクトならクラス名を返します。

ログ出力によるデバッグ

アプリケーションをデバッグするもう一つの良い方法はログメッセージです。 Cake\Log\Log を使うことで、あなたのアプリケーションでログ出力をさせることができます。 LogTrait を利用するすべてのオブジェクトは、インスタンスメソッド log() を持っており、ログメッセージを出力するのに使えます。

$this->log('通ったよ', 'debug');

上記では 通ったよ がデバッグログに出力されます。ログに出力することで、リダイレクトや複雑なループを含むメソッドをデバッグしやすくなるでしょう。また、Cake\Log\Log::write() を使うことで、ログメッセージを書きだすことも可能です。このメソッドは Log がロードされているなら static にあなたのアプリケーション内のどこからでも呼び出すことができるのです。

// ログを使用したいファイルの一番最初で
use Cake\Log\Log;

// Log がインポートされている場所で
Log::debug('通ったよ');

Debug Kit

DebugKit は便利なデバッグツールをたくさん提供してくれるプラグインです。まずは、レンダリングされた HTML 内にツールバーを表示して、あなたのアプリケーションや現在のリクエストについての情報を大量に提供してくれます。 DebugKit のインストールと使用方法については Debug Kit の章を見てください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

デプロイ

アプリケーションが一度完成したら、または、完成する前でさえも、デプロイしたいと思うでしょう。CakePHP アプリケーションをデプロイするにあたり、いくつかのことをしなければなりません。

ファイルの移動

git commit とあなたのサーバー上で commit やリポジトリーの pull や clone を作成し、 composer install を実行することを奨励されます。git に関する幾つかの知識や git や composer のインストールの知識が必要とされますが、このプロセスは、ライブラリーの依存関係やファイルやフォルダーのパーミッションについて扱います。

FTP 経由でデプロイするとき、少なくともファイルやフォルダーのパーミッションを修正しなければならないことを理解してください。

ステージングやデモサーバー (試作品) をセットアップし、あなたの開発環境と同期を保つためのデプロイ技術を使用することもできます。

config/app.php の調整

app.php、特に debug の値を調整することは非常に重要なことです。debug を false に変更することにより、開発に関連する部分で、決して広くインターネットに晒されるべきでない部分を無効にすることができます。debug を無効とすることにより、以下の種類のことが変更されます。

pr() 、　debug() 及び dd() により生成されたデバッグメッセージが、無効化されます。

CakePHP コアのキャッシュが、開発時の 10 秒ごとの代わりに毎年 (約365日ごとに) 破棄されるようになります。

エラービューの情報量は少なくなり、一般的なエラーメッセージしか表示されなくなります。

PHP エラーは表示されなくなります。

例外のスタックトレースは無効化されます。

上記に加え、多くのプラグインとアプリケーションの拡張機能は、自らの振る舞いを修正するために、 debug を使用します。

環境間でデバッグレベルを動的にセットするため、環境変数に対してチェックをかけることができます。このことにより、アプリケーションをデバッグ true の状態でデプロイすることを避けることができるだけでなく、毎回本番環境にデプロイする度にデバッグレベルを変更せずに済むこととなります。

例えば、Apache の設定にて、環境変数をセットすることができます。

SetEnv CAKEPHP_DEBUG 1

それから、app_local.php にてデバッグレベルを動的にセットすることができます。

$debug = (bool)getenv('CAKEPHP_DEBUG');

return [
    'debug' => $debug,
    .....
];

It is recommended that you put configuration that is shared across all of your application's environments in config/app.php. For configuration that varies between environments either use config/app_local.php or environment variables.

セキュリティのチェック

もしあなたがウェブ上の荒野にアプリケーションを解き放とうとするなら、何か抜け穴がないかを確認しておくことをお勧めします。

CSRFミドルウェア（クロスサイトリクエストフォージェリミドルウェア） コンポーネントまたはミドルウェアを使用していることを確認して下さい。

セキュリティ コンポーネントを有効化しておいた方がいいかもしれません。フォームの改ざんや一括代入 (mass-assignment) 脆弱性に関する問題の発生可能性を削減することができます。

各モデルにおいて、正しい バリデーション ルールが有効化されているかどうかを確認して下さい。

webroot ディレクトリーのみが公開されており、その他の秘密の部分（ソルト値やセキュリティキー等）は非公開でかつユニークな状態となっていることを確認して下さい。

ドキュメントルートの指定

アプリケーションでドキュメントルートを正しく指定することはコードをセキュアに、またアプリケーションを安全に保つために重要なステップの内の一つです。 CakePHP のアプリケーションは、アプリケーションの webroot にドキュメントルートを指定する必要があります。これによってアプリケーション、設定のファイルが URL を通してアクセスすることができなくなります。ドキュメントルートの指定の仕方はウェブサーバーごとに異なります。ウェブサーバー特有の情報については URL Rewriting ドキュメントを見てください。

どの場合においても webroot/ をバーチャルホスト（バーチャルドメイン）のドキュメントルートに設定すべきでしょう。これは webroot ディレクトリーの外側のファイルを実行される可能性を取り除きます。

アプリケーションのパフォーマンス改善

クラスローディングは、アプリケーションのプロセス時間の大部分を占めることがあります。このような問題を避けるために、アプリケーションがデプロイされたら以下のコマンドを本番サーバーにて走らせることを推奨します。

php composer.phar dumpautoload -o

プラグインの画像や JavaScript、CSS ファイルなどの静的なアセットを扱う場合、 Dispatcher を通すことはかなり非効率です。本番環境においては、次のようにシンボリックリンクにすることを強くお勧めします。これは、 plugin シェルを利用することで実行できます。

bin/cake plugin assets symlink

上記のコマンドは、アプリケーション内での webroot ディレクトリーの適切なパスに対して、全てのロードされたプラグインの webroot ディレクトリーのシンボリックリンクします。

もし、あなたのファイルシステムがシンボリックリンクを作成できない場合、ディレクトリーをシンボリックリンクする代わりにコピーします。また、以下を使用して、明示的にディレクトリーをコピーすることができます。

bin/cake plugin assets copy

更新のデプロイ

デプロイごとに、Webサーバーで調整するタスクがいくつかある可能性があります。いくつかの典型的なものは次のとおりです。

composer install を使用して依存関係をインストールします。予期しないバージョンのパッケージを取得する可能性があるため、デプロイを行うときに composer update は使用しないでください。

Migrations プラグインまたは別のツールを使用して、データベースの マイグレーション を実行します。

bin/cake schema_cache clear を実行してモデルスキーマキャッシュをクリアします。スキーマキャッシュツール には、このコマンドに関する詳細情報があります。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Mailer

class Cake\Mailer\Mailer(string|array|null $profile = null)

Mailer は、メール送信の新しいクラスです。このクラスを使用すると、アプリケーションの任意の場所からメール送信できます。

基本的な使用法

まず最初に、クラスがロードされていることを確認する必要があります。

use Cake\Mailer\Mailer;

Mailer をロードしたら、次のようにメールを送信することができます。

$mailer = new Mailer('default');
$mailer->setFrom(['me@example.com' => 'My Site'])
    ->setTo('you@example.com')
    ->setSubject('About')
    ->deliver('My message');

Mailer のセッターメソッドは、クラスのインスタンスを返すので、メソッド・チェーンでプロパティーを設定することができます。

Mailer は、受信者を定義するためのいくつかの方法があります - setTo() 、 setCc() 、 setBcc() 、 addTo() 、 addCc() そして addBcc() 。主な違いは最初の3つは、すでに設定されていたものを上書きし、後者は単にそれぞれのフィールドに複数の受信者を追加することです。

$mailer = new Mailer();
$mailer->setTo('to@example.com', 'To Example');
$mailer->addTo('to2@example.com', 'To2 Example');
// メールの To 受信者は to@example.com と to2@example.com
$mailer->setTo('test@example.com', 'ToTest Example');
// メールの To 受信者は test@example.com

送り主の選択

他の人々に代わってメールを送信するとき、Sender ヘッダーを使用して、元の送り主を定義することは良い考えです。 setSender() を使用して行えます。

$mailer = new Mailer();
$mailer->setSender('app@example.com', 'MyApp emailer');

注釈

別の人の代わりにメール送信するときに送り主 (envelope sender) をセットするのは良い考えです。これは、配信失敗に関するメッセージの受信を防ぐことができます。

設定

メーラーのプロファイルとメールのトランスポートは、アプリケーションの設定ファイルを使用して作成されます。 Email と EmailTransport はそれぞれメーラーのプロファイルとメールのトランスポート設定を定義します。アプリケーションのブートストラップ設定は setConfig() を使用することにより Configure から　Mailer および TransportFactory クラスに渡されます。プロファイルおよびトランスポートを定義することにより、アプリケーションコードにおいて設定データの自由を保ち、メンテナンスおよび配備をより困難にする重複を避けることができます。

あらかじめ定義された設定をロードするには、 setProfile() メソッドを使用するか、または Mailer のコンストラクターに渡すことができます。

$mailer = new Mailer();
$mailer->setProfile('default');

// または、コンストラクター内で
$mailer = new Mailer('default');

設定名の文字列を渡す代わりに、オプションの配列をロードすることもできます。

$mailer = new Mailer();
$mailer->setProfile(['from' => 'me@example.org', 'transport' => 'my_custom']);

// または、コンストラクター内で
$mailer = new Mailer(['from' => 'me@example.org', 'transport' => 'my_custom']);

設定プロファイル

配信プロファイルを定義すると、再利用可能なプロファイルに共通のメール設定を統合することができます。アプリケーションは、必要な数のプロファイルを持つことができます。次の設定キーが使用されます。

'from': 送信者のメールアドレスまたは配列。 Mailer::setFrom() を参照。

'sender': 実際の送信者のメールアドレスまたは配列。 Mailer::setSender() を参照。

'to': 宛先のメールアドレスまたは配列。 Mailer::setTo() を参照。

'cc': CC のメールアドレスまたは配列。 Mailer::setCc() を参照。

'bcc': BCC のメールアドレスまたは配列。 Mailer::setCcc() を参照。

'replyTo': メールの返信先のメールアドレスまたは配列。 Mailer::setReplyTo() を参照。

'readReceipt': 開封通知先メールアドレスまたはアドレスの配列。 Mailer::setReadReceipt() を参照。

'returnPath': エラーの返信先メールアドレスまたはアドレスの配列。 Mailer::setReturnPath() を参照。

'messageId': メールのメッセージID。 Mailer::setMessageId() を参照。

'subject': メッセージのサブジェクト。 Mailer::setSubject() を参照。

'message': メッセージ本文。レンダリングされた本文を使用する場合は、 この項目を設定しないでください。

'priority': メールの優先度 (数値。通常は 1 から 5 で、1 が最高)。

'headers': ヘッダー情報。 Mailer::setHeaders() を参照。

'viewRender': レンダリングされた本文を使用する場合は、ビュークラス名をセット。 Mailer::viewRender() を参照。

'template': レンダリングされた本文を使用する場合は、テンプレート名をセット。 ViewBuilder::setTemplate() を参照。

'theme': テンプレートをレンダリングする際のテーマ。 ViewBuilder::setTheme() を参照。

'layout': レンダリングされた本文を使用する場合、描画するレイアウトをセット。レイアウトなしでテンプレートをレンダリングしたい場合は、このフィールドに null をセット。 ViewBuilder::setTemplate() を参照。

'viewVars': レンダリングされた本文を使用する場合は、ビューで使用する変数の配列をセット。 Mailer::setViewVars() を参照。

'attachments': 添付ファイルの一覧。 Mailer::setAttachments() を参照。

'emailFormat': メールの書式 (html, text または both) Mailer::setEmailFormat() を参照。

'transport': トランスポート名。 トランスポート設定を参照。

'log': メールヘッダーとメッセージをログに記録するログレベル。 true なら LOG_DEBUG を使用します。 レベルを使う を参照。ログはスコープ名 email で出力されることに注意してください。 ロギングスコープ を参照。

'helpers': メールテンプレート内で使用するヘルパーの配列。 ViewBuilder::setHelpers() 。

注釈

メールアドレスや配列で使用する上記のキーの値 (from, to, cc 他）は、関連するメソッドの第一引数として渡されます。例をあげると $mailer->setFrom('my@example.com', 'My Site') は、設定の中では 'from' => ['my@example.com' => 'My Site'] と定義されます。

ヘッダーの設定

Mailer の中に、自由にヘッダーをセットできます。Email を使用する際、独自のヘッダーにプレフィックスの X- をつけることを忘れないでください。

Mailer::setHeaders() と Mailer::addHeaders() を参照してください。

テンプレートメールの送信

メールはしばしば単純なテキストメッセージを超えたものになります。それを容易にするために CakePHP は、 ビューレイヤー を使用してメールを送信することができます。

メールのテンプレートは、 templates/email と呼ばれる特別なフォルダーに置かれます。メールのビューは、普通のビューと同様にレイアウトとエレメントを使用します。

$mailer = new Mailer();
$mailer
            ->setEmailFormat('html')
            ->setTo('bob@example.com')
            ->setFrom('app@domain.com')
            ->viewBuilder()
                ->setTemplate('welcome')
                ->setLayout('fancy');

$mailer->deliver();

上記は、ビューとして templates/email/html/welcome.php を使用し、レイアウトとして templates/layout/email/html/fancy.php を使用します。以下のように、マルチパートのテンプレートメールを送信することもできます。

$mailer = new Mailer();
$mailer
            ->setEmailFormat('both')
            ->setTo('bob@example.com')
            ->setFrom('app@domain.com')
            ->viewBuilder()
                ->setTemplate('welcome')
                ->setLayout('fancy');

$mailer->deliver();

この例では、次のテンプレートファイルを使用します。

templates/email/text/welcome.php

templates/layout/email/text/fancy.php

templates/email/html/welcome.php

templates/layout/email/html/fancy.php

テンプレートメールを送信する時、 text 、 html と both のうちのどれかを送信オプションとして指定します。

Mailer :: viewBuilder() で取得されたビュービルダーのインスタンスを使用して、コントローラーで行うことと似たように、すべてのビュー関連の設定をすることができます。

Mailer::setViewVars() でビューの変数をセットできます。

$mailer = new Mailer('templated');
$mailer->setViewVars(['value' => 12345]);

または、 ビュービルダーのメソッド ViewBuilder::setVar() および ViewBuilder::setVars() を使用できます。

以下のようにメールテンプレート内で使用します。

<p>あなたの値は次のとおりです: <b><?= $value ?></b></p>

メールでも普通のテンプレートファイルと同様にヘルパーを使用できます。デフォルトでは、 HtmlHelper のみがロードされます。 ViewBuilder::setHelpers() メソッドを使うことで追加でヘルパーをロードできます。

$mailer->viewBuilder()->setHelpers(['Html', 'Custom', 'Text']);

ヘルパーを設定する時は、’Html’ を含めて下さい。そうしなければ、メールテンプレートにロードされません。

もし、プラグインの中でテンプレートを使用してメール送信したい場合、おなじみの プラグイン記法を使います。

$mailer = new Mailer();
$mailer->viewBuilder()->setTemplate('Blog.new_comment');

上記の例は、 Blog プラグインのテンプレートとレイアウトを使用しています。

いくつかのケースで、プラグインで用意されたデフォルトのテンプレートを上書きしたい場合があるかもしれません。テーマを利用して行うことができます。

$mailer->viewBuilder()
    ->setTemplate('Blog.new_comment')
    ->setLayout('Blog.auto_message')
    ->setTheme('TestTheme');

これは、Blog プラグインを更新せずにあなたのテーマの new_comment テンプレートで上書きできます。テンプレートファイルは、以下のパスで作成する必要があります: templates/plugin/TestTheme/plugin/Blog/email/text/new_comment.php

添付ファイルの送信

Cake\Mailer\Mailer::setAttachments($attachments)

メールにファイルを添付することができます。添付するファイルの種類や、宛先のメールクライアントにどのようなファイル名で送りたいのかによって幾つかの異なる書式があります。

配列: $email->attachments(['/full/file/path/file.png']) は、 文字列の場合と同じ振る舞いをします。

キー付き配列: $mailer->setAttachments(['photo.png' => '/full/some_hash.png']) は、 photo.png というファイル名で some_hash.png ファイルを添付します。受信者からは、some_hash.png ではなく photo.png として見えます。

ネストした配列:

$mailer->setAttachments([
    'photo.png' => [
        'file' => '/full/some_hash.png',
        'mimetype' => 'image/png',
        'contentId' => 'my-unique-id'
    ]
]);

上記は、異なる mimetype と独自のコンテンツID を添付します (添付をインラインに変換する場合にコンテンツIDをセットします)。 mimetype と contentId はこの形式のオプションです。

3.1. contentId を指定した時、HTML 内で <img src="cid:my-content-id"> のようにファイルを使用できます。

3.2. 添付の Content-Disposition ヘッダーを無効にするために contentDisposition オプションを使用できます。これは、outlook を使って ical の招待状をクライアントに送る時に便利です。

3.3. file オプションの代わりに data オプションを使うと、ファイル本文を文字列として添付することができます。これは、ファイルパスを指定せずに添付することができます。

アドレス検証ルールの緩和

Cake\Mailer\Mailer::setEmailPattern($pattern)

もし、規約に準拠していないアドレスに送信するときにバリデーションに問題がある場合、メールアドレスのバリデーションに使用するパターンを緩和することができます。いくつかの ISP に送信するときに必要になります。

$mailer = new Mailer('default');

// 規約に準拠しないアドレスに送信できるように
// メールのパターンを緩和します。
$mailer->setEmailPattern($newPattern);

メッセージの即時送信

しばしば、メールの素早い送信が必要で、送信ごとに毎回設定のセットアップが必要ないことがあります。そのような目的のために Cake\Mailer\Email::deliver() が用意されています。

Cake\Mailer\Email::config() で設定を作成したり、 Email::deliver() スタティックメソッドにすべての必要なオプションを配列で指定することができます。例:

Email::deliver('you@example.com', 'Subject', 'Message', ['from' => 'me@example.com']);

このメソッドは、 「you@example.com」宛に、「me@example.com」から、件名「Subject」、本文「Message」でメールを送信します。

deliver() の戻り値は、 すべての設定を持つ Cake\Mailer\Email インスタンスです。もし、メールを送信せず送信前に幾つか設定変更したい場合、第５引数に false をセットしてインスタンスを取得してください。

第３引数には、メッセージの本文か、レンダリングされた本文を使用時には変数の配列を指定します。

第４引数は、設定の配列や Configure 内の設定名の文字列を指定します。

もしあなたが望むのなら、サブジェクトと本文に null をセットして、すべての設定を (配列か Configure を使用して) 第４引数で指定できます。全ての設定を知るために 設定 一覧を確認してください。

CLI からのメール送信

シェルやタスクなどの CLI スクリプトでメールを送信するとき、Email に使用するドメイン名をセットしなければなりません。(ホスト名が CLI 環境にないとき) ドメイン名は、メッセージ ID のホスト名として使用されます。

$mailer->setDomain('www.example.org');
// メッセージ ID は ``<UUID@>`` (無効) の代わりに、
// ``<UUID@www.example.org>`` (有効) を返します。

正しいメッセージ ID は、迷惑メールフォルダーへ振り分けられることを防ぐのに役立ちます。

再利用可能なメールの作成

Mailer は、アプリケーション全体で再利用可能なメールを作成することができます。また、一ヶ所に複数のメール設定を格納するために使用することができます。これは、コードを DRY に保つことができますし、アプリケーション内の他の領域から、メールの設定ノイズを除外します。

この例では、ユーザー関連のメールが含まれている Mailer を作成します。 UserMailer を作成するには、 src/Mailer/UserMailer.php ファイルを作成します。ファイルの内容は次のようになります。

namespace App\Mailer;

use Cake\Mailer\Mailer;

class UserMailer extends Mailer
{
    public function welcome($user)
    {
        $this
            ->setTo($user->email)
            ->setSubject(sprintf('Welcome %s', $user->name))
            ->viewBuilder()
                ->setTemplate('welcome_mail'); // デフォルトでテンプレートはメソッドと同じ名前が使われます。
    }

    public function resetPassword($user)
    {
        $this
            ->setTo($user->email)
            ->setSubject('Reset password')
            ->setViewVars(['token' => $user->token]);
    }
}

この例では、2つのメソッドを作成しました。１つは、ウェルカムメールを送信するため、もう１つは、パスワードのリセットメールを送信するためのものです。これらの各メソッドは、ユーザー Entity を受け取り、各メールを設定するために、そのプロパティーを利用しています。

これで、アプリケーション内のどこからでも、ユーザー関連のメールを送信するために UserMailer を使用することができます。例えば、ウェルカムメールを送信したいのであれば、以下のようにするとよいでしょう。

namespace App\Controller;

use Cake\Mailer\MailerAwareTrait;

class UsersController extends AppController
{
    use MailerAwareTrait;

    public function register()
    {
        $user = $this->Users->newEmptyEntity();
        if ($this->request->is('post')) {
            $user = $this->Users->patchEntity($user, $this->request->getData())
            if ($this->Users->save($user)) {
                $this->getMailer('User')->send('welcome', [$user]);
            }
        }
        $this->set('user', $user);
    }
}

アプリケーションのコードからユーザーへのウェルカムメールの送信を完全に分離したい場合、 UserMailer が Model.afterSave イベントを受け取ることができます。イベントを受け取ることによって、アプリケーションのユーザー関連のクラスは、メール関連のロジックや命令から完全に解放されます。たとえば、 UserMailer に以下を追加することができます。

public function implementedEvents()
{
    return [
        'Model.afterSave' => 'onRegistration'
    ];
}

public function onRegistration(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
    if ($entity->isNew()) {
        $this->send('welcome', [$entity]);
    }
}

Mailer オブジェクトは、イベントリスナーとして登録され、 onRegistration() メソッドは Model.afterSave イベントが起こるたびに呼び出されます。

// Users イベントマネージャへアタッチする
$this->Users->getEventManager()->on($this->getMailer('User'));

注釈

イベントリスナーオブジェクトを登録する方法については、リスナーの登録 のドキュメントを参照してください。

トランスポートの設定

メールメッセージは、トランスポートによって配信されます。さまざまなトランスポートを使用すると、 PHP の mail() 関数や SMTP サーバーでメッセージを送信したり、デバッグが捗るようメッセージを送信しないこともできます。トランスポートを設定すると、アプリケーションのコードの外に、設定データを保持することができ、単純に設定データを変更できるのでデプロイが簡単になります。トランスポートの設定例は、次のようになります。

// config/app.php の中で
'EmailTransport' => [
    // Mail構成の例
    'default' => [
        'className' => 'Mail',
    ],
    // SMTP構成の例
    'gmail' => [
        'host' => 'smtp.gmail.com',
        'port' => 587,
        'username' => 'my@gmail.com',
        'password' => 'secret',
        'className' => 'Smtp',
        'tls' => true
    ]
],

TransportFactory::setConfig() を利用して設定することもできます。:

use Cake\Mailer\TransportFactory;

// STMPトランスポートを定義する
TransportFactory::setConfig('gmail', [
    'host' => 'ssl://smtp.gmail.com',
    'port' => 465,
    'username' => 'my@gmail.com',
    'password' => 'secret',
    'className' => 'Smtp'
]);

Gmail のように、SSL SMTP サーバーを設定することができます。これを行うには、 host に ssl:// プレフィックスをつけて、それに伴い port の値を設定してください。また、 tls オプションを使用して TLS SMTP を有効にすることもできます。

use Cake\Mailer\TransportFactory;

TransportFactory::setConfig('gmail', [
    'host' => 'smtp.gmail.com',
    'port' => 587,
    'username' => 'my@gmail.com',
    'password' => 'secret',
    'className' => 'Smtp',
    'tls' => true
]);

上記の設定では、メールメッセージの TLS 通信を可能にします。

特定のトランスポートを使用するようにメーラーを構成するには、 Cake\Mailer\Mailer::setTransport() メソッドを使用するか、設定にトランスポートを含めることができます。

// TransportFactory::setConfig() を使用して設定済の名前付きトランスポートを使用します。
$mailer->setTransport('gmail');

// 構築されたオブジェクトを使用します。
$mailer->setTransport(new \Cake\Mailer\Transport\DebugTransport());

警告

あなたのグーグルアカウントでこれを動作させるためには安全性の低いアプリへのアクセスを有効にする必要があります: 安全性の低いアプリがアカウントにアクセスするのを許可する [https://support.google.com/accounts/answer/6010255] 。

注釈

Gmail の SMTP 設定 [https://support.google.com/a/answer/176600?hl=ja] 。

注釈

SSL + SMTP を使用するには、PHP のインストール時に SSL が設定されている必要があります。

設定オプションは、 DSN 文字列として指定することもできます。これは、環境変数を使ったり PaaS プロバイダーで動作する場合に便利です。

TransportFactory::setConfig('default', [
    'url' => 'smtp://my@gmail.com:secret@smtp.gmail.com:587?tls=true',
]);

DSN 文字列を使用するときは、クエリー文字列引数として任意の追加のパラメーターやオプションを定義することができます。

static Cake\Mailer\Mailer::drop($key)

設定が完了すると、トランスポートを変更することはできません。トランスポートを変更するためには、まずこれを取り消してから再設定する必要があります。

独自のトランスポートの作成

SwiftMailer のような他のメールシステムを使うために独自のトランスポートを作成することができます。トランスポートを作るためには、(Example という名前のトランスポートの場合）最初に src/Mailer/Transport/ExampleTransport.php ファイルを作成してください。作成開始時点のファイルは次のようになります。

namespace App\Mailer\Transport;

use Cake\Mailer\AbstractTransport;
use Cake\Mailer\Message;

class ExampleTransport extends AbstractTransport
{
    public function send(Message $message): array
    {
        // 何かをします。
    }
}

独自のロジックで、 send(Mailer $mailer) メソッドを実装してください。

Mailerを利用しないメール送信

Mailer は、Cake\Mailer\Message、Cake\Mailer\Renderer、および``CakeMailerAbstractTransport`` 間の橋渡しをするより高いレベルの抽象化クラスであり、メールの設定と配信を簡単にするクラスです。

必要であれば、これらのクラスを `` Mailer`` で直接使用することもできます。

例

$render = new \Cake\Mailer\Renderer();
$render->viewBuilder()
    ->setTemplate('custom')
    ->setLayout('sparkly');

$message = new \Cake\Mailer\Message();
$message
    ->setFrom('admin@cakephp.org')
    ->setTo('user@foo.com')
    ->setBody($render->render());

$transport = new \Cake\Mailer\Transport\MailTransport();
$result = $transport->send($message);

Renderer の使用をスキップして、メッセージ本文を直接設定することもできます Message::setBodyText() および Message::setBodyHtml() メソッドを使用します。

メールのテスト

メールをテストするためには、テストケースに Cake\TestSuite\EmailTrait を追加します。 MailerTrait は、PHPUnitのフックを使用して、アプリケーションのメールトランスポートをプロキシに置き換えます。プロキシはメールのメッセージをインターセプトし、配信されるメールに対してアサーションを実行することを可能とします。

テストケースにトレイトを追加してメールのテストを開始します。メールでURLを生成する必要がある場合はルートを読み込みます。

namespace App\Test\TestCase\Mailer;

use App\Mailer\WelcomeMailer;
use App\Model\Entity\User;

use Cake\TestSuite\EmailTrait;
use Cake\TestSuite\TestCase;

class WelcomeMailerTestCase extends TestCase
{
    use EmailTrait;

    public function setUp(): void
    {
        parent::setUp();
        $this->loadRoutes();
    }
}

新しいユーザーが登録したときにウェルカムメールを配信するメーラーがあるとします。件名と本文にユーザーの名前が含まれていることを確認したい場合は、以下のようにします。

//  WelcomeMailerTestCase クラスにて
public function testName()
{
    $user = new User([
        'name' => 'Alice Alittea',
        'email' => 'alice@example.org',
    ]);
    $mailer = new WelcomeMailer();
    $mailer->send('welcome', [$user]);

    $this->assertMailSentTo($user->email);
    $this->assertMailContainsText('こんにちは。' . $user->name);
    $this->assertMailContainsText('CakePHPへようこそ！');
}

アサーションメソッド

Cake\TestSuite\EmailTrait トレイトは次のアサーションを提供します。

// 期待した数のメールが送信されたことをアサート
$this->assertMailCount($count);

// メールが送信されていないことをアサート
$this->assertNoMailSent();

// アドレスに対してメールが送信されたことをアサート
$this->assertMailSentTo($address);

// アドレスからメールが送信されたことをアサート
$this->assertMailSentFrom($address);

// メールに期待した内容が含まれていることをアサート
$this->assertMailContains($contents);

// メールに期待したHTMLコンテンツが含まれていることをアサート
$this->assertMailContainsHtml($contents);

// メールに期待したテキストコンテンツが含まれていることをアサート
$this->assertMailContainsText($contents);

// メールにメールゲッター内の期待値が含まれていることをアサート（例："subject"）
$this->assertMailSentWith($expected, $parameter);

// 特定のインデックスのメールがアドレスに対して送信されたことをアサート
$this->assertMailSentToAt($at, $address);

// 特定のインデックスのメールがアドレスから送信されたことをアサート
$this->assertMailSentFromAt($at, $address);

// 特定のインデックスのメールに期待した内容が含まれていることをアサート
$this->assertMailContainsAt($at, $contents);

// 特定のインデックスのメールに期待したHTMLコンテンツが含まれていることをアサート
$this->assertMailContainsHtmlAt($at, $contents);

// 特定のインデックスのメールに期待したテキストコンテンツが含まれていることをアサート
$this->assertMailContainsTextAt($at, $contents);

//  メールに添付ファイルが含まれていることをアサート
$this->assertMailContainsAttachment('test.png');

// 特定のインデックスのメールにメールゲッター内の期待値が含まれていることをアサート（例："subject"）
$this->assertMailSentWithAt($at, $expected, $parameter);

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

エラーと例外の処理

CakePHP アプリケーションには、エラー処理と例外処理が用意されています。 PHP エラーはトラップされ、表示またはログに記録されます。キャッチされなかった例外はエラーページに自動的にレンダリングされます。

エラーと例外の設定

エラーの設定はアプリケーションの config/app.php ファイル中で行われます。デフォルトでは、 CakePHP は PHP エラーと例外の両方を処理するために Cake\Error\ErrorHandler を使います。エラーの設定を使用すると、アプリケーションのエラー処理をカスタマイズできます。次のオプションをサポートします。

errorLevel - int - あなたが捕捉したいエラーレベル。組み込みの PHP エラー定数を使い、捕捉したいエラーレベルを選択するためにビットマスクします。非推奨の警告を無効にするために、 E_ALL ^ E_USER_DEPRECATED をセットすることができます。

trace - bool - ログファイル中にエラーのスタックトレースを含めます。スタックトレースはログ中の各エラーの後に含まれるでしょう。これはどこで／いつそのエラーが引き起こされたかを見つけるために役に立ちます。

exceptionRenderer - string - キャッチされなかった例外を描画する役目を担うクラス。もしもカスタムクラスを選択する場合は src/Error 中にそのクラスのファイルを置くべきです。このクラスは render() メソッドを実装する必要があります。

log - bool - true の時、 Cake\Log\Log によって例外とそのスタックトレースが Cake\Log\Log に記録されます。

skipLog - array - ログに記録されるべきではない例外クラス名の配列。これは NotFoundException や他のありふれた、でもログにはメッセージを残したくない例外を除外するのに役立ちます。

extraFatalErrorMemory - int - 致命的エラーが起きた時にメモリーの上限を増加させるためのメガバイト数を設定します。これはログの記録やエラー処理を完了するために猶予を与えます。

errorLogger - Cake\Error\ErrorLoggerInterface - エラーログの記録および未処理の例外を担当するクラス。デフォルトは Cake\Error\ErrorLogger

ignoredDeprecationPaths - array - 非推奨のエラーが無視されるべきパス（glob互換）のリスト。 4.2.0で追加

エラーハンドラーは既定では、 debug が true の時にエラーを表示し、 debug が false の時にエラーをログに記録します。いずれも捕捉されるエラータイプは errorLevel によって制御されます。致命的エラーのハンドラーは debug レベルや errorLevel とは独立して呼び出されますが、その結果は debug レベルによって変わるでしょう。致命的エラーに対する既定のふるまいは内部サーバーエラーページ (debug 無効) またはエラーメッセージ、ファイルおよび行を含むページ (debug 有効) を表示します。

注釈

もしカスタムエラーハンドラーを使うなら、サポートされるオプションはあなたのハンドラーに依存します。

非推奨の警告

CakePHPは、非推奨の警告を使用して、機能が非推奨になったことを示します。また、このシステムをプラグインやアプリケーションコードで使用することをお勧めします。 deprecationWarning() を使用して非推奨の警告をトリガーできます。

deprecationWarning('The example() method is deprecated. Use getExample() instead.');

CakePHPまたはプラグインをアップグレードすると、新しい非推奨の警告が表示される場合があります。いくつかの方法のいずれかで、非推奨の警告を一時的に無効にすることができます。

Error.errorLevel を E_ALL ^ E_USER_DEPRECATED に設定して、すべての非推奨警告を無視します。

Error.ignoredDeprecationPaths 設定オプションを使用して、glob互換の表現で非推奨を無視します。以下をご覧ください。

'Error' => [
    'ignoredDeprecationPaths' => [
        'vendors/company/contacts/*',
        'src/Models/*',
    ]
],

アプリケーションにおける Models ディレクトリと Contacts プラグインからのすべての非推奨を無視します。

バージョン 4.2.0 で追加: Error.ignoredDeprecationPaths オプションが追加されました。

class ExceptionRenderer(Exception $exception)

例外処理の変更

例外処理では、例外の処理方法を調整するいくつかの方法が用意されています。それぞれのアプローチでは、例外処理プロセスの制御量が異なります。

エラーテンプレートのカスタマイズ 描画されたビューテンプレートをアプリケーション内の他のテンプレートと同様に変更できます。

ErrorController のカスタマイズ 例外ページの描画方法を制御できます。

ExceptionRenderer のカスタマイズ 例外ページとロギングの実行方法を制御できます。

独自のエラーハンドラーの作成と登録 エラーと例外がどのように処理され、記録され、描画されるかを完全に制御することができます。

エラーテンプレートのカスタマイズ

デフォルトのエラーハンドラは、 Cake\Error\ExceptionRenderer とアプリケーションの ErrorController の助けを借りて、アプリケーションで発生した全ての捕捉されない例外を描画します。

エラーページのビューは templates/Error/ に配置されます。デフォルトでは、すべての 4xx エラーは error400.php テンプレートを使い、すべての 5xx エラーは error500.php を使います。エラーテンプレートの変数は次のとおりです。

message 例外メッセージ。

code 例外コード。

url リクエスト URL。

error 例外オブジェクト。

デバッグモードでエラーが Cake\Core\Exception\Exception を継承した場合、 getAttributes() によって返されたデータはビュー変数としても公開されます。

注釈

error404 と error500 テンプレートを表示するには debug を false に設定する必要があります。デバッグモードだと、 CakePHP の開発用エラーページが表示されます。

エラーページレイアウトのカスタマイズ

デフォルトでは、エラーテンプレートは、レイアウトに templates/layout/error.php を使います。 layout プロパティーを使って別のレイアウトを選ぶことができます。

// templates/Error/error400.php の中で
$this->layout = 'my_error';

上記は、エラーページのレイアウトとして templates/layout/my_error.php を使用します。 CakePHP によって引き起こされる多くの例外は、特定のビューテンプレートをデバッグモードで描画します。デバッグをオフにすると、CakePHP によって生成されたすべての例外は、ステータスコードに基づいて error400.php または error500.php のいずれかを使用します。

ErrorController のカスタマイズ

App\Controller\ErrorController クラスは CakePHP の例外レンダリングでエラーページビューを描画するために使われ、すべての標準リクエストライフサイクルイベントを受け取ります。このクラスを変更することで、どのコンポーネントが使用され、どのテンプレートが描画されるかを制御できます。

アプリケーション内で プレフィックスルーティング を利用している場合は、それぞれのルーティングプレフィックスに対してカスタムエラーコントローラーを作成できます。例えば、 Admin プレフィックスの場合は以下のクラスを作成することができます。

namespace App\Controller\Admin;

use App\Controller\AppController;
use Cake\Event\EventInterface;

class ErrorController extends AppController
{
    /**
     * Initialization hook method.
     *
     * @return void
     */
    public function initialize(): void
    {
        $this->loadComponent('RequestHandler');
    }

    /**
     * beforeRender callback.
     *
     * @param \Cake\Event\EventInterface $event Event.
     * @return void
     */
    public function beforeRender(EventInterface $event)
    {
        $this->viewBuilder()->setTemplatePath('Error');
    }
}

このコントローラーは、プレフィックス付きのコントローラーでエラーが発生したときにのみ利用できます。そして、必要に応じてプレフィックス固有のロジック/テンプレートを定義できます。

ExceptionRenderer の変更

例外レンダリングとロギングプロセス全体を制御したい場合は config/app.php の Error.exceptionRenderer オプションを使用して、例外ページをレンダリングするクラスを選択することができます。ExceptionRenderer の変更は、アプリケーション固有の例外クラスに対してカスタムエラーページを提供する場合に便利です。

カスタム例外レンダラークラスは src/Error に配置する必要があります。アプリケーションで App\Exception\MissingWidgetException を使用して欠落しているウィジェットを示すとしましょう。このエラーが処理されたときに特定のエラーページをレンダリングする例外レンダラーを作成することができます。

// src/Error/AppExceptionRenderer.php の中で
namespace App\Error;

use Cake\Error\ExceptionRenderer;

class AppExceptionRenderer extends ExceptionRenderer
{
    public function missingWidget($error)
    {
        $response = $this->controller->response;
        return $response->withStringBody('おっとウィジェットが見つからない！');
    }
}

// config/app.php の中で
'Error' => [
    'exceptionRenderer' => 'App\Error\AppExceptionRenderer',
    // ...
],
// ...

上記は MissingWidgetException 型のあらゆる例外を処理し、それらのアプリケーション例外を表示／処理するためのカスタム処理ができるようにします。

例外レンダリングメソッドは、引数として処理される例外を受け取り、 Response オブジェクトを返さなければなりません。また、CakePHP のエラーを処理する際にロジックを追加するメソッドを実装することもできます。

// src/Error/AppExceptionRenderer.php の中で
namespace App\Error;

use Cake\Error\ExceptionRenderer;

class AppExceptionRenderer extends ExceptionRenderer
{
    public function notFound($error)
    {
        // NotFoundException オブジェクトで何かをします。
    }
}

ErrorController クラスの変更

例外レンダラーは、例外の描画に使用されるコントローラーを指定します。例外を描画するコントローラーを変更したい場合は、例外レンダラーの _getController() メソッドをオーバーライドしてください。

// src/Error/AppExceptionRenderer の中で
namespace App\Error;

use App\Controller\SuperCustomErrorController;
use Cake\Error\ExceptionRenderer;

class AppExceptionRenderer extends ExceptionRenderer
{
    protected function _getController()
    {
        return new SuperCustomErrorController();
    }
}

// config/app.php の中で
'Error' => [
    'exceptionRenderer' => 'App\Error\AppExceptionRenderer',
    // ...
],
// ...

独自エラーハンドラーの作成

エラーハンドラーを置き換えることによって、エラーおよび例外処理プロセス全体をカスタマイズできます。 Cake\Error\BaseErrorHandler を継承することでエラーを処理するためのカスタムロジックを提供できます。たとえば、エラーを処理するために AppError というクラスを使うことができます。

// config/bootstrap.php の中で
use App\Error\AppError;

$errorHandler = new AppError();
$errorHandler->register();

// src/Error/AppError.php の中で
namespace App\Error;

use Cake\Error\ErrorHandler;
use Throwable;

class AppError extends ErrorHandler
{
    protected function _displayError(array $error, bool $debug): void
    {
        echo 'エラーがありました！';
    }

    protected function _displayException(Throwable $exception): void
    {
        echo '例外がありました！';
    }
}

Then we can register our error handler as the PHP error handler:

// In config/bootstrap.php
use App\Error\AppError;

if (PHP_SAPI !== 'cli') {
    $errorHandler = new AppError();
    $errorHandler->register();
}

Finally, we can use our error handler in the ErrorHandlerMiddleware:

// in src/Application.php
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $error = new AppError(Configure::read('Error'));
    $middleware->add(new ErrorHandlerMiddleware($error));

    return $middleware;
}

For console error handling, you should extend Cake\Error\ConsoleErrorHandler instead:

// In /src/Error/AppConsoleErrorHandler.php
namespace App\Error;
use Cake\Error\ConsoleErrorHandler;

class AppConsoleErrorHandler extends ConsoleErrorHandler {

    protected function _displayException(Throwable $exception): void {
        parent::_displayException($exception);
        if (isset($exception->queryString)) {
            $this->_stderr->write('Query String: ' . $exception->queryString);
        }
    }

}

BaseErrorHandler は二つの抽象メソッドを定義しています。 _displayError() はエラーが引き起こされた時に使われます。 _displayException() メソッドはキャッチされなかった例外がある時に呼ばれます。

致命的エラーのふるまい変更

既定のエラーハンドラーは致命的エラーを例外に変換しエラーページを描画するための例外処理方法を再利用します。もし標準のエラーページを表示したくない場合は、あなたはそれをオーバーライドできます。

// src/Error/AppError.php の中で
namespace App\Error;

use Cake\Error\BaseErrorHandler;

class AppError extends BaseErrorHandler
{
    // 他のメソッド

    public function handleFatalError(int $code, string $description, string $file, int $line): bool
    {
        echo '致命的エラーが発生しました';
    }
}

Custom Error Logging

Error handlers use instances of Cake\Error\ErrorLoggingInterface to create log messages and log them to the appropriate place. You can replace the error logger using the Error.errorLogger configure value. An example error logger:

namespace App\Error;

use Cake\Error\ErrorLoggerInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;

/**
 * Log errors and unhandled exceptions to `Cake\Log\Log`
 */
class ErrorLogger implements ErrorLoggerInterface
{
    /**
     * @inheritDoc
     */
    public function logMessage($level, string $message, array $context = []): bool
    {
        // Log PHP Errors
    }

    public function log(Throwable $exception, ?ServerRequestInterface $request = null): bool
    {
        // Log exceptions.
    }
}

バージョン 4.1.0 で追加: ErrorLoggerInterface was added.

独自アプリケーション例外の作成

組み込みの SPL の例外 [https://php.net/manual/en/spl.exceptions.php] 、 Exception そのもの、または Cake\Core\Exception\Exception のいずれかを使って、独自のアプリケーション例外を作ることができます。もしアプリケーションが以下の例外を含んでいたなら:

use Cake\Core\Exception\Exception;

class MissingWidgetException extends Exception
{
}

templates/Error/missing_widget.php を作ることで、素晴らしい開発用エラーを提供できるでしょう。本番モードでは、上記のエラーは 500 エラーとして扱われ、 error500 テンプレートを使用するでしょう。

例外コードが 400 と 506 の間にある場合、例外コードは HTTP レスポンスコードとして使用されます。

Cake\Core\Exception\Exception のコンストラクターが継承されており、追加のデータを渡すことができます。それら追加のデータは _messageTemplate に差し込まれます。これにより、エラー用の多くのコンテキスト提供して、データ豊富な例外を作ることができます。

use Cake\Core\Exception\Exception;

class MissingWidgetException extends Exception
{
    // コンテキストデータはこのフォーマット文字列に差し込まれます。
    protected $_messageTemplate = '%s が見当たらないようです。';

    // デフォルトの例外コードも設定できます。
    protected $_defaultCode = 404;
}

throw new MissingWidgetException(['widget' => 'Pointy']);

レンダリングされると、このビューテンプレートには $widget 変数が設定されます。もしその例外を文字列にキャストするかその getMessage() メソッドを使うと Pointy が見当たらないようです。 を得られるでしょう。

例外のログ記録

組み込みの例外処理を使うと、 config/app.php 中で log オプションに true を設定することで ErrorHandler によって対処されるすべての例外をログに記録することができます。これを有効にすることで Cake\Log\Log と設定済みのロガーに各例外の記録が残るでしょう。

注釈

もしもカスタム例外ハンドラーを使用している場合、あなたの実装の中でそれを参照しない限り、この設定は効果がないでしょう。

CakePHP 用の組み込みの例外

HTTP の例外

CakePHP 内部のいくつかの組み込みの例外には、内部的なフレームワークの例外の他に、 HTTP メソッド用のいくつかの例外があります。

exception Cake\Http\Exception\BadRequestException

400 Bad Request エラーに使われます。

exception Cake\Http\Exception\UnauthorizedException

401 Unauthorized エラーに使われます。

exception Cake\Http\Exception\ForbiddenException

403 Forbidden エラーに使われます。

exception Cake\Http\Exception\InvalidCsrfTokenException

無効な CSRF トークンによって引き起こされた 403 エラーに使われます。

exception Cake\Http\Exception\NotFoundException

404 Not Found エラーに使われます。

exception Cake\Http\Exception\MethodNotAllowedException

405 Method Not Allowed エラーに使われます。

exception Cake\Http\Exception\NotAcceptableException

406 Not Acceptable エラーに使われます。

exception Cake\Http\Exception\ConflictException

409 Conflict エラーに使われます。

exception Cake\Http\Exception\GoneException

410 Gone エラーに使われます。

HTTP 4xx エラーステータスコードの詳細は RFC 2616#section-10.4 [https://datatracker.ietf.org/doc/html/rfc2616.html#section-10.4] をご覧ください。

exception Cake\Http\Exception\InternalErrorException

500 Internal Server Error に使われます。

exception Cake\Http\Exception\NotImplementedException

501 Not Implemented エラーに使われます。

exception Cake\Http\Exception\ServiceUnavailableException

503 Service Unavailable エラーに使われます。

HTTP 5xx エラーステータスコードの詳細は RFC 2616#section-10.5 [https://datatracker.ietf.org/doc/html/rfc2616.html#section-10.5] をご覧ください。

失敗の状態や HTTP エラーを示すためにあなたのコントローラーからこれらの例外を投げることができます。 HTTP の例外の使用例はアイテムが見つからなかった場合に 404 ページを描画することでしょう。

use Cake\Http\Exception\NotFoundException;

public function view($id = null)
{
    $article = $this->Articles->findById($id)->first();
    if (empty($article)) {
        throw new NotFoundException(__('記事が見つかりません'));
    }
    $this->set('article', $article);
    $this->viewBuilder()->setOption('serialize', ['article']);
}

HTTP エラー用の例外を使うことで、あなたのコードを綺麗にし、かつ RESTful なレスポンスをアプリケーションのクライアントやユーザーに返すことができます。

コントローラー中での HTTP の例外の使用

失敗の状態を示すためにコントローラーのアクションからあらゆる HTTP 関連の例外を投げることができます。例:

use Cake\Network\Exception\NotFoundException;

public function view($id = null)
{
    $article = $this->Articles->findById($id)->first();
    if (empty($article)) {
        throw new NotFoundException(__('記事が見つかりません'));
    }
    $this->set('article', 'article');
    $this->viewBuilder()->setOption('serialize', ['article']);
}

上記は NotFoundException をキャッチして処理するための例外ハンドラーを設定するでしょう。デフォルトではエラーページを作り、例外をログに記録するでしょう。

その他の組み込みの例外

さらに、CakePHP は次の例外を使用します。

exception Cake\View\Exception\MissingViewException

選択されたビュークラスが見つかりません。

exception Cake\View\Exception\MissingTemplateException

選択されたテンプレートファイルが見つかりません。

exception Cake\View\Exception\MissingLayoutException

選択されたレイアウトが見つかりません。

exception Cake\View\Exception\MissingHelperException

選択されたヘルパーが見つかりません。

exception Cake\View\Exception\MissingElementException

選択されたエレメントのファイルが見つかりません。

exception Cake\View\Exception\MissingCellException

選択されたセルクラスが見つかりません。

exception Cake\View\Exception\MissingCellViewException

選択されたセルのビューファイルが見つかりません。

exception Cake\Controller\Exception\MissingComponentException

設定されたコンポーネントが見つかりません。

exception Cake\Controller\Exception\MissingActionException

要求されたコントローラーのアクションが見つかりません。

exception Cake\Controller\Exception\PrivateActionException

private／protected／_ が前置されたアクションへのアクセス。

exception Cake\Console\Exception\ConsoleException

コンソールライブラリークラスがエラーに遭遇しました。

exception Cake\Console\Exception\MissingTaskException

設定されたタスクが見つかりません。

exception Cake\Console\Exception\MissingShellException

シェルクラスが見つかりません。

exception Cake\Console\Exception\MissingShellMethodException

選択されたシェルクラスが該当の名前のメソッドを持っていません。

exception Cake\Database\Exception\MissingConnectionException

モデルの接続がありません。

exception Cake\Database\Exception\MissingDriverException

データベースドライバーが見つかりません。

exception Cake\Database\Exception\MissingExtensionException

データベースドライバーのための PHP 拡張がありません。

exception Cake\ORM\Exception\MissingTableException

モデルのテーブルが見つかりません。

exception Cake\ORM\Exception\MissingEntityException

モデルのエンティティーが見つかりません。

exception Cake\ORM\Exception\MissingBehaviorException

モデルのビヘイビアーが見つかりません。

exception Cake\ORM\Exception\PersistenceFailedException

Cake\ORM\Table::saveOrFail() や Cake\ORM\Table::deleteOrFail() を使用しましたが、エンティティーは、保存/削除されませんでした。

exception Cake\Datasource\Exception\RecordNotFoundException

要求されたレコードが見つかりません。これにより HTTP 応答ヘッダーも 404 に設定されます。

exception Cake\Routing\Exception\MissingControllerException

要求されたコントローラーが見つかりません。

exception Cake\Routing\Exception\MissingRouteException

要求された URL はルーティングの逆引きができないか解析できません。

exception Cake\Routing\Exception\MissingDispatcherFilterException

ディスパッチャーフィルターが見つかりません。

exception Cake\Core\Exception\Exception

CakePHP での基底例外クラス。 CakePHP によって投げられるすべてのフレームワーク層の例外はこのクラスを継承するでしょう。

これらの例外クラスはすべて Exception を継承します。 Exception を継承することにより、あなたは独自の‘フレームワーク’エラーを作ることができます。

Cake\Core\Exception\Exception::responseHeader($header = null, $value = null)

Cake\Network\Request::header() をご覧ください。

すべての Http と Cake の例外は Exception クラスを継承し、レスポンスにヘッダーを追加するためのメソッドを持っています。例えば、405 MethodNotAllowdException を投げる時、RFC2616 によると:

"The response MUST include an Allow header containing a list of valid
methods for the requested resource."

「レスポンスは要求されたリソースに有効なメソッドの一覧を含むAllowヘッダーを含まなければ【ならない】」

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

イベントシステム

メンテナンス性の高いアプリケーションの創造は、科学でもあり芸術でもあります。良く知られていることですが、高い品質のコードを保持するための鍵は、オブジェクトが疎結合すると同時に、高い凝集度も合わせ持つということです。結合が疎であるということが、あるクラスがいかに少ししか外部のオブジェクトに "束縛されて" おらず、どの程度そのクラスがそれらの外部オブジェクトに依存しているかの指標となる一方で、高い凝集度は、クラスの全てのメソッドおよびプロパティーがそのクラス自身と強く関連を持ちつつ他のオブジェクトがやるべき仕事をしようとはしないということを意味します。

凝集度が失われ、クラスの結合度が増加してしまわないように、依存関係をガチガチにコードすることなくシステム内の別の箇所とクリーンにやりとりすることが必要な場面も確かにあります。 Observer パターンを使用すると、オブジェクトがイベントを発生させることができ、無名のリスナーに対して内部状態の変化について通知することができるので、この目的を達成するのに便利なパターンです。

Observer パターンにおけるリスナーは、そのようなイベントを受信することが可能で、それらに基づいて振る舞いを選択したり、サブジェクトの状態を変更したり、単に何かを記録したりします。もしあなたがすでに JavaScript を使っていたなら、すでにイベント駆動プログラミングに親しんでいることでしょう。

CakePHP は、jQuery などの一般的な JavaScript フレームワークにおいてイベントがトリガーされ管理される方法のいくつかの側面をエミュレートします。CakePHP の実装においてイベントオブジェクトは、全てのリスナーに行き渡ります。イベントオブジェクトは、イベントに関する情報を持ち、任意のポイントでイベントの伝播を止めることができます。リスナーは自分自身を登録することが可能であるか、もしくは他のオブジェクトにそのタスクを委任することができ、まだ実行されていないコールバックのために、状態とイベント自体を変更する機会を持ちます。

イベントシステムは、モデル・ビヘイビアー・コントローラー・ビュー・ヘルパーのコールバックの心臓部にあたります。もし、あなたがこれらをいつも使用しているなら、すでにある程度、CakePHP のイベントに親しんでいることになります。

イベントの使用例

ショッピングカートプラグインを構築していて、注文ロジックの操作を行いたい場合を考えてみましょう。ユーザーへのメール送信や在庫から商品を減らすことをショッピングのロジックに含めたくはありません。しかし、これらはプラグインを使用する人にとって重要なタスクです。もし、イベントを使用しないなら、これをモデルにビヘイビアーを適用したり、コントローラーにコンポーネントを追加することで実装しようとするかもしれません。そうすることは、外部でビヘイビアーをロードしたりプラグインコントローラーにフックを取り付けるためにコードを用意しなくてはならないので、多くの時間を費やすことを意味します。

一方、コードの関心事を明確に分離するためにイベントを使用することができます。そして、イベントを使用しているプラグインにフックする関心事を追加することができます。例えば、カートプラグイン中に、注文の作成を処理する Order モデルがあるとします。このアプリケーションで注文が作成される合間に通知したい。そんな時、 Order モデルを綺麗に保つためにイベントを使用できます。

// Cart/Model/Table/OrdersTable.php
namespace Cart\Model\Table;

use Cake\Event\Event;
use Cake\ORM\Table;

class OrdersTable extends Table
{
    public function place($order)
    {
        if ($this->save($order)) {
            $this->Cart->remove($order);
            $event = new Event('Model.Order.afterPlace', $this, [
                'order' => $order
            ]);
            $this->getEventManager()->dispatch($event);
            return true;
        }
        return false;
    }
}

バージョン 3.5.0 で非推奨: 代わりに getEventManager() を使用してください。

上記のコードは、注文が作成されていることをアプリケーションの別のパーツに通知することを簡単にできます。例えば、メール通知の送信、在庫の更新、ログに関する分析、その他のタスクのような関心ごとに注目した独立したオブジェクトの中で実行することができます。

イベントマネージャーへのアクセス

CakePHP の中でイベントはイベントマネージャーに対して動作します。イベントマネージャーは、全てのモデル、ビュー、コントローラーの中で getEventManager() を使用して取得されます。

$events = $this->getEventManager();

ビューやコントローラーで共有している各モデルは独立したイベントマネージャーを持ちます。これは、モデルイベントを自分自身に含むことができ、もし必要であれば、ビューで作成されたイベントをコンポーネントやコントローラーに対して作用させることができます。

グローバルイベントマネージャー

インスタンスレベルのイベントマネージャーに加えて、 CakePHP はアプリケーション内で起こる任意のイベントを受け取ることができるグローバルイベントマネージャーを提供します。これは、面倒で難しい具体的なインスタンスに対してリスナーをアタッチする時に便利です。グローバルマネージャーは Cake\Event\EventManager のシングルトンインスタンスです。グローバルディスパッチャーに登録されたリスナーは、同じ優先度でインスタンスのリスナーよりも前に実行されます。静的メソッドを使用してグローバルマネージャーにアクセスすることができます。

// イベントの前に実行される任意の設定ファイルやコードの一部の中で
use Cake\Event\EventManager;

EventManager::instance()->on(
    'Model.Order.afterPlace',
    $aCallback
);

重要なことは、同じ名前で異なる内容を持っているイベントがあることを考慮すべきということです。そして、グローバルに割り当てられた任意の機能の中のバグを防ぐためにイベントオブジェクトをチェックすることがいつも必要です。グローバルマネージャーを使用する柔軟性により、複雑さが増すことに注意してください。

Cake\Event\EventManager::dispatch() メソッドは、引数としてイベントオブジェクトを受け取り、すべてのリスナーとコールバックにこのオブジェクトを伝達させながら通知します。リスナーは、 afterPlace イベントの余分なロジックをすべて処理し、時間を記録したり、メールを送信したり、別のオブジェクトにユーザー統計を更新したり、必要に応じてオフラインタスクに委任することもできます。

イベントの追跡

特定の EventManager から実行されるイベントのリストを維持するために、イベントの追跡を有効にすることができます。これを行うには、マネージャーに Cake\Event\EventList を登録してください。

EventManager::instance()->setEventList(new EventList());

マネージャーでイベントを実行した後は、イベントリストからそれを取得することができます。

$eventsFired = EventManager::instance()->getEventList();
$firstEvent = $eventsFired[0];

追跡は、イベントリストを削除したり、 Cake\Event\EventList::trackEvents(false) を呼ぶことで無効にできます。

コアイベント

アプリケーションが受け取れるフレームワーク内のコアイベントが沢山あります。 CakePHP の各レイヤーで、アプリケーションで使用できるイベントを発行します。

ORM/Model イベント

コントローラーイベント

ビューイベント

リスナーの登録

リスナーは、イベントのためにコールバックを登録するための好ましい方法です。これは、コールバックをいくつか登録したいとあなたが望む任意のクラスに対し Cake\Event\EventListenerInterface インターフェイスを実装することによって実現されます。このインターフェイスを実装しているクラスは、 implementedEvents() メソッドを提供し、クラスが処理するすべてのイベント名を持つ連想配列を返す必要があります。

それでは先ほどの例につづき、ユーザーの購入履歴を計算しグローバルサイトの統計をまとめる役割を果たす UserStatistic クラスがあると仮定しましょう。これは、リスナークラスを使うとても良い機会です。一ヶ所に統計ロジックを集中することでき、イベントに対して必要な反応ができます。 UserStatistics リスナーは以下のように開始します。

use Cake\Event\EventListenerInterface;

class UserStatistic implements EventListenerInterface
{
    public function implementedEvents(): array
    {
        return [
            'Model.Order.afterPlace' => 'updateBuyStatistic',
        ];
    }

    public function updateBuyStatistic($event)
    {
        // 統計値を更新するコード
    }
}

// UserStatistic オブジェクトを Order のイベントマネージャーに追加
$statistics = new UserStatistic();
$this->Orders->getEventManager()->on($statistics);

上記のコードを見るとわかるように、 on() 関数は EventListener インターフェイスのインスタンスを受け取ります。内部的には、イベント·マネージャーは implementedEvents() メソッドを使用して、正しいコールバックを追加します。

無名リスナーの登録

イベントリスナーオブジェクトがリスナーを実装するために一般的に良いやり方ですが、イベントリスナーとして任意の callable をバインドすることもできます。例えば、ログファイルに注文を書き込みたい場合、そのためには無名関数が使えます。

use Cake\Log\Log;

$this->Orders->getEventManager()->on('Model.Order.afterPlace', function ($event) {
    Log::write(
        'info',
        'A new order was placed with id: ' . $event->getSubject()->id
    );
});

無名関数に加えてその他の PHP がサポートする呼び出し可能な形式を使用することもできます。

$events = [
    'email-sending' => 'EmailSender::sendBuyEmail',
    'inventory' => [$this->InventoryManager, 'decrement'],
];
foreach ($events as $callable) {
    $eventManager->on('Model.Order.afterPlace', $callable);
}

特定のイベントをトリガーしないプラグインを操作するときは、デフォルトのイベントにイベントリスナーを活用することができます。ユーザーからのお問い合わせフォームを扱う「UserFeedback」プラグインを例にあげましょう。アプリケーションからは、フィードバックレコードが保存されたことを検知し、最終的には、それに基づいて行動したいと思います。グローバルな Model.afterSave イベントを受け取ることができことができます。ただし、より直接的なアプローチを取ることができ、本当に必要とするイベントのみ受け取ることができます。

// 保存操作の前に、以下を作成することが
// できます。 config/bootstrap.php で、
use Cake\ORM\TableRegistry;
// メールを送信する場合
use Cake\Mailer\Email;

FactoryLocator::get('Table')->get('ThirdPartyPlugin.Feedbacks')
    ->getEventManager()
    ->on('Model.afterSave', function($event, $entity)
    {
        // 例えば、管理者のメールを送信することができます。
        // 3.4 より前は from()/to()/subject() メソッドを使用してください。
        $email = new Email('default');
        $email->setFrom(['info@yoursite.com' => 'Your Site'])
            ->setTo('admin@yoursite.com')
            ->setSubject('New Feedback - Your Site')
            ->send('Body of message');
    });

リスナーオブジェクトをバインドするために、これと同じアプローチを使用することができます。

既存のリスナーとの対話

いくつかのイベントリスナーが登録されていると仮定すると、特定のイベントパターンの有無を、ある動作の基礎として使用できます。

// EventManager にリスナーを追加
$this->getEventManager()->on('User.Registration', [$this, 'userRegistration']);
$this->getEventManager()->on('User.Verification', [$this, 'userVerification']);
$this->getEventManager()->on('User.Authorization', [$this, 'userAuthorization']);

// アプリケーションのどこか別の場所で
$events = $this->getEventManager()->matchingListeners('Verification');
if (!empty($events)) {
    // 'Verification' イベントリスナーが存在する場合のロジックを実行。
    // 例えば、存在するリスナーを削除。
    $this->getEventManager()->off('User.Verification');
} else {
    // 'Verification' イベントリスナーが存在しない場合のロジックを実行。
}

注釈

matchingListeners メソッドに渡されたパターンは、大文字と小文字が区別されます。

優先順位の設定

いくつかのケースでは、リスナーを実行する順番を制御したいこともあるでしょう。例としてユーザーの統計情報の場合についてもう一度考えて見ましょう。このリスナーがスタックの最後に呼び出されることが理想的です。リスナースタックの最後にそれを呼び出すことによって、イベントがキャンセルされなかったことや、他のリスナーが例外を発生させていないことを確認できます。他のリスナーがサブジェクトやイベントオブジェクトを変更した場合、オブジェクトの最終状態を得ることができます。

優先順位は、リスナーに追加する際に整数値として定義されます。数字が大きいほど、後に実行されるメソッドです。すべてのリスナーのデフォルトの優先度は 10 に設定されています。もしメソッドをもっと早く実行したい場合は、このデフォルト値よりも小さい任意の値を使用することで動作します。逆に、コールバックを他よりもあとに実行させたいなら、 10 よりも大きい数字を使用してください。

2つのコールバックが同じ優先順位キューに割り当てられるた場合は、追加された順番に実行されます。コールバックの優先順位を設定するためには on() メソッドを用い、 リスナーの優先順位を設定するためには implementedEvent() 関数内での宣言を行います。

// コールバックの優先順位を設定
$callback = [$this, 'doSomething'];
$this->getEventManager()->on(
    'Model.Order.afterPlace',
    ['priority' => 2],
    $callback
);

// リスナーの優先順位を設定
class UserStatistic implements EventListenerInterface
{
    public function implementedEvents()
    {
        return [
            'Model.Order.afterPlace' => [
                'callable' => 'updateBuyStatistic',
                'priority' => 100
            ],
        ];
    }
}

ご覧のとおり、 EventListener オブジェクトにおける主な違いは、呼び出し可能なメソッドと優先順位を指定するために配列を使用する必要があるということです。 callable キーはマネージャーがクラス内のどのような関数が呼ばれるべきかを知るために読み込むであろう、特別な配列エントリーです。

イベントデータを関数のパラメーターとして取得

イベントがそのコンストラクターに渡されたデータを持っている場合、渡されたデータは、リスナーの引数に変換されます。ビュー層の afterRender のコールバックの例です。

$this->getEventManager()
    ->dispatch(new Event('View.afterRender', $this, ['view' => $viewFileName]));

View.afterRender コールバックのリスナーは、次のシグネチャを持つ必要があります。

function (Event $event, $viewFileName)

イベントコンストラクターに渡される各値は、データ配列に表示されている順序で関数のパラメーターに変換されます。連想配列を使用する場合は、 array_values の結果が、関数の引数の順序を決定します。

注釈

2.x とは異なり、リスナーの引数にイベントデータを変換することは、デフォルトの振る舞いで、無効にすることはできません。

イベントのディスパッチ

一度、イベントマネージャーのインスタンスを取得すると、 dispatch() メソッドを使ってイベントをディスパッチできます。このメソッドは Cake\Event\Event クラスのインスタンスを受け取ります。さぁ、イベントをディスパッチしてみましょう。

// イベントをディスパッチする前に、イベントリスナーをインスタンス化する必要があります。
// 新しいイベントの作成とディスパッチ。
$event = new Event('Model.Order.afterPlace', $this, [
    'order' => $order
]);
$this->getEventManager()->dispatch($event);

Cake\Event\Event は、コンストラクターに3つの引数を受け取ります。最初のものはイベント名で、読みやすくすると同時にできるだけ唯一性を維持することを心掛けてください。次のような規則をお勧めします: レイヤーレベルで発生する一般的なイベントのための Layer.eventName (例えば Controller.startup, View.beforeRender) 、そして、あるレイヤーの特定のクラスで発生するイベントのための Layer.Class.eventName 、例えば Model.User.afterRegister や Controller.Courses.invalidAccess です。

2番目の引数は subject です。サブジェクトとはイベントに関連付けられているオブジェクトを意味し、通常それ自身に関するイベントをトリガーしているものと同じクラスであり、 $this の使用が一般的なケースとなります。とは言え、コンポーネントがコントローラーイベントをトリガーしたりもできます。サブジェクトクラスは重要です。なぜなら、リスナーがオブジェクトのプロパティーへの即時アクセスを取得し、それらを動的に検査したり変更するチャンスを持てるようになるからです。

最後に、3番目の引数はイベントのパラメーターです。これは、リスナーがそれに基づいて行動できるようにするための任意のデータです。これは、どのような型の引数でも指定できますが、検査を容易にするために連想配列を渡すことをお勧めします。

dispatch() メソッドは、引数としてイベントオブジェクトを受け取り、すべてのリスナーとコールバックにこのオブジェクトを伝達させながら通知します。

イベントの中止

DOM イベントのように、追加のリスナーへ通知されることを防ぐためにイベントを中止したいときがあるかもしれません。それ以上処理を進めることができないことをコードが検出した時に保存操作を中止できるモデルのコールバック (例えば beforeSave) の動作から分かります。

イベントを中止するためには、コールバックで false を返すか、またはイベントオブジェクトで stopPropagation() メソッドを呼び出すかのいずれかを行うことができます。

public function doSomething($event)
{
    // ...
    return false; // イベントを中止
}

public function updateBuyStatistic($event)
{
    // ...
    $event->stopPropagation();
}

イベントの中止は追加のコールバックが呼び出される事を妨げます。それに加え、イベントを発生させるコードは、イベントが中止させられるかそうでないかを元に振る舞いを変えることができます。一般的に、イベントの '後 (after)' に中止することに意味はありませんが、 イベントの '前 (before)' に中止する事は、全ての操作が起こる事を防止するためにしばしば使用されます。

イベントが中止されたかどうかを確認するには、イベントオブジェクトの isStopped() メソッドを呼び出します。

public function place($order)
{
    $event = new Event('Model.Order.beforePlace', $this, ['order' => $order]);
    $this->getEventManager()->dispatch($event);
    if ($event->isStopped()) {
        return false;
    }
    if ($this->Orders->save($order)) {
        // ...
    }
    // ...
}

上記の例では、イベントが beforePlace の処理の間に中止した場合は、注文内容は保存されません。

イベントの結果の取得

コールバックが null や false 以外の値を返すたびに、それはイベントオブジェクトの $result プロパティーに格納されます。これは、コールバックでイベントの実行を変更したい時に便利です。再び beforePlace を例にとり、コールバックが $order データを変更してみましょう。

イベントの結果は、イベントオブジェクトの result プロパティーを直接用いるか、またはコールバック自体の値を返すことで変更できます。

// リスナーコールバック
public function doSomething($event)
{
    // ...
    $alteredData = $event->getData('order') + $moreData;
    return $alteredData;
}

// 別のリスナーコールバック
public function doSomethingElse($event)
{
    // ...
    $event->setResult(['order' => $alteredData] + $this->result());
}

// イベントの結果を使用
public function place($order)
{
    $event = new Event('Model.Order.beforePlace', $this, ['order' => $order]);
    $this->getEventManager()->dispatch($event);
    if (!empty($event->getResult()['order'])) {
        $order = $event->getResult()['order'];
    }
    if ($this->Orders->save($order)) {
        // ...
    }
    // ...
}

任意のイベントオブジェクトのプロパティーを変更し、次のコールバックに渡された新たなデータを有することが可能です。ほとんどの場合、オブジェクトがイベントデータまたは結果として提供され、オブジェクトを直接変更することは、参照が同じに保たれ、すべてのコールバック呼び出しで変更が共有されるため、最適なソリューションです。

コールバック及びリスナーの削除

何らかの理由でイベントマネージャーから任意のコールバックを削除したい場合は、 Cake\Event\EventManager::off() を引数の最初の2つのパラメーターを追加のときと同様の用い方で呼び出すだけで良いです。

// 関数の追加
$this->getEventManager()->on('My.event', [$this, 'doSomething']);

// 関数の削除
$this->getEventManager()->off('My.event', [$this, 'doSomething']);

// 無名関数の追加
$myFunction = function ($event) { ... };
$this->getEventManager()->on('My.event', $myFunction);

// 無名関数の削除
$this->getEventManager()->off('My.event', $myFunction);

// EventListener の追加
$listener = new MyEventLister();
$this->getEventManager()->on($listener);

// リスナーから単一のイベントキーを削除
$this->getEventManager()->off('My.event', $listener);

// リスナーで実装された全てのコールバックを削除
$this->getEventManager()->off($listener);

イベントはあなたのアプリケーション内の関心事を分離させる偉大な方法であり、クラスに凝集と疎結合の両方をもたらします。イベントは、アプリケーションコードの疎結合や拡張可能なプラグインの作成に利用できます。

偉大な力には偉大な責任が伴うことを心に留めておいてください。イベントを利用すればするほど、デバッグが難しくなり、追加の結合テストが必要になります。

その他の情報

ビヘイビアー

コンポーネント

ヘルパー

イベントのテスト

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

国際化と地域化

アプリケーションをより多くのユーザーに届けるのに最も良い方法の一つは、複数の言語に対応することです。これは、しばしば気が遠くなるような作業になります。しかし、CakePHP の国際化と地域化の機能は、これを容易にします。

まずは、いくつかの専門用語について理解しましょう。国際化 (internationalization) とは、あるアプリケーションを地域化できるようにすることです。地域化 (localization) とは、あるアプリケーションを特定の言語や文化での表現( すなわちロケール (locale)) に適応させることです。国際化と地域化は、それぞれ「i18n」と「l10n」というように省略されます。「internationalization (国際化)」の最初と最後の文字の間に 18文字あるから「i18n」となり、「localization (地域化)」も同様の理由で「l10n」となります。

翻訳の準備

単一言語のアプリケーションから、複数言語のアプリケーションに移行するためには、数ステップを踏むだけです。最初のステップは、 __() 関数をあなたのコードで使用することです。以下が単一言語アプリケーションのとあるコードの例です。

<h2>Popular Articles</h2>

あなたのコードを国際化するためには、以下のように __() で文字列を囲んでください。

<h2><?= __('Popular Articles') ?></h2>

これ以上何もしなければ、上記の２つのコードの例は、機能的に同じです。それらは、両方ともブラウザーに同じ内容を送信します。 __() 関数は、与えられた文字列を翻訳がある場合は翻訳し、そうでなければ何も変更せずに返します。

言語ファイル

翻訳はアプリケーションの中にある言語ファイルを使って有効になります。 CakePHP 翻訳ファイルのデフォルトの形式は、 Gettext [https://en.wikipedia.org/wiki/Gettext] です。ファイルは resources/locales/ 以下に置かれる必要があります。各言語用のサブフォルダーは以下のようになっている必要があります。

/resources
    /locales
        /en_US
            default.po
        /en_GB
            default.po
            validation.po
        /es
            default.po

デフォルトのドメインは 'default' です。ロケールフォルダーは上記のように少なくとも default.po ファイルを含まなくてはなりません。ドメインは翻訳メッセージの任意のグルーピングを参照します。グループが使われていない場合、デフォルトのグループが選択されます。

CakePHP のライブラリーから抜き出されたコアの文字列は resources/locales/ 内の cake.po という名前のファイルに分けて置かれます。 CakePHP localized library [https://github.com/cakephp/localized] は、コア (Cake のドメイン) の中にクライエント・フェイシングな翻訳文字列を置いています。これらのファイルを利用するには、期待された場所 resources/locales/<locale>/cake.po にリンクまたはコピーをしてください。もし不完全または正しくない場合は、修正するためにこのリポジトリーに PR を送ってください。

プラグインは、翻訳ファイルをも含みます。プラグインの名前が under_scored なバージョンのものを、翻訳メッセージのドメインとして利用する方法は以下の通りです。

MyPlugin
    /resources
        /locales
            /fr
                my_plugin.po
            /de
                my_plugin.po

翻訳フォルダーは、2文字または3文字の言語 ISO コード、または、言語及び話されている国を含む fr_FR, es_AR, da_DK のような完全なロケールの名称にしてください。

https://www.localeplanet.com/icu/ (参考)

翻訳ファイルの具体例は以下のようになります。

msgid "My name is {0}"
msgstr "Je m'appelle {0}"

msgid "I'm {0,number} years old"
msgstr "J'ai {0,number} ans"

注釈

翻訳はキャッシュされています。翻訳を変更した後は、必ずキャッシュをクリアしてください。キャッシュツール を使って、例えば bin/cake cache clear _cake_core_ を実行するか、手動で tmp/cache/persistent フォルダをクリアすることができます (ファイルベースのキャッシュを使用している場合)。

I18n を利用して Pot ファイルを生成する

アプリケーション内の、 __() や他の国際化されたメッセージから pot ファイルを生成するためには、 i18n シェルを利用できます。より知りたい場合は、 次の章を読んでください。

デフォルトのロケールを設定する

デフォルトのロケールは config/app.php ファイルの App.defaultLocale を以下のようにすることで設定できます。

'App' => [
    ...
    'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'),
    ...
]

これは、CakePHP が提供している地域化のライブラリーを使うと示されている場合いつでも、デフォルトの翻訳言語、日付のフォーマット、番号のフォーマットおよび通貨を含む、アプリケーションのいくつかの様相をコントロールします。

実行時にロケールを変更する

翻訳文字列の言語を変更する場合はこのメソッドを呼び出せます。

use Cake\I18n\I18n;

I18n::setLocale('de_DE');

地域化のツールを使うと、これは数字や日付がどのようにフォーマットされているかについても変更します。

翻訳の機能を利用する

CakePHP はアプリケーションを国際化する手助けになるさまざまな機能を提供しています。最も頻繁に使われているものとして __() があります。この機能は一つの翻訳メッセージを引き出すか、見つからなかった場合は同じ文字列を返します。

echo __('Popular Articles');

もし、プラグインの中などで、メッセージをまとめる必要がある場合は、別のドメインからメッセージを取ってくるのに __d() が利用できます。

echo __d('my_plugin', 'Trending right now');

注釈

もし、名前空間付きのプラグインを翻訳したい場合、ドメイン文字列には Namespace/PluginName と名前を付けなければなりません。しかし、関連する言語ファイルは、プラグインのフォルダーの中の plugins/Namespace/PluginName/resources/locales/plugin_name.po になります。

翻訳の際に、翻訳すべき文字列が曖昧であることがあります。これは、2つの文字列がまったく同じであるのに異なることがらを指し示している場合に起こりえます。例えば、英語では 'letter' という単語は複数の意味を持ちます。この問題を解決するために __x() を利用することができます。

echo __x('written communication', 'He read the first letter');

echo __x('alphabet learning', 'He read the first letter');

第1引数はメッセージの文脈を示し、第2引数は翻訳されるべきメッセージです。

msgctxt "written communication"
msgid "He read the first letter"
msgstr "彼は最初の手紙を読みました"

翻訳メッセージで変数を利用する

翻訳関数を利用すると、メッセージの中あるいは翻訳された文字列の中で定義された特別なマーカーを用いているメッセージの中で変数を補完することができます。

echo __("Hello, my name is {0}, I'm {1} years old", ['Sara', 12]);

マーカーは数値で、渡された配列のキーに対応します。関数に独立した引数として変数を渡すことも可能です。

echo __("Small step for {0}, Big leap for {1}", 'Man', 'Humanity');

あらゆる翻訳関数はプレースホルダーの置き換えに対応しています。

__d('validation', 'The field {0} cannot be left empty', 'Name');

__x('alphabet', 'He read the letter {0}', 'Z');

' (シングルクオーテーション) は、翻訳メッセージの中ではエスケープコードとして扱われます。シングルクオーテーションの間の変数は、置き換えられませんし、文字通りのテキストとして扱われます。例えば、

__("This variable '{0}' be replaced.", 'will not');

変数の中で2つ連続してクオーテーションを用いると適切に置き換えられます。

__("This variable ''{0}'' be replaced.", 'will');

これらの関数は ICU MessageFormatter [https://php.net/manual/ja/messageformatter.format.php] を活用しています。そのためメッセージと地域化された日付や番号、通貨とを同時に翻訳することが可能です。

echo __(
    'Hi {0}, your balance on the {1,date} is {2,number,currency}',
    ['Charles', new FrozenTime('2014-01-13 11:12:00'), 1354.37]
);

// 結果
Hi Charles, your balance on the Jan 13, 2014, 11:12 AM is $ 1,354.37

プレースホルダーの中の数字は、出力のきめ細やかなコントロールによって、同様にフォーマットされます。

echo __(
    'You have traveled {0,number} kilometers in {1,number,integer} weeks',
    [5423.344, 5.1]
);

// 結果
You have traveled 5,423.34 kilometers in 5 weeks

echo __('There are {0,number,#,###} people on earth', 6.1 * pow(10, 8));

// 結果
There are 6,100,000,000 people on earth

以下は、 number という言葉の後に続けられるフォーマット修飾子のリストです:

integer: 小数の部分を取り除く

currency: 地域の通貨、を利用し、小数点以下を丸めます

percent: パーセントとして数をフォーマットします

日付は、プレースホルダーの数値の後に date という語を利用することによってフォーマットされます。以下は特別なオプションのリストです:

short

medium

long

full

プレースホルダーの数値の後に time という語も使用でき、 date と同じオプションとして認識されます。

注釈

named プレースホルダーは PHP 5.5 以上でサポートされており、 {name} としてフォーマットされます。named プレースホルダーを用いたい場合は、key/value ペアを用いた配列として変数を渡してください。たとえば、 ['name' => 'Sara', 'age' => 12] というようにです。

CakePHP で国際化の機能を活用する場合は PHP 5.5 以上を利用することが推奨されています。 php5-intl エクステンションがインストールされていなくてはなりませんし、ICU のバージョンは 48.x.y よりも上であるべきです ( Intl::getIcuVersion() で ICU のバージョンを確認してください)。

複数形

見せる言語によって、メッセージを正しく複数形にすることは、アプリケーションの国際化において重要な部分のひとつです。CakePHP はメッセージの中の複数形を正しく選択するいつかの方法を提供しています。

ICU の複数形選択を利用する

一つ目は、翻訳関数のデフォルトである ICU のメッセージフォーマットを活用する方法です。翻訳ファイルにおいて、以下の文字列があるかもしれません。

msgid "{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}"
msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{# resultados}}"

msgid "{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found {1} records}}"
msgstr "{placeholder,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}"

そしてアプリケーション内では、このような文字列の翻訳のどちらかを出力するために、以下のようなコードを使ってください。

__('{0,plural,=0{No records found }=1{Found 1 record} other{Found # records}}', [0]);

// 引数 {0} を 0 として "Ningún resultado" を返します。

__('{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}', [1]);

// 引数 {0} は 1 なので "1 resultado" を返します。

__('{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found {1} records}}', [0, 'many', 'placeholder' => 2])

// 引数 {placeholder} は 2 で、引数 {1} は 'many' なので
// "many resultados" を返します。

いま利用したフォーマットをよくみると、どのようにメッセージが構築されているのかがはっきりするでしょう。

{ [count placeholder],plural, case1{message} case2{message} case3{...} ... }

この [count placeholder] は翻訳関数にわたす変数の配列の key の番号です。正しい複数形を選択するのに使われます。

{message} の中の [count placeholder] を参照するためには # を利用しなくてはならないことに注意してください。

もちろん、コードの中で完全な複数形を求めていない場合は、メッセージ ID をよりシンプルにすることができます。

msgid "search.results"
msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}"

この場合は新しい文字列を使います。

__('search.results', [2, 2]);

// 戻り値: "2 resultados"

後者のバージョンでは、デフォルトの言語でさえも翻訳ファイルが必要になるという欠点がありますが、コードの可読性が上がり、複雑な複数形の選択文字列が翻訳ファイルに入らないという利点もあります。

複数形において、直接数値を指定するやり方は実用的でないことがあります。例えば、アラビア語のような言語では、少ないものの複数形と多いものの複数形が異なります。このような場合は ICU のマッチングエイリアスを利用できます。以下のように書く代わりに:

=0{No results} =1{...} other{...}

以下のようにすることができます。

zero{No Results} one{One result} few{...} many{...} other{...}

各言語のエイリアスの完全な概要を知りたい場合は Language Plural Rules Guide [https://unicode-org.github.io/cldr-staging/charts/37/supplemental/language_plural_rules.html] をご参照ください。

Gettext の複数形選択を使用する

二番目の複数形のフォーマットは、Gettext のビルトイン機能を用いたものです。この場合、複数形ごとに分かれた翻訳メッセージの行を作成した .po ファイルに複数形が置かれます。:

# One message identifier for singular
msgid "One file removed"
# Another one for plural
msgid_plural "{0} files removed"
# Translation in singular
msgstr[0] "Un fichero eliminado"
# Translation in plural
msgstr[1] "{0} ficheros eliminados"

これを別のフォーマットで利用するとき、別の翻訳機能を利用する必要があります。

// 戻り値: "10 ficheros eliminados"
$count = 10;
__n('One file removed', '{0} files removed', $count, $count);

// ドメインの中でそれを使うことが可能です。
__dn('my_plugin', 'One file removed', '{0} files removed', $count, $count);

msgstr[] 内の数値は、言語の複数形のために Gettext によって割り当てられた数値です。言語によっては、例えばクロアチア語では、2つ以上の複数形が存在します。

msgid "One file removed"
msgid_plural "{0} files removed"
msgstr[0] "{0} datoteka je uklonjena"
msgstr[1] "{0} datoteke su uklonjene"
msgstr[2] "{0} datoteka je uklonjeno"

各言語の数値の複数形についてより詳細な説明は Launchpad languages page [https://translations.launchpad.net/+languages] をご覧ください。

独自の翻訳機構を作成する

翻訳のメッセージが置かれている場所や方法についての CakePHP の慣習を拡張する必要がもしあるのなら、独自の翻訳メッセージローダーを作成することができます。独自の翻訳機構を作成する最も簡単な方法は、 1つのドメインのローダーを指定して、以下を設置します。

use Aura\Intl\Package;

I18n::setTranslator('animals', function () {
    $package = new Package(
        'default', // フォーマット戦略 (ICU)
        'default'  // フォールバックドメイン
    );
    $package->setMessages([
        'Dog' => 'Chien',
        'Cat' => 'Chat',
        'Bird' => 'Oiseau'
        ...
    ]);

    return $package;
}, 'fr_FR');

上記のコードは config/bootstrap.php に追加してください。そうすれば翻訳の機能が使われる前に見つかります。翻訳機構を作成するのに最低限必要なのは、ローダー機能が Aura\Intl\Package オブジェクトを返すことです。一旦コードを置けば、翻訳機能は以下のように利用できるでしょう。

I18n::setLocale('fr_FR');
__d('animals', 'Dog'); // "Chien" を返す

見てお分かりの通り、 Package オブジェクトは配列として翻訳メッセージを受け取ります。インラインコードや、他のファイルの読み込み、別の機能の呼び出しなどのときに、いつでも setMessages() メソッドを渡すことができます。CakePHP はメッセージが読み込まれる場所を変える必要がある場合に、使いまわせるいくつかのローダー機能を提供しています。例えば、 .po ファイルを利用しているのに、他の場所から読み込みたい場合は、

use Cake\I18n\MessagesFileLoader as Loader;

// Load messages from resources/locales/folder/sub_folder/filename.po からメッセージをロード
I18n::setTranslator(
    'animals',
    new Loader('filename', 'folder/sub_folder', 'po'),
    'fr_FR'
);

のようになります。

メッセージのパーサーを作成する

CakePHP が利用しているものと同じやり方を使い続けることもできますが、 PoFileParser 以外のメッセージパーサーを利用してみてください。たとえば、 YAML を用いた翻訳メッセージを読み込みたい場合、まずはじめにパーサークラスを作成する必要があります。

namespace App\I18n\Parser;

class YamlFileParser
{
    public function parse($file)
    {
        return yaml_parse_file($file);
    }
}

アプリケーションの src/I18n/Parser ディレクトリー内にこのファイルを作成してください。続いて、 resources/locales/fr_FR/animals.yaml として翻訳ファイルを作ります。

Dog: Chien
Cat: Chat
Bird: Oiseau

最後に、翻訳を読み込むドメインと場所を設定します。

use Cake\I18n\MessagesFileLoader as Loader;

I18n::setTranslator(
    'animals',
    new Loader('animals', 'fr_FR', 'yaml'),
    'fr_FR'
);

包括的な翻訳機構を作成する

対応が必要なドメインおよび場所ごとに、 I18n::translator() を呼び出して翻訳機構を設定するのは、非常に面倒です。わずかな違いで対応が必要な場合は特にです。この問題を避けるために、CakePHP ではドメインごとに包括的な翻訳機構のローダーを定義することができます。

デフォルトのドメインとあらゆる言語のすべての翻訳を、外部のサービス読み込みたいときのことを想像してみてください。

use Cake\I18n\Package;
// Prior to 4.2 you need to use Aura\Intl\Package

I18n::config('default', function ($domain, $locale) {
    $locale = Locale::parseLocale($locale);
    $lang = $locale['language'];
    $messages = file_get_contents("http://example.com/translations/$lang.json");

    return new Package(
        'default', // フォーマット機構
        null, // フォールバック (デフォルトドメインにはありません)
        json_decode($messages, true)
    )
});

上記の例は、翻訳を含む JSON ファイルを読み込む外部のサービスの例です。 アプリケーション内でリクエストされたどの場所でも Package オブジェクトをビルドします。

特定のローダーが設定されていない全てのパッケージで、パッケージをロードする方法を変更したい場合、 _fallback パッケージを使用することによって、代替パッケージローダーに置き換えることができます。

I18n::config('_fallback', function ($domain, $locale) {
    // パッケージを生成するカスタムコードはこちら。
});

独自の翻訳機構における複数形と文脈について

setMessages() に用いられている配列は、異なるドメイン配下にメッセージを翻訳機構が置くために指示をだす、または、Gettext の複数形選択のきっかけとなるために作成されます。以下は、異なる文脈において同じキーを翻訳に設置する例です。

[
    'He reads the letter {0}' => [
        'alphabet' => 'Él lee la letra {0}',
        'written communication' => 'Él lee la carta {0}'
    ]
]

同様にして、メッセージの配列で用いられているGettextの複数形を、複数形ごとのキーを用いてネストされた配列で表現することもできます。

[
    'I have read one book' => 'He leído un libro',
    'I have read {0} books' => [
        'He leído un libro',
        'He leído {0} libros'
    ]
]

異なるフォーマット機構を使う

前の例では最初の引数として default を用いるようにパッケージが作成されていました。そして、これは使用されているフォーマット機構と対応するコメントを示します。フォーマット機構は、翻訳メッセージに変数を渡す、そして正しい複数形を選択するクラスです。

もし、レガシーなアプリケーションを扱っている、あるいは ICU メッセージフォーマットが提供している機能が必要ない場合、CakePHP は sprinf フォーマット機構も提供しています。

return Package('sprintf', 'fallback_domain', $messages);

翻訳されるメッセージは sprintf() 関数に引数を入れて引き渡されます。

__('Hello, my name is %s and I am %d years old', 'José', 29);

デフォルトのフォーマット機構を最初に使われる以前の CakePHP に作成されたすべての翻訳機構に設置することができます。

これは、 translator() や config() メソッドを使って手で作成された翻訳機構を含みません。

I18n::defaultFormatter('sprintf');

日付や数値を地域化する

アプリケーションで日付や数値を出力する際に、ページが表示される国や地域の適切なフォーマットに沿ってフォーマットされる必要があることがあります。

日付や数値を表示する方法を変えるためには、現在のロケールの設定を変更し、正しいクラスを使用する必要があります。

use Cake\I18n\I18n;
use Cake\I18n\Time;
use Cake\I18n\Number;

I18n::setLocale('fr-FR');

$date = new Time('2015-04-05 23:00:00');

echo $date; // 05/04/2015 23:00 と表示

echo Number::format(524.23); // 524,23 と表示

フォーマットのオプションをより知りたい場合は、 日付と時刻 や Number を読んでください。

ORM で返されるデフォルトの日付では結果は Cake\I18n\Time クラスを利用しています。そのため、アプリケーションで直接表示することは、現在のロケールの変更に影響されます。

地域化された日時データをパースする

リクエストから地域化されたデータを受け取る場合、ユーザーが地域化したフォーマットから日時の情報を取得するのが良いでしょう。コントローラー、あるいは ミドルウェア では、日付、時刻、そして日時の型が地域化のフォーマットをパースするために定義できます。

use Cake\Database\TypeFactory;

// デフォルトのロケールフォーマットのパースを有効化
TypeFactory::build('datetime')->useLocaleParser();

// カスタム datetime フォーマットパース書式の設定
TypeFactory::build('datetime')->useLocaleParser()->setLocaleFormat('dd-M-y');

// IntlDateFormatter 定数を使用することもできます。
TypeFactory::build('datetime')->useLocaleParser()
    ->setLocaleFormat([IntlDateFormatter::SHORT, -1]);

デフォルトでパースするフォーマットは、デフォルトの文字列のフォーマットと同じです。

リクエストデータをユーザーのタイムゾーンから変換する

様々なタイムゾーンのユーザーからのデータを扱う場合には、リクエストデータにおける日時をアプリケーションのタイムゾーンへ変換する必要が出てきます。この処理を簡単にするために、コントローラーもしくは ミドルウェア の setUserTimezone() を使うことができます:

// ユーザーのタイムゾーンを設定する
TypeFactory::build('datetime')->setUserTimezone($user->timezone);

いったん設定をすると、アプリケーションがリクエストデータからエンティティーを作成もしくは更新をする時に、 ORM が日時の値をユーザーのタイムゾーンからアプリケーションのタイムゾーンへ自動で変換します。これは、常にアプリケーションが App.defaultTimezone で定義されたタイムゾーンで動作することを保証します。

あなたのアプリケーションが様々なアクションにおける日時の情報を扱う場合、ミドルウェアを使ってタイムゾーンの変換とロケールのパースの両方を設定することができます:

namespace App\Middleware;

use Cake\Database\TypeFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class DatetimeMiddleare implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        // リクエストからユーザーを取得
        // この例では、ユーザーエンティティーにタイムゾーン属性があるものと仮定
        $user = $request->getAttribute('identity');
        if ($user) {
            TypeFactory::build('datetime')
                ->useLocaleParser()
                ->setUserTimezone($user->timezone);
        }

        return $handler->handle($request);
    }
}

バージョン 4.3.0 で追加: setUserTimezone() メソッドが追加されました。

自動でリクエストデータに基づいたロケールを選択する

LocaleSelectorMiddleware をアプリケーション内で使用すると、CakePHP は自動で現在のユーザーに基づいたロケールを設定します。

// src/Application.php の中で
use Cake\I18n\Middleware\LocaleSelectorMiddleware;

// 新しいミドルウェアを追加するために middleware 関数を更新してください。
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    // ミドルウェアの追加し、有効なロケールの設定
    $middlewareQueue->add(new LocaleSelectorMiddleware(['en_US', 'fr_FR']));
    // 全てののロケールヘッダー値を受け入れる
    $middlewareQueue->add(new LocaleSelectorMiddleware(['*']));
}

LocaleSelectorMiddleware は Accept-Language ヘッダーを用いて、ユーザーの選択したロケールを自動的に設定します。どのロケールが自動で使われるかを制限するロケールリストオプションを使用することができます。

コンテンツ／エンティティーの翻訳

コンテンツ／エンティティーを翻訳したい場合には、 Translate Behavior をご覧ください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ロギング

CakePHP コアクラスの Configure 設定は、内部で何が起きているかを知るための有益な手段です。そこで、何が起きているかをディスクにログデータとして保存する必要が出てくるでしょう。 SOAP や AJAX、REST API のような技術を一緒に使うとデバッグはむしろ難しくなるでしょう。

ロギングは、時系列でアプリケーションで何が起きているかを知るための手段です。何の検索ワードが使われましたか？何のエラーがユーザーに表示されましたか？どのくらいの頻度で特定のクエリーが実行されましたか？

CakePHP でデータのロギングは簡単です。 log() 関数は、多くの CakePHP クラスの共通の祖先である LogTrait により提供されています。もし、環境が CakePHP のクラス (Controller や Component, View 等) であれば、あなたはデータを記録することができます。直接 Log::write() を同様に使うことも出来ます。 ログへの書き込み を参照してください。

ロギング設定

アプリケーションの起動処理の間に、 Log の設定は完了しているはずで config/app.php は、これのためです。多かれ少なかれアプリケーションが望むロガーを定義できます。ロガーは、 Cake\Log\Log を使い設定する必要があります。一例として:

use Cake\Log\Engine\FileLog;
use Cake\Log\Log;

// Classname using logger 'class' constant
Log::setConfig('info', [
    'className' => FileLog::class,
    'path' => LOGS,
    'levels' => ['info'],
    'file' => 'info',
]);

// 短いクラス名
Log::setConfig('debug', [
    'className' => 'File',
    'path' => LOGS,
    'levels' => ['notice', 'debug'],
    'file' => 'debug',
]);

// 完全な名前空間の名前
Log::setConfig('error', [
    'className' => 'Cake\Log\Engine\FileLog',
    'path' => LOGS,
    'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
    'file' => 'error',
]);

上記では二つのロガーを作っています。一つは debug で、その他は error です。それぞれが異なるレベルのメッセージを処理するために構成されています。また、それらは別々のファイルにメッセージを格納しますので、 debug/notice/info のログをより深刻なエラーから分離するのが簡単です。

色々なレベルに応じた情報と意味の一覧は レベルを使う を参照してください。

一度設定を作成した後、それを変更することはできません。 Cake\Log\Log::drop() と Cake\Log\Log::setConfig() を使って、設定を削除、再作成すべきです。

それらもまた、クロージャーを与えることによりロガーを作成することが可能です。クロージャーこれはロガーオブジェクトがどのように構築されるかを完全に制御する必要がある時に役立ちます。クロージャーは構築されたロガーのインスタンスを返さなければなりません。例えば:

Log::setConfig('special', function () {
    return new \Cake\Log\Engine\FileLog(['path' => LOGS, 'file' => 'log']);
});

設定オプションは DSN の文字列で渡すことも可能です。これは環境変数や PaaS プロバイダーを扱っている時に役立ちます。

Log::setConfig('error', [
    'url' => 'file:///full/path/to/logs/?levels[]=warning&levels[]=error&file=error',
]);

警告

If you do not configure logging engines, log messages will not be stored.

ログエンジンの作成

ログエンジンはアプリケーションの一部やプラグインの一部になりえます。例えば DatabaseLog という名前のデータベースロガーがあったとします。アプリケーションの一部として src/Log/Engine/DatabaseLog.php に置かれます。プラグインの一部として plugins/LoggingPack/src/Log/Engine/DatabaseLog.php に置かれます。また、ログエンジンの設定は Cake\Log\Log::setConfig() を使う必要があります。例えば DatabaseLog の設定はこのようになります。

// src/Log 用
Log::setConfig('otherFile', [
    'className' => 'Database',
    'model' => 'LogEntry',
    // ...
]);

// LoggingPack というプラグイン用
Log::setConfig('otherFile', [
    'className' => 'LoggingPack.Database',
    'model' => 'LogEntry',
    // ...
]);

ログエンジンを設定する時、 className パラメーターは、ログハンドラーを配置しロードするために使用されます。その他の設定プロパティーの全ては、ログエンジンのコンストラクターに配列として渡されます。

namespace App\Log\Engine;
use Cake\Log\Engine\BaseLog;

class DatabaseLog extends BaseLog
{
    public function __construct($options = [])
    {
        parent::__construct($options);
        // ...
    }

    public function log($level, $message, array $context = [])
    {
        // データベースに書き込みます。
    }
}

CakePHP では 全てのログエンジンにおいて Psr\Log\LoggerInterface を実装する必要があります。 CakeLogEngineBaseLog クラスは、 log() メソッドを実装することだけを要求しますので、そのインターフェイスを満たすための簡単な方法です。

ロギングフォーマッタ

ロギングフォーマッタは、ログメッセージがストレージエンジンに依存せずにフォーマットされる方法を制御することができます。各コア提供のログエンジンは、後方互換性のある出力を維持するように設定されたフォーマッタが付属しています。しかし、あなたの要件に合うようにフォーマッタを調整することができます。フォーマッターはロギングエンジンと一緒に設定されます。

use Cake\Log\Engine\SyslogLog;
use App\Log\Formatter\CustomFormatter;

// オプションのない単純なフォーマット構成
Log::setConfig('error', [
    'className' => SyslogLog::class,
    'formatter' => CustomFormatter::class,
]);

// 追加オプションを使用してフォーマッターを構成します
Log::setConfig('error', [
    'className' => SyslogLog::class,
    'formatter' => [
        'className' => CustomFormatter::class,
        'key' => 'value',
    ],
]);

独自のロギングフォーマッターを実装するには Cake\Log\Format\AbstractFormatter またはそのサブクラスのいずれかを継承する必要があります。実装する必要がある主なメソッドは format($level, $message, $context) で、これはログメッセージの書式設定を担当します。

バージョン 4.3.0 で追加: ロギングフォーマッタは4.3.0で追加されました。

FileLog エンジンは次のオプションを受け取ります。

size 基本的なログファイルローテーションの実装に使われます。もしログファイルサイズが特定のファイルサイズに到達した場合、既存のファイルはファイル名にタイムスタンプを付け加えることで名前が変更され、新しいログファイルが作成されます。整数バイト値か '10MB' や '100KB' などの人間が読みやすい文字列にすることができます。デフォルトは 10MB です。

rotate ログファイルが削除される前に指定された回数ローテートされます。もし値が 0 の場合は、ログローテーションされずに削除されます。デフォルトは 10 です。

mask 作成されるファイルのパーミッションを設定します。もし空のままであればデフォルトのパーミッションが使われます。

警告

エンジンは接尾辞 Log を持っています。クラス名が SomeLogLog のような接尾辞が二重になった名前は避けるべきです。

注釈

起動処理でロガーの設定をすべきです。 config/app.php はログアダプターの設定の慣習的な場所です。

デバッグモード中では、FileEngine 使用時に無用なエラーの発生を避けるため、ディレクトリーが存在しない時には自動的に作成されるようになりました。

エラーと例外のロギング

エラーと例外も記録できます。 app.php ファイル内に関連する値を設定することでログに記録することができます。debug が true のときにエラーが表示され、debug が false のときにログに記録されます。捕捉されなかった例外をログに記録するときは log オプションを true に設定してください。詳しくは、 構成設定 を参照ください。

ログストリームの相互作用

Cake\Log\Log::configured() で一連の設定を確認することができます。 configured() の戻り値は、現在設定されている全てを配列で返します。 Cake\Log\Log::drop() を使って、ストリームを削除することができます。一度、ログの設定が削除されると、ロガーはメッセージを受信しなくなります。

FileLog アダプターの利用

その名前が示すように、 FileLog は、ログメッセージをファイルに書き込みます。書かれたログメッセージのレベルは、メッセージが書き込まれたファイルの名前で決まります。もしレベルが指定されなければ、エラーログを書き込むための LOG_ERR が使われます。デフォルトのログの場所は logs/$level.log です。

// CakePHP クラスの中でこれを実行
$this->log("何かがうまくいかなかった！");

// logs/error.log に追記された結果
// 2007-11-02 10:22:02 Error: 何かがうまくいかなかった！

設定されたディレクトリーは、ウェブサーバーユーザー権限で正しくロギングできるように書き込み可能にしなければなりません。

ロガーの設定により、追加/代替の FileLog の場所を設定できます。FileLog は、独自のパスを使用するために path を設定できます。

Log::setConfig('custom_path', [
    'className' => 'File',
    'path' => '/path/to/custom/place/'
]);

警告

もしロギングアダプターを設定していなければ、ログメッセージは保存されません。

Syslog へのロギング

本番環境では、ファイルロガーの代わりに syslog を使用するようにシステムをセットアップすることを強く勧めます。これは、(大部分は）ノンブロッキング方式で全て書き込むため、よりよく動作し、そしてあなたのオペレーティングシステムのロガーは、独立してファイルのローテーションの設定ができ、前処理を記述したり、ログを完全に別のストレージを使うことができます。

syslog を使うためには、デフォルトの FileLog エンジンを使うのとよく似ています。ロギングに使用するエンジンとして Syslog を指定する必要があります。下記の設定は、デフォルトのロガーを Syslog に置き換えるものです。これは、 bootstrap.php ファイルで設定します。

Log::setConfig('default', [
    'engine' => 'Syslog'
]);

Syslog ロギングエンジンのための設定配列は、以下のキーを認識します。

format: ２つのプレースホルダーを持つ sprintf テンプレート文字列で１つ目は、エラーレベルで、２つ目はメッセージのためのものです。このキーは、ロギングメッセージ内のサーバーやプロセスに関する追加の情報を付加するのに便利です。例えば、 %s - Web Server 1 - %s は、プレースホルダーが置き換えられると、 error - Web Server 1 - An error occurred in this request のようになります。このオプションは非推奨です。代わりに ロギングフォーマッタ を使用する必要があります。

prefix: 全てのログメッセージの先頭につく文字列です。

flag: ロガーへの接続を開くために使用される整数値のフラグで、デフォルトは、 LOG_ODELAY が使用されます。 詳しくは、 openlog のドキュメントをご覧ください。

facility: syslog で使用するロギングスロット。デフォルトでは、 LOG_USER が使用されます。詳しくは、 ドキュメントの syslog をご覧ください。

ログへの書き込み

ログファイルへの書き込みは、２つの方法があります。１つは、静的な Cake\Log\Log::write() メソッドを使用することです。

Log::write('debug', '何かがうまくいかなかった');

２つ目は、 LogTrait を使用しているクラスに用意された log() ショートカット関数を使用することです。 log() を呼ぶと、内部的に Log::write() が呼ばれます。

// LogTrait を使用した クラス内でこれを実行
$this->log("何かがうまくいかなかった！", 'debug');

全ての設定されたログストリームは、 Cake\Log\Log::write() が呼ばれるたびに順次書き込まれます。もし設定されていないログアダプターを持っているならば、 log() は false を返し何も書き込みません。

レベルを使う

CakePHP は、標準 POSIX のロギングレベルをサポートします。各レベルは、増加する重要度を表します。

Emergency: システムは使用出来ません

Alert: 今すぐ行動する必要がある

Critical: 致命的な状態

Error: エラー状態

Warning: 警告状態

Notice: 正常であるが、重大な状態

Info: インフォメーションメッセージ

Debug: デバッグレベルメッセージ

ロガー設定時やログメッセージの書き出し中に、名前からこれらのレベルを引くことができます。あるいは、 Cake\Log\Log::error() のような便利メソッドを使うとログレベルを明確に示すことができます。上記のレベルにないレベルを使っていると例外が発生します。

注釈

ロガー設定の中で levels が空の値をセットされたとき、任意のレベルのメッセージを受け取ります。

ロギングスコープ

しばしば、異なるサブシステムやアプリケーションの一部で異なるロギングの振る舞いを設定したくなるでしょう。ある E コマースショップの例を挙げます。注文と支払いのロギングをその他の重大ではないログとは分けておきたい場合です。

CakePHP は、このコンセプトをロギングスコープで実現します。ログメッセージが書かれた時、スコープ名を指定できます。そのスコープとして設定されたロガーがある場合、ログメッセージはこれらのロガーに向けられます。例:

// すべてのレベルを受け取るように、 logs/shops.log を設定。
// スコープは `orders` と `payments` のみ
Log::setConfig('shops', [
    'className' => 'File',
    'path' => LOGS,
    'levels' => [],
    'scopes' => ['orders', 'payments'],
    'file' => 'shops.log',
]);

// すべてのレベルを受け取るように、 logs/payments.log を設定。
// スコープは `payments` のみ
Log::setConfig('payments', [
    'className' => 'File',
    'path' => LOGS,
    'levels' => [],
    'scopes' => ['payments'],
    'file' => 'payments.log',
]);

Log::warning('これは、 shops.log のみに書かれます', ['scope' => ['orders']]);
Log::warning('これは、 shops.log と payments.log の両方に書かれます', ['scope' => ['payments']]);

スコープは単一の文字列もしくは数値インデックス配列として渡すことができます。コンテキストとしてより多くのデータを渡す機能が、この形式を使用すると制限されることに注意してください。

Log::warning('これは警告です', ['orders']);
Log::warning('これは警告です', 'payments');

注釈

ロガー設定の中で scopes に空の配列や null がセットされたとき、任意のメッセージを受け取ります。それに false をセットすると、スコープのないメッセージにしかマッチしません。

Log API

class Cake\Log\Log

ログを書き込むためのシンプルなクラス。

static Cake\Log\Log::setConfig($key, $config)

パラメータ:

$name (string) -- 接続されるロガーの名前で、後でロガーを削除するために使用されます。

$config (array) -- ロガーの設定情報とコンストラクター引数の配列です。

ロガーの設定を取得したり、セットしたりします。詳細は ロギング設定 を参照してください。

static Cake\Log\Log::configured

戻り値:

設定されたロガーの配列です。

設定された複数のロガーの名前を取得します。

static Cake\Log\Log::drop($name)

パラメータ:

$name (string) -- 今後メッセージを受信させたくないロガーの名前です。

static Cake\Log\Log::write($level, $message, $scope = [])

全ての設定されたロガーにメッセージを書き込みます。 $level は、作成されたログメッセージのレベルを表します。 $message は、書き込みたいログのメッセージです。 $scope は、スコープ（一つもしくは複数）でログメッセージが作成されます。

static Cake\Log\Log::levels

引数なしでメソッドを呼び出します。例えば、 Log::levels() は、現在のレベルの設定を取得します。

便利なメソッド

以下の便利メソッドは、適切なログレベルで $message を記録するために追加されました。

static Cake\Log\Log::emergency($message, $scope = [])

static Cake\Log\Log::alert($message, $scope = [])

static Cake\Log\Log::critical($message, $scope = [])

static Cake\Log\Log::error($message, $scope = [])

static Cake\Log\Log::warning($message, $scope = [])

static Cake\Log\Log::notice($message, $scope = [])

static Cake\Log\Log::info($message, $scope = [])

static Cake\Log\Log::debug($message, $scope = [])

ロギングトレイト

trait Cake\Log\LogTrait

トレイトはロギングへのショートカットを提供します。

Cake\Log\LogTrait::log($msg, $level = LOG_ERR)

ログにメッセージを記録します。デフォルトではエラーメッセージを記録します。

Monolog を使用する

Monolog は、PHP で人気のロガーです。CakePHP のロガーと同じインターフェイスを実装しています。なので、アプリケーションでデフォルトのロガーとして使うことが簡単です。

Composer を使って Monolog をインストールしたら、 Log::setConfig() メソッドを使ってロガーを設定してください。

// config/bootstrap.php

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

Log::setConfig('default', function () {
    $log = new Logger('app');
    $log->pushHandler(new StreamHandler('path/to/your/combined.log'));
    return $log;
});

// オプションで、今使っていない不要なデフォルトのロガーを止めてください
Log::drop('debug');
Log::drop('error');

もし異なるロガーをコンソールで設定したいのであれば、同じ方法を使ってください。

// config/bootstrap_cli.php

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

Log::setConfig('default', function () {
    $log = new Logger('cli');
    $log->pushHandler(new StreamHandler('path/to/your/combined-cli.log'));
    return $log;
});

// オプションで、今使っていない不要なデフォルトの CLI ロガーを止めてください
Configure::delete('Log.debug');
Configure::delete('Log.error');

注釈

コンソールで固有なロガーを使用する場合は、アプリケーションロガーを条件付きで設定してください。これは複数のログが重複することを防ぎます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

モデルのないフォーム

class Cake\Form\Form

ほとんどの場合、フォームの背後には ORM エンティティー や ORM テーブル あるいはその他の永続的ストアーがありますが、時にはユーザー入力を検証し、そのデータが有効であれば何らかのアクションを実行する、という場合もあります。この最も一般的な例は問い合わせフォームです。

フォームの作成

一般的に Form クラスを使う時は、フォームを定義するためにサブクラスを使います。これはテストを簡単にし、フォームを再利用できるようにします。フォームは src/Form に置かれ、普通はクラスのサフィックスして Form を持ちます。たとえば、単純な問い合わせフォームは次のようになります。

// src/Form/ContactForm.php の中で
namespace App\Form;

use Cake\Form\Form;
use Cake\Form\Schema;
use Cake\Validation\Validator;

class ContactForm extends Form
{
    protected function _buildSchema(Schema $schema): Schema
    {
        return $schema->addField('name', 'string')
            ->addField('email', ['type' => 'string'])
            ->addField('body', ['type' => 'text']);
    }

    public function validationDefault(Validator $validator): Validator
    {
        $validator->minLength('name', 10)
            ->email('email');

        return $validator;
    }

    protected function _execute(array $data): bool
    {
        // メールを送信する
        return true;
    }
}

上の例ではフォームが提供する３つのフックメソッドが見えます。

_buildSchema は FormHelper が HTML フォームを作成する際に使用するスキーマデータを定義するために使います。フィールドの型、長さ、および精度を定義できます。

validationDefault はバリデーターを加えることができる Cake\Validation\Validator のインスタンスを受け取ります。

_execute では execute() が呼ばれ、データが有効な時に望むふるまいを定義します。

もちろん必要に応じて追加の公開メソッドを定義することもできます。

リクエストデータの処理

フォームを定義したら、リクエストデータを処理し検証するためにコントローラー中でそれを使うことができます。

// 何らかのコントローラー中で
namespace App\Controller;

use App\Controller\AppController;
use App\Form\ContactForm;

class ContactController extends AppController
{
    public function index()
    {
        $contact = new ContactForm();
        if ($this->request->is('post')) {
            if ($contact->execute($this->request->getData())) {
                $this->Flash->success('すぐにご連絡いたします。');
            } else {
                $this->Flash->error('フォーム送信に問題がありました。');
            }
        }
        $this->set('contact', $contact);
    }
}

上の例では、データが有効な時にのみフォームの _execute() を走らせるために execute() メソッドを実行し、それに応じたフラッシュメッセージを設定しています。データ検証のみ行うために validate() を使うこともできます。

if ($contact->execute($this->request->getData(), 'update')) {
    // Handle form success.
}

This option can also be set to false to disable validation.

We could have also used the validate() method to only validate the request data:

$isValid = $form->validate($this->request->getData());

// You can also use other validation sets. The following
// would use the rules defined by `validationUpdate()`
$isValid = $form->validate($this->request->getData(), 'update');

バージョン 4.3.0 で追加: Using validators other than default was added.

フォーム値の設定

モデルのないフォームのフィールドにデフォルト値を設定するために、 setData() メソッドが使用できます。このメソッドで設定された値はフォームオブジェクトの既存のデータを上書きします。

// 何らかのコントローラー中で
namespace App\Controller;

use App\Controller\AppController;
use App\Form\ContactForm;

class ContactController extends AppController
{
    public function index()
    {
        $contact = new ContactForm();
        if ($this->request->is('post')) {
            if ($contact->execute($this->request->getData())) {
                $this->Flash->success('すぐにご連絡いたします。');
            } else {
                $this->Flash->error('フォーム送信に問題がありました。');
            }
        }

        if ($this->request->is('get')) {
            $contact->setData([
                'name' => 'John Doe',
                'email' => 'john.doe@example.com'
            ]);
        }

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

値はリクエストメソッドが GET の時にのみ定義する必要があります。そうしないと、修正が必要なバリデーションエラーの直前の POST データを上書きしてしまいます。また、 set() を使用して、個々のフィールドまたはフィールドのサブセットを追加または置換することができます。:

// 一つのフィールドをセット
$contact->set('name', 'John Doe');

// 複数のフィールドをセット;
$contact->set([
    'name' => 'John Doe',
    'email' => 'john.doe@example.com',
]);

フォームエラーの取得

フォームが検証されたら、エラーを取得することができます。

$errors = $form->getErrors(); // $form->errors(); // 3.7.0 より前
/* $errors の中身
[
    'email' => ['有効なメールアドレスが要求されます']
]
*/

コントローラーから各フォームフィールドを無効化

Validator クラスを使用せずに、コントローラーから各フォームフィールドを無効化（訳注：無効化は invalidate の訳で、ここでは「誤っていることを示す」の意味です）することができます。この最も一般的な使用例はリモートサーバー上で検証が行われる時です。そうした場合、手動でリモートサーバーからのフィードバックに応じてそのフィールドを無効化しなければなりません。

// src/Form/ContactForm.php 中で
public function setErrors($errors)
{
    $this->_errors = $errors;
}

バージョン 3.5.1 で変更: setErrors をもう指定する必要はありません。これは、利便性のため Form クラスに既に含まれているからです。

バリデータークラスのエラーの返し方にならって、 $errors はこの形式でなければなりません。

["フィールド名" => ["検証名" => "表示するエラーメッセージ"]]

さて、フィールド名を設定することでフォームフィールドを無効化し、その時にメッセージを設定できるようになりました。

// コントローラーの中で
$contact = new ContactForm();
$contact->setErrors(["email" => ["_required" => "メールアドレスは必須です"]]);

結果を見るためには「FormHelper で HTML 作成」に進みます。

FormHelper で HTML 作成

Form クラスを作ったら、その HTML フォームを作成したいはずです。 FormHelper は Form オブジェクトを ORM エンティティーとちょうど同じように理解します。

echo $this->Form->create($contact);
echo $this->Form->control('name');
echo $this->Form->control('email');
echo $this->Form->control('body');
echo $this->Form->button('Submit');
echo $this->Form->end();

上記は先に定義した ContactForm フォーム用の HTML フォームを作成します。 FormHelper で作成される HTML フォームはフィールド型、最大長、およびエラーを決定するために定義されたスキーマとバリデーターを使用します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

プラグイン

CakePHP では、コントローラー・モデル・ビューの組み合わせを設定し、他の CakePHP アプリケーションで使用できるよう事前にパッケージ化されたアプリケーションプラグインとしてリリースできます。あなたのアプリケーションの１つに、素敵なユーザー管理モジュールやシンプルなブログやウェブサービスモジュールを作成したら、 CakePHP プラグインとしてパッケージ化してみませんか？そうすれば、他のアプリケーションで再利用したり、コミュニティーで共有したりすることができます！

CakePHP プラグインは、ホストアプリケーション自身とは基本的に分離されており、一般的にきちんとパッケージ化され、他のアプリケーションではほとんど手間をかけずに再利用できる明確な機能を提供します。アプリケーションとプラグインは、それぞれの空間で動作しますが、アプリケーションの設定によって定義・共有される、アプリケーション固有のプロパティー（データベース接続パラメーターなど）を共有します。

プラグインは最上位の名前空間を定義しなければなりません。例えば DebugKit などです。規約により、プラグインはそのパッケージ名が名前空間の名前となります。もし別の名前空間名を使いたいなら、プラグインをロードする時に設定することが可能です。

Composer を使ったプラグインのインストール

Packagist [https://packagist.org] には多くのプラグインがあり、 Composer を使ってインストールが可能です。DebugKit をインストールには、下記のコマンドを実行します。

php composer.phar require cakephp/debug_kit

これによって最新の DebugKit をインストールして、あなたの composer.json, composer.lock ファイルを更新し、 vendor/cakephp-plugins.php とオートローダーも更新します。

プラグインの手動インストール

もしあなたがインストールしたいプラグインが packagist.org になければ、プラグインのコードを plugins ディレクトリーにコピーすることもできます。例えば 'ContactManager' という名前のプラグインをインストールするなら、 plugins フォルダーの中に 'ContactManager' という名前のフォルダーを作り、この下にプラグインの src, tests といったフォルダーを作ります。

プラグインクラスを手動で自動読み込み

composer や bake を使ってプラグインをインストールするなら、プラグインのためにクラスの自動読み込みの設定をする必要はありません。

一方、 MyPlugin という名前のプラグインを手動でインストールした場合、アプリケーションの composer.json ファイルを次の情報を含むように更新する必要があります。

{
    "autoload": {
        "psr-4": {
            "MyPlugin\\": "plugins/MyPlugin/src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "MyPlugin\\Test\\": "plugins/MyPlugin/tests/"
        }
    }
}

あなたのプラグインがベンダーの名前空間を使うなら、パスマッピングの名前空間は下記のようになるでしょう。

{
    "autoload": {
        "psr-4": {
            "AcmeCorp\\Users\\": "plugins/AcmeCorp/Users/src/",
            "AcmeCorp\\Users\\Test\\": "plugins/AcmeCorp/Users/tests/"
        }
    }
}

そして、 Composer の自動読み込みキャッシュを初期化する必要があります。

php composer.phar dumpautoload

プラグインの読み込み

もし、プラグインのルート、コンソールコマンド、ミドルウェア、またはイベントリスナーが欲しい場合、プラグインを読み込む必要があります。プラグインは、アプリケーションの bootstrap() 関数の中で読み込まれます。

// src/Application.php の中。
use Cake\Http\BaseApplication;
use ContactManager\Plugin as ContactManagerPlugin;

class Application extends BaseApplication {
    public function bootstrap()
    {
        parent::bootstrap();
        // クラス名で contact manager プラグインを読み込み
        $this->addPlugin(ContactManagerPlugin::class);

        // '短縮名' でベンダーの名前空間付きプラグインを読み込み
        $this->addPlugin('AcmeCorp/ContactManager');

        // 本番環境には存在しない、開発環境の依存関係をロード
        $this->addOptionalPlugin('AcmeCorp/ContactManager');
    }
}

単にプラグインのヘルパー、ビヘイビアー、またはコンポーネントが欲しいだけの場合、プラグインを読み込む必要はありません。

また、プラグインを有効にする便利なシェルコマンドがあります。次の行を実行してください。

bin/cake plugin load ContactManager

これは、アプリケーションの bootstrap メソッドを更新、または $this->addPlugin('ContactManager'); を bootstrap に書き込みます。

バージョン 4.1.0 で追加: addOptionalPlugin() が追加されました。

プラグインフックの設定

プラグインは、プラグインがアプリケーションの適切な部分に自分自身を注入できるようにするいくつかのフックを提供します。

フックは以下の通りです。

bootstrap プラグインのデフォルト設定ファイルの読み込み、定数やその他のグローバル関数の定義に使用されます。

routes プラグインのルートをロードするために使用されます。アプリケーションのルートがロードされた後に呼ばれます。

middleware プラグインのミドルウェアをアプリケーションのミドルウェアキューに追加するために使用されます。

console アプリケーションのコマンドコレクションにコンソールコマンドを追加するために使用されます。

プラグインをロードするとき、有効にするフックを設定できます。デフォルトでは、 Plugin オブジェクト のないプラグインはすべてのフックを無効にします。新しいスタイルのプラグインを使用すると、プラグイン作成者はデフォルトを設定できます。これを利用者はアプリケーション中で変更可能です。

// Application::bootstrap() の中で
use ContactManager\Plugin as ContactManagerPlugin;

// ContactManager プラグインのルートを無効化
$this->addPlugin(ContactManagerPlugin::class, ['routes' => false]);

フックを配列オプションで設定することも、 Plugin クラスで提供されるメソッドで設定することもできます。

// Application::bootstrap() の中で
use ContactManager\Plugin as ContactManagerPlugin;

// フックを設定するために disable/enable を使用
$plugin = new ContactManagerPlugin();

$plugin->disable('bootstrap');
$plugin->enable('routes');
$this->addPlugin($plugin);

Plugin オブジェクトは、名前とパス情報も知っています。

$plugin = new ContactManagerPlugin();

// プラグイン名を取得
$name = $plugin->getName();

// プラグインルートへのパスとその他のパス
$path = $plugin->getPath();
$path = $plugin->getConfigPath();
$path = $plugin->getClassPath();

プラグインの利用

クラス名の前にプラグイン名を付けることで、プラグインのコントローラー、モデル、コンポーネント、ビヘイビアーとヘルパーを参照できます。

例えば、あなたの画面で整形された連絡先情報を表示するために、 ContactManager プラグインの ContactInfoHelper を使いたいとしましょう。コントローラーの addHelper() を使うと、次のようになります。

$this->viewBuilder()->addHelper('ContactManager.ContactInfo');

注釈

このドット区切りのクラス名は、 プラグイン記法 と呼ばれます。

すると、あなたが作った他のヘルパー同様に、 ContactInfoHelper にアクセスできるようになります。

echo $this->ContactInfo->address($contact);

プラグインは、アプリケーションが提供するモデル、コンポーネント、ビヘイビア、ヘルパー、または必要に応じて他のプラグインを使用することができます。

// アプリケーションコンポーネントを使用する
$this->loadComponent('AppFlash');

// 他のプラグインのビヘイビアを使用する
$this->addBehavior('OtherPlugin.AuditLog');

独自プラグインの作成

動作サンプルとして、上記を参考に ContactManager を作りましょう。まず始めに、プラグインの基本ディレクトリー構成を準備します。それはこのようになります。

/src
/plugins
    /ContactManager
        /config
        /src
            /Plugin.php
            /Controller
                /Component
            /Model
                /Table
                /Entity
                /Behavior
            /View
                /Helper
        /templates
            /layout
        /tests
            /TestCase
            /Fixture
        /webroot

プラグインフォルダーの名前が 'ContactManager' になっています。このフォルダーがプラグインと同じ名前になる事が大切です。

プラグインフォルダーの中は CakePHP アプリケーションと同じような構成であることに気づく思いますが、それが基本的な構成です。使わないフォルダーは作る必要はありません。コンポーネントとビヘイビアーだけで定義されるプラグインもあれば、 'templates' ディレクトリーが完全に省略されるプラグインもあります。

プラグインは、アプリケーションが持つ Config, Console, webroot 等といったディレクトリーも設置できます。

Bake を使ってプラグインを作成

プラグイン制作の過程は、Bake shell を使えば非常に簡単です。

プラグインを bake するのは以下のコマンドになります。

bin/cake bake plugin ContactManager

Bake を使用して、プラグインのクラスを作成できます。例えばプラグインのコントローラーを bake するには

bin/cake bake controller --plugin ContactManager Contacts

もしコマンドラインで問題があれば、 Bake でコード生成 の章を参照してください。また、プラグインを作ったら必ずオートローダーを再作成してください。

php composer.phar dumpautoload

Plugin オブジェクト

Plugin オブジェクトを使用すると、プラグイン作成者は設定ロジックを定義し、デフォルトのフックを定義し、ルート、ミドルウェア、およびコンソールコマンドをロードできます。 Plugin オブジェクトは、 src/Plugin.php にあります。 ContactManager プラグイン の場合、 plugin クラスは、次のようになります。

namespace ContactManager;

use Cake\Core\BasePlugin;
use Cake\Core\PluginApplicationInterface;
use Cake\Console\CommandCollection;
use Cake\Http\MiddlewareQueue;

class Plugin extends BasePlugin
{
    public function middleware(MiddlewareQueue $middleware): MiddlewareQueue
    {
        // ここにミドルウェアを追加。
        $middleware = parent::middleware($middleware);

        return $middleware;
    }

    public function console(CommandCollection $commands): CommandCollection
    {
        // ここにコンソールコマンドを追加。
        $commands = parent::console($commands);

        return $commands;
    }

    public function bootstrap(PluginApplicationInterface $app): void
    {
        // 定数を追加。デフォルトの設定をロード。
        // デフォルトでは、プラグインの中の `config/bootstrap.php` をロードします。
        parent::bootstrap($app);
    }

    public function routes($routes): void
    {
        // ルートの追加。
        // デフォルトでは、プラグインの中の `config/routes.php` をロードします。
        parent::routes($routes);
    }
}

プラグインのルート

プラグインは、ルートを含むルートファイルを提供できます。各プラグインは、 config/routes.php ファイルを含むことができます。このルートファイルは、プラグインが追加された時、またはアプリケーションのルートファイルの中でロードすることができます。ContactManager プラグインのルートを作成するためには、 plugins/ContactManager/config/routes.php の中に以下を記述してください。

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

$routes->plugin(
    'ContactManager',
    ['path' => '/contact-manager'],
    function ($routes) {
        $routes->setRouteClass(DashedRoute::class);

        $routes->get('/contacts', ['controller' => 'Contacts']);
        $routes->get('/contacts/{id}', ['controller' => 'Contacts', 'action' => 'view']);
        $routes->put('/contacts/{id}', ['controller' => 'Contacts', 'action' => 'update']);
    }
);

上記のようにすれば、プラグインのデフォルトルートに接続できるでしょう。このファイルをカスタマイズすることで、後から個別のルートを設定することができます。

コントローラーにアクセスする前に、プラグインがロードされ、ルートがロードされる必要があります。 src/Application.php に下記を追加してください。

$this->addPlugin('ContactManager', ['routes' => true]);

アプリケーションのルート一覧の中で、プラグインのルートをロードすることもできます。これにより、プラグインのルートをロードする方法をより詳細に制御し、追加のスコープやプレフィックスでプラグインのルートをラップすることができます。

$routes->scope('/', function ($routes) {
    // 他のルートに接続。
    $routes->scope('/backend', function ($routes) {
        $routes->loadPlugin('ContactManager');
    });
});

上記の結果は、 /backend/contact-manager/contacts のような URL になります。

プラグインのコントローラー

ContactManager プラグインのコントローラーは、 plugins/ContactManager/src/Controller/ に設置されます。主にやりたい事は contacts の管理ですので、このプラグインには ContactsController が必要です。

そこで ContactsController を plugins/ContactManager/src/Controller に設置し、このように書きます。

// plugins/ContactManager/src/Controller/ContactsController.php
namespace ContactManager\Controller;

use ContactManager\Controller\AppController;

class ContactsController extends AppController
{
    public function index()
    {
        //...
    }
}

まだ作っていないなら、 AppController も作りましょう。

// plugins/ContactManager/src/Controller/AppController.php
namespace ContactManager\Controller;

use App\Controller\AppController as BaseController;

class AppController extends BaseController
{
}

プラグインの AppController は、プラグイン内の全コントローラー共通のロジックを持ちますが、使わないようでしたら作らなくても構いません。

これまでのところでアクセスするなら、 /contact-manager/contacts にアクセスしてみてください。 "Missing Model" エラーが表示されるでしょうが、これはまだ Contact モデルが定義されていないためです。

もしあなたのアプリケーションが、CakePHP の提供するデフォルトルーティングを含むなら、あなたのプラグインコントローラーへは下記のような URL でアクセスできます。

// プラグインコントローラーの index にアクセスする
/contact-manager/contacts

// プラグインコントローラーのそれぞれのアクションにアクセスする
/contact-manager/contacts/view/1

もしあなたのアプリケーションでルーティングプレフィックスを定義しているなら、 CakePHP のデフォルトルーティングは下記の書式でルーティングします。

/{prefix}/{plugin}/{controller}
/{prefix}/{plugin}/{controller}/{action}

特定ファイルにルーティングするようなプラグインの読み込み方法については、プラグインフックの設定 のセクションをご覧ください。

bake で作っていないプラグインなら、クラスを自動的に読み込むために composer.json ファイルを編集して、あなたのプラグインを追加する必要があります。これは プラグインクラスを手動で自動読み込み に従って行うことができます。

プラグインのモデル

プラグインのモデルは plugins/ContactManager/src/Model に設置されます。既にこのプラグインの ContactsController は定義してありますから、このコントローラーのためのテーブルとエンティティーを作成しましょう。

// plugins/ContactManager/src/Model/Entity/Contact.php:
namespace ContactManager\Model\Entity;

use Cake\ORM\Entity;

class Contact extends Entity
{
}

// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;

use Cake\ORM\Table;

class ContactsTable extends Table
{
}

エンティティークラスを作った時や関連付けを行いたい時など、あなたのプラグイン内のモデルを参照したい場合には、プラグイン名とクラス名をドットで区切る必要があります。例えば:

// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;

use Cake\ORM\Table;

class ContactsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasMany('ContactManager.AltName');
    }
}

もし関連付け配列のキーにプラグインの接頭語をつけたくないのなら、代わりにこのような構文が使えます。

// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;

use Cake\ORM\Table;

class ContactsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasMany('AltName', [
            'className' => 'ContactManager.AltName',
        ]);
    }
}

おなじみの プラグイン記法 を使う事で、プラグインのテーブルを読み込むために Cake\ORM\Locator\LocatorAwareTrait を使用することができます。

use Cake\ORM\Locator\LocatorAwareTrait;

$contacts = $this->fetchTable('ContactManager.Contacts');

プラグインのビュー

ビューは通常のアプリケーション内と同じように動作します。 plugins/[PluginName]/templates/ フォルダーの中の正しいフォルダー内に配置するだけです。我々の ContactManager プラグインでは ContactsController::index() アクションにビューが必要ですから、このような内容になります。

// plugins/ContactManager/templates/Contacts/index.php:
<h1>連絡先</h1>
<p>ソート可能なあなたの連絡先一覧は次のとおりです</p>
<!-- ソート可能な連絡先一覧はこちら....-->

プラグインは独自のレイアウトを提供することができます。プラグインレイアウトを追加するためには、テンプレートファイルを plugins/[PluginName]/templates/layout に配置します。プラグインレイアウトをコントローラーで使用するには、下記のようにします。

$this->viewBuilder()->setLayout('ContactManager.admin');

プラグインのプレフィックスを省略した場合は、レイアウトやビューファイルは通常のものを使用します。

注釈

プラグインからのエレメントの使い方については、エレメント を参照してください。

アプリケーション内からプラグインのテンプレートを上書き

プラグインのビューはあるパスを使って上書きできます。仮にあなたが 'ContactManager' という名前のプラグインを持っているとして、 templates/plugin/[Plugin]/[Controller]/[view].php というファイルを作ってそこにビューロジックを書いておけば、プラグインのテンプレートファイルを上書きすることができます。 Contacts コントローラーなら、次のようなファイルを作成します。

templates/plugin/ContactManager/Contacts/index.php

このファイルを作成すると、 plugins/ContactManager/templates/Contacts/index.php を上書きします。

もし、あなたのプラグインが composer の依存関係の中にある場合 (例えば 'Company/ContactManager')、 Custom コントローラーの 'index' ビューへのパスは、次の通りです。

templates/plugin/ContactManager/Contacts/index.php

このファイルを作成すると、 vendor/Company/ContactManager/templates/Contacts/index.php を上書きします。

プラグインがルーティングプレフィックスを実装する場合、上書きするアプリケーションテンプレートのパスにルーティングプレフィックスが含まなければなりません。例えば、 'ContactManager' プラグインが 'Admin' プレフィックスを実装する場合、上書きするパスは、次の通りです。

templates/plugin/ContactManager/Admin/ContactManager/index.php

プラグインのアセット

プラグインのウェブアセット (PHP 以外のファイル） は、メインアプリケーションのアセットと同様にプラグインの webroot ディレクトリーを介して配信されます。

/plugins/ContactManager/webroot/
                               css/
                               js/
                               img/
                               flash/
                               pdf/

通常の webroot と同じようにどのディレクトリーにどんなファイルでも置くことができます。

警告

ディスパッチャーを介して静的アセット (画像や JavaScript や CSS ファイル) を取り扱うことは非常に非効率です。詳細は アプリケーションのパフォーマンスの向上 をご覧ください。

プラグイン内のアセットへのリンク

HtmlHelper の script, image, css メソッドを使ってプラグイン内のアセットへのリンクを作りたい場合、 プラグイン記法 が使えます。

// /contact_manager/css/styles.css への URL を生成します
echo $this->Html->css('ContactManager.styles');

// /contact_manager/js/widget.js への URL を生成します
echo $this->Html->script('ContactManager.widget');

// /contact_manager/img/logo.jpg への URL を生成します
echo $this->Html->image('ContactManager.logo');

プラグインのアセットは、デフォルトで AssetMiddleware ミドルウェアを使用して提供されます。これは開発時のみ使用することが推奨されます。本番環境ではパフォーマンスを向上させるために、プラグインのアセットをシンボリックリンク化 すべきです。

もしあなたがヘルパーを使わないなら、プラグインのアセットを提供するためには URL の先頭にプラグイン名を付加します。 '/contact_manager/js/some_file.js' へのリンクで、 plugins/ContactManager/webroot/js/some_file.js というアセットを提供します。

コンポーネント、ヘルパーとビヘイビアー

プラグインには通常の CakePHP アプリケーションと同じように、コンポーネント、ヘルパー、ビヘイビアーを持つ事ができます。あなたはコンポーネント、ヘルパー、ビヘイビアーだけからなるプラグインを作る事もできます。これはコンポーネントを他のプロジェクトに簡単に導入すれば再利用可能となるような素晴らしい方法です。

このようなコンポーネントを作る事は、実際、通常のアプリケーションとして作る事と同じであり、特別な命名規則もありません。

プラグインの内部や外部からコンポーネントを参照する方法は、コンポーネント名の前にプラグイン名を付けるだけです。例えば:

// 'ContactManager' プラグインのコンポーネントとして定義
namespace ContactManager\Controller\Component;

use Cake\Controller\Component;

class ExampleComponent extends Component
{
}

// コントローラーの中で
public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('ContactManager.Example');
}

同じテクニックはヘルパーとビヘイビアーにも使えます。

コマンド

プラグインは、 console() フックの中で、コマンドを登録することができます。デフォルトでは、プラグイン内のすべてのシェルとコマンドが自動検出され、アプリケーションのコマンドリストに追加されます。プラグインコマンドの先頭にはプラグイン名が付いています。例えば、 ContactManager プラグインによって提供される UserCommand は、 contact_manager.user と user の両方として登録されます。プレフィックスのない名前は、アプリケーションや他のプラグインで使用されていないプラグインでのみ使用されます。

プラグインで各コマンドを定義することによって、コマンド名をカスタマイズすることができます。

public function console($commands)
{
    // ネストされたコマンドを作成
    $commands->add('bake model', ModelCommand::class);
    $commands->add('bake controller', ControllerCommand::class);

    return $commands;
}

プラグインのテスト

コントローラーやURLの生成をテストする場合、プラグインがルート tests/bootstrap.php に接続していることを確認してください。

詳細は testing plugins ページを確認してください。

プラグインの公開

CakePHP のプラグインは the packagist [https://packagist.org] に公開しましょう。こちらでは、他の人々は composer の依存関係として使用することができます。 awesome-cakephp list [https://github.com/FriendsOfCake/awesome-cakephp] に申し込みできます。

パッケージ名は意味のあるセマンティックな名前を選んでください。できれば、 "cakephp" をフレームワークとして依存関係を設定するべきです。ベンダー名は、通常あなたの GitHub ユーザー名になります。 CakePHP 名前空間 (cakephp) を 使用しない でください。これは、CakePHP 自身のプラグインのために予約されています。小文字と区切り文字のダッシュを使用することが決まりです。

もし、あなたの GitHub アカウントが "FooBar" で "Logging" プラグインを作成する場合、 foo-bar/cakephp-logging と名付けるといいでしょう。そして、CakePHP 自身の "Localized" プラグンは、 cakephp/localized で見つけられます。

プラグイン マップ ファイル

Composer 経由でインストールすると、 vendor/cakephp-plugins.php というファイルが作られることに気付くかもしれません。この設定ファイルにはプラグイン名とファイルシステム上の配置場所の情報が含まれています。これによって、プラグインを通常の検索パスの外の、標準の vendor ディレクトリーにインストールすることが可能になります。通常の検索パスのプラグインクラスは、このファイルを使ってプラグインが addPlugin() で読み込まれたときに、そのプラグインが読み込まれるようになります。通常このファイルを手動で編集する必要はなく、 Composer や plugin-installer パッケージが管理してくれます。

Mixer を使用したプラグインの管理

CakePHP アプリケーションでプラグインを発見して管理する別の方法は、 Mixer [https://github.com/CakeDC/mixer] です。これは Packagist からプラグインをインストールするのに便利な CakePHP のプラグインです。また、既存のプラグインを管理するのにも役立ちます。

注釈

重要: 本番環境でこれを使用しないでください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

REST

最近のアプリケーションプログラマーは、サービスのコア機能をユーザーにオープンにする必要があると気付き始めています。簡単に提供でき、自由にコア API にアクセスできれば、広く受け入れられ、マッシュアップされたり、簡単に他のシステムと統合できます。

簡単にあなたの作ったアプリケーションロジックにアクセスさせる方法は色々ありますが、 REST はその中でもすばらしい方法でしょう。とてもシンプルで、大抵は XML ベース (SOAP のようなものではなく、単純な XML のこと) で、HTTP ヘッダーによって制御されます。 CakePHP を使って REST の API を提供するのはすごく簡単です。

簡単なセットアップ

REST を動かすための手っ取り早い方法は、 config/routes.php ファイルにリソースルート をセットアップするための数行を追記することです。

一度、Router が REST リクエストを、コントローラーのアクションにマッピングしておけば、そのコントローラーのアクションのロジックの作成に移ることができます。基本的なコントローラーのサンプルは下記のようになります。

// src/Controller/RecipesController.php
class RecipesController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('RequestHandler');
    }

    public function index()
    {
        $recipes = $this->Recipes->find('all')->all();
        $this->set('recipes', $recipes);
        $this->viewBuilder()->setOption('serialize', ['recipes']);
    }

    public function view($id)
    {
        $recipe = $this->Recipes->get($id);
        $this->set('recipe', $recipe);
        $this->viewBuilder()->setOption('serialize', ['recipe']);
    }

    public function add()
    {
        $this->request->allowMethod(['post', 'put']);
        $recipe = $this->Recipes->newEntity($this->request->getData());
        if ($this->Recipes->save($recipe)) {
            $message = 'Saved';
        } else {
            $message = 'Error';
        }
        $this->set([
            'message' => $message,
            'recipe' => $recipe,
        ]);
        $this->viewBuilder()->setOption('serialize', ['recipe', 'message']);
    }

    public function edit($id)
    {
        $this->request->allowMethod(['patch', 'post', 'put']);
        $recipe = $this->Recipes->get($id);
        $recipe = $this->Recipes->patchEntity($recipe, $this->request->getData());
        if ($this->Recipes->save($recipe)) {
            $message = 'Saved';
        } else {
            $message = 'Error';
        }
        $this->set([
            'message' => $message,
            'recipe' => $recipe,
        ]);
        $this->viewBuilder()->setOption('serialize', ['recipe', 'message']);
    }

    public function delete($id)
    {
        $this->request->allowMethod(['delete']);
        $recipe = $this->Recipes->get($id);
        $message = 'Deleted';
        if (!$this->Recipes->delete($recipe)) {
            $message = 'Error';
        }
        $this->set('message', $message);
        $this->viewBuilder()->setOption('serialize', ['message']);
    }
}

しばしば、RESTful なコントローラーは、リクエストの種類ごとに異なるビューファイルを扱うためにパースされた拡張子を使用します。REST リクエストが処理できるようになったので、XML ビューなどが作成できます。CakePHP の組み込みの JSON と XML ビュー を利用して JSON ビューを作成できます。組み込みの XmlView を扱うために、 _serialize というビュー変数を定義します。この特別なビュー変数は、 XmlView の中に取り込まれ、出力結果が XML に変換されます。

XML データに変換する前にデータを修正したい場合は、 _serialize ビュー変数ではなく、ビューファイルを使いましょう。RecipesController に対する REST ビューを templates/Recipes/xml 以下に置きます。 Xml クラスを使えば、このビューファイル内で簡単に素早く XML を出力させることができます。下記に index ビューの例を載せます。

// templates/Recipes/xml/index.php
// $recipes 配列に対して いくつかのフォーマットと操作を行う。
$xml = Xml::fromArray(['response' => $recipes]);
echo $xml->asXML();

Cake\Routing\Router::extensions() を使って、特定のコンテンツタイプを扱う場合、 CakePHP は自動的にそのタイプに対応するビューヘルパーを探します。ここではコンテンツタイプとして XML を利用していて、標準のビルトインヘルパーは存在しないのですが、もし自作のヘルパーがあれば CakePHP はそれを自動読込みして利用可能にします。

レンダリングされた XML は下記のような感じになります。

<recipes>
    <recipe>
        <id>234</id>
        <created>2008-06-13</created>
        <modified>2008-06-14</modified>
        <author>
            <id>23423</id>
            <first_name>Billy</first_name>
            <last_name>Bob</last_name>
        </author>
        <comment>
            <id>245</id>
            <body>Yummy yummmy</body>
        </comment>
    </recipe>
    ...
</recipes>

edit アクションのロジックを作るのは少しだけトリッキーです。XML 出力 の API を提供する場合、入力も XML で受付けるほうが自然です。心配せずとも、 Cake\Controller\Component\RequestHandler と Cake\Routing\Router クラスが取り計らってくれます。 POST もしくは PUT リクエストのコンテンツタイプが XML であれば、入力データは CakePHP の Xml クラスに渡され、配列に変換され、 $this->request->getData() に入ります。この機能によって、XML と POST データの処理はシームレスになるのです。コントローラーもモデルも XML の入力を気にせずに、 $this->request->getData() のみを扱えば良いのです。

他のフォーマットのインプットデータ

REST アプリケーションの場合、様々なフォーマットのデータを扱います。 CakePHP では、 RequestHandlerComponent クラスが助けてくれます。デフォルトでは、POST や PUT で送られてくる JSON/XML の入力データはデコードされ、配列に変換されてから $this->request->getData() に格納されます。独自のデコード処理も RequestHandler::addInputType() を利用すれば追加可能です。

RESTful ルーティング

CakePHP の Router は、 RESTful なリソースルートへの接続は容易です。詳しくは、 RESTful なルーティング セクションをご覧ください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

セキュリティ

CakePHP はあなたのアプリケーションをセキュアにする幾つかのツールを提供します。以下のセクションにこれらのツールを記載します。

セキュリティユーティリティ

Form Protection Middleware

CSRF Protection

Content Security Policy

Security Headers

HTTPS Enforcer

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

セキュリティユーティリティ

class Cake\Utility\Security

security library [https://api.cakephp.org/4.x/class-Cake.Utility.Security.html] は、データのハッシュ化や暗号化などのメソッドなどの基本的なセキュリティ分野を取り扱います。

データの暗号化と復号

static Cake\Utility\Security::encrypt($text, $key, $hmacSalt = null)

static Cake\Utility\Security::decrypt($cipher, $key, $hmacSalt = null)

$text の暗号化には AES-256 を利用します。 $key は例えば「よいパスワード」のように、沢山の分散された値であるべきです。返却される結果は HMAC チェックサム（checksum）つきの暗号化された値となります。

このメソッドは、 openssl [https://php.net/openssl] か mcrypt [https://php.net/mcrypt] のいずれかを利用し、あなたのシステムで有効であることが基準になります。いずれかの実装で暗号化されたデータは、もう一方の実装に移植できます。

警告

mcrypt [https://php.net/mcrypt] 拡張は、PHP7.1 で非推奨になりました。

このメソッドは 決して パスワードの保存に使ってはいけません。代わりに一方通行のハッシュ化メソッド hash() を利用すべきです。以下に一例を挙げます。

// キーがどこかに保存されたと仮定すれば、あとで復号のために再利用されることが可能
$key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA';
$result = Security::encrypt($value, $key);

もしあなたが HMAC ソルトを提供しなければ、 Security.salt の値が利用されます。暗号化された値は Cake\Utility\Security::decrypt() を利用して復号できます。

すでに暗号化された値を復号します。 $key と $hmacSalt のパラメーターは、暗号化時に利用された各々の値と一致する必要があり、さもなければ復号は失敗します。以下に一例を挙げます。

// キーがどこかに保存されたと仮定すれば、あとで復号のために再利用されることが可能
$key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA';

$cipher = $user->secrets;
$result = Security::decrypt($cipher, $key);

キーや HMAC ソルトの変更により値が復号できない場合、 false が返却されます。

特定の暗号化を実装を選択

もし CakePHP 2.x からアプリケーションをアップグレードするならば、 2.x で暗号化されたデータは openssl では互換性がありません。これは、暗号化されたデータは AES に完全には準拠していないからです。もしデータを再度暗号化するときにトラブルを避けたければ、 engine() メソッドを使用して mcrypt の利用を CakePHP に強制できます。

// config/bootstrap.php の中で
use Cake\Utility\Crypto\Mcrypt;

Security::engine(new Mcrypt());

上記は古いバージョンの CakePHP のデータのシームレスな読み込みを許可し、新しいデータを暗号化すると OpenSSL 互換となります。

データのハッシュ化

static Cake\Utility\Security::hash($string, $type = NULL, $salt = false)

このメソッドで文字列からハッシュを作成します。次の有効なメソッドを頼ってください。もし $salt が true にセットされていた場合、アプリケーションのソルト値が利用されます。

// アプリケーションのソルト値を利用
$sha1 = Security::hash('CakePHP Framework', 'sha1', true);

// カスタムソルト値を利用
$sha1 = Security::hash('CakePHP Framework', 'sha1', 'my-salt');

// デフォルトのハッシュアルゴリズムを利用
$hash = Security::hash('CakePHP Framework');

hash() メソッドは以下のハッシュ方法をサポートします。

md5

sha1

sha256

そして、PHP の hash() 関数がサポートしている他のハッシュアルゴリズムもサポートします。

警告

新しいアプリケーションのパスワード用に hash() を利用すべきではありません。代わりにデフォルトで bcrypt を利用する DefaultPasswordHasher クラスを利用すべきです。

セキュアなランダムデータの取得

static Cake\Utility\Security::randomBytes($length)

セキュアなランダムソースから $length バイト数を取得します。この関数は、以下のソースの１つからデータを生成します。

PHP の random_bytes 関数。

SSL 拡張の openssl_random_pseudo_bytes 。

どちらのソースも利用できない場合、警告が発せられ、後方互換のために安全ではない値が使用されます。

static Cake\Utility\Security::randomString($length)

セキュアなランダムソースから長さ $length のランダムな文字列を取得します。このメソッドは、 randomBytes() と同じランダムソースから生成し、データを16進文字列としてエンコードします。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CSRFプロテクション

クロスサイトリクエストフォージェリ（CSRF）は、認証されたユーザーの知らないうちに同意なしに不正なコマンドが実行されるエクスプロイト(攻撃手法)の一種です

CakePHPは、2つの形式のCSRFプロテクションを提供します。:

SessionCsrfProtectionMiddleware は、CSRFトークンをセッションに保存します。このため、side-effects(「副作用」と訳されますが、コンピューティング用語としては、想定外の動作を起こす可能性を持つあらゆる処理を意味します。エクスプロイトはこれを悪用します)を持つすべてのリクエストでセッションを開く必要があります。セッションベースのCSRFトークンの利点は、それらが特定のユーザーにスコープされ、セッションが有効である間のみ有効であるということです。

CsrfProtectionMiddleware はCSRFトークンをクッキーに保存します。 クッキー values クッキーを使用すると、サーバー上にstateがなくてもCSRFチェックを実行できます。クッキーの値は、HMACチェックを使用して信頼性が検証されます。ただし、ステートレスであるため、CSRFトークンはユーザーとセッション間で再利用できます。

注釈

両方のアプローチを同時に使用することはできません。ひとつだけを選択する必要があります。両方のアプローチを同時に使用すると、すべての PUT および POST リクエストでCSRFトークンの不一致エラーが発生します。

CSRFミドルウェア（クロスサイトリクエストフォージェリミドルウェア）

CSRFプロテクションは、アプリケーション全体、または特定のルーティングスコープに適用できます。CSRFミドルウェアをアプリケーションミドルウェアスタックに適用することで、アプリケーション内のすべてのアクションを保護できます。:

// src/Application.php
// クッキーベースの CSRF トークンの場合
use Cake\Http\Middleware\CsrfProtectionMiddleware;

// セッションベースの CSRF トークンの場合
use Cake\Http\Middleware\SessionCsrfProtectionMiddleware;

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $options = [
        // ...
    ];
    $csrf = new CsrfProtectionMiddleware($options);
    // または
    $csrf = new SessionCsrfProtectionMiddleware($options);

    $middlewareQueue->add($csrf);
    return $middlewareQueue;
}

ルーティングスコープにCSRF保護を適用することにより、CSRFを特定のルートグループに条件付きで適用できます。:

// src/Application.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;

public function routes(RouteBuilder $routes) : void
{
    $options = [
        // ...
    ];
    $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options));
    parent::routes($routes);
}

// config/routes.php
$routes->scope('/', function (RouteBuilder $routes) {
    $routes->applyMiddleware('csrf');
});

CookieベースのCSRFミドルウェアオプション

使用可能なオプションは次のとおりです。

cookieName 送信するクッキーの名前。デフォルトは csrfToken 。

expiry CSRFトークンの存続期間。デフォルトはブラウザセッションです。

secure クッキーがSecureフラグで設定されるかどうか。つまりHTTPS接続が有効なアプリの場合のみ設定されるため、 HTTP接続の場合は失敗します。デフォルトは false です。

httponly クッキーがHttpOnlyフラグで設定されるかどうか。デフォルトは false です。4.1.0より前では、このhttpOnlyオプションを使用してください。

samesite クッキーをファーストパーティまたは同じサイトのコンテキストに制限する必要があるかどうかを宣言できます。可能な値は Lax ・ Strict ・ None で、デフォルトは null です。

field チェックするフォームフィールド。デフォルトは _csrfToken 。これを変更するには、FormHelperの構成も必要になります。

セッションベースのCSRFミドルウェアオプション

使用可能なオプションは次のとおりです。

key 使用するセッションキー。デフォルトは csrfToken です。

field チェックするフォームフィールド。これを変更するには、FormHelperの構成も必要になります。

有効にすると、リクエストオブジェクトのCSRFトークンを取得できます。:

$token = $this->request->getAttribute('csrfToken');

特定のアクションのCSRFチェックをスキップする

どちらのCSRFミドルウェア実装でも、チェックコールバック機能をスキップして、 CSRFトークンチェックを実行する必要があるURLをよりきめ細かく制御できます。:

// src/Application.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $csrf = new CsrfProtectionMiddleware();

    // コールバックが `true`を返す場合、トークンチェックはスキップされます。
    $csrf->skipCheckCallback(function ($request) {
        // Skip token check for API URLs.
        if ($request->getParam('prefix') === 'Api') {
            return true;
        }
    });

    // CSRF保護ミドルウェアの前にルーティングミドルウェアがキューに追加されていることを確認してください。
    $middlewareQueue->add($csrf);

    return $middlewareQueue;
}

注釈

CSRFプロテクションミドルウェアは、クッキーまたはセッションを使用してステートフルリクエストを処理するルートにのみ適用する必要があります。たとえば、APIを開発する場合、認証にクッキーを使用しないステートレスリクエストは CSRFの影響を受けないため、これらのルートにミドルウェアを適用する必要はありません。

フォームヘルパーとの統合

CSRFプロテクションミドルウェア は フォームヘルパー とシームレスに統合されます。フォームヘルパーを使用して作成したフォームを生成するたびに、CSRFトークンを持つ hiddenフィールドが挿入されます。

注釈

CSRFプロテクションを使用する場合は フォームヘルパー でフォームを作成する必要があります。そうでない場合は、hiddenフィールドを手動してください。

CSRFプロテクションとAJAXリクエスト

リクエストデータパラメータに加えて、CSRFトークンは特別な X-CSRF-Token ヘッダーを介して送信できます。多くの場合、ヘッダーを使用すると、CSRFトークンを重めのJavaScriptのアプリケーションまたは XML / JSONベースのAPIエンドポイントと簡単に統合できます。

CSRFトークンは、JavaScriptでは csrfToken クッキーを介して、 PHPでは csrfToken という名前のリクエストオブジェクト属性を介して取得できます。 JavaScriptコードがCakePHPビューテンプレートとは別のファイルにある場合、およびJavaScriptを介してクッキーを解析する機能がすでにある場合は、クッキーの使用が簡単になる可能性があります。

個別のJavaScriptファイルがあるが、クッキーの処理をしたくない場合は、たとえば、次のようなスクリプトブロックを定義することにより、レイアウトのグローバルJavaScript変数にトークンを設定できます。:

echo $this->Html->scriptBlock(sprintf(
    'var csrfToken = %s;',
    json_encode($this->request->getAttribute('csrfToken'))
));

次に、このスクリプトブロックの後にロードされる任意のスクリプトファイルで、 csrfToken または window.csrfToken としてトークンにアクセスできます。

metaタグにトークンを設定する方法もあります。:

echo $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken'));

csrfToken という名前の metaタグ を探すことでスクリプトからアクセスできます。 jQueryを使用する場合と同じくらい簡単で

var csrfToken = $('meta[name="csrfToken"]').attr('content');

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コンテンツセキュリティポリシーミドルウェア

CspMiddleware を使用すると、Content-Security-Policyヘッダーをアプリケーションに簡単に追加できます。使用する前に paragonie/csp-builder をインストールしておく必要があります

composer require paragonie/csp-builder

次に、配列を使用するか、ビルドされた CSPBuilder オブジェクトを渡すことで、ミドルウェアを構成できます。:

use Cake\Http\Middleware\CspMiddleware;

$csp = new CspMiddleware([
    'script-src' => [
        'allow' => [
            'https://www.google-analytics.com',
        ],
        'self' => true,
        'unsafe-inline' => false,
        'unsafe-eval' => false,
    ],
]);

$middlewareQueue->add($csp);

厳密なCSP構成を使用したい場合は、 scriptNonce および styleNonce オプションを使用してノンス(ナンス)ベースのCSPルールを有効にできます。これらのオプションを有効にすると、CSPポリシーが変更され、リクエストに cspScriptNonce 属性と``cspStyleNonce`` 属性が設定されます。これらの属性は、 HtmlHelper によって作成されたすべてのスクリプトおよびCSSリンク要素のnonce属性に適用されます。これにより nonce-base64 [https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy/script-src] と strict-dynamic を使用するポリシーの採用が簡素化され、セキュリティが強化され、メンテナンスが容易になります。:

$policy = [
    // script-srcにノンスを設定するには、内容が空の場合でも作成する必要があります
    'script-src' => [],
    'style-src' => [],
];
// スクリプトとCSSリンクタグへのナンス自動追加を有効にします。
$csp = new CspMiddleware($policy, [
    'scriptNonce' => true,
    'styleNonce' => true,
]);
$middlewareQueue->add($csp);

バージョン 4.3.0 で追加: ナンス自動追加機能が実装されました。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

セキュリティヘッダーミドルウェア

セキュリティヘッダーミドルウェア レイヤーを使用すると、セキュリティ関連のヘッダーをレスポンスに追加できます。

X-Content-Type-Options

X-Download-Options

X-Frame-Options

X-Permitted-Cross-Domain-Policies

Referrer-Policy

このミドルウェアは、アプリケーションのミドルウェアスタックに追加される前に、流暢なインターフェイスを使用して構成されます。

このミドルウェアは、ミドルウェアスタックに適用される前に、スマートなインターフェイスを使用して構成されます。:

use Cake\Http\Middleware\SecurityHeadersMiddleware;

$securityHeaders = new SecurityHeadersMiddleware();
$securityHeaders
    ->setCrossDomainPolicy()
    ->setReferrerPolicy()
    ->setXFrameOptions()
    ->setXssProtection()
    ->noOpen()
    ->noSniff();

$middlewareQueue->add($securityHeaders);

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

HTTPSエンフォーサミドルウェア

アプリケーションをHTTPS接続経由でのみ使用できるようにする場合は、 HttpsEnforcerMiddleware を使用できます:

use Cake\Http\Middleware\HttpsEnforcerMiddleware;

// Always raise an exception and never redirect.
$https = new HttpsEnforcerMiddleware([
    'redirect' => false,
]);

// Send a 302 status code when redirecting
$https = new HttpsEnforcerMiddleware([
    'redirect' => true,
    'statusCode' => 302,
]);

// Send additional headers in the redirect response.
$https = new HttpsEnforcerMiddleware([
    'headers' => ['X-Https-Upgrade' => 1],
]);

// Disable HTTPs enforcement when ``debug`` is on.
$https = new HttpsEnforcerMiddleware([
    'disableOnDebug' => true,
]);

GETメソッドを使用しない非HTTPリクエストを受信すると、 BadRequestException が発生します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

セッション

CakePHP は PHP のネイティブ session 拡張上に、ユーティリティ機能のスイートとラッパーを提供します。セッションはリクエストにまたがるユニークユーザーの識別と各ユーザーごとの永続的データの格納を可能にします。クッキーとは異なり、セッションデータはクライアント側では利用されません。 CakePHP において、 $_SESSION の利用は通常避けています。代わりに Session クラスの利用を推奨しています。

セッションの設定

セッションの設定は、通常 /config/app.php で定義されます。また、いくつかのオプションが使用可能です:

Session.timeout - CakePHP のセッションハンドラがセッションを破棄するまでの時間を分 単位で指定します。

Session.defaults - セッションの設定の基礎としてビルトインのデフォルト設定を使用出来ます。ビルトインの defaults に関しては、以下をご覧ください。

Session.handler - カスタムセッションハンドラーの定義が可能です。コアデータベースとキャッシュセッションハンドラーが使います。このオプションは以前のバージョンの Session.save を置き換えます。詳しくはセッションハンドラーの追加情報をご覧下さい。

Session.ini - 追加のセッション ini セッティングを config に加えることが出来ます。これは Session.handler と合わせて以前のバージョンのカスタムセッションハンドリング機能を置き換えます。

Session.cookie - 使用するクッキー名。デフォルトは、php.iniの、session.name で設定された値です。

Session.cookiePath - セッションクッキーを設定するための url パス。 php.ini の設定 session.cookie_path にマップします。デフォルトは、アプリのベースパス。

アプリケーションが SSL プロトコル上にある時、CakePHP のデフォルトは、 session.cookie_secure が true です。SSL と 非 SSL の両方のプロトコルでアプリケーションを動かす場合、セッション消失の問題が発生するかも知れません。SSL と 非 SSL のドメイン両方でセッションにアクセスする必要がある場合、これを無効にします。

Configure::write('Session', [
    'defaults' => 'php',
    'ini' => [
        'session.cookie_secure' => false
    ]
]);

v4.0 以降、CakePHP はセッション Cookie の SameSite [https://owasp.org/www-community/SameSite] 属性をデフォルトで Lax に設定し、 CSRF 攻撃から保護することもできるようになりました。このデフォルト値は、session.cookie_samesite php.ini config: で変更することができます。

Configure::write('Session', [
    'defaults' => 'php',
    'ini' => [
        'session.cookie_samesite' => 'Strict'
    ]
]);

セッションクッキーパスは、デフォルトでアプリケーションのベースパスになります。これを変更するためには、 session.cookie_path ini の値を使用できます。例えば、すべてのサブドメインにまたがってセッションを永続化させたい場合、以下のようにします。

Configure::write('Session', [
    'defaults' => 'php',
    'ini' => [
        'session.cookie_path' => '/',
        'session.cookie_domain' => '.yourdomain.com'
    ]
]);

デフォルトでは、 PHP は Session.timeout の設定に関わらず、ブラウザーを閉じるとすぐに有効期限切れになるセッションクッキーを設定します。クッキーのタイムアウトは session.cookie_lifetime ini の値で制御します。以下のように設定できます。

Configure::write('Session', [
    'defaults' => 'php',
    'ini' => [
        // サイト上のページに訪問せず 30 分経つとクッキーを無効にします。
        'session.cookie_lifetime' => 1800
    ]
]);

Session.timeout と session.cookie_lifetime の値の間の違いは、後者はクライアントがクッキーを正しく伝えていることに依存していることです。もしあなたがより厳密なタイムアウトチェックを必要な場合、クライアントの情報を信頼せず、 Session.timeout を使用してください。

Session.timeout は、ユーザーが無活動の合計時間 (例えば、セッションが使われるページを見ていない時間)に相当し、そして、ユーザーがサイト上に留まることのできる合計時間(分)を制限しないことに注意してください。

ビルトインセッションハンドラーと設定

CakePHP にはいくつかビルトインなセッションの設定があります。これらをセッションの設定の基本として使用するか、完全にカスタマイズしたソリューションを作成するかは自由です。デフォルトを使用する場合、単純に 'defaults' キーに使用したいデフォルト名をセットします。セッション config で宣言をすればサブセッティングだけを上書きすることも出来ます。

Configure::write('Session', [
    'defaults' => 'php'
]);

上記はビルトインの 'php' 設定を使用します。下記のように全てまたは部分的に設定を上書きすることも出来ます。

Configure::write('Session', [
    'defaults' => 'php',
    'cookie' => 'my_app',
    'timeout' => 4320 // 3日間
]);

上記は 'php' 設定のタイムアウトとクッキー名を上書きします。ビルトイン設定は:

php - php.ini ファイルの標準セッティングに従ってセッションを保存します。

cake - セッションをファイルとして app/tmp/sessions に保存します。ホストの設定がホームディレクトリー以外の書き込みを禁止している場合、有効なオプションです。

database ビルトインのデータベースセッションを使用します。詳しくは下記をご覧下さい。

cache - ビルトインのキャッシュセッションを使用します。詳しくは下記をご覧下さい。

セッションハンドラー

セッションハンドラーはセッション config 配列でも定義出来ます。 'handler.engine' 設定キーを定義すると、クラス名の命名やハンドラーインスタンスの提供ができます。クラスやオブジェクトは、ネイティブ PHP の SessionHandlerInterface を実装しなければなりません。このインターフェースを実装することによって、 Session がハンドラーのメソッドと自動的にマップすることができます。コアの Cache や Database セッションハンドラーは、セッションに保存する際にこのメソッドを使用します。ハンドラーのための追加の設定は、 handler 配列内に配置されます。ハンドラー内からこれらの値を読むことができます。

'Session' => [
    'handler' => [
        'engine' => 'DatabaseSession',
        'model' => 'CustomSessions'
    ]
]

上記は、どのようにアプリケーションのモデルを使ってデータベースセッションハンドラーを設定できるかを示しています。クラス名をあなたの handler.engine に使用した時、 CakePHP は、 Http\Session 名前空間内にクラスがあることを期待します。例えば、 AppSessionHandler クラスを持っていた場合、ファイルは、 src/Http/Session/AppSessionHandler.php に置いてください。そして、クラス名は、 App\Http\Session\AppSessionHandler にしてください。プラグインの中のセッションハンドラーを使うこともできます。その場合、エンジンを MyPlugin.PluginSessionHandler のように設定します。

データーベースセッション

もし、セッションデータを保存するためにデータベースを使用する必要がある場合、以下のように設定してください。

'Session' => [
    'defaults' => 'database'
]

この設定は、以下の項目を持つデータベーステーブルが必要になります。

CREATE TABLE `sessions` (
  `id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created` datetime DEFAULT CURRENT_TIMESTAMP, -- Optional
  `modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- Optional
  `data` blob DEFAULT NULL, -- for PostgreSQL use bytea instead of blob
  `expires` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

アプリケーションスケルトン [https://github.com/cakephp/app] の中の config/schema/sessions.sql に、sessions テーブルのためのスキーマのコピーがあります。

セッションの保存を処理するための独自の Table クラスを使用することもできます。

'Session' => [
    'defaults' => 'database',
    'handler' => [
        'engine' => 'DatabaseSession',
        'model' => 'CustomSessions'
    ]
]

上記は、 Session にビルドインの 'database' の defaults を使用することを伝え、データベースにセッション情報を保存するために CustomSessions と呼ばれる Table に委任することを指定します。

キャッシュセッション

キャッシュクラスはセッションの格納にも使用されます。これはキャッシュ内のセッションを APCu または Memcached のように格納することを可能にします。キャッシュセッションの使用ではいくつか注意する点があります。もし、キャッシュ容量を使い果たした場合、セッションは、レコードが追い出されるように有効期限切れになり始めます。

キャッシュベースのセッションを使うために Session の config を以下のように設定します。

Configure::write('Session', [
    'defaults' => 'cache',
    'handler' => [
        'config' => 'session'
    ]
]);

これは Session に CacheSession クラスをセッション保存先として委任する設定です。 'config' キーをキャッシュの設定に使用できます。デフォルトのキャッシュ設定は 'default' です。

ini ディレクティブの設定

ビルドイン defaults は、セッション設定のための共通の基盤を提供することを試みます。必要に応じて特定の ini フラグを微調整することもあります。 CakePHP ではデフォルト設定にしろ、カスタム設定にしろ、両者の ini セッティングをカスタマイズ出来ます。セッションセッティングの ini キーで、個別の設定値を指定することが可能です。例えば session.gc_divisor のようなセッティングをコントロールするのに使えます。

Configure::write('Session', [
    'defaults' => 'php',
    'ini' => [
        'session.cookie_name' => 'MyCookie',
        'session.cookie_lifetime' => 1800, // 30分有効
        'session.gc_divisor' => 1000,
        'session.cookie_httponly' => true
    ]
]);

カスタムセッションハンドラーの作成

カスタムセッションハンドラーの作成は CakePHP で容易に出来ます。この例で、セッションをキャッシュ (APC) とデータベースの両方に格納するセッションハンドラーを作成します。これは APC による、キャッシュ限度を超過した際の消失について心配が不要な、最善で高速な IO をもたらします。

まずカスタムクラスを作成し src/Http/Session/ComboSession.php に保存する必要があります。クラスは以下のようになります。

namespace App\Http\Session;

use Cake\Cache\Cache;
use Cake\Core\Configure;
use Cake\Http\Session\DatabaseSession;

class ComboSession extends DatabaseSession
{
    protected $cacheKey;

    public function __construct()
    {
        $this->cacheKey = Configure::read('Session.handler.cache');
        parent::__construct();
    }

    // セッションからデータ読込み
    public function read($id): string
    {
        $result = Cache::read($id, $this->cacheKey);
        if ($result) {
            return $result;
        }
        return parent::read($id);
    }

    // セッションへのデータ書込み
    public function write($id, $data): bool
    {
        Cache::write($id, $data, $this->cacheKey);
        return parent::write($id, $data);
    }

    // セッションの破棄
    public function destroy($id): bool
    {
        Cache::delete($id, $this->cacheKey);
        return parent::destroy($id);
    }

    // 有効期限切れセッションの削除
    public function gc($expires = null): bool
    {
        return parent::gc($expires);
    }
}

このクラスはビルトインの DatabaseSession を継承しそのロジックや振る舞いを重複して定義することを避けています。それぞれのオペレーションを Cake\Cache\Cache オペレーションでラップします。これで高速なキャッシュからセッションを取得しつつ、キャッシュ限度の考慮を不要にしています。このセッションハンドラーを使うのもまた簡単です。 app.php のセッションブロックを以下のように設定します。

'Session' => [
    'defaults' => 'database',
    'handler' => [
        'engine' => 'ComboSession',
        'model' => 'Session',
        'cache' => 'apc'
    ]
],
// apc キャッシュ config を追加すること
'Cache' => [
    'apc' => ['engine' => 'Apc']
]

これでアプリケーションはカスタムセッションハンドラーを使ったセッションデータの読み書きを行います。

class Session

セッションオブジェクトへのアクセス

リクエストオブジェクトにアクセスできる任意の場所でセッションデータにアクセスすることができます。これは、以下でセッションにアクセス可能であることを意味します。

Controllers

Views

Helpers

Cells

Components

基本的なセッションの使用例は以下の通りです。

$name = $this->request->getSession()->read('User.name');

// 複数回セッションにアクセスする場合、
// ローカル変数にしたくなるでしょう。
$session = $this->request->getSession();
$name = $session->read('User.name')

ヘルパーでは、 $this->getView()->getRequest() を使用して、リクエストオブジェクトを取得します。コンポーネントでは、 $this->getController()->getRequest() を使用します。

セッションデータの読込みと書込み

Session::read($key, $default = null)

Hash::extract() 互換の構文を使ってセッションから値を読込みます。

$session->read('Config.language', 'en');

バージョン 4.1.0 で変更: default パラメータが追加されました。

Session::readOrFail($key)

Null値でない戻り値に対する便宜的なラッパーと同じです。

$session->readOrFail('Config.language');

これは、このキーが設定されなければならないことが分かっていて、コード自体でその存在を確認する必要がない場合に便利です。

バージョン 4.1.0 で追加: readOrFail() が追加されました。

Session::write($key, $value)

$key は、ドット区切りで $value の書込み先を指定します。

$session->write('Config.language', 'en');

以下のように１つもしくは複数のハッシュを指定することもできます。

$session->write([
  'Config.theme' => 'blue',
  'Config.language' => 'en',
]);

Session::delete($key)

セッションからデータ削除が必要なら delete() が使用できます。

$session->delete('Some.value');

static Session::consume($key)

セッションからデータの読込みと削除が必要なら consume() が使用できます。

$session->consume('Some.value');

Session::check($key)

セッションにデータが存在するかどうかを知りたいなら check() が使用できます。

if ($session->check('Config.language')) {
    // Config.language が存在し null ではない。
}

セッションの破棄

Session::destroy()

ユーザーがログアウトするときにセッションの破棄は便利です。セッションを破棄するために destroy() メソッドを使用してください。

$session->destroy();

セッションの破棄は、セッション内の全てのサーバー側データを削除しますが、セッションクッキーの削除はしません 。

セッション ID の切替え

Session::renew()

ユーザーがログインやログアウトした時、 AuthComponent は自動的にセッション ID を更新しますが、セッション ID を手動で切り替えたい時もあるでしょう。そのためには、 renew() メソッドを使います。

$session->renew();

フラッシュメッセージ

フラッシュメッセージは、エンドユーザーに一度だけ表示する短いメッセージです。それらは、エラーメッセージの表示や、アクションが上手くいったことを確認するためにしばしば用いられます。

フラッシュメッセージのセットや表示には、フラッシュ と Flash を使いましょう。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

テスト

CakePHP には PHPUnit [https://phpunit.de] をベースとした高度なインテグレーションが組み込まれており、PHPUnit 本体が持つ機能に加えて、CakePHPでテストをスマートに管理するための便利な拡張機能を備えています。このセクションでは、PHPUnit のインストールからユニットテストの始め方、そしてCakePHP が提供する拡張機能について説明します。

PHPUnit のインストール

CakePHP のテストフレームワークは PHPUnit をベースとしています。PHPUnit は PHP ユニットテストフレームワークのデファクトスタンダードであり、思いどおりのコードを安全に書くための豊富な機能を提供します。 PHPUnit は Composer [https://getcomposer.org] または PHAR パッケージ [https://phpunit.de/#download] のいずれかを使用してインストールできます。

Composer による PHPUnit のインストール

Composer で PHPUnit をインストールする場合:

$ php composer.phar require --dev phpunit/phpunit:"^8.5"

コマンドラインで上記のように実行すると、 composer.json の require-dev セクションに依存関係を追加し、すべての依存関係と一緒に PHPUnit をインストールします。

これで、PHPUnit を以下のように実行することができます。

$ vendor/bin/phpunit

PHAR ファイルを使用する場合

phpunit.phar ファイルをダウンロードすると、テストを実行するために使用することができます。

php phpunit.phar

Tip

次のようにすると、都合よく phpunit.phar をグローバルに Unix や Linux で利用できます。

chmod +x phpunit.phar
sudo mv phpunit.phar /usr/local/bin/phpunit
phpunit --version

Windows 上で PHPUnit の PHAR をグローバルにインストールする方法 [https://phpunit.de/manual/current/ja/installation.html#installation.phar.windows] に関する手順については、PHPUnit のドキュメントを参照してください。

テスト用データベースのセットアップ

テストを実行する前に config/app.php ファイルで debug が有効になっていることを忘れないでください。テストを実行する前に config/app.php に test データソース設定を追加する必要があります。この設定は、CakePHP でフィクスチャーのテーブルとデータのために使用されます。

'Datasources' => [
    'test' => [
        'datasource' => 'Cake\Database\Driver\Mysql',
        'persistent' => false,
        'host' => 'dbhost',
        'username' => 'dblogin',
        'password' => 'dbpassword',
        'database' => 'test_database'
    ],
],

注釈

トラブルを防ぐため、テストデータベースはメインのデータベースとは別に用意することをお勧めします。

テストのセットアップの確認

PHPUnit をインストールして test データソースを設定した後、 独自のテストを作成し実行する準備ができていることを、アプリケーションのテストを実行することにより確認できます。

# phpunit.phar について
$ php phpunit.phar

# Composer でインストールされた phpunit
$ vendor/bin/phpunit

上記を実行するとテストが実行されます(テストが作成されている場合)。

特定のテストを実行したい場合は、パラメーターとしてテストのパスを指定します。例えば、ArticlesTable クラスのテストケースがある場合、次のように実行します。

$ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest

実行したテストや成功したテスト・失敗したテストの数など、 各種情報がカラーで表示されます。

注釈

Windows システムの場合、おそらくカラー表示はされません。

テストケースの規約

CakePHP が全般的にそうであるように、テストケースにもいくつか規約があります。以下のとおりです。

テストを含むPHPファイルは、 tests/TestCase/[Type] ディレクトリーに置きます。

ファイル名のサフィックスは .php ではなく Test.php とします。

テストを含むクラスは Cake\TestSuite\TestCase 、 Cake\TestSuite\IntegrationTestCase または \PHPUnit\Framework\TestCase を継承する必要があります。

他のクラス名と同様に、テストケースのクラス名はファイル名と一致する必要があります。 RouterTest.php は、 class RouterTest extends TestCase が含まれている必要があります。

テストを含むメソッド (つまり、アサーションを含むメソッド) の名前は testPublished() のように test で始める必要があります。 @test というアノテーションをメソッドにマークすることでテストメソッドとすることもできます。

最初のテストケースを作成

一例として、とても簡単な、ヘルパーメソッドのためのテストケースを作成します。これからテストのために作成するメソッドは HTML でプログレスバーを描画するものです。ヘルパーは次のようになります。

namespace App\View\Helper;

use Cake\View\Helper;

class ProgressHelper extends Helper
{
    public function bar($value)
    {
        $width = round($value / 100, 2) * 100;
        return sprintf(
            '<div class="progress-container">
                <div class="progress-bar" style="width: %s%%"></div>
            </div>', $width);
    }
}

作成したヘルパーを保存したら、 tests/TestCase/View/Helper/ProgressHelperTest.php としてテストケースのファイルを作成します。このファイルにまず、以下のように書き込みます。:

namespace App\Test\TestCase\View\Helper;

use App\View\Helper\ProgressHelper;
use Cake\TestSuite\TestCase;
use Cake\View\View;

class ProgressHelperTest extends TestCase
{
    public function setUp(): void
    {
    }

    public function testBar(): void
    {
    }
}

空のメソッドが2つあります。次にメソッドの中身を書きます。最初は setUp() です。このメソッドはこのテストケースクラスのテストメソッドが 呼び出される前に毎回呼び出されます。セットアップメソッドはテストに必要なオブジェクトの初期化や設定を行います。今回のセットアップメソッドには次のように書き加えます。

public function setUp(): void
{
    parent::setUp();
    $View = new View();
    $this->Progress = new ProgressHelper($View);
}

親メソッドを必ずロードしてください。 TestCase::setUp() は、 Configure の値をバックアップしたり、 App にパスを保存したりといった、いくつかの作業をしているからです。

次に、テストメソッドの内容を記述します。期待した結果を出力できるかどうかをテストするため「アサーション」を使います。

public function testBar(): void
{
    $result = $this->Progress->bar(90);
    $this->assertStringContainsString('width: 90%', $result);
    $this->assertStringContainsString('progress-bar', $result);

    $result = $this->Progress->bar(33.3333333);
    $this->assertStringContainsString('width: 33%', $result);
}

assertStringContainsString() というアサーションを用いることで、ヘルパーが返した値に期待した文字列が含まれていることをテストできます。期待した文字列が含まれていなければテストは失敗し、コードが正しくないことがわかります。

テストケースを使うことにより、 既知の入力セットと期待される出力結果との関係を 簡単に記述することができます。つまり、書いたコードが期待した動作を満たしているかどうかを自動的にテストできます。これにより、新たなバグの発生を未然に検知し、私達は自信を持って開発を進めていくことができるようになります。

注釈

EventManager は、各テストメソッドごとにリフレッシュされます。これは、一度に複数のテストを実行した際、ブートストラップは一度だけ実行されるため、 config/bootstrap.php に登録されたイベントリスナーは失われることを意味します。

テストの実行

PHPUnit をインストールし、テストケースをいくつか書いたら、テストを何度も実行したくなるでしょう。すべての変更をコミットする前に、何も壊れていないことを確認するために、テストを実行することをお勧めします。

phpunit を使うことで、アプリケーションのテストを実行できます。アプリケーションのテストを実行するには、シンプルに実行することができます。

# composer でインストールされたファイルを実行する場合
vendor/bin/phpunit

# phar 形式のファイルを実行する場合
php phpunit.phar

GitHub から CakePHP ソース [https://github.com/cakephp/cakephp] をクローンして CakePHP のユニットテストを実行したい場合、 phpunit を実行する前に、すべての依存関係がインストールされているように、以下の Composer コマンドを実行することを忘れないでください。

composer install

アプリケーションのルートディレクトリーから以下を行います。アプリケーションのソースの一部であるプラグインのテストを実行するには、まず cd でプラグインディレクトリーに移動し、その後、 PHPUnit のインストール方法に合わせて phpunit コマンドを使用してください。

cd plugins

# composer でインストールされた phpunit を使用
../vendor/bin/phpunit

# phar 形式のファイルを使用
php ../phpunit.phar

スタンドアロンのプラグインのテストを実行するには、最初に別のディレクトリーにプロジェクトをインストールして、その依存関係をインストールする必要があります。

git clone git://github.com/cakephp/debug_kit.git
cd debug_kit
php ~/composer.phar install
php ~/phpunit.phar

テストケースのフィルタリング

たくさんのテストケースがあると、その中からサブセットだけをテストしたいときや、失敗したテストだけを実行したいときがあると思います。コマンドラインからテストメソッドをフィルタリングするときはオプションを使用します。

$ phpunit --filter testSave tests/TestCase/Model/Table/ArticlesTableTest

テストメソッドを実行するためフィルタリングとして、filter パラメーターは大文字と小文字を区別する正規表現を使用します。

コードカバレッジの生成

PHPUnit に組み込まれたコードカバレッジツールを用いて、コードカバレッジのレポートを HTML ファイル形式で生成することができます。テストケースのカバレッジを生成するには以下のようにします。

$ phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest

カバレッジ結果のHTMLファイルは、アプリケーションの webroot ディレクトリー内に生成されます。 http://localhost/your_app/coverage にアクセスすると、結果を表示することができます。

また、カバレッジを生成するために xdebug の代わりに phpdbg を使用できます。カバレッジの生成は phpdbg の方が高速です。

$ phpdbg -qrr phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest

プラグインテストのためのテストスイート

アプリケーションは、複数のプラグインで構成されることもあります。各プラグインのテストは手間がかかるものですが、アプリケーションの phpunit.xml.dist ファイルに <testsuite> セクションを追加すると、アプリケーションを構成する各プラグインのテストを手軽に実行することができます。

<testsuites>
    <testsuite name="app">
        <directory>./tests/TestCase/</directory>
    </testsuite>

    <!-- Add your plugin suites -->
    <testsuite name="forum">
        <directory>./plugins/Forum/tests/TestCase/</directory>
    </testsuite>
</testsuites>

phpunit を使用すると、 <testsuites> 要素に追加されたテストスイートは自動的に実行されます。

もし、 composer でインストールされたプラグインのフィクスチャーを使用するために <testsuites> を使用している場合、プラグインの composer.json ファイルにフィクスチャーの名前空間を autoload セクションに追加してください。例:

"autoload-dev": {
    "psr-4": {
        "PluginName\\Test\\Fixture\\": "tests/Fixture/"
    }
},

テストケースのライフサイクルコールバック

テストケースは以下のようにいくつかのライフサイクルコールバックを持っており、テストの際に使うことができます。

setUp は、テストメソッドの前に毎回呼び出されます。テストされるオブジェクトの生成や、テストのためのデータの初期化に使われます。 parent::setUp() を呼び出すことを忘れないでください。

tearDown は、テストメソッドの後に毎回呼び出されます。テストが完了した後のクリーンアップに使われます。 parent::tearDown() を呼び出すことを忘れないでください。

setupBeforeClass はクラスのテストメソッドを実行する前に一度だけ呼ばれます。このメソッドは static でなければなりません。

tearDownAfterClass はクラスのテストメソッドをすべて実行した後に一度だけ呼ばれます。このメソッドは static でなければなりません。

フィクスチャー

テストコードの挙動がデータベースやモデルに依存するとき、テストに使うためのテーブルを生成し、一時的なデータをロードするために フィクスチャー を使うことができます。フィクスチャーを使うことにより、 実際のアプリケーションに使われているデータを破壊することなくテストができるというメリットがあります。 また、アプリケーションのためのコンテンツを実際に用意するより先にコードをテストすることができます。

このとき、CakePHP は設定ファイル config/app.php にある test という名前のデータベース接続設定を使います。この接続が使えないときは例外が発生し、フィクスチャーを使うことができません。

CakePHP はフィクスチャーに基づいたテストケースを実行するにあたり、以下の動作をします。

各フィクスチャーで必要なテーブルを作成します。

フィクスチャーにデータが存在すれば、それをテーブルに投入します。

テストメソッドを実行します。

フィクスチャーのテーブルを空にします。

テスト接続

デフォルトでは、CakePHP のアプリケーション内の各データベース接続は別名になります。アプリケーションのブートストラップで定義された (test_ がつかない) 各データベース接続は、 test_ プレフィクスがついた別名を持つことになります。テストケースで誤って間違った接続を使用しないことを、エイリアシングの接続が保証します。接続エイリアシングは、アプリケーションの残りの部分には透過的です。例えば 'default' コネクションを使用している場合、代わりに、テストケースで test コネクションを取得します。 'replica' コネクションを使用する場合、テストスイートは 'test_replica' を使おうとします。

PHPUnitの設定

フィクスチャーを使う前に、 phpunit.xml にフィクスチャExtensionが含まれていることを再確認する必要があります。

<!-- in phpunit.xml -->
<!-- Setup the extension for fixtures -->
<extensions>
    <extension class="\Cake\TestSuite\Fixture\PHPUnitExtension" />
</extensions>

※CakePHP 4.3.0より以前はPHPUnitのフィクスチャExtensionではなくテストリスナー機能が使用されていたため、phpunit.xmlには下記のように書く必要があります。

<!-- in phpunit.xml -->
<!-- Setup a listener for fixtures -->
<listeners>
    <listener
    class="\Cake\TestSuite\Fixture\FixtureInjector">
        <arguments>
            <object class="\Cake\TestSuite\Fixture\FixtureManager" />
        </arguments>
    </listener>
</listeners>

※リスナーは非推奨であり、 フィクスチャ構成を更新 する必要があります。

テスト用のデータベーススキーマ作成

CakePHPのマイグレーション機能・SQLダンプファイルのロード、または他のスキーマ管理ツールを使用して、テスト用のデータベーススキーマを生成できます。アプリケーションの tests/bootstrap.php ファイルにスキーマを作成する必要があります。

CakePHPの migrations プラグイン を使用してアプリケーションのスキーマを管理する場合は、それらのマイグレーションを利用してテストデータベーススキーマを生成することもできます。:

// in tests/bootstrap.php
use Migrations\TestSuite\Migrator;

$migrator = new Migrator();

// Simple setup for with no plugins
$migrator->run();

// Run migrations for multiple plugins
$migrator->run(['plugin' => 'Contacts']);

// Run the Documents migrations on the test_docs connection.
$migrator->run(['plugin' => 'Documents', 'connection' => 'test_docs']);

複数のマイグレーションを実行する必要がある場合は、次のように実行できます。:

// Run migrations for plugin Contacts on
$migrator->runMany([
    // Run app migrations on test connection.
    ['connection' => 'test']
    // Run Contacts migrations on test connection.
    ['plugin' => 'Contacts'],
    // Run Documents migrations on test_docs connection.
    ['plugin' => 'Documents', 'connection' => 'test_docs']
]);

runMany() を使うと、データベースを共有するプラグインが、各マイグレーションが実行される時にテーブルをドロップしないようになります。

マイグレーションプラグインは、適用されていないマイグレーションのみを実行し、カレントのマイグレーションヘッドが適用されたマイグレーションと異なる場合はマイグレーションをリセットします。

データソース構成のテストでマイグレーションを実行する方法を構成することもできます。詳細については、 マイグレーションに関するドキュメント を参照してください。

SQLダンプファイルをロードしたい場合は、下記のメソッドを使用できます。:

// in tests/bootstrap.php
use Cake\TestSuite\Fixture\SchemaLoader;

// Load one or more SQL files.
(new SchemaLoader())->loadSqlFiles('path/to/schema.sql', 'test');

各テスト実行の SchemaLoader 開始時に、コネクションに紐づく全のテーブルを削除し、提供されたスキーマファイルに基づいてテーブルを再構築します。

バージョン 4.3.0 で追加: SchemaLoaderが追加されました。

フィクスチャステートマネージャ

デフォルトでは、CakePHPは、データベース内のすべてのテーブルを truncate することにより、各テストの最後にフィクスチャの状態をリセットします。この処理は、アプリケーションが大きくなるにつれてコストがかかる可能性があります。 TransactionStrategy を各テストメソッドに使用すると、テストの最後にロールバックされるトランザクション内で実行されます。これによりパフォーマンスが向上しますが、各テストの前に自動インクリメント値がリセットされないため、テストで静的フィクスチャデータに大きく依存しないようにする必要があります。

フィクスチャの状態管理は、テストケース内で定義できます。:

use Cake\TestSuite\TestCase;
use Cake\TestSuite\Fixture\FixtureStrategyInterface;
use Cake\TestSuite\Fixture\TransactionStrategy;

class ArticlesTableTest extends TestCase
{
    /**
     * Create the fixtures strategy used for this test case.
     * You can use a base class/trait to change multiple classes.
     */
    protected function getFixtureStrategy(): FixtureStrategyInterface
    {
        return new TransactionStrategy();
    }
}

バージョン 4.3.0 で追加.

フィクスチャーの作成

フィクスチャは、テストのためにデータベースに挿入されるレコードを定義します。

それでは最初のフィクスチャーを作成してみましょう。この例ではArticleモデルのフィクスチャーを作成します。以下の内容で、 tests/Fixture ディレクトリーに ArticlesFixture.php という名前のファイルを作成してください。

namespace App\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;

class ArticlesFixture extends TestFixture
{
      // (オプション) 異なるテストデータソースにフィクスチャーをロードするために、このプロパティーを設定
      public $connection = 'test';

      public $records = [
          [
              'title' => 'First Article',
              'body' => 'First Article Body',
              'published' => '1',
              'created' => '2007-03-18 10:39:23',
              'modified' => '2007-03-18 10:41:31'
          ],
          [
              'title' => 'Second Article',
              'body' => 'Second Article Body',
              'published' => '1',
              'created' => '2007-03-18 10:41:23',
              'modified' => '2007-03-18 10:43:31'
          ],
          [
              'title' => 'Third Article',
              'body' => 'Third Article Body',
              'published' => '1',
              'created' => '2007-03-18 10:43:23',
              'modified' => '2007-03-18 10:45:31'
          ]
      ];
 }

注釈

autoincrementカラムに手動で値を追加しないことをお勧めします。PostgreSQLおよびSQLServerでのシーケンス生成に干渉するためです。

$connection プロパティは、フィクスチャーが使用するデータソースを定義します。アプリケーションが複数のデータソースを使用している場合、フィクスチャーはモデルのデータソースと一致しますが、 test_ プレフィックスを付ける必要があります。例えば、お使いのモデルが mydb データソースを使用している場合、フィクスチャーは、 test_mydb データソースになります。 test_mydb 接続が存在しない場合、モデルはデフォルトの test データソースを使用します。テストを実行するときにテーブル名の衝突を避けるため、フィクスチャーのデータソースには test のプレフィックスが必ず付きます。

フィクスチャテーブルの作成後に入力される一連のレコードを定義できます。 $records はレコードの配列データです。 $records 内の各項目は単一の行である必要があります。各行の中には、行の列と値の連想配列が必要です。複数レコードを一括挿入する際に用いる $records 配列内の各レコードは、同じキー構成が必要であることに注意してください。

バージョン 4.3.0 で変更: 4.3.0より前のフィクスチャは、テーブルのスキーマも定義していました。フィクスチャでスキーマを定義する必要がある場合は、 フィクスチャのスキーマ を確認できます。

動的データ

フィクスチャレコードで関数またはその他の動的データを使用するには、フィクスチャの init() メソッドでレコードを定義できます。例えば、created と modified のタイムスタンプに今日の日付を反映させたいのであれば、以下のようにするとよいでしょう。:

namespace App\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;

class ArticlesFixture extends TestFixture
{
    public function init(): void
    {
        $this->records = [
            [
                'title' => 'First Article',
                'body' => 'First Article Body',
                'published' => '1',
                'created' => date('Y-m-d H:i:s'),
                'modified' => date('Y-m-d H:i:s'),
            ],
        ];
        parent::init();
    }
}

注釈

init() をオーバーライドするときは、必ず parent::init() をコールしてください。

テストケースにフィクスチャを読み込む

各テストケースごとにフィクスチャを定義します。クエリを実行するすべてのモデルのフィクスチャをロードする必要があります。フィクスチャをロードするには、モデルで $fixtures プロパティを定義します。:

class ArticlesTest extends TestCase
{
    protected $fixtures = ['app.Articles', 'app.Comments'];
}

4.1.0以降、フィクスチャを定義するために getFixtures() メソッドを使うことができます。:

public function getFixtures(): array
{
    return [
        'app.Articles',
        'app.Comments',
    ];
}

上記の例では、アプリケーションのFixtureディレクトリからArticleおよびCommentフィクスチャをロードします。

CakePHPコアまたはプラグインからフィクスチャをロードすることもできます。:

class ArticlesTest extends TestCase
{
    protected $fixtures = [
        'plugin.DebugKit.Articles',
        'plugin.MyVendorName/MyPlugin.Messages',
        'core.Comments'
    ];
}

core プレフィックスを使用すると、CakePHPコアからフィクスチャがロードされます。また、プラグイン名をプレフィックスとして使用すると、指定されたプラグインからフィクスチャがロードされます。

サブディレクトリを作成してフィクスチャを整理することができます。大規模なアプリケーションを使用している場合などに便利です。サブディレクトリ内のフィクスチャをロードするには、フィクスチャ名にサブディレクトリ名を含めるだけです。:

class ArticlesTest extends CakeTestCase
{
    protected $fixtures = ['app.Blog/Articles', 'app.Blog/Comments'];
}

上記の例では、各フィクスチャが tests/Fixture/Blog/ ディレクトリからロードされます。

フィクスチャファクトリー

アプリケーションが大規模になると、テストフィクスチャの量も肥大化し、システム全体の管理が困難になりがちです。フィクスチャファクトリープラグイン [https://github.com/vierge-noire/cakephp-fixture-factories] は、大規模システム管理のための有効な解決手段です。

このプラグインは、各テストの前にすべてのダーティテーブルを切り捨てるために、 テストスイートライトプラグイン [https://github.com/vierge-noire/cakephp-test-suite-light] を使用します。

下記のcakeコマンドでフィクスチャファクトリーをbakeできます。:

bin/cake bake fixture_factory -h

ファクトリー [https://github.com/vierge-noire/cakephp-fixture-factories/blob/main/docs/factories.md] のbakeが完了すると、すぐにテストフィクスチャを作成することができます。

データベースとの不要なインタラクションは、テストとアプリケーションの速度を低下させます。テストフィクスチャを永続化せずに作成できます。これは、DBとのインタラクションなしでメソッドをテストする場合に役立ちます。:

$article = ArticleFactory::make()->getEntity();

永続化したい場合は下記のように。:

$article = ArticleFactory::make()->persist();

ファクトリーは、関連するフィクスチャの生成にも役立ちます。記事が複数の著者に属していると仮定すると、たとえば、それぞれ5つの記事を持つ2人の著者を作成できます。:

$articles = ArticleFactory::make(5)->with('Authors', 2)->getEntities();

フィクスチャファクトリはフィクスチャの作成または宣言を必要としません。それでも、それらはCakePHPに付属しているフィクスチャと完全に互換性があります。 ここ [https://github.com/vierge-noire/cakephp-fixture-factories] に追加の洞察とドキュメントがあります。

テストでルーティング設定を読み込む

メール送信・コントローラー・コンポーネント、またはその他クラスのテストでURLを紐付ける必要がある場合は、ルーティング設定を読み込む必要があります。 setUp() またはそれぞれのテストメソッドの中で、 loadRoutes() を記述します:

public function setUp(): void
{
    parent::setUp();
    $this->loadRoutes();
}

このメソッドは、 Application インスタンスの作成と、そのインスタンスでの routes() メソッドの呼び出しを行ないます。この Application インスタンスのコンストラクタには、 loadRoutes($constructorArgs) としてパラメータを渡すことができます。

テストにおけるルーティングの作成

プラグインや拡張性のあるアプリケーションを開発する場合など、 テスト内で動的にルートを追加することが必要になることがあります。例えば、プラグインや拡張性のあるアプリケーションを開発する場合などです。

既存のアプリケーションのルートを読み込むのと同じように、これはテストメソッドの setup() の中で行うことができます。で、あるいは個々のテストメソッド自身で行うことができます。:

use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
use Cake\Routing\Router;
use Cake\TestSuite\TestCase;

class PluginHelperTest extends TestCase
{
    protected RouteBuilder $routeBuilder;

    public function setUp(): void
    {
        parent::setUp();

        $this->routeBuilder = Router::createRouteBuilder('/');
        $this->routeBuilder->scope('/', function (RouteBuilder $routes) {
            $routes->setRouteClass(DashedRoute::class);
            $routes->get(
                '/test/view/{id}',
                ['controller' => 'Tests', 'action' => 'view']
            );
            // ...
        });

        // ...
    }
}

これは新しいルートビルダのインスタンスを作成し、接続されたルートをマージします。を、他のすべてのルートビルダーインスタンスで使われる同じルートコレクションにマージします。接続されたルートを、その環境に存在する、あるいはまだ作成されていない他のすべてのルートビルダーインスタンスで使用される同じルートコレクションにマージします。

プラグインをロード

プラグインをロードしたい場合は loadPlugins() メソッドを使用できます。:

public function testMethodUsingPluginResources()
{
    $this->loadPlugins(['Company/Cms']);
    // Test logic that requires Company/Cms to be loaded.
}

テーブルクラスのテスト

src/Model/Table/ArticlesTable.php に ArticlesTable クラスが定義されているとします。

namespace App\Model\Table;

use Cake\ORM\Table;
use Cake\ORM\Query;

class ArticlesTable extends Table
{
    public function findPublished(Query $query, array $options): Query
    {
        $query->where([
            $this->alias() . '.published' => 1
        ]);
        return $query;
    }
}

このテーブルクラスに対するテストを設定します。以下の内容で、 tests/TestCase/Table ディレクトリーに ArticlesTableTest.php というファイルを作成してください。

namespace App\Test\TestCase\Model\Table;

use App\Model\Table\ArticlesTable;
use Cake\TestSuite\TestCase;

class ArticlesTableTest extends TestCase
{
    protected $fixtures = ['app.Articles'];
}

このテストケースの $fixtures 変数に、使用したいフィクスチャーを設定します。クエリーを実行するために要なフィクスチャーをすべて設定してください。

テストメソッドの作成

次に、ArticlesTable の published() メソッドに対するテストを追加してみましょう。 tests/TestCase/Model/Table/ArticlesTableTest.php ファイルを次のように編集してください。

namespace App\Test\TestCase\Model\Table;

use App\Model\Table\ArticlesTable;
use Cake\TestSuite\TestCase;

class ArticlesTableTest extends TestCase
{
    protected $fixtures = ['app.Articles'];

    public function setUp(): void
    {
        parent::setUp();
        $this->Articles = $this->getTableLocator()->get('Articles');
    }

    public function testFindPublished(): void
    {
        $query = $this->Articles->find('published')->all();
        $this->assertInstanceOf('Cake\ORM\Query', $query);
        $result = $query->enableHydration(false)->toArray();
        $expected = [
            ['id' => 1, 'title' => 'First Article'],
            ['id' => 2, 'title' => 'Second Article'],
            ['id' => 3, 'title' => 'Third Article']
        ];

        $this->assertEquals($expected, $result);
    }
}

testFindPublished() というメソッドがあります。 ArticlesTable クラスのインスタンスを作成した後、 find('published') メソッドを実行します。 $expected に、期待する適切な結果をセットします。 (article テーブルに配置されるレコードを定義します。) assertEquals() メソッドを使用して、結果が期待どおりであることをテストします。テストケースを実行する方法の詳細についてはテストの実行 セクションをご覧ください。

フィクスチャファクトリを使用する場合は、テストは次のようになります。

namespace App\Test\TestCase\Model\Table;

use App\Test\Factory\ArticleFactory;
use Cake\TestSuite\TestCase;

class ArticlesTableTest extends TestCase
{
    public function testFindPublished(): void
    {
        // Persist 3 published articles
        $articles = ArticleFactory::make(['published' => 1], 3)->persist();
        // Persist 2 unpublished articles
        ArticleFactory::make(['published' => 0], 2)->persist();

        $result = ArticleFactory::find('published')->find('list')->toArray();

        $expected = [
            $articles[0]->id => $articles[0]->title,
            $articles[1]->id => $articles[1]->title,
            $articles[2]->id => $articles[2]->title,
        ];

        $this->assertEquals($expected, $result);
    }
}

フィクスチャをロードする必要はありません。作成された5つの記事は、このテストにのみ存在します。staticメソッド ::find() は、テーブル ArticlesTable やそのイベントを使用せずにデータベースを読み込みます。

モデルメソッドのモック化

モデルメソッドのモックが必要になることがあります。 getMockForModel を使用してtableクラスのテストモックを作成できます。通常のモックが持つ反映されたプロパティーの問題を回避します。

public function testSendingEmails(): void
{
    $model = $this->getMockForModel('EmailVerification', ['send']);
    $model->expects($this->once())
        ->method('send')
        ->will($this->returnValue(true));

    $model->verifyEmail('test@example.com');
}

tearDown() メソッドの中でモックを削除してください。

TableRegistry::clear();

コントローラーの統合テスト

ヘルパー、モデル、およびコンポーネントと同様にコントローラーもテストできますが、 CakePHP では特別に IntegrationTestTrait トレイトを提供しています。コントローラーのテストケースにこのトレイトを用いると、レベルが高いテストが可能になります。

統合テストに不慣れな場合は、複数ユニットの一括テストを容易にするためのアプローチがあります。CakePHP の統合テスト機能は、アプリケーションによって処理される HTTP リクエストをシミュレートします。例えば、コントローラーをテストすると、与えられたリクエストに関するコンポーネント、モデル、そしてヘルパーを実行します。これはアプリケーションとその動作する部品の全てに、ハイレベルなテストを提供します。

典型的な ArticlesController、およびそれに対応するモデルがあるとします。コントローラーのコードは次のようになります。

namespace App\Controller;

use App\Controller\AppController;

class ArticlesController extends AppController
{
    public $helpers = ['Form', 'Html'];

    public function index($short = null)
    {
        if ($this->request->is('post')) {
            $article = $this->Articles->newEntity($this->request->getData());
            if ($this->Articles->save($article)) {
                // PRG パターンのためリダイレクト
                return $this->redirect(['action' => 'index']);
            }
        }
        if (!empty($short)) {
            $result = $this->Articles->find('all', [
                    'fields' => ['id', 'title']
                ])
                ->all();
        } else {
            $result = $this->Articles->find()->all();
        }

        $this->set([
            'title' => 'Articles',
            'articles' => $result
        ]);
    }
}

tests/TestCase/Controller ディレクトリーに ArticlesControllerTest.php という名前のファイルを作成し、内部に以下を記述してください。

namespace App\Test\TestCase\Controller;

use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;

class ArticlesControllerTest extends TestCase
{
    use IntegrationTestTrait;

    protected $fixtures = ['app.Articles'];

    public function testIndex(): void
    {
        $this->get('/articles');

        $this->assertResponseOk();
        // さらにアサート
    }

    public function testIndexQueryData(): void
    {
        $this->get('/articles?page=1');

        $this->assertResponseOk();
        // さらにアサート
    }

    public function testIndexShort(): void
    {
        $this->get('/articles/index/short');

        $this->assertResponseOk();
        $this->assertResponseContains('Articles');
        // さらにアサート
    }

    public function testIndexPostData(): void
    {
        $data = [
            'user_id' => 1,
            'published' => 1,
            'slug' => 'new-article',
            'title' => 'New Article',
            'body' => 'New Body'
        ];
        $this->post('/articles', $data);

        $this->assertResponseSuccess();
        $articles = $this->getTableLocator()->get('Articles');
        $query = $articles->find()->where(['title' => $data['title']]);
        $this->assertEquals(1, $query->count());
    }
}

この例では、いくつかのリクエストを送信するメソッドと IntegrationTestTrait が提供するいくつかのアサーションを示しています。任意のアサーションを行う前に、リクエストをディスパッチする必要があります。リクエストを送信するには、以下のいずれかのメソッドを使用することができます。

get() GET リクエストを送信します。

post() POST リクエストを送信します。

put() PUT リクエストを送信します。

delete() DELETE リクエストを送信します。

patch() PATCH リクエストを送信します。

options() OPTIONS リクエストを送信します。

head() HEAD リクエストを送信します。

get() と delete() を除く全てのメソッドは、リクエストボディーを送信することを可能にする二番目のパラメーターを受け入れます。リクエストをディスパッチした後、ユーザのリクエストに対して正しく動作したことを確実にするために IntegrationTestTrait や、PHPUnit が提供するさまざまなアサーションを使用することができます。

リクエストの設定

IntegrationTestTrait トレイトを使用すると、テスト対象のアプリケーションに送信するリクエストを設定することが容易にするために多くのヘルパーが付属しています。

// クッキーのセット
$this->cookie('name', 'Uncle Bob');

// セッションデータのセット
$this->session(['Auth.User.id' => 1]);

// ヘッダーの設定
$this->configRequest([
    'headers' => ['Accept' => 'application/json']
]);

これらのヘルパーメソッドによって設定された状態は、 tearDown() メソッドでリセットされます。

認証が必要なアクションのテスト

もし AuthComponent を使用している場合、AuthComponent がユーザーの ID を検証するために使用するセッションデータをスタブ化する必要があります。これを行うには、 IntegrationTestTrait のヘルパーメソッドを使用します。 ArticlesController が add メソッドを含み、その add メソッドに必要な認証を行っていたと仮定すると、次のテストを書くことができます。

public function testAddUnauthenticatedFails(): void
{
    // セッションデータの未設定
    $this->get('/articles/add');

    $this->assertRedirect(['controller' => 'Users', 'action' => 'login']);
}

public function testAddAuthenticated(): void
{
    // セッションデータのセット
    $this->session([
        'Auth' => [
            'User' => [
                'id' => 1,
                'username' => 'testing',
                // 他のキー
            ]
        ]
    ]);
    $this->get('/articles/add');

    $this->assertResponseOk();
    // その他のアサーション
}

ステートレス認証と API のテスト

Basic 認証のようなステートレス認証を使用する API をテストするために、実際の認証のリクエストヘッダーをシミュレートする環境変数やヘッダーを注入するためにリクエストを設定できます。

Basic または Digest 認証をテストする際、自動的に PHP が作成する [https://php.net/manual/ja/features.http-auth.php] 環境変数を追加できます。これらの環境変数は、 Basic 認証の使用 に概説されている認証アダプター内で使用されます。

public function testBasicAuthentication(): void
{
    $this->configRequest([
        'environment' => [
            'PHP_AUTH_USER' => 'username',
            'PHP_AUTH_PW' => 'password',
        ]
    ]);

    $this->get('/api/posts');
    $this->assertResponseOk();
}

OAuth2 のようなその他の認証方法をテストしている場合、Authorization ヘッダーを直接セットできます。

public function testOauthToken(): void
{
    $this->configRequest([
        'headers' => [
            'authorization' => 'Bearer: oauth-token'
        ]
    ]);

    $this->get('/api/posts');
    $this->assertResponseOk();
}

configRequest() 内の headers キーは、アクションに必要な追加の HTTP ヘッダーを設定するために使用されます。

CsrfComponent や SecurityComponent で保護されたアクションのテスト

SecurityComponent または CsrfComponent のいずれかで保護されたアクションをテストする場合、テストがトークンのミスマッチで失敗しないように自動トークン生成を有効にすることができます。

public function testAdd(): void
{
    $this->enableCsrfToken();
    $this->enableSecurityToken();
    $this->post('/posts/add', ['title' => 'Exciting news!']);
}

また、トークンを使用するテストで debug を有効にすることは重要です。SecurityComponent が「デバッグ用トークンがデバッグ以外の環境で使われている」と考えてしまうのを防ぐためです。 requireSecure() のような他のメソッドでテストした時は、適切な環境変数をセットするために configRequest() を利用できます。:

// SSL 接続を装います。
$this->configRequest([
    'environment' => ['HTTPS' => 'on']
]);

アクションでアンロックされたフィールドが必要な場合は、 setUnlockedFields() で宣言することができます。

$this->setUnlockedFields(['dynamic_field']);

PSR-7 ミドルウェアの統合テスト

統合テストは、PSR-7 アプリケーション全体や ミドルウェア をテストするために利用されます。デフォルトで IntegrationTestTrait は、 App\Application クラスの存在を自動検知し、アプリケーションの統合テストを自動的に有効にします。

configApplication() メソッドを使うことによって、使用するアプリケーションクラス名とコンストラクターの引数をカスタマイズすることができます。

public function setUp(): void
{
    $this->configApplication('App\App', [CONFIG]);
}

イベントやルートを含むプラグインを読み込むために Application::bootstrap() を試してみてください。そうすることで、各テストケースごとにイベントやルートが接続されます。テスト中に手動でプラグインをロードしたい場合は loadPlugins() メソッドを使うことができます。

暗号化されたクッキーを使用したテスト

アプリケーションで クッキー暗号化ミドルウェア を使用している場合、テストケースで暗号化クッキーを設定するためのヘルパーメソッドがあります。

// AES とデフォルトキーを使ってクッキーをセット
$this->cookieEncrypted('my_cookie', '何か秘密の値');

// このアクションは、クッキーを変更するものとします。
$this->get('/bookmarks/index');

$this->assertCookieEncrypted('更新された値', 'my_cookie');

フラッシュメッセージのテスト

描画された HTML ではなく、セッション内にフラッシュメッセージが存在することをアサートする場合、テスト内で enableRetainFlashMessages() を使ってセッション内のフラッシュメッセージを保持し、アサーションを書くことができます。

// Enable retention of flash messages instead of consuming them.
$this->enableRetainFlashMessages();
$this->get('/bookmarks/delete/9999');

$this->assertSession('ブックマークは存在しません', 'Flash.flash.0.message');

// 'flash' キー内のフラッシュメッセージをアサート
$this->assertFlashMessage('Bookmark deleted', 'flash');

// ２つ目のフラッシュメッセージをアサート
$this->assertFlashMessageAt(1, 'Bookmark really deleted');

// 最初の位置の 'auth' キーにフラッシュメッセージをアサート
$this->assertFlashMessageAt(0, 'You are not allowed to enter this dungeon!', 'auth');

// フラッシュメッセージがエラーエレメントを使用していることをアサート
$this->assertFlashElement('Flash/error');

// ２つ目のフラッシュメッセージのエレメントをアサート
$this->assertFlashElementAt(1, 'Flash/error');

JSON を返すコントローラーのテスト

JSON は、ウェブサービスの構築において、とても馴染み深く、かつ基本的なフォーマットです。 CakePHP を用いたウェブサービスのエンドポイントのテストはとてもシンプルです。 JSON を返すコントローラーの簡単な例を示します。

class MarkersController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('RequestHandler');
    }

    public function view($id)
    {
        $marker = $this->Markers->get($id);
        $this->set('marker', $marker);
        $this->viewBuilder()->setOption('serialize', ['marker']);
    }
}

今、 tests/TestCase/Controller/MarkersControllerTest.php ファイルを作成し、ウェブサービスが適切な応答を返していることを確認してください。

class MarkersControllerTest extends IntegrationTestCase
{
    public function testGet(): void
    {
        $this->configRequest([
            'headers' => ['Accept' => 'application/json']
        ]);
        $result = $this->get('/markers/view/1.json');

        // レスポンスが 200 であることを確認
        $this->assertResponseOk();

        $expected = [
            ['id' => 1, 'lng' => 66, 'lat' => 45],
        ];
        $expected = json_encode($expected, JSON_PRETTY_PRINT);
        $this->assertEquals($expected, (string)$this->_response->getBody());
    }
}

CakePHP の組込み JsonView で、 debug が有効になっている場合、 JSON_PRETTY_PRINT オプションを使用します。

ファイルアップロードのテスト

デフォルトの「オブジェクトとしてアップロードされたファイル」モードを使用すると、ファイルのアップロードのシミュレーションは簡単です。 \Psr\Http\Message\UploadedFileInterface [https://www.php-fig.org/psr/psr-7/#16-uploaded-files] (現在CakePHPで使用されているデフォルトの実装は \Laminas\Diactoros\UploadedFile )を実装するインスタンスを作成し、それらをテストリクエストデータに渡すだけ。 CLI環境では、このようなオブジェクトはデフォルトで、ファイルが HTTP経由でアップロードされたかどうかをテストするバリデーションに合格します。 $_FILES にある配列スタイルのデータには同じことが当てはまらず、バリデーションは失敗します。

アップロードされたファイルオブジェクトが通常のリクエストでどのように存在するかを正確にシミュレートするには、リクエストデータでそれらを渡すだけでなく、 files オプションを介してテストリクエスト構成に渡す必要があります。ただし、コードが Cake\Http\ServerRequest::getUploadedFile() または Cake\Http\ServerRequest::getUploadedFiles() メソッドを介してアップロードされたファイルにアクセスしない限り、技術的には必要ありません。

記事にティザー画像と 複数の添付ファイル の関連付けがあるとして、フォームはそれに応じて、1つの画像ファイルと複数の添付ファイル/ファイルとして受け入れます。:

<?= $this->Form->create($article, ['type' => 'file']) ?>
<?= $this->Form->control('title') ?>
<?= $this->Form->control('teaser_image', ['type' => 'file']) ?>
<?= $this->Form->control('attachments.0.attachment', ['type' => 'file']) ?>
<?= $this->Form->control('attachments.0.description']) ?>
<?= $this->Form->control('attachments.1.attachment', ['type' => 'file']) ?>
<?= $this->Form->control('attachments.1.description']) ?>
<?= $this->Form->button('Submit') ?>
<?= $this->Form->end() ?>

対応するリクエストをシミュレートするテストは、次のようになります。:

public function testAddWithUploads(): void
{
    $teaserImage = new \Laminas\Diactoros\UploadedFile(
        '/path/to/test/file.jpg', // stream or path to file representing the temp file
        12345,                    // the filesize in bytes
        \UPLOAD_ERR_OK,           // the upload/error status
        'teaser.jpg',             // the filename as sent by the client
        'image/jpeg'              // the mimetype as sent by the client
    );

    $textAttachment = new \Laminas\Diactoros\UploadedFile(
        '/path/to/test/file.txt',
        12345,
        \UPLOAD_ERR_OK,
        'attachment.txt',
        'text/plain'
    );

    $pdfAttachment = new \Laminas\Diactoros\UploadedFile(
        '/path/to/test/file.pdf',
        12345,
        \UPLOAD_ERR_OK,
        'attachment.pdf',
        'application/pdf'
    );

    // This is the data accessible via `$this->request->getUploadedFile()`
    // and `$this->request->getUploadedFiles()`.
    $this->configRequest([
        'files' => [
            'teaser_image' => $teaserImage,
            'attachments' => [
                0 => [
                    'attachment' => $textAttachment,
                ],
                1 => [
                    'attachment' => $pdfAttachment,
                ],
            ],
        ],
    ]);

    // This is the data accessible via `$this->request->getData()`.
    $postData = [
        'title' => 'New Article',
        'teaser_image' => $teaserImage,
        'attachments' => [
            0 => [
                'attachment' => $textAttachment,
                'description' => 'Text attachment',
            ],
            1 => [
                'attachment' => $pdfAttachment,
                'description' => 'PDF attachment',
            ],
        ],
    ];
    $this->post('/articles/add', $postData);

    $this->assertResponseOk();
    $this->assertFlashMessage('The article was saved successfully');
    $this->assertFileExists('/path/to/uploads/teaser.jpg');
    $this->assertFileExists('/path/to/uploads/attachment.txt');
    $this->assertFileExists('/path/to/uploads/attachment.pdf');
}

Tip

ファイルを使用してテストリクエストを構成する場合は、POSTデータの構造と 必ず 一致する必要があります（ただし、アップロードされたファイルオブジェクトのみが含まれます）。

同様に、 アップロードエラー [https://www.php.net/manual/en/features.file-upload.errors.php] や、検証に合格しない無効なファイルをシミュレートできます。:

public function testAddWithInvalidUploads(): void
{
    $missingTeaserImageUpload = new \Laminas\Diactoros\UploadedFile(
        '',
        0,
        \UPLOAD_ERR_NO_FILE,
        '',
        ''
    );

    $uploadFailureAttachment = new \Laminas\Diactoros\UploadedFile(
        '/path/to/test/file.txt',
        1234567890,
        \UPLOAD_ERR_INI_SIZE,
        'attachment.txt',
        'text/plain'
    );

    $invalidTypeAttachment = new \Laminas\Diactoros\UploadedFile(
        '/path/to/test/file.exe',
        12345,
        \UPLOAD_ERR_OK,
        'attachment.exe',
        'application/vnd.microsoft.portable-executable'
    );

    $this->configRequest([
        'files' => [
            'teaser_image' => $missingTeaserImageUpload,
            'attachments' => [
                0 => [
                    'file' => $uploadFailureAttachment,
                ],
                1 => [
                    'file' => $invalidTypeAttachment,
                ],
            ],
        ],
    ]);

    $postData = [
        'title' => 'New Article',
        'teaser_image' => $missingTeaserImageUpload,
        'attachments' => [
            0 => [
                'file' => $uploadFailureAttachment,
                'description' => 'Upload failure attachment',
            ],
            1 => [
                'file' => $invalidTypeAttachment,
                'description' => 'Invalid type attachment',
            ],
        ],
    ];
    $this->post('/articles/add', $postData);

    $this->assertResponseOk();
    $this->assertFlashMessage('The article could not be saved');
    $this->assertResponseContains('A teaser image is required');
    $this->assertResponseContains('Max allowed filesize exceeded');
    $this->assertResponseContains('Unsupported file type');
    $this->assertFileNotExists('/path/to/uploads/teaser.jpg');
    $this->assertFileNotExists('/path/to/uploads/attachment.txt');
    $this->assertFileNotExists('/path/to/uploads/attachment.exe');
}

テスト中のエラー処理ミドルウェアの無効化

アプリケーションにエラーが発生したために失敗したテストをデバッグする場合、エラー処理ミドルウェアを一時的に無効にして、根本的なエラーを目立たせることができます。これをするために disableErrorHandlerMiddleware() が使用できます。

public function testGetMissing(): void
{
    $this->disableErrorHandlerMiddleware();
    $this->get('/markers/not-there');
    $this->assertResponseCode(404);
}

上の例では、テストは失敗し、描画されたエラーページがチェックされる代わりに、基本的な例外メッセージとスタックトレースが表示されます。

アサーションメソッド

IntegrationTestTrait トレイトはレスポンスのテストがとても簡単になるアサーションメソッドを多数提供しています。いくつかの例をあげます。

// 2xx レスポンスコードをチェック
$this->assertResponseOk();

// 2xx/3xx レスポンスコードをチェック
$this->assertResponseSuccess();

// 4xx レスポンスコードをチェック
$this->assertResponseError();

// 5xx レスポンスコードをチェック
$this->assertResponseFailure();

// 指定したレスポンスコードをチェック。例: 200
$this->assertResponseCode(200);

// Location ヘッダーをチェック
$this->assertRedirect(['controller' => 'Articles', 'action' => 'index']);

// Location ヘッダーが設定されていないことをチェック
$this->assertNoRedirect();

// Location ヘッダーの一部をチェック
$this->assertRedirectContains('/articles/edit/');

// Location ヘッダーが含まれていないことをチェック
$this->assertRedirectNotContains('/articles/edit/');

// レスポンスが空ではないことをアサート
$this->assertResponseNotEmpty();

// レスポンス内容が空であることをアサート
$this->assertResponseEmpty();

// レスポンス内容をアサート
$this->assertResponseEquals('Yeah!');

// レスポンス内容が等しくないことをアサート
$this->assertResponseNotEquals('No!');

// レスポンス内容の一部をアサート
$this->assertResponseContains('You won!');
$this->assertResponseNotContains('You lost!');

// 返されたファイルをアサート
$this->assertFileResponse('/absolute/path/to/file.ext');

// レイアウトをアサート
$this->assertLayout('default');

// テンプレートが表示されたかどうかをアサート
$this->assertTemplate('index');

// セッション内のデータをアサート
$this->assertSession(1, 'Auth.User.id');

// レスポンスヘッダーをアサート
$this->assertHeader('Content-Type', 'application/json');
$this->assertHeaderContains('Content-Type', 'html');

// content-typeのヘッダーにxmlが含まれていないことをアサート
$this->assertHeaderNotContains('Content-Type', 'xml');

// ビュー変数をアサート
$user =  $this->viewVariable('user');
$this->assertEquals('jose', $user->username);

// レスポンス内のクッキーをアサート
$this->assertCookie('1', 'thingid');

// コンテンツタイプをチェック
$this->assertContentType('application/json');

上記のアサーションメソッドに加えて、 TestSuite [https://api.cakephp.org/4.x/class-Cake.TestSuite.TestCase.html] と PHPUnit [https://phpunit.de/manual/current/ja/appendixes.assertions.html] の中にある全てのアサーションを使用することができます。

ファイルへのテスト結果を比較

例えば、ビューのレンダリングされた出力をテストする場合 - いくつかのタイプのテストにとっては、ファイルの内容とテストの結果を比較する方が簡単かもしれません。 StringCompareTrait は、この目的のために簡単なアサートメソッドを追加します。

使用方法は、トレイトを用いて比較元のパスを設定し、 assertSameAsFile を呼び出すことです。

use Cake\TestSuite\StringCompareTrait;
use Cake\TestSuite\TestCase;

class SomeTest extends TestCase
{
    use StringCompareTrait;

    public function setUp(): void
    {
        $this->_compareBasePath = APP . 'tests' . DS . 'comparisons' . DS;
        parent::setUp();
    }

    public function testExample(): void
    {
        $result = ...;
        $this->assertSameAsFile('example.php', $result);
    }
}

上記の例では、 APP/tests/comparisons/example.php ファイルの内容と $result を比較します。

それらが参照されているように、テストの比較ファイルが作成・更新され、環境変数 UPDATE_TEST_COMPARISON_FILES を設定することで、テストファイルを更新/書き込みするために仕組みが提供されています。

phpunit
...
FAILURES!
Tests: 6, Assertions: 7, Failures: 1

UPDATE_TEST_COMPARISON_FILES=1 phpunit
...
OK (6 tests, 7 assertions)

git status
...
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   tests/comparisons/example.php

コンソールの統合テスト

シェルとコマンドをテストについては コマンドのテスト をご覧ください。

Mocking Injected Dependencies

See サービスをモック化してテストする for how to replace services injected with the dependency injection container in your integration tests.

ビューのテスト

一般的に、ほとんどのアプリケーションは、直接 HTML コードをテストしません。そのため、多くの場合、テストは壊れやすく、メンテナンスが困難になっています。 IntegrationTestTrait を使用して機能テストを書くときに ‘view’ に return オプションを設定することで、レンダリングされたビューの内容を調べることができます。 IntegrationTestTrait を使用してビューのコンテンツをテストすることは可能ですが、より堅牢でメンテナンスしやすい統合/ビューテストは、 Selenium webdriver [https://www.selenium.dev/] のようなツールを使うことで実現できます

コンポーネントのテスト

PagematronComponent というコンポーネントがアプリケーションにあったとしましょう。このコンポーネントは、このコンポーネントを使用している全てのコントローラーにおいて、ページネーションの limit 値を設定することができます。 src/Controller/Component/PagematronComponent.php に置かれたコンポーネントの例はこちらです。

class PagematronComponent extends Component
{
    public $controller = null;

    public function setController($controller)
    {
        $this->controller = $controller;
        // コントローラーが、ページネーションを使用していることを確認
        if (!isset($this->controller->paginate)) {
            $this->controller->paginate = [];
        }
    }

    public function startup(EventInterface $event)
    {
        $this->setController($event->getSubject());
    }

    public function adjust($length = 'short'): void
    {
        switch ($length) {
            case 'long':
                $this->controller->paginate['limit'] = 100;
            break;
            case 'medium':
                $this->controller->paginate['limit'] = 50;
            break;
            default:
                $this->controller->paginate['limit'] = 20;
            break;
        }
    }
}

コンポーネントの中の adjust() メソッドによって、ページネーションの limit パラメーターが正しく設定されていることを保証するためのテストを書くことができます。 tests/TestCase/Controller/Component/PagematronComponentTest.php ファイルを作成します。

namespace App\Test\TestCase\Controller\Component;

use App\Controller\Component\PagematronComponent;
use Cake\Controller\Controller;
use Cake\Controller\ComponentRegistry;
use Cake\Event\Event;
use Cake\Http\ServerRequest;
use Cake\Http\Response;
use Cake\TestSuite\TestCase;

class PagematronComponentTest extends TestCase
{
    protected $component;
    protected $controller;

    public function setUp(): void
    {
        parent::setUp();
        // コンポーネントと偽のテストコントローラーのセットアップ
        $request = new ServerRequest();
        $response = new Response();
        $this->controller = $this->getMockBuilder('Cake\Controller\Controller')
            ->setConstructorArgs([$request, $response])
            ->setMethods(null)
            ->getMock();
        $registry = new ComponentRegistry($this->controller);
        $this->component = new PagematronComponent($registry);
        $event = new Event('Controller.startup', $this->controller);
        $this->component->startup($event);
    }

    public function testAdjust(): void
    {
        // 異なるパラメーター設定で、adjust メソッドをテスト
        $this->component->adjust();
        $this->assertEquals(20, $this->controller->paginate['limit']);

        $this->component->adjust('medium');
        $this->assertEquals(50, $this->controller->paginate['limit']);

        $this->component->adjust('long');
        $this->assertEquals(100, $this->controller->paginate['limit']);
    }

    public function tearDown(): void
    {
        parent::tearDown();
        // 完了後のクリーンアップ
        unset($this->component, $this->controller);
    }
}

ヘルパーのテスト

相当な量のロジックがヘルパークラスに存在するので、これらのクラスがテストケースによってカバーされていることを確認することは重要です。

はじめに、テストのための例として、ヘルパーを作成します。 CurrencyRendererHelper には、ビューで通貨の表示を補助するための、 yen() というシンプルなメソッドを記述します。

// src/View/Helper/CurrencyRendererHelper.php
namespace App\View\Helper;

use Cake\View\Helper;

class CurrencyRendererHelper extends Helper
{
    public function yen($amount): string
    {
        return number_format($amount, 2, '.', ',') . '円';
    }
}

このメソッドは、小数点以下2桁まで表示し、小数点としてドット、3桁ごとの区切りとしてカンマを使用するフォーマットで数字を表し、さらに ’円’ という文字を数字の末尾に追加します。

それではテストを作成します。

// tests/TestCase/View/Helper/CurrencyRendererHelperTest.php

namespace App\Test\TestCase\View\Helper;

use App\View\Helper\CurrencyRendererHelper;
use Cake\TestSuite\TestCase;
use Cake\View\View;

class CurrencyRendererHelperTest extends TestCase
{
    public $helper = null;

    // ここでヘルパーをインスタンス化
    public function setUp(): void
    {
        parent::setUp();
        $View = new View();
        $this->helper = new CurrencyRendererHelper($View);
    }

    // yen() 関数をテスト
    public function testYen(): void
    {
        $this->assertEquals('5.30円', $this->helper->yen(5.30));

        // 常に小数第２位まで持つべき
        $this->assertEquals('1.00円', $this->helper->yen(1));
        $this->assertEquals('2.05円', $this->helper->yen(2.05));

        // 桁区切りのテスト
        $this->assertEquals(
          '12,000.70円',
          $this->helper->yen(12000.70)
        );
    }
}

yen() を異なるパラメーターで呼び出すことで、このテストスイートは 期待した値と同じ値を返しているかどうかをアサートすることができます。

ファイルに保存しテストを実行します。これにより、緑のバー(※Windowsは非対応)と 1つのテスト、4つのアサーションに成功したことを指し示すメッセージを見ることができるでしょう。

他のヘルパーを使用するヘルパーをテストしている時、View クラスの loadHelpers メソッドをモックにしてください。

イベントのテスト

イベントシステム は、アプリケーションコードを分離する素晴らしい方法ですが、テストの際、これらのイベントを実行するテストケース内のイベントの結果をテストすることになりがちです。これは、 assertEventFired や assertEventFiredWith を代わりに使うことで削除ができる、余分な結合の一種です。

Orders を例に詳しく説明します。以下のテーブルを持っているとします。

class OrdersTable extends Table
{
    public function place($order): bool
    {
        if ($this->save($order)) {
            // CartsTable へ移されたカートの移動
            $event = new Event('Model.Order.afterPlace', $this, [
                'order' => $order
            ]);
            $this->getEventManager()->dispatch($event);
            return true;
        }
        return false;
    }
}

class CartsTable extends Table
{
    public function implementedEvents(): array
    {
        return [
            'Model.Order.afterPlace' => 'removeFromCart'
        ];
    }

    public function removeFromCart(EventInterface $event): void
    {
        $order = $event->getData('order');
        $this->delete($order->cart_id);
    }
}

注釈

イベントの発生をアサートするために、イベントマネージャー上で最初に イベントの追跡を有効にする必要があります。

上記の OrdersTable をテストするために、 setUp() 内でトラッキングを有効にした後、イベントが発生することをアサートし、そして $order エンティティーがイベントデータに渡されることをアサートします。

namespace App\Test\TestCase\Model\Table;

use App\Model\Table\OrdersTable;
use Cake\Event\EventList;
use Cake\TestSuite\TestCase;

class OrdersTableTest extends TestCase
{
    protected $fixtures = ['app.Orders'];

    public function setUp(): void
    {
        parent::setUp();
        $this->Orders = $this->getTableLocator()->get('Orders');
        // イベントトラッキングの有効化
        $this->Orders->getEventManager()->setEventList(new EventList());
    }

    public function testPlace(): void
    {
        $order = new Order([
            'user_id' => 1,
            'item' => 'Cake',
            'quantity' => 42,
        ]);

        $this->assertTrue($this->Orders->place($order));

        $this->assertEventFired('Model.Order.afterPlace', $this->Orders->getEventManager());
        $this->assertEventFiredWith('Model.Order.afterPlace', 'order', $order, $this->Orders->getEventManager());
    }
}

デフォルトでは、アサーションのためにグローバルな EventManager が利用されるため、グローバルイベントのテストは、イベントマネージャーに渡す必要はありません。

$this->assertEventFired('My.Global.Event');
$this->assertEventFiredWith('My.Global.Event', 'user', 1);

メールのテスト

メールのテストについては メールのテスト をご覧ください。

テストスイートの作成

いくつかのテストを同時に実行したいときはテストスイートを作成することができます。テストスイートは、いくつかの テストケースから構成されています。アプリケーションの phpunit.xml ファイルにテストスイートを作成することによって実行することができます。簡単な例は次のようになります。

<testsuites>
  <testsuite name="Models">
    <directory>src/Model</directory>
    <file>src/Service/UserServiceTest.php</file>
    <exclude>src/Model/Cloud/ImagesTest.php</exclude>
  </testsuite>
</testsuites>

プラグインのテスト作成

プラグインのテストは、プラグインフォルダー内のディレクトリーに作成されます。

/src
/plugins
    /Blog
        /tests
            /TestCase
            /Fixture

それらは通常のテストと同じように動作しますが、別のクラスをインポートする場合、プラグインの命名規則を使用することを覚えておく必要があります。これは、このマニュアルのプラグインの章から BlogPost モデルのテストケースの一例です。他のテストとの違いは、 'Blog.BlogPost' がインポートされている最初の行です。プラグインフィクスチャーに plugin.Blog.BlogPosts とプレフィックスをつける必要があります。

namespace Blog\Test\TestCase\Model\Table;

use Blog\Model\Table\BlogPostsTable;
use Cake\TestSuite\TestCase;

class BlogPostsTableTest extends TestCase
{
    // /plugins/Blog/tests/Fixture/ 内のプラグインのフィクスチャーをロード
    protected $fixtures = ['plugin.Blog.BlogPosts'];

    public function testSomething(): void
    {
        // 何らかのテスト
    }
}

アプリのテストにおいてプラグインのフィクスチャーを使用したい場合は、 $fixtures 配列に plugin.pluginName.fixtureName 構文を使用して参照することができます。さらに、ベンダーのプラグイン名またはフィクスチャーのディレクトリーを使用する場合は、 plugin.vendorName/pluginName.folderName/fixtureName を使用できます:

フィクスチャーを使用する前に、 phpunit.xml に fixture listener が設定されていることを確認してください。

また、フィクスチャーがロード可能であることを確認する必要があります。次のように composer.json ファイル内に存在することを確認してください。

"autoload-dev": {
    "psr-4": {
        "MyPlugin\\Test\\": "plugins/MyPlugin/tests/"
    }
}

注釈

新しいオートロードのマッピングを追加するときに composer.phar dumpautoload を実行することを忘れないでください。

Bake でのテストの生成

スキャフォールディングを生成するために bake を使う場合、テストのスタブも生成します。テストケースのスケルトンを再生成する必要がある場合、または、書いたコードのテストスケルトンを生成する場合、 bake を使用することができます。

bin/cake bake test <type> <name>

<type> は以下のいずれかである必要があります。

Entity

Table

Controller

Component

Behavior

Helper

Shell

Task

ShellHelper

Cell

Form

Mailer

Command

<name> は作成したいテストの雛形のオブジェクトの名前です。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

バリデーション

CakePHP のバリデーションは、任意の配列データに対するバリデーションを簡単に行うためのバリデーター構築のパッケージを提供します。 API 中の利用可能なバリデーションルールの一覧 [https://api.cakephp.org/4.x/class-Cake.Validation.Validation.html] をご覧ください。

バリデーターを作成する

class Cake\Validation\Validator

バリデーターオブジェクトは１セットのフィールドに適用されるルールを定義します。バリデーターオブジェクトはフィールドとバリデーションセットとの間のマッピングを含みます。その引き換えに、バリデーションセットは、関係するフィールドに適用されるルールの集合体を提供します。バリデーターを作成するのは簡単です。

use Cake\Validation\Validator;

$validator = new Validator();

一度作成した後、バリデーションを適用したいフィールドに対して、実際にルールを設定していきます。

$validator
    ->requirePresence('title')
    ->notEmptyString('title', 'このフィールドに入力してください')
    ->add('title', [
        'length' => [
            'rule' => ['minLength', 10],
            'message' => 'タイトルは 10 文字以上必要です',
        ]
    ])
    ->allowEmptyDateTime('published')
    ->add('published', 'boolean', [
        'rule' => 'boolean'
    ])
    ->requirePresence('body')
    ->add('body', 'length', [
        'rule' => ['minLength', 50],
        'message' => '記事は中身のある本文を持っていなければなりません。',
    ]);

上記例に見られるように、バリデーションは、実際にバリデーションを実行したいフィールドに対してルールを適用するような形で、流暢なインターフェイスとともに構築されて行きます。

上記例には、いくつかのメソッドが呼ばれていたので、様々な特徴について見て行きましょう。 add() メソッドを用いることにより、バリデーターに対して新しいルールを加えることができます。上記のように個別にルールを加えることもできますし、グループ単位でルールを加えることもできます。

フィールドが実在することを求める

requirePresence() メソッドは、バリデーションの対象となる配列について、フィールドが実在することを求めます。もし、フィールドが存在していなければ、バリデーションは失敗します。 requirePresence() には4つのモードがあります:

true この場合、フィールドが存在することが常に求められます。

false この場合、フィールドが存在することは必要なくなります。

create create 実行時にバリデーションを行う場合、このフィールドが存在することが求められます。

update update 実行時にバリデーションを行う場合、このフィールドが存在することが求められます。

デフォルトでは、 true が用いられます。キーの存在は、 array_key_exists() を用いることによりチェックされるため、 null 値は、「存在する」としてカウントされます。モードについては、２番目のパラメーターを用いることにより設定できます。

$validator->requirePresence('author_id', 'create');

もし要求するフィールドが複数ある場合、それらをリストとして定義することができます。

// create として複数フィールドを定義
$validator->requirePresence(['author_id', 'title'], 'create');

// 混在したモードで複数フィールドを定義
$validator->requirePresence([
    'author_id' => [
        'mode' => 'create',
        'message' => '著者は必須です。',
    ],
    'published' => [
        'mode' => 'update',
        'message' => '公開された状態が必要です。',
    ]
]);

空のフィールドを認める

バリデーターは、フィールドが空の値を許容するかどうかを制御するためのいくつかのメソッドを提供します。そして、許容された空の値は他の名前付きフィールドのバリデーションルールには転送されません。 CakePHPは6つの異なる形状のデータに対して空の値のサポートを提供します。

allowEmptyString() 空文字列を許容する場合にのみ使用します

allowEmptyArray() 配列を許容する場合に使用します

allowEmptyDate() 空文字列か、日付フィールドに取り込まれる配列を許容する場合に使用します

allowEmptyTime() 空文字列か、時刻フィールドに取り込まれる配列を許容する場合に使用します

allowEmptyDateTime() 空文字列か、日付時刻フィールドまたはタイムスタンプフィールドに取り込まれる配列を許容する場合に使用します

allowEmptyFile() 空ファイルのアップロードが含まれた配列を許容する場合に使用します

notEmpty() を使用して空のフィールドを無効にすることもできますが、推奨されるのは notEmpty() は使用せず、より具体的なバリデーターである notEmptyString(), notEmptyArray(), notEmptyFile(), notEmptyDate(), notEmptyTime(), notEmptyDateTime() を使用することです。

allowEmpty* メソッドは、空のフィールドを許容するかを制御する when パラメーターをサポートします。

false フィールドは空にできません

create create 操作のバリデーション時にフィールドを空にできます

update update 操作のバリデーション時にフィールドを空にできます

フィールドが空にできるかどうかを示す true または false を返すコールバック。このパラメーターの使用方法の例については、：ref：conditional-validation セクションを参照してください。

これらのメソッドの使用例は次のとおりです。:

$validator->allowEmptyDateTime('published')
    ->allowEmptyString('title', 'タイトルは空にできません', false)
    ->allowEmptyString('body', '本文は空にできません', 'update')
    ->allowEmptyFile('header_image', 'update');
    ->allowEmptyDateTime('posted', 'update');

バリデーションルールの追加

Validator クラスはバリデーターの構築をシンプルかつ表現力豊かにするメソッドを提供します。例えば、バリデーションルールを username フィールドに追加するには以下のようになります。

$validator = new Validator();
$validator
    ->email('username')
    ->ascii('username')
    ->lengthBetween('username', [4, 8]);

バリデータメソッドの完全なセットについては、 Validator API ドキュメント [https://api.cakephp.org/4.x/class-Cake.Validation.Validator.html] をご覧ください。

カスタムバリデーションルールの使用

Validator やプロバイダーから与えられるメソッドを使うことに加え、匿名関数を含むコールバック関数も、バリデーションルールとして用いることができます。

// グローバル関数を利用する
$validator->add('title', 'custom', [
    'rule' => 'validate_title',
    'message' => 'タイトルが正しくありません'
]);

// プロバイダーではないコールバック関数を利用する
$validator->add('title', 'custom', [
    'rule' => [$this, 'method'],
    'message' => 'タイトルが正しくありません'
]);

// クロージャーを利用する
$extra = 'クロージャー内に必要な追加値';
$validator->add('title', 'custom', [
    'rule' => function ($value, $context) use ($extra) {
        // true/falseを返すカスタムロジックを記入
    },
    'message' => 'タイトルが正しくありません'
]);

// カスタムプロバイダーからのルールを利用する
$validator->add('title', 'custom', [
    'rule' => 'customRule',
    'provider' => 'custom',
    'message' => 'タイトルが十分にユニークではありません'
]);

クロージャーやコールバックメソッドは、呼び出された際に2つの設定を受けることとなります。最初は、バリデーションが行われるフィールド値であり、２番目はバリデーションプロセスに関連するデータを含む配列です。

data: バリデーションメソッドに与えられた元々のデータのことです。値を比較するようなルールを作る場合には、利用価値が高いといえます。

providers: プロバイダーオブジェクトについての完成されたリストのことです。複数のプロバイダーを呼び出すことにより複雑なルールを作りたいときに、利用価値が高いといえます。

newRecord:　バリデーションコールが新しいレコードのためのものか、すでにあるレコードのためのものかを示します。

既存ユーザーの ID のようにあなたのバリデーションメソッドに追加のデータを渡す必要がある場合、あなたのコントローラーからカスタム動的プロバイダー利用できます。

$this->Examples->validator('default')->provider('passed', [
    'count' => $countFromController,
    'userid' => $this->Auth->user('id')
]);

そのとき、あなたのバリデーションメソッドが、第２コンテキストパラメーターを持つことを保証します。

public function customValidationMethod($check, array $context)
{
    $userid = $context['providers']['passed']['userid'];
}

もし、バリデーションに合格した場合、クロージャーはブーリアン型の true を返さなければなりません。もし、失敗した場合、ブーリアン型の false またはカスタムエラーメッセージとして文字列を返してください。詳しくは 条件付き/動的なエラーメッセージをご覧ください。

条件付き/動的なエラーメッセージ

バリデーションルールのメソッドは、 カスタムコールバックまたは プロバイダーによって提供されるメソッド であり、検証が成功したかどうかを示すブーリアン型を返すか、検証が失敗したことを意味する文字列を返すことができ、返された文字列はエラーメッセージとして使用されます。

message オプションで定義された既存のエラーメッセージは、バリデーションルールメソッドから返されたエラーメッセージによって上書きされます。

$validator->add('length', 'custom', [
    'rule' => function ($value, $context) {
        if (!$value) {
            return false;
        }

        if ($value < 10) {
            return '値が 10 より小さい場合のエラーメッセージ';
        }

        if ($value > 20) {
            return '値が 20 より大きい場合のエラーメッセージ';
        }

        return true;
    },
    'message' => '`false` が返されたときに使われる一般的なエラーメッセージ'
]);

条件付バリデーション

バリデーションルールを定義する際、 on キーを用いることで、バリデーションルールが適用されるべきか否かを定義することができます。未定義のままにすると、ルールは常に適用されます。他に有効な値は、 create 及び update です。これらの値を利用することにより、 create や update 実行時にのみ、ルールが適用されることとなります。

加えて、特定なルールが適用されるべきか決めるためのコールバック関数を活用することもできます。

$validator->add('picture', 'file', [
    'rule' => ['mimeType', ['image/jpeg', 'image/png']],
    'on' => function ($context) {
        return !empty($context['data']['show_profile_picture']);
    }
]);

$context['data'] 配列を用いることで、他の送信されたフィールドにアクセスすることができます。上記例では、 show_profile_picture の値が空かどうかで 'picture' のルールを任意なものとします。また、 uploadedFile を用いることで、任意のファイルアップロードに関する入力を設定することができます。

$validator->add('picture', 'file', [
    'rule' => ['uploadedFile', ['optional' => true]],
]);

allowEmpty*, notEmpty() 及び requirePresence() メソッドは、最後に引数としてコールバック関数を受け付けることができます。もしこれがあれば、ルールが適用されるべきか否かをコールバック関数が決めます。例えば、以下のように、フィールド値が空のままでも許容される時もあります。

$validator->allowEmptyString('tax', function ($context) {
    return !$context['data']['is_taxable'];
});

一方で、以下のように、一定の条件が満たされた場合にのみ、フィールド値が求められる（空欄が許容されない）場合もあります。

$validator->notEmpty('email_frequency', 'このフィールドは必須です', function ($context) {
    return !empty($context['data']['wants_newsletter']);
});

上記の例は、ユーザーがニュースレターを受領したい場合には、 email_frequency フィールドが空欄のまま残されてはいけない、という例です。

さらに、一定の条件の下でのみフィールドが存在することを求めることも可能です。

$validator->requirePresence('full_name', function ($context) {
    if (isset($context['data']['action'])) {
        return $context['data']['action'] === 'subscribe';
    }
    return false;
});
$validator->requirePresence('email');

これは、申し込みを作成したいユーザーの場合のみ full_name フィールドの存在を求め、 email フィールドは常に要求されます。申し込みをキャンセルした時にも必要とされます。

条件付きのカスタムコールバックに渡される $context パラメータには、以下のキーが含まれます。

data バリデートされるデータ

newRecord 新規または既存のレコードが存在しているかどうかを示すブール値

field バリデートされるフィールド

providers バリデーターに付与されるバリデーションプロバイダー

最後に適用されるルールとして設定する

フィールドに複数のルールが存在する場合は、前回のバリデーションが上手く機能しなかった場合でも、個々のバリデーションルールは適用されます。このことにより、一回のパスにより、好きなだけバリデーションエラーを設定することが可能となります。ただし、あるルールが上手くいかなかった後にその後のバリデーションを適用したくない場合は、 last オプションを true に設定することができます。

$validator = new Validator();
$validator
    ->add('body', [
        'minLength' => [
            'rule' => ['minLength', 10],
            'last' => true,
            'message' => 'コメントには中身のある本文が必要です。',
        ],
        'maxLength' => [
            'rule' => ['maxLength', 250],
            'message' => 'コメントが長すぎることはできません。'
        ]
    ]);

上記例にて、minLength ルール適用によりエラーとなった場合は、maxLength ルールは適用されません。

バリデーションプロバイダーを加える

Validator, ValidationSet, ValidationRule の各クラスは、自らのバリデーションメソッドを提供するわけではありません。バリデーションルールは 'プロバイダー' からもたらされるのです。バリデーターオブジェクトに対しては、いくつでもプロバイダーを設定することができます。バリデーターインスタンスには、自動的にデフォルトのプロバイダー設定が付随しています。デフォルトのプロバイダーは、 Validation のクラスにマッピングされております。このことが、このクラスにおけるメソッドをバリデーションルールとして使用することを容易にします。バリデーターと ORM をともに用いる場合は、テーブル及びエンティティーのオブジェクトのために追加のプロバーダーが設定されます。アプリケーションの用途に応じてプロバイダーを追加したい場合は、 setProvider() メソッドを用います。

$validator = new Validator();

// オブジェクトインスタンスを使います。
$validator->setProvider('custom', $myObject);

// クラス名を使います。メソッドは静的なものでなければなりません。
$validator->setProvider('custom', 'App\Model\Validation');

バリデーションプロバイダーは、オブジェクトか、あるいはクラス名で設定されます。クラス名が使用されるのであれば、メソッドは静的でなければなりません。デフォルト以外のプロバイダーを使うには、ルールの中に provider キーを挿入することを忘れないこと。

// テーブルプロバイダーからのルールを使用する
$validator->add('title', 'custom', [
    'rule' => 'customTableMethod',
    'provider' => 'table'
]);

今後作成される全ての Validator オブジェクトに provider を追加したい場合、以下のように addDefaultProvider() メソッドを使用できます。

use Cake\Validation\Validator;

// オブジェクトインスタンスを使います。
Validator::addDefaultProvider('custom', $myObject);

// クラス名を使います。メソッドは静的なものでなければなりません。
Validator::addDefaultProvider('custom', 'App\Model\Validation');

注釈

デフォルトプロバイダーは、 Validator オブジェクトが作成される前に追加されなければなりません。そのため config/bootstrap.php がデフォルトプロバイダーの設定に最適な場所です。

国に基いて提供するための Localized プラグイン [https://github.com/cakephp/localized] が利用できます。このプラグインで、国に依存するモデルのフィールドをバリデートできます。例:

namespace App\Model\Table;

use Cake\ORM\Table;
use Cake\Validation\Validator;

class PostsTable extends Table
{
    public function validationDefault(Validator $validator): Validator
    {
        // バリデーターにプロバイダーを追加
        $validator->setProvider('fr', 'Cake\Localized\Validation\FrValidation');
        // フィールドのバリデーションルールの中にプロバイダーを利用
        $validator->add('phoneField', 'myCustomRuleNameForPhone', [
            'rule' => 'phone',
            'provider' => 'fr'
        ]);

        return $validator;
    }
}

Localized プラグインは、バリデーションのための国の２文字の ISO コード (例えば en, fr, de) を使用します。

ValidationInterface インターフェイス [https://github.com/cakephp/localized/blob/master/src/Validation/ValidationInterface.php] によって定義されたすべてのクラスに共通する幾つかのメソッドがあります。

電話番号のチェックのための phone()
郵便番号のチェックのための postal()
国が定めた個人 ID のチェックのための personId()

バリデーターをネストする

ネストされたデータで モデルのないフォーム をバリデートする場合、また配列データを含むモデルを使用する場合、保有するネストされたデータをバリデートすることが必要となります。CakePHP では、簡単に特定の属性に対してバリデーターを加えることが可能となります。例えば、非リレーショナルデータベースを用いて作業しており、とある記事とそれに対するコメントを保存したいとします。

$data = [
    'title' => 'Best article',
    'comments' => [
        ['comment' => '']
    ]
];

コメントに対してバリデーションをかけたい場合は、ネストされたバリデーターを使用します。

$validator = new Validator();
$validator->add('title', 'not-blank', ['rule' => 'notBlank']);

$commentValidator = new Validator();
$commentValidator->add('comment', 'not-blank', ['rule' => 'notBlank']);

// ネストされたバリデーターをつなげる
$validator->addNestedMany('comments', $commentValidator);

// ネストされたバリデーターからのエラーを含むすべてのエラーを取得する
$validator->validate($data);

addNested() を用いることで、1:1 の関係を構築することができ、 addNestedMany() を用いることで 1:N の関係を築くことができます。両方のメソッドを用いることにより、ネストされたバリデーターのエラーは親バリデーターのエラーに貢献し、最終結果に影響を与えます。他のバリデーター機能と同様に、ネストされたバリデーターは、エラーメッセージと条件付きアプリケーションをサポートします。

$validator->addNestedMany(
    'comments',
    $commentValidator,
    'Invalid comment',
    'create'
);

ネストされたバリデーターのエラーメッセージは、 _nested キーにあります。

再利用可能なバリデーターを作成する

バリデーターを、使用されている場所で定義するのは、良いサンプルコードにはなりますが、簡単にメンテナンス可能なアプリケーションには結びつきません。実際には、再利用可能なバリデーションのロジックを使用する際、 Validator サブクラスを使うべきです。

// src/Model/Validation/ContactValidator.php にて
namespace App\Model\Validation;

use Cake\Validation\Validator;

class ContactValidator extends Validator
{
    public function __construct()
    {
        parent::__construct();
        // バリデーションのルールを加える
    }
}

データをバリデートする

バリデーターを作成し、適用したいルールを加えたので、実際にデータを用いてバリデーションを実施して行きましょう。バリデーターを用いることにより、配列ベースのデータのバリデーションが可能となります。例えば、 email を作成し、送る前にコンタクト先のバリデーションを行いたい場合は、以下のようにするとよいでしょう。

use Cake\Validation\Validator;

$validator = new Validator();
$validator
    ->requirePresence('email')
    ->add('email', 'validFormat', [
        'rule' => 'email',
        'message' => 'Eメールは有効でなければなりません。'
    ])
    ->requirePresence('name')
    ->notEmpty('name', '名前が必要です。')
    ->requirePresence('comment')
    ->notEmpty('comment', 'コメントが必要です。');

$errors = $validator->validate($this->request->getData());
if (empty($errors)) {
    // email を送る。
}

errors() メソッドは、バリデーションエラーがあった場合に、空でない配列を返します。返されたエラー配列は、以下のような構造となっております。

$errors = [
    'email' => ['Eメールは有効でなければなりません。']
];

もし単一のフィールドに複数のエラーがあった場合は、エラーメッセージの配列はフィールドごとに返されます。デフォルトでは errors() メソッドは、 'create' を実行する際のルールが適用されますが、 'update' を実行する際のルールを適用したい場合は、以下のことが可能となります。

$errors = $validator->validate($this->request->getData(), false);
if (empty($errors)) {
    // email を送る。
}

注釈

もし、エンティティーをバリデーションしたい場合は、エンティティーのバリデーションのために用意された次のようなメソッドを利用するべきです。 newEntity(), newEntities(), patchEntity(), patchEntities() as they are designed for that.

エンティティーをバリデーションする

エンティティーは保存される際にバリデーションが実行されますが、保存を試みる前にエンティティーのバリデーションを行いたいようなケースがあるかもしれません。 newEntity(), newEntities(), patchEntity() または patchEntities() を使った場合、保存前のエンティティーのバリデーションは自動的に実行されます。

// ArticlesController クラスにおいて
$article = $this->Articles->newEntity($this->request->getData());
if ($article->errors()) {
    // エラーメッセージが表示されるためのコードを書く
}

同様に、いくつかのエンティティーに対して同時に事前のバリデーションを実行したい場合は、 newEntities() メソッドを用いることができます。

// ArticlesController クラスにおいて
$entities = $this->Articles->newEntities($this->request->getData());
foreach ($entities as $entity) {
    if (!$entity->errors()) {
        $this->Articles->save($entity);
    }
}

newEntity(), patchEntity(), newEntities() 及び patchEntities() メソッドを用いることによりどのアソシエーションがバリデーションされたか、 options パラメーターを用いることによりどのバリデーションセットを適用させるかを特定することができます。

$valid = $this->Articles->newEntity($article, [
  'associated' => [
    'Comments' => [
      'associated' => ['User'],
      'validate' => 'special',
    ]
  ]
]);

バリデーションは、ユーザーフォームやインターフェイスに主に利用され、その用途はテーブル内のコラムをバリデーションすることに限られません。しかしながら、データ元がどこであったとしても、データの統一性を維持することは重要です。この問題を解決するために、CakePHP は "アプリケーションルール" と呼ばれる２段階目のバリデーションを提供します。本件については、 アプリケーションルールの適用セクションにて詳述します。

コアバリデーションルール

CakePHP は Validation クラス内にバリデーションメソッドに関する基本的な構文を提供します。バリデーションクラスには、色々な一般的なバリデーションのシチュエーションに対する、様々な静的なメソッドが含まれます。

Validation クラスにおける API ドキュメント [https://api.cakephp.org/4.x/class-Cake.Validation.Validation.html] では、利用可能なバリデーションのルールについてのリスト及び基本的な使い方が案内されております。

いくつかのバリデーションメソッドは、上限下限に関する条件や有効なオプションを設定することができます。このような上限下限に関する条件や有効なオプションは、以下のように提供可能です。

$validator = new Validator();
$validator
    ->add('title', 'minLength', [
        'rule' => ['minLength', 10]
    ])
    ->add('rating', 'validValue', [
        'rule' => ['range', 1, 5]
    ]);

追加のパラメーターが設定できるコアなルールには、 rule キーの中に、最初の要素としてルールそのものを含むような配列が設定されるべきであり、その後のパラメーターには、残りのパラメーターを含ませるべきです。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Appクラス

class Cake\Core\App

App クラスはリソースの位置とパスの管理を担当します。

クラスの検索

static Cake\Core\App::className($name, $type = '', $suffix = '')

この方法は CakePHP 全体でクラス名を解決するために使われます。 CakePHP が使用する短い形式の名前を解決し、完全解決されたクラス名を返します。

// 短いクラス名を名前空間とサフィックスで解決します。
App::className('Auth', 'Controller/Component', 'Component');
// Cake\Controller\Component\AuthComponent を返します

// プラグイン名を解決します。
App::className('DebugKit.Toolbar', 'Controller/Component', 'Component');
// Returns DebugKit\Controller\Component\ToolbarComponent

// \を含む名前はそのまま返されます。
App::className('App\Cache\ComboCache');
// App\Cache\ComboCache を返します。

クラスを解決する時、 App 名前空間による解決が試みられ、もしそのクラスが存在しなければ Cake 名前空間による解決が行われます。もし両方のクラス名が存在しない場合、 false が返されます。

Finding Paths to Resources

static Cake\Core\App::path(string $package, ?string $plugin = null)

The method returns paths set using App.paths app config:

// Get the templates path set using ``App.paths.templates`` app config.
App::path('templates');

The same way you can retrieve paths for locales, plugins.

名前空間のパスの検索

static Cake\Core\App::classPath(string $package, ?string $plugin = null)

規約上のパスについて位置を得るために使われます。

// アプリケーション中の Controller/ までのパスを得ます
App::classPath('Controller');

これはアプリケーションを構成するすべての名前空間に対して行うことができます。

App::classPath() は既定のパスのみを返し、オートローダーに設定された追加のパスに関するいかなる情報も返しません。

static Cake\Core\App::core(string $package)

CakePHP 内部のパッケージのパスを検索するために使われます。

// Cache エンジンのパスを得ます
App::core('Cache/Engine');

テーマの検出

テーマはプラグインなので、テーマのパスを取得するには上のメソッドを使うことができます。

ベンダーファイルの読込

理想的にはベンダーファイルは Composer でオートロードされるべきで、もし Composer でベンダーファイルをオートロードまたはインストールできない場合、それらを読み込むために require を使う必要があるかもしれません。

もしライブラリーを Composer でインストールできないのであれば、Composer の規約に沿ったディレクトリーである vendor/$author/$package に各ライブラリーをインストールするのが最善です。もし AcmeLib というライブラリーを持っていたなら、 vendor/Acme/AcmeLib にインストールするのです。仮に PSR-0 互換のクラス名を使ってないとしたら、あなたのアプリケーションの composer.json 中で次のようにオートロードできます。

"autoload": {
    "psr-4": {
        "App\\": "src/",
        "App\\Test\\": "tests/"
    },
    "classmap": [
        "vendor/Acme/AcmeLib"
    ]
}

もしもあなたのベンダーライブラリーがクラスを使っておらず、代わりに関数を提供する場合、 files オートロードの手法を使って各リクエストの最初にそれらのファイルを読み込むように Composer を設定することができます。

"autoload": {
    "psr-4": {
        "App\\": "src/",
        "App\\Test\\": "tests/"
    },
    "files": [
        "vendor/Acme/AcmeLib/functions.php"
    ]
}

ベンダーライブラリーの設定をした後はあなたのアプリケーションのオートローダーを再生成する必要があります。

$ php composer.phar dump-autoload

もしもあなたのアプリケーションで Composer を使っていないとしたら、自分ですべてのベンダーライブラリーを手動で読み込む必要があるでしょう。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コレクション

class Cake\Collection\Collection

コレクションクラスは、配列または Traversable オブジェクトを操作するためのツールのセットを提供します。もし今までに underscore.js を使用したことがあるなら、コレクションクラスに何を期待できるかの考え方を理解しているはずです。

コレクションのインスタンスはイミュータブルです。コレクションを変更すると、代わりに新しいコレクションを生成します。これは、操作が副作用を含まないので、コレクションオブジェクトの働きがより予測可能になります。

簡単な例

コレクションは、配列または Traversable オブジェクトを使用して作成することができます。また、CakePHP での ORM と対話するたびに、コレクションと対話することになるでしょう。コレクションの簡単な使い方は次のようになります。

use Cake\Collection\Collection;

$items = ['a' => 1, 'b' => 2, 'c' => 3];
$collection = new Collection($items);

// １より大きい要素を含む
// 新しいコレクションを作成
$overOne = $collection->filter(function ($value, $key, $iterator) {
    return $value > 1;
});

また、 new Collection() の代わりに collection() ヘルパー関数を使用することができます。

$items = ['a' => 1, 'b' => 2, 'c' => 3];

// 両方ともコレクションのインスタンスを作成します。
$collectionA = new Collection($items);
$collectionB = collection($items);

ヘルパーメソッドの利点は、 (new Collection($items)) よりも連鎖が容易であるということです。

CollectionTrait は、あなたのアプリケーションにある任意の Traversable オブジェクトにコレクションのような機能を統合することができます。

メソッド一覧

append

appendItem

avg

buffered

chunk

chunkWithKeys

combine

compile

contains

countBy

each

every

extract

filter

first

firstMatch

groupBy

indexBy

insert

isEmpty

last

listNested

map

match

max

median

min

nest

prepend

prependItem

reduce

reject

sample

shuffle

skip

some

sortBy

stopWhen

sumOf

take

through

transpose

unfold

zip

反復

Cake\Collection\Collection::each($callback)

コレクションは、 each() と map() メソッドで反復したり新しいコレクションに変換することができます。 each() メソッドは新しいコレクションを作成しませんが、コレクション内の任意のオブジェクトを変更できます。

$collection = new Collection($items);
$collection = $collection->each(function ($value, $key) {
    echo "要素 $key: $value";
});

each() の戻り値はコレクションオブジェクトです。即時にコレクション内の各値にコールバックを適用する反復処理します。

Cake\Collection\Collection::map($callback)

map() メソッドは、元のコレクション内の各オブジェクトに適用されるコールバックの出力に基づいて新しいコレクションを作成します。

$items = ['a' => 1, 'b' => 2, 'c' => 3];
$collection = new Collection($items);

$new = $collection->map(function ($value, $key) {
    return $value * 2;
});

// $result には [2, 4, 6] が含まれています。
$result = $new->toList();

// $result には ['a' => 2, 'b' => 4, 'c' => 6] が含まれています。
$result = $new->toArray();

map() メソッドは、新しいイテレータを作成し、反復する時に得られた項目を遅延して作成します。

Cake\Collection\Collection::extract($path)

map() 関数の最も一般的な用途の1つはコレクションから単一の列を抽出することです。特定のプロパティーの値を含む要素のリストを構築したい場合は、 extract() メソッドを使用することができます。

$collection = new Collection($people);
$names = $collection->extract('name');

// $result には ['mark', 'jose', 'barbara'] が含まれています。
$result = $names->toList();

コレクションクラス内の他の多くの関数と同様に、列を抽出するために、ドット区切りのパスを指定することができます。この例では、記事のリストから著者名を含むコレクションを返します。

$collection = new Collection($articles);
$names = $collection->extract('author.name');

// $result には ['Maria', 'Stacy', 'Larry'] が含まれています。
$result = $names->toList();

最後に、あなたが取得したいプロパティーがパスで表現できない場合は、それを返すようにコールバック関数を使用することができます。

$collection = new Collection($articles);
$names = $collection->extract(function ($article) {
    return $article->author->name . ', ' . $article->author->last_name;
});

しばしば、他の構造の内部に深くネストされている複数の配列やオブジェクトに存在する共通のキーでプロパティーを抽出する必要があります。これらの例については、パスのキーに {*} マッチャを使用することができます。このマッチャは、 HasMany や BelongsToMany の関連データを照合する時に便利です。

$data = [
    [
        'name' => 'James',
        'phone_numbers' => [
            ['number' => 'number-1'],
            ['number' => 'number-2'],
            ['number' => 'number-3'],
        ]
    ],
    [
        'name' => 'James',
        'phone_numbers' => [
            ['number' => 'number-4'],
            ['number' => 'number-5'],
        ]
    ]
];

$numbers = (new Collection($data))->extract('phone_numbers.{*}.number');
$numbers->toList();
// 戻り値は ['number-1', 'number-2', 'number-3', 'number-4', 'number-5']

この最後の例では、 他の例とは異なり toList() メソッドを使用していますが、おそらく重複したキーで結果を取得する場合に重要になります。 toList() メソッドを使用することにより、重複するキーが存在する場合でも、すべての値を取得することが保証されます。

Cake\Utility\Hash::extract() とは異なり、このメソッドは {*} ワイルドカードのみをサポートしています。他のすべてのワイルドカードと属性のマッチャはサポートされていません。

Cake\Collection\Collection::combine($keyPath, $valuePath, $groupPath = null)

既存のコレクションの中のキーと値から作られた新しいコレクションを作成することができます。キーと値の両方のパスは、ドット記法のパスで指定することができます。

$items = [
    ['id' => 1, 'name' => 'foo', 'parent' => 'a'],
    ['id' => 2, 'name' => 'bar', 'parent' => 'b'],
    ['id' => 3, 'name' => 'baz', 'parent' => 'a'],
];
$combined = (new Collection($items))->combine('id', 'name');

// 配列に変換すると、結果は次のようになります。
[
    1 => 'foo',
    2 => 'bar',
    3 => 'baz',
];

また、オプションでパスに基づいた結果のグループ化に groupPath を使用することができます。

$combined = (new Collection($items))->combine('id', 'name', 'parent');

// 配列に変換すると、結果は次のようになります。
[
    'a' => [1 => 'foo', 3 => 'baz'],
    'b' => [2 => 'bar']
];

最後に、動的にキーと値とグループのパスを構築するために クロージャー を使用することができます。例えば、エンティティーや(ORM によって Cake/Time インスタンスに変換された) 日付で作業する場合、日付で結果をグループ化するのによいでしょう。

$combined = (new Collection($entities))->combine(
    'id',
    function ($entity) { return $entity; },
    function ($entity) { return $entity->date->toDateString(); }
);

// 配列に変換すると、結果は次のようになります。
[
    'date string like 2015-05-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN]
    'date string like 2015-06-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN]
]

Cake\Collection\Collection::stopWhen(callable $c)

stopWhen() メソッドを使用して、任意の時点で反復を停止することができます。コレクションの中でこのメソッドを呼び出すと、新しいコレクションを作成し、要素のいずれかで、渡された callable が false を返した場合、結果の引き渡しを停止します。

$items = [10, 20, 50, 1, 2];
$collection = new Collection($items);

$new = $collection->stopWhen(function ($value, $key) {
    // 30 より大きい最初の値で停止します。
    return $value > 30;
});

// $result には [10, 20] が含まれています。
$result = $new->toList();

Cake\Collection\Collection::unfold(callable $callback)

時々、コレクション内の要素に、複数の要素を持つ配列やイテレータが含まれています。すべての要素に対して一回の反復で済むように内部構造を平坦化したい場合は、 unfold() メソッドが使用できます。これは、コレクション内のネストされたすべての単一の要素をもたらす新しいコレクションを作成します。

$items = [[1, 2, 3], [4, 5]];
$collection = new Collection($items);
$new = $collection->unfold();

// $result には [1, 2, 3, 4, 5] が含まれています。
$result = $new->toList();

unfold() に callable を渡すとき、 要素が元のコレクション内の各項目から展開されるかを制御することができます。これは、ページ制御するサービスからのデータを得るのに便利です。

$pages = [1, 2, 3, 4];
$collection = new Collection($pages);
$items = $collection->unfold(function ($page, $key) {
    // 結果のページを返す架空のウェブサービス
    return MyService::fetchPage($page)->toList();
});

$allPagesItems = $items->toList();

PHP 5.5 以降を使用している場合は、 コレクション内の各アイテムを必要なだけ複数の要素として返すために unfold() の中で yield キーワードを使用することができます。

$oddNumbers = [1, 3, 5, 7];
$collection = new Collection($oddNumbers);
$new = $collection->unfold(function ($oddNumber) {
    yield $oddNumber;
    yield $oddNumber + 1;
});

// $result には [1, 2, 3, 4, 5, 6, 7, 8] が含まれています。
$result = $new->toList();

Cake\Collection\Collection::chunk($chunkSize)

コレクション内の大量のアイテムを扱う場合には、一つ一つの要素を処理する代わりにバッチ処理が適しています。コレクションをある程度の大きさの複数の配列に分割するために、 chunk() 関数を使用することができます。

$items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
$collection = new Collection($items);
$chunked = $collection->chunk(2);
$chunked->toList(); // [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]

chunk 関数は、例えばデータベースの結果のために、バッチ処理を行う場合、 特に便利です。

$collection = new Collection($articles);
$collection->map(function ($article) {
        // article のプロパティーを変更します。
        $article->property = 'changed';
    })
    ->chunk(20)
    ->each(function ($batch) {
        myBulkSave($batch); // この関数は、バッチごとに呼び出されます。
    });

Cake\Collection\Collection::chunkWithKeys($chunkSize)

chunk() 同様、 chunkWithKeys() は、コレクションを小さい塊に薄切りにしますが、キーは保持されます。これは、連想配列を分割するのに便利です。

$collection = new Collection([
    'a' => 1,
    'b' => 2,
    'c' => 3,
    'd' => [4, 5]
]);
$chunked = $collection->chunkWithKeys(2)->toList();
// 作成物
[
    ['a' => 1, 'b' => 2],
    ['c' => 3, 'd' => [4, 5]]
]

フィルタリング

Cake\Collection\Collection::filter($callback)

コレクションは、コールバック関数の結果に基づいてフィルタリングし、新しいコレクションを作成が容易になります。基準のコールバックに一致する要素の新しいコレクションを作成するには、 filter() を使用することができます。

$collection = new Collection($people);
$ladies = $collection->filter(function ($person, $key) {
    return $person->gender === 'female';
});
$guys = $collection->filter(function ($person, $key) {
    return $person->gender === 'male';
});

Cake\Collection\Collection::reject(callable $c)

filter() の逆の関数は reject() です。このメソッドは、打ち消しのフィルタリングを行い、フィルター関数に一致する要素を削除します。

$collection = new Collection($people);
$ladies = $collection->reject(function ($person, $key) {
    return $person->gender === 'male';
});

Cake\Collection\Collection::every($callback)

フィルター関数で真偽のテストを行うことができます。コレクション内のすべての要素が条件を満たしているかどうかを確認するには、 every() が使用できます。

$collection = new Collection($people);
$allYoungPeople = $collection->every(function ($person) {
    return $person->age < 21;
});

Cake\Collection\Collection::some($callback)

フィルター関数に合致する要素が、コレクションに少なくとも１つ含まれているかどうかを some() メソッドを使用して確認することができます。

$collection = new Collection($people);
$hasYoungPeople = $collection->some(function ($person) {
    return $person->age < 21;
});

Cake\Collection\Collection::match($conditions)

指定したプロパティーを持つ要素のみを含んだ新しいコレクションを抽出する場合、 match() メソッドを使用しましょう。

$collection = new Collection($comments);
$commentsFromMark = $collection->match(['user.name' => 'Mark']);

Cake\Collection\Collection::firstMatch($conditions)

プロパティー名は、ドット区切りのパスになります。ネストされたエンティティーを横断し、それらに含まれる値を一致させることができます。コレクションから、最初に一致した要素が必要な場合、 firstMatch() を使用することができます。

$collection = new Collection($comments);
$comment = $collection->firstMatch([
    'user.name' => 'Mark',
    'active' => true
]);

上記の通り、 match() と firstMatch() の両方は、一致させたい複数の条件を指定できます。また、条件は、異なるパスで、一致する複雑な条件を表現することができます。

集約

Cake\Collection\Collection::reduce($callback, $initial)

map() の反対の操作は、一般的には reduce です。この関数を使用すると、コレクション内のすべての要素から１つの結果を得ることができます。

$totalPrice = $collection->reduce(function ($accumulated, $orderLine) {
    return $accumulated + $orderLine->price;
}, 0);

上記の例では、 $totalPrice は、コレクションに含まれるすべての価格の合計になります。 reduce() 関数の第二引数に、reduce 操作を開始するための初期値を渡していることに注意してください。

$allTags = $collection->reduce(function ($accumulated, $article) {
    return array_merge($accumulated, $article->tags);
}, []);

Cake\Collection\Collection::min(string|callable $callback, $type = SORT_NUMERIC)

プロパティーに基づいて、コレクションの最小値を抽出するには、 min() 関数を使用します。これは、コレクションから、見つかったプロパティーの最小値だけでなく完全な要素を返します。

$collection = new Collection($people);
$youngest = $collection->min('age');

echo $youngest->name;

また、パスまたはコールバック関数を指定することで、比較するプロパティーを表現することができます。

$collection = new Collection($people);
$personYoungestChild = $collection->min(function ($person) {
    return $person->child->age;
});

$personWithYoungestDad = $collection->min('dad.age');

Cake\Collection\Collection::max(string|callable $callback, $type = SORT_NUMERIC)

同様に、 max() 関数を使用すると、コレクションから最も高いプロパティー値を持つ要素を返します。

$collection = new Collection($people);
$oldest = $collection->max('age');

$personOldestChild = $collection->max(function ($person) {
    return $person->child->age;
});

$personWithOldestDad = $collection->max('dad.age');

Cake\Collection\Collection::sumOf($path = null)

最後に、 sumOf() メソッドは、すべての要素のプロパティーの合計を返します。

$collection = new Collection($people);
$sumOfAges =  $collection->sumOf('age');

$sumOfChildrenAges = $collection->sumOf(function ($person) {
    return $person->child->age;
});

$sumOfDadAges = $collection->sumOf('dad.age');

Cake\Collection\Collection::avg($path = null)

コレクション内の要素の平均値を計算します。必要に応じて、平均値を生成するためのマッチャーパスや値を抽出する関数を指定してください。

$items = [
   ['invoice' => ['total' => 100]],
   ['invoice' => ['total' => 200]],
];

// 平均値: 150
$average = (new Collection($items))->avg('invoice.total');

Cake\Collection\Collection::median($path = null)

要素の集合の中央値を計算します。必要に応じて、中央値を生成するためのマッチャーパスや値を抽出する関数を指定してください。

$items = [
  ['invoice' => ['total' => 400]],
  ['invoice' => ['total' => 500]],
  ['invoice' => ['total' => 100]],
  ['invoice' => ['total' => 333]],
  ['invoice' => ['total' => 200]],
];

// 中央値: 333
$median = (new Collection($items))->median('invoice.total');

グループ化とカウント

Cake\Collection\Collection::groupBy($callback)

コレクションの要素がプロパティーに同じ値を持つ場合、キー別にグループ化した新しいコレクションを作ることができます。

$students = [
    ['name' => 'Mark', 'grade' => 9],
    ['name' => 'Andrew', 'grade' => 10],
    ['name' => 'Stacy', 'grade' => 10],
    ['name' => 'Barbara', 'grade' => 9]
];
$collection = new Collection($students);
$studentsByGrade = $collection->groupBy('grade');

// 配列に変換すると、結果は次のようになります。
[
  10 => [
    ['name' => 'Andrew', 'grade' => 10],
    ['name' => 'Stacy', 'grade' => 10]
  ],
  9 => [
    ['name' => 'Mark', 'grade' => 9],
    ['name' => 'Barbara', 'grade' => 9]
  ]
]

例のごとく、動的にグループを生成するために、ネストされたプロパティーのドットで区切られたパスまたは独自のコールバック関数のいずれかを指定することができます。

$commentsByUserId = $comments->groupBy('user.id');

$classResults = $students->groupBy(function ($student) {
    return $student->grade > 6 ? 'approved' : 'denied';
});

Cake\Collection\Collection::countBy($callback)

グループごとの出現数を知りたい場合は、 countBy() メソッドを使用して行うことができます。それは既にあなたもご存知の groupBy と同じ引数を受け取ります。

$classResults = $students->countBy(function ($student) {
    return $student->grade > 6 ? 'approved' : 'denied';
});

// 配列に変換すると、結果は次のようになります。
['approved' => 70, 'denied' => 20]

Cake\Collection\Collection::indexBy($callback)

グループ化したいプロパティーに対して要素が一意であることがわかっている一定のケースがあります。グループごとに単一の結果が欲しいなら、 indexBy() 関数を使用することができます。

$usersById = $users->indexBy('id');

// 配列に変換すると、結果は次のようになります。
[
    1 => 'markstory',
    3 => 'jose_zap',
    4 => 'jrbasso'
]

groupBy() 関数と同じように、プロパティーパスまたはコールバックを使用することができます。

$articlesByAuthorId = $articles->indexBy('author.id');

$filesByHash = $files->indexBy(function ($file) {
    return md5($file);
});

Cake\Collection\Collection::zip($items)

zip() メソッドを使用して、異なるコレクションの要素をグループ化することができます。このメソッドは、各コレクションから同じ位置に配置されている要素をグループ化する配列の新しいコレクションを返します。

$odds = new Collection([1, 3, 5]);
$pairs = new Collection([2, 4, 6]);
$combined = $odds->zip($pairs)->toList(); // [[1, 2], [3, 4], [5, 6]]

また、一度に複数のコレクションを zip することができます。

$years = new Collection([2013, 2014, 2015, 2016]);
$salaries = [1000, 1500, 2000, 2300];
$increments = [0, 500, 500, 300];

$rows = $years->zip($salaries, $increments)->toList();
// 戻り値：
[
    [2013, 1000, 0],
    [2014, 1500, 500],
    [2015, 2000, 500],
    [2016, 2300, 300]
]

既にお見せした通り、 zip() メソッドは、多次元配列を転置するのに非常に便利です。

$data = [
    2014 => ['jan' => 100, 'feb' => 200],
    2015 => ['jan' => 300, 'feb' => 500],
    2016 => ['jan' => 400, 'feb' => 600],
]

// jan と feb のデータを取得

$firstYear = new Collection(array_shift($data));
$firstYear->zip($data[0], $data[1])->toList();

// また PHP >= 5.6 で $firstYear->zip(...$data)

// 戻り値
[
    [100, 300, 400],
    [200, 500, 600]
]

ソート

Cake\Collection\Collection::sortBy($callback, $order = SORT_DESC, $sort = SORT_NUMERIC)

コレクションの値は、カラムまたはカスタム関数に基づいて昇順または降順でソートすることができます。コレクションの値から新たにソートされたコレクションを作成するには、 sortBy を使用することができます。

$collection = new Collection($people);
$sorted = $collection->sortBy('age');

上で見たように、コレクションの値に存在するカラム名またはプロパティー名を渡すことで並べ替えることができます。また、代わりにドット表記を使用して、プロパティーのパスを指定することができます。次の例では、その著者の名前で記事をソートします。

$collection = new Collection($articles);
$sorted = $collection->sortBy('author.name');

sortBy() メソッドは、コレクション内の２つの異なる値を比較する値を動的に選択する抽出関数を指定するのに十分な柔軟性があります。

$collection = new Collection($articles);
$sorted = $collection->sortBy(function ($article) {
    return $article->author->name . '-' . $article->title;
});

コレクションのソート順を指定するには、昇順や降順にソートするために、２番目のパラメーターに SORT_ASC や SORT_DESC のどちらかを指定する必要があります。デフォルトでは、コレクションは降順にソートされます。

$collection = new Collection($people);
$sorted = $collection->sortBy('age', SORT_ASC);

時には、一貫性のある結果を得るように、比較しようとしているデータのタイプを指定する必要があります。この目的のためには、 sortBy() 関数の第３引数に次のいずれかの定数を指定する必要があります。

SORT_NUMERIC: 数字を比較

SORT_STRING: 文字列値を比較

SORT_NATURAL: 数字を含む文字列をソート。これらの数字は、自然な方法の並び順になります。例: "2" の後に "10" を表示。

SORT_LOCALE_STRING: 現在のロケールに基づいて文字列を比較。

デフォルトでは、 SORT_NUMERIC が使用されます。

$collection = new Collection($articles);
$sorted = $collection->sortBy('title', SORT_ASC, SORT_NATURAL);

警告

複数回ソートされたコレクションで反復処理することは高コストです。そのような計画をしている場合、コレクションを配列への変換を検討したり、 単純に compile() メソッドを使用してください。

ツリーデータの操作

Cake\Collection\Collection::nest($idPath, $parentPath)

全てのデータが、線形に表現できるわけではありません。コレクションは、簡単に階層またはネストされた構造を、構築したり平坦化することができます。親の識別子プロパティーによって子がグループ化されるような、ネストされた構造を作成するには、 nest() メソッドが簡単です。

この関数には、２つのパラメーターが必要です。１つ目は、項目の識別子を表すプロパティーです。２つ目のパラメーターは、親項目の識別子を表すプロパティーの名前です。

$collection = new Collection([
    ['id' => 1, 'parent_id' => null, 'name' => 'Birds'],
    ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'],
    ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'],
    ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'],
    ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'],
    ['id' => 6, 'parent_id' => null, 'name' => 'Fish'],
]);

$collection->nest('id', 'parent_id')->toList();
// 戻り値
[
    [
        'id' => 1,
        'parent_id' => null,
        'name' => 'Birds',
        'children' => [
            ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => []],
            ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => []],
            ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => []],
        ]
    ],
    [
        'id' => 6,
        'parent_id' => null,
        'name' => 'Fish',
        'children' => [
            ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => []],
        ]
    ]
];

子要素は、コレクション内の各項目の children プロパティーの中にネストされています。このデータ表現のタイプは、メニューを描画したり、ツリー内の特定のレベルまでの要素を走査するのに便利です。

Cake\Collection\Collection::listNested($order = 'desc', $nestingKey = 'children')

nest() の逆の関数は listNested() です。このメソッドは、ツリー構造を線形構造に戻すように平坦にすることができます。このメソッドは、2つのパラメーターを持ちます。 1つ目は、走査モード（昇順、降順または、そのまま）であり、 2つ目は、コレクション内の各要素の子を含むプロパティー名です。

前の例で構築したネストされたコレクションを入力として利用し、それを平らにすることができます。

$nested->listNested()->toList();

// 戻り値
[
    ['id' => 1, 'parent_id' => null, 'name' => 'Birds', 'children' => [...]],
    ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'],
    ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'],
    ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'],
    ['id' => 6, 'parent_id' => null, 'name' => 'Fish', 'children' => [...]],
    ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish']
]

デフォルトでは、ツリーはルートから末端へと走査されます。また、ツリーの中の末端の要素のみを返すように指示することができます。

$nested->listNested('leaves')->toList();

// 戻り値
[
    ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'],
    ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'],
    ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish']
]

ツリーをネストされたリストに変換すると、リスト出力の書式設定方法を設定するには、 printer() メソッドを使用することができます。

$nested->listNested()->printer('name', 'id', '--')->toArray();

// 戻り値
[
    3 => 'Eagle',
    4 => 'Seagull',
    5 -> '--Clown Fish',
]

printer() メソッドはまた、キーとまたは値を生成するためにコールバックを使用することができます。

$nested->listNested()->printer(
    function ($el) {
        return $el->name;
    },
    function ($el) {
        return $el->id;
    }
);

その他のメソッド

Cake\Collection\Collection::isEmpty()

コレクションに要素が含まれているかどうかを確認することができます。

$collection = new Collection([]);
// 戻り値は true
$collection->isEmpty();

$collection = new Collection([1]);
// 戻り値は false
$collection->isEmpty();

Cake\Collection\Collection::contains($value)

コレクションは、 contains() メソッドを使用して、ある特定の値が含まれているかどうかを、すぐに確認することができます。

$items = ['a' => 1, 'b' => 2, 'c' => 3];
$collection = new Collection($items);
$hasThree = $collection->contains(3);

比較は === 演算子を使用して実行されます。緩い比較タイプを行いたい場合は、 some() メソッドを使用することができます。

Cake\Collection\Collection::shuffle()

時には、コレクションでランダムな順序の値を表示したいこともあるでしょう。ランダムな位置にそれぞれの値を返す新しいコレクションを作成するためには、 shuffle を使用してください。

$collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3]);

// これは [2, 3, 1] を返します。
$collection->shuffle()->toList();

Cake\Collection\Collection::transpose()

コレクションを transpose (行列の転置) すると、元の列のそれぞれで作られた行を含む新しいコレクションを取得します。

$items = [
   ['Products', '2012', '2013', '2014'],
   ['Product A', '200', '100', '50'],
   ['Product B', '300', '200', '100'],
   ['Product C', '400', '300', '200'],
]
$transpose = (new Collection($items))->transpose()->toList();

// 戻り値
[
    ['Products', 'Product A', 'Product B', 'Product C'],
    ['2012', '200', '300', '400'],
    ['2013', '100', '200', '300'],
    ['2014', '50', '100', '200'],
]

要素の取り出し

Cake\Collection\Collection::sample($length = 10)

手早く統計分析を行うときにコレクションをシャッフルすることが有用であることが多いです。この種のタスクを行う一般的な操作は、より多くのテストが実行できるよう、コレクションから、いくつかのランダムな値を取り出します。例えば、いくつかの A/B テストを適用したい５ユーザーをランダムに選びたい場合、 sample() 関数を使用することができます。

$collection = new Collection($people);

// このコレクションからランダムに最大 20 ユーザーを取り出します。
$testSubjects = $collection->sample(20);

sample() は、最大で最初の引数で指定した値の数だけ取り出します。 sample を満たすためのコレクション内に十分な要素がない場合、ランダムな順序で全てのコレクションが返されます。

Cake\Collection\Collection::take($length, $offset)

コレクションのスライスを取り出したいときは、 take() 関数を使用してください。その関数は二番目の引数で渡されたポジションから開始して、最初の引数で指定した値の数だけの新しいコレクションを作成します。

$topFive = $collection->sortBy('age')->take(5);

// ポジション 4 から始まるコレクションから５人取り出します。
$nextTopFive = $collection->sortBy('age')->take(5, 4);

ポジションはゼロが基準なので、最初のポジション番号は 0 です。

Cake\Collection\Collection::skip($length)

take() の第二引数は、コレクションから取得する前にいくつかの要素をスキップすることができますが、特定のポジションの後にある残りの要素を取る方法として、同じ目的のために skip() を使用できます。

$collection = new Collection([1, 2, 3, 4]);
$allExceptFirstTwo = $collection->skip(2)->toList(); // [3, 4]

Cake\Collection\Collection::first()

take() の最も一般的な用途の1つは、コレクションの最初の要素を取得することです。同じ目標を達成するためのショートカットメソッドとして first() メソッドを使用しています。

$collection = new Collection([5, 4, 3, 2]);
$collection->first(); // 戻り値は 5

Cake\Collection\Collection::last()

同様に、last() メソッドを使用して、コレクションの最後の要素を取得することができます。

$collection = new Collection([5, 4, 3, 2]);
$collection->last(); // 戻り値は 2

コレクションの拡張

Cake\Collection\Collection::append(array|Traversable $items)

複数のコレクションから１つのコレクションを作成することができます。これは、さまざまなソースからデータを収集し、それを連結し、非常にスムーズに他のコレクション関数を適用することができます。 append() メソッドは両方のソースの値を含む新しいコレクションを返します。

$cakephpTweets = new Collection($tweets);
$myTimeline = $cakephpTweets->append($phpTweets);

// 両方のソースから cakefest を含むつぶやき
$myTimeline->filter(function ($tweet) {
    return strpos($tweet, 'cakefest');
});

Cake\Collection\Collection::appendItem($value, $key)

オプションのキーを持つアイテムをコレクションに追加できます。コレクション内の既存のキーを指定した場合、値は上書きされません。

$cakephpTweets = new Collection($tweets);
$myTimeline = $cakephpTweets->appendItem($newTweet, 99);

Cake\Collection\Collection::prepend($items)

prepend() メソッドは両方のソースの値を含む新しいコレクションを返します。

$cakephpTweets = new Collection($tweets);
$myTimeline = $cakephpTweets->prepend($phpTweets);

Cake\Collection\Collection::prependItem($value, $key)

オプションのキーを持つアイテムをコレクションに追加できます。コレクション内の既存のキーを指定した場合、値は上書きされません。

$cakephpTweets = new Collection($tweets);
$myTimeline = $cakephpTweets->prependItem($newTweet, 99);

警告

異なるソースから追加するときは、両方のコレクションのいくつかのキーが同じこともありえます。例えば、2つの単純な配列を付加します。これは、 toArray() を使用してコレクションを配列に変換するときに問題を示すことができます。あるコレクションの値で、キーを基にして以前のコレクションの値を上書きしたくないなら、キーを削除して、すべての値を保持するために toList() を呼び出すことを確認してください。

要素の更新

Cake\Collection\Collection::insert($path, $items)

時には、２つの別々のデータの集合があり、一方の集合の要素を、他方のそれぞれの要素に挿入したいこともあるでしょう。もともとデータのマージや結合をサポートしないデータソースからデータを取得する際に非常に一般的なケースです。

あるコレクションの各要素を別のコレクションの各要素のプロパティーに挿入することができる insert() メソッドを提供します。

$users = [
    ['username' => 'mark'],
    ['username' => 'juan'],
    ['username' => 'jose']
];

$languages = [
    ['PHP', 'Python', 'Ruby'],
    ['Bash', 'PHP', 'Javascript'],
    ['Javascript', 'Prolog']
];

$merged = (new Collection($users))->insert('skills', $languages);

配列に変換すると、 $merged コレクションは、次のようになります。

[
    ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']],
    ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']],
    ['username' => 'jose', 'skills' => ['Javascript', 'Prolog']]
];

insert() メソッドの最初のパラメーターは、要素がその位置に挿入することができるように示したプロパティーのドット区切りのパスです。第２引数は、コレクションオブジェクトに変換することができるものです。

要素が順番に挿入されていることを確認してください。第２のコレクションの最初の要素は、第１のコレクションの最初の要素にマージされます。

第１のコレクションに挿入する第２のコレクションに十分な要素が存在しない場合、対象のプロパティーは、 null 値が入力されます。

$languages = [
    ['PHP', 'Python', 'Ruby'],
    ['Bash', 'PHP', 'Javascript']
];

$merged = (new Collection($users))->insert('skills', $languages);

// 結果
[
    ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']],
    ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']],
    ['username' => 'jose', 'skills' => null]
];

insert() メソッドは、配列の要素や ArrayAccess インターフェイスを実装するオブジェクトを操作することができます。

コレクションメソッドの再利用

コレクションのメソッドにクロージャーを使用することは、なすべき仕事が小さくて目的に合うと素晴らしいのですが、とてもすぐに厄介な事になります。異なる多くのメソッドの呼び出しが必要だったり、クロージャーメソッドの長さが数行では収まらないときに、より顕著になります。

コレクションのメソッドで使用されるロジックは、アプリケーションの複数の部分で再利用できる場合もあります。複雑なコレクションのロジックを抽出してクラスに分離することを検討してください。例えば、このような長いクロージャーを想像してください。

$collection
        ->map(function ($row, $key) {
            if (!empty($row['items'])) {
                $row['total'] = collection($row['items'])->sumOf('price');
            }

            if (!empty($row['total'])) {
                $row['tax_amount'] = $row['total'] * 0.25;
            }

            // コードが続きます・・・

            return $modifiedRow;
        });

これは、別のクラスを作成することでリファクタリングすることができます。

class TotalOrderCalculator
{
        public function __invoke($row, $key)
        {
            if (!empty($row['items'])) {
                $row['total'] = collection($row['items'])->sumOf('price');
            }

            if (!empty($row['total'])) {
                $row['tax_amount'] = $row['total'] * 0.25;
            }

            // コードが続きます・・・

            return $modifiedRow;
        }
}

// map() 呼び出しでロジックを使用
$collection->map(new TotalOrderCalculator)

Cake\Collection\Collection::through($callback)

時々、コレクションメソッド呼び出しの連鎖は、特定の順序で呼び出された場合にのみ、アプリケーションの他の部分で再利用可能になります。これらの例では、便利なデータ処理の呼び出しを割り当てるために __invoke を実装したクラスと組み合わせて through() を使用することができます。

$collection
        ->map(new ShippingCostCalculator)
        ->map(new TotalOrderCalculator)
        ->map(new GiftCardPriceReducer)
        ->buffered()
       ...

上記のメソッド呼び出しは、毎回繰り返す必要がないように、新しいクラスに抽出することができます。

class FinalCheckOutRowProcessor
{
        public function __invoke($collection)
        {
                return $collection
                        ->map(new ShippingCostCalculator)
                        ->map(new TotalOrderCalculator)
                        ->map(new GiftCardPriceReducer)
                        ->buffered()
                       ...
        }
}

// 一度に全てのメソッドを呼び出すために through() メソッドを使用できます。
$collection->through(new FinalCheckOutRowProcessor);

コレクションの最適化

Cake\Collection\Collection::buffered()

コレクションは、多くの場合、その関数の使用を遅延して作成する操作を実行します。これは、関数を呼び出すことができていても、それはすぐに実行されないことを意味します。これは、このクラス内の多くの関数についても同様です。遅延評価は、コレクション内のすべての値を使用していない状況で資源を節約することができます。反復が早期に停止した場合、または例外や失敗事例が早期に到達したときは、すべての値を使用しない場合があります。

また、遅延評価は、いくつかの操作をスピードアップするのに役立ちます。次の例を考えてみましょう。

$collection = new Collection($oneMillionItems);
$collection = $collection->map(function ($item) {
    return $item * 2;
});
$itemsToShow = $collection->take(30);

コレクションに遅延評価がなかったら、そのうち 30 の要素だけを見せたかったにもかかわらず、 100 万の操作を実行しているでしょう。代わりに、 map の操作は、使用した 30 の要素にのみ適用しました。小さいコレクションでも、複数の操作を行うとき、遅延評価から利益を得ることができます。たとえば、 map() を２回と filter() の呼び出しなどです。

遅延評価にも欠点があります。早い段階でコレクションを最適化する場合は、複数回同じ操作を行うことができました。この例を考えてみましょう。

$ages = $collection->extract('age');

$youngerThan30 = $ages->filter(function ($item) {
    return $item < 30;
});

$olderThan30 = $ages->filter(function ($item) {
    return $item > 30;
});

youngerThan30 と olderThan30 の両方を反復する場合、コレクションは残念ながら二度 extract() 操作を実行します。コレクションは不変であり、遅延抽出操作は両方のフィルターのために行われることになるためです。

幸いにも、一つの関数で、この問題を克服することができます。特定操作の値を複数回再利用する場合は、 buffered() 関数を使用して別のコレクションに結果をコンパイルすることができます。

$ages = $collection->extract('age')->buffered();
$youngerThan30 = ...
$olderThan30 = ...

両方のコレクションを反復処理しているときに、抽出操作を一度だけ呼び出します。

巻き戻し可能なコレクションの作成

buffered() メソッドは、巻き戻せないイテレータを複数回繰り返し可能なコレクションに変換するのに便利です。

// PHP 5.5 以上で
public function results()
{
    ...
    foreach ($transientElements as $e) {
        yield $e;
    }
}
$rewindable = (new Collection(results()))->buffered();

コレクションの複製

Cake\Collection\Collection::compile($preserveKeys = true)

時には、別のコレクションから要素の複製を取得する必要があります。同時に異なる場所から同じセットを反復処理する必要がある場合に便利です。別のコレクションからコレクションを複製するために compile() メソッドを使用します。

$ages = $collection->extract('age')->compile();

foreach ($ages as $age) {
    foreach ($collection as $element) {
        echo h($element->name) . ' - ' . $age;
    }
}

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Folder & File

Folder と File ユーティリティは、ファイルの読み書きやフォルダー内のファイル名一覧の取得、その他ディレクトリーに関連するタスクにおいて便利なクラスです。

基本的な使用法

クラスがロードされていることを確認してください。

use Cake\Filesystem\Folder;
use Cake\Filesystem\File;

すると、新しいフォルダーインスタンスをセットアップすることができるようになります。

$dir = new Folder('/path/to/folder');

そして、そのフォルダー内から .php の拡張子が付いたファイルを正規表現を使って検索できます。

$files = $dir->find('.*\.php');

これでファイルをループしたり、読み込み、内容の書き込み・追記、ファイルの削除などが行えるようになります。

foreach ($files as $file) {
    $file = new File($dir->pwd() . DS . $file);
    $contents = $file->read();
    // $file->write('このファイルの内容を上書きします');
    // $file->append('このファイルの最後に追記します。');
    // $file->delete(); // このファイルを削除します
    $file->close(); // 終了時にファイルをクローズしましょう
}

Folder API

class Cake\Filesystem\Folder(string $path = false, boolean $create = false, string|boolean $mode = false)

// 0755 のパーミッションで新しいフォルダーを作成します
$dir = new Folder('/path/to/folder', true, 0755);

property Cake\Filesystem\Folder::$path

フォルダーの現在のパス。 Folder::pwd() は同じ情報を返します。

property Cake\Filesystem\Folder::$sort

リストを取得する際に、名前によるソートを実行するかどうか。

property Cake\Filesystem\Folder::$mode

フォルダー作成時のモード。デフォルトは 0755 です。 Windows マシンでは何も影響しません。

static Cake\Filesystem\Folder::addPathElement(string $path, string $element)

$path と $element の間に適切なスラッシュを加えて返します。

$path = Folder::addPathElement('/a/path/for', 'testing');
// $path は /a/path/for/testing となります

$element は、配列も指定できます。

$path = Folder::addPathElement('/a/path/for', ['testing', 'another']);
// $path は /a/path/for/testing/another となります

Cake\Filesystem\Folder::cd($path)

ディレクトリーを $path へ移動します。失敗時には false が返ります。

$folder = new Folder('/foo');
echo $folder->path; // /foo を表示
$folder->cd('/bar');
echo $folder->path; // /bar を表示
$false = $folder->cd('/non-existent-folder');

Cake\Filesystem\Folder::chmod(string $path, integer $mode = false, boolean $recursive = true, array $exceptions = [])

ディレクトリー構造のモードを再帰的に変更します。ファイルのモードも同様に変更します。

$dir = new Folder();
$dir->chmod('/path/to/folder', 0755, true, ['skip_me.php']);

Cake\Filesystem\Folder::copy(array|string $options = [])

再帰的にディレクトリーをコピーします。唯一のパラメーターである $options にはコピー先のパスか、オプションの配列を指定します。

$folder1 = new Folder('/path/to/folder1');
$folder1->copy('/path/to/folder2');
// folder2 の中に folder1 とその全ての内容を配置します

$folder = new Folder('/path/to/folder');
$folder->copy([
    'to' => '/path/to/new/folder',
    'from' => '/path/to/copy/from', // Will cause a cd() to occur
    'mode' => 0755,
    'skip' => ['skip-me.php', '.git'],
    'scheme' => Folder::SKIP  // 既存のディレクトリーやファイルはスキップ。
]);

以下の３つの動作 (scheme) に対応します。

Folder::SKIP コピー・移動先にファイルやディレクトリーが既に存在している場合は、スキップします。

Folder::MERGE コピー元とコピー先のディレクトリーをマージします。コピー元のディレクトリーにある ファイルは、対象のディレクトリーにあるファイルを置き換えます。ディレクトリーの中身はマージされます。

Folder::OVERWRITE 対象のディレクトリーに存在するファイルやディレクトリーはコピー元のディレクトリーの内容で上書きされます。対象とコピー先の両方にサブディレクトリーが含まれる場合、対象のディレクトリーの内容は、コピー元の内容に削除や置き換えられます。

static Cake\Filesystem\Folder::correctSlashFor(string $path)

$path に与えるべき適切なスラッシュを返します。（Windows のパスは '\' で、その他のパスは '/'）

Cake\Filesystem\Folder::create(string $pathname, integer $mode = false)

再帰的にディレクトリー構造を作成します。 /foo/bar/baz/shoe/horn のような深い階層の作成も可能です。

$folder = new Folder();
if ($folder->create('foo' . DS . 'bar' . DS . 'baz' . DS . 'shoe' . DS . 'horn')) {
    // 入れ子になっているフォルダーの作成に成功
}

Cake\Filesystem\Folder::delete(string $path = null)

システムが許可していた場合、再帰的にディレクトリーを削除します。

$folder = new Folder('foo');
if ($folder->delete()) {
    // foo とその入れ子になっているフォルダーの削除に成功
}

Cake\Filesystem\Folder::dirsize()

フォルダーとその内容のサイズをバイト数で返します。

Cake\Filesystem\Folder::errors()

直近で利用したメソッドのエラーを返します。

Cake\Filesystem\Folder::find(string $regexpPattern = '.*', boolean $sort = false)

現在のディレクトリーでマッチしたファイルを配列で返します。

// webroot/img/ フォルダー内の .png を検索し、ソートして返す
$dir = new Folder(WWW_ROOT . 'img');
$files = $dir->find('.*\.png', true);
/*
Array
(
    [0] => cake.icon.png
    [1] => test-error-icon.png
    [2] => test-fail-icon.png
    [3] => test-pass-icon.png
    [4] => test-skip-icon.png
)
*/

注釈

フォルダーの find メソッドと findRecursive メソッドは、ファイルのみを検索します。フォルダーとファイルを取得したい場合は、 Folder::read() もしくは Folder::tree() 参照してください。

Cake\Filesystem\Folder::findRecursive(string $pattern = '.*', boolean $sort = false)

現在のディレクトリー内とそれ以下のすべての一致するファイルの配列を返します。

// test もしくは index で始まるファイルを再帰的に検索する
$dir = new Folder(WWW_ROOT);
$files = $dir->findRecursive('(test|index).*');
/*
Array
(
    [0] => /var/www/cake/webroot/index.php
    [1] => /var/www/cake/webroot/test.php
    [2] => /var/www/cake/webroot/img/test-skip-icon.png
    [3] => /var/www/cake/webroot/img/test-fail-icon.png
    [4] => /var/www/cake/webroot/img/test-error-icon.png
    [5] => /var/www/cake/webroot/img/test-pass-icon.png
)
*/

Cake\Filesystem\Folder::inCakePath(string $path = '')

ファイルが与えられた CakePath の中に存在すれば true を返します。

Cake\Filesystem\Folder::inPath(string $path = '', boolean $reverse = false)

ファイルが与えられたパスの中に存在すれば true を返します。

$Folder = new Folder(WWW_ROOT);
$result = $Folder->inPath(APP);
// $result = false, /var/www/example/src/ は /var/www/example/webroot/ の中ではありません

$result = $Folder->inPath(WWW_ROOT . 'img' . DS, true);
// $result = true, /var/www/example/webroot/img/ は /var/www/example/webroot/ の中です

static Cake\Filesystem\Folder::isAbsolute(string $path)

与えられた $path が絶対パスであれば true を返します。

static Cake\Filesystem\Folder::isSlashTerm(string $path)

与えられた $path がスラッシュで終了していれば true を返します。（つまり、スラッシュ終端）

$result = Folder::isSlashTerm('/my/test/path');
// $result = false
$result = Folder::isSlashTerm('/my/test/path/');
// $result = true

static Cake\Filesystem\Folder::isWindowsPath(string $path)

与えられた $path が Windows のパスであれば true を返します。

Cake\Filesystem\Folder::messages()

直近で利用したメソッドのメッセージを取得します。

Cake\Filesystem\Folder::move(array $options)

再帰的にディレクトリーを移動。

static Cake\Filesystem\Folder::normalizePath(string $path)

与えられた $path を適切なスラッシュに調整して返します。（Windows のパスは '\' で、その他のパスは '/'）

Cake\Filesystem\Folder::pwd()

現在のパスを返します。

Cake\Filesystem\Folder::read(boolean $sort = true, array|boolean $exceptions = false, boolean $fullPath = false)

現在のディレクトリーの内容を配列で返します。戻り値は2つの配列となります。1つはディレクトリー名の配列。もう1つはファイル名の配列です。

$dir = new Folder(WWW_ROOT);
$files = $dir->read(true, ['files', 'index.php']);
/*
Array
(
    [0] => Array // Folders
        (
            [0] => css
            [1] => img
            [2] => js
        )
    [1] => Array // Files
        (
            [0] => .htaccess
            [1] => favicon.ico
            [2] => test.php
        )
)
*/

Cake\Filesystem\Folder::realpath(string $path)

本当のパスを取得します（".." などを考慮して）

static Cake\Filesystem\Folder::slashTerm(string $path)

引数の $path に (Windows や、その他の OS で正しい) 終端のスラッシュを付けたパスを返します。

Cake\Filesystem\Folder::tree(null|string $path = null, array|boolean $exceptions = true, null|string $type = null)

入れ子になったディレクトリーと各ディレクトリー中のファイルの配列を返します。

File API

class Cake\Filesystem\File(string $path, boolean $create = false, integer $mode = 755)

// 0644 のパーミッションで新しいファイルを作成します
$file = new File('/path/to/file.php', true, 0644);

property Cake\Filesystem\File::$Folder

ファイルが属するフォルダー・オブジェクト。

property Cake\Filesystem\File::$name

拡張子付きのファイル名。拡張子なしのファイル名を返す File::name() とは異なります。

property Cake\Filesystem\File::$info

ファイル情報の配列。代わりに File::info() を使用してください。

property Cake\Filesystem\File::$handle

ファイルをオープンしている場合のファイルハンドラを保持します。

property Cake\Filesystem\File::$lock

ファイルの読み書き時のロックを有効にします。

property Cake\Filesystem\File::$path

現在のファイルの絶対パス。

Cake\Filesystem\File::append(string $data, boolean $force = false)

与えられたデータ文字列を現在のファイルに追記します。

Cake\Filesystem\File::close()

ファイルがオープンされていた場合、そのファイルをクローズします。

Cake\Filesystem\File::copy(string $dest, boolean $overwrite = true)

ファイルを $dest へコピーします。

Cake\Filesystem\File::create()

ファイルを作成します。

Cake\Filesystem\File::delete()

ファイルを削除します。

Cake\Filesystem\File::executable()

ファイルが実行可能な場合に true を返します。

Cake\Filesystem\File::exists()

ファイルが存在する場合に true を返します。

Cake\Filesystem\File::ext()

ファイルの拡張子を返します。

Cake\Filesystem\File::Folder()

現在のフォルダーを返します。

Cake\Filesystem\File::group()

ファイルのグループを返します。エラーの場合は false を返します。

Cake\Filesystem\File::info()

ファイル情報を返します。

Cake\Filesystem\File::lastAccess()

最終アクセス時刻を返します。

Cake\Filesystem\File::lastChange()

最終更新時刻を返します。エラーの場合は false を返します。

Cake\Filesystem\File::md5(integer|boolean $maxsize = 5)

ファイルサイズを事前にチェックした上で、ファイルの md5 チェックサムを取得します。エラーの場合、 false を取得します。

Cake\Filesystem\File::name()

拡張子を省いたファイル名を返します。

Cake\Filesystem\File::offset(integer|boolean $offset = false, integer $seek = 0)

現在オープンしているファイルのオフセット値を設定または取得します。

Cake\Filesystem\File::open(string $mode = 'r', boolean $force = false)

現在のファイルを与えられた $mode でオープンします。

Cake\Filesystem\File::owner()

ファイルのオーナーを返します。

Cake\Filesystem\File::perms()

ファイルの "chmod" (パーミッション) を返します。

static Cake\Filesystem\File::prepare(string $data, boolean $forceWindows = false)

ASCII 文字列をファイルへ書き出す事前処理を行います。現在の実行環境に合わせて改行文字を変換します。 Windows なら "\r\n" を、その他の環境なら "\n" が利用されます。

Cake\Filesystem\File::pwd()

ファイルのフルパスを返します。

Cake\Filesystem\File::read(string $bytes = false, string $mode = 'rb', boolean $force = false)

現在のファイルの内容を文字列で返します。失敗時は false を返します。

Cake\Filesystem\File::readable()

ファイルが読み出し可能な場合に true を返します。

Cake\Filesystem\File::safe(string $name = null, string $ext = null)

保存するファイル名を安全にします。

Cake\Filesystem\File::size()

ファイルサイズをバイト数で返します。

Cake\Filesystem\File::writable()

ファイルが書き込み可能な場合に true を返します。

Cake\Filesystem\File::write(string $data, string $mode = 'w', boolean$force = false)

与えられたデータを現在のファイルへ書き込みます。

Cake\Filesystem\File::mime()

ファイルの MIME タイプを取得します。失敗時は false を取得します。

Cake\Filesystem\File::replaceText($search, $replace)

ファイル内のテキストを置換します。失敗時に false を返し、成功時に true を返します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Hash

class Cake\Utility\Hash

配列マネジメントはとても強力かつ便利なツールであり、適切に使いさえすれば、よりスマートでより最適化されたコードを書くことができるようになるものです。 CakePHP ではとても便利なユーティリティ群を Hash クラスの中に static で用意しており、まさにこれをするのに使えます。

CakePHP の Hash クラスは Inflector クラスと同様で、どのモデルやコントローラーからでも呼ぶことができます。 例: Hash::combine() 。

Hash パス構文

下記のパス構文は Hash が持つすべてのメソッドで使われるものです。ただし、すべてのパス構文が、すべてのメソッドで使用可能であるとは限りません。パスの式はいくつものトークンで構成されます。トークンは、配列データの移動に使う『式』と、要素を絞り込む『マッチャー』の２つのグループに大きく分けられます。マッチャーは要素の式に対して適用することができます。

式の種類

式

説明

{n}

数値キーを意味する。どんな文字列キーでも数値型のキーでも一致する。

{s}

文字列キーを意味する。数値文字列を含め、どんな文字列でも一致する。

{*}

任意の値と一致する。

Foo

完全に同じ値だった場合のみ一致する。

要素の式はいずれも、すべてのメソッドで使うことができます。特定のメソッドでは、要素の式に加え、 属性で絞り込むこともできます。該当するメソッドは、 extract(), combine(), format(), check(), map(), reduce(), apply(), sort(), insert(), remove() と nest() です。

属性の絞り込み種別

マッチャー

説明

[id]

記述されたキーと一致する要素に絞り込む。

[id=2]

id が 2 となっている要素に絞り込む。

[id!=2]

id が 2 ではない要素に絞り込む。

[id>2]

id が 2 より大きい要素に絞り込む。

[id>=2]

id が 2 以上の要素に絞り込む。

[id<2]

id が 2 より小さい要素に絞り込む。

[id<=2]

id が 2 以下の要素に絞り込む。

[text=/.../]

正規表現 ... と合致する値を持っている要素に絞り込む。

static Cake\Utility\Hash::get(array|\ArrayAccess $data, $path, $default = null)

get() は extract() のシンプル版で、直接的に指定するパス式のみがサポートされます。 {n} 、 {s} 、 {*} 、または、マッチャーを使ったパスはサポートされません。配列から１つの値だけを取り出したい場合に get() を使ってください。もしマッチするパスが見つからない場合、デフォルト値が返ります。

static Cake\Utility\Hash::extract(array|\ArrayAccess $data, $path)

Hash::extract() は Hash パス構文 にあるすべての式とマッチャーをサポートします。extract を使うことで、配列もしくは ArrayAccess インターフェイスを実装したオブジェクトから好きなパスに沿ったデータを手早く取り出すことができます。もはやデータ構造をループする必要はありません。その代わりに欲しい要素を絞り込むパス式を使うのです。

// 普通の使い方:
$users = [
    ['id' => 1, 'name' => 'mark'],
    ['id' => 2, 'name' => 'jane'],
    ['id' => 3, 'name' => 'sally'],
    ['id' => 4, 'name' => 'jose'],
];
$results = Hash::extract($users, '{n}.id');
// $results は以下のとおり:
// [1,2,3,4];

static Cake\Utility\Hash::insert(array $data, $path, $values = null)

$values を $path の定義に従って配列の中に挿入します。

$a = [
    'pages' => ['name' => 'page']
];
$result = Hash::insert($a, 'files', ['name' => 'files']);
// $result は以下のようになります:
[
    [pages] => [
        [name] => page
    ]
    [files] => [
        [name] => files
    ]
]

{n} 、 {s} そして {*} を使ったパスを使うことで、複数のポイントにデータを挿入することができます。

$users = Hash::insert($users, '{n}.new', 'value');

insert() では属性のマッチャーも動きます。

$data = [
    0 => ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
    1 => ['Item' => ['id' => 2, 'title' => 'second']],
    2 => ['Item' => ['id' => 3, 'title' => 'third']],
    3 => ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
    4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
];
$result = Hash::insert($data, '{n}[up].Item[id=4].new', 9);
/* $result は以下のようになります:
    [
        ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
        ['Item' => ['id' => 2, 'title' => 'second']],
        ['Item' => ['id' => 3, 'title' => 'third']],
        ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth', 'new' => 9]],
        ['Item' => ['id' => 5, 'title' => 'fifth']],
    ]
*/

static Cake\Utility\Hash::remove(array $data, $path)

$path に合致するすべての要素を配列から削除します。

$a = [
    'pages' => ['name' => 'page'],
    'files' => ['name' => 'files']
];
$result = Hash::remove($a, 'files');
/* $result は以下のようになります:
    [
        [pages] => [
            [name] => page
        ]

    ]
*/

{n} 、 {s} そして {*} を使うことで、複数の値を一度に削除することができます。また、remove() では属性のマッチャーを使用することもできます。

$data = [
    0 => ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
    1 => ['Item' => ['id' => 2, 'title' => 'second']],
    2 => ['Item' => ['id' => 3, 'title' => 'third']],
    3 => ['clear' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
    4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
];
$result = Hash::remove($data, '{n}[clear].Item[id=4]');
/* $result は以下のようになります:
    [
        ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
        ['Item' => ['id' => 2, 'title' => 'second']],
        ['Item' => ['id' => 3, 'title' => 'third']],
        ['clear' => true],
        ['Item' => ['id' => 5, 'title' => 'fifth']],
    ]
*/

static Cake\Utility\Hash::combine(array $data, $keyPath, $valuePath = null, $groupPath = null)

$keyPath のパスをキー、$valuePath （省略可） のパスを値として使って連想配列を作ります。 $valuePath が省略された場合や、$valuePath に合致するものが無かった場合は、値は null で初期化されます。 $groupPath が指定された場合は、そのパスにしたがって生成したものをグルーピングします。

$a = [
    [
        'User' => [
            'id' => 2,
            'group_id' => 1,
            'Data' => [
                'user' => 'mariano.iglesias',
                'name' => 'Mariano Iglesias'
            ]
        ]
    ],
    [
        'User' => [
            'id' => 14,
            'group_id' => 2,
            'Data' => [
                'user' => 'phpnut',
                'name' => 'Larry E. Masters'
            ]
        ]
    ],
];

$result = Hash::combine($a, '{n}.User.id');
/* $result は以下のようになります:
    [
        [2] =>
        [14] =>
    ]
*/

$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.user');
/* $result は以下のようになります:
    [
        [2] => 'mariano.iglesias'
        [14] => 'phpnut'
    ]
*/

$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data');
/* $result は以下のようになります:
    [
        [2] => [
                [user] => mariano.iglesias
                [name] => Mariano Iglesias
        ]
        [14] => [
                [user] => phpnut
                [name] => Larry E. Masters
        ]
    ]
*/

$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name');
/* $result は以下のようになります:
    [
        [2] => Mariano Iglesias
        [14] => Larry E. Masters
    ]
*/

$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data', '{n}.User.group_id');
/* $result は以下のようになります:
    [
        [1] => [
                [2] => [
                        [user] => mariano.iglesias
                        [name] => Mariano Iglesias
                ]
        ]
        [2] => [
                [14] => [
                        [user] => phpnut
                        [name] => Larry E. Masters
                ]
        ]
    ]
*/

$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.User.group_id');
/* $result は以下のようになります:
    [
        [1] => [
                [2] => Mariano Iglesias
        ]
        [2] => [
                [14] => Larry E. Masters
        ]
    ]
*/

// As of 3.9.0 $keyPath can be null
$result = Hash::combine($a, null, '{n}.User.Data.name');
/* $result now looks like:
    [
        [0] => Mariano Iglesias
        [1] => Larry E. Masters
    ]
*/

$keyPath および $valuePath で配列を指定することができます。これにより、最初の要素で指定した形式に合わせて、その他のパスで指定した値がフォーマットされます。

$result = Hash::combine(
    $a,
    '{n}.User.id',
    ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
    '{n}.User.group_id'
);
/* $result は以下のようになります:
    [
        [1] => [
                [2] => mariano.iglesias: Mariano Iglesias
        ]
        [2] => [
                [14] => phpnut: Larry E. Masters
        ]
    ]
*/

$result = Hash::combine(
    $a,
    ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
    '{n}.User.id'
);
/* $result は以下のようになります:
    [
        [mariano.iglesias: Mariano Iglesias] => 2
        [phpnut: Larry E. Masters] => 14
    ]
*/

static Cake\Utility\Hash::format(array $data, array $paths, $format)

配列から取り出し、フォーマット文字列でフォーマットされた文字列の配列を返します。

$data = [
    [
        'Person' => [
            'first_name' => 'Nate',
            'last_name' => 'Abele',
            'city' => 'Boston',
            'state' => 'MA',
            'something' => '42'
        ]
    ],
    [
        'Person' => [
            'first_name' => 'Larry',
            'last_name' => 'Masters',
            'city' => 'Boondock',
            'state' => 'TN',
            'something' => '{0}'
        ]
    ],
    [
        'Person' => [
            'first_name' => 'Garrett',
            'last_name' => 'Woodworth',
            'city' => 'Venice Beach',
            'state' => 'CA',
            'something' => '{1}'
        ]
    ]
];

$res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%2$d, %1$s');
/*
[
    [0] => 42, Nate
    [1] => 0, Larry
    [2] => 0, Garrett
]
*/

$res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%1$s, %2$d');
/*
[
    [0] => Nate, 42
    [1] => Larry, 0
    [2] => Garrett, 0
]
*/

static Cake\Utility\Hash::contains(array $data, array $needle)

一方のハッシュや配列の中に、もう一方のキーと値が厳密に見てすべて存在しているかを判定します。

$a = [
    0 => ['name' => 'main'],
    1 => ['name' => 'about']
];
$b = [
    0 => ['name' => 'main'],
    1 => ['name' => 'about'],
    2 => ['name' => 'contact'],
    'a' => 'b'
];

$result = Hash::contains($a, $a);
// true
$result = Hash::contains($a, $b);
// false
$result = Hash::contains($b, $a);
// true

static Cake\Utility\Hash::check(array $data, string $path = null)

配列の中に特定のパスがセットされているかをチェックします。

$set = [
    'My Index 1' => ['First' => 'The first item']
];
$result = Hash::check($set, 'My Index 1.First');
// $result == true

$result = Hash::check($set, 'My Index 1');
// $result == true

$set = [
    'My Index 1' => [
        'First' => [
            'Second' => [
                'Third' => [
                    'Fourth' => 'Heavy. Nesting.'
                ]
            ]
        ]
    ]
];
$result = Hash::check($set, 'My Index 1.First.Second');
// $result == true

$result = Hash::check($set, 'My Index 1.First.Second.Third');
// $result == true

$result = Hash::check($set, 'My Index 1.First.Second.Third.Fourth');
// $result == true

$result = Hash::check($set, 'My Index 1.First.Seconds.Third.Fourth');
// $result == false

static Cake\Utility\Hash::filter(array $data, $callback = ['Hash', 'filter'])

配列から空の要素（ただし '0' 以外）を取り除きます。また、カスタム引数 $callback を指定することで配列の要素を抽出することができます。コールバック関数が false を返した場合、その要素は配列から取り除かれます。

$data = [
    '0',
    false,
    true,
    0,
    ['one thing', 'I can tell you', 'is you got to be', false]
];
$res = Hash::filter($data);

/* $res は以下のようになります:
    [
        [0] => 0
        [2] => true
        [3] => 0
        [4] => [
                [0] => one thing
                [1] => I can tell you
                [2] => is you got to be
        ]
    ]
*/

static Cake\Utility\Hash::flatten(array $data, string $separator = '.')

多次元配列を１次元配列へと平坦化します。

$arr = [
    [
        'Post' => ['id' => '1', 'title' => 'First Post'],
        'Author' => ['id' => '1', 'user' => 'Kyle'],
    ],
    [
        'Post' => ['id' => '2', 'title' => 'Second Post'],
        'Author' => ['id' => '3', 'user' => 'Crystal'],
    ],
];
$res = Hash::flatten($arr);
/* $res は以下のようになります:
    [
        [0.Post.id] => 1
        [0.Post.title] => First Post
        [0.Author.id] => 1
        [0.Author.user] => Kyle
        [1.Post.id] => 2
        [1.Post.title] => Second Post
        [1.Author.id] => 3
        [1.Author.user] => Crystal
    ]
*/

static Cake\Utility\Hash::expand(array $data, string $separator = '.')

Hash::flatten() によって前もって平坦化された配列を再構築します。

$data = [
    '0.Post.id' => 1,
    '0.Post.title' => First Post,
    '0.Author.id' => 1,
    '0.Author.user' => Kyle,
    '1.Post.id' => 2,
    '1.Post.title' => Second Post,
    '1.Author.id' => 3,
    '1.Author.user' => Crystal,
];
$res = Hash::expand($data);
/* $res は以下のようになります:
[
    [
        'Post' => ['id' => '1', 'title' => 'First Post'],
        'Author' => ['id' => '1', 'user' => 'Kyle'],
    ],
    [
        'Post' => ['id' => '2', 'title' => 'Second Post'],
        'Author' => ['id' => '3', 'user' => 'Crystal'],
    ],
];
*/

static Cake\Utility\Hash::merge(array $data, array $merge[, array $n])

この関数は PHP の array_merge と array_merge_recursive の両方の機能を持っていると考えることができます。この２つの関数との違いは、一方の配列キーがもう一方に含まれていた場合には (array_merge と違って) 再帰的に動きますが、含まれていなかった場合には (array_merge_recursive と違って) 再帰的には動きません。

注釈

この関数の引数の個数に制限はありません。また、配列以外が引数に指定された場合は配列へとキャストされます。

$array = [
    [
        'id' => '48c2570e-dfa8-4c32-a35e-0d71cbdd56cb',
        'name' => 'mysql raleigh-workshop-08 < 2008-09-05.sql ',
        'description' => 'Importing an sql dump'
    ],
    [
        'id' => '48c257a8-cf7c-4af2-ac2f-114ecbdd56cb',
        'name' => 'pbpaste | grep -i Unpaid | pbcopy',
        'description' => 'Remove all lines that say "Unpaid".',
    ]
];
$arrayB = 4;
$arrayC = [0 => "test array", "cats" => "dogs", "people" => 1267];
$arrayD = ["cats" => "felines", "dog" => "angry"];
$res = Hash::merge($array, $arrayB, $arrayC, $arrayD);

/* $res は以下のようになります:
[
    [0] => [
            [id] => 48c2570e-dfa8-4c32-a35e-0d71cbdd56cb
            [name] => mysql raleigh-workshop-08 < 2008-09-05.sql
            [description] => Importing an sql dump
    ]
    [1] => [
            [id] => 48c257a8-cf7c-4af2-ac2f-114ecbdd56cb
            [name] => pbpaste | grep -i Unpaid | pbcopy
            [description] => Remove all lines that say "Unpaid".
    ]
    [2] => 4
    [3] => test array
    [cats] => felines
    [people] => 1267
    [dog] => angry
]
*/

static Cake\Utility\Hash::numeric(array $data)

配列内のすべての値が数値であるかをチェックします。

$data = ['one'];
$res = Hash::numeric(array_keys($data));
// $res は true

$data = [1 => 'one'];
$res = Hash::numeric($data);
// $res は false

static Cake\Utility\Hash::dimensions(array $data)

配列の次元数を数えます。このメソッドは配列の１つ目の要素だけを見て次元を判定します。

$data = ['one', '2', 'three'];
$result = Hash::dimensions($data);
// $result == 1

$data = ['1' => '1.1', '2', '3'];
$result = Hash::dimensions($data);
// $result == 1

$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => '3.1.1']];
$result = Hash::dimensions($data);
// $result == 2

$data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
$result = Hash::dimensions($data);
// $result == 1

$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
$result = Hash::dimensions($data);
// $result == 2

static Cake\Utility\Hash::maxDimensions(array $data)

dimensions() に似ていますが、このメソッドは配列内にあるもっとも大きな次元数を返します。

$data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
$result = Hash::maxDimensions($data);
// $result == 2

$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
$result = Hash::maxDimensions($data);
// $result == 3

static Cake\Utility\Hash::map(array $data, $path, $function)

$path で抽出し、各要素に $function を割り当て（map）ることで新たな配列を作ります。このメソッドでは式とマッチャーの両方を使うことができます。

// $data のすべての要素に対して noop 関数 $this->noop() を呼びます。
$result = Hash::map($data, "{n}", [$this, 'noop']);

public function noop(array $array)
{
    // 配列に詰めて、結果を返してください。
    return $array;
}

static Cake\Utility\Hash::reduce(array $data, $path, $function)

$path で抽出し、抽出結果を $function で縮小（reduce）することでを単一の値を作ります。このメソッドでは式とマッチャーの両方を使うことができます。

static Cake\Utility\Hash::apply(array $data, $path, $function)

$function を使用して、抽出された値のセットにコールバックを適用します。この関数は第一引数として抽出された値を取得します。

$data = [
    ['date' => '01-01-2016', 'booked' => true],
    ['date' => '01-01-2016', 'booked' => false],
    ['date' => '02-01-2016', 'booked' => true]
];
$result = Hash::apply($data, '{n}[booked=true].date', 'array_count_values');
/* $result は以下のようになります:
    [
        '01-01-2016' => 1,
        '02-01-2016' => 1,
    ]
*/

static Cake\Utility\Hash::sort(array $data, $path, $dir, $type = 'regular')

Hash パス構文 によって、どの次元のどの値によってでもソートすることができます。このメソッドでは式のみがサポートされます。

$a = [
    0 => ['Person' => ['name' => 'Jeff']],
    1 => ['Shirt' => ['color' => 'black']]
];
$result = Hash::sort($a, '{n}.Person.name', 'asc');
/* $result は以下のようになります:
    [
        [0] => [
                [Shirt] => [
                        [color] => black
                ]
        ]
        [1] => [
                [Person] => [
                        [name] => Jeff
                ]
        ]
    ]
*/

$dir には asc もしくは desc を指定することができます。 $type には次のいずれかを指定することができます。

regular : 通常のソート。

numeric : 数値とみなしてソート。

string : 文字列としてソート。

natural : ヒューマン・フレンドリー・ソート。例えば、 foo10 が foo2 の下に配置される。

static Cake\Utility\Hash::diff(array $data, array $compare)

２つの配列の差分を計算します:

$a = [
    0 => ['name' => 'main'],
    1 => ['name' => 'about']
];
$b = [
    0 => ['name' => 'main'],
    1 => ['name' => 'about'],
    2 => ['name' => 'contact']
];

$result = Hash::diff($a, $b);
/* $result は以下のようになります:
    [
        [2] => [
                [name] => contact
        ]
    ]
*/

static Cake\Utility\Hash::mergeDiff(array $data, array $compare)

この関数は２つの配列をマージし、差分は、その結果の配列の下部に push します。

例１

$array1 = ['ModelOne' => ['id' => 1001, 'field_one' => 'a1.m1.f1', 'field_two' => 'a1.m1.f2']];
$array2 = ['ModelOne' => ['id' => 1003, 'field_one' => 'a3.m1.f1', 'field_two' => 'a3.m1.f2', 'field_three' => 'a3.m1.f3']];
$res = Hash::mergeDiff($array1, $array2);

/* $res は以下のようになります:
    [
        [ModelOne] => [
                [id] => 1001
                [field_one] => a1.m1.f1
                [field_two] => a1.m1.f2
                [field_three] => a3.m1.f3
            ]
    ]
*/

例２

$array1 = ["a" => "b", 1 => 20938, "c" => "string"];
$array2 = ["b" => "b", 3 => 238, "c" => "string", ["extra_field"]];
$res = Hash::mergeDiff($array1, $array2);
/* $res は以下のようになります:
    [
        [a] => b
        [1] => 20938
        [c] => string
        [b] => b
        [3] => 238
        [4] => [
                [0] => extra_field
        ]
    ]
*/

static Cake\Utility\Hash::normalize(array $data, $assoc = true)

配列を正規化します。 $assoc が true なら、連想配列へと正規化された配列が返ります。値を持つ数値キーは null を持つ文字列キーへと変換されます。配列を正規化すると、 Hash::merge() で扱いやすくなります。

$a = ['Tree', 'CounterCache',
    'Upload' => [
        'folder' => 'products',
        'fields' => ['image_1_id', 'image_2_id']
    ]
];
$result = Hash::normalize($a);
/* $result は以下のようになります:
    [
        [Tree] => null
        [CounterCache] => null
        [Upload] => [
                [folder] => products
                [fields] => [
                        [0] => image_1_id
                        [1] => image_2_id
                ]
        ]
    ]
*/

$b = [
    'Cacheable' => ['enabled' => false],
    'Limit',
    'Bindable',
    'Validator',
    'Transactional'
];
$result = Hash::normalize($b);
/* $result は以下のようになります:
    [
        [Cacheable] => [
                [enabled] => false
        ]

        [Limit] => null
        [Bindable] => null
        [Validator] => null
        [Transactional] => null
    ]
*/

static Cake\Utility\Hash::nest(array $data, array $options = [])

平坦な配列から、多次元配列もしくはスレッド状（threaded）の構造化データを生成します。

オプション:

children : 子の配列のために使われる戻り値のキー名。デフォルトは 'children'。

idPath : 各要素を識別するためのキーを指すパス。 Hash::extract() と同様に指定する。デフォルトは {n}.$alias.id

parentPath : 各要素の親を識別するためのキーを指すパス。 Hash::extract() と同様に指定する。デフォルトは {n}.$alias.parent_id

root : 最上位となる要素の id 。

次の配列データを使用した例:

$data = [
    ['ThreadPost' => ['id' => 1, 'parent_id' => null]],
    ['ThreadPost' => ['id' => 2, 'parent_id' => 1]],
    ['ThreadPost' => ['id' => 3, 'parent_id' => 1]],
    ['ThreadPost' => ['id' => 4, 'parent_id' => 1]],
    ['ThreadPost' => ['id' => 5, 'parent_id' => 1]],
    ['ThreadPost' => ['id' => 6, 'parent_id' => null]],
    ['ThreadPost' => ['id' => 7, 'parent_id' => 6]],
    ['ThreadPost' => ['id' => 8, 'parent_id' => 6]],
    ['ThreadPost' => ['id' => 9, 'parent_id' => 6]],
    ['ThreadPost' => ['id' => 10, 'parent_id' => 6]]
];

$result = Hash::nest($data, ['root' => 6]);
/* $result は以下のようになります:
    [
        (int) 0 => [
            'ThreadPost' => [
                'id' => (int) 6,
                'parent_id' => null
            ],
            'children' => [
                (int) 0 => [
                    'ThreadPost' => [
                        'id' => (int) 7,
                        'parent_id' => (int) 6
                    ],
                    'children' => []
                ],
                (int) 1 => [
                    'ThreadPost' => [
                        'id' => (int) 8,
                        'parent_id' => (int) 6
                    ],
                    'children' => []
                ],
                (int) 2 => [
                    'ThreadPost' => [
                        'id' => (int) 9,
                        'parent_id' => (int) 6
                    ],
                    'children' => []
                ],
                (int) 3 => [
                    'ThreadPost' => [
                        'id' => (int) 10,
                        'parent_id' => (int) 6
                    ],
                    'children' => []
                ]
            ]
        ]
    ]
    */

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Http Client

class Cake\Http\Client(mixed $config = [])

CakePHP includes a PSR-18 compliant HTTP client which can be used for making requests. It is a great way to communicate with webservices, and remote APIs.

CakePHP には、リクエストの実行に使用できる基本的ながら強力な HTTP クライアントが含まれています。これは、ウェブサービスや、リモート API と通信するための素晴らしい方法です。

バージョン 3.3.0 で変更: 3.3.0 より前なら、 Cake\Network\Http\Client を使用してください。

リクエストの実行

リクエストの実行は、シンプルで簡単です。 GET リクエストは、次のようになります。

use Cake\Http\Client;

$http = new Client();

// 単純な GET
$response = $http->get('http://example.com/test.html');

// クエリー文字列を使用した単純な GET
$response = $http->get('http://example.com/search', ['q' => 'widget']);

// クエリー文字列と追加ヘッダーを使用した単純な GET
$response = $http->get('http://example.com/search', ['q' => 'widget'], [
  'headers' => ['X-Requested-With' => 'XMLHttpRequest']
]);

POST や PUT のリクエストを実行することは、同様に簡単です。

// application/x-www-form-urlencoded エンコードデータを POST リクエストで送信
$http = new Client();
$response = $http->post('http://example.com/posts/add', [
  'title' => 'testing',
  'body' => 'content in the post'
]);

// application/x-www-form-urlencoded エンコードデータを PUT リクエストで送信
$response = $http->put('http://example.com/posts/add', [
  'title' => 'testing',
  'body' => 'content in the post'
]);

// 他のメソッドも同様に、
$http->delete(...);
$http->head(...);
$http->patch(...);

If you have created a PSR-7 request object you can send it using sendRequest():

use Cake\Http\Client;
use Cake\Http\Client\Request as ClientRequest;

$request = new ClientRequest(
    'http://example.com/search',
    ClientRequest::METHOD_GET
);
$client = new Client();
$response = $client->sendRequest($request);

ファイルを使用したマルチパートリクエストの作成

配列の中にファイルハンドルを含めることで、リクエストのボディー内にファイルを含めることができます。

$http = new Client();
$response = $http->post('http://example.com/api', [
  'image' => fopen('/path/to/a/file', 'r'),
]);

ファイルハンドルは、最後まで読まれます。それが読まれる前に巻き戻されることはありません。

警告

互換性の理由から、 @ で始まる文字列は、ローカルまたはリモートのファイルパスとして評価されます。

この機能は、CakePHP 3.0.5 からは非推奨で、将来のバージョンで削除されます。それまでは、HTTP クライアントに渡されるユーザーデータは、次のようにサニタイズする必要があります。

$response = $http->post('http://example.com/api', [
    'search' => ltrim($this->request->getData('search'), '@'),
]);

クエリー文字列の先頭の @ 文字を維持する必要がある場合は、 http_build_query() で予めエンコードされたクエリー文字列を渡すことができます。

$response = $http->post('http://example.com/api', http_build_query([
    'search' => $this->request->getData('search'),
]));

手動でマルチパートリクエストのボディーを構築

非常に特殊な方法でリクエストボディーを構築しなければならない場合もあるかもしれません。このような状況では、多くの場合、あなたが望んだ特殊なマルチパートの HTTP リクエストを作るために Cake\Http\Client\FormData を使用することができます。

use Cake\Http\Client\FormData;

$data = new FormData();

// XML 部分を作成
$xml = $data->newPart('xml', $xmlString);
// コンテンツタイプを設定
$xml->type('application/xml');
$data->add($xml);

// addFile() でファイルアップロードの作成
// 同様にフォームデータにファイルを追加します。
$file = $data->addFile('upload', fopen('/some/file.txt', 'r'));
$file->contentId('abc123');
$file->disposition('attachment');

// リクエストの送信
$response = $http->post(
    'http://example.com/api',
    (string)$data,
    ['headers' => ['Content-Type' => $data->contentType()]]
);

リクエストボディーを送信

REST API を扱うとき、多くの場合、フォームエンコードされていないリクエストボディーを送信する必要があります。 Http\Client は、type オプションを介してこれを公開します。

// JSON リクエストボディーの送信
$http = new Client();
$response = $http->post(
  'http://example.com/tasks',
  json_encode($data),
  ['type' => 'json']
);

type キーは「json」、「xml」または完全な MIME タイプのいずれかになります。 type オプションを使用するときは、文字列としてデータを提供してください。クエリー文字列パラメーターとリクエストボディーの両方を必要とする GET リクエストを行う場合は、次の操作で行うことができます。

// クエリー文字列パラメーター付きの GET リクエストで JSON ボディーを送信
$http = new Client();
$response = $http->get(
  'http://example.com/tasks',
  ['q' => 'test', '_content' => json_encode($data)],
  ['type' => 'json']
);

リクエストメソッドのオプション

各 HTTP メソッドは、追加のリクエスト情報を提供するための $options パラメーターを受け取ります。以下のキーが $options で使用することができます。

headers - 追加ヘッダーの配列。

cookie - 使用するクッキーの配列。

proxy - プロキシー情報の配列。

auth - 認証データの配列、 type キーが認証ストラテジーに委譲するために使用されます。デフォルトでは、Basic 認証が使用されます。

ssl_verify_peer - デフォルトは true 。SSL 証明書の検証を無効にするには false を設定します（推奨されません）。

ssl_verify_peer_name - デフォルトは true 。SSL 証明書を検証する場合、ホスト名検証を無効にするには false を設定します（推奨されません）。

ssl_verify_depth - デフォルトは 5 。CA チェーンを通過する深さ。

ssl_verify_host - デフォルトは true 。ホスト名に対して SSL 証明書を検証します。

ssl_cafile - デフォルトは組み込みの cafile 。カスタム CA バンドルを使用するためには上書きしてください。

timeout - 秒単位でタイムアウトするまで待つ時間。

type - 独自のコンテンツタイプでリクエストボディーを送信します。 $data を文字列にするか、 GET リクエストで _content オプションを指定する必要があります。

redirect - フォローするリダイレクトの数。デフォルトは false です。

オプションのパラメーターは、いつも HTTP メソッドの 3 番目のパラメーターです。スコープ指定クライアント を作成するために Client を構築する場合にも使用できます。

認証

Cake\Http\Client は、いくつかの異なる認証システムをサポートしています。異なる認証ストラテジーを、開発者によって追加することができます。認証ストラテジーは、リクエストが送信される前に呼び出され、リクエストの文脈にヘッダーを追加することができます。

Basic 認証の使用

Basic 認証の例:

$http = new Client();
$response = $http->get('http://example.com/profile/1', [], [
  'auth' => ['username' => 'mark', 'password' => 'secret']
]);

デフォルトでは、 auth オプションに 'type' キーが存在しない場合、 Cake\Http\Client は Basic 認証を使用します。

ダイジェスト認証の使用

Basic 認証の例:

$http = new Client();
$response = $http->get('http://example.com/profile/1', [], [
  'auth' => [
    'type' => 'digest',
    'username' => 'mark',
    'password' => 'secret',
    'realm' => 'myrealm',
    'nonce' => 'onetimevalue',
    'qop' => 1,
    'opaque' => 'someval'
  ]
]);

'type' キーに 'digest' を設定することによって、認証サブシステムにダイジェスト認証を使用することを伝えます。

OAuth 1 認証

多くのモダンなウェブサービスは、API にアクセスするために OAuth 認証を必要とします。含まれる OAuth 認証は、すでにコンシューマキーとコンシューマシークレットがあることを前提としています。

$http = new Client();
$response = $http->get('http://example.com/profile/1', [], [
  'auth' => [
    'type' => 'oauth',
    'consumerKey' => 'bigkey',
    'consumerSecret' => 'secret',
    'token' => '...',
    'tokenSecret' => '...',
    'realm' => 'tickets',
  ]
]);

OAuth 2 認証

OAuth2 は、多くの場合、単一のヘッダーであるため、特殊な認証アダプターがありません。代わりに、アクセストークンを使用してクライアントを作成することができます。

$http = new Client([
    'headers' => ['Authorization' => 'Bearer ' . $accessToken]
]);
$response = $http->get('https://example.com/api/profile/1');

プロキシー認証

いくつかのプロキシーは使用するために認証を必要とします。一般に、この認証は Basic ですが、任意の認証アダプターによって実装することができます。デフォルトでは、 type キーが設定されていない限り、 Http\Client は Basic 認証を前提としています。

$http = new Client();
$response = $http->get('http://example.com/test.php', [], [
  'proxy' => [
    'username' => 'mark',
    'password' => 'testing',
    'proxy' => '127.0.0.1:8080',
  ]
]);

２番目のプロキシーパラメーターは、プロトコルのない IP またはドメインの文字列でなければなりません。ユーザー名とパスワードは、ヘッダー通じて渡されますが、プロキシー文字列は stream_context_create() [https://php.net/manual/ja/function.stream-context-create.php] を通じて渡されます。

スコープ指定クライアントの作成

ドメイン名を再入力すると、認証とプロキシーの設定が面倒になり、エラーが発生しやすくなります。間違いの可能性を減らし、いくつかの退屈さを緩和するために、スコープ指定クライアントを作成することができます。

// スコープ指定クライアントの作成
$http = new Client([
  'host' => 'api.example.com',
  'scheme' => 'https',
  'auth' => ['username' => 'mark', 'password' => 'testing']
]);

// api.example.com にリクエストします
$response = $http->get('/test.php');

スコープ指定クライアントを作成する場合、以下の情報を使用することができます。

host

basePath

scheme

proxy

auth

port

cookies

timeout

ssl_verify_peer

ssl_verify_depth

ssl_verify_host

リクエスト実行時に、これらのオプションのいずれかを指定することで上書きすることができます。リクエスト URL 中のホスト、スキーム、プロキシー、ポートが上書きされます。

// 先ほど作成したスコープ指定クライアントの使用
$response = $http->get('http://foo.com/test.php');

上記は、ドメインやスキーム、ポートが置き換えられます。ただし、このリクエストは、スコープ指定クライアントの作成時に定義された他のすべてのオプションを使用して引き続き行われます。対応するオプションの詳細については リクエストメソッドのオプション をご覧ください。

クッキーの設定と管理

Http\Client はまた、リクエストを行うときクッキーを受け入れることができます。クッキーを受け入れることに加えて、レスポンス中に自動的に設定された有効なクッキーを格納します。クッキーを持つ任意のレスポンスが、元の Http\Client のインスタンスに格納されています。 Client インスタンスに格納されているクッキーは、それ以後のドメイン+パスの組み合わせが一致するリクエストに自動的に含まれます。

$http = new Client([
    'host' => 'cakephp.org'
]);

// いくつかのクッキーをセットしたリクエストを実行
$response = $http->get('/');

// デフォルトで、初めのリクエストのクッキーが
// 含まれます。
$response2 = $http->get('/changelogs');

リクエストの $options パラメーターの中に設定することにより、自動で含まれるクッキーをいつでも上書きすることができます。

// 格納されたクッキーを独自の値に置き換えます。
$response = $http->get('/changelogs', [], [
    'cookies' => ['sessionid' => '123abc']
]);

addCookie() メソッドを使って、作成された後のクライアントにクッキーオブジェクトを追加することができます。

use Cake\Http\Cookie\Cookie;

$http = new Client([
    'host' => 'cakephp.org'
]);
$http->addCookie(new Cookie('session', 'abc123'));

レスポンスオブジェクト

class Cake\Http\Client\Response

Response オブジェクトは、レスポンスデータを検査するための多くのメソッドを持ちます。

バージョン 3.3.0 で変更: 3.3.0 では、 Cake\Http\Client\Response は PSR-7 ResponseInterface [https://www.php-fig.org/psr/psr-7/#3-3-psr-http-message-responseinterface] を実装します。

レスポンスボディーの読み込み

文字列としてレスポンスボディー全体を読み込みます。

// 文字列としてレスポンス全体を読み込み
$response->getStringBody();

また、レスポンスのストリームオブジェクトにアクセスし、そのメソッドを使用することができます。

// レスポンスボディーを含む Psr\Http\Message\StreamInterface を取得
$stream = $response->getBody();

// ストリームを一度に 100 バイト読み込み
while (!$stream->eof()) {
    echo $stream->read(100);
}

JSON や XML レスポンスボディーの読み込み

JSON や XML のレスポンスが一般的に使用されているので、レスポンスオブジェクトは、デコードされたデータを読み取るためにアクセサーを簡単に使用することができます。 JSON は配列にデコードされ、XML データは、 SimpleXMLElement ツリーにデコードされます。

// 何らかの XML を取得
$http = new Client();
$response = $http->get('http://example.com/test.xml');
$xml = $response->getXml();

// 何らかの JSON を取得
$http = new Client();
$response = $http->get('http://example.com/test.json');
$json = $response->getJson();

デコードされたレスポンスデータはそれを複数回アクセスし、レスポンスオブジェクトに格納されても、何も追加コストはかかりません。

レスポンスヘッダーへのアクセス

いくつかの異なるメソッドを介してヘッダーにアクセスすることができます。メソッドを介してアクセスする際に、ヘッダー名は常に大文字と小文字を区別しない値として扱われます。

// 連想配列として全てのヘッダーを取得
$response->getHeaders();

// 配列として単一のヘッダーを取得
$response->getHeader('content-type');

// 文字列としてヘッダーを取得
$response->getHeaderLine('content-type');

// レスポンスのエンコーディングを取得
$response->getEncoding();

// 全てのヘッダーの key=>value の配列を取得
$response->headers;

クッキーデータへのアクセス

クッキーについて必要なデータ量に応じて、いくつかの異なる方法でクッキーを読むことができます。

// 全てのクッキー (全データ) を取得
$response->getCookies();

// 単一のクッキーの値を取得
$response->getCookie('session_id');

// 単一のクッキーの完全なデータを取得
// value, expires, path, httponly, secure キーを含みます
$response->getCookieData('session_id');

// 全てのクッキーの完全なデータにアクセス
$response->cookies;

ステータスコードの確認

レスポンスオブジェクトは、ステータスコードを確認するためのいくつかのメソッドを提供します。

// レスポンスが 20x だった
$response->isOk();

// レスポンスが 30x だった
$response->isRedirect();

// ステータスコードの取得
$response->getStatusCode();

// __get() ヘルパー
$response->code;

Changing Transport Adapters

By default Http\Client will prefer using a curl based transport adapter. If the curl extension is not available a stream based adapter will be used instead. You can force select a transport adapter using a constructor option:

use Cake\Http\Client\Adapter\Stream;

$client = new Client(['adapter' => Stream::class]);

Testing

trait Cake\Http\TestSuite\HttpClientTrait

In tests you will often want to create mock responses to external APIs. You can use the HttpClientTrait to define responses to the requests your application is making:

use Cake\Http\TestSuite\HttpClientTrait;
use Cake\TestSuite\TestCase;

class CartControllerTests extends TestCase
{
    use HttpClientTrait;

    public function testCheckout()
    {
        // Mock a POST request that will be made.
        $this->mockClientPost(
            'https://example.com/process-payment',
            $this->newClientResponse(200, [], json_encode(['ok' => true]))
        );
        $this->post("/cart/checkout");
        // Do assertions.
    }
}

There are methods to mock the most commonly used HTTP methods:

$this->mockClientGet(...);
$this->mockClientPatch(...);
$this->mockClientPost(...);
$this->mockClientPut(...);
$this->mockClientDelete(...);

Cake\Http\TestSuite\HttpClientTrait::newClientResponse(int $code = 200, array $headers = [], string $body = '')

As seen above you can use the newClientResponse() method to create responses for the requests your application will make. The headers need to be a list of strings:

$headers = [
    'Content-Type: application/json',
    'Connection: close',
];
$response = $this->newClientResponse(200, $headers, $body)

バージョン 4.3.0 で追加.

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Inflector

class Inflector

Inflector は文字列の複数形やキャメルケースへの変換を取り扱うクラスです。 Inflector のメソッドは通常では静的にアクセスします。例: Inflector::pluralize('example') は "examples" を返します。

inflector.cakephp.org [https://inflector.cakephp.org/] にてオンライン上で変換を試すことができます。

Inflector メソッドの概要と出力

Inflector の組み込みメソッドの簡単な概要と、複数単語の引数を指定したときに出力される結果:

メソッド

引数

出力

pluralize()

BigApple

BigApples

big_apple

big_apples

singularize()

BigApples

BigApple

big_apples

big_apple

camelize()

big_apples

BigApples

big apple

BigApple

underscore()

BigApples

big_apples

Big Apples

big apples

humanize()

big_apples

Big Apples

bigApple

BigApple

classify()

big_apples

BigApple

big apple

BigApple

dasherize()

BigApples

big-apples

big apple

big apple

tableize()

BigApple

big_apples

Big Apple

big apples

variable()

big_apple

bigApple

big apples

bigApples

複数形と単数形の作成

static Inflector::singularize($singular)

static Inflector::pluralize($singular)

pluralize や singularize() の両方は、多くの英語名詞に作用します。もし、他の言語の対応が必要な場合、 使用するルールをカスタマイズするために Inflection の設定 を使用することができます。

// Apples
echo Inflector::pluralize('Apple');

注釈

pluralize() は、すでに複数形の名詞をいつも正しく変換できるわけではありません。

// Person
echo Inflector::singularize('People');

注釈

singularize() は、すでに単数形の名詞をいつも正しく変換できるわけではありません。

キャメルケースやアンダースコアーの作成

static Inflector::camelize($underscored)

static Inflector::underscore($camelCase)

これらのメソッドは、クラス名やプロパティー名を作成する時便利です。

// ApplePie
Inflector::camelize('Apple_pie')

// apple_pie
Inflector::underscore('ApplePie');

underscore メソッドは、 キャメルケース形式の単語のみ変換することに注意してください。スペースを含む単語は、小文字になりますが、アンダースコアーは含まれません。

人間が読みやすい形式の作成

static Inflector::humanize($underscored)

このメソッドは、アンダースコアー形式を人間が読みやすい値「タイトルケース」形式に変換する時に便利です。

// Apple Pie
Inflector::humanize('apple_pie');

テーブル名やクラス名の作成

static Inflector::classify($underscored)

static Inflector::dasherize($dashed)

static Inflector::tableize($camelCase)

コードの生成や CakePHP の規約を使用する時、テーブル名やクラス名に加工するために必要になります。

// UserProfileSetting
Inflector::classify('user_profile_settings');

// user-profile-setting
Inflector::dasherize('UserProfileSetting');

// user_profile_settings
Inflector::tableize('UserProfileSetting');

変数名の作成

static Inflector::variable($underscored)

規約をもとにしたコード生成や仕事をするのに必要なメタプログログラミングの作業を行う時に、変数名はしばしば役に立ちます。

// applePie
Inflector::variable('apple_pie');

Inflection の設定

CakePHP の命名規則は、本当に良くなります。あなたはデータベーステーブル big_boxes と名付け、モデルを BigBoxes 、コントローラーを BigBoxesController 、そして、すべては、ただ自動的に一緒に動作します。CakePHP は、単数形と複数形の間で単語を加工することにより、お互いを紐付ける方法を知っています。

(特に英語を話さない友人にとって) CakePHP の inflector (複数形、単数形、キャメルケース、アンダースコアーに変換するクラス) があなたの希望通りの動作をしないような、状況に陥るかもしれません。もし、CakePHP に Foci や Fish を認識させたくない場合、 CakePHP に特別なケースを伝えることができます。

カスタム Inflection のロード

static Inflector::rules($type, $rules, $reset = false)

Inflector で使用する新しい変換・翻訳の規則を定義します。しばしば、このメソッドは、 config/bootstrap.php 内で使用されます。

Inflector::rules('singular', ['/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta']);
Inflector::rules('uninflected', ['singulars']);
Inflector::rules('irregular', ['phylum' => 'phyla']); // キーは単数形、値は複数形

与えられたルールは、 Cake/Utility/Inflector で定義された各変換セットの中にマージされます。コアのルールよりも追加されたルールが優先されます。ルールをクリアして Inflector の元の状態に戻すために Inflector::reset() が使用できます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Number

class Cake\I18n\Number

あなたが View の外で NumberHelper 機能が必要な場合、 Number クラスを次のように使います。

namespace App\Controller;

use Cake\I18n\Number;

class UsersController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Auth');
    }

    public function afterLogin()
    {
        $storageUsed = $this->Auth->user('storage_used');
        if ($storageUsed > 5000000) {
            // 割り当てられた users に通知
            $this->Flash->success(__('あなたは {0} ストレージを使用しています', Number::toReadableSize($storageUsed)));
        }
    }
}

以下の全ての機能は、整形された数値を返します。これらは自動的にビューに出力を表示しません。

通貨フォーマット

Cake\I18n\Number::currency(mixed $value, string $currency = null, array $options = [])

このメソッドは、共通通貨フォーマット（ユーロ、英ポンド、米ドル）で数値を表示するために使用されます。ビュー内で次のように使います。

// NumberHelper としてコール
echo $this->Number->currency($value, $currency);

// Number としてコール
echo Number::currency($value, $currency);

1つ目のパラメーター $value は、合計金額をあらわす浮動小数点数でなければいけません。 2つ目のパラメーターは、あらかじめ定義された通貨フォーマット方式を選択するための文字列です。

$currency

通貨の種類によってフォーマットされた 1234.56

EUR

€1.234,56

GBP

£1,234.56

USD

$1,234.56

3つ目のパラメーターは、出力を定義するためのオプションの配列です。次のオプションが用意されています。

オプション

説明

before

レンダリングされた数値の前に表示されるテキスト。

after

レンダリングされた数値の後に表示されるテキスト。

zero

ゼロ値の場合に使用するテキストで、文字列か数値で指定できます。例. 0, '無料!'

places

小数点以下の桁数を指定します。例. 2

precision

小数点以下の最大桁数を指定します。例. 2

locale

数値フォーマットに使用するロケール名。例. "fr_FR".

fractionSymbol

小数に使用する文字列。例. ' cents'

fractionPosition

fractionSymbol で指定した文字列を、小数の 'before' または 'after' のどちらに配置するか。

pattern

数値のフォーマットに使用する ICU 数値パターン。例. #,###.00

useIntlCode

通貨記号を国際通貨コードに置き換えるために true を指定する。

$currency の値が null の場合、デフォルト通貨は Cake\I18n\Number::defaultCurrency() によって設定されます。

デフォルト通貨の設定

Cake\I18n\Number::defaultCurrency($currency)

デフォルト通貨のための setter/getter です。これによって、常に Cake\I18n\Number::currency() に通貨を渡したり、他のデフォルトを設定することによって全ての通貨の出力を変更したりする必要がなくなります。 $currency に false が設定された場合、現在格納されている値をクリアします。デフォルトでは、設定されていれば intl.default_locale を取得し、そうでない場合は 'en_US' を設定します。

浮動小数点数フォーマット

Cake\I18n\Number::precision(float $value, int $precision = 3, array $options = [])

このメソッドは指定された精度(小数点以下)で数値を表示します。定義された精度のレベルを維持するために丸めます。

// NumberHelper としてコール
echo $this->Number->precision(456.91873645, 2);

// 出力
456.92

// Number としてコール
echo Number::precision(456.91873645, 2);

パーセンテージフォーマット

Cake\I18n\Number::toPercentage(mixed $value, int $precision = 2, array $options = [])

オプション

説明

multiply

値を 100 で乗算しなければならないかどうかを示す Boolean 値です。少数のパーセンテージに便利です。

このメソッドは Cake\I18n\Number::precision() のように、与えられた精度に応じて(精度を満たすように丸めて)数値をフォーマットします。このメソッドはパーセンテージとして数値を表現し、パーセント記号を追加して出力します。

// NumberHelper としてコール。 出力: 45.69%
echo $this->Number->toPercentage(45.691873645);

// Number としてコール。 出力: 45.69%
echo Number::toPercentage(45.691873645);

// multiply オプションとともにコール。 出力: 45.7%
echo Number::toPercentage(0.45691, 1, [
    'multiply' => true
]);

人が読める形式の値との相互作用

Cake\I18n\Number::toReadableSize(string $size)

このメソッドはデータサイズを人が読める形式に整形します。これは、バイト数を KB、MB、GB、および TB へ変換するための近道を提供します。サイズは、データのサイズに応じて小数点以下二桁の精度で表示されます。(例 大きいサイズの表現):

// NumberHelper としてコール
echo $this->Number->toReadableSize(0); // 0 Byte
echo $this->Number->toReadableSize(1024); // 1 KB
echo $this->Number->toReadableSize(1321205.76); // 1.26 MB
echo $this->Number->toReadableSize(5368709120); // 5 GB

// Number としてコール
echo Number::toReadableSize(0); // 0 Byte
echo Number::toReadableSize(1024); // 1 KB
echo Number::toReadableSize(1321205.76); // 1.26 MB
echo Number::toReadableSize(5368709120); // 5 GB

数字の整形

Cake\I18n\Number::format(mixed $value, array $options = [])

このメソッドは、ビューで使うための数値の整形をより制御しやすくします。 (および、メインのメソッドとして、NumberHelper のその他のほとんどのメソッドから使用されます。) このメソッドは以下のように使用します。

// NumberHelper としてコール
$this->Number->format($value, $options);

// Number としてコール
Number::format($value, $options);

$value パラメーターは、出力のために整形しようとしている数値です。 $options が未指定の場合、1236.334 という数値は 1,236 として出力されるでしょう。デフォルトの制度は1の位であることに注意してください。

$options パラメーターはこのメソッドに存在している手品のタネの在りかです。

もし整数を渡した場合、精度もしくは小数点以下の桁数になります。

もし連想配列を渡した場合、以下のキーが使用できます。

オプション

説明

places

小数点以下の桁数を指定します。例. 2

precision

小数点以下の最大桁数を指定します。例. 2

pattern

数値のフォーマットに使用する ICU 数値パターン。例. #,###.00

locale

数値フォーマットに使用するロケール名。例. "fr_FR".

before

レンダリングされた数値の前に表示されるテキスト。

after

レンダリングされた数値の後に表示されるテキスト。

例:

// NumberHelper としてコール
echo $this->Number->format('123456.7890', [
    'places' => 2,
    'before' => '¥ ',
    'after' => ' !'
]);
// 出力 '¥ 123,456.79 !'

echo $this->Number->format('123456.7890', [
    'locale' => 'fr_FR'
]);
// 出力 '123 456,79 !'

// Number としてコール
echo Number::format('123456.7890', [
    'places' => 2,
    'before' => '¥ ',
    'after' => ' !'
]);
// 出力 '¥ 123,456.79 !'

echo Number::format('123456.7890', [
    'locale' => 'fr_FR'
]);
// 出力 '123 456,79 !'

Cake\I18n\Number::ordinal(mixed $value, array $options = [])

このメソッドは序数を出力します。

例:

echo Number::ordinal(1);
// 出力 '1st'

echo Number::ordinal(2);
// 出力 '2nd'

echo Number::ordinal(2, [
    'locale' => 'fr_FR'
]);
// 出力 '2e'

echo Number::ordinal(410);
// 出力 '410th'

差分フォーマット

Cake\I18n\Number::formatDelta(mixed $value, array $options = [])

このメソッドは、符号付きの数として値の差分を表示します。

// NumberHelper としてコール
$this->Number->formatDelta($value, $options);

// Number としてコール
Number::formatDelta($value, $options);

$value パラメーターは、出力のために整形しようとしている数値です。 $options が未指定の場合、1236.334 という数値は 1,236 として出力されるでしょう。デフォルトの制度は1の位であることに注意してください。

$options パラメーターは Number::format() と同じキーを取ります。

オプション

説明

places

小数点以下の桁数を指定します。例. 2

precision

小数点以下の最大桁数を指定します。例. 2

locale

数値フォーマットに使用するロケール名。例. "fr_FR".

before

レンダリングされた数値の前に表示されるテキスト。

after

レンダリングされた数値の後に表示されるテキスト。

例:

// NumberHelper としてコール
echo $this->Number->formatDelta('123456.7890', [
    'places' => 2,
    'before' => '[',
    'after' => ']'
]);
// 出力 '[+123,456.79]'

// Number としてコール
echo Number::formatDelta('123456.7890', [
    'places' => 2,
    'before' => '[',
    'after' => ']'
]);
// 出力 '[+123,456.79]'

フォーマッター設定

Cake\I18n\Number::config(string $locale, int $type = NumberFormatter::DECIMAL, array $options = [])

このメソッドを使用すると、様々なメソッドの呼び出し間で持続的なフォーマッターのデフォルトを設定することができます。

例:

Number::config('en_IN', \NumberFormatter::CURRENCY, [
    'pattern' => '#,##,##0'
]);

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

レジストリーオブジェクト

レジストリークラスは、指定されたオブジェクト型のロードされたインスタンスの、作成および取得する簡単な方法を提供します。コンポーネント、ヘルパー、タスク、およびビヘイビアーのレジストリークラスがあります。

以下の例ではコンポーネントを使用しますが、コンポーネントに加えて、ヘルパー、ビヘイビアー、およびタスクに同じ動作を期待することができます。

オブジェクトのロード

オブジェクトはその場で add<レジストリーオブジェクト>() でロードすることができます。例:

$this->loadComponent('Acl.Acl');
$this->addHelper('Flash')

これは、 Acl プロパティーと Flash ヘルパーをロードしています。設定も同様に、その場で行なうことができます。例:

$this->loadComponent('Cookie', ['name' => 'sweet']);

提供される任意のキーと値は、コンポーネントのコンストラクターに渡されます。このルールの唯一の例外は、 className です。クラス名は、レジストリーのエイリアスオブジェクトに使用される特殊なキーです。これは、クラス名を反映していないコンポーネント名を持つことができ、コアコンポーネントを拡張する際に役立ちます。

$this->Auth = $this->loadComponent('Auth', ['className' => 'MyCustomAuth']);
$this->Auth->user(); // 実態は MyCustomAuth::user();

コールバックトリガー

コールバックは、レジストリーオブジェクトによって提供されていません。アプリケーションのすべてのイベント/コールバックをディスパッチするために events system を使用する必要があります。

コールバックの無効化

以前のバージョンでは、コレクションオブジェクトは、コールバックを受けるオブジェクトを無効にする disable() メソッドを提供していました。現在、これを実現するためには、イベントシステムの機能を使用する必要があります。たとえば、次のようにコンポーネントのコールバックを無効にすることができます。

// Auth コールバックを無効化
$this->getEventManager()->off($this->Auth);

// Auth コールバックを再度有効化
$this->getEventManager()->on($this->Auth);

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Text

class Cake\Utility\Text

Text クラスは文字列を作ったり操作したりする便利なメソッドを持っており、通常は static にアクセスします。例: Text::uuid()

View の外側で Cake\View\Helper\TextHelper の機能が必要なときは Text クラスを使ってください。

namespace App\Controller;

use Cake\Utility\Text;

class UsersController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Auth')
    };

    public function afterLogin()
    {
        $message = $this->Users->find('new_message')->first();
        if (!empty($message)) {
            // ユーザーに新しいメッセージを通知
            $this->Flash->success(__(
                'You have a new message: {0}',
                Text::truncate($message['Message']['body'], 255, ['html' => true])
            ));
        }
    }
}

ASCII 文字への変換

static Cake\Utility\Text::transliterate($string, $transliteratorId = null)

transliterate はデフォルトで、与えられた文字列の文字すべてを同じ意味の ASCII 文字に置き換えます。このメソッドは UTF-8 エンコーディングであることが前提になっています。文字変換はトランスリテレーション識別子で制御することができます。この識別子は引数 $transliteratorId を使って渡すか、 Text::setTransliteratorId() を使ってデフォルトの識別子文字列を変更することができます。 ICU のトランスリテレーション識別子は基本的に <元のスクリプト>:<変換後のスクリプト> の形で、 ; で区切って複数の変換の組み合わせを指定することができます。トランスリテレーション識別子についての詳細はここ [https://unicode-org.github.io/icu/userguide/transforms/general/#transliterator-identifiers] を参照してください。

// apple puree
Text::transliterate('apple purée');

// Ubermensch (ラテン文字だけを変換する)
Text::transliterate('Übérmensch', 'Latin-ASCII;');

URL に安全な文字列の作成

static Cake\Utility\Text::slug($string, $options = [])

slug はすべての文字を ASCII バージョンにトランスリテレートし（別言語の文字に置き換え）、マッチしない文字や空白はダッシュに変換します。 slug メソッドは UTF-8 エンコーディングであること前提にしています。

slug をコントロールするオプション配列を渡すことができます。 $options は文字列で指定することもでき、その場合は置き換え文字列として使われます。利用可能なオプションは次の通りです:

replacement 置き換え文字列。デフォルトは '-' 。

transliteratorId 有効なトランスリテレータIDの文字列。

デフォルトの null なら、Text::$_defaultTransliteratorId が使われます。 false なら、トランスリテレーションは実行されず、非単語のみが削除されます。

preserve 保持したい特定の非単語文字。デフォルトは null 。

たとえば、このオプションに '.' をセットすることでクリーンなファイル名を生成することができます。

// apple-puree
Text::slug('apple purée');

// apple_puree
Text::slug('apple purée', '_');

// foo-bar.tar.gz
Text::slug('foo bar.tar.gz', ['preserve' => '.']);

UUID の生成

static Cake\Utility\Text::uuid

UUID メソッドは RFC 4122 [https://datatracker.ietf.org/doc/html/rfc4122.html] 準拠のユニークな識別子を生成するのに使います。 UUID は 485fc381-e790-47a3-9794-1337c0a8fe68 というフォーマットの 128 ビットの文字列です。

Text::uuid(); // 485fc381-e790-47a3-9794-1337c0a8fe68

単純な文字列のパース

static Cake\Utility\Text::tokenize($data, $separator = ', ', $leftBound = '(', $rightBound = ')')

$separator を使って文字列をトークン化します。その際、 $leftBound と $rightBound の間にある $separator は無視されます。

このメソッドはタグリストのような標準フォーマットを持つデータを分割するのに役立ちます。

$data = "cakephp 'great framework' php";
$result = Text::tokenize($data, ' ', "'", "'");
// 結果
['cakephp', "'great framework'", 'php'];

Cake\Utility\Text::parseFileSize(string $size, $default)

このメソッドは人が読みやすいバイトのサイズのフォーマットから、バイトの整数値へと変換します。

$int = Text::parseFileSize('2GB');

文字列のフォーマット

static Cake\Utility\Text::insert($string, $data, $options = [])

insert メソッドは文字列テンプレートを作り、key/value で置き換えるのに使います。

Text::insert(
    'My name is :name and I am :age years old.',
    ['name' => 'Bob', 'age' => '65']
);
// これを返す: "My name is Bob and I am 65 years old."

static Cake\Utility\Text::cleanInsert($string, $options = [])

$options 内の 'clean' キーに従って、 Text::insert でフォーマットされた文字列を掃除します。デフォルトで method に使われるのは text ですが html も使えます。この機能の目的は、Text::insert で置き換えられなかった、プレースホルダ周辺のすべての空白と不要なマークアップを置き換えることにあります。

options 配列内で下記のオプションを使うことができます。

$options = [
    'clean' => [
        'method' => 'text', // もしくは html
    ],
    'before' => '',
    'after' => ''
];

テキストの改行

static Cake\Utility\Text::wrap($text, $options = [])

テキストのブロックを幅やインデントを指定して改行させます。単語が別の行に分離されないように賢く改行してくれます。

$text = 'This is the song that never ends.';
$result = Text::wrap($text, 22);

// 戻り値
This is the song that
never ends.

オプション配列でどのように改行されるのかを制御できます。利用できるオプションは次の通りです。

width 改行の幅。デフォルトは 72。

wordWrap 単語単位で改行するか。デフォルトは true 。

indent インデントに使う文字。デフォルトは '' 。

indentAt 何行目からテキストのインデントを開始するか。デフォルトは 0 。

static Cake\Utility\Text::wrapBlock($text, $options = [])

生成されたブロックの合計幅が内部的なインデントと同じ幅を確実に超えないようにする必要があるなら、 wrap() の代わりに wrapBlock() を使う必要があります。これは例えばコンソール向けのテキストを生成するのにとても便利です。 wrap() と同じオプションが使えます。

$text = 'This is the song that never ends. This is the song that never ends.';
$result = Text::wrapBlock($text, [
    'width' => 22,
    'indent' => ' → ',
    'indentAt' => 1
]);

// 戻り値
This is the song that
 → never ends. This
 → is the song that
 → never ends.

文字列の一部をハイライトする

Cake\Utility\Text::highlight(string $haystack, string $needle, array $options = [])

$options['format'] で指定された文字列か、デフォルトの文字列を使って $haystack 中の $needle をハイライトします。

オプション:

format string - ハイライトするフレーズに適用する HTML パーツ

html bool - true ならすべての HTML タグを無視して、正確にテキストのみをハイライトするよう保証します。

例:

// TextHelper として呼ぶ
echo $this->Text->highlight(
    $lastSentence,
    '使って',
    ['format' => '<span class="highlight">\1</span>']
);

// Text として呼ぶ
use Cake\Utility\Text;

echo Text::highlight(
    $lastSentence,
    '使って',
    ['format' => '<span class="highlight">\1</span>']
);

出力:

$options['format'] で指定された文字列か、デフォルトの文字列を<span class="highlight">使って</span>
$haystack 中の $needle をハイライトします。

リンク除去

Cake\Utility\Text::stripLinks($text)

渡された $text から HTML リンクを取り除きます。

テキストの切り詰め

Cake\Utility\Text::truncate(string $text, int $length = 100, array $options)

$text が $length より長い場合、このメソッドはそれを $length の長さに切り詰め、 'ellipsis' が定義されているなら末尾にその文字列を追加します。もし 'exact' に false が渡されたなら、 $length を超えた最初の空白で切り詰められます。もし 'html' に true が渡されたなら、HTML タグは尊重され、削除されなくなります。

$options はすべての追加パラメーターを渡すのに使われ、下記のようなキーがデフォルトになっており、すべてが省略可能です。

[
    'ellipsis' => '...',
    'exact' => true,
    'html' => false
]

例:

// TextHelper として呼ぶ
echo $this->Text->truncate(
    'The killer crept forward and tripped on the rug.',
    22,
    [
        'ellipsis' => '...',
        'exact' => false
    ]
);

// Text として呼ぶ
use Cake\Utility\Text;

echo Text::truncate(
    'The killer crept forward and tripped on the rug.',
    22,
    [
        'ellipsis' => '...',
        'exact' => false
    ]
);

出力:

The killer crept...

文字列の末尾を切り詰める

Cake\Utility\Text::tail(string $text, int $length = 100, array $options)

$text が $length より長い場合、このメソッドは先頭から差となる長さの文字列を取り除き、 'ellipsis' が定義されているなら先頭にその文字列を追加します。もし 'exact' に false が渡されたなら、切り詰めが本来発生したであろう場所の前にある最初の空白で切り詰められます。

$options はすべての追加パラメーターを渡すのに使われ、下記のようなキーがデフォルトになっており、すべてが省略可能です。

[
    'ellipsis' => '...',
    'exact' => true
]

例:

$sampleText = 'I packed my bag and in it I put a PSP, a PS3, a TV, ' .
    'a C# program that can divide by zero, death metal t-shirts'

// TextHelper として呼ぶ
echo $this->Text->tail(
    $sampleText,
    70,
    [
        'ellipsis' => '...',
        'exact' => false
    ]
);

// Text として呼ぶ
use Cake\Utility\Text;

echo Text::tail(
    $sampleText,
    70,
    [
        'ellipsis' => '...',
        'exact' => false
    ]
);

出力:

...a TV, a C# program that can divide by zero, death metal t-shirts

抜粋の抽出

Cake\Utility\Text::excerpt(string $haystack, string $needle, integer $radius=100, string $ellipsis="...")

$haystack から、 $needle の前後 $radius で指定された文字数分を含む文字列を抜粋として抽出し、その先頭と末尾に $ellipsis の文字列を追加します。このメソッドは検索結果には特に便利でしょう。クエリー文字列やキーワードを結果の文章中とともに表示することができます。

// TextHelper として呼ぶ
echo $this->Text->excerpt($lastParagraph, 'method', 50, '...');

// Text として呼ぶ
use Cake\Utility\Text;

echo Text::excerpt($lastParagraph, 'method', 50, '...');

出力:

... by $radius, and prefix/suffix with $ellipsis. This method is especially
handy for search results. The query...

配列を文章的なものに変換する

Cake\Utility\Text::toList(array $list, $and='and', $separator=', ')

最後の２要素が 'and' で繋がっている、カンマ区切りのリストを生成します。

$colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];

// TextHelper として呼ぶ
echo $this->Text->toList($colors);

// Text として呼ぶ
use Cake\Utility\Text;

echo Text::toList($colors);

出力:

red, orange, yellow, green, blue, indigo and violet

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

日付と時刻

class Cake\I18n\FrozenTime

TimeHelper の機能を View の外で使いたい場合は、 FrozenTime クラスを利用してください。

use Cake\I18n\FrozenTime;

class UsersController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Auth');
    }

    public function afterLogin()
    {
        $time = new FrozenTime($this->Auth->user('date_of_birth'));
        if ($time->isToday()) {
            / 誕生日祝いのメッセージでユーザーに挨拶
            $this->Flash->success(__('Happy birthday to you...'));
        }
    }
}

内部的には、この FrozenTime ユーティリティを動かすために、CakePHP は Chronos [https://github.com/cakephp/chronos] を利用しています。 Chronos と DateTime でできることはなんでも、 FrozenTime と FrozenDate ですることができます。

Chronos についてより詳しく知りたい場合は API ドキュメント [https://api.cakephp.org/chronos/1.0/] をご覧ください。

Time インスタンスを作成する

FrozenTime インスタンスを作成するにはいくつかの方法があります。

use Cake\I18n\FrozenTime;

// 日時文字列から作成
$time = FrozenTime::createFromFormat(
    'Y-m-d H:i:s',
    '2021-01-31 22:11:30',
    'America/New_York'
);

// タイムスタンプから作成
$time = FrozenTime::createFromTimestamp(1612149090, 'Asia/Tokyo');

// 現在時刻を取得
$time = FrozenTime::now();

// または 'new' を使用して
$time = new FrozenTime('2021-01-31 22:11:30', 'Asia/Tokyo');

$time = new FrozenTime('2 hours ago');

FrozenTime クラスのコンストラクターは、内部の PHP クラスである DateTimeImmutable が受け取れる、あらゆるパラメーターを受け取ることができます。数値や数字文字列を渡したとき、 UNIX タイムスタンプとして解釈されます。

テストケースでは、 setTestNow() を使うことで now() をモックアップできます。

// 時間の固定
$time = new FrozenTime('2021-01-31 22:11:30');
FrozenTime::setTestNow($time);

// 結果は '2021-01-31 22:11:30'
$now = FrozenTime::now();
echo $now->i18nFormat('yyyy-MM-dd HH:mm:ss');

// 結果は '2021-01-31 22:11:30'
$now = FrozenTime::parse('now');
echo $now->i18nFormat('yyyy-MM-dd HH:mm:ss');

操作

FrozenTime インスタンスは、それ自体を変更するのではなく、常にセッターから新しいインスタンスを返すことを忘れないでください。

$time = FrozenTime::now();

// Create and reassign a new instance
$newTime = $time->year(2013)
    ->month(10)
    ->day(31);
// Outputs '2013-10-31 22:11:30'
echo $newTime->i18nFormat('yyyy-MM-dd HH:mm:ss');

PHP のビルトインの DateTime クラスで提供されているメソッドも使用できます。

$time = $time->setDate(2013, 10, 31);

日付はコンポーネントの引き算や足し算で編集できます。

$time->year(2013)
    ->month(10)
    ->day(31);
// Outputs '2021-01-31 22:11:30'
echo $time->i18nFormat('yyyy-MM-dd HH:mm:ss');

コンポーネントの減算と加算により、日付が変更された別のインスタンスを作成できます。

$time = FrozenTime::create(2021, 1, 31, 22, 11, 30);
$newTime = $time->subDays(5)
    ->addHours(-2)
    ->addMonth(1);
// Outputs '2/26/21, 8:11 PM'
echo $newTime;

// Using strtotime strings.
$newTime = $time->modify('+1 month -5 days -2 hours');
// Outputs '2/26/21, 8:11 PM'
echo $newTime;

プロパティにアクセスすることで、日付の内部コンポーネントを取得できます。

$time = FrozenTime::create(2021, 1, 31, 22, 11, 30);
echo $time->year; // 2021
echo $time->month; // 1
echo $time->day; // 31
echo $time->timezoneName; // America/New_York

フォーマットする

static Cake\I18n\FrozenTime::setJsonEncodeFormat($format)

このメソッドは、オブジェクトを json 形式に変換するときに使われるデフォルトのフォーマットをセットします。

Time::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss');  // 可変の DataTime 用
FrozenTime::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss');  // 不変の DateTime 用
Date::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss');  // 可変の Date 用
FrozenDate::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss');  // 不変の Date 用

$time = FrozenTime::parse('2021-01-31 22:11:30');
echo json_encode($time);   // Outputs '2021-01-31 22:11:30'

// Added in 4.1.0
FrozenDate::setJsonEncodeFormat(static function($time) {
    return $time->format(DATE_ATOM);
});

注釈

このメソッドは静的に呼び出されなくてはなりません。

バージョン 4.1.0 で変更: callable パラメータタイプが追加されました。

Cake\I18n\FrozenTime::i18nFormat($format = null, $timezone = null, $locale = null)

Time インスタンスで行うごく一般的なことは、フォーマットされたデータを出力することです。 CakePHP は snap を作成します。

$now = Time::parse('2014-10-31');

// 地域化された日時のスタンプを出力します。
echo $now;

// en-US ロケールでは '10/31/14, 12:00 AM' を出力します。
$now->i18nFormat();

// 日付と時刻のフルフォーマットを利用します。
$now->i18nFormat(\IntlDateFormatter::FULL);

// 日付のフルフォーマットと時刻のショートフォーマットを利用します。
$now->i18nFormat([\IntlDateFormatter::FULL, \IntlDateFormatter::SHORT]);

// '2014-10-31 00:00:00' と出力します。
$now->i18nFormat('yyyy-MM-dd HH:mm:ss');

文字列が表示される希望のフォーマットを特定することも可能です。この関数に第1引数として IntlDateFormatter 定数 [https://www.php.net/manual/ja/class.intldateformatter.php] を渡したり、あるいは以下のリソースで指定されている ICU の日付フルフォーマット文字列を渡すことができます: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax.

グレゴリオ暦以外の暦で日付をフォーマットすることも可能です。

// 出力結果 'Friday, Aban 9, 1393 AP at 12:00:00 AM GMT'
$result = $now->i18nFormat(\IntlDateFormatter::FULL, null, 'en-IR@calendar=persian');

以下の暦のタイプがサポートされています。

japanese

buddhist

chinese

persian

indian

islamic

hebrew

coptic

ethiopic

注釈

IntlDateFormatter::FULL のような文字列定数のために Intl は ICU ライブラリーを使用します。そのライブラリーは、 CLDR (https://cldr.unicode.org/) からデータを取り入れています。ライブラリーのバージョンは、 PHP のインストールにとても依存し、バージョンにより異なる結果を返します。

Cake\I18n\FrozenTime::nice()

あらかじめ定義されている 'nice' フォーマットで出力します。

$time = Time::parse('2014-10-31');

// en-USでは 'Oct 31, 2014 12:00 AM' と出力されます。
echo $time->nice();

Time オブジェクトそのものを変更することなく、出力される日付のタイムゾーンを変更することができます。一つのタイムゾーンでデータを保存しているけれども、ユーザーのそれぞれのタイムゾーンで表示したい場合に便利です。

$time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris');

第1引数を null のままにしておくと、デフォルトのフォーマット文字列を使用します。

$time->i18nFormat(null, 'Europe/Paris');

最後に、日付を表示するのに異なるロケールを利用することができます。

echo $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris', 'fr-FR');

echo $time->nice('Europe/Paris', 'fr-FR');

デフォルトのロケールとフォーマット文字列を設定する

nice や i18nFormat を利用している際に表示される日付のデフォルトのロケールは、 intl.default_locale [https://www.php.net/manual/en/intl.configuration.php#ini.intl.default-locale] の指令です。しかしながら、このデフォルト値は実行時にも変更できます。

Time::setDefaultLocale('es-ES'); // 可変の DateTime 用
FrozenTime::setDefaultLocale('es-ES'); // 不変の DateTime 用
Date::setDefaultLocale('es-ES'); // 可変の Date 用
FrozenDate::setDefaultLocale('es-ES'); // 不変の Date 用

フォーマットメソッドの中で直接異なるローケルが指示されていない限り、今後、日時はスペインのフォーマットで表示されます。

同様に、 i18nFormat を利用することでデフォルトのフォーマット文字列を変更できます。

Time::setToStringFormat(\IntlDateFormatter::SHORT); // 可変の DateTime 用
FrozenTime::setToStringFormat(\IntlDateFormatter::SHORT); // 不変の DateTime 用
Date::setToStringFormat(\IntlDateFormatter::SHORT); // 可変の Date 用
FrozenDate::setToStringFormat(\IntlDateFormatter::SHORT); // 不変の Date 用

// Date, FrozenDate, FrozenTime にも同じメソッドがあります。
Time::setToStringFormat([
    \IntlDateFormatter::FULL,
    \IntlDateFormatter::SHORT
]);

// Date, FrozenDate, FrozenTime にも同じメソッドがあります。
Time::setToStringFormat('yyyy-MM-dd HH:mm:ss');

日付のフォーマット文字列を直接渡すよりも、定数を常に利用することが推奨されています。

相対時間のフォーマットについて

Cake\I18n\FrozenTime::timeAgoInWords(array $options = [])

現在との相対的な時間を出力することが有用なときがしばしばあります。

$time = new FrozenTime('Jan 31, 2021');
// On June 12, 2021, this would output '4 months, 1 week, 6 days ago'
echo $time->timeAgoInWords(
    ['format' => 'MMM d, YYY', 'end' => '+1 year']
);

format オプションを利用してフォーマットされた相対時間の位置は end オプションによって定義されます。 accuracy オプションは、それぞれの間隔幅に対してどのレベルまで詳細を出すかをコントロールします。

// Outputs '4 months ago'
echo $time->timeAgoInWords([
    'accuracy' => ['month' => 'month'],
    'end' => '1 year'
]);

accuracy を文字列で設定すると、出力をどのレベルまで詳細を出すかの最大値を指定できます。

$time = new Time('+23 hours');
// 出力結果 'in about a day'
$result = $time->timeAgoInWords([
    'accuracy' => 'day'
]);

変換

Cake\I18n\FrozenTime::toQuarter()

一旦作成しても、 Time インスタンスを、タイムスタンプや四半期の値に変換することができます。

$time = new FrozenTime('2021-01-31');
echo $time->toQuarter();  // Outputs '1'
echo $time->toUnixString();  // Outputs '1612069200'

現在と比較する

Cake\I18n\FrozenTime::isYesterday()

Cake\I18n\FrozenTime::isThisWeek()

Cake\I18n\FrozenTime::isThisMonth()

Cake\I18n\FrozenTime::isThisYear()

様々な方法で Time インスタンスと現在とを比較することができます。

$time = new FrozenTime('+3 days');

debug($time->isYesterday());
debug($time->isThisWeek());
debug($time->isThisMonth());
debug($time->isThisYear());

上述のメソッドのいずれも、 Time インスタンスが現在と一致するかどうかによって、 true/false を返します。

間隔を比較する

Cake\I18n\FrozenTime::isWithinNext($interval)

wasWithinLast() および isWithinNext() を使用して FrozenTime インスタンスが特定の範囲内にあるかどうかを確認できます。

$time = new FrozenTime('+3 days');

// Within 2 days. Outputs 'false'
debug($time->isWithinNext('2 days'));

// Within 2 next weeks. Outputs 'true'
debug($time->isWithinNext('2 weeks'));

Cake\I18n\FrozenTime::wasWithinLast($interval)

過去の範囲内の FrozenTime インスタンスと比較することもできます。

$time = new FrozenTime('-72 hours');

// Within past 2 days. Outputs 'false'
debug($time->wasWithinLast('2 days'));

// Within past 3 days. Outputs 'true'
debug($time->wasWithinLast('3 days'));

// Within past 2 weeks. Outputs 'true'
debug($time->wasWithinLast('2 weeks'));

FrozenDate

CakePHP の不変の FrozenDate クラスは Cake\I18n\FrozenTime と同じAPIとメソッドを実装しています。 FrozenTime と FrozenDate の主な違いは、 FrozenDate が時間コンポーネントを追跡しないことです。以下のコードをご覧ください。

use Cake\I18n\FrozenDate;
$date = new FrozenDate('2021-01-31');

$newDate = $date->modify('+2 hours');
// Outputs '2021-01-31 00:00:00'
echo $newDate->format('Y-m-d H:i:s');

$newDate = $date->addHours(36);
// Outputs '2021-01-31 00:00:00'
echo $newDate->format('Y-m-d H:i:s');

$newDate = $date->addDays(10);
// Outputs '2021-02-10 00:00:00'
echo $newDate->format('Y-m-d H:i:s');

FrozenDate インスタンスのタイムゾーンを変更する試みも無視されます。

use Cake\I18n\FrozenDate;
$date = new FrozenDate('2021-01-31', new \DateTimeZone('America/New_York'));
$newDate = $date->setTimezone(new \DateTimeZone('Europe/Berlin'));

// Outputs 'America/New_York'
echo $newDate->format('e');

Mutable Dates and Times

class Cake\I18n\Time

class Cake\I18n\Date

CakePHP は、変更可能な仲間と同じインターフェイスを実装する、不変な日付と時刻のクラスを提供しています。不変なオブジェクトは、偶発的にデータが変わってしまうのを防ぎたいときや、順番に依存する問題を避けたいときに、便利です。以下のコードをご覧ください。

use Cake\I18n\Time;
$time = new Time('2015-06-15 08:23:45');
$time->modify('+2 hours');

// このメソッドは $time インスタンスも変更します。
$this->someOtherFunction($time);

// ここでの出力結果は不明です。
echo $time->format('Y-m-d H:i:s');

メソッドの呼び出しの順番が変わった場合、あるいは someOtherFunction によって変更された場合、出力は予期できません。このオブジェクトの変更可能な性質によって、一時的結合が作成されます。不変のオブジェクトを用いれば、この問題を避けることができます。

use Cake\I18n\FrozenTime;
$time = new FrozenTime('2015-06-15 08:23:45');
$time = $time->modify('+2 hours');

// このメソッドの変更は $time を変更しません。
$this->someOtherFunction($time);

// ここでの出力結果は明らかです。
echo $time->format('Y-m-d H:i:s');

不変の日付と時刻は、エンティティー内での偶然的な更新を防ぎ、変更を明示するよう強制したいときに便利です。不変なオブジェクトを利用することで、ORM が変更を追跡したり、日付や日付と時刻のカラムを正しく保持することが、より簡単になります。

// 記事が保存されるとき、この変更は消去されます。
$article->updated->modify('+1 hour');

// 時刻のオブジェクトを置き換えると、プロパティーが保存されます。
$article->updated = $article->updated->modify('+1 hour');

地域化されたリクエストデータの受け入れ

日付を操作するテキストの入力を作成するとき、きっと地域化された日時の文字列を受け入れてパースしたいはずです。 地域化された日時データをパースする をご覧ください。

サポートされるタイムゾーン

CakePHP はすべての有効な PHP タイムゾーンをサポートしています。サポートされるタイムゾーンの一覧は、このページをご覧ください [https://php.net/manual/ja/timezones.php] 。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Xml

class Cake\Utility\Xml

Xml クラスを利用して、 配列から SimpleXMLElement もしくは DOMDocument オブジェクトに変換を、また配列に戻すことを可能にします。

データを Xml クラスにインポートする

static Cake\Utility\Xml::build($input, array $options = [])

Xml::build() を利用することで、XML 形式のデータの読み込みが可能となります。 $options により、このメソッドは SimpleXMLElement (デフォルト) もしくは DOMDocument を返却します。様々なソースから XML オブジェクトのビルドに Xml::build() が利用できます。例えば、文字列から XML をロードできます。

$text = '<?xml version="1.0" encoding="utf-8"?>
<post>
    <id>1</id>
    <title>Best post</title>
    <body> ... </body>
</post>';
$xml = Xml::build($text);

ローカルファイルから Xml オブジェクトにビルドも可能です。

// ローカルファイル
$xml = Xml::build('/home/awesome/unicorns.xml');

配列を利用して Xml オブジェクトのビルドも可能です。

$data = [
    'post' => [
        'id' => 1,
        'title' => 'Best post',
        'body' => ' ... '
    ]
];
$xml = Xml::build($data);

もし入力が不正であれば、 Xml クラスは例外を投げます。

$xmlString = 'What is XML?';
try {
    $xmlObject = Xml::build($xmlString); // ここで例外が投げられます
} catch (\Cake\Utility\Exception\XmlException $e) {
    throw new InternalErrorException();
}

注釈

DOMDocument [https://php.net/domdocument] と SimpleXML [https://php.net/simplexml] は異なる API を実装します。 Xml によって返却されるオブジェクトの正しいメソッドを利用しているか確認してください。

XML 文字列を配列に変換する

toArray($obj);

XML テキストを配列に変換するのは、 Xml クラスと同様にシンプルです。標準で SimpleXml オブジェクトから受け取ります。

$xmlString = '<?xml version="1.0"?><root><child>value</child></root>';
$xmlArray = Xml::toArray(Xml::build($xmlString));

もし XML が不正の場合、 Cake\Utility\Exception\XmlException が起こります。

配列を XML 文字列に変換する

$xmlArray = ['root' => ['child' => 'value']];
// Xml::build() を使うこともできます
$xmlObject = Xml::fromArray($xmlArray, ['format' => 'tags']);
$xmlString = $xmlObject->asXML();

配列の "トップレベル" 要素はたった一つであり、それは数字ではいけません。もし配列がこのフォーマットに従っていない時、Xml クラスは例外を投げます。不正な配列の例です。

// トップレベルのキーが数字
[
    ['key' => 'value']
];

// トップレベルに複数のキーがある
[
    'key1' => 'first value',
    'key2' => 'other value'
];

標準では、配列の値が XML のタグとして出力されます。属性やテキストの値を定義したければ、接頭辞として許されている @ をキーに付与します。値のテキストは、 @ をキーにします。

$xmlArray = [
    'project' => [
        '@id' => 1,
        'name' => 'Name of project, as tag',
        '@' => 'Value of project'
    ]
];
$xmlObject = Xml::fromArray($xmlArray);
$xmlString = $xmlObject->asXML();

$xmlString の内容は以下になります。

<?xml version="1.0"?>
<project id="1">Value of project<name>Name of project, as tag</name></project>

名前空間を利用する

XML の名前空間を利用するには、配列のキーに包括的な名前空間である xmlns: を使用するか、独自の名前空間に xmlns: を接頭語として加えたキーを使用して配列を作成します。

$xmlArray = [
    'root' => [
        'xmlns:' => 'https://cakephp.org',
        'child' => 'value'
    ]
];
$xml1 = Xml::fromArray($xmlArray);

$xmlArray(
    'root' => [
        'tag' => [
            'xmlns:pref' => 'https://cakephp.org',
            'pref:item' => [
                'item 1',
                'item 2'
            ]
        ]
    ]
);
$xml2 = Xml::fromArray($xmlArray);

$xml1 と $xml2 の値はそれぞれ以下になります。

<?xml version="1.0"?>
<root xmlns="https://cakephp.org"><child>value</child>

<?xml version="1.0"?>
<root><tag xmlns:pref="https://cakephp.org"><pref:item>item 1</pref:item><pref:item>item 2</pref:item></tag></root>

子要素を作成

XML 文書を作成したのち、その文書に子要素を追加したり取り除いたり操作するには、単純に標準の実装を利用します。

// SimpleXML を利用
$myXmlOriginal = '<?xml version="1.0"?><root><child>value</child></root>';
$xml = Xml::build($myXmlOriginal);
$xml->root->addChild('young', 'new value');

// DOMDocument を利用
$myXmlOriginal = '<?xml version="1.0"?><root><child>value</child></root>';
$xml = Xml::build($myXmlOriginal, ['return' => 'domdocument']);
$child = $xml->createElement('young', 'new value');
$xml->firstChild->appendChild($child);

Tip

SimpleXMLElement や DomDocument を利用して XML を操作したのちは、 Xml::toArray() を問題なく利用できます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

定数および関数

CakePHP を使った皆さんの日常のほとんどの業務ではコアクラスやメソッドを用いることになるでしょうが、ちょっとした役に立つ便利なグローバル関数も CakePHP にはたくさんあります。この関数のほとんどは CakePHP のクラスと一緒に使うためのもの（モデルやコンポーネントクラスのローディングなど）ですが、他の多くは、配列や文字列の扱いを少し楽にしてくれるものです。

また、CakePHP のアプリケーションで利用可能な定数も同時におさえておきましょう。これらの定数を用いることはよりスムースなアップグレードの助けになるだけでなく、 CakePHP アプリケーション内の特定のファイルやディレクトリーを指し示す便利なやり方でもあります。

グローバル関数

以下、CakePHP の使用可能なグローバル関数です。その多くは、デバッグしたり内容を翻訳したりといった、機能的に他の CakePHP の単なる便利なラッパーになっています。

\_\_(string $string_id, [$formatArgs])

この関数は CakePHP のアプリケーションでのローカライズを担います。 $string_id で翻訳時の ID を定めます。その文字列内のプレースホルダーを置き換えるための、追加の引数を供給できます。

__('You have {0} unread messages', $number);

また、置換する名前インデックス配列を指定できます。

__('You have {unread} unread messages', ['unread' => $number]);

注釈

より詳しい情報は国際化と地域化のセクションを確認して下さい。

__d(string $domain, string $msg, mixed $args = null)

メッセージを一つ取得するために、現在のドメインを変更することが可能です。

プラグインを国際化するときに便利です: echo __d('PluginName', 'This is my plugin');

__dn(string $domain, string $singular, string $plural, integer $count, mixed $args = null)

複数形のメッセージを一つ取得するために、現在のドメインを変更することが可能です。 $domain でドメインを指定し、 $count の数を数え、 $singular と $plural に基いて複数形を正しく処理したメッセージを返します。

__dx(string $domain, string $context, string $msg, mixed $args = null)

メッセージを一つ取得するために、現在のドメインを変更することが可能です。また、あなたがコンテキストを指定することができます。

コンテキストは、同じドメイン内で、それがユニークな翻訳文字列の一意の識別子です。

__dxn(string $domain, string $context, string $singular, string $plural, integer $count, mixed $args = null)

複数形のメッセージを一つ取得するために、現在のドメインを変更することが可能です。また、あなたがコンテキストを指定することができます。 $domain でドメインを指定し、 $count の数を数え、 $singular と $plural に基いて複数形を正しく処理したメッセージを返します。幾つかの言語が、数に応じた複数形の形式を一つ以上持っています。

コンテキストは、同じドメイン内で、それがユニークな翻訳文字列の一意の識別子です。

__n(string $singular, string $plural, integer $count, mixed $args = null)

$count の数を数え、 $singular と $plural に基いて複数形を正しく処理したメッセージを返します。幾つかの言語が、数に応じた複数形の形式を一つ以上持っています。

__x(string $context, string $msg, mixed $args = null)

コンテキストは、同じドメイン内で、それがユニークな翻訳文字列の一意の識別子です。

__xn(string $context, string $singular, string $plural, integer $count, mixed $args = null)

$count の数を数え、 $singular と $plural に基いて複数形を正しく処理したメッセージを返します。また、あなたがコンテキストを指定することができます。幾つかの言語が、数に応じた複数形の形式を一つ以上持っています。

コンテキストは、同じドメイン内で、それがユニークな翻訳文字列の一意の識別子です。

collection(mixed $items)

渡された引数をラップする、新しい Cake\Collection\Collection オブジェクトをインスタンス化するための簡易ラッパー。 $items パラメーターは Traversable オブジェクトまたは配列のいずれかを取ります。

debug(mixed $var, boolean $showHtml = null, $showFrom = true)

バージョン 3.3.0 で変更: このメソッドを呼ぶと、渡された $var を返します。例えば、return 文にこのメソッドを置くことができます。

コア $debug 変数が true であれば、 $var が出力されます。 $showHTML が true あるいは null のままであればデータはブラウザー表示に相応しいように描画されます。 $showFrom が false にセットされない場合、それがコールされた行の情報を伴ってデバッグ情報の出力が始まります。デバッグ もご覧ください。

dd(mixed $var, boolean $showHtml = null)

debug() のように動作しますが、実行を終了します。コア $debug 変数が true であれば、 $var が出力されます。 $showHTML が true あるいは null のままであればデータはブラウザー表示に相応しいように描画されます。 デバッグ もご覧ください

pr(mixed $var)

バージョン 3.3.0 で変更: このメソッドを呼ぶと、渡された $var を返します。例えば、return 文にこのメソッドを置くことができます。

出力を <pre> タグで周りを囲む機能を追加した print_r() の便利なラッパー。

pj(mixed $var)

バージョン 3.3.0 で変更: このメソッドを呼ぶと、渡された $var を返します。例えば、return 文にこのメソッドを置くことができます。

出力を <pre> タグで周りを囲む機能を追加した JSON 整形表示の便利な関数。

それは、オブジェクトと配列のJSON 表現をデバッグために意図されています。

env(string $key, string $default = null)

可能な限りの環境変数を取得します。仮に $_SERVER か $_ENV が使用不可の場合にはバックアップとして用いられます。

この関数はまた、 PHP_SELF と DOCUMENT_ROOT を、非サポートのサーバー上でエミュレートします。これは完全なエミュレーションラッパーなので、 $_SERVER や getenv() の代わりに env() を常に用いることは、（とりわけあなたがコードを配布する予定なら）とても良い考えです。

h(string $text, boolean $double = true, string $charset = null)

htmlspecialchars() の便利なラッパー。

pluginSplit(string $name, boolean $dotAppend = false, string $plugin = null)

ドット記法されたプラグイン名をプラグインとクラス名に分離します。 $name にドットが含まれない場合、インデックスが 0 の箇所は null になります。

一般に list($plugin, $name) = pluginSplit('Users.User'); のように使われます。

namespaceSplit(string $class)

ネームスペースをクラス名から分離します。

一般に list($namespace, $className) = namespaceSplit('Cake\Core\App'); のように使われます。

コア定義定数

以下のほとんどの定数はあなたのアプリケーション内部のパスへの参照です。

constant APP

アプリケーションディレクトリーへの絶対パス。末尾にスラッシュが付きます。

constant APP_DIR

あなたのアプリケーションのディレクトリー名。app かも知れません。

constant CACHE

キャッシュファイルディレクトリーへのパス。複数サーバーをセットアップした際のホスト間で共有できます。

constant CAKE

cake ディレクトリーへのパス。

constant CAKE_CORE_INCLUDE_PATH

ルートの lib ディレクトリーへのパス。

constant CONFIG

config ディレクトリーへのパス。

constant CORE_PATH

ルートディレクトリーへの、末尾にディレクトリースラッシュを付加したパス。

constant DS

PHP の DIRECTORY_SEPARATOR (Linux の場合は / Windows の場合は \) のショートカット。

constant LOGS

ログディレクトリーへのパス。

constant ROOT

ルートディレクトリーへのパス。

constant TESTS

テストディレクトリーへのパス。

constant TMP

一時ファイルディレクトリーへのパス。

constant WWW_ROOT

ウェブルートへのフルパス。

時間定義定数

constant TIME_START

アプリケーションが開始された時点の、浮動小数点マイクロ秒での UNIX タイムスタンプ。

constant SECOND

1 と等しい

constant MINUTE

60 と等しい

constant HOUR

3600 と等しい

constant DAY

86400 と等しい

constant WEEK

604800 と等しい

constant MONTH

2592000 と等しい

constant YEAR

31536000 と等しい

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Chronos

このページは 移動しました [https://book.cakephp.org/chronos/2.x/ja/] 。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Debug Kit

このページは 移動しました [https://book.cakephp.org/debugkit/4.x/ja/] 。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Migrations

このページは 移動しました [https://book.cakephp.org/migrations/3/ja/] 。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ElasticSearch

このページは 移動しました [https://book.cakephp.org/elasticsearch/3/ja/] 。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

付録

ここでは、各バージョンで導入された新機能に関する情報と、バージョン間の移行手順を解説します。

4.x 移行ガイド

4.0 移行ガイド

4.1 移行ガイド

4.2 移行ガイド

4.3 移行ガイド

フィクスチャのアップグレード

後方互換性の補完

3.x の挙動に関する対応が必要な場合、または段階的な移行に関する助けが必要な場合、 Shim プラグイン [https://github.com/dereuromark/cakephp-shim] を確認してください。後方互換性を損なう変更を移行するのに役立ちます。

前方互換性の補完

前方互換性の補完は、次のメジャーリリース (4.x) のために、3.x アプリを準備できます。

既存の 3.x で 4.x の挙動に合わせたい場合、 Shim プラグイン [https://github.com/dereuromark/cakephp-shim] を確認してください。後方互換性を損なう変更を移行するのに役立つ可能性があります。 3.x アプリが 4.x に近いほど、変更の差分は小さくなり、最終的なアップグレードはよりスムーズになります。

一般的な情報

CakePHP の開発プロセス

用語集

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

フィクスチャのアップグレード

4.3.0では、フィクスチャのスキーマとデータ管理の責務が分割されました。フィクスチャクラスやマイグレーションでスキーマを管理することは、アプリケーションに複雑さとメンテナンスコストをもたらしていました。 4.3.0 では新しい API が追加され、既存のマイグレーションやスキーマ管理ツールをテストでより簡単に再利用できるようになりました。

新しいフィクスチャシステムにアップグレードするには、いくつかのアップデートを行う必要があります:

まず、phpunit.xml から <listeners> ブロックを削除します。

次の内容を phpunit.xml に追加します:

<extensions>
    <extension class="\Cake\TestSuite\Fixture\PHPUnitExtension" />
</extensions>

これは、テストフィクスチャマネージャからスキーマの管理を取り除きます。代わりに アプリケーションは、各テスト実行の最初にスキーマを作成/更新する必要があります。

次に、スキーマを作成するために tests/boostrap.php を更新します。スキーマを作成するにはいくつかの異なる方法があります。 CakePHPで提供されている方法については、creating-test-database-schema を参照してください。

それから、フィクスチャから $fields と $import のプロパティをすべて削除してください。これらのプロパティは新しいフィクスチャシステムでは使用されません。

テストは引き続き通過するはずです。そして、fixture-state-management を使って実験できるようになります。大幅な性能向上を実現する TransactionStrategy を試すことができます。 TransactionStrategy のトレードオフは、テストごとに自動インクリメントの値が 1 から始まらなくなることです。

レガシーなフィクスチャのドキュメント

以下の説明は、4.3.0以前のデフォルトであるリスナー・ベースのフィクスチャにのみ適用されます。

フィクスチャのスキーマ

テーブルの一部となるフィールドとその定義を $fields で指定します。これらのフィールドを定義するためのフォーマットは、 Cake\SchemaTable と同じです。テーブルの定義に使用できるキーは以下のとおりです:

型

CakePHP の内部データ型。現在サポートされているものは以下:

string: は VARCHAR にマップします

char: は CHAR にマップします

uuid: は UUID にマップします

text: は TEXT にマップします

integer: は INT にマップします

biginteger: は BIGINTEGER にマップします

decimal: は DECIMAL にマップします

float: は FLOAT にマップします

datetime: は DATETIME にマップします

datetimefractional: は DATETIME(6) か TIMESTAMP にマップします

timestamp: は TIMESTAMP にマップします

timestampfractional: は TIMESTAMP(6) か TIMESTAMP にマップします

time: は TIME にマップします

date: は DATE にマップします

binary: は BLOB にマップします

長さ

フィールドに必要な長さを設定します。

精度

floatおよびdecimalフィールドで使用される小数点以下の桁数を設定します。

null

true （NULLを許可する場合）、または false （許可しない場合）を設定します。

デフォルト

フィールドが取るデフォルト値。

テーブル情報のインポート

フィクスチャ・ファイルでスキーマを定義することは、プラグインやライブラリを作成するときや、データベース・ベンダー間でポータブルにする必要のあるアプリケーションを作成するときに、とても便利です。フィクスチャでスキーマを再定義することは、大規模なアプリケーションでは維持するのが難しくなります。そのため、CakePHP では既存の接続からスキーマをインポートし、反映されたテーブル定義を使用してテストスイートで使用するテーブル定義を作成する機能を提供しています。

まずは例を挙げて説明します。 articlesという名前のテーブルがあるとして、前節で示した例の フィクスチャを変更します。 (tests/Fixture/ArticlesFixture.php) を次のように変更します:

class ArticlesFixture extends TestFixture
{
    public $import = ['table' => 'articles'];
}

別の接続を使用したい場合は、以下を使用します:

class ArticlesFixture extends TestFixture
{
    public $import = ['table' => 'articles', 'connection' => 'other'];
}

通常、フィクスチャに沿ったTableクラスもあるはずです。それを使用してテーブル名を取得することも可能です:

class ArticlesFixture extends TestFixture
{
    public $import = ['model' => 'Articles'];
}

また、プラグインシンタックスにも対応しています。

既存のモデル/テーブルからテーブル定義をインポートするのは当然として、前のセクションで示したように、フィクスチャ上でレコードを直接定義することができます。例えば以下のようになります:

class ArticlesFixture extends TestFixture
{
    public $import = ['table' => 'articles'];
    public $records = [
        [
          'title' => 'First Article',
          'body' => 'First Article Body',
          'published' => '1',
          'created' => '2007-03-18 10:39:23',
          'modified' => '2007-03-18 10:41:31'
        ],
        [
          'title' => 'Second Article',
          'body' => 'Second Article Body',
          'published' => '1',
          'created' => '2007-03-18 10:41:23',
          'modified' => '2007-03-18 10:43:31'
        ],
        [
          'title' => 'Third Article',
          'body' => 'Third Article Body',
          'published' => '1',
          'created' => '2007-03-18 10:43:23',
          'modified' => '2007-03-18 10:45:31'
        ]
    ];
}

最後に、フィクスチャでスキーマをロード/作成しないことも可能です。これは、すでにテスト用のデータベースを用意していて、空のテーブルをすべて作成している場合に便利です。フィクスチャに $fields も $import も定義しないことで、フィクスチャはレコードを挿入し、各テストメソッドでレコードを切り詰めるだけになります。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CakePHP の開発プロセス

ここで CakePHP フレームワークの開発をする時に使ってるプロセスを説明しようと思います。私たちはチケットや IRC チャットを通したコミュニティーの対話に非常に信頼しています。 IRC は 開発チーム [https://github.com/cakephp?tab=members] のメンバーを見つけてアイデア、最新のコードについて議論する、全般的なコメントをするのに最適な場所です。もし何か、より正式な要望の提出、リリースに問題があるなどの場合は、チケットシステムはあなたの考えを共有する最適な場所となります。

私たちは現在 CakePHP の4つのバージョンを整備しています。

タグ付けリリース (tagged release) : タグ付けリリースは、機能より安定が重要な製品のために用意されたリリースです。これらのリリースに対して起こった課題は、関連するブランチで修正され、次のリリースの一部になります。

メインラインブランチ (mainline branch) : これらのブランチは、全てのバグ修正がマージされるところです。安定版リリースは、これらのブランチからタグ付けされます。 master は、現行リリースシリーズのためのメインラインブランチです。 2.x は、 2.x リリースシリーズの保守ブランチです。もし、安定版リリースを使用していて、タグ付けリリースに入っていない修正が必要なら、これをチェックしてください。

開発ブランチ (development branches) : 開発ブランチは最先端の修正と機能を含みます。これらはメジャーバージョン番号にちなんで命名されます。例えば、 3.next になります。一旦安定版リリースの瞬間が近づいてきたとき、開発ブランチはメインラインブランチにマージされます。

機能ブランチ (feature branches) : 機能ブランチは完了してないまたは安定しない可能性のある機能で、最先端の機能セットに興味のあるへビーユーザーのみに推奨されており、コミュニティーへ貢献のお返しをする意思もあります。機能ブランチは次の規約を元に命名されています： バージョン-機能 。例としては、 3.3-router 、これは 3.3 の Router のための新しい機能となります。

このことが、きっとどのバージョンがあなたにとって正しいかどうかの理解を助けることでしょう。一度バージョンを選択したら、バグ報告やコードの全般的なコメントをせずにはいられないかもしれません。

もし安定バージョンまたは保守ブランチを使っているなら、チケットを送るか IRC で私たちと議論してください。

もし開発ブランチか機能ブランチを使っているなら、まず最初に行くのは IRC です。もし言いたいことがあり、1～2日 IRC で私たちと会うことが出来なかったら、チケットを送ってください。

もし問題を見つけたら、テストを書くことが最高の答えです。テストを書く中で私たちが提案する最大のアドバイスは、コアに含まれるテストを見ましょうということです。

いつも通り、どんな質問でもコメントでもありましたら、 irc.freenode.net の #cakephp で私たちを訪ねてください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

用語集

CDN

Content Delivery Network の略。世界中のデータセンターにあなたのコンテンツを配信するために利用するサードパーティベンダーです。地理的に分散したユーザーの近くの静的なアセットを送信するのに便利です。

カラム

データベーステーブルの中のテーブルカラムを参照するときに ORM の中で使用されます。

CSRF

クロスサイトリクエストフォージェリ(Cross Site Request Forgery)。再生攻撃、二重投稿、他ドメインからの偽造リクエストを防止します。

DSN

データソース名。URI のような書式の接続文字列フォーマット。CakePHP は、キャッシュ、データベース、ログ、Eメール接続のための DSN に対応します。

ドット記法

ドット記法は、 . を用いてネストされたレベルを区切ることによって配列のパスを定義します。例:

Cache.default.engine

は以下の値を指し示します。

[
    'Cache' => [
        'default' => [
            'engine' => 'File'
        ]
    ]
]

DRY

同じことを繰り返さない (Don’t repeat yourself)。これはあらゆる情報の繰り返しを少なくするためのソフトウェア開発の原則です。CakePHP では同じコードは１カ所に書いて再利用するという形で DRY 原則に従っています。

フィールド

エンティティーのプロパティー、もしくはデータベースのカラムを記述するために使用される一般的な用語。FormHelper と組み合わせて使用されることがよくあります。

HTML属性

HTML の属性を構成するキー => 値の配列。例:

// これを与えると
['class' => 'my-class', 'target' => '_blank']

// これが生成される
class="my-class" _target="blank"

オプションが最小化できるか、名前そのものが値として許可される場合は、 true が利用できます。

// これを与えると
['checked' => true]

// これが生成される
checked="checked"

PaaS

Platform as a Service の略。クラウドベースのホスティング、データベース、キャッシュリソースを提供します。有名なプロバイダーは、Heroku、EngineYard、 PagodaBox などです。

プロパティー

ORM エンティティーにマップされたカラムを参照するときに使用されます。

プラグイン記法

プラグイン記法はドットで区切られたクラス名で、クラスがプラグインの一部であることを指定しています。

// プラグインは "DebugKit", クラス名は "Toolbar".
'DebugKit.Toolbar'

// プラグインは "AcmeCorp/Tools", クラス名は "Toolbar".
'AcmeCorp/Tools.Toolbar'

routes.php

config ディレクトリー中のファイルで、ルーティングの設定が入っています。このファイルは全てのリクエストが処理される前に読み込まれます。リクエストが正しいコントローラー+アクションにルーティングされるように、アプリケーションが必要とする全てのルートに接続する必要があります。

ルーティング配列

Router::url() に渡される属性の配列。典型的には以下のようになります。

['controller' => 'Posts', 'action' => 'view', 5]

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

PHP Namespace Index

c

| c |
- | Cake |
|     Cake\Cache |
|     Cake\Collection |
|     Cake\Console |
|     Cake\Console\Exception |
|     Cake\Controller |
|     Cake\Controller\Component |
|     Cake\Controller\Exception |
|     Cake\Core |
|     Cake\Core\Exception |
|     Cake\Database |
|     Cake\Database\Exception |
|     Cake\Database\Schema |
|     Cake\Datasource |
|     Cake\Datasource\Exception |
|     Cake\Error |
|     Cake\Filesystem |
|     Cake\Form |
|     Cake\Http |
|     Cake\Http\Client |
|     Cake\Http\Cookie |
|     Cake\Http\Exception |
|     Cake\Http\TestSuite |
|     Cake\I18n |
|     Cake\Log |
|     Cake\Mailer |
|     Cake\ORM |
|     Cake\ORM\Behavior |
|     Cake\ORM\Exception |
|     Cake\Routing |
|     Cake\Routing\Exception |
|     Cake\Utility |
|     Cake\Validation |
|     Cake\View |
|     Cake\View\Exception |
|     Cake\View\Helper |

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ページが見つかりません。

お探しのページが見つかりません。お探しのページを見つけるために検索バーをお試しください。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

コンテンツ

はじめに

CakePHP 概要

設定より規約

モデル層

ビュー層

コントローラーレイヤー

CakePHP のリクエストサイクル

さっそく始めましょう

付録

情報の探し方

公式 CakePHP ウェブサイト

Cookbook

Bakery

API

テストケース

IRC チャンネル

公式 CakePHP フォーラム

Stackoverflow

あなたの言語で情報を得るには

デンマーク語

フランス語

ドイツ語

Iranian

Dutch

日本語

ポルトガル語

スペイン語

CakePHP の規約

コントローラーの規約

コントローラー名と URL

ファイルとクラス名の規約

データベースの規約

モデルの規約

ビューの規約

プラグインの規約

要約

Database Convention Summary

CakePHP のフォルダー構成

src フォルダー

クイックスタートガイド

コンテンツ管理チュートリアル

CakePHP の取得

インストールの確認

CMS チュートリアル - データベース作成

データベースの設定

最初のモデルの作成

CMS チュートリアル - Articles コントローラーの作成

記事一覧のテンプレート作成

view アクションの作成

view テンプレートの作成

記事の追加

add テンプレートの作成

シンプルなスラグ生成の追加

edit アクションの追加

edit テンプレートの作成

Articles の検証ルールの更新

delete アクションの追加

移行ガイド

4.0 アップグレードガイド

非推奨の警告を修正

PHP 7.2 へのアップグレード

アップグレードツールの使用

Rector によるリファクタリングを適用する

CakePHP の依存関係をアップデートする

Application.php

4.0 移行ガイド

非推奨機能の削除

非推奨

Component

Filesystem

ORM

View

Mailer

App

破壊的変更

Cache

Controller

Console

Component

Database

Datasources

Error

Event

Http

HttpSession

I18n

Mailer

ORM

Router

TestSuite

Utility

View

Helper

Log

その他

新機能

Console

Core

Database

Error

Http

I18n

Mailer

ORM

Routing

TestSuite

Validation

View

4.1 移行ガイド

4.1.0へのアップグレード

非推奨

Controller

Database

Datasource

Form

Http

ORM

Routing

View

Behaviorの変更

Database

Http

ORM

TestSuite

View

新機能

Datasource

Console

Database

Error

Form

Http

I18n

Log

ORM

Routing

TestSuite

View

Utility

4.2 移行ガイド

4.2.0へのアップグレード

非推奨

Core

Database

ORM

Behaviorの変更

Collection

Controller

Component

Database

Error

Validation

破壊的変更

I18n

テスト

新機能

Core

Console

Database

Http

ORM

TestSuite

View

4.3 移行ガイド

4.3.0へのアップグレード

非推奨

Connection

Controller

Database

I18n

Log

Middleware

Network

ORM

Routing

TestSuite

View

Behaviorの変更

Collection

Command

Core

Database

Datasource

Http

ORM

Routing

TestSuite

Validation

View

破壊的変更

Log

View

新機能

Controller

Core

Database

Form

Http

Log

ORM

TestSuite

View

4.4 移行ガイド

4.4.0へのアップグレード

非推奨

Controller

Datasource

ErrorHandler & ConsoleErrorHandler

RequestHandlerComponent

PaginationComponent

ORM

プラグイン

TestSuite

Behaviorの変更

ORM

Routing

新機能

Cache

Command

Controller

Database

Error

Http

Mailer

ORM

Routing

View

チュートリアルと例

コンテンツ管理チュートリアル

CakePHP の取得

インストールの確認

CMS チュートリアル - データベース作成

データベースの設定

最初のモデルの作成

CMS チュートリアル - Articles コントローラーの作成

記事一覧のテンプレート作成

view アクションの作成

view テンプレートの作成

記事の追加

add テンプレートの作成

シンプルなスラグ生成の追加

edit アクションの追加

edit テンプレートの作成

Articles の検証ルールの更新

delete アクションの追加

CMS チュートリアル - タグとユーザー

記事へのタグ付けの追加

タグ付けを有効にする記事の更新

タグによる記事の検索

ファインダーメソッドの追加

ビューの作成

タグ付け体験の改善

計算フィールドの追加

ビューの更新

タグ文字列の永続化

タグ文字列の自動入力

CMS チュートリアル - 認証

認証プラグインのインストール

パスワードハッシュ化の追加

ログインの追加

ログアウト

ユーザー登録の有効化

CMS Authorization

貢献

ドキュメント

翻訳

新たに翻訳する言語

ドキュメント管理者へのリマインド

翻訳者tips

ドキュメントのフォーマットガイド

行の長さ

見出しとセクション

段落(Paragraphs)

インラインマークアップ

リスト

リンク

外部リンク

他のページへのリンク

:doc:

相互参照リンク

:ref:

Sphinx が出力する警告を防ぐ

クラスとその内容を記述する

クラスと構成物を記述する

.. php:global::

.. php:function::

.. php:const::

.. php:exception::

.. php:class::

.. php:method::

.. php:staticmethod::

.. php:attr::

Sphinx が出力する警告を防ぐ

相互参照

:php:func:

:php:global:

:php:const:

:php:class:

:php:meth:

:php:attr:

:php:exc:

ソースコード

注意と警告

サンプル

チケット

バグの報告

セキュリティ問題の報告

コード

最初のセットアップ

修正に取りかかる

Pull Request を送信する

変更対象のマージ先を選ぶ

コーディング規約

新しい機能の追加

IDE の設定

インデント

行の長さ

制御構造

三項演算子

テンプレートファイル

比較

関数の呼び出し

メソッドの定義

アーリーリターン

タイプヒンティング

無名関数 (クロージャー)

メソッドチェーン

コードのコメント

変数の型

ファイルの読み込み

PHP タグ

Echoの短縮記法

命名規約

関数

クラス

変数

メンバーのアクセス権(visibility)

アドレスの例示

ファイル

キャスト

定数

empty()/isset() の使用に注意

後方互換性ガイド

移行ガイド

CakePHP の使用

インターフェイス

クラス

CakePHP での作業

非推奨

Experimental Features

リリースポリシー

メジャーリリース

マイナーリリース

パッチリリース

実験的な機能

CakePHP 入門

インストール

システム要件

CakePHP のインストール

Composer のインストール

CakePHP プロジェクトを作成

CakePHP の変更に合わせて最新の状態に保つ

Oven を使用したインストール

パーミッション

開発サーバー

公開用

始動

URL Rewriting

Apache

nginx

NGINX Unit

IIS7 (Windows hosts)

Lighttpd

URL リライティングを使わない場合

構成設定

アプリケーションの設定

追加の設定ファイルの読み込み

環境変数

一般的な設定

CDNの利用

データベースの設定

キャッシュの設定

エラーと例外ハンドリング設定

ログの設定

メールの設定

セッションの設定

ルーティングの設定

追加のクラスパス

プラグイン、ビュー、テンプレート、ロケールのパス

Inflection の設定

Configure クラス

Configure

設定データの書き込み

Configure::write

設定データの読み込み

Configure::read

Configure::readOrFail

定義されている設定データのチェック

Configure::check

設定データの削除

Configure::delete

設定データの読み書き

Configure::consume

Configure::consumeOrFail

設定ファイルの読み書き

Configure::config

Configure::drop

設定ファイルの読み込み

Configure::load

設定ファイルの作成や編集

Configure::dump

実行時の設定を保存

Configure::store

実行時の設定を復元

Configure::restore

設定エンジン

汎用テーブルの無効化

アプリケーション

Application::bootstrap()

依存性の注入(DI)

サービスの追加

共有サービスを追加する

定義の拡張

サービスのタグ化

設定データを使用する場合

サービス・プロバイダー

サービス・プロバイダーの作成

サービス・プロバイダーの使用

起動可能なサービス・プロバイダー

サービスをモック化してテストする

ルーティング

RouterBuilder

クイックツアー

ルートを接続

ルート要素

予約済みルート要素

ルートオプションの設定

アクションへのパラメーター渡し

名前付きルートの使用

プレフィックスルーティング

RouterBuilder::prefix

プレフィックスルートへのリンクの作成

プラグインのルーティング

RouterBuilder::plugin

プラグインルートへのリンクの作成

SEO に親和性があるルーティング

指定した HTTP メソッドとの照合

指定したホスト名との照合

ファイル拡張子のルーティング

RouterBuilder::extensions

スコープ付きミドルウェアの接続

ミドルウェアのグループ化

RESTful なルーティング

ネストされたリソースのルートを作成

ルートの作成を制限

使用するコントローラーアクションの変更

追加のリソースへのルートをマップする

プレフィックス付きのリソースルーティング

リソースルートのためのカスタムルートクラス

リソースルートのための URL 語形変化

パス要素の変更

渡された引数

URL の生成

RouterBuilder::url

リダイレクトルーティング

エンティティールーティング

カスタムルートクラス

デフォルトルートクラス

RouterBuilder::defaultRouteClass

フォールバックメソッド

RouterBuilder::fallbacks()

永続的な URL パラメーターの生成

URL 内での名前付きパラメーターの扱い

ディスパッチャーフィルター

既定のフィルター

フィルターの使用

フィルターの順番の設定

条件にしたがって適用されるフィルター

フィルターの作成

リクエストとレスポンスオブジェクト

リクエスト

ServerRequest

リクエストパラメーター

クエリー文字列パラメーター

ServerRequest::getQuery()

リクエストのボディーデータ

ServerRequest::getData()

ファイルのアップロード

ServerRequest::getUploadedFile()

ServerRequest::getUploadedFiles()

ServerRequest::withUploadedFiles()

PUT、PATCH または DELETE データ

ServerRequest::input()

環境変数 ($ _SERVER と $ _ENV より)

ServerRequest::env()

XML または JSON データ

パス情報

リクエストの状態をチェック

ServerRequest::is()

ServerRequest::addDetector()

セッションデータ

ホストとドメイン名

ServerRequest::domain()

ServerRequest::subdomains()

ServerRequest::host()

HTTP メソッドの読み込み

ServerRequest::getMethod()

アクションが受け入れる HTTP メソッドの制限

ServerRequest::allowMethod()

HTTP ヘッダーの読み込み

ServerRequest::referer()

ServerRequest::clientIp()

プロキシーヘッダーの信頼

Accept ヘッダーの確認

ServerRequest::accepts()

ServerRequest::acceptLanguage()

クッキーの読込み

アップロードされたファイル

URIの操作

レスポンス

Response

コンテンツタイプの扱い

Response::withType()

ファイルの送信

Response::withFile()

文字列をファイルとして送信

ヘッダーの設定

Response::withHeader()

ボディーの設定

Response::withStringBody()

Response::withBody()

文字コードの設定

Response::withCharset()

ブラウザーキャッシュとの対話

Response::withDisableCache()

Response::withCache()

HTTP キャッシュのチューニング

Cache Control ヘッダー

Response::withSharable()

Expiration ヘッダー

Response::withExpires()

Etag ヘッダー

Response::withEtag()

Last Modified ヘッダー

Response::withModified()

Vary ヘッダー

Response::withVary()

Not-Modified レスポンスの送信

Response::checkNotModified()

クッキーの設定

クロスオリジンリクエストヘッダー（CORS）の設定

不変レスポンスに伴うよくある失敗

クッキーコレクション

CookieCollection

クッキーの作成

Cookie

クッキーの読込み

ミドルウェア

CakePHP のミドルウェア

ミドルウェアの使用

プラグインからのミドルウェア追加

ミドルウェアの作成

ルーティングミドルウェア

クッキー暗号化ミドルウェア

ボディパーサミドルウェア

コントローラー

Controller

AppController

リクエストの流れ

コントローラーのアクション

ビューとの相互作用

ビュー変数の設定

Controller::set()

ビューオプションの設定

ビューの描画

Controller::render()

特定のテンプレートの描画

コンテンツタイプのネゴシエーション

Controller::viewClasses()

コンテンツタイプネゴシエーションのフォールバック

他のページへのリダイレクト

Controller::redirect()

同じコントローラーの他のアクションへの転送

Controller::setAction()

追加のモデル読み込み

Controller::loadModel()

モデルのページネーション

Controller::paginate()

コンポーネント読み込みの設定

Controller::loadComponent()

Controller::$components

ヘルパー読み込みの設定

Controller::$helpers

リクエストライフサイクルコールバック

イベント一覧

コントローラーのコールバックメソッド

Controller::beforeFilter()

Controller::beforeRender()

Controller::afterFilter()

Controller Middleware

Controller::middleware()

コントローラーのより詳細

ページコントローラー

コンポーネント

AuthComponent

AuthComponent

以降を読む前に

認証

認証タイプの選択

認証ハンドラーの設定

検索クエリーのカスタマイズ

ユーザーの識別とログイン

AuthComponent::identify()

ログイン後のリダイレクト

AuthComponent::redirectUrl()

ステートレス認証システムの作成

Basic 認証の使用

Basic 認証のための API キーの作成

ダイジェスト認証の使用

ダイジェスト認証のパスワードのハッシュ化

カスタム認証オブジェクトの作成

カスタム認証オブジェクトの利用

認証されていないリクエストの処理

認証関連のフラッシュメッセージの表示

パスワードのハッシュ化

カスタムパスワードハッシュ化クラスの作成

ハッシュ化アルゴリズムの変更

手動でのユーザーログイン

AuthComponent::setUser()

ログインしているユーザーへのアクセス

AuthComponent::user()

ログアウト

AuthComponent::logout()

認証実行時の判定

認可

認可ハンドラーの設定

カスタム認可オブジェクトの作成

カスタム認可オブジェクトの利用

認可を使用しない

公開するアクションの作成

AuthComponent::allow()

認可が必要なアクションの作成

AuthComponent::deny()

ControllerAuthorize の利用

設定オプション

AuthComponent で保護されたアクションのテスト

フラッシュ

FlashComponent

フラッシュメッセージの設定

フラッシュメッセージ内の HTML

セキュリティ

SecurityComponent

ブラックホールコールバックの処理

SecurityComponent::blackHole()

アクションを SSL 通信に限定

フォーム改ざん防止

使い方

CSRF 防御

指定したアクションの Security コンポーネントの無効化

ページネーション

PaginatorComponent

基本的な使用方法

高度な使用方法

シンプルなページネーション

PaginatorComponent を直接使用する

複数のクエリのページ分割

同じモデルを複数回ページ分割する

ソート時に使用するフィールドの制御

1ページあたりの最大行数を制限する

追加の関連付けへのジョイン

範囲外のページ要求

ビューのページネーション

リクエストハンドリング

RequestHandlerComponent

リクエスト情報の取得

RequestHandlerComponent::accepts()

RequestHandlerComponent::isXml()

RequestHandlerComponent::isRss()

RequestHandlerComponent::isAtom()

RequestHandlerComponent::isMobile()

RequestHandlerComponent::isWap()

リクエストデータの自動デコード

コンテンツタイプの設定を確認

RequestHandlerComponent::prefers()

リクエストへの応答

RequestHandlerComponent::renderAs()

RequestHandlerComponent::respondAs()

RequestHandlerComponent::responseType()

HTTP キャッシュバリデーションの活用

カスタムビュークラスの利用

フォームの保護

FormProtection

フォームの改ざん防止

使用方法

フォームの改ざんチェックを無効にする

特定のアクションのためにフォームの改ざんを無効にする

コールバックによる検証失敗の処理

HTTPキャッシュの確認

CheckHttpCacheComponent

コンポーネントの設定

コンポーネントの別名

コンポーネントの動的ロード

コンポーネントの使用

コンポーネントの作成

コントローラーの中にコンポーネントを読み込む

コンポーネントの中で他のコンポーネントを使用する

コンポーネントのコントローラーへのアクセス

コンポーネントのコールバック

beforeFilter()

startup()

beforeRender()

shutdown()

beforeRedirect()

コンポーネントイベントでのリダイレクトの使用

ビュー

View

App ビュー

ビューテンプレート

別の echo

別の制御構文

ビュー変数

ビュー変数の設定

View::set()

ビューの継承

ビューブロックの使用

ブロックの表示

スクリプトと CSS ファイルのためのブロック使用

レイアウト

プラグインのレイアウト使用

エレメント

View::element()

エレメントに変数を渡す

エレメントのキャッシュ

プラグインのエレメントの要求

ルーティングプレフィックスとエレメント

ビューの断片のキャッシュ

View::cache()

ビューイベント

イベント一覧

独自のビュークラス作成

ビューのより詳細

ビューセル

セルを使う時

セルの作成

セルの実装

セルの呼び出し

セルに引数を渡す

ビューの描画

別のテンプレートの描画

セルの出力のキャッシュ

セル内のデータのページ制御

セルのオプション

テーマ

テーマアセット

JSON と XML ビュー

あなたのアプリケーションでデータビューを有効化する

シリアライズキーをデータビューで使用する

テンプレートファイルをデータビューで使用する

XML ビューの作成

XmlView

JSON ビューの作成

JsonView

JSONP レスポンス

使用例

ヘルパー

Breadcrumbs (パンくず)

BreadcrumbsHelper

パンくずリストを作成

パンくずリストを描画

出力のカスタマイズ

アイテムの属性を定義

パンくずの消去

Flash

FlashHelper

フラッシュメッセージの描画

ルーティングのプレフィックスとフラッシュメッセージ

フラッシュメッセージとテーマ

Form

FormHelper

フォームの開始

FormHelper::create()

フォーム作成のためのオプション

クエリー文字列からフォームの値を取得

フォームの HTTP メソッドを変更

フォームの URL を設定

カスタムバリデーターの利用

コンテキストクラスの作成

フォームコントロールの作成

FormHelper::control()

フィールドの命名規則

コントロールのオプション

コントロールの特定のタイプを生成

特定のコントロールのための共通オプション

input 要素の作成

テキスト入力の作成

FormHelper::text()

パスワード入力の作成

FormHelper::password()

非表示入力の作成

FormHelper::hidden()

テキストエリアの作成

FormHelper::textarea()

セレクト、チェックボックス、ラジオコントロールの作成

セレクト、チェックボックス、ラジオに関するオプション

オプションの作成にコレクションを使用

チェックボックスの作成

FormHelper::checkbox()

ラジオボタンの作成

FormHelper::radio()

選択ピッカーの作成

FormHelper::select()

ファイル入力の作成

FormHelper::file()

日付と時刻に関するコントロールの作成

日付と時刻のコントロールの共通オプション

日付関連コントロールのオプション

時刻関連コントロールのオプション

日時入力の作成

FormHelper::dateTime()

日付コントロールの作成

FormHelper::date()

時間コントロールの作成

FormHelper::time()

年コントロールの作成

FormHelper::year()

月コントロールの作成

FormHelper::month()

時間コントロールの作成

分コントロールの作成

FormHelper::minute()

ラベルの作成

FormHelper::label()

エラーの表示と確認

エラーの表示

FormHelper::error()

エラーの確認

FormHelper::isFieldError()

HTML5 検証メッセージにバリデーションメッセージを表示

ボタンと submit 要素の作成

Submit 要素の作成

FormHelper::submit()

ボタン要素の作成

FormHelper::button()

フォームを閉じる

FormHelper::end()

単独のボタンと POST リンクの作成

POST ボタンの作成

FormHelper::postButton()

POST リンクの作成

FormHelper::postLink()

FormHelper で使用するテンプレートのカスタマイズ

テンプレート一覧

異なるカスタムコントロールコンテナーの使用

異なるカスタムフォームグループの使用

テンプレートにテンプレート変数を追加

チェックボックスとラジオのラベル外への移動

フォーム全体の生成

複数のコントロールの作成

FormHelper::controls()

エンティティー全体のコントロールを作成

FormHelper::allControls()

関連データの入力を作成

独自ウィジェットの追加

Widget クラスの構築

ウィジェットの使用

SecurityComponent との連携

FormHelper::unlockField()

FormHelper::secure()

Html

HtmlHelper

整形式の要素を挿入

文字セットのタグを作成

HtmlHelper::charset()

CSS ファイルへのリンク

HtmlHelper::css()

プログラムによる CSS の作成

HtmlHelper::style()

meta タグの作成

HtmlHelper::meta()

DOCTYPE の作成

HtmlHelper::docType()

画像のリンク

HtmlHelper::image()

リンクの作成

HtmlHelper::link()

動画と音声ファイルのリンク

HtmlHelper::media()

JavaScript ファイルへのリンク

HtmlHelper::script()

インライン Javascript ブロックの作成

HtmlHelper::scriptBlock()

HtmlHelper::scriptStart()

HtmlHelper::scriptEnd()

ネストされたリストの作成

HtmlHelper::nestedList()

テーブルヘッダーを作成

HtmlHelper::tableHeaders()

テーブルのセルを作成

HtmlHelper::tableCells()

HtmlHelper によるタグ出力の変更

HtmlHelper::setTemplates()

HtmlHelper でパンくずリストを作成

HtmlHelper::addCrumb()

HtmlHelper::getCrumbs()

HtmlHelper::getCrumbList()

Number

NumberHelper

通貨フォーマット

NumberHelper::currency()

デフォルト通貨の設定

NumberHelper::defaultCurrency()

浮動小数点数フォーマット

NumberHelper::precision()

パーセンテージフォーマット

NumberHelper::toPercentage()

人が読める形式の値との相互作用

NumberHelper::toReadableSize()

数字の整形

NumberHelper::format()

NumberHelper::ordinal()

差分フォーマット

NumberHelper::formatDelta()

Paginator

PaginatorHelper

PaginatorHelper テンプレート

ファイルからテンプレートをロードする

実行時にテンプレートを変更する

PaginatorHelper::setTemplates()

テンプレート名

ソートリンクの作成

PaginatorHelper::sort()

PaginatorHelper::sortDir()

PaginatorHelper::sortKey()

ページ番号リンクの作成

PaginatorHelper::numbers()

ジャンプ用リンクの作成

PaginatorHelper::prev()

PaginatorHelper::next()

PaginatorHelper::first()

PaginatorHelper::last()

ヘッダーリンクタグの作成

ページ制御状態の確認

PaginatorHelper::current()

PaginatorHelper::hasNext()

PaginatorHelper::hasPrev()

PaginatorHelper::hasPage()

PaginatorHelper::total()

ページカウンターの生成

PaginatorHelper::counter()

ページ制御 URL の生成

PaginatorHelper::generateUrl()

制限セレクトボックスコントロールの作成

PaginatorHelper::limitControl()

ページ制御オプションの設定

PaginatorHelper::options()

使用例

複数の結果の改ページ

Text

TextHelper

メールアドレスのリンク化

TextHelper::autoLinkEmails()

URLのリンク化

TextHelper::autoLinkUrls()

URLとメールアドレス両方のリンク化

TextHelper::autoLink()

テキストの段落化

TextHelper::autoParagraph()

文字列の一部をハイライトする

TextHelper::highlight()

リンク除去

TextHelper::stripLinks()

テキストの切り詰め

TextHelper::truncate()

文字列の末尾を切り詰める

TextHelper::tail()

抜粋の抽出

TextHelper::excerpt()

配列を文章的なものに変換する

TextHelper::toList()

Time

TimeHelper

ヘルパーの使い方

Url

UrlHelper

URL の生成

UrlHelper::build()

ヘルパーの設定

条件付きヘルパーの読み込み

設定オプション

ヘルパーの別名

ヘルパーの使用

ヘルパーの動的ロード

コールバックメソッド

ヘルパーの作成

他のヘルパーの読み込み

独自のヘルパーを使用

ヘルパー内部でビュー変数にアクセス

ヘルパー内部でビューエレメントの描画

Helper クラス

Helper

コールバック

Helper::beforeRenderFile()

Helper::afterRenderFile()

Helper::beforeRender()

Helper::afterRender()

Helper::beforeLayout()

Helper::afterLayout()

データベースアクセス & ORM

簡単な例

詳細

データベースの基本

クイックツアー

サポートしているデータベース

Select 文の実行

Insert 文の実行

Update 文の実行

Delete 文の実行

設定

コネクションの管理

ConnectionManager

コネクションへのアクセス

ConnectionManager::get

実行時にコネクションを生成する

データの型

TypeFactory

DateTime タイプ

DateTimeType

DateTimeType::setTimezone()

DateTimeFractionalType

DateTimeTimezoneType

独自の型を作成する

TypeFactory::map

独自データ型から SQL 表現への変換

イミュータブル DateTime オブジェクトの有効化

Connection クラス

Connection

クエリーの実行

Connection::query()

Connection::execute()

Connection::newQuery()

トランザクションを使う

Connection::transactional()

ステートメントとの対話

ステートメントを準備する

値をバインドする

実行と結果行の取得

行数を取得する

エラーコードをチェックする

クエリーロギング

引用識別子

メタデータ・キャッシング

データベースの作成

クエリービルダー

Query

クエリーオブジェクト

テーブルから行を取得する

テーブルから単一行を取得する

カラムから値リストを取得する

クエリーは Collection オブジェクトである

クエリーの遅延評価

データを select する

特定のフィールドを選択

SQL 関数を使う

カスタム関数

集約 - Group と Having

Case 文

エンティティーの代わりに配列を取得

計算フィールドを追加する

高度な条件

式の中で識別子を使用する

IN 句を自動生成する

IS NULL を自動生成する

IS NOT NULL を自動生成する

未加工の式

結果を取得する

レコードの合計数を返す

ロードされた結果をキャッシュする

関連付くデータをロードする

contain に条件を渡す

関連を含んだソート

関連付くデータでフィルターする

innerJoinWith を使う

notMatching を使う

leftJoinWith を使う

Join を追加する

データを insert する

データを update する

データを delete する

SQL インジェクションを防止する

値のバインディング

より複雑なクエリー

UNION

サブクエリー

ステートメントのロックの追加

複雑なクエリーを実行する

テーブルオブジェクト

基本的な使い方

テーブルが使うエンティティークラスのカスタマイズ

テーブルクラスのインスタンスを取得する

コールバックのライフサイクル

イベント一覧

initialize

Table::initialize()

beforeMarshal

Table::beforeMarshal()

afterMarshal

Table::afterMarshal()

beforeFind

Table::beforeFind()

buildValidator

Table::buildValidator()

buildRules

Table::buildRules()

beforeRules

Table::beforeRules()

afterRules

Table::afterRules()

beforeSave

Table::beforeSave()

afterSave

Table::afterSave()

afterSaveCommit

Table::afterSaveCommit()

beforeDelete

Table::beforeDelete()

afterDelete

Table::afterDelete()

afterDeleteCommit

Table::afterDeleteCommit()

Stopping Table Events

Callback priorities

ビヘイビアー

Table::addBehavior()

接続設定

TableLocator の利用

TableLocator

テーブルオブジェクトの設定

TableLocator::get

レジストリーの初期化

TableLocator::clear()

ORM クラスを配置する名前空間の設定

エンティティー

Entity

エンティティークラスの生成

エンティティー生成

エンティティーのデータへのアクセス

アクセサーとミューテーター

Entity::get()

Entity::set()

仮想プロパティーの生成

エンティティーが変更されたかチェックする

Entity::dirty()

バリデーションエラー

一括代入 (Mass Assignment)

一括代入に対する保護の回避

保護されたフィールドを実行時に変更する

フィールドに対する保護を受け渡す

エンティティーが永続化されているかチェックする

アソシエーションの Lazy ローディング

トレイトを使った再利用可能なコードの生成

配列や JSON への変換

仮想プロパティーが含まれるようにする

フィールドを隠す

複合型の保存

データの取り出しと結果セット

Table

クエリーのデバッグと結果セット

主キーで単一のエンティティーを取得する

Table::get()

データのロードに Finder を使う

Table::find()

１つ目の結果を取得する

結果の件数を取得する

キー/値のペアを検索する

キーと値の出力をカスタマイズ

スレッド状のデータを検索する

カスタム Finder メソッド

動的な Finder

関連付いたデータを取得する

contain を用いた関連データのイーガーロード

contain に条件を渡す

関連を含んだソート

matching と joins を用いた関連データによるフィルタリング

innerJoinWith を使う

notMatching を使う

leftJoinWith を使う

フェッチの戦略の変更する

サブクエリー戦略でフェッチする

関連をレイジーロード(Lazy Load)する

結果セットを使いこなす

ResultSet から最初/最後のレコードを取得する

ResultSet から任意の場所を指定して取得する

Query や ResultSet が空かどうかをチェックする

追加で関連をロードする

結果を Map/Reduce で変更する

複数の操作を stack する(重ねて呼ぶ)

stack されたすべての MapReduce 操作を取り除く

データの検証

エンティティー構築前のデータ検証

既定のバリデーションセットの作成

異なるバリデーションセットの使用

アソシエーションに異なるバリデーションセットを使用

バリデータの組み合わせ

バリデーションプロバイダー

テーブルからのバリデータ取得

既定のバリデータクラス

アプリケーションルールの適用

ルールチェッカーの作成

一意フィールドルールの作成

外部キールール

アソシエーションカウントルール

アソシエーションリンクの制約ルール

エンティティーメソッドをルールとして使用

条件付きルールの使用

条件付き/動的なエラーメッセージ

再利用可能なカスタムルールの作成

カスタムルールオブジェクト作成

ルールの無効化

バリデーション対アプリケーションルール

バリデーションをアプリケーションルールとして使用

データの保存

データ保存の概要

データの挿入

データの更新

アソシエーションの保存

多対多レコードの関連付け

多対多レコードの紐付け解除

リクエストデータのエンティティーへの変換

BelongsToMany データの変換

HasMany データの変換

複数レコードの変換

アクセス可能なフィールドの変更

リクエストデータをエンティティーにマージ

検証と patchEntity

HasMany と BelongsToMany へのパッチ

エンティティー構築前のリクエストデータ変更

エンティティー構築前のデータ検証

プロパティーのマスアサインメント攻撃の回避

エンティティーの保存

Table::save()

アソシエーションの保存

BelongsTo アソシエーションの保存

HasOne アソシエーションの保存

HasMany アソシエーションの保存

BelongsToMany アソシエーションの保存

結合テーブルへの追加データの保存

複雑な型の保存

厳密な保存

Table::saveOrFail()

複数のエンティティーの保存

Table::saveMany()

一括更新

Table::updateAll()

データの削除

Table::delete()

連鎖削除

一括削除

Table::deleteAll()

厳密な削除

Table::deleteOrFail()

アソシエーション - モデル同士を繋ぐ

hasOne アソシエーション

belongsTo アソシエーション

hasMany アソシエーション

belongsToMany アソシエーション

'through' オプションの使用

既定のアソシエーションの条件

アソシエーションの読み出し

ビヘイビアー

ビヘイビアーの利用

コアビヘイビアー

CounterCache

CounterCacheBehavior

基本的な使用方法

高度な使用方法

Timestamp

TimestampBehavior

一般的な使い方

使い方と設定方法

Entity での Timestamp 更新

編集のタイムスタンプ無しで更新の保存

Translate

TranslateBehavior

クイックツアー

i18n データベーステーブルの初期化

Table への Translate ビヘイビアーの追加

別の翻訳テーブルの利用

翻訳された内容の読み込み

エンティティーのすべての翻訳を取得

取得する翻訳を制限

空の翻訳の取得を防止

アソシエーションのすべての翻訳を取得

I18n::setLocale を使用せずに一つの言語の取得

翻訳されたフィールドのクエリー

別の言語で保存

複数の翻訳を保存

翻訳されたエンティティーの検証

Tree

TreeBehavior

必要条件

クイックツアー

設定

ノードレベル (深さ)

スコープとマルチツリー

独自のソートフィールドでのリカバリ

階層データの保存

ノードの削除

ビヘイビアーの生成

ミックスインメソッドの定義

公開されたミックスインメソッドの制限または名前の変更

イベントリスナーの定義

ファインダーの定義

公開されたファインダーメソッドの制限または名前の変更

リクエストデータをエンティティープロパティーに変換する

ロードされたビヘイビアーの削除

ロードされたビヘイビアーへのアクセス

ロードされたビヘイビアーの再構成

スキーマシステム

Schema\TableSchema オブジェクト

TableSchema

カラムデータへのアクセス

インデックスと制約

インデックスと制約の読み込み

テーブルオプションの追加

Table を SQL に変換

スキーマコレクション

Collection

スキーマキャッシュツール

一般的なトピック

キャッシュ

Cache

Cache エンジンの設定

Cache::setConfig

エンジンのオプション

FileEngine オプション

RedisEngine オプション

MemcacheEngine オプション

キャッシュフォールバックの設定

設定されたキャッシュエンジンを削除する

Cache::drop

キャッシュへの書き込み

Cache::write

一度に複数のキーを書き込む

Cache::writeMany

アトミックな書き込み

Cache::add

Read-through キャッシュ

Cache::remember

キャッシュからの読み込み

Cache::read

一度に複数のキーを読み込む

Cache::readMany

キャッシュからの削除

Cache::delete

一度に複数のキーの削除

Cache::deleteMany

キャッシュデータのクリア

Cache::clear

キャッシュを使用してカウンターを保存する

Cache::increment

Cache::decrement

キャッシュを使用して共通のクエリー結果を格納する

グループの使用

Cache::clearGroup()

Cache::groupConfigs

全体的にキャッシュを有効または無効にする

Cache::disable

Cache::enable

Cache::enabled

キャッシュエンジンの作成

CacheEngine

CacheEngine::write()

CacheEngine::read()

CacheEngine::delete()

CacheEngine::clear()

CacheEngine::clearGroup()

CacheEngine::decrement()

CacheEngine::increment()

コンソールコマンド

CakePHP のコンソール

コンソールアプリケーション

コマンド名を変更

コマンド

コマンドオブジェクト

Command

コマンドの作成

デフォルトのコマンド名の変更

引数やオプションの定義

ファイルに出力

コマンド内でのモデルの使用

終了コードと実行停止

他のコマンドの呼び出し

コマンド説明文の設定

コマンドのテスト

対話的なシェルのテスト

CommandRunner のテスト

アサーションメソッド

コマンドの入力と出力

ConsoleIo

コマンドヘルパー

ヘルパーの作成

組み込みヘルパー

Table ヘルパー

Progress ヘルパー

ユーザー入力の取得

ConsoleIo::ask()

ファイルの作成

ConsoleIo::createFile()

出力の作成

出力のレベル

出力のスタイル

カラー表示の無効化

オプションパーサー

ConsoleOptionParser

OptionParser の定義

引数の使用

ConsoleOptionParser::addArgument()

複数の引数の追加

ConsoleOptionParser::addArguments()

引数の検証

オプションの利用

ConsoleOptionParser::addOption()

複数オプションの追加

ConsoleOptionParser::addOptions()

オプションの検証

ブール型オプションの使用

配列から ConsoleOptionParser の構築

ConsoleOptionParser::buildFromArray()

オプションパーサーのマージ

ConsoleOptionParser::merge()

シェルからヘルプを取得

ヘルプを XML で取得

ヘルプの出力をカスタマイズ

説明文の設定

ConsoleOptionParser::setDescription()

エピローグの設定

ConsoleOptionParser::setEpilog()

サブコマンドの追加

ConsoleOptionParser::addSubcommand()

cron ジョブに登録してシェルを実行する

共有ホスティング上の cron ジョブ

CakePHPが提供するコマンド

キャッシュツール

Completion ツール

サブコマンド

commands

subCommands

options

CakePHP コンソール用の Bash 自動補完を有効にする方法

Bash 補完ファイルの内容

自動補完の利用

コマンド

サブコマンド

オプション

I18N ツール

POT ファイルの生成

プラグイン用 POT ファイルの生成

一括で複数のフォルダーを抽出

特定フォルダーを除外

既存の POT ファイルの上書き警告をスキップする

CakePHP コアライブラリーからのメッセージ抽出

Plugin シェル

プラグインのロード

プラグインのアンロード

プラグインのアセット

スキーマキャッシュツール

Routes ツール

すべてのルートの一覧を取得

URL 解析のテスト

URL 生成のテスト

Server ツール

ポートおよびドキュメントルートの変更

インタラクティブ・コンソール (REPL)

シェル

Shell

シェルの作成

シェルのタスク

TaskRegistry による動的なタスクのロード

シェルの中でのモデルの使用

シェルヘルパー

シェルから他のシェルの呼び出し

Shell::dispatchShell()

ディスパッチされたシェルへのパラメーター追加

CLI オプションのパース

入出力との対話

シェルの実行を停止

ステータスとエラーコード

フックメソッド

Shell::initialize()

Shell::startup()

コンソール環境におけるルーティング

デバッグ

基本的なデバッグ

stackTrace()

breakpoint()

Debugger クラスの使用

Debugger

値の出力

Debugger::dump

データのマスク

スタックトレース付きのログ出力

Debugger::log

スタックトレースの生成

Debugger::trace

ファイルから抜粋を取得

Debugger::excerpt

Debugger::getType

ログ出力によるデバッグ

Debug Kit

デプロイ

ファイルの移動

config/app.php の調整

セキュリティのチェック

ドキュメントルートの指定

アプリケーションのパフォーマンス改善

更新のデプロイ

Mailer

Mailer

基本的な使用法

送り主の選択

設定

設定プロファイル

ヘッダーの設定

テンプレートメールの送信

添付ファイルの送信

Mailer::setAttachments()

アドレス検証ルールの緩和

Mailer::setEmailPattern()

メッセージの即時送信

CLI からのメール送信

再利用可能なメールの作成

トランスポートの設定

Mailer::drop

独自のトランスポートの作成

Mailerを利用しないメール送信

メールのテスト

アサーションメソッド

エラーと例外の処理

エラーと例外の設定

非推奨の警告

ExceptionRenderer

例外処理の変更

エラーテンプレートのカスタマイズ

エラーページレイアウトのカスタマイズ

ErrorController のカスタマイズ

ExceptionRenderer の変更

ErrorController クラスの変更

独自エラーハンドラーの作成

致命的エラーのふるまい変更

Custom Error Logging

独自アプリケーション例外の作成

例外のログ記録

CakePHP 用の組み込みの例外

HTTP の例外

BadRequestException

UnauthorizedException

ForbiddenException

InvalidCsrfTokenException

NotFoundException

MethodNotAllowedException

NotAcceptableException

ConflictException

GoneException

InternalErrorException

NotImplementedException

ServiceUnavailableException

コントローラー中での HTTP の例外の使用

その他の組み込みの例外

MissingViewException

MissingTemplateException

MissingLayoutException

MissingHelperException

MissingElementException

MissingCellException

MissingCellViewException

MissingComponentException

MissingActionException

PrivateActionException

ConsoleException

MissingTaskException

MissingShellException

MissingShellMethodException

MissingConnectionException

MissingDriverException

MissingExtensionException

MissingTableException

MissingEntityException

MissingBehaviorException

PersistenceFailedException

RecordNotFoundException

MissingControllerException

MissingRouteException

MissingDispatcherFilterException

Exception

Exception::responseHeader()

イベントシステム

イベントの使用例

イベントマネージャーへのアクセス

グローバルイベントマネージャー

イベントの追跡

コアイベント

リスナーの登録

無名リスナーの登録

既存のリスナーとの対話

優先順位の設定

イベントデータを関数のパラメーターとして取得

イベントのディスパッチ

イベントの中止

イベントの結果の取得

コールバック及びリスナーの削除

その他の情報

国際化と地域化

翻訳の準備

言語ファイル

I18n を利用して Pot ファイルを生成する

デフォルトのロケールを設定する

実行時にロケールを変更する

翻訳の機能を利用する

翻訳メッセージで変数を利用する

複数形

ICU の複数形選択を利用する

Gettext の複数形選択を使用する

独自の翻訳機構を作成する

メッセージのパーサーを作成する

包括的な翻訳機構を作成する

独自の翻訳機構における複数形と文脈について

異なるフォーマット機構を使う

日付や数値を地域化する

地域化された日時データをパースする

リクエストデータをユーザーのタイムゾーンから変換する

自動でリクエストデータに基づいたロケールを選択する

コンテンツ／エンティティーの翻訳

ロギング

ロギング設定

ログエンジンの作成

ロギングフォーマッタ

エラーと例外のロギング

ログストリームの相互作用

FileLog アダプターの利用

Syslog へのロギング

ログへの書き込み

レベルを使う

ロギングスコープ

Log API

Log

Log::setConfig

Log::configured

Log::drop

Log::write

Log::levels

便利なメソッド

Log::emergency

Log::alert

Log::critical

Log::error

Log::warning

Log::notice

Log::info

Log::debug

ロギングトレイト

LogTrait

LogTrait::log()

Monolog を使用する

モデルのないフォーム

Form

フォームの作成

リクエストデータの処理

フォーム値の設定

フォームエラーの取得

コントローラーから各フォームフィールドを無効化

FormHelper で HTML 作成

ページネーション

PaginatorComponent

基本的な使用方法

高度な使用方法

シンプルなページネーション

PaginatorComponent を直接使用する

複数のクエリのページ分割

同じモデルを複数回ページ分割する

ソート時に使用するフィールドの制御

1ページあたりの最大行数を制限する

追加の関連付けへのジョイン

範囲外のページ要求

ビューのページネーション

プラグイン

Composer を使ったプラグインのインストール

プラグインの手動インストール

プラグインクラスを手動で自動読み込み

プラグインの読み込み

プラグインフックの設定

プラグインの利用

独自プラグインの作成

Bake を使ってプラグインを作成

Plugin オブジェクト

プラグインのルート

プラグインのコントローラー

プラグインのモデル

プラグインのビュー

アプリケーション内からプラグインのテンプレートを上書き

プラグインのアセット

プラグイン内のアセットへのリンク

コンポーネント、ヘルパーとビヘイビアー

コマンド

プラグインのテスト

プラグインの公開

プラグイン マップ ファイル

Mixer を使用したプラグインの管理

REST

簡単なセットアップ

他のフォーマットのインプットデータ

RESTful ルーティング

セキュリティ

セキュリティユーティリティ

Security

データの暗号化と復号

Security::encrypt

Security::decrypt

特定の暗号化を実装を選択

データのハッシュ化

Security::hash

セキュアなランダムデータの取得

Security::randomBytes

Security::randomString

Form Protection Middleware

FormProtection

フォームの改ざん防止

使用方法

フォームの改ざんチェックを無効にする

特定のアクションのためにフォームの改ざんを無効にする

コールバックによる検証失敗の処理

CSRF Protection

CSRFミドルウェア（クロスサイトリクエストフォージェリミドルウェア）

CookieベースのCSRFミドルウェアオプション

セッションベースのCSRFミドルウェアオプション

特定のアクションのCSRFチェックをスキップする

フォームヘルパーとの統合

CSRFプロテクションとAJAXリクエスト

Content Security Policy

Security Headers

HTTPS Enforcer

セッション

セッションの設定

ビルトインセッションハンドラーと設定

セッションハンドラー

データーベースセッション

キャッシュセッション

ini ディレクティブの設定

カスタムセッションハンドラーの作成

Session

セッションオブジェクトへのアクセス

セッションデータの読込みと書込み

Session::read()

Session::readOrFail()

Session::write()

Session::delete()

Session::consume

Session::check()

セッションの破棄

Session::destroy()

セッション ID の切替え

Session::renew()

フラッシュメッセージ

テスト

PHPUnit のインストール

Composer による PHPUnit のインストール

PHAR ファイルを使用する場合

テスト用データベースのセットアップ

テストのセットアップの確認

テストケースの規約

最初のテストケースを作成

テストの実行

テストケースのフィルタリング

コードカバレッジの生成

プラグインテストのためのテストスイート

テストケースのライフサイクルコールバック

フィクスチャー

テスト接続

PHPUnitの設定

テスト用のデータベーススキーマ作成

フィクスチャステートマネージャ

フィクスチャーの作成

動的データ

テストケースにフィクスチャを読み込む

フィクスチャファクトリー

テストでルーティング設定を読み込む

テストにおけるルーティングの作成

プラグインをロード

テーブルクラスのテスト

テストメソッドの作成

モデルメソッドのモック化

コントローラーの統合テスト

リクエストの設定

認証が必要なアクションのテスト

ステートレス認証と API のテスト

CsrfComponent や SecurityComponent で保護されたアクションのテスト

PSR-7 ミドルウェアの統合テスト

暗号化されたクッキーを使用したテスト

フラッシュメッセージのテスト

JSON を返すコントローラーのテスト

ファイルアップロードのテスト

テスト中のエラー処理ミドルウェアの無効化

アサーションメソッド

ファイルへのテスト結果を比較

コンソールの統合テスト

Mocking Injected Dependencies

ビューのテスト

コンポーネントのテスト

ヘルパーのテスト

イベントのテスト

メールのテスト

テストスイートの作成

プラグインのテスト作成

Bake でのテストの生成

バリデーション

バリデーターを作成する

Validator

フィールドが実在することを求める

空のフィールドを認める

バリデーションルールの追加

カスタムバリデーションルールの使用

条件付き/動的なエラーメッセージ

条件付バリデーション

最後に適用されるルールとして設定する

バリデーションプロバイダーを加える

バリデーターをネストする

再利用可能なバリデーターを作成する

データをバリデートする

エンティティーをバリデーションする

コアバリデーションルール

ユーティリティ

Appクラス

App

クラスの検索

App::className

Finding Paths to Resources

App::path

名前空間のパスの検索

App::classPath

App::core

テーマの検出

ベンダーファイルの読込

コレクション

Collection

簡単な例

メソッド一覧

反復

Collection::each()

Collection::map()

Collection::extract()

Collection::combine()

Collection::stopWhen()

Collection::unfold()

Collection::chunk()

Collection::chunkWithKeys()

フィルタリング

Collection::filter()

Collection::reject()

Collection::every()

Collection::some()

Collection::match()

Collection::firstMatch()

集約

Collection::reduce()

Collection::min()

Collection::max()

Collection::sumOf()

Collection::avg()

Collection::median()

グループ化とカウント

Collection::groupBy()

Collection::countBy()

Collection::indexBy()

Collection::zip()

ソート

Collection::sortBy()

ツリーデータの操作

Collection::nest()

Collection::listNested()

その他のメソッド

Collection::isEmpty()

Collection::contains()

Collection::shuffle()

Collection::transpose()

要素の取り出し

Collection::sample()

Collection::take()

Collection::skip()

Collection::first()

Collection::last()

コレクションの拡張

Collection::append()

Collection::appendItem()

Collection::prepend()

Collection::prependItem()

要素の更新

Collection::insert()

コレクションメソッドの再利用

Collection::through()

コレクションの最適化

Collection::buffered()

巻き戻し可能なコレクションの作成

コレクションの複製

Collection::compile()

Folder & File

基本的な使用法

Folder API

Folder

Folder::$path

Folder::$sort

Folder::$mode

Folder::addPathElement

Folder::cd()

Folder::chmod()

Folder::copy()

Folder::correctSlashFor

Folder::create()

Folder::delete()

Folder::dirsize()

Folder::errors()

Folder::find()

Folder::findRecursive()

Folder::inCakePath()

Folder::inPath()

Folder::isAbsolute

Folder::isSlashTerm

Folder::isWindowsPath

Folder::messages()

Folder::move()

Folder::normalizePath

Folder::pwd()

Folder::read()

Folder::realpath()

Folder::slashTerm

Folder::tree()

File API

File

File::$Folder

File::$name

File::$info

File::$handle

File::$lock

File::$path

File::append()

File::close()

File::copy()

File::create()

File::delete()

File::executable()

File::exists()

File::ext()

File::Folder()

File::group()

File::info()

File::lastAccess()

File::lastChange()

File::md5()

File::name()

File::offset()

File::open()

File::owner()

File::perms()

File::prepare

File::pwd()

File::read()

File::readable()

File::safe()

File::size()

File::writable()

File::write()

File::mime()

File::replaceText()

Hash

Hash

Hash パス構文

式の種類

属性の絞り込み種別

Hash::get

Hash::extract

Hash::insert

Hash::remove

Hash::combine

Hash::format

Hash::contains

Hash::check

Hash::filter

Hash::flatten

Hash::expand

Hash::merge

Hash::numeric

Hash::dimensions

Hash::maxDimensions

Hash::map

Hash::reduce

Hash::apply

Hash::sort

Hash::diff

Hash::mergeDiff

Hash::normalize

Hash::nest

Http Client

Client

リクエストの実行

ファイルを使用したマルチパートリクエストの作成

手動でマルチパートリクエストのボディーを構築

リクエストボディーを送信

リクエストメソッドのオプション

認証

Basic 認証の使用

ダイジェスト認証の使用

OAuth 1 認証

OAuth 2 認証

プロキシー認証

スコープ指定クライアントの作成

クッキーの設定と管理

レスポンスオブジェクト

Response

レスポンスボディーの読み込み

JSON や XML レスポンスボディーの読み込み

レスポンスヘッダーへのアクセス

クッキーデータへのアクセス

ステータスコードの確認

Changing Transport Adapters

Testing

HttpClientTrait

HttpClientTrait::newClientResponse()

Inflector

Inflector

Inflector メソッドの概要と出力

複数形と単数形の作成

Inflector::singularize

Inflector::pluralize

キャメルケースやアンダースコアーの作成

Inflector::camelize

Inflector::underscore

人間が読みやすい形式の作成

Inflector::humanize

テーブル名やクラス名の作成

Inflector::classify

Inflector::dasherize

Inflector::tableize

変数名の作成

Inflector::variable

Inflection の設定

カスタム Inflection のロード

Inflector::rules

Number

Number

通貨フォーマット

Number::currency()

デフォルト通貨の設定

Number::defaultCurrency()

浮動小数点数フォーマット

Number::precision()

パーセンテージフォーマット

Number::toPercentage()

人が読める形式の値との相互作用

Number::toReadableSize()

数字の整形

Number::format()

Number::ordinal()

差分フォーマット

Number::formatDelta()

フォーマッター設定

Number::config()

Plugin Class

Plugin

Locating Plugins

Plugin::path

Check if a Plugin is Loaded

Finding Paths to Namespaces

Plugin::classPath

Finding Paths to Resources

Plugin::templatePath

レジストリーオブジェクト

オブジェクトのロード

コールバックトリガー

コールバックの無効化

Text

Text

ASCII 文字への変換

Text::transliterate

URL に安全な文字列の作成

Text::slug

UUID の生成

Text::uuid

単純な文字列のパース

Text::tokenize

Text::parseFileSize()

文字列のフォーマット

Text::insert

Text::cleanInsert

テキストの改行

Text::wrap

Text::wrapBlock

文字列の一部をハイライトする

Text::highlight()

リンク除去

Text::stripLinks()

テキストの切り詰め

Text::truncate()

文字列の末尾を切り詰める

Text::tail()

抜粋の抽出

Text::excerpt()

配列を文章的なものに変換する

Text::toList()

日付と時刻

FrozenTime

Time インスタンスを作成する

操作

フォーマットする

FrozenTime::setJsonEncodeFormat

FrozenTime::i18nFormat()

FrozenTime::nice()

デフォルトのロケールとフォーマット文字列を設定する

相対時間のフォーマットについて

FrozenTime::timeAgoInWords()

変換

FrozenTime::toQuarter()

現在と比較する

FrozenTime::isYesterday()

FrozenTime::isThisWeek()

FrozenTime::isThisMonth()

FrozenTime::isThisYear()

間隔を比較する

FrozenTime::isWithinNext()

FrozenTime::wasWithinLast()

FrozenDate

Mutable Dates and Times

Time

Date

地域化されたリクエストデータの受け入れ

サポートされるタイムゾーン

Xml

Xml

データを Xml クラスにインポートする

Xml::build

XML 文字列を配列に変換する

配列を XML 文字列に変換する

名前空間を利用する

子要素を作成

プラグインとパッケージ

スタンドアロンパッケージ

ORM

Database

Datasource

HTTP

Console

Collection

I18n

Cache

Log

Event

Utility

Validation

Form

Authentication [https://book.cakephp.org/authentication/2/ja/]

Authorization [https://book.cakephp.org/authorization/2/]

Bake [https://book.cakephp.org/bake/2/ja/]

Debug Kit [https://book.cakephp.org/debugkit/4/ja/]

Migrations [https://book.cakephp.org/migrations/3/ja/]

Elasticsearch [https://book.cakephp.org/elasticsearch/3/ja/]

Phinx [https://book.cakephp.org/phinx/0/ja/]

Chronos [https://book.cakephp.org/chronos/2/ja/]

Queue [https://book.cakephp.org/queue/1/en/]

その他

定数および関数

グローバル関数

__d()

__dn()

__dx()

__dxn()

__n()

__x()

__xn()

collection()

debug()

dd()

pr()

pj()

env()

h()

pluginSplit()

namespaceSplit()

コア定義定数

APP

APP_DIR

CACHE

CAKE

CAKE_CORE_INCLUDE_PATH

CONFIG

CORE_PATH

DS

LOGS

ROOT

TESTS

TMP

WWW_ROOT

時間定義定数

TIME_START

SECOND

MINUTE

HOUR

DAY

WEEK

MONTH

YEAR

付録

4.x 移行ガイド

4.0 移行ガイド

非推奨機能の削除

非推奨

Component

Filesystem

ORM

View

Mailer

App

破壊的変更

Cache

Controller

Console

Component

Database

Datasources

Error

Event

Http

HttpSession

I18n

Mailer

ORM

Router

TestSuite

Utility

View

Helper

Log

その他

新機能

Console

Core

Database

Error

Http

I18n

Mailer

ORM

Routing

TestSuite

Validation

View

4.1 移行ガイド

4.1.0へのアップグレード

非推奨

Controller

Database

Datasource

Form

Http

ORM

Routing

View

Behaviorの変更

Database

Http

ORM

TestSuite

View

新機能

Datasource

Console

Database

Error

Form

Http

I18n

Log

ORM

Routing

TestSuite

View

Utility

4.2 移行ガイド

4.2.0へのアップグレード

非推奨

Core

Database

ORM

Behaviorの変更

Collection

Controller

Component

Database

Error

Validation

破壊的変更

I18n

テスト

新機能

Core

Console

Database

Http

ORM

TestSuite

View

4.3 移行ガイド

4.3.0へのアップグレード

非推奨

Connection

Controller

Database

I18n

Log

Middleware

Network

ORM

Routing

TestSuite

View

Behaviorの変更

Collection

Command

Core

Database

Datasource

Http

ORM

Routing

TestSuite

Validation

View

破壊的変更

Log

View

新機能

Controller

Core

Database

Form

Http

Log

ORM

TestSuite

View

フィクスチャのアップグレード

レガシーなフィクスチャのドキュメント

フィクスチャのスキーマ

テーブル情報のインポート

後方互換性の補完

前方互換性の補完

一般的な情報

CakePHP の開発プロセス

用語集

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ようこそ

CakePHP 4 は、 PHP 8.2 (最小は PHP 7.4) 上で動作するウェブ開発フレームワークです。 CakePHP の基礎に入門するには CakePHP 概要 を読んでください。

CakePHP クックブックは、オープンに開発されている、コミュニティーで編集可能なドキュメントのプロジェクトです。ドキュメンテーションの品質、正当性、正確性の高さを維持することを期待しています。右端に固定された鉛筆アイコンボタンに注目してください。このボタンは、あなたがドキュメンテーションの追加・削除・修正などの貢献が簡単にできるように、該当ページの GitHub のオンラインエディターへ誘導します。

公式ドキュメントがどこででも読める！

どこでも CakePHP のレシピをお楽しみいただけます。PDF と EPUB をご用意しましたので、多くのデバイス上でオフラインでドキュメントを読むことができます。

PDF (英語)

EPUB

オリジナルソース [https://github.com/cakephp/docs]

助けを得るには

もし何か困っていたら、 いくつもの助けを得られる場所があります。

はじめに

新しいフレームワークを学ぶことは、不安と同時にワクワクします。あなたの役に立つために、共通のタスクをやり遂げるための例やレシピをまとめたクックブックを作成しました。もし、あなたが初心者の場合、 CakePHP は何を提供できて、どのように動作するのかのクイックツアーを体験するために クイックスタートガイド から始めましょう。

クイックスタートチュートリアルを終えた後、 CakePHP アプリケーションの重要な要素の復習ができます。

CakePHP のリクエストサイクル

CakePHP が使用する 規約

コントローラー は、リクエストを処理し、あなたのモデルとあなたのアプリケーションが作成したレスポンスを調整します。

ビュー は、あなたのアプリケーションのプレゼンテーション層です。あなたのアプリケーションに必要な HTML, JSON, その他の出力を作成する強力な道具になります。

モデル は、アプリケーションの重要な要素です。バリデーションやあなたのアプリケーションのドメインロジックを処理します。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Contents

CakePHP 概要

設定より規約

モデル層

ビュー層

コントローラーレイヤー

CakePHP のリクエストサイクル

さっそく始めましょう

付録

クイックスタートガイド

コンテンツ管理チュートリアル

CMS チュートリアル - データベース作成

CMS チュートリアル - Articles コントローラーの作成

移行ガイド

4.0 アップグレードガイド

4.0 移行ガイド

4.1 移行ガイド

4.2 移行ガイド

4.3 移行ガイド

4.4 移行ガイド

チュートリアルと例

コンテンツ管理チュートリアル

CMS チュートリアル - データベース作成

CMS チュートリアル - Articles コントローラーの作成

CMS チュートリアル - タグとユーザー

CMS チュートリアル - 認証

CMS Authorization

貢献

ドキュメント

チケット

コード

コーディング規約

後方互換性ガイド

インストール

システム要件

CakePHP のインストール

パーミッション

開発サーバー

公開用

始動

URL Rewriting

構成設定

アプリケーションの設定

環境変数

追加のクラスパス

Inflection の設定

Configure クラス

設定ファイルの読み書き

汎用テーブルの無効化

ルーティング

RouterBuilder

クイックツアー

ルートを接続

RESTful なルーティング

渡された引数

URL の生成

リダイレクトルーティング

エンティティールーティング

カスタムルートクラス

永続的な URL パラメーターの生成

URL 内での名前付きパラメーターの扱い

リクエストとレスポンスオブジェクト

リクエスト

レスポンス

クロスオリジンリクエストヘッダー（CORS）の設定

不変レスポンスに伴うよくある失敗

クッキーコレクション

コントローラー

Controller

AppController

リクエストの流れ

コントローラーのアクション

ビューとの相互作用

コンテンツタイプのネゴシエーション

コンテンツタイプネゴシエーションのフォールバック

他のページへのリダイレクト

追加のモデル読み込み

モデルのページネーション

コンポーネント読み込みの設定

ヘルパー読み込みの設定

リクエストライフサイクルコールバック

Controller Middleware

コントローラーのより詳細

ビュー

View

App ビュー

ビューテンプレート

ビューブロックの使用

レイアウト

エレメント

ビューイベント

独自のビュークラス作成

ビューのより詳細

データベースアクセス & ORM

簡単な例

詳細

AuthComponent

AuthComponent

以降を読む前に

認証

認可

設定オプション

AuthComponent で保護されたアクションのテスト

キャッシュ

Cache

Cache エンジンの設定

キャッシュへの書き込み

キャッシュからの読み込み

キャッシュからの削除

キャッシュデータのクリア

キャッシュを使用してカウンターを保存する

キャッシュを使用して共通のクエリー結果を格納する

グループの使用

全体的にキャッシュを有効または無効にする

キャッシュエンジンの作成

Bake コンソール

コンソールコマンド

CakePHP のコンソール

コンソールアプリケーション

コマンド名を変更

コマンド

CakePHPが提供するコマンド

コンソール環境におけるルーティング

デバッグ

基本的なデバッグ

Debugger クラスの使用

値の出力

スタックトレース付きのログ出力

スタックトレースの生成

ファイルから抜粋を取得

ログ出力によるデバッグ

Debug Kit

デプロイ

ファイルの移動

config/app.php の調整

セキュリティのチェック

ドキュメントルートの指定

アプリケーションのパフォーマンス改善

更新のデプロイ

Mailer

Mailer

基本的な使用法

設定

ヘッダーの設定

テンプレートメールの送信

添付ファイルの送信

メッセージの即時送信

CLI からのメール送信

再利用可能なメールの作成

トランスポートの設定

Mailerを利用しないメール送信

メールのテスト

アサーションメソッド

エラーと例外の処理

エラーと例外の設定

例外処理の変更

エラーテンプレートのカスタマイズ

ErrorController のカスタマイズ

ExceptionRenderer の変更

独自エラーハンドラーの作成

Custom Error Logging

独自アプリケーション例外の作成

CakePHP 用の組み込みの例外

イベントシステム

イベントの使用例

イベントマネージャーへのアクセス

コアイベント

リスナーの登録

イベントのディスパッチ

その他の情報

国際化と地域化

翻訳の準備

翻訳の機能を利用する

独自の翻訳機構を作成する

日付や数値を地域化する

自動でリクエストデータに基づいたロケールを選択する

コンテンツ／エンティティーの翻訳

ロギング

ロギング設定

ログエンジンの作成

エラーと例外のロギング

ログストリームの相互作用

FileLog アダプターの利用

Syslog へのロギング

ログへの書き込み

Log API

ロギングトレイト

Monolog を使用する

モデルのないフォーム

Form

フォームの作成

リクエストデータの処理

フォーム値の設定

フォームエラーの取得

コントローラーから各フォームフィールドを無効化

FormHelper で HTML 作成

ページネーション

PaginatorComponent

基本的な使用方法

高度な使用方法

シンプルなページネーション

PaginatorComponent を直接使用する

複数のクエリのページ分割

同じモデルを複数回ページ分割する

ソート時に使用するフィールドの制御

1ページあたりの最大行数を制限する

追加の関連付けへのジョイン

範囲外のページ要求

ビューのページネーション

プラグイン

Composer を使ったプラグインのインストール

プラグインの手動インストール

プラグインの読み込み

プラグインフックの設定

プラグインの利用

独自プラグインの作成

Plugin オブジェクト

プラグインのルート

プラグインのコントローラー

プラグインのモデル

プラグインのビュー

プラグインのアセット

コンポーネント、ヘルパーとビヘイビアー

コマンド

プラグインのテスト

プラグインの公開

プラグイン マップ ファイル

Mixer を使用したプラグインの管理

REST

簡単なセットアップ

他のフォーマットのインプットデータ

RESTful ルーティング

セキュリティ

セキュリティユーティリティ

Form Protection Middleware

CSRF Protection

Content Security Policy

Security Headers

HTTPS Enforcer

セッション

セッションの設定

ビルトインセッションハンドラーと設定

ini ディレクティブの設定

カスタムセッションハンドラーの作成

セッションオブジェクトへのアクセス

セッションデータの読込みと書込み

セッションの破棄

セッション ID の切替え

フラッシュメッセージ

テスト

PHPUnit のインストール

テスト用データベースのセットアップ

テストのセットアップの確認

テストケースの規約

最初のテストケースを作成

テストの実行

テストケースのライフサイクルコールバック

フィクスチャー

テーブルクラスのテスト

コントローラーの統合テスト

コンソールの統合テスト

Mocking Injected Dependencies

ビューのテスト

コンポーネントのテスト

ヘルパーのテスト

イベントのテスト

メールのテスト

テストスイートの作成

プラグインのテスト作成

Bake でのテストの生成

バリデーション

バリデーターを作成する

データをバリデートする

エンティティーをバリデーションする

コアバリデーションルール

Appクラス

App

クラスの検索

Finding Paths to Resources

名前空間のパスの検索

テーマの検出

ベンダーファイルの読込

コレクション

Collection

簡単な例

メソッド一覧

反復

フィルタリング

集約

ソート

ツリーデータの操作

その他のメソッド

Folder & File

基本的な使用法

Folder API

File API

Hash

Hash

Hash パス構文

Http Client

Client

リクエストの実行

ファイルを使用したマルチパートリクエストの作成

リクエストボディーを送信

リクエストメソッドのオプション

認証

スコープ指定クライアントの作成

クッキーの設定と管理

レスポンスオブジェクト

Changing Transport Adapters

Testing

Inflector

Inflector

Inflector メソッドの概要と出力

複数形と単数形の作成

キャメルケースやアンダースコアーの作成

人間が読みやすい形式の作成

テーブル名やクラス名の作成

変数名の作成

Inflection の設定

Number

Number

通貨フォーマット

デフォルト通貨の設定

浮動小数点数フォーマット

パーセンテージフォーマット

人が読める形式の値との相互作用

数字の整形

差分フォーマット

フォーマッター設定

レジストリーオブジェクト

オブジェクトのロード

コールバックトリガー

コールバックの無効化

Text

Text

ASCII 文字への変換

URL に安全な文字列の作成

UUID の生成

単純な文字列のパース

文字列のフォーマット

テキストの改行

文字列の一部をハイライトする

リンク除去

テキストの切り詰め

文字列の末尾を切り詰める

抜粋の抽出

配列を文章的なものに変換する

日付と時刻

FrozenTime

Time インスタンスを作成する

操作

フォーマットする

変換

現在と比較する

間隔を比較する

FrozenDate

Mutable Dates and Times

地域化されたリクエストデータの受け入れ

サポートされるタイムゾーン

Xml

Xml

データを Xml クラスにインポートする

XML 文字列を配列に変換する

配列を XML 文字列に変換する

定数および関数

グローバル関数

コア定義定数

時間定義定数

Chronos

Debug Kit

Migrations

ElasticSearch

付録

4.x 移行ガイド

後方互換性の補完

前方互換性の補完

一般的な情報

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Phinx マイグレーション

このページは 移動しました [https://book.cakephp.org/phinx/0/ja/] 。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

リリースポリシー

CakePHPは、すべてのリリースにおいてセマンティックバージョニングを採用します。これは ** major.minor.patch ** という形式のバージョン管理規則です。

開発チームは、各リリースが以下の制限と保証に従うことを保証するよう努めます。

メジャーリリース

一般的にメジャーリリースは下位互換性がありません。CakePHPはメジャーリリースにおいて全体的な特徴を変更しないように配慮しますが、APIは変更されます。

メジャーリリースにおいては、さまざまな変更の可能性がありますが、常に行われるのは非推奨機能の削除とインターフェイスの改善です。

各メジャーリリースには通常、アップグレードガイドと、 Rector を使用したさまざまな自動コードアップグレードが付属します。

マイナーリリース

一般的にマイナーリリースは、以前のマイナーリリースおよびパッチリリースに対し、下位互換性があります。

機能が非推奨になる可能性はありますが、削除されることはありません。

インターフェイスは変更されませんが、CakePHPによって提供される実装で公開される新しいメソッドにアノテーションが追加される可能性があります。

新機能は通常、マイナーリリースでのみ追加されるため、ユーザーは移行に関する注意事項を追跡することができます。新機能には、動作が修正されたとき、または不具合が報告されたときにスローされる新しい例外を含めることもできます。

ドキュメンテーションを必要とする動作の変更はマイナーリリースで行われますが、通常は下位互換性があります。ただし問題が深刻な場合は、例外もあります。

パッチリリース

パッチリリースには常に下位互換性があります。不具合修正のみが含まれます。

通常、ユーザーは、動作を変更せずに不具合修正を行なうパッチリリースに依存できる必要があります。

長年の動作を変更してしまうような問題は、通常、パッチリリースにはありません。それは動作の変更と見なされ、ユーザーが移行できるようにマイナーリリースまたはメジャーリリースのいずれかの形で提供されます。

実験的な機能

APIの変更が進行している箇所に新しい機能を追加する場合、 experimental として実験的なマークを付けることができます。

実験的な機能は、同じマイナーリリースおよびバグ修正リリースの規則に従うものとします。ただし、APIの変更により動作が大幅に変わる場合は、次のマイナーリリースに組み込まれる可能性があります。

ユーザーは、これらの実験的な機能が正式にリリースされる前に、 APIが変更されることを予想することができます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

スタンドアロンパッケージ

CakePHPコアは、独立して使用できる複数のスタンドアロンパッケージに分割されています。

ORM [https://github.com/cakephp/orm]

DataMapperパターンを使用して実装された、柔軟・軽量で強力なORMです。

Database [https://github.com/cakephp/database]

PHPデベロッパーにはなじみが深いPDOライクなAPIを備えた、柔軟で強力なデータベース抽象化ライブラリ。

Datasource [https://github.com/cakephp/datasource]

さまざまなデータストアで再利用できる、エンティティとクエリのコネクション管理とトレイトを提供します。

HTTP [https://github.com/cakephp/http]

PSR-18・PSR-15準拠のHTTPクライアントおよびサーバーライブラリ。

Console [https://github.com/cakephp/console]

一連のコマンドからコマンドラインアプリケーションを構築するためのライブラリ。

Collection [https://github.com/cakephp/collection]

配列またはTraversableなオブジェクトを操作するためのツールのセットを提供するライブラリ。

I18n [https://github.com/cakephp/i18n]

日時データと数字の文字列変換とローカリゼーションのサポートを提供します。

Cache [https://github.com/cakephp/cache]

ファイル・Memcached・Redisなどさまざまなサーバーサイドキャッシュをサポートする PSR-16 準拠のキャッシュ制御ライブラリ。

Log [https://github.com/cakephp/log]

複数の異なるストリームをサポートするPSR-3準拠のログライブラリ。

Event [https://github.com/cakephp/event]

イベントディスパッチャライブラリ。

Utility [https://github.com/cakephp/utility]

Inflector、Text、Hash、Security、Xmlなどのユーティリティクラス。

Validation [https://github.com/cakephp/validation]

CakePHPのバリデーションライブラリ。

Form [https://github.com/cakephp/form]

ORMでサポートされているモデルや、他の永続的なデータストアに関連付けられていないフォームを作成するために使用することができるフォームアブストラクション。通常の問い合わせフォーム作成や、APIサービスを使ったフォーム実装などに便利。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

CakePHP の利用

CakePHP のすべての重要な部分の紹介

インストール

構成設定

ルーティング

リクエストとレスポンスオブジェクト

コントローラー

コンポーネント

ページコントローラー

ビュー

ビューセル

ヘルパー

JSON と XML ビュー

データベースアクセス & ORM

テーブルオブジェクト

クエリービルダー

エンティティー

エラーと例外の処理

キャッシュ

ロギング

モデルのないフォーム

セッション

REST

AuthComponent

ページネーション

CSRFミドルウェア（クロスサイトリクエストフォージェリミドルウェア）

Mailer

Form

Html

バリデーション

テスト

デプロイ

コンソールコマンド

貢献

チュートリアルと例

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Bake の拡張

このページは 移動しました [https://book.cakephp.org/bake/2.x/ja/development.html] 。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Bake でコード生成

このページは 移動しました [https://book.cakephp.org/bake/2.x/ja/usage.html] 。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

ミドルウェア

ミドルウェアオブジェクトは、再利用可能で構成可能なリクエスト処理、あるいはレスポンス構築処理の層でアプリケーションを「ラップ」する機能を提供します。視覚的には、アプリケーションは中央で終了し、ミドルウェアはタマネギのようにアプリの周囲を包み込みます。ここでは、Routes、Assets、例外処理、および CORS ヘッダーミドルウェアでラップされたアプリケーションを確認できます。

アプリケーションによってリクエストが処理されると、最も外側のミドルウェアからリクエストが入力されます。各ミドルウェアは、リクエスト/レスポンスを次のレイヤーに委譲するか、またはレスポンスを返すことができます。レスポンスを返すことで、下位層がリクエストを見なくなります。たとえば、開発中にプラグインの画像のリクエストを処理する AssetMiddleware がその例です。

ミドルウェアがリクエストを処理するアクションを受け取らない場合、コントローラーが用意され、そのアクションを呼び出すか、例外が発生してエラーページが生成されます。

ミドルウェアは CakePHP における新しい HTTP スタックの部分で、 PSR-7 のリクエストおよびレスポンスのインターフェイスを活用しています。 PSR-7 標準の活用によって、 Packagist [https://packagist.org] で利用可能な、あらゆる PSR-7 互換のミドルウェアを使うことができます。

CakePHP のミドルウェア

CakePHP はウェブアプリケーションの一般的なタスクを処理するいくつかのミドルウェアを提供します。

Cake\Error\Middleware\ErrorHandlerMiddleware はラップされたミドルウェアからくる例外を捕まえ、 エラーと例外の処理 の例外ハンドラーを使ってエラーページを描画します。

Cake\Routing\AssetMiddleware はリクエストが、プラグインの webroot フォルダーあるいはテーマのそれに格納された CSS 、 JavaScript または画像ファイルといった、テーマまたはプラグインのアセットファイルを参照するかどうかを確認します。

Cake\Routing\Middleware\RoutingMiddleware は受け取った URL を解析して、リクエストにルーティングパラメーターを割り当てるために Router を使用します。

Cake\I18n\Middleware\LocaleSelectorMiddleware はブラウザーによって送られる Accept-Language ヘッダーによって自動で言語を切り替えられるようにします。

Cake\Http\Middleware\HttpsEnforcerMiddleware の利用はHTTPSを要求します。

Cake\Http\Middleware\SecurityHeadersMiddleware は、 X-Frame-Options のようなセキュリティに関連するヘッダーをレスポンスに簡単に追加することができます。

Cake\Http\Middleware\EncryptedCookieMiddleware は、難読化されたデータで Cookie を操作する必要がある場合に備えて、暗号化された Cookie を操作する機能を提供します。

Cake\Http\Middleware\CsrfProtectionMiddleware は、アプリケーションに CSRF 保護を追加します。

Cake\Http\Middleware\BodyParserMiddleware は Content-Type ヘッダーに基づいて JSON, XML, その他のエンコードされたリクエストボディーをデコードすることができます。

Cake\Http\Middleware\CspMiddleware を使用すると、アプリケーションに Content-Security-Policy ヘッダを追加するのがより簡単になります。

ミドルウェアの使用

ミドルウェアは、アプリケーションの全体、または個々のルーティングスコープに適用できます。

全てのリクエストにミドルウェアを適用するには、 App\Application クラスの middleware メソッドを使用してください。アプリケーションの middleware フックメソッドはリクエスト処理の開始時に呼ばれて、 MiddlewareQueue オブジェクトを加えることができます。

namespace App;

use Cake\Http\BaseApplication;
use Cake\Http\MiddlewareQueue;
use Cake\Error\Middleware\ErrorHandlerMiddleware;

class Application extends BaseApplication
{
    public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
    {
        // ミドルウェアのキューにエラーハンドラーを結びつけます。
        $middlewareQueue->add(new ErrorHandlerMiddleware());
        return $middlewareQueue;
    }
}

MiddlewareQueue の末尾に追加するだけではなくて、さまざまな操作をすることができます。

$layer = new \App\Middleware\CustomMiddleware;

// 追加されたミドルウェアは行列の末尾になります。
$middlewareQueue->add($layer);

// 追加されたミドルウェアは行列の先頭になります。
$middlewareQueue->prepend($layer);

// 特定の位置に挿入します。もし位置が範囲外の場合、
// 末尾に追加されます。
$middlewareQueue->insertAt(2, $layer);

// 別のミドルウェアの前に挿入します。
// もしその名前のクラスが見つからない場合、
// 例外が発生します。
$middlewareQueue->insertBefore(
    'Cake\Error\Middleware\ErrorHandlerMiddleware',
    $layer
);

// 別のミドルウェアの後に挿入します。
// もしその名前のクラスが見つからない場合、
// ミドルウェアは末尾に追加されます。
$middlewareQueue->insertAfter(
    'Cake\Error\Middleware\ErrorHandlerMiddleware',
    $layer
);

If your middleware is only applicable to a subset of routes or individual controllers you can use Route scoped middleware, or Controller middleware.

プラグインからのミドルウェア追加

プラグインは middleware フックメソッドを使って、ミドルウェアをアプリケーションのミドルウェアキューに適用することができます。

// ContactManager プラグインの bootstrap.php の中で
namespace ContactManager;

use Cake\Core\BasePlugin;
use Cake\Http\MiddlewareQueue;
use ContactManager\Middleware\ContactManagerContextMiddleware;

class Plugin extends BasePlugin
{
    public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
    {
        $middlewareQueue->add(new ContactManagerContextMiddleware());

        return $middlewareQueue;
    }
}

ミドルウェアの作成

ミドルウェアは無名関数（クロージャ）として、あるいは呼び出し可能なクラスとしても実装できます。また、 Psr\Http\\ServerMiddlewareInterface を拡張するクラスとして実装することもできます。クロージャは小さな課題に適している一方で、テストを行うのを難しくしますし、複雑な Application クラスを作ってしまいます。 CakePHP のミドルウェアクラスは、いくつかの規約を持っています。

ミドルウェアクラスのファイルは src/Middleware に置かれるべきです。例えば src/Middleware/CorsMiddleware.php です。

ミドルウェアクラスには Middleware と接尾語が付けられるべきです。例えば LinkMiddleware です。

ミドルウェアは、 Psr\Http\ServerMiddlewareInterface` を実装しなければなりません。

ミドルウェアは $handler->handle() を呼ぶか、独自のレスポンスを作成することによって、レスポンスを返すことができます。我々の単純なミドルウェアで、両方のオプションを見ることができます。

// src/Middleware/TrackingCookieMiddleware.php の中で
namespace App\Middleware;

use Cake\Http\Cookie\Cookie;
use Cake\I18n\Time;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Server\MiddlewareInterface;

class TrackingCookieMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface
    {
        // Calling $handler->handle()  を呼ぶことで、アプリケーションのキューの中で
        // *次の* ミドルウェアにコントロールを任せます。
        $response = $handler->handle($request);

        if (!$request->getCookie('landing_page')) {
            $expiry = new Time('+ 1 year');
            $response = $response->withCookie(new Cookie(
                'landing_page',
                $request->getRequestTarget(),
                $expiry
            ));
        }

        return $response;
    }
}

さて、我々はごく単純なミドルウェアを作成しましたので、それを我々のアプリケーションに加えてみましょう。

// src/Application.php の中で
namespace App;

use App\Middleware\TrackingCookieMiddleware;
use Cake\Http\MiddlewareQueue;

class Application
{
    public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
    {
        // 単純なミドルウェアをキューに追加します
        $middlewareQueue->add(new TrackingCookieMiddleware());

        // もう少しミドルウェアをキューに追加します

        return $middlewareQueue;
    }
}

ルーティングミドルウェア

ルーティングミドルウェアは、アプリケーションのルートの適用や、リクエストが実行するプラグイン、コントローラー、アクションを解決することができます。起動時間を向上させるために、アプリケーションで使用されているルートコレクションをキャッシュすることができます。キャッシュされたルートを有効にするために、目的の キャッシュ設定をパラメーターとして指定します。

// Application.php の中で
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    // ...
    $middlewareQueue->add(new RoutingMiddleware($this, 'routing'));
}

上記は、生成されたルートコレクションを格納するために routing キャッシュエンジンを使用します。

クッキー暗号化ミドルウェア

アプリケーションが難読化してユーザーの改ざんから保護したいデータを含むクッキーがある場合、 CakePHP のクッキー暗号化ミドルウェアを使用して、ミドルウェア経由でクッキーデータを透過的に暗号化や復号化することができます。 クッキーデータは、OpenSSL 経由で AES を使用して暗号化されます。

use Cake\Http\Middleware\EncryptedCookieMiddleware;

$cookies = new EncryptedCookieMiddleware(
    // 保護するクッキーの名前
    ['secrets', 'protected'],
    Configure::read('Security.cookieKey')
);

$middlewareQueue->add($cookies);

注釈

クッキーデータで使用する暗号化キーは、クッキーデータ のみ に使用することをお勧めします。

このミドルウェアが使用する暗号化アルゴリズムとパディングスタイルは、 CakePHP の以前のバージョンの CookieComponent と後方互換性があります。

ボディパーサミドルウェア

アプリケーションが JSON、XML、またはその他のエンコードされたリクエストボディを受け入れる場合、 BodyParserMiddleware を使用すると、それらのリクエストを配列にデコードして、 $request->getParsedData() および $request->getData() で利用可能です。デフォルトでは json ボディのみがパースされますが、オプションでXMLパースを有効にすることができます。独自のパーサーを定義することもできます。

use Cake\Http\Middleware\BodyParserMiddleware;

// JSONのみがパースされます。
$bodies = new BodyParserMiddleware();

// XMLパースを有効にする
$bodies = new BodyParserMiddleware(['xml' => true]);

// JSONパースを無効にする
$bodies = new BodyParserMiddleware(['json' => false]);

// content-type ヘッダーの値にマッチする独自のパーサを
// それらをパース可能な callable に追加します。
$bodies = new BodyParserMiddleware();
$bodies->addParser(['text/csv'], function ($body, $request) {
    // Use a CSV parsing library.
    return Csv::parse($body);
});

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

Plugin Class

class Cake\Core\Plugin

The Plugin class is responsible for resource location and path management of plugins.

Locating Plugins

static Cake\Core\Plugin::path(string $plugin)

Plugins can be located with Plugin. Using Plugin::path('DebugKit'); for example, will give you the full path to the DebugKit plugin:

$path = Plugin::path('DebugKit');

Check if a Plugin is Loaded

You can check dynamically inside your code if a specific plugin has been loaded:

$isLoaded = Plugin::isLoaded('DebugKit');

Use Plugin::loaded() if you want to get a list of all currently loaded plugins.

Finding Paths to Namespaces

static Cake\Core\Plugin::classPath(string $plugin)

Used to get the location of the plugin's class files:

$path = App::classPath('DebugKit');

Finding Paths to Resources

static Cake\Core\Plugin::templatePath(string $plugin)

The method returns the path to the plugins' templates:

$path = Plugin::templatePath('DebugKit');

The same goes for the config path:

$path = Plugin::configPath('DebugKit');

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

アプリケーション

Application はあなたのアプリケーションの心臓部です。アプリケーションがどのように構成され、何のプラグイン、ミドルウェア、コンソールコマンド、およびルートが含まれているかを制御します。

Application クラスは src/Application.php にあります。デフォルトでは非常にスリムで、いくつかのデフォルトの ミドルウェアを定義しているだけです。 Application は、次のフックメソッドを定義できます。

bootstrap 設定ファイル を読み込み、定数やグローバル関数を定義するために使用されます。デフォルトでは、 config/bootstrap.php を含みます。これは、あなたのアプリケーションが使用する プラグイン を読み込むのに理想的な場所です。

routes ルート を読み込むために使用されます。デフォルトでは、 config/routes.php を含みます。

middleware アプリケーションに ミドルウェアを追加するために使用されます。

console アプリケーションに コンソールコマンドを追加するために使用されます。デフォルトでは、アプリケーションとすべてのプラグインのシェルとコマンドが自動的に検出されます。

events アプリケーションのイベントマネージャーにイベントリスナー を追加するために使用されます。

Application::bootstrap()

アプリケーションの低レベルな関心事を設定するために使用する config/bootstrap.php ファイルに加えて、プラグインのロードや初期化、グローバルイベントリスナーの追加のために Application::bootstrap() フックメソッドが利用できます:

// in src/Application.php
namespace App;

use Cake\Http\BaseApplication;

class Application extends BaseApplication
{
    public function bootstrap()
    {
        // Call the parent to `require_once` config/bootstrap.php
        parent::bootstrap();

        // Load MyPlugin
        $this->addPlugin('MyPlugin');
    }
}

Application::bootstrap() でプラグインとイベントをロードすると、イベントとルートが各テストメソッドで再処理されるので、コントローラーの統合テスト が簡単になります。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.

依存性の注入(DI)

CakePHPのサービスコンテナは依存性の注入(DI)によりアプリケーションのサービスのためのクラス依存性を管理できます。 DIは手動でインスタンス化することなく、自動でコンストラクタを通してオブジェクトの依存性を"注入"します。

サービスコンテナを使うことで‘application services’を定義することができ、これらのクラスはモデルを使います。また、loggerやmailerなどを使って再利用可能なワークフローの構築やアプリケーションのビジネスロジックに作用します。

CakePHPはコントローラーでアクションを呼ぶ際サービスコンテナを使い、その後コンソールコマンドを呼び出します。コントローラーのコンストラクタにもDIを持たせることができます。

簡単な例:

// src/Controller/UsersController.php
class UsersController extends AppController
{
    // サービスコンテナを通して$usersサービスが作られる
    public function ssoCallback(UsersService $users)
    {
        if ($this->request->is('post')) {
            // シングルサインオンのプロバイダーからUsersServiceのcreateやgetを使う
            $user = $users->ensureExists($this->request->getData());
        }
    }
}

この例では、UsersController::ssoCallback() アクションはシングルサインオンプロバイダーからユーザーを取得し、ローカルのデータベースにその値が存在すると保証されている必要があります。このサービスはコントローラーに注入されているからこそ、テストをする際に簡単に実装をモックオブジェクトや偽のサブクラスと交換できるのです。

コマンド内でサービス注入を行う例:

// In src/Command/CheckUsersCommand.php
class CheckUsersCommand extends Command
{
    /** @var UsersService */
    public $users;

    public function __construct(UsersService $users)
    {
        parent::__construct();
        $this->users = $users;
    }

    public function execute( Arguments $args, ConsoleIo $io )
    {
        $valid = $this->users->check('all');
    }

}

// In src/Application.php
public function services( ContainerInterface $container ): void
{
    $container
        ->add(CheckUsersCommand::class)
        ->addArgument(UsersService::class);
    $container->add(UsersService::class);
}

ここでは、インジェクションの手順が少し異なります。 UsersService をコンテナに追加するのではなく、まずCommand全体をコンテナに追加し、 UsersService を引数として追加する必要があります。これで、コマンドのコンストラクタ内でそのサービスにアクセスできるようになります。

サービスの追加

コンテナに作成したサービスを持たせるには、どのクラスが作成でき、どうビルドするかを伝える必要があります。

最もシンプルな方法はクラス名で定義することです:

// 名前でクラスを追加する
$container->add(BillingService::class);

アプリケーションとプラグイン内の services() フックメソッドからサービスを定義します。:

// in src/Application.php
namespace App;

use App\Service\BillingService;
use Cake\Core\ContainerInterface;
use Cake\Http\BaseApplication;

class Application extends BaseApplication
{
    public function services(ContainerInterface $container): void
    {
        $container->add(BillingService::class);
    }
}

アプリケーションが使うインターフェースに実装を定義できます:

use App\Service\AuditLogServiceInterface;
use App\Service\AuditLogService;

// あなたのApplication::services() メソッド内

// 実装をインターフェースに追加
$container->add(AuditLogServiceInterface::class, AuditLogService::class);

必要ならオブジェクト生成にコンテナ側でファクトリー関数を活用できます:

$container->add(AuditLogServiceInterface::class, function (...$args) {
    return new AuditLogService(...$args);
});

ファクトリー関数はすべてのクラス解決された依存関係を引数として受け取ります。

一度クラスを定義すると求められる依存性も定義する必要があります。それらの依存性はオブジェクトやプリミティブ値にもなります。:

// 文字列や配列や数値のプリミティブ値を追加する
$container->add('apiKey', 'abc123');

$container->add(BillingService::class)
    ->addArgument('apiKey');

共有サービスを追加する

デフォルトではサービスは共有されません。オブジェクトや(依存性)はコンテナから取得される時にそれぞれ生成されます。もしシングルトン・パターンに基づく単一のインスタンスを再利用したい場合は、サービスに'shared'をつけてください。:

// あなたのApplication::services()メソッド内で

$container->share(BillingService::class);

定義の拡張

定義の拡張によって、一度サービスが定義されてからも編集や更新が可能です。これにより、定義されたサービスに引数を追加できます。

コード内どこかで:

// 部分的に定義されたサービスのどこかで引数の追加
$container->extend(BillingService::class)
    ->addArgument('logLevel');

サービスのタグ化

サービスのタグ化により同時にすべてのタグ化されたサービスを取得できます。レポートシステムなど他サービスのコレクションと組み合わせるサービスをビルドする際に使えます。:

$container->add(BillingReport::class)->addTag('reports');
$container->add(UsageReport::class)->addTag('reports');

$container->add(ReportAggregate::class, function () use ($container) {
    return new ReportAggregate($container->get('reports'));
});

設定データを使用する場合

しばしば、サービスで設定データが必要な時がありますよね。コンテナに入れる際必要なサービスの設定キーをすべて追加するなんてうんざりします。サービス設定をより簡単にするために、CakePHPの注入可能な設定読み込み機能を使います。:

use Cake\Core\ServiceConfig;

// シェアされたインスタンスを使用する
$container->share(ServiceConfig::class);

ServiceConfig クラスは Configure で利用可能な全データのread-onlyな一覧を提供します。なので、誤って設定が変わる心配はありません。

サービス・プロバイダー

サービス・プロバイダーによって関連したサービスをまとめ上げる補助をし、グループ化することができます。

また、サービス・プロバイダーは定義したサービスが初めて使われる際、遅延登録されアプリケーションのパフォーマンスを上げることができます。

サービス・プロバイダーの作成

ServiceProviderの一例:

namespace App\ServiceProvider;

use Cake\Core\ContainerInterface;
use Cake\Core\ServiceProvider;
// 他はここにインポート

class BillingServiceProvider extends ServiceProvider
{
    protected $provides = [
        StripeService::class,
        'configKey',
    ];

    public function services(ContainerInterface $container): void
    {
        $container->add(StripService::class);
        $container->add('configKey', 'some value');
    }
}

サービス・プロバイダーは自身の services() メソッドを使って、提供するサービスをすべて定義します。さらに、それらのサービスは 絶対に $provides に正しく定義する必要があります。正しく $provides に含められなかった場合、コンテナから読み込めなくなります。

サービス・プロバイダーの使用

サービス・プロバイダーを読み込むには addServiceProvider() メソッドを使ってコンテナに追加してください:

// Application::services()メソッド内で
$container->addServiceProvider(new BillingServiceProvider());

起動可能なサービス・プロバイダー

もしサービス・プロバイダーがコンテナに追加された時、ロジックを走らせる必要がある場合 bootstrap() メソッドを使ってください。想定される状況としてサービス・プロバイダーが追加の設定ファイルを読み込む必要があったり、追加のサービス・プロバイダーを読み込んだり、アプリケーションのどこかで定義されたサービスを変更する場合などが考えられます。

起動可能なサービス・プロバイダーの例:

namespace App\ServiceProvider;

use Cake\Core\ServiceProvider;
// 他はここにインポート

class BillingServiceProvider extends ServiceProvider
{
    protected $provides = [
        StripeService::class,
        'configKey',
    ];

    public function bootstrap($container)
    {
        $container->addServiceProvider(new InvoicingServiceProvider());
    }
}

サービスをモック化してテストする

テスト内で ConsoleIntegrationTestTrait や IntegrationTestTrait を使うことででコンテナを通して注入されるサービスとスタブやモックを入れ替えることができます。:

// テストメソッドやsetup()内で
$this->mockService(StripeService::class, function () {
    return new FakeStripe();
});

// モックを削除する場合
$this->removeMockService(StripeService::class);

テスト時には定義されたどんなモックもアプリケーションのコンテナ内で交換されます。そして、自動的にコンテナやコマンドに注入されます。それぞれのテストの最後でモックは除去されます。

© Copyright 2024 Cake Software Foundation, Inc. 最終更新: 3月 29, 2024 Created using Sphinx 7.0.1.