昨日に引き続き、symfony2を読む。カレントディレクトリをsymfony2のインストールディレクトリとする。
web/app_dev.php
[php]
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();
$kernel->handle(Request::createFromGlobals())->send();
[/php]
useにより、$SYMFONY2_DIR/vendor/symfony/src/Symfony/Component/HttpFoundation/Request.php を読み出している。
この $SYMFONY2_DIR/vendor/symfony/src の位置はどうやって伝えられたのだろうか?それは bootstrap.php.cache 内で読み込まれた autoload.php に秘密があった。
app/autoload.php
[php]
$loader = new UniversalClassLoader();
$loader->registerNamespaces(array(
'Symfony' => array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/bundles'),
[/php]
loadClassCache(), handle()はともにAppKernelの親クラスであるKernelクラスの下記メソッドが呼ばれている。
vendor/symfony/src/Symfony/Component/HttpKernel/Kernel.php
[php]
public function loadClassCache($name = 'classes', $extension = '.php')
{
if (!$this->booted && file_exists($this->getCacheDir().'/classes.map')) {
ClassCollectionLoader::load(include($this->getCacheDir().'/classes.map'), $this->getCacheDir(), $name, $this->debug, false, $extension);
}
}
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
if (false === $this->booted) {
$this->boot();
}
return $this->getHttpKernel()->handle($request, $type, $catch);
}
[/php]
今日は loadClassCache()の挙動をよく調べてみよう。まず
[php]
$this->booted # false
$this->getCacheDir().'/classes.map' # app/cache/dev/classes.map
[/php]
となっている。bootedの値ついては__constructメソッドを読めば分かる。
[php]
ClassCollectionLoader::load
[/php]
では、下記クラスのloadメソッドが呼ばれている。
vendor/symfony/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php
[php]
static public function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php')
{
// each $name can only be loaded once per PHP process
if (isset(self::$loaded[$name])) {
return;
}
self::$loaded[$name] = true;
[/php]
$nameという名前で、$classesに渡されたクラス群を読み込むらしい。$nameと$classesは一対一対応となっている。
[php]
if ($adaptive) {
// don't include already declared classes
$classes = array_diff($classes, get_declared_classes(), get_declared_interfaces());
// the cache is different depending on which classes are already declared
$name = $name.'-'.substr(md5(implode('|', $classes)), 0, 5);
}
[/php]
ここでは $adaptive = false なのでif文の中には入らない。
[php]
// auto-reload
$reload = false;
if ($autoReload) {
$metadata = $cacheDir.'/'.$name.$extension.'.meta';
if (!file_exists($metadata) || !file_exists($cache)) {
$reload = true;
} else {
$time = filemtime($cache);
$meta = unserialize(file_get_contents($metadata));
if ($meta[1] != $classes) {
$reload = true;
} else {
foreach ($meta[0] as $resource) {
if (!file_exists($resource) || filemtime($resource) > $time) {
$reload = true;
break;
}
}
}
}
}
if (!$reload && file_exists($cache)) {
require_once $cache;
return;
}
[/php]
$autoReload変数には今回true(AppKernel->debug)が渡されているのでif文の中に入る。ここでは$classesを再度読み込み直すか?それともキャッシュされたクラスをそのまま使うのかが指定されている。再読み込みする条件は3つ。
- そもそもキャッシュが存在しない
- キャッシュされているクラス群と読み込もうとしているクラス群が異なる
- キャッシュ元のクラスが更新されている
[php]
$files = array();
$content = '';
foreach ($classes as $class) {
if (!class_exists($class) && !interface_exists($class) && (!function_exists('trait_exists') || !trait_exists($class))) {
throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
}
$r = new \ReflectionClass($class);
$files[] = $r->getFileName();
$c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($r->getFileName()));
// add namespace declaration for global code
if (!$r->inNamespace()) {
$c = "\nnamespace\n{\n".self::stripComments($c)."\n}\n";
} else {
$c = self::fixNamespaceDeclarations(' $c = preg_replace('/^\s*<\?php/', '', $c);
}
$content .= $c;
}
// cache the core classes
if (!is_dir(dirname($cache))) {
mkdir(dirname($cache), 0777, true);
}
self::writeCacheFile($cache, '[/php]
$classesを1つずつ読み込み、最終的にすべてのクラスを1つのファイル($cacheDir.'/'.$name.$extension)にまとめている。ここで$nameと$classesを一対一対応にしたことが効いてくる。
[php]
if ($autoReload) {
// save the resources
self::writeCacheFile($metadata, serialize(array($files, $classes)));
}
[/php]
オブジェクトを直列化してファイルを保存する。
dev環境、prod環境でのautoReloadの違い
prod環境では$autoReload=falseとなり、キャッシュを消すまで古いクラスが使われ続ける。これは安定した速い運用が求められるprod環境に適している。対してdev環境では$autoReload=trueとなり、常に最新のソースコードでSymfonyを動かすことになる。多少ブラウザ表示が遅くなるとはいえ、開発効率を上げるための工夫である。