CakePHPには、プラグイン機能やViewを切り替えられるテーマ機能があり、静的なアセットファイルをwebrootとして一緒に含んで配布することが可能。便利なんだけど、当然、外から見られるような場所にあるわけじゃないので、CakePHPが用意したディスパッチャーを通して出力される。
前提
- CakePHP 2.4.x
- Apache 2.2.x
注意する点
何に注意しなくちゃいけないのかというと2点。
- PHPを通して静的ファイルを読み込み、出力するのでパフォーマンスが悪い(マニュアルにも記載されている)
- JSとCSSファイルは readfile() ではなく include で読み込まれるためPHPとして解釈される
1点目のパフォーマンス問題に関しては、マニュアルにあるように通常のwebrootからシンボリックリンクを貼れば解消される。
問題は2点目のJSとCSSをPHPとして解釈して出力する点。例えば以下の様なJSファイルが書ける。
var url = '<?php echo 'http://example.com'; ?>';
var url = 'http://example.com';
一見便利そうに見える。えせsassっぽいこともできそう。が、これを表示させると出力が途中で切れる可能性がある。なぜ…と思って、CakePHPのディスパッチャーの該当ソースを見てみた。AssetDispatcher.phpに書かれている。
<?php
protected function _deliverAsset(CakeResponse $response, $assetFile, $ext) {
ob_start();
$compressionEnabled = Configure::read('Asset.compress') && $response->compress();
if ($response->type($ext) === $ext) {
$contentType = 'application/octet-stream';
$agent = env('HTTP_USER_AGENT');
if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
$contentType = 'application/octetstream';
}
$response->type($contentType);
}
if (!$compressionEnabled) {
$response->header('Content-Length', filesize($assetFile));
}
$response->cache(filemtime($assetFile));
$response->send();
ob_clean();
if ($ext === 'css' || $ext === 'js') {
include $assetFile;
} else {
readfile($assetFile);
}
if ($compressionEnabled) {
ob_end_flush();
}
}
最後の方にある、拡張子がcssとjsだった場合は、readfile() ではなく include を使っていることが分かる。これによってPHPのコードが書ける恩恵がある。が、その上のコードを見るとContent-Lengthを出力している。
$response->header('Content-Length', filesize($assetFile));
これ、何がまずいかというと、PHPのコードを含んだ状態でのファイルサイズになるため、PHPが解釈されて実際に出力されるファイルサイズとは違うことになる。そうなると、10Kbあるはずのものが、12Kb出力しようとして、途中で切れてしまったりする。
解決方法
正直ここの根本的な解消が思いつかなかったが、gzip(mod_deflate)を有効にして、ここのContent-Lengthを無視させることで解消できる。
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/atom_xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/x-httpd-php
AddOutputFilterByType DEFLATE application/x-httpd-fastphp
AddOutputFilterByType DEFLATE application/x-httpd-eruby
</IfModule>
設定は超適当。
だが、なんかそもそもこのJSやCSSにPHPのコードを書いて、PHP経由で出力させること自体キモい感じするので、素直にシンボリックリンクを貼って、静的ファイルとして配信することが一番かもしれない。
参考URL