如何正确地从另一个bundle覆盖Symfony扩展

14

我需要覆盖文件/vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php

它使用Twig函数“asset”从包中加载twig getAssetUrl。

/**
 * Returns a list of functions to add to the existing list.
 *
 * @return array An array of functions
 */
public function getFunctions()
{
    return array(
        new \Twig_SimpleFunction('asset', array($this, 'getAssetUrl')),
        new \Twig_SimpleFunction('assets_version', array($this, 'getAssetsVersion')),
    );
}

我想在不更改核心文件的情况下覆盖asset() twig函数,否则它将被下一个Symfony更新所覆盖。

3个回答

9
考虑您的代码位于AppBundle命名空间中。首先,在app/config.yml或src/AppBundle/Resources/config/something.yml中创建服务配置(您必须在bundle extension中加载此配置文件)。不要忘记将其放置在services关键字下方。
twig.extension.assets:
    class:     AppBundle\Twig\AssetsExtension
    arguments: ["@service_container", "@?router.request_context"]
    public: false
    tags:
        - { name: twig.extension }

现在让我们创建扩展src/AppBundle/Twig/AssetsExtension.php。这个类继承自原始扩展,我将仅覆盖一个方法(在模板资产中)。
<?php

namespace AppBundle\Twig;

class AssetsExtension extends \Symfony\Bundle\TwigBundle\Extension\AssetsExtension
{
    public function getAssetUrl($path, $packageName = null, $absolute = false, $version = null)
    {
        return parent::getAssetUrl('/something/' . $path, $packageName, $absolute, $version);
    }
}

现在,重新加载后,所有资产默认情况下都应该不正确并带有/something/前缀。如果出现问题,您可以尝试删除Symfony缓存。我在Symfony 2.5.5上测试了这种情况。
另一种使用内置服务的方法是编译器传递。在编译器传递中,您可以修改整个Symfony服务容器(删除、替换、修改服务)。Symfony就是关于DIC的。我希望这是足够好的解决方案。

1
不必复制服务定义,您也可以将参数twig.extension.assets.class设置为指向新扩展类。但编译器通行证确实是最好的解决方案。实际上,我质疑更改getAssertUrl是否有必要。如果您正在使用任何第三方模板,这可能会引起更多麻烦,不值得尝试。考虑编写自己的扩展和函数。 - Cerad
是的,没错。您还可以使用自定义扩展设置参数twig.extension.assets.class。问题很明确 - 如何在不触及Symfony代码的情况下覆盖asset()。我真的不知道用户3531149需要它的用例是什么。 - kba
你的解决方案很好。希望这不是另一个“路过式”评论。 - Cerad

9

我提供另一种解决方案,因为自从symfony 3.0以来,服务的类名已经不可重写了。你必须选择编译器传递的解决方案。

假设你想要在twig中覆盖url()函数,你应该为此创建一个编译器传递并更改定义:

<?php
// AppBundle/AppBundle.php

namespace AppBundle;

use AppBundle\DependencyInjection\Compiler\TwigRoutingExtensionPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class AppBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $container->addCompilerPass(new TwigRoutingExtensionPass());
    }
}

然后创建TwigRoutingExtensionPass文件:
<?php
// AppBundle/DependencyInjectoin/Compiler/TwigRoutingExtensionPass.php    

namespace AppBundle\DependencyInjection\Compiler;

use AppBundle\Twig\Extension\RoutingExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;

class TwigRoutingExtensionPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if (false === $container->hasDefinition('twig.extension.routing')) {
            return;
        }

        $definition = $container->getDefinition('twig.extension.routing');
        $definition->setClass(RoutingExtension::class);
    }
}

然后创建您的RoutingExtension文件。

如果您想将某些服务注入到新扩展程序中:

<?php

    $definition->addArgument('some_service');
    // OR
    $definition->addMethodCall('setSomeService' [$container->get('some_service')]);

1
我尝试了你的解决方案,但是我收到了一个错误The "Symfony\Bridge\Twig\Extension\TranslationExtension" extension is not enabled in。在我的情况下,我正在尝试覆盖trans()函数。有什么想法吗? - Plamen Vasilev
1
抱歉,您是否已启用翻译器?http://symfony.com/doc/current/translation.html#configuration - nikophil

0

你可能也想看看Symfony的“decorate”

# If it is a named service
My\CoreBundle\Twig\OverrideItWithThisExtension:
    decorates: 'some.other.twig_extension'

# If the service has a "FQCN"
My\CoreBundle\Twig\OverrideItWithThisExtension:
    decorates: Some\Other\TwigExtension

这样,原始服务其实仍然存在,如果您真的想访问它,仍然可以访问。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接