tsyama記

プログラミングとそのほか

CakePHP 3.8.0のPull requestsを読み込んだ

はじめに

こちらはCakePHP Advent Calendar 2019 18日目の記事です。

ここのところ、個人的にも仕事的にもLaravelに手を出し始め、CakePHPをとんと触っていなかったおかげで、CakeFestやCakePHP4系の話など、Cake界隈の盛り上がりをキャッチアップできていません。

新卒1年目の頃からお世話になっていたCakePHPは僕にとって親鳥のような存在なので、このまま流れに置いていかれないために、今年リリースされたCakePHP3.8.0でマージされたPull requestsを読み込んでみました。

GitHubで近況を知る

github.com

CakePHPリポジトリはここにあります。

リリースノートを確認する

Releases · cakephp/cakephp · GitHub

ここからリリースノートを確認します。
マイナーバージョン以上に限定すると、今年は3.8のリリースがありました。他にも記事執筆時点では12/15に4.0のリリースが予定されているようです。

4系の話題はすでに他の方が別の記事で丁寧に取り上げているので、今回は3.8に焦点をあてましょう。
ちなみに3.8のリリースノートはコレです。

Release CakePHP 3.8.0 released · cakephp/cakephp · GitHub

ありがたいことに移行ガイドもあったりします。

https://book.cakephp.org/3/ja/appendices/3-8-migration-guide.html

Milestoneを確認する

もうちょっと詳しく見るためにMilestoneを確認しましょう。
cakephp/cakephpでは、IssueやPull requestsにMilestoneが設定されているため、特定のバージョンにマージされたPull requests一覧が確認できたりします。

3.8.0 Milestone · GitHub

3.8.0のIssueやPull requestsの一覧はここです。
[Issuesタブ] > [Milestones] > [Closed] > [3.8.0] > [Closed] でいけます。

3.8.0でマージされたもの

#13088 - Validation::datetime

Validation::datetime()は、入力文字列が有効な日付と時刻の表記になっていることをチェックするメソッドです。

https://api.cakephp.org/3.8/class-Cake.Validation.Validation.html#_datetime

もともとは第二引数 $dateFormat で日付のフォーマットは指定できるものの、日付と時刻の区切り文字についてはスペースのみを有効なものとみなしていましたが、 $dateFormatValidation::DATETIME_ISO8601 を指定することにより、ISO8601形式(2019-09-06T15:00:00+02:00 のようなTを区切り文字とする表記)を受け入れることができるようになりました。

3.next - Add notEmpty* validator methods

こちらもValidation周りに関するPull Requestです。

もともとValidator::allowEmpty()という、空であることを許容するバリデーション用メソッドがあったのですが、これが3.7で非推奨になり、代わりに

  • allowEmptyString()
  • allowEmptyArray()
  • allowEmptyDate()
  • allowEmptyTime()
  • allowEmptyDateTime()
  • allowEmptyFile()

といったメソッドが追加され、フィールドが空とみなすべきものをより細かく制御できるようになりました。
これを補完するように、空でないことを強制するバリデーションを追加したのがこのPull Requestです。

  • notEmptyString()
  • notEmptyArray()
  • notEmptyDate()
  • notEmptyTime()
  • notEmptyDateTime()
  • notEmptyFile()

が追加され、同じく3.7で非推奨となったValidator::notEmpty()を置き換えて厳密なバリデーションを制御できるようになります。

さらに、3.7で実装されたallowEmpty*系メソッドの引数の取り方を、

allowEmptyString($field, $when, $message)

から

allowEmptyString($field, $message, $when)

に変更し、前者の渡し方をしようとすると非推奨のエラーが出るようにしたみたいです。
これはcallableを取りうる$whenを中間の引数に置くのは微妙だよねっていう経緯らしいです。3.8で新しく追加されたnotEmpty*系のメソッドについても同様の引数の取り方になっています。

Fix backward incompatibility for allowEmpty* methods

こちらは今年のCakePHP Advent Calendarにも参加されている@chinpei215さんのPull Requestです。
ひとつ上のPull requestに関連して、第二引数にcreateupdateが指定された場合の後方互換用処理を修正しています。

3.next - Improve console testing with interactive input

現在、インタラクティブな入力を使用するシェル/コマンドのテストは、十分な入力値を提供しないと、プロセスが入力を待機してロックするかのように退屈です。この変更により、十分な入力値がない場合に例外が発生し、テストの構築が容易になります。

