CakePHP3系の便利なTips

CakePHP3系の便利なTips

私は CakePHP3 系のリリースの頃から CakePHP3 を使い続けていますが、友人から質問を受けたり MeetUpイベントでお話しすることが多くなってきました。そこで感じたことですが、CakePHP3 系の便利機能を使っておらず、場合によっては CakePHP2 系の頃のようなコードを書いて苦労されている印象に思いました。
本投稿では、便利な機能を利用してコードの再利用性・可読性をあげて、少しでも幸せな CakePHP ライフを送れるように願いを込めて色々と書きます。

Helper 編

まずは気軽に試せれる便利な Template 側の機能になります。CakePHP 3 系では、sec/Template ディレクトリ内に Html に関するファイルを作成します。そのファイル内で CakePHP 標準の便利なヘルパーを利用します。

POST ボタン、POST リンク

見た目はただのボタンやリンクなんだけど、実際には form の POST で値を送りたいなんて要求に応えた機能です。標準の Form ヘルパーには 1 行で POST 送信させるボタンやリンクを書くことができます。
公式マニュアル

POST ボタンの場合

# この 1 行をテンプレートファイル内に記述する
Form->postButton('削除する', ['controller' => 'Articles', 'action' => 'delete', 'id' => 100]) ?>

#以下のhtmlが書き出される

POST リンクの場合

# この 1 行をテンプレートファイル内に記述する
Form->postLink('削除する', ['controller' => 'Articles', 'action' => 'delete', 'id' => 100]) ?>

#以下のhtmlが書き出される
削除する

リンククリック時の確認ダイアログ表示

地味にありがたい機能で、見た目を気にせずに確認ダイアログを表示させたい場合、リンクのヘルパーに引数を渡すことで実現できます。これを知らずに独自で js の confirm() を実装されている方は是非一度お試しください。
公式マニュアル

# このオプションを渡す
Html->link('記事を削除する',
    ['controller' => 'Articles', 'action' => 'delete', 'id' => 100],
    ['confirm' => '削除してよろしいですか?']
) ?>

#以下のhtmlが書き出される
記事を削除する

自動リンク化

限定的な利用範囲に限った方がいいかと思いますが、テキストを自動でリンクさせる諸刃の剣のような機能になります。テキストを全部渡すと、その中から email アドレスと url を自動でリンクに変換してくれます。文字列の区切りを伝えるために私は半角スペースを利用しました。私は管理画面からニュースを登録する場合に利用しています。
公式マニュアル

# この 1 行をテンプレートファイル内に記述する(該当の文字列の前後にスペースを入れる)
Text->autoLink('ダミーテキスト内部に [email protected] という文字列や http://example.com などのURLを含めると勝手にリンク化されます。') ?>

#以下のhtmlが書き出される
ダミーテキスト内部に [email protected] という文字列や http://example.com などのURLを含めると勝手にリンク化されます。  

テキストの段落化

こちらも管理画面からのニュースなどに利用していますが、1 行の改行は br タグ、2 行の改行は p タグでくくられます。以前は CKEditor 等を利用していましたが、サイトのお知らせやニュース等では改行とパラグラフとリンクくらいしか利用しなくなってきたため、こちらで十分になりました。実利用は DB にお知らせを保存して利用しています。

# このように適当に書いても適切に処理される
Text->autoParagraph("ダミーテキスト内部に改行を見つけ、
1 行の場合は br タグで改行を表現し、

2 行の場合は p タグでくくられます。") ?>

# このように書き出される

ダミーテキスト内部に改行を見つけ、
1 行の場合は br タグで改行を表現し、

2 行の場合は p タグでくくられます。

テキストの切り詰め

この機能はいろんなところでよく使っています。「…続きを読む」みたいな表現は色々な場面で登場するので必須です。

# このようなテキストを指定文字数の切り詰めで書き出される

Text->truncate($text, 60, ['ellipsis' => '...続きを読む']) ?>

# このように書き出される
山やまみちを登りながら、こう考えた。
智に働けば角が立つ。情に棹させば流される。
意地を通せば窮屈だ。と...続きを読む

Table (Model) 編

CakePHP2 系ではモデルクラスにテータ取得処理やバリデーション、また、取得したデータの加工などを記述して、これをビジネスロジックと呼んでコントローラーから呼び出していましたが、CakePHP3 系では、モデルはテーブルとエンティティに分かれました。テーブルでは DB 接続に関することや取得条件、値のバリデーション、スキーマに関するルールなどを実装します。エンティティは後述します。

カスタムファインダー

会員制サイトを構築する際、例えばユーザに関して以下の条件が加わることがあります。

  • メールや電話番号などの疎通確認済みかどうか
  • 有料プランユーザかどうか

こういった場合、カスタムファインダーで以下のように実装するとコードの再利用性が高まります。

class UsersTable extends Table
{
    # 疎通確認済みの条件
    public function findActive(Query $query, array $options)
    {
        return $query->where(['Users.status' => 1]);
    }

    # 有料プランの条件
    public function findPaid(Query $query, array $options)
    {
        return $query->where(['Users.premium' => 1]);
    }
}

# コントローラ内、下記のように利用
# (この時点では SQL 文の作成しか行われず実行されませんが便宜上取得と書きました)

class UsersController extends AppController
{
    # 疎通確認済みのユーザの取得
    $active = $this->Users->find('active');

    # 有料プランのユーザの取得
    $paid = $this->Users->find('paid');

    # 疎通確認済み & 有料プランのユーザの取得
    $premium = $this->Users->find('active')
        ->find('paid');
}

このようにいろんな条件を繋ぐことができるので、ある条件に追加で別の条件を加えたい場合は、簡単に実装ができます。また、コードのメンテナンス性が上がるので、システムの仕様変更に合わせて柔軟に組み合わせることが可能です。

validationDefault(バリデーション)と buildRules(ルール)の使い分け

ちょっと横道にそれますが、よく聞かれることなのでこのタイミングで使い分けについて記述します。

バリデーションには DB 保存時の値に関する条件を記述し、ルールには schema に関する条件を記述します。例えば nickname カラムを varchar(255) とした場合、バリデーションには nickname の文字数や半角英数のみなど、値に関する条件を記述します。ここで、nickname にユニーク制限をかけたい場合、ルールにユニーク制限を記述します。こうすることで、DB アクセスが必要なエラーチェックと不要なエラーチェックを分けることができ、DB へアクセスする回数を減らすことが可能になります。

Entity (Model) 編

こちらは DB から取得した値(1レコード)に関する処理を主に書きます。仮想プロパティや自動オブジェクト変換機能を使うと、値に関する処理が非常に簡単になります。

仮想プロパティ

短縮した値の受け取りや初期値のセット

例えば以下のようにコードを書くと、UUIDの短縮表示などを表現できます。管理画面でのお問い合わせの一覧表示など、値を丸めて視認性をあげたい時に私は使ったりしてます。
(表示上ユニークではなくなる可能性がありますが、値を上書きするわけではないです)

また、nullを許可しているけど明確に「–」と表示したい場合などには、初期値をセットすることでテンプレート内に条件分岐を書く必要がなくなります。DB から取得した値に関する処理はエンティティに書くことでコードの可読性が上がります。

# エンティティファイル内で記述
class Contact extends Entity
{
    protected function _getIdShort()
    {
        return Text::truncate($this->_properties['id'], 13, ['ellipsis' => null]);
    }

    protected function _getViaCodeDp()
    {
        return $this->_properties['via_code']? $this->_properties['via_code'] : '--';
    }
}

# コントローラやテンプレートでは下記のようにアクセスできる
Paginator->sort('id', 'お問い合わせID') ?> Paginator->sort('created', '受信日時') ?> Paginator->sort('title', 'タイトル') ?> Paginator->sort('via_code', '経路コード') ?>
id_short ?> created->format('Y年m月d日') ?> title) ?> via_code_dp) ?>

enum っぽい利用方法

また、ちょっとこんな感じで実装するのは良くないかもしれませんが、あり・なしなどのフラグやマスタデータ化するほどではないなと思うものには enum っぽい使い方をしています。

# エンティティファイル内で記述
class Gim extends Entity
{
    protected function _getLocker()
    {
        return $this->_properties['has_locker']? 'あり' : 'なし';
    }

    protected function _getShower()
    {
        return $this->_properties['has_shower']? 'あり' : 'なし';
    }

    protected function _getScale()
    {
        if($this->_properties['size'] == 1){
            return '小規模';
        }elseif($this->_properties['size'] == 2){
            return '中規模';
        }else{
            return '大規模';
        }
    }
}

# テンプレートでのアクセス
ロッカーの有無
locker ?>
シャワーの有無
shower ?>
施設の規模
scale ?>

日付オブジェクトの利用

仮想プロパティのコード内にさらっと書きましたが、created や modified など datetime 型にセットしたものは自動で Timeオブジェクトに変換されます。したがって、好きなフォーマットに表示させることができます。

# 好きなフォーマットで表示できる
created->format('Y年m月d日') ?>

# こんなことも可能
$limit = Time::createFromDate(2018, 12, 31);
無料期間はあとcreated->diff($limit)->days ?>日です。

Controller 編

利用方法は上記と合わせて書いたので、特記することは少なくなりますが、CakePHP2 系を利用されていた方からよく「ビジネスロジックはどこに書きますか?」と聞かれます。上記 Table クラスと Entity クラスを適切に利用し、controller で条件分岐させることで、そもそも以前行っていたモデルファイルにビジネスロジックを書くといったことをしなくなりました。DB アクセス(スキーマ関連)に関するものが Table クラス、値(取得したレコード 1 行)に対する処理が Entity クラスと認識すればわかりやすいかもしれません。

異なるハッシュ化方法のパスワードのシームレスな移行は私が実に便利と感じたものなので再度紹介します。
シームレスなパスワードハッシュ化の移行

終わりに
最初は Tips 集をかく予定でしたが、後半では CakePHP3 系を使うと便利だよというただの紹介文になってしまったかもしれません。書き忘れたことや MeetUpイベントでよく聞かれることなど、また何かあれば随時情報を公開していきます。みなさま楽しい CakePHP ライフを送り下さいませ。

そして来年は CakePHP の国際イベントが日本で開催されます。ぜひ会場でお会いしましょう〜。

Enjoy && Happy New Year!!