このエントリーは Symfony Advent Calendar 2015 24日目の記事です。 昨日は @ochi51 さんの 「CybozuHttpBundleについて」 でした。(2015/12/24 9:00 現在未投稿 )
SpBowerBundleはbowerでインストールしたパッケージをSymfonyのアセットにハンドリングしてくれる便利なバンドルです。 このバンドルのキャッシュエラーで非常にハマった時の事例を紹介します。
この記事を書いた環境
- Symfony 2.7
- SpBowerBundle 0.11
SpBowerBundleの簡単なおさらい
インストール
SpBowerBundleのコマンドを使用するにはnpmパッケージbowerがインストールされている必要があります。
- https://www.npmjs.com/package/bower
SpBowerBundleのインストール方法はここでは省略します。詳しくは公式ドキュメントを参照してください。
- v0.11 Installation https://github.com/Spea/SpBowerBundle/blob/v0.11/Resources/doc/index.md
使い方
bower.json
バンドル配下にbower.json
を作成し依存パッケージを記述します。
// [バンドル]/Resources/config/bower/bower.json
{
"name": "acme-demo-bundle",
"dependencies": {
"jquery": "~1.8.2"
}
}
依存パッケージのインストール
$ php app/console sp:bower:install
コマンドを実行すると、依存パッケージのインストール・キャッシュ作成が行われます。
/Resources
/public
/components
/jquery
/cache
twigから参照
twigで依存パッケージのファイルを読み込みます。
{% block javascripts %}
{% javascripts '@AcmeDemoBundle/Resources/public/components/jquery/dist/jquery.js' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endblock %}
アセットの作成
$ php app/console assetic:dump
これで/web
配下に依存パッケージのアセットが作成され、ブラウザで開くことができるようになります。
Symfonyのページにアクセスした時のキャッシュエラー
ある日いつも通りブラウザにアクセスしたところ、このようなエラーが表示されました。
Dependency cache keys not yet generated, run "app/console sp:bower:install" to initiate the cache: Cached dependencies for "/path/to/project/AcmeDemoBundle/Resources/config/bower" not found, create it with the method createDependencyMappingCache() in . (which is being imported from "/path/to/project/app/cache/dev/assetic/routing.yml").
ユニットテストの実行でも同じメッセージが表示されます。 キャッシュエラーは時々目にしていてキャッシュクリアやインストールコマンド再実行で解決していたのですが、この日ばかりはエラーが全く解決できず、非常に悩みました。
sp:bower:installコマンドの動作を追ってみる
あまりに解決できなかったのでSpBowerBundleのソースコードを追いかけて原因を探る事にしました。
1. バンドルのロード
https://github.com/Spea/SpBowerBundle/blob/v0.11/DependencyInjection/SpBowerExtension.php#L46
SpBowerBundleがロードされると、まずconfig.yml
で設定された内容が読み込まれます。
2. sp:bower:installコマンドの実行
https://github.com/Spea/SpBowerBundle/blob/v0.11/Command/InstallCommand.php#L39
config.yml
で設定されたバンドルごとに、順番にインストール処理が行われます。
作業フォルダ
インストール処理の作業フォルダとして使用されるのは/cache/[env]/sp_bower/
です。
この設定はconfig.yml
で上書きする事ができます。
2-1. bower.json
のコピー
https://github.com/Spea/SpBowerBundle/blob/v0.11/EventListener/ExecListener.php#L52
バンドルごとに作成したbower.json
が作業フォルダにコピーされます。
2-2. .bowerrc
の出力
https://github.com/Spea/SpBowerBundle/blob/v0.11/Bower/Bower.php#L189 https://github.com/Spea/SpBowerBundle/blob/v0.11/Bower/Configuration.php#L136
config.yml
の設定内容を.bowerrc
として作業フォルダに出力します。
この時、依存パッケージのインストール先が[各バンドル]/Resources/public/components
に設定されます。
2-3. プロセス作成
https://github.com/Spea/SpBowerBundle/blob/v0.11/Bower/Bower.php#L229-L232
プロセスが実行するbowerのバイナリのパスはデフォルトでは/usr/bin/bower
ですがconfig.yml
で設定している場合はそちらで上書きされます。
7. インストールの実行
https://github.com/Spea/SpBowerBundle/blob/v0.11/Bower/Bower.php#L245
いよいよプロセスが実行されます。 この時作業フォルダは以下のような状態になっています。
/cache/[env]/sp_bower
.bowerrc
bower.json
インストールの動作は以下のコマンドを繰り返し呼び出すのと同じです。
$ cd /path/to/project/app/cache/[env]/sp_bower
$ bower install
8. キャッシュの作成
https://github.com/Spea/SpBowerBundle/blob/v0.11/EventListener/CacheCreateListener.php#L41 https://github.com/Spea/SpBowerBundle/blob/v0.11/Bower/Bower.php#L124
インストールが終わると結果がキャッシュに保存されます。
- 使用するオブジェクト:DoctrineCache (filesystem)
-
保存先:
[バンドル]/Resources/public/components/cache
-
キー:
[バンドル]/Resources/config/bower
をsha1でハッシュしたもの -
内容:
bower list --json
で取得したインストール結果
キャッシュキーはバンドル直下のクラスAcmeDemoBundle.php
をReflectionClass::getFileNameした絶対パスから生成されます。
9. アセットの作成
$ php app/console assetic:dump
コマンド実行時にSpBowerBundleはキャッシュから依存パッケージを読み込もうとします。
https://github.com/Spea/SpBowerBundle/blob/v0.11/Resources/config/assetic.xml#L21-L22 https://github.com/Spea/SpBowerBundle/blob/v0.11/Assetic/BowerResource.php#L96-L98 https://github.com/Spea/SpBowerBundle/blob/v0.11/Bower/Bower.php#L148
-
config.yml
で登録されたバンドルを読み込み - キャッシュキーを再作成
- キャッシュを読み込み
この時にキャッシュが存在しないと Dependency cache keys not yet generated… のエラーが発生する、という仕組みになっています。
今回発生していたエラーの解決
キャッシュキーがどのように作成されているかを調べたところで、ファイルシステムの返す絶対パスがおかしくなっている事に気づきました。ファイルシステムの不具合の原因までは調査していませんが、SpBowerBundleのエラーに関してはPC再起動で解決する事ができました。
キャッシュエラーの解決策を紹介
今回調査したSpBowerBundleの仕組みを元に有効と思われる解決策を記載します。
注意
フォルダやパッケージ削除を含みますので実行には十分ご注意ください
インストールコマンド再実行
新しくバンドルが追加された場合など、キャッシュ未作成時にエラーが出るのは正しい状態です。大抵のキャッシュエラーはインストールコマンド実行で解決します。
$ php app/console sp:bower:install
特定の環境だけで起きる場合
dev(ブラウザ)では問題なくページが開くのにtest(ユニットテスト)はキャッシュエラーが発生する、という状況もあります。そのような場合は環境を指定してインストールコマンドを実行します。
$ php app/console sp:bower:install --env=[dev/test]
bowerパッケージ全削除
ひとつ前のキャッシュがエラーを起こしている場合はキャッシュフォルダの削除が有効です。
またインストール済みパッケージが残っているとコンフリクトを起こす事もあるのでcomponents
配下の全削除をおすすめします。
# 全バンドルのcomponentsを削除して再インストール
$ rm -rf $(find src -regex ".*\/Resources\/public\/components$")
$ php app/console sp:bower:install
引用元
http://qiita.com/ttskch/items/8fb6c6c3d67a958e4179
作業ディレクトリ削除
キャッシュエラーは[バンドル]/Resources/public/components/cache
に由来するものと、/app/cache/[env]/sp_bower
に由来するものと2通りがあります。
// エラーをよく見るとパスがapp/cacheになっている
Dependency cache keys not yet generated, run "app/console sp:bower:install" to initiate the cache: Cached dependencies for "/path/to/project/app/cache/dev/sp_bower" not found, create it with the method createDependencyMappingCache().
このような場合はSymfonyのキャッシュクリアが有効です。
$ php app/console cache:clear
$ php app/console sp:bower:install
bower再インストール
今回のキャッシュエラーとは関連しませんが./node_modules/bin/bower
の破損が原因でエラーになる事もありました。そのような場合はnpmのキャッシュクリアと再インストールを試します。
$ rm -rf ~/.npm
$ rm -rf ./node_modules
$ npm install
$ php app/console sp:bower:install
フォルダリネーム
何らかの原因でキャッシュキーの生成に原因がある可能性も考えられます。私が今回遭遇したトラブルの原因はプロジェクトフォルダの大文字・小文字の違いによるものでした。実際のフォルダ名Project
とPHPのReflectionClassが返すフォルダ名project
の先頭1文字の違いにより、ハッシュ化したキー名が異なっていました。このような状況ではフォルダのリネームやPC再起動も有効な解決策となるかもしれません。
まとめ
以前からたびたびこのバンドルのキャッシュエラーに遭遇して悩まされてきました。今回はじっくり原因を調査した事で「今まで試したどの対応が有効だったのか」を理解する事ができ、またコードリーディングのいい機会となりました。同じトラブルに遭って悩んでいる方の参考になればと思います。
明日の担当は @iteman さんです!