とのこと。

Backport 4.x deprecations for easier upgrading.

Entityのvisibleなプロパティを取得するEntityTrait::visibleProperties()が非推奨となり、getVisible()への移行を促すようになりました。
メソッド名の変更のみで、処理自体に変更はありません。

3.next - Improve nested validator support in EntityContext

今までサポートされていなかった、ネストされたバリデーションエラーをEntity::getError()EntityContext::error()で検出できるようになったみたいです。テストコードを見るとわかりやすいかも。

ちなみにこの変更、マージはされているようですが、移行ガイドには記載がないみたいですね…

Make mime type validation case insensitive for capitalized mime types

フォームでアップロードしたファイルのMIMEタイプのバリデーションをチェックするValidation::mimeType()について、大文字小文字の区別をしないように変更されました。

Backport 4.x model aware fix for inflected assignment.

軽微なバグ修正のPull Requestです。

loadModel()でモデルを呼び出せたりするModelAwareTraitを使用しているClass(Controllerなど)について、$modelClassプロパティを明示的にfalseに設定しているときに予期せぬ動作をする不具合を修正しています。

3.next - Add success/error exit code assertions

ConsoleIntegrationTestTraitクラスに、assertExitSuccess()assertExitError()という2つのメソッドが追加されました。これにより、今までassertExitCode()でまかなっていたアサーションが、よりわかりやすく簡潔な処理になります。

ISO8601 for datetime json format, when on Danish locale: Resolves #13232

Cake\I18n\Time 、 FrozenTime 、 Date および FrozenDate のデフォルトフォーマットは、 デンマーク語や他のヨーロッパのロケールにおけるローカリゼーション問題を解決する yyyy-MM-dd'T'HH':'mm':'ssxxx になります。

とのこと。もともとは

yyyy-MM-dd'T'HH:mm:ssxxx

でした。詳しくはよくわからないのですが、デンマーク語などを使用している場合に、引用符で囲まれていないコロンがjsonで誤った値を生成することがあるとかなんとか。

ISO8601 for datetime json format, when on Danish locale · Issue #13232 · cakephp/cakephp · GitHub

Avoid unnecessary inflection to generate file name for error message.

MissingTemplateExceptionが発生した際に、不要なファイル名の生成処理があったのを修正しています。

Add Command::executeCommand()

現在のコマンドから別のコマンドを簡単に呼び出せる、Command::executeCommand()が追加されました。
こんな感じで使うようです。

$output = new ConsoleOutput();
$command = new Command();
$result = $command->executeCommand(DemoCommand::class, ['Jane'], $this->getMockIo($output));

https://api.cakephp.org/3.8/class-Cake.Console.Command.html#_executeCommand

Add Response::withCookieCollection().

引数にCookieCollectioncookieを管理するクラス)インスタンスを渡し、それをセットした状態のResponseインスタンスを新しく作成して返してくれるResponse::withCookieCollection()メソッドが追加されています。

Add missing autoload to make $modelClass property useful.

Commandクラスに$modelClassプロパティが設定されている場合、loadModel()を呼び出さなくてもそのModelを自動でロードしてくれるようになりました。
これはControllerShellにはすでに実装されている機能で、そちらと一貫性をもたせるための機能追加です。

Allow using FQCN with ModelAwareTrait::loadModel().

FQCNとは、「完全修飾クラス名」のことです。
ModelAwareTraitが提供するloadModel()メソッドは今まで、

$this->loadModel('Products');

のような呼び出し方しかできませんでしたが、

$this->loadModel(CommentsTable::class);

という呼び出し方ができるようになります。IDEの補完とかが使いやすくなるので、これは地味にありがたい機能追加ですね。

Allow setting multiple headers of the same name.

テストコードを見るのがわかりやすいですね。
Emailでヘッダを設定するaddHeaders()などで同一nameのヘッダを複数指定できるようになりました。

Add test case for patching.

Table::findOrCreate()に関するテストコードが追加されました。

Allow custom label attributes array for each radio button

echo $this->Form->radio(
    'favorite_color',
    [
        ['value' => 'r', 'text' => 'Red', 'label' => ['style' => 'color:red;']],
        ['value' => 'u', 'text' => 'Blue', 'label' => ['style' => 'color:blue;']],
        ['value' => 'g', 'text' => 'Green', 'label' => ['style' => 'color:green;']],
    ]
);

