データベースのトランザクションやプロパティの一時的な変更など必ずある種の処理を処理本体の前後に実行すべき時があります。こういった時に便利な書き方が Laravel のソースコード内にありましたので紹介いたします。
抽象的なコードは次です。
function withPrePost(callable $callback)
{
// ここで前処理
try {
return $callback();
} finally {
// ここで後処理
}
}
何をやっているかというと前処理と後処理を一つの関数に書いて、前処理と後処理の間で行う処理を外から渡せるようにしています。こうすると前処理だけ書いた、後処理だけ書いたといった半端なミスがなくなります。
より具体的な例を考えると次の様に書けます。
class Some {
private array $options = [];
/**
* 何がしかのオプションを持つクラスのオプションを一時的に無効化して処理をする
*/
public function runWithoutlOptions(callable $callback)
{
// 復帰用に現在のオプションを変数に格納
$preOptions = $this->oprions;
// オプションを空にする(無効化する)
$this->oprions = [];
try {
// 外から渡された処理を実行して結果を返す
return $callback();
} finally {
// 処理が成功しようがしまいがオプションを元に戻す
$this->oprions = $preOptions;
}
}
}
上例は finally ですが、データベースのトランザクションなども同様の考えで書くといい感じに処理がまとまります。
public function runWithTransaction(callable $callback)
{
\DB::beginTransaction();
try {
// 外から渡された SQL を使った色々な処理を実行
$ret = $callback();
// ここまで来たら成功なのでトランザクションをコミット
\DB::commit();
// 外から渡された処理の結果を返す
return $ret;
} catch(\Throwable $e){
// 処理失敗時はトランザクション範囲をロールバック
\DB::rollback();
// 失敗した内容についての例外を更に上へ投げる
throw $e;
}
}
// 使用例
$somePdo->runWithTransaction(function(){
// 複数テーブルに対しての操作など一貫性の担保が必要なSQL諸々の実行を一つの匿名関数にまとめて渡す
\DB::update();
\DB::delete();
\DB::update();
});
このあたりの処理の確実性を求めていくと次のプロセス終了時系の処理も考えたくなります。
PHP: register_shutdown_function – Manual
PHP: pcntl_signal – Manual
というのも try, catch, finally の流れにおいて try, catch 中にプロセスが死んだ場合、finally の実行はされないからです。これを考慮する場合、try 前にイベント登録、finally でイベント登録を削除、となるでしょう。元々の register_shutdown_function に削除用関数はありませんが、これは次記事の様にソースコード上の工夫で対処可能な範囲です。
【PHP】register_shutdown_function を一度登録した関数が消せる様にラッピング – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発