Symfony 添加默认模板搜索路径

Symfony add default template search path

我的问题与 this question 类似,但略有不同,由于该问题没有得到解答,我想我会再试一次。

我知道 Symfony 会首先在 app/Resources/FooBundle/views 中查找模板,然后在 FooBundle/Resources/views 中查找模板。我想 "inject" 两者之间的另一个位置,例如

  1. app/Resources/FooBundle/views
  2. BarBundle/Resources/FooBundle/views
  3. FooBundle/Resources/views

我尝试覆盖 \Symfony\Component\HttpKernel\Config\FileLocator 服务并在那里添加路径,但这没有用。我也在 controller::render 方法中尝试了答案 here 但没有成功。 (建议的 'native' 方法不起作用,因为路径来自动态设置。)

作为次要说明,我需要能够在以后(可能是 onRequest)而不是在创建容器时添加此路径。因此,在控制器中使用 EventListener、自定义服务方法或调用是合适的。

解决这个问题的部分挑战是意识到(至少在 Symfony 2.7 中)至少有两种方法来指定模板名称:'old way'(因为没有更好的名称)和后来实现的 'name-spaced' 方法。例如

class MyController extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
    // the 'old way'
    public function barAction()
    {
        return $this->render('FooBundle:User:foo.html.twig');
    }
    // namespaced
    public function fooAction()
    {
        return $this->render('@Foo/User/foo.html.twig');
    }
}

这两种方法会导致不同的模板位置搜索,因此必须在两个不同的地方修改代码。

为了修改'old'方式,我覆盖了Kernel::locateResource()方法

public function locateResource($name, $dir = null, $first = true)
{
    $customBundle = $this->container->get('my_custom_bundle');
    $locations = parent::locateResource($name, $dir, false);
    if ($locations && (false !== strpos($locations[0], $dir))) {
        // if found in $dir (typically app/Resources) return it immediately.
        return $locations[0];
    }

    // add custom path to template locator
    // this method functions if the controller uses `@Template` or `FooBundle:Foo:index.html.twig` naming scheme
    if ($customBundle && (false === strpos($name, $customBundle->getName()))) {
        // do not add override path to files from same bundle
        $customPath = $customBundle->getPath() . '/Resources';
        return parent::locateResource($name, $customPath, true);
    }
    return $locations[0];
}

为了修改命名空间的方式,我添加了一个ControllerListener

class OverrideListener implements EventSubscriberInterface
{
    private $loader;
    private $customBundle;

    function __construct(\Twig_Loader_Filesystem $loader, $customBundle)
    {
        $this->loader = $loader;
        $this->customBundle = $customBundle;
    }

    /**
     * @param FilterControllerEvent $event
     * @throws \Twig_Error_Loader
     */
    public function setUpOverrides(FilterControllerEvent $event)
    {
        // add custom path to template locator
        // This 'twig.loader' functions only when @Bundle/template (name-spaced) name-scheme is used
        $controller = $event->getController()[0];
        if ($controller instanceof AbstractController) {
            $bundleName = $controller->getName(); // my AbstractController adds a getName method returning the BundleName
            if ($this->customBundle) {
                $overridePath = $this->customBundle->getPath() . '/Resources/' . $bundleName . '/views';
                if (is_readable($overridePath)) {
                    $paths = $this->loader->getPaths($bundleName);
                    // inject overridePath before the original path in the array
                    array_splice($paths, count($paths) - 1, 0, array($overridePath));
                    $this->loader->setPaths($paths, $bundleName);
                }
            }
        }
    }

    public static function getSubscribedEvents()
    {
        return array(
            KernelEvents::CONTROLLER => array(array('setUpOverrides')),
        );
    }
}

我希望这可以帮助任何正在寻找类似解决方案的人。