Viewでラジオボタンレンダリングする際、個別のラジオボタンの設定を配列で渡す場合にlabelをこんな感じで設定できるようになったみたいです。

このキーはトップレベルのオプションで定義された label キーの代わりに使われます。

とのことなので、トップレベルのオプションでデフォルト値を設定しておき、個別で設定が必要な場合に都度指定する、という使い方でしょうか。

Improve generated .pot references compatibility with PO editors

多言語対応をするためのPO Editorというサービスがあるようなのですが、それに関連する機能修正Pull requestです。

poeditor.com

該当のファイル名/行数の参照が

#: ./vendor/cakephp/cakephp/src/I18n/RelativeTimeFormatter.php:47;126;316

という出力になっていたのを

#: ./vendor/cakephp/cakephp/src/I18n/RelativeTimeFormatter.php:47
#: ./vendor/cakephp/cakephp/src/I18n/RelativeTimeFormatter.php:126
#: ./vendor/cakephp/cakephp/src/I18n/RelativeTimeFormatter.php:316

に直したっぽい?詳しくはよくわかりません。

New test to show how plugin based assets interact with BaseUrl config

こちらもテストコード追加のPull requestです。

$result = $this->Helper->image('TestTheme.text.jpg');

のようにプラグインのアセットを呼び出す際、

$cdnPrefix = 'https://cdn.example.com/';
Configure::write('App.imageBaseUrl', $cdnPrefix);

みたいにbaseUrlの設定が有効だったときにちゃんとその設定を使用してファイルを参照することを保証するテストのようです。

Patching search data in findOrCreate to ensure valid entity

みんな大好きTable::findOrCreate()、以前はfindできずcreateする場合にこのような処理になっていました。

$entity->set($search, ['guard' => false]);
return $this->save($entity, $options) ?: $entity;

set()を使ってしまうとバリデーションが効かないですし、save()に失敗してもExceptionなどは出さず、そのままEntityを返すようになっていました。

修正後の処理がこんな感じです。

$entity = $this->patchEntity($entity, $search, ['accessibleFields' => $accessibleFields]);
$result = $this->save($entity, $options);

if ($result === false) {
    throw new PersistenceFailedException($entity, ['findOrCreate']);
}

patchEntity()を使うことでバリデーションチェックを実行し、save()に失敗した場合はPersistenceFailedExceptionをスローするようになりました。

Support multiple table locations.

Tableインスタンスの呼び出しを行うTableLocatorに関する修正です。
TableLocatorはデフォルトではModel/Tableの中からクラスを探しますが、

$locator = new TableLocator(['Infrastructure/Table']);

のようにして、クラスを探すnamespaceを指定できるようになりました。

$locator = new TableLocator([
    'Infrastructure/Table',
    'Model/Table',
]);

といった感じで複数指定もできます。
これにより、フレームワークに依存せずORMが使いやすくなったり、ORMをカスタマイズしやすくなりそうです。

Better grouping in CLI for commands.

bin/cake --help

で出てくるコマンドのリストが見づらいので直したよ!という話っぽいです。

- annotations
- asset_compress
- bake
- benchmark
- cache
- cache.cache
- clear
- code_completion
- completion
- current_config
- database_log
- db_backup
- db_maintenance
- db_migration

...

今まではこんな感じで全部のコマンドが並列で並んでいましたが、

AssetCompress:
 - asset_compress
Bake:
 - bake
Cache:
 - cache
DatabaseLog:
 - database_log
DebugKit:
 - benchmark
 - whitespace

...

[app]:
 - main
 - notification
 - update
[core]:
 - cache
 - completion
 - help
 - i18n
 - orm_cache
 - plugin
 - routes
 - schema_cache
 - server
 - version

app, coreと各プラグインのコマンドが分けて表示されるようになりました。

おわりに

CakePHP 3.8.0リリースのPull requestsをひたすら追っかけただけですが、思ったよりもタメになりました。リリースノートに記載されていない修正や機能追加もあるみたいですし、機能追加に至るまでのコミッタのやりとりを見るのも面白いです。

今回は3.8.0だけを見てみましたが、パッチバージョンや他のマイナー/メジャーバージョンのものも見てみるとよりしっかりキャッチアップできそうですね。

明日は@itoshoさんです。