Laravelのbootstrap/cache/services.phpが消えて500エラーになった

起きたこと

Laravelのプロジェクトで、新しいライブラリの導入を行いたかったので、composer updateを行ったところ、その後アプリケーションが500エラーとなってしまいました。エラーログは以下の通り(パスは一部ダミー)。

[2017-07-04 13:07:09] ERROR   ErrorException: file_put_contents(/path/to/laravel/application/bootstrap/cache/services.php): failed to open stream: Permission denied in /path/to/laravel/application/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php:111
Stack trace:
#0 [internal function]: Illuminate\Foundation\Bootstrap\HandleExceptions->handleError(2, 'file_put_conten...', '/path/to/laravel...', 111, Array)

エラーメッセージにあるパスを確認すると、 /path/to/laravel/application/bootstrap/cache/services.php にあたるファイルが消えてしまっているようです。

どうして消えてしまうのか? 再作成する方法はないのか? そもそもこのservice.phpというファイルは何なのか? いろいろと疑問がいっぱいだったので調べてみました。

bootstrap/cache/services.phpとは

services.phpファイルは、アプリケーションで使用しているサービスプロバイダの読み込みを最適化するためのファイルです。vimなどで中身を見てみると、PHPの配列の形で、遅延読み込みをするかどうかなどの情報が保存されているのがわかります。

ちなみにこのファイル、あるバージョンまではservices.jsonという名前で、形式もJSONだったのですが、パフォーマンス上の理由からphpのvar_exportしたものを吐き出す形に変わったようです。 (このあたりのgithubの履歴参照 )

実際に作成する処理は ProviderRepository.phpのwriteManifestメソッドで、これを呼び出すのは、 App\Http\Kernel, App\Console\Kernel のいずれかです。

composer updateの後にbootstrap/cache/services.phpが消えてしまう理由

デフォルト設定だと、composer.jsonの設定により、composer install, composer updateの後には php artisan optimize のコマンドが実行されます。このコマンドは「optimize」の名の通り、読み込みの最適化のため、現在のservices.php を削除します。

で、ここが私の勘違いしていたポイントなのですが、このコマンドでは、services.phpの再作成は行われない のです。「最適化のため、現在のキャッシュされたservices.phpファイルを削除する」というところまでがこのコマンドの領域で、services.phpが作成されるのは、次のコマンド実行 or ページアクセスの行われたタイミングとなります。(私はしばらくこのコマンドが再作成まで担うものだと思いこんでいました……)

私の開発環境の場合、Webサーバーの実行は apache:apacheユーザー、コマンド実行は vagrant:vagrantユーザーとなっていて、bootstrap/cache の権限は755でした。そのため、以下のような事態となっていました。

  • ディレクトリのownerをvagrantグループにすると、artisanコマンドにより bootstrap/cache/services.phpが削除されるが再作成ができない
  • ディレクトリのownerをapacheグループにすると、artisanコマンドにより bootstrap/cache/services.phpを削除することができない  (この場合特にエラーメッセージなどは表示されませんが、サービスプロバイダを足したりした場合に反映されない不具合が起こりそうです……)

対処

Laravelプロジェクト内のファイル・ディレクトリのオーナーおよび権限の見直し をしました。

  • bootstrap/cache/services.phpの作成をするのは、Webサーバーの実行ユーザーもしくはartisanコマンドの実行ユーザー
  • bootstrap/cache/services.phpの削除をするのは、artisanコマンドを実行するユーザー

以上であることを踏まえると、webサーバーの実行ユーザーと、artisanコマンドの実行ユーザーのユーザーグループを同じにしておくのがベストのようなので、ユーザーグループをいじって対応しました。(ファイルないしフォルダのアクセス権限を広げることも可能ですが、セキュリティなどを考慮するとあまり良くないですよね。)

ちなみにディレクトリの権限の話題については、StackOverflowでも議論されていて、やはり同一ユーザーグループにするのが良いという結論になっているようです。 php - File permissions for Laravel 5 (and others) - Stack Overflow

参考