Skip to content

Commit

Permalink
Migrate to Twig ~3.0 (#394)
Browse files Browse the repository at this point in the history
* Migrate to Twig ~3.0

 - Add global variables from View::shared
 - Remove Laravel events

* Fix signature inconsistencies

* Add Laravel Events support
Fix phpcs issues

* correct some formatting issues

* Use the same php requirements as Twig3
  • Loading branch information
filcius committed Jan 6, 2021
1 parent 5d6dc0c commit 9338abd
Show file tree
Hide file tree
Showing 33 changed files with 241 additions and 236 deletions.
1 change: 0 additions & 1 deletion .travis.yml
@@ -1,7 +1,6 @@
language: php

php:
- 7.1
- 7.2
- 7.3
- hhvm
Expand Down
8 changes: 8 additions & 0 deletions UPGRADE.md
@@ -1,5 +1,13 @@
# TwigBridge Upgrade Guide

## Upgrade v0.11.x -> x.x.x
Version greater than v0.11.x now require Twig3. There are some feature that were removed in Twig3, so we were forced to remove some features as well in TwigBridge

- add `'TwigBridge\Extension\Loader\Globals'` in the configuration twigbridge.extensions.enabled. Or else, you will loose global variables `errors`, `app` and all other shared with `View::share`
- add `'TwigBridge\Extension\Loader\Event'` in the configuration twigbridge.extensions.enabled. Or else, `composing:{view name}` and `creating:{view name}` events will no longer be triggered.
- Remove config 'base_template_class' in config/twigbridge.php. It is no longer possible to use a custom template class.
- Make sure you no longer support Twig2 deprecated features: https://twig.symfony.com/doc/2.x/deprecated.html

## Upgrade 0.5.x -> 0.6.x

There have been some big changes since the last 0.5.x release. 0.6.x now only supports PHP 5.4+ and is targeted for Laravel 4.2. It probably still works for L4.0/4.1, but this is not tested/supported.
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Expand Up @@ -14,8 +14,8 @@
}
],
"require": {
"php": ">=7.1",
"twig/twig": "^2.11",
"php": ">=7.2.5",
"twig/twig": "~3.0",
"illuminate/support": "^5.5|^6|^7|^8",
"illuminate/view": "^5.5|^6|^7|^8"
},
Expand Down
6 changes: 2 additions & 4 deletions config/twigbridge.php
Expand Up @@ -44,10 +44,6 @@
// default: utf-8
'charset' => 'utf-8',

// The base template class to use for generated templates.
// default: TwigBridge\Twig\Template
'base_template_class' => 'TwigBridge\Twig\Template',

