干预模板渲染
Intervene template rendering
我有一个控制器方法,我用它来 "collect" 变量分配给模板。我重写了控制器的 render() 方法来合并 "collected" 和渲染参数并将它们分配给模板。
示例:
class Controller extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
private $jsVars = [];
protected function addJsVar($name, $value)
{
$this->jsVars[$name] = $value;
}
public function render($view, array $parameters = [], Response $response = null)
{
return parent::render($view, array_merge($parameters, ['jsVars' => $this->jsVars], $response);
}
public function indexAction()
{
// collect variables for template
$this->addJsVar('foo', 'bar');
return $this->render('@App/index.html.twig', ['foo2' => 'bar2']);
}
}
我刚刚升级到 Symfony 3.4,它抱怨说自从 Symfony4 以来,我不允许重写 render() 方法,因为它将是最终的。
我怎样才能让它无缝运行,即不定义新方法?
- 我知道 Twig 全局变量,但这些对我没有帮助
- 我可以使用服务来收集变量并将该服务注入 Twig,但这看起来很奇怪
- 是否有我可以收听的事件,例如 TwigPreRender 或 smth?
您可以像这样从 Twig 内部渲染控制器:
{{ render(controller('App\Controller\YourController::yourAction', { 'args': 'hi' })) }}
文档here
好像没有捷径
基本上有2种选择:
- 通过扩展当前 Symfony\Bundle\TwigBundle\TwigEngine
创建您自己的模板引擎
- 修饰当前模板引擎服务
templating.engine.mytwig
我选择了后者
少量解释:
- 我创建了 decorates 当前引擎
templating.engine.twig
的服务 templating.engine.mytwig
。 Class 将获取当前的“TwigEngine”作为输入,我会将大部分内容委托给它
- class 也需要通过实施
\Twig_ExtensionInterface
成为 twig extension(或者扩展 \Twig_Extension
对我来说就足够了)。服务还需要有标签 twig.extension
。否则,您最终会遇到诸如 "Cannot find private service 'assetic' etc" 之类的错误
- setParameter/getParameter用于收集和返回参数
- 然后我向控制器添加了快捷方式 -
setJsVar
- Twig 模板还需要处理这些变量,最好是在布局级别的某个地方。但这不包括在这里
- 你可以使用这个解决方案来收集任意模板参数,例如,如果你想从另一个方法或其他方法分配
- 最好在渲染后清除收集的参数
这一切值得吗?我不知道 :) 无法理解为什么 Symfony 团队首先选择 Controller::render 决赛。但无论如何它是:
TwigEnging class:
namespace My\CommonBundle\Component\Templating\MyTwigEngine;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Bundle\TwigBundle\TwigEngine;
use Symfony\Component\HttpFoundation\Response;
class MyTwigEngine extends \Twig_Extension implements EngineInterface
{
/**
* @var TwigEngine $twig Original Twig Engine object
*/
private $twig;
/**
* @var array $parameters Collected parameters to be passed to template
*/
private $parameters = [];
/**
* MyTwigEngine constructor.
*
* @param TwigEngine $twig
*/
public function __construct(TwigEngine $twig)
{
$this->twig = $twig;
}
/**
* "Collects" parameter to be passed to template.
*
* @param string $key
* @param mixed $value
*
* @return static
*/
public function setParameter($key, $value)
{
$this->parameters[$key] = $value;
return $this;
}
/**
* Returns "collected" parameter
*
* @param string $key
* @return mixed
*/
public function getParameter($key, $default = null)
{
$val = $this->parameters[$key] ?? $default;
return $val;
}
/**
* @param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
* @param array $parameters
*
* @return string
* @throws \Twig\Error\Error
*/
public function render($name, array $parameters = array())
{
return $this->twig->render($name, $this->getTemplateParameters($parameters));
}
/**
* @param string $view
* @param array $parameters
* @param Response|null $response
*
* @return Response
* @throws \Twig\Error\Error
*/
public function renderResponse($view, array $parameters = array(), Response $response = null)
{
return $this->twig->renderResponse($view, $this->getTemplateParameters($parameters), $response);
}
/**
* @param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
*
* @return bool
*/
public function exists($name)
{
return $this->twig->exists($name);
}
/**
* @param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
*
* @return bool
*/
public function supports($name)
{
return $this->twig->supports($name);
}
/**
* @param $name
* @param array $parameters
*
* @throws \Twig\Error\Error
*/
public function stream($name, array $parameters = array())
{
$this->twig->stream($name, $this->getTemplateParameters($parameters));
}
/**
* Returns template parameters, with merged jsVars, if there are any
* @param array $parameters
* @return array
*/
protected function getTemplateParameters(array $parameters = [])
{
$parameters = array_merge($this->parameters, $parameters);
return $parameters;
}
}
装饰器服务(services.yml):
services:
templating.engine.mytwig:
decorates: templating.engine.twig
class: My\CommonBundle\Component\Templating\MyTwigEngine
# pass the old service as an argument
arguments: [ '@templating.engine.mytwig.inner' ]
# private, because you probably won't be needing to access "mytwig" directly
public: false
tags:
- { name: twig.extension }
基地控制器改动:
namespace My\CommonBundle\Controller;
use My\CommonBundle\Component\Templating\MyTwigEngine;
abstract class Controller extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
/**
* Allows to set javascript variable from action
*
* It also allows to pass arrays and objects - these are later json encoded
*
* @param string $name Variable name
* @param mixed $value - string|int|object|array
*
* @return static
*/
protected function setJsVar($name, $value)
{
/** @var MyTwigEngine $templating */
$templating = $this->getTemplating();
if (!$templating instanceof MyTwigEngine) {
throw new \RuntimeException(sprintf(
'Method %s is implemented only by %s', __METHOD__, MyTwigEngine::class
));
}
$jsvars = $templating->getParameter('jsVars', []);
$jsvars[$name] = $value;
$templating->setParameter('jsVars', $jsvars);
return $this;
}
/**
* Returns templating service
* @return null|object|\Twig\Environment
*/
private function getTemplating()
{
if ($this->container->has('templating')) {
$templating = $this->container->get('templating');
} elseif ($this->container->has('twig')) {
$templating = $this->container->get('twig');
} else {
$templating = null;
}
return $templating;
}
}
我有一个控制器方法,我用它来 "collect" 变量分配给模板。我重写了控制器的 render() 方法来合并 "collected" 和渲染参数并将它们分配给模板。
示例:
class Controller extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
private $jsVars = [];
protected function addJsVar($name, $value)
{
$this->jsVars[$name] = $value;
}
public function render($view, array $parameters = [], Response $response = null)
{
return parent::render($view, array_merge($parameters, ['jsVars' => $this->jsVars], $response);
}
public function indexAction()
{
// collect variables for template
$this->addJsVar('foo', 'bar');
return $this->render('@App/index.html.twig', ['foo2' => 'bar2']);
}
}
我刚刚升级到 Symfony 3.4,它抱怨说自从 Symfony4 以来,我不允许重写 render() 方法,因为它将是最终的。
我怎样才能让它无缝运行,即不定义新方法?
- 我知道 Twig 全局变量,但这些对我没有帮助
- 我可以使用服务来收集变量并将该服务注入 Twig,但这看起来很奇怪
- 是否有我可以收听的事件,例如 TwigPreRender 或 smth?
您可以像这样从 Twig 内部渲染控制器:
{{ render(controller('App\Controller\YourController::yourAction', { 'args': 'hi' })) }}
文档here
好像没有捷径
基本上有2种选择:
- 通过扩展当前 Symfony\Bundle\TwigBundle\TwigEngine 创建您自己的模板引擎
- 修饰当前模板引擎服务
templating.engine.mytwig
我选择了后者
少量解释:
- 我创建了 decorates 当前引擎
templating.engine.twig
的服务templating.engine.mytwig
。 Class 将获取当前的“TwigEngine”作为输入,我会将大部分内容委托给它 - class 也需要通过实施
\Twig_ExtensionInterface
成为 twig extension(或者扩展\Twig_Extension
对我来说就足够了)。服务还需要有标签twig.extension
。否则,您最终会遇到诸如 "Cannot find private service 'assetic' etc" 之类的错误
- setParameter/getParameter用于收集和返回参数
- 然后我向控制器添加了快捷方式 -
setJsVar
- Twig 模板还需要处理这些变量,最好是在布局级别的某个地方。但这不包括在这里
- 你可以使用这个解决方案来收集任意模板参数,例如,如果你想从另一个方法或其他方法分配
- 最好在渲染后清除收集的参数
这一切值得吗?我不知道 :) 无法理解为什么 Symfony 团队首先选择 Controller::render 决赛。但无论如何它是:
TwigEnging class:
namespace My\CommonBundle\Component\Templating\MyTwigEngine;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Bundle\TwigBundle\TwigEngine;
use Symfony\Component\HttpFoundation\Response;
class MyTwigEngine extends \Twig_Extension implements EngineInterface
{
/**
* @var TwigEngine $twig Original Twig Engine object
*/
private $twig;
/**
* @var array $parameters Collected parameters to be passed to template
*/
private $parameters = [];
/**
* MyTwigEngine constructor.
*
* @param TwigEngine $twig
*/
public function __construct(TwigEngine $twig)
{
$this->twig = $twig;
}
/**
* "Collects" parameter to be passed to template.
*
* @param string $key
* @param mixed $value
*
* @return static
*/
public function setParameter($key, $value)
{
$this->parameters[$key] = $value;
return $this;
}
/**
* Returns "collected" parameter
*
* @param string $key
* @return mixed
*/
public function getParameter($key, $default = null)
{
$val = $this->parameters[$key] ?? $default;
return $val;
}
/**
* @param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
* @param array $parameters
*
* @return string
* @throws \Twig\Error\Error
*/
public function render($name, array $parameters = array())
{
return $this->twig->render($name, $this->getTemplateParameters($parameters));
}
/**
* @param string $view
* @param array $parameters
* @param Response|null $response
*
* @return Response
* @throws \Twig\Error\Error
*/
public function renderResponse($view, array $parameters = array(), Response $response = null)
{
return $this->twig->renderResponse($view, $this->getTemplateParameters($parameters), $response);
}
/**
* @param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
*
* @return bool
*/
public function exists($name)
{
return $this->twig->exists($name);
}
/**
* @param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
*
* @return bool
*/
public function supports($name)
{
return $this->twig->supports($name);
}
/**
* @param $name
* @param array $parameters
*
* @throws \Twig\Error\Error
*/
public function stream($name, array $parameters = array())
{
$this->twig->stream($name, $this->getTemplateParameters($parameters));
}
/**
* Returns template parameters, with merged jsVars, if there are any
* @param array $parameters
* @return array
*/
protected function getTemplateParameters(array $parameters = [])
{
$parameters = array_merge($this->parameters, $parameters);
return $parameters;
}
}
装饰器服务(services.yml):
services:
templating.engine.mytwig:
decorates: templating.engine.twig
class: My\CommonBundle\Component\Templating\MyTwigEngine
# pass the old service as an argument
arguments: [ '@templating.engine.mytwig.inner' ]
# private, because you probably won't be needing to access "mytwig" directly
public: false
tags:
- { name: twig.extension }
基地控制器改动:
namespace My\CommonBundle\Controller;
use My\CommonBundle\Component\Templating\MyTwigEngine;
abstract class Controller extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
/**
* Allows to set javascript variable from action
*
* It also allows to pass arrays and objects - these are later json encoded
*
* @param string $name Variable name
* @param mixed $value - string|int|object|array
*
* @return static
*/
protected function setJsVar($name, $value)
{
/** @var MyTwigEngine $templating */
$templating = $this->getTemplating();
if (!$templating instanceof MyTwigEngine) {
throw new \RuntimeException(sprintf(
'Method %s is implemented only by %s', __METHOD__, MyTwigEngine::class
));
}
$jsvars = $templating->getParameter('jsVars', []);
$jsvars[$name] = $value;
$templating->setParameter('jsVars', $jsvars);
return $this;
}
/**
* Returns templating service
* @return null|object|\Twig\Environment
*/
private function getTemplating()
{
if ($this->container->has('templating')) {
$templating = $this->container->get('templating');
} elseif ($this->container->has('twig')) {
$templating = $this->container->get('twig');
} else {
$templating = null;
}
return $templating;
}
}