CakePHPバリデーションメッセージにフィールド名を動的に表示させる

※2017/12/21追記
日付フォーマット処理にミスがあったため修正しました。

この記事は CakePHP Advent Calendar 2017 20日目の記事です。

皆さん、CakePHP使ってますか。
私はPHPで初めて触ったフレームワークということもあり、かなり思い入れがあります。
そんなCakePHPのバリデーションについて紹介します。

アジェンダ

  • この記事を書こうと思ったきっかけ
  • 最終的なゴール
  • カスタムバリデーションの実装
  • まとめ

この記事を書こうと思ったきっかけ

そもそもCakePHPを使うきっかけが、「就職してずっと使ってきたJava以外の言語を本格的に学びたい」という思いからでした。
仕事では簡単なツールにPHPを使っていたこともあり、CakePHP導入を決めました。

そして、実装を進めていたとき、

「バリデーションにフィールド名が出ないな…」

と思ったことがきっかけです。

最終的なゴール

以下のようなメッセージを出力します。

  • 「ID」は必須入力です。
  • 「名前」は空欄にできません。
  • 「年」は数値のみで入力してください。
  • 「生年月日」は「y/m/d」形式で入力してください。

上記は、デフォルトの翻訳ファイル(./bin/cake i18nで作成されたもの)を利用すると、以下のように出力されます。

  • このフィールドは必須です
  • このフィールドは空欄にできません
  • 与えられた値は無効です
  • 与えられた値は無効です

Oh…。

バリデーション機構の拡張

モデル・翻訳ファイルの定義

モデル・翻訳ファイルは以下のように定義します。
今回、翻訳ファイルはバリデーション用に切り出しました。

カスタムバリデータクラス追加

基本的な方針として、requiredempty以外のバリデーションルールはValidator::add()メソッドを利用するので、それを継承して調整します。
コードを見ていただければわかるとおり、requiredemptyは別途実装しています。
※もう少し良い実装方法があるはずなので、引き続き検討します…。

利用するモデル・フォームに定義

作成したバリデーションを利用するよう、TableおよびFormで定義します。

  • Table

  • Form

以上で実装完了です。

まとめ

ErrorHelperなどは、規約に則っていれば特定ディレクトリにファイルを生成するだけで利用できます。
今回のバリデーションはこれができず、少し手間に感じました。
ただ、フレームワークのコードはわかりやすく、特に問題なく理解できたので、
自分でカスタマイズするのもそこまで難しくはありませんでした。

CakePHPは個人開発で一番使っているフレームワークで、先日出した初PRも無事マージされました。

これからも内容を理解しながらぼちぼち貢献していけたらなと思っています。
それでは、皆さんも良いCakePHPライフを!

一部記事用に修正しましたが、今回利用したソースコードの元はこちらにありますので、良ければご覧ください。

CakePHPでステータスコード204がOK扱いにならなかった話

今年のQiita Advent Calendarに投稿予定です(CakePHPとDjango)。
いずれも12/20ですので、興味があればご覧ください。

CakePHP
Django

さて、今回はCakePHPに関する記事です。
結論から言うと、PRがマージされましたというお話です。

背景

私は自作アプリのログイン・ログアウトにGoの外部API(これも自作したやつです)を使っており、
それぞれ以下のように処理しています。

ログイン

  1. アプリ:ユーザ・パスワードで認証APIをコール(POSTメソッド)
  2. API:処理成功時、ステータスコード200とアクセストークン返却
  3. アプリ:アクセストークンを利用してユーザ取得APIをコール(GETメソッド)
  4. API:処理成功時、ステータスコード200とユーザ情報を返却
  5. アプリ:取得したユーザ情報をセッションに格納してログイン成功

ログアウト

  1. アプリ:アクセストークンを利用して認証解除APIをコール(DELETEメソッド)
  2. API:処理成功時、ステータスコード204を返却

ある時、CakePHP3を使ったアプリで、正常にログアウトした場合でもエラーログが出ていることに気づきました。

「エラーのくせに詳細が何も出力されないだと…」

という衝撃を隠しきれませんでしたが(笑)、一旦落ち着いて発生箇所を調査することに。

原因

コメントに書いた通りなのですが、上記コードをもとに説明します。

  • ログアウト時の処理2でステータスコード204が返ってくる(5行目)
  • isOk()の対象でないためif文に入らない(12行目)
  • レスポンスボディもないため、CakePHP側16行目Log::error()で出力する$bodyも空(17行目)

という流れで、何も情報が無いエラーログが出力されてしまっていたということです。
これだけなら「あえて204をOKしてないのかな」とも思えたのですが。

CakePHP3側の基底テストケースではまさかのOK対象内。

修正~PR

CakePHP側のコード修正・テストコードの修正および実施を行い、
それらのPRを出したところ、5時間ほどで無事マージされました。

ドキュメントは以前PRがマージされたのですが、コード側は初めてです。
「PRは内容に問題なければサクッとマージしてもらえるんだな」ということがわかりました。

とはいえ、IssueやPR作成時の説明にもあるように、注意する点もいくつかありますので、詳細は以下をご覧ください。
CakePHP コミュニティセンター

それでは、良いCakePHPライフを。

Laravelでカスタムコレクションクラスを利用する

今回はLaravelのお話です。
先日5.5(LTS)がリリースされるなど最近何かと話題のLaravelですが、今回はそんな新しい機能の話ではありません。

Laravel5.xやCakePHP3.xでは、PHPの残念な配列(連想配列含む)を良い感じに扱えるCollectionというクラスが存在します。
上記2フレームワークでは、ORMを使ってDBへ問い合わせた結果がCollection型で返ってくるケースが多いこともあって、new Collection()するよりはそのインスタンスを使うケースのほうが多いように感じます。

通常のforforeachでループできることはもちろん、map()filter()といったデータの整形や絞り込みをメソッドチェーンで行えるので、直感的にコードを書くことができます。
そこで、「拡張コレクションを作って特定モデルの結果はそいつを使おうぜ」というのが今回のテーマです。

注意しないといけないのは、map()メソッドのように内部でコレクションを生成する関数を呼び出した場合です。
map()関数は、内部で保持しているインスタンスがModelでなかった場合、Collectionクラスを返します。
上記evenPosts()の例だと、メソッドが最終的に返却するArticleCollectionクラスが内包しているのは配列なので、この戻り値からさらにmap()すると戻り値はCollection型になってしまいます。
つまり、evenPosts()map()evenPosts()といった呼び出し方をするとエラーになります。

その場合は上記コードにあるように、継承元であるIlluminate\Support\CollectiontoBase()メソッドをオーバーライドすれば良いです。

同様のことがCakePHP3でもできるんじゃないかと思っているんですが、こちらは今のところIteratorクラスを使うことになりそうです。

Laravelはドキュメントも充実しているので、大体はそこから情報を得られます。
以下に私が利用しているサイトのリンクを掲載しておきます。

今日からあなたもCakePHPer

昔の記事を読んでいると、「いろんな意味で若いな」と思いますね。
そんな自分もアラサーです。
イメージしていた姿とは大きく違っていますが、まあ色々経験した結果かなとプラスに考えておきます。

さて、今回は、先日3.5がリリースされたCakePHPについて書きます。
私がPHPを学習する際、初めて触れたフレームワークがCakePHP2.xでした。
2.xの配列地獄をご存知の方は、3.xになった際のORMの変更に少なからず戸惑ったのではないかと思います(もちろん良い意味です)。
マイナーバージョンで大きい変更があることも少なくありませんが、ある程度の後方互換が維持されているのはありがたいですね。

最近はLaravelに人気を持っていかれている感はありますが、個人的にはCakePHP3が一番好きなフレームワークなので、いくつかおすすめポイントを書こうと思います。
PHPをある程度使っている方はもちろん、以前の私のように、

  • PHPフレームワーク使いたいけど何が良いのか
  • そもそもPHPの基礎がそこまで固まっていない

といった方も対象です。

「設定より規約(CoC)」という安全性

これが一番大きいと思います。
ファイル名・クラス名・ディレクトリ構成などの「決まり」に則って開発するということは、個人的にはプラスだと思っています。
ある程度の経験があるとこのあたりはどちらでも問題ないのかもしれません。
ただ、PHPフレームワークの経験が浅いと、どのクラスがどういった役割をするのかがイマイチピンとこないケースは多いように感じます。

個人開発なら、ある程度自由にコードを書き始めて、慣れたら適切にモジュール分割していくことが良い学習になります(私もこのタイプでしたし、今も日々改善しています)。

チーム開発なら、開発者に規約という前提があるので、レビューでもコードに集中できます。
経験者がいればなお良いですね。

日本語ドキュメントが充実している

こちらにあるドキュメントがとても役に立ちます。
サンプルコードも多く掲載されているので、実装に迷うこともありません。
オフラインドキュメントも取得できます(こちらは英語です)。

デバッグバーが優秀

デバッグモードだと画面下にバーが表示され、画面表示時に実行されたクエリや変数の値、メモリ使用量や環境変数などを確認できます。
情報量がとても多く、開発中とてもお世話になります。

CLI

bakeコマンドです。
これによってControllerやModelなどのひな型(+テストケース)を作成できます。
マイグレーションも実行可能ですし、それをもとにテスト用のダミーデータの生成スクリプトも用意してくれます。
バッチ処理も書けます。

CakePHPで完結できる安心感

RubyのRuby on Rails、PythonのDjangoのように、基本的にはフレームワークの標準機能(+一部拡張機能)でカバーできるため、導入コストが大きくありません。
基本的なMVCはもちろん、APIClientやメール送信・セキュリティといったアプリケーションに必要な機能は一通り揃っており、上記ドキュメントを見れば大体解決できます。
また、コレクションやHash、テキスト処理などのユーティリティも充実しています。

Repository/Entityパターン

CakePHP3.xではRepository/Entityパターンを採用しており、DB問い合わせを行うクラス(Table)とデータを格納するクラス(Entity)は別です。
個人的にはModelになんでもかんでも突っ込むのがあまり好きでないので、これが標準なのはありがたいです。

他にも便利な機能はたくさんあります。
私も日々勉強しているので、皆さんも是非一度使ってみてください。

CakePHP3を使ったシステムのリポジトリはこちら
※PRお待ちしています。

Laravel・Lumenでログ出力先を変更する

最近勢いがあるPHPフレームワークの1つであるLaravelですが、ログファイルのデフォルト出力先は以下になっています。

  • storage/logs/laravel.log

普段、私は自分が作成した複数アプリ(CakePHP・Rails・Django・バッチ処理など)で同じログディレクトリを利用しています(管理しやすいので)。
また、デプロイ時にはシェル側でリンクを切り替えて対応しています。
そのため、プロジェクトディレクトリ以下に出力されると結構不便なんですよね。

Laravelでは、他フレームワークのように「configファイルをいじるだけでディレクトリ・ファイル名を操作する」というのができませんでした。
※ディレクトリの変更自体はApplicationクラスのuseStoragePath()メソッドで可能です。
なので、今回はこれをカスタマイズした方法を紹介します。
といっても、他言語や他フレームワークでやるような継承を使うだけです。

1. 独自のLogServiceProviderを作成

まずはじめに、\Illuminate\Log\LogServiceProviderを継承した独自プロバイダを生成します。

2. 独自のApplicationクラスを作成

次に、独自のApplicationクラスを生成します。
ここで、上記のMyLogServiceProviderを利用するように変更します。

3. 利用するApplicationクラスの変更

最後に、利用するApplicationクラスをMyApplicationに変更します。

これでログ出力をカスタマイズできます。

ちなみに、Laravelの軽量版であるLumenでは、Laravelの手順1が不要で、手順2の独自アプリケーションクラスでgetMonologHandler()を継承するだけで良いです。

CakePHP3で「.env」を使う

世間一般に言われる「ゴールデンウィーク」も今日が最終日ですが、皆様いかがお過ごしでしょうか。
ちなみに、私はずっと開発に集中していました。

さて、今回はCakePHP3で.envファイルを利用する方法についてここに記載しておきます。
Laravelではプロジェクトルートに「.env」ファイルを配置し、その中に設定した値を利用して環境ごとの差異を吸収するという手法が取られています(このファイルはバージョン管理外とします)。

Javaを長くやってきた私は、アプリケーションサーバに設定する手法を主に使ってきたので、PHPのWebアプリで使う環境変数はすべてApacheに設定していました。
ただ、最近Webサーバをnginxに変えた際、環境変数の設定に手こずり、あげく「ほんとにこれで良いのか?」となっていました。
アプリ単位で管理するのならそれが良いのかなと今では思っていますので、同様のことをCakePHP3でも実施したというのが今回の対応です。

※2017/8/30追記

CakePHP3.5から、dotenvの機能が標準搭載されています。
以下に記載したやり方と異なりますが、今後を考えるとそちらに倣っていただいたほうが良いです。
詳しくはこちらをご覧ください。

ファイルの配置

まずはプロジェクトルートに「.env」を配置します。
今回はini形式にします。

設定読み込み処理追加

リクエスト時に読み込まれるファイル(今回はbootstrap.phpにします)に以下を記載します。

これで、コード上で「.env」に定義したファイルを読み込むことができます。

ちなみに、より柔軟に対応できる「PHP dotenv」というライブラリもありますが、今回はファイル名固定かつ最低限値が読み込めればよかったので、上記対応としました。