はじめに
この前、弊社 CakePHP4 プロジェクトにて beforeSave() で失敗してしまう不具合がありましたが「見かけ上は save() で失敗しているのに Entity の errors には何のエラーも保存されていない…」という困った状態でした。
しかも beforeSave() の処理そのものは Behavior に書いており、 Table クラスには何も書いてなかったので、beforeSave() の存在に気付くのも遅れました。
なので beforeSave() での失敗を何とか検知したいなと思って調べた結果をメモ。
cookbook を読んでみる
CakePHP4 の cookbook によると
beforeSave
Cake\ORM\Table::beforeSave(EventInterface, EntityInterface, ArrayObject)
Model.beforeSave イベントはエンティティーが保存する前に発行されます。 このイベントを止めることによって、保存を停止できます。イベントが停止すると、 このイベントの結果が返されます。
ちょっと分かりにくいですが、beforeSave() を止める(※falseを返す)と、save() も失敗しますよ。その場合 beforeSave() の結果を返しますよ、というお話かなと思います。
beforeSave() が false を返したので、save() もそれに従い false を返したという感じですかね?だとすると Entity の errors に何も保存されていないのは当然です。そもそも保存処理を実行してないので。
ただそれだと困るので、何とかしたい。
いくつか出た案まとめ
案1 - 例外を出す
シンプルに BeforeSaveException クラスでも作って失敗したら例外を出す、
BeforeSaveException を catch したら、beforeSave() で失敗したね、って判別出来ます。
namespace App\Exceptions;
final class BeforeSaveException
{
// 略
}
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
// 略
throw new BeforeSaveException('beforeSave() に失敗しました');
// 略
}
ただコレだと BeforeSaveException をキャッチする処理を他のクラスに書く必要があるんですよね
あと beforeSave() に複数のエラー判定ロジックがある場合、それぞれを判別できるようにまた新しく Exception を作るのか、もしくは例外メッセージで判別するのか…みたいな。
案2 - Entity の errors に突っ込む
普通に Entity の setError() メソッドを利用して、errors に突っ込みます。
Entity の errors にエラーが格納されるので、他のクラスに手を加えなくてもエラーを検知出来ます。
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
if (
// 何らかの条件
) {
$entity->setError('beforeSave', 'beforeSave() に失敗しました');
return false;
}
}
しかし保存処理を実行してないのに Entity にエラーを突っ込んで良いのか?
というか beforeSave() のエラーは Entity に入れるべきなの?という疑問が残ります…いいのかな
案3 - Entity の errors に何も入ってなかったら beforeSave() のエラーと考える
さすがにちょっと乱暴すぎる気がしますが…プロジェクトの規模によってはアリかも
まとめ
弊社では案1と悩んだ挙げ句案2を採用しました