Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #3535: Syntax changes #3978

Merged
merged 2 commits into from
Jun 20, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 60 additions & 4 deletions docs/guide/tutorial-template-engines.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ component's behavior:
//'cachePath' => '@runtime/Twig/cache',
//'options' => [], /* Array of twig options */
'globals' => ['html' => '\yii\helpers\Html'],
'uses' => ['yii\bootstrap'],
],
// ...
],
Expand Down Expand Up @@ -78,15 +79,70 @@ In case you don't need result you shoud use `void` wrapper:

```
{{ void(my_function({'a' : 'b'})) }}
{{ void(myObject.my_function({'a' : 'b'})} }}
{{ void(myObject.my_function({'a' : 'b'})) }}
```

#### Importing namespaces and classes

You can import additional classes and namespaces right in the template:

```
Namespace import:
{{ use('/app/widgets') }}

Class import:
{{ use('/yii/widgets/ActiveForm') }}

Aliased class import:
{{ use({'alias' => '/app/widgets/MyWidget'}) }}
```

#### Widgets

Extension helps using widgets in convenient way converting their syntax to function calls:

```
{{ use('yii/bootstrap') }}
{{ nav_bar_begin({
'brandLabel': 'My Company',
}) }}
{{ nav_widget({
'options': {
'class': 'navbar-nav navbar-right',
},
'items': [{
'label': 'Home',
'url': '/site/index',
}]
}) }}
{{ nav_bar_end() }}
```

In the template above `nav_bar_begin`, `nav_bar_end` or `nav_widget` consists of two parts. First part is widget name
coverted to lowercase and underscores: `NavBar` becomes `nav_bar`, `Nav` becomes `nav`. `_begin`, `_end` and `_widget`
are the same as `::begin()`, `::end()` and `::widget()` calls of a widget.

One could also use more generic `widget_end()` that executes `Widget::end()`.

#### Assets

Assets could be registered the following way:

```
{{ use('yii/web/JqueryAsset') }}
{{ register_jquery_asset() }}
```

In the call above `register` identifies that we're working with assets while `jquery_asset` translates to `JqueryAsset`
class that we've already imported with `use`.

#### Forms

There are two form helper functions `form_begin` and `form_end` to make using forms more convenient:
You can build forms the following way:

```
{% set form = form_begin({
{{ use('yii/widgets/ActiveForm') }}
{% set form = active_form_begin({
'id' : 'login-form',
'options' : {'class' : 'form-horizontal'},
}) %}
Expand All @@ -96,7 +152,7 @@ There are two form helper functions `form_begin` and `form_end` to make using fo
<div class="form-group">
<input type="submit" value="Login" class="btn btn-primary" />
</div>
{{ form_end() }}
{{ active_form_end() }}
```


Expand Down
5 changes: 5 additions & 0 deletions extensions/twig/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ Yii Framework 2 twig extension Change Log
- Bug #3767: Fixed repeated adding of extensions when using config. One may now pass extension instances as well (grachov)
- Bug #3877: Fixed `lexerOptions` throwing exception (dapatrese)
- Enh #1799: Added `form_begin`, `form_end` to twig extension (samdark)
- Chg #3535: Syntax changes:
- Removed `form_begin`, `form_end` (samdark)
- Added `use()` and `ViewRenderer::uses` that are importing classes and namespaces (grachov, samdark)
- Added widget dynamic functions `*_begin`, `*_end`, `*_widget`, `widget_end` (grachov, samdark)
- Added more tests (samdark)
- Chg: Renamed `TwigSimpleFileLoader` into `FileLoader` (samdark)

2.0.0-beta April 13, 2014
Expand Down
198 changes: 198 additions & 0 deletions extensions/twig/Extension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/

namespace yii\twig;

use yii\base\InvalidCallException;
use yii\helpers\Inflector;
use yii\helpers\StringHelper;
use yii\helpers\Url;

/**
* Extension provides Yii-specific syntax for Twig templates.
*
* @author Andrey Grachov <[email protected]>
* @author Alexander Makarov <[email protected]>
*/
class Extension extends \Twig_Extension
{
/**
* @var array used namespaces
*/
protected $namespaces = [];

/**
* @var array used class aliases
*/
protected $aliases = [];

/**
* @var array used widgets
*/
protected $widgets = [];

/**
* Creates new instance
*
* @param array $uses namespaces and classes to use in the template
*/
public function __construct(array $uses = [])
{
$this->addUses($uses);
}

/**
* @inheritdoc
*/
public function getNodeVisitors()
{
return [
new Optimizer(),
];
}

/**
* @inheritdoc
*/
public function getFunctions()
{
$options = [
'is_safe' => ['html'],
];
$functions = [
new \Twig_SimpleFunction('use', [$this, 'addUses'], $options),
new \Twig_SimpleFunction('*_begin', [$this, 'beginWidget'], $options),
new \Twig_SimpleFunction('*_end', [$this, 'endWidget'], $options),
new \Twig_SimpleFunction('widget_end', [$this, 'endWidget'], $options),
new \Twig_SimpleFunction('*_widget', [$this, 'widget'], $options),
new \Twig_SimpleFunction('path', [$this, 'path']),
new \Twig_SimpleFunction('url', [$this, 'url']),
new \Twig_SimpleFunction('void', function(){}),
];

$options = array_merge($options, [
'needs_context' => true,
]);
$functions[] = new \Twig_SimpleFunction('register_*', [$this, 'registerAsset'], $options);
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, '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'])) {
$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)
{
$callable = [$className, $method];
if ($arguments === null) {
return call_user_func($callable);
} else {
return call_user_func_array($callable, $arguments);
}
}

public function resolveClassName($className)
{
$className = Inflector::id2camel($className, '_');
if (isset($this->aliases[$className])) {
return $this->aliases[$className];
}
$resolvedClassName = null;
foreach ($this->namespaces as $namespace) {
$resolvedClassName = $namespace . '\\' . $className;
if (class_exists($resolvedClassName)) {
return $this->aliases[$className] = $resolvedClassName;
}
}
return $className;
}

public function addUses($args)
{
foreach ((array)$args as $key => $value) {
$value = str_replace('/', '\\', $value);
if (is_int($key)) {
// namespace or class import
if (class_exists($value)) {
// class import
$this->aliases[StringHelper::basename($value)] = $value;
} else {
// namespace
$this->namespaces[] = $value;
}
} else {
// aliased class import
$this->aliases[$key] = $value;
}
}
}

public function path($path, $args = [])
{
return Url::to(array_merge([$path], $args));
}

public function url($path, $args = [])
{
return Url::to(array_merge([$path], $args), true);
}

/**
* @inheritdoc
*/
public function getName()
{
return 'yii2-twig';
}
}
66 changes: 66 additions & 0 deletions extensions/twig/Optimizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/

namespace yii\twig;

/**
* Optimizer removes echo before special functions call and injects function name as an argument for the view helper
* calls.
*
* @author Andrey Grachov <[email protected]>
* @author Alexander Makarov <[email protected]>
*/
class Optimizer implements \Twig_NodeVisitorInterface
{
/**
* @inheritdoc
*/
public function enterNode(\Twig_NodeInterface $node, \Twig_Environment $env)
{
return $node;
}

/**
* @inheritdoc
*/
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');
if (preg_match('/^(?:register_.+_asset|use|.+_begin|.+_end)$/', $name)) {
return new \Twig_Node_Do($expression, $expression->getLine());
} elseif (in_array($name, ['begin_page', 'end_page', 'begin_body', 'end_body', 'head'])) {
$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));
return new \Twig_Node_Do($expression, $expression->getLine());
}
}
}
return $node;
}

/**
* @inheritdoc
*/
public function getPriority()
{
return 100;
}
}
Loading