CakePHP でカスタム Validation を追加しやすくする #cakephp

概要

CakePHP には独自に定義したカスタム Validation を使う方法がいくつかある。

Default のバリデーションプロバイダーを差し替えることで、アプリケーション全体で使うようなルールを追加しやすくする。

例えば「会員IDは 0 埋めを含む8桁の数字」のようなカスタムバリデーションを使う場合、下記のような形で呼び出せるようにする。

$validator = new AppValidator();
$validator->memberId('member_id', 'member_id は8桁の数字で指定してください');

Validation を拡張したクラスを作る

Validation を拡張して、カスタム Validation を追加するための独自クラスを作る。

Cake\Validation\Validation を拡張することで、既存のチェック処理を直接的にも間接的にも使える。

class AppValidation extends Validation
{
    /**
     * 会員IDをチェックする
     *
     * @param string $check Value to check
     * @return bool Success
     */
    public static function isMemberId($check)
    {
        // 数字8桁のチェック
        return self::custom($check, '/^\d{8}$/');
    }
}

Validator を拡張したクラスを作る

Validator を拡張して、プロキシメソッドを追加するための独自クラスを作る。

このクラスはなくてもよいのだが、カスタムバリデーションを使う際にいちいち配列を作ったりせずに済むようになるため、よく使うものには用意しておくと便利だと思う。

class AppValidator extends Validator
{
    /**
     * 会員IDのルールを追加する
     *
     * @param string $field The field you want to apply the rule to.
     * @param string|null $message The error message when the rule fails.
     * @return $this
     * @see AppValidation::isMemberId()
     */
    public function memberId($field, $message = null)
    {
        // この辺の書き方は、本家 Validator の類似メソッドを参考にするといいと思う
        $extra = array_filter(['message' => $message]);

        return $this->add(
            $field,
            'member_id',
            $extra + [
                'rule' => 'isMemberId',
            ]
        );
    }
}

デフォルトのバリデーションプロバイダーを差し替える

config/bootstrap.php でバリデーションプロバイダーを差し替えることで、アプリケーション全体に拡張した Validation が適用される。

addDefaultProvider で name を default にすることで、カスタムバリデーションでも provider の明示的な指定が不要になる。

ただし default を差し替える場合、 RulesProvider インスタンスで渡してやる必要がある。

ちなみに name を固有のものにすれば、 プロバイダーを使い分けることが可能。

※その場合、プロバイダーの名前を rule で渡してやる必要がある

// bootstrap.php でデフォルトのプロバイダーを差し替える
Validator::addDefaultProvider(
    'default', 
    new RulesProvider(AppValidation::class)
);

まとめ

  • 独自Validationを作る
  • 独自Validatorを作る
  • 独自Validationをデフォルトのバリデーションプロバイダーにする

上記を行うことで、カスタムバリデーションの追加がしやすくなる。

アプリケーション内横断で使いたいようなカスタムルールはそれなりにあると思うので、同じルールが分散するよりかは、定義する場所を確保しておくとメンテしやすいと思う。

参考

CakePHP で uploadedFile の Validation をテストする #cakephp #php

概要

CakePHP にはアップロードファイル用の validator が用意されていて便利。

ただテストをするときにちょっとめんどくさい点が2つある。

1つ目はファイルをアップロードするリクエストを作ること。

リクエストするデータとは別に $_FILES にも情報を突っ込んでおかないと、Controller で \Cake\Http\ServerRequest::getUploadedFile あたりを呼び出した時に値が取れなくなる。

もう1つは is_uploaded_file を stub にする必要があること。

uploadedFile の Validation はアップロードファイル判定に is_uploaded_file を使っており、テスト時にこの判定をくぐり抜けるのが難しい。

uploadedFile の Validation

Controller 内で↓のように書ける。便利。

$validator = new Validator();
$validator->uploadedFile(
  'my_file',
  ['types' => ['text/plain']], 
  'ファイル形式が正しくありません'
);
$errors = $validator->errors($this->request->getData());

オプションは mime type 以外にも、サイズ判定などがある。

テストで uploadedFile のリクエストを作る

uploadedFile を Controller のテスト (IntegrationTestTrait) で使う場合、 \Psr\Http\Message\UploadedFileInterface の実装インスタンスを生成してリクエストに乗せる。

\Zend\Diactoros\UploadedFile を使えばいいと思う。

このとき、リクエストデータとは別に $_FILES にもセットしておく必要がある。

直接代入してもいいのだが、 \Cake\TestSuite\IntegrationTestTrait::configRequestfiles いうキーで渡してあげると、結果的に同じことができるようだ。