// An absolute path where to store the compiled templates, or false to disable caching. If null
// then the cache file path is used.
// default: cache file storage path
Expand Down Expand Up @@ -111,9 +107,11 @@
|
*/
'enabled' => [
'TwigBridge\Extension\Laravel\Event',
'TwigBridge\Extension\Loader\Facades',
'TwigBridge\Extension\Loader\Filters',
'TwigBridge\Extension\Loader\Functions',
'TwigBridge\Extension\Loader\Globals',

'TwigBridge\Extension\Laravel\Auth',
'TwigBridge\Extension\Laravel\Config',
Expand Down
64 changes: 5 additions & 59 deletions src/Bridge.php
Expand Up @@ -12,11 +12,11 @@
namespace TwigBridge;

use Illuminate\Contracts\Container\Container;
use Illuminate\View\ViewFinderInterface;
use InvalidArgumentException;
use Twig\Environment;
use Twig\Error\Error;
use Twig\Loader\LoaderInterface;
use Twig\Source;

/**
* Bridge functions between Laravel & Twig
Expand Down Expand Up @@ -70,15 +70,6 @@ public function setApplication(Container $app)
$this->app = $app;
}

public function loadTemplate($name, $index = null)
{
$template = parent::loadTemplate($name, $index);

$template->setName($this->normalizeName($name));

return $template;
}

/**
* Lint (check) the syntax of a file on the view paths.
*
Expand All @@ -88,10 +79,12 @@ public function loadTemplate($name, $index = null)
*/
public function lint($file)
{
/** @var Source $template */
$template = $this->app['twig.loader.viewfinder']->getSourceContext($file);

if (!$template) {
throw new InvalidArgumentException('Unable to find file: '.$file);
$code = trim($template->getCode());
if (empty($code)) {
throw new InvalidArgumentException('Unable to find file: ' . $file);
}

try {
Expand All @@ -102,51 +95,4 @@ public function lint($file)

return true;
}

/**
* Merges a context with the shared variables, same as mergeGlobals()
*
* @param array $context An array representing the context
*
* @return array The context merged with the globals
*/
public function mergeShared(array $context)
{
// we don't use array_merge as the context being generally
// bigger than globals, this code is faster.
foreach ($this->app['view']->getShared() as $key => $value) {
if (!array_key_exists($key, $context)) {
$context[$key] = $value;
}
}

return $context;
}

/**
* Normalize a view name.
*
* @param string $name
*
* @return string
*/
protected function normalizeName($name)
{
$extension = '.' . $this->app['twig.extension'];
$length = strlen($extension);

if (substr($name, -$length, $length) === $extension) {
$name = substr($name, 0, -$length);
}

// Normalize namespace and delimiters
$delimiter = ViewFinderInterface::HINT_PATH_DELIMITER;
if (strpos($name, $delimiter) === false) {
return str_replace('/', '.', $name);
}

list($namespace, $name) = explode($delimiter, $name);

return $namespace.$delimiter.str_replace('/', '.', $name);
}
}
2 changes: 0 additions & 2 deletions src/Command/Clean.php
Expand Up @@ -12,8 +12,6 @@
namespace TwigBridge\Command;

use Illuminate\Console\Command;
use Twig_Environment;
use Illuminate\Filesystem\Filesystem;

/**
* Artisan command to clear the Twig cache.
Expand Down
16 changes: 5 additions & 11 deletions src/Engine/Compiler.php
Expand Up @@ -15,8 +15,7 @@
use Illuminate\View\Compilers\CompilerInterface;
use InvalidArgumentException;
use Twig\Environment;
use Twig\LoaderError;
use TwigBridge\Twig\Template;
use Twig\TemplateWrapper;

/**
* Compiles Twig templates.
Expand Down Expand Up @@ -88,24 +87,19 @@ public function compile($path)
*
* @param string $path
*
* @return TemplateWrapper
* @throws \InvalidArgumentException
*
* @return string \TwigBridge\Twig\Template
*/
public function load($path)
{
// Load template
try {
$template = $this->twig->loadTemplate($path);
} catch (LoaderError $e) {
$tmplWrapper = $this->twig->load($path);
} catch (Exception $e) {
throw new InvalidArgumentException("Error loading $path: ". $e->getMessage(), $e->getCode(), $e);
}

if ($template instanceof Template) {
// Events are already fired by the View Environment
$template->setFiredEvents(true);
}

return $template;
return $tmplWrapper;
}
}
26 changes: 26 additions & 0 deletions src/Extension/Laravel/Event.php
@@ -0,0 +1,26 @@
<?php
/**
* This file is part of the TwigBridge package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace TwigBridge\Extension\Laravel;

use Twig\Extension\AbstractExtension;
use TwigBridge\NodeVisitor\LaravelEventNodeVisitor;

/**
* Send Laravel View Events
*/
class Event extends AbstractExtension
{

public function getNodeVisitors()
{
return [
new LaravelEventNodeVisitor(),
];
}
}
2 changes: 1 addition & 1 deletion src/Extension/Loader/Facades.php
Expand Up @@ -41,7 +41,7 @@ public function getName()
/**
* {@inheritDoc}
*/
public function getGlobals()
public function getGlobals(): array
{
$load = $this->config->get('twigbridge.extensions.facades', []);
$globals = [];
Expand Down
21 changes: 21 additions & 0 deletions src/Extension/Loader/Globals.php
@@ -0,0 +1,21 @@
<?php


namespace TwigBridge\Extension\Loader;

use Twig\Extension\AbstractExtension;
use Twig\Extension\GlobalsInterface;

/**
* Add 'app' and all global variables shared through View::share
*/
class Globals extends AbstractExtension implements GlobalsInterface
{

public function getGlobals(): array
{
$globals = app('view')->getShared();
$globals['app'] = app();
return $globals;
}
}
46 changes: 46 additions & 0 deletions src/Node/EventNode.php
@@ -0,0 +1,46 @@
<?php


namespace TwigBridge\Node;

use Illuminate\Support\Str;
use Illuminate\View\View;
use Twig\Compiler;
use Twig\Node\Node;

class EventNode extends Node
{

public function compile(Compiler $compiler): void
{
$compiler
->write(
sprintf(
'$context = ' .
EventNode::class . '::triggerLaravelEvents($this->getTemplateName(), $context);'
)
)
->raw("\n");
}

public static function triggerLaravelEvents(string $templateName, array &$context): array
{
if (Str::endsWith($templateName, '.twig')) {
$templateName = Str::substr($templateName, 0, mb_strlen($templateName) - 5);
}
/** @var \Illuminate\View\Factory $factory */
$env = resolve('view');
$viewName = $templateName;

$view = new View(
$env,
$env->getEngineResolver()->resolve('twig'),
$viewName,
null,
$context
);
$env->callCreator($view);
$env->callComposer($view);
return $view->getData();
}
}
2 changes: 1 addition & 1 deletion src/Node/GetAttrNode.php
Expand Up @@ -37,7 +37,7 @@ public function __construct(array $nodes = [], array $attributes = [], int $line
/**
* @inheritdoc
*/
public function compile(Compiler $compiler)
public function compile(Compiler $compiler): void
{
$env = $compiler->getEnvironment();

Expand Down
4 changes: 2 additions & 2 deletions src/NodeVisitor/GetAttrAdjuster.php
Expand Up @@ -21,7 +21,7 @@ class GetAttrAdjuster implements NodeVisitorInterface
/**
* @inheritdoc
*/
public function enterNode(Node $node, Environment $env)
public function enterNode(Node $node, Environment $env): Node
{
// Make sure this is a GetAttrExpression (and not a subclass)
if (get_class($node) !== GetAttrExpression::class) {
Expand Down Expand Up @@ -51,7 +51,7 @@ public function enterNode(Node $node, Environment $env)
/**
* @inheritdoc
*/
public function leaveNode(Node $node, Environment $env)
public function leaveNode(Node $node, Environment $env): ?Node
{
return $node;
}
Expand Down
47 changes: 47 additions & 0 deletions src/NodeVisitor/LaravelEventNodeVisitor.php
@@ -0,0 +1,47 @@
<?php
/**
* This file is part of the TwigBridge package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace TwigBridge\NodeVisitor;

use LogicException;
use Twig\Environment;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;
use TwigBridge\Node\EventNode;

class LaravelEventNodeVisitor implements NodeVisitorInterface
{

public function enterNode(Node $node, Environment $env): Node
{
if ($node instanceof ModuleNode) {
try {
$parentNode = $node->getNode('parent');
//https://regex101.com/r/4PQe3r/1
$isEmbedded = !!preg_match('/$\s*{%\s*embed/m', $parentNode->getSourceContext()->getCode());
} catch (LogicException $e) {
$isEmbedded = false;
}
if (!$isEmbedded) {
$node->setNode('display_start', new Node([new EventNode(), $node->getNode('display_start')]));
}
}
return $node;
}

public function leaveNode(Node $node, Environment $env): ?Node
{
return $node;
}

public function getPriority()
{
return 0;
}
}

0 comments on commit 9338abd

Please sign in to comment.