config_handlers.ymlを書き換えるときの注意点

デバッグモードが無効な状態で、config_handlers.ymlに自前のコンフィグハンドラーを指定している場合、たぶんクラスが存在しないエラーがでちゃいますよ!ということです。

<?php
abstract class sfApplicationConfiguration extends ProjectConfiguration
{
  // ...
  public function initConfiguration()
  {
    $configCache = $this->getConfigCache();

    // ...

    // required core classes for the framework
    if (!$this->isDebug() && !sfConfig::get('sf_test') && !self::$coreLoaded)
    {
      $configCache->import('config/core_compile.yml', false);  // 原因はこいつ!
    }

    // autoloader(s)
    $this->dispatcher->connect('autoload.filter_config', array($this, 'filterAutoloadConfig'));
    sfAutoload::getInstance()->register();
    if ($this->isDebug())
    {
      sfAutoloadAgain::getInstance()->register();
    }
    // ...
  }
}

sfAutoload::register()を実行するまえに、core_compile.ymlを読み込もうとしてくれています。このとき、sfConfigCacheにはコンフィグハンドラーは一切設定されていないため、config_handlers.ymlを読み込もうとします。

<?php
class sfConfigCache
{
  protected function callHandler($handler, $configs, $cache)
  {
    if (count($this->handlers) == 0)
    {
      // we need to load the handlers first
      $this->loadConfigHandlers();
    }
    // ...
  }

  protected function loadConfigHandlers()
  {
    // manually create our config_handlers.yml handler
    $this->handlers['config_handlers.yml'] = new sfRootConfigHandler();

    // application configuration handlers

    require $this->checkConfig('config/config_handlers.yml');
    // ...
  }

基本はsfConfigCache::checkConfig()にコンフィグファイルの名前を指定すると、内部でcallHandler()が呼ばれます。最初にcallHandler()が呼び出された際は$this->handlesが空なのでloadConfigHandlers()が呼ばれて、その内部でconfig_handlers.ymlを読み込むということを行っています。

たとえばconfig_handlers.ymlのroutingにMyRoutingConfigHandlerクラスを設定していたとしましょう。config_handlers.ymlはsfRootConfigHandlerクラスでYAMLからPHPコードに変換されます。

# apps/frontend/config/config_handlers.yml
config/routing.yml:
  class:    MyRoutingConfigHandler
<?php
// cache/frontend/config/config_config_handlers.yml.php

// ...
$this->handlers['config/routing.yml'] = new MyRoutingConfigHandler();

で、ここでMyRoutingConfigHandlerがnewされているわけですが、オートローダーが登録されていなければもちろんクラスが見つからずエラーになってしまいます。prodとかだと登録される前なので実際にエラーがでちゃった、というわけです。。。

これ解決しようと思ったらrequireしろって話です。frontendConfigurationの中にrequire_onceを書いて対応しました。実際はMyPluginの中に入れていたので、次のようにしました。

<?php
class frontendConfiguration extends sfApplicationConfiguration
{
  public function configure()
  {
    require_once $this->pluginConfigurations['MyPlugin']->getRootDir()
      . '/lib/config/MyRoutingConfigHandler.class.php';
  }
}

追記

id:innx_hidenoriさんから一瞬で返答が返ってきました。

config_handlers.ymlの各エントリにfileってオプションを指定すると、指定したファイルを直接require_onceしてくれた気がしますが、そういう話とは別です?

Hidenori Goto on Twitter: "@fivestr config_handlers.ymlの各エントリにfileってオプションを指定すると、指定したファイルを直接require_onceしてくれた気がしますが、そういう話とは別です? #symfony_ja"
# apps/frontend/config/config_handlers.yml
config/routing.yml:
  class:    MyRoutingConfigHandler
  file:     <?php echo ProjectConfiguration::getActive()->getPluginConfiguration('MyPlugin')->getRootDir() ?>/lib/config/MyRoutingConfigHandler.class.php

知らなかったよ・・・。

さらに追記

はてブのコメントでid:brtRiverさんより。

symfonyでrequireしないと駄目なパターン. see also: http://d.hatena.ne.jp/vector_xenon/20090608/1244484490

はてなブックマーク - config_handlers.ymlを書き換えるときの注意点 - ゆっくり*ゆっくり

んで、id:vector_xenonさんの記事をみて気付いたのですが、そういえば%SF_XXX%とかって書いておくと置換してくれましたね・・・。

# apps/frontend/config/config_handlers.yml
config/routing.yml:
  class:    MyRoutingConfigHandler
  file:     %SF_PLUGINS_DIR%/MyPlugin/lib/config/MyRoutingConfigHandler.class.php

すっきり!