$testFile = TESTS . 'Fixture' . DS . 'files' . DS . 'my_file.txt';
$uploadedFile = new UploadedFile(
    $testFile,
    10,
    UPLOAD_ERR_OK,
    'my_file.txt',
    'application/octet-stream'
);

// 別途設定する
$this->configRequest(
    [
        'files' => [
            'my_file'=> [
                'error' => $uploadedFile->getError(),
                'name' => $uploadedFile->getClientFilename(),
                'size' => $uploadedFile->getSize(),
                'tmp_name' => $testFile,
                'type' => $uploadedFile->getClientMediaType(),
            ]
        ]
    ]
);

// リクエスト
$this->post("your/api/", ['my_file' => $uploadedFile]);

ちなみにファイルの中身の確認は ext/finfo で 'tmp_name' に対して行われる。

実ファイルに対してチェックが行われるので、配置しておく必要がある。

is_uploaded_file を stub 化する

\Cake\Validation\Validation::uploadedFile が is_uploaded_file を使用しており、どうにも validation をテストで通過できなかったが、 stub 化することで対応できた。

このやり方は CakePHP 本体が同じことをやってる。

stub 用のファイルを用意して、テスト側で読み込めばOK。

// stub 側はこんな感じ
namespace Cake\Validation;

function is_uploaded_file($filename)
{
    return file_exists($filename);
}
// テスト側で stub を読み込む
require_once __DIR__ . DS . 'stubs.php';

参考

CakePHP4 と php8 の Docker コンテナを作った #cakephp #php

概要

公式 Docker Image の php:8.0-apache をベースにして、CakePHP のコンテナを作った。

DockerHub & GitHub に置いてある。

hub.docker.com

github.com

なんで作ったか

ここ最近は仕事で触る中心が CakePHP になっているので、手元で色々試せる環境をサクッと用意できるようにしておきたかったから。

あと僕は元々 php 畑じゃないこともあり、手前の理解と準備でそれなりに時間がかかったから。

OS に必要なパッケージとか php 拡張とか、設定含めた Web サーバの準備あたり。
(Web サーバについては開発だけならビルトインサーバでもいいんだけど)

突然 CakePHP 触ることになった自分みたいなケースのように、「学習の順番」をある程度柔軟にコントロールしたかったりすると役に立つかもしれない。

雑記

php:<version>-apache をベースにしている

phpApache とセットになっているイメージが公式から提供されているので、それを元にしている。

Web サーバーに強いこだわりがない(nginx 使いたいとか)がなかったのと、せっかく公式が提供しているなら使わない手はないかなと。

設定ファイルの構成とかも乗っかったりできるのが楽だった。

GitHub Actions を使ってみた

GitHub での Tag 作成をトリガーにして、 DockerHub へ反映するのに GitHub Actions を使ってみた。

DockerHub の Automated Build は正規表現でブランチ名やタグ名を引っ掛けることができるはずなんだけど、どうにもできないので、せっかくならと試してみた。

今はタグだけなんだけど、いっそ main ブランチもまとめて寄せてしまってもいいかもしれない。

それっぽい Issue は見つけた。

Automated Build Regex Doesn't Work · Issue #1831 · docker/hub-feedback · GitHub

CI もしてみた

CakePHP の実行環境ということで、新規プロジェクトを作って http アクセスできることをテストにしている

docker-cakephp/ci.yml at main · su-kun1899/docker-cakephp · GitHub

その他

KGDC Tech Conference で LT してきました #KGDC

speakerdeck.com

「息を吸うようにエラーを監視する」というタイトルで発表してきました。

イベントについて

KDDI グループ会社それぞれの技術者が集まって主催するイベントです。

グループ会社とはいえやってることはそれぞれ違うので、アドテクの話だったりインフラの話だったりE2Eの話だったりと、いい意味でバラエティに富んだ楽しいイベントだったと思います。

kgdc.connpass.com

今後継続的に開催していくようです!

LT について

本当は5分にとても収まらない色々があったw

確か入社初日の夜遅くに Slack にアラートが飛んできて、でも誰も反応してなくて、「これ大丈夫なやつですか?」とドキドキしながら聞いて回ったころからするとだいぶいい感じになりました。 (結果大丈夫なやつでスルーされてたんだけど、新参者は分からんので)

最初は一人でひたすら Issue 立てて、聞いて回って記録して、Slackの通知チャンネル整理して。

今はかなりチームっぽくなり、発生したエラーにはすべて何かしらのリアクションが 見える形で されています。

夕会やモブプロはある意味みんなで謎解きしてるみたいな楽しさもあります。

エラー監視と運用のところは自分がコネヒトに入社してから割と力を入れてきたところなので、ひとつ対外的なアウトプットにできてよかったなと思います。

「More Effective Agile」を読んだ

「CODE COMPLETE」のスティーブ・マコネルがアジャイルについて書いたということで読んでみた。

アジャイル関連の書籍はちょこちょこ読んでるんだけれども、久々に「これはいいなぁ!」と強く感じた本です。

個人的に、「アジャイル」ってエモーショナルな部分にフォーカスされがちなときがあると思っている。

一緒に考えるべき技術的な側面がどこか切り離されて表現されているように見えたり、宗教的になって「熱狂的な信者とそれ以外」のように感じてしまったり。

この本はある意味淡々と論理的に、「ソフトウェア開発をいかに効果的に行うか」という話をアジャイルのコンテキストで述べている。(タイトルそのまんまだ!)

「なぜアジャイルでやるのか」という問いに答えるためのヒントがたくさん詰まっていると思う。

非常におすすめです。

PHPStorm で PHP_CodeSniffer をかける #phpstorm

はじめに

開発環境は Docker を利用しており、最初は Docker 上の php を利用して連携しようと思っていた。

実際に連携自体はできるのだが、ホストマシンのスペックの問題なのか、避けられない遅延なのか、Inspection が走るたびに指摘事項が一瞬消えてまたつくという状態になってしまった。

なかなかにストレスフルだったので、結局ローカルの php と連携させることにした。

なおローカルで連携させるなら composer 経由のほうが楽だと思うが、 composer 周りまでローカルで用意するのは避けたかったため利用していない。

PHPStorm は 2020.3.2 を利用。

PHP_CodeSniffer を設定

設定自体は Quality Tools からできる。

f:id:su-kun1899:20210228211354p:plain

Local はたぶん最初から入っているが、 Docker 上のものを使いたいなら事前に CLI Interpreter を設定しておく必要がある(設定しないと選択肢に出てきてくれない)。

Path をフルパスで設定する。うまく認識していれば validate 押下後に成功メッセージが表示される。

f:id:su-kun1899:20210228211451p:plain

phpcbf も利用するなら合わせて設定する。

timeout は Docker 連携の場合引っかかる可能性があるので、30~60秒くらいにしておいたほうがいいが、ローカルなら 5 秒でも問題なさそう。

ちなみに Xdebug を利用していると validate 時に下記のようなエラーメッセージが出る場合がある。

Xdebug: [Step Debug] Could not connect to debugging client. Tried: host.docker.internal:9003 (through xdebug.client_host/xdebug.client_port) :-(

これは PHPStorm 側で Xdebug の Listen を切っている場合に起きてしまう警告なのだが、 phpcs のためだけに常に Listen しておいたり xdebug の有効無効を切り替えるのが微妙だったのもローカルに振り切った理由のひとつ(開発用の環境だと大抵 Xdebug 使うので)。

Inspection を設定

PHP_CodeSniffer validation を有効化する。

f:id:su-kun1899:20210228211626p:plain

自前の設定を利用するには Coding Standard を Custom にする。

ruleset を絶対パスで設定する。

f:id:su-kun1899:20210228211830p:plain

Docker 連携の場合はリモート側のパスで設定する。

これで PHPStorm 上で Inspection が効くようになる。

参考

pleiades.io

「THE MODEL」を読んだ

以前勤めていた職場の同僚に勧められて、てっきりエンジニアリングの本かと思ったら、セールスとかマーケティングとかの本だったという。

エンジニアリングとは少しテーマが違うけど、セールスフォースやマルケトの話が元になっているので、ITサービスに関わっている人なら興味深く読めると思う。

自社の営業組織がどうなってるかとは別に、自分のような非営業職が読むと、ひとつ共通言語を持てて協業がはかどりそうだなと思った。

いわゆるITサービスの営業というものを、そもそも深く知ろうとしたことすらなかったことにも気付かされた。

  • 自社の営業組織やプロセスとの違いはどこにあるだろう
  • 開発組織やプロセスとの違いはどこにあるだろう
  • 開発組織やプロセスと似ているところはどこにあるだろう

上記のようなことを考えてみると色々と学びがある。

分業による効率化とキャリアパスは Web 開発の考え方とはマッチしなそうだなーと思うけど、仕事が労働集約にならないようにとか、協力する(せざるを得ない)ような目標設定する、みたいなところはどんな職種でもそうだよなー、とか。

顧客中心、みたいなのはプロダクト開発っぽいよね。 数字は大事だけど、数字そのものよりそこから何を読み取るかとか、数字だけで評価しちゃだめとか。

具体的な How もたくさん扱っているし、採用やマネジメント、評価にも言及しているのでだいぶお得感のある内容でした。

たぶん自分からは読まなかっただろう本なので、勧めてくれた人に感謝。