Skip to content

Instantly share code, notes, and snippets.

@grachov
Last active August 29, 2015 14:02
Show Gist options
  • Save grachov/48a26a82a1348a29bce2 to your computer and use it in GitHub Desktop.
Save grachov/48a26a82a1348a29bce2 to your computer and use it in GitHub Desktop.
Yii2 Twig helper extension

Installation

Customize the view component:

'view' => [
	'class' => 'yii\web\View',
	'renderers' => [
		'twig' => [
			'class' => 'yii\twig\ViewRenderer',
			'globals' => [
				'html' => '\yii\helpers\Html',
			],
			'extensions' => [
				new yii\twig\Extension\YiiHelper([
					'yii\bootstrap',
					'app\assets',
				], [
					\yii\bootstrap\NavBar::className(),
				]),
			],
		],
	],
],

Optimizer

Optimizer removes echo before special functions call and also inject function name as the argument to the viewHelper call.

{{ begin_body() }}
{{ register_app_asset() }}
{{ begin_nav_bar_widget({
'brandLabel': 'My Company',
}) }}
{{ widget_nav({
'options': {
'class': 'navbar-nav navbar-right',
},
'items': [{
'label': 'Home',
'url': '/site/index',
}]
}) }}
{{ end_nav_bar_widget() }}
{{ use('yii/widgets/ActiveForm') }}
{% set form = begin_active_form_widget({
'id': 'login-form',
'options': {
'class': 'form-horizontal',
}
}) %}
{{ form.field(model, 'username') }}
<div class="form-group">
<div class="col-lg-offset-1 col-lg-11">
{{ html.submitButton('Login', {
'class': 'btn btn-primary',
}) }}
</div>
</div>
{{ end_widget() }}
{{ end_body() }}
<?php
namespace yii\twig\NodeVisitor;
class Optimizer implements \Twig_NodeVisitorInterface
{
public function enterNode(\Twig_NodeInterface $node, \Twig_Environment $env)
{
return $node;
}
public function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env)
{
if ($node instanceof \Twig_Node_Print) {
$expression = $node->getNode('expr');
if ($expression instanceof \Twig_Node_Expression_Function) {
$name = $expression->getAttribute('name');
$matched = false;
if (preg_match('/^((register_.+_asset)|use|(begin_.+_widget)|(end(_.+)?_widget))$/', $name)) {
$matched = true;
} else {
if (preg_match('/^(((begin|end)_(page|body))|head)$/', $name)) {
$arguments = [
new \Twig_Node_Expression_Constant($name, $expression->getLine()),
];
if ($expression->hasNode('arguments') && $expression->getNode('arguments') !== null) {
foreach ($expression->getNode('arguments') as $key => $value) {
if (is_int($key)) {
$arguments[] = $value;
} else {
$arguments[$key] = $value;
}
}
}
$expression->setNode('arguments', new \Twig_Node($arguments));
$matched = true;
}
}
if ($matched) {
return new \Twig_Node_Do($expression, $expression->getLine());
}
}
}
return $node;
}
public function getPriority()
{
return 100;
}
}
<?php
namespace yii\twig\Extension;
use yii\base\InvalidCallException;
use yii\helpers\Inflector;
use yii\helpers\StringHelper;
use yii\twig\NodeVisitor\Optimizer;
class YiiHelper extends \Twig_Extension
{
protected $namespaces = [];
protected $aliases = [];
protected $widgets = [];
public function __construct(array $namespaces = [], array $aliases = [])
{
$this->addNamespaces($namespaces);
$this->addAliases($aliases);
}
public function getNodeVisitors()
{
return [
new Optimizer(),
];
}
public function getFunctions()
{
$options = [
'is_safe' => [
'html',
],
];
$functions = [
new \Twig_SimpleFunction('use', [
$this,
'addAliases',
], $options),
new \Twig_SimpleFunction('register_*_asset', [
$this,
'registerAsset',
], array_merge($options, [
'needs_context' => true,
])),
new \Twig_SimpleFunction('begin_*_widget', [
$this,
'beginWidget',
], $options),
new \Twig_SimpleFunction('end_*_widget', [
$this,
'endWidget',
], $options),
new \Twig_SimpleFunction('end_widget', [
$this,
'endWidget',
], $options),
new \Twig_SimpleFunction('widget_*', [
$this,
'widget',
], $options),
];
$options = array_merge($options, [
'needs_context' => true,
]);
foreach (['begin_page', 'end_page', 'begin_body', 'end_body', 'head'] as $helper) {
$functions[] = new \Twig_SimpleFunction($helper, [
$this,
'viewHelper',
], $options);
}
return $functions;
}
public function registerAsset($context, $asset)
{
return $this->resolveAndCall($asset . '_asset', 'register', [
isset($context['this']) ? $context['this'] : null,
]);
}
public function beginWidget($widget, $config = [])
{
$widget = $this->resolveClassName($widget);
$this->widgets[] = $widget;
return $this->call($widget, 'begin', [
$config,
]);
}
public function endWidget($widget = null)
{
if ($widget === null) {
if (empty($this->widgets)) {
throw new InvalidCallException('Unexpected end_widget() call. A matching begin_widget() is not found.');
}
$this->call(array_pop($this->widgets), 'end');
} else {
array_pop($this->widgets);
$this->resolveAndCall($widget, 'end');
}
}
public function widget($widget, $config = [])
{
return $this->resolveAndCall($widget, 'widget', [
$config,
]);
}
public function viewHelper($context, $name = null)
{
if ($name === null || !isset($context['this'])) {
return;
}
$this->call($context['this'], Inflector::variablize($name));
}
public function resolveAndCall($className, $method, $arguments = null)
{
return $this->call($this->resolveClassName($className), $method, $arguments);
}
public function call($className, $method, $arguments = null)
{
$method = [
$className,
$method,
];
if ($arguments === null) {
return call_user_func($method);
} else {
return call_user_func_array($method, $arguments);
}
}
public function resolveClassName($className)
{
$className = Inflector::id2camel($className, '_');
if (isset($this->aliases[$className])) {
return $this->aliases[$className];
}
$found = false;
$resolvedClassName = null;
foreach ($this->namespaces as $namespace) {
$resolvedClassName = $namespace . '\\' . $className;
if (class_exists($resolvedClassName)) {
$found = true;
break;
}
}
if ($found) {
return $this->aliases[$className] = $resolvedClassName;
}
return $className;
}
public function addNamespaces(array $namespaces = [])
{
$this->namespaces = array_merge($this->namespaces, $namespaces);
}
public function addAliases($aliases = [])
{
foreach ((array)$aliases as $alias => $class) {
$class = str_replace('/', '\\', $class);
if (is_int($alias)) {
$alias = StringHelper::basename($class);
}
$this->aliases[$alias] = $class;
}
}
public function getName()
{
return 'yiiHelper';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment