From 696044ceb917f697fab6bc148f70cf0cdb46ee52 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 15 Sep 2016 10:48:26 +0200 Subject: [PATCH 01/29] Transfer Package to Namics --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index b8d9a7c..d5ed2ee 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,9 @@ { - "name": "deniaz/terrific-twig", - "description": "Adds a component tag to Twig, analogue to the component handlebars helper in Splendid.", + "name": "namics/terrific-twig", + "description": "Extension to embrace the Terrific frontend methodology in Twig. Currently it adds a custom component tag to Twig which mimics Nitro's handlebars helper.", "type": "library", - "keywords": ["terrific", "splendid", "twig"], - "homepage": "https://github.com/deniaz/terrific-twig", + "keywords": ["terrific", "nitro", "twig", "integration", "namics"], + "homepage": "https://github.com/namics/terrific-twig", "license": "MIT", "authors": [ { From 75c83e4ae2c3a367a0e0a8e73ccf8b3045b10f50 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 15 Sep 2016 10:52:52 +0200 Subject: [PATCH 02/29] Update all Repository Links/References --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 634bb19..737da5f 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ # Terrific Twig -[![Build Status](https://travis-ci.org/deniaz/terrific-twig.svg?branch=master)](https://travis-ci.org/deniaz/terrific-twig) -[![Latest Stable Version](https://poser.pugx.org/deniaz/terrific-twig/v/stable.svg)](https://packagist.org/packages/deniaz/terrific-twig) -[![Total Downloads](https://poser.pugx.org/deniaz/terrific-twig/downloads.svg)](https://packagist.org/packages/deniaz/terrific-twig) -[![License](https://poser.pugx.org/deniaz/terrific-twig/license.svg)](https://packagist.org/packages/deniaz/terrific-twig) +[![Build Status](https://travis-ci.org/namics/terrific-twig.svg?branch=master)](https://travis-ci.org/namics/terrific-twig) +[![Latest Stable Version](https://poser.pugx.org/namics/terrific-twig/v/stable.svg)](https://packagist.org/packages/namics/terrific-twig) +[![Total Downloads](https://poser.pugx.org/namics/terrific-twig/downloads.svg)](https://packagist.org/packages/namics/terrific-twig) +[![License](https://poser.pugx.org/namics/terrific-twig/license.svg)](https://packagist.org/packages/namics/terrific-twig) Extension to embrace the [Terrific](https://github.com/brunschgi/terrificjs) frontend methodology in [Twig](http://twig.sensiolabs.org/). Currently it adds a custom `component` tag to Twig which mimics [Nitro](https://github.com/namics/generator-nitro)'s handlebars helper. ## Installation -Using [composer](https://packagist.org/packages/deniaz/terrific-twig): +Using [composer](https://packagist.org/packages/namics/terrific-twig): ```bash -$ composer require deniaz/terrific-twig +$ composer require namics/terrific-twig ``` ## Requirements @@ -101,7 +101,7 @@ The token parser contains the parsing step for the component tag. It tokenizes t The functionality is based on the fantastic `Twig_TokenParser_Include`. ### Node -The Node compiles the tokenized tag to PHP. To see some of the output, check the [`ComponentTest`](https://github.com/deniaz/terrific-twig/blob/master/test/Twig/Node/ComponentTest.php). +The Node compiles the tokenized tag to PHP. To see some of the output, check the [`ComponentTest`](https://github.com/namics/terrific-twig/blob/master/test/Twig/Node/ComponentTest.php). ### Loader The `TerrificLoader` extends the `Twig_Loader_Filesystem` as it actually loads templates from the filesystem. An implementation of `TemplateLocatorInterface` provides the paths where the loader should search for templates. From 45277432759c745d9715ce7accc77c65c6d3fdb2 Mon Sep 17 00:00:00 2001 From: Hendrik Grahl Date: Mon, 10 Oct 2016 13:17:54 +0200 Subject: [PATCH 03/29] Project rename to unify all Terrific/Nitro names --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index d5ed2ee..7545fc5 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,9 @@ { - "name": "namics/terrific-twig", + "name": "namics/twig-nitro-library", "description": "Extension to embrace the Terrific frontend methodology in Twig. Currently it adds a custom component tag to Twig which mimics Nitro's handlebars helper.", "type": "library", "keywords": ["terrific", "nitro", "twig", "integration", "namics"], - "homepage": "https://github.com/namics/terrific-twig", + "homepage": "https://github.com/namics/twig-nitro-library", "license": "MIT", "authors": [ { From f2a747839b1b203d9664a9251029d06a5e17fed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Thu, 3 Oct 2019 14:48:13 +0200 Subject: [PATCH 04/29] Make it possible to use strings or variable as component name --- src/Twig/Node/ComponentNode.php | 35 +++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index 561b813..f93f34b 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -12,6 +12,9 @@ namespace Deniaz\Terrific\Twig\Node; use Deniaz\Terrific\Provider\ContextProviderInterface; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\NameExpression; use \Twig_Compiler; use \Twig_Node; use \Twig_NodeOutputInterface; @@ -66,9 +69,12 @@ public function compile(Twig_Compiler $compiler) { $compiler->addDebugInfo($this); + // Create data. $this->createTerrificContext($compiler); + // Load component template. $this->addGetTemplate($compiler); + $compiler ->raw('->display($tContext);') ->raw("\n\n"); @@ -96,17 +102,30 @@ protected function createTerrificContext(Twig_Compiler $compiler) /** * Adds the first expression (Component Identifier) and compiles the template loading logic. + * + * IMPORTANT: Has to be executed after the Terrific context was created (ComponentNode::createTerrificContext). + * * @param Twig_Compiler $compiler + * The Twig compiler. */ protected function addGetTemplate(Twig_Compiler $compiler) { - $compiler - ->write('$this->loadTemplate(') - ->subcompile($this->getNode('component')) - ->raw(', ') - ->repr($compiler->getFilename()) - ->raw(', ') - ->repr($this->getLine()) - ->raw(')'); + $compiler->write('$this->loadTemplate('); + + // If a variable is used for component name, use it's value (is inside Terrifc context "$tContext") for template name. + if ($this->getNode('component') instanceof NameExpression) { + $compiler->raw('$tContext["' . $this->getNode('component')->getAttribute('name') . '"]'); + } + // If a static string (constant) is used for component name, compile it (prints it). + elseif ($this->getNode('component') instanceof ConstantExpression) { + $compiler->subcompile($this->getNode('component')); + } + + $compiler + ->raw(', ') + ->repr($compiler->getFilename()) + ->raw(', ') + ->repr($this->getLine()) + ->raw(')'); } } From 648a2f1243a6b6cc39b4b63b9d7bccddc051e343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Thu, 3 Oct 2019 14:57:39 +0200 Subject: [PATCH 05/29] Formatting, add editorconfig --- .editorconfig | 17 ++ src/Config/ConfigReader.php | 91 +++--- src/Provider/ContextProviderInterface.php | 44 ++- .../TemplateInformationProviderInterface.php | 39 ++- src/Twig/Extension/TerrificExtension.php | 77 +++-- src/Twig/Loader/TerrificLoader.php | 95 +++--- src/Twig/Node/ComponentNode.php | 222 +++++++------- src/Twig/TokenParser/ComponentTokenParser.php | 132 ++++----- test/Twig/Extension/TerrificExtensionTest.php | 46 +-- test/Twig/Loader/TerrificLoaderTest.php | 106 +++---- test/Twig/Node/ComponentTestNode.php | 270 +++++++++--------- .../TokenParser/ComponentTokenParserTest.php | 36 +-- 12 files changed, 585 insertions(+), 590 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..686c443 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# Drupal editor configuration normalization +# @see http://editorconfig.org/ + +# This is the top-most .editorconfig file; do not search in parent directories. +root = true + +# All files. +[*] +end_of_line = LF +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[composer.{json,lock}] +indent_size = 4 diff --git a/src/Config/ConfigReader.php b/src/Config/ConfigReader.php index eb06657..cd59519 100644 --- a/src/Config/ConfigReader.php +++ b/src/Config/ConfigReader.php @@ -1,65 +1,60 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Deniaz\Terrific\Config; -use \DomainException; +use DomainException; /** * ConfigReader reads Terrific Nitro's config.json and parses essential information such as component paths and the view * file extension. * - * Class ConfigReader + * Class ConfigReader. + * * @package Deniaz\Terrific\Config */ -final class ConfigReader -{ - /** +final class ConfigReader { + /** * @const string FILE_NAME The Configuration's Filename. */ - const FILE_NAME = 'config.json'; - - /** - * @var array $config Loaded Config Array. - */ - private $config = null; - - /** - * ConfigReader constructor. - * @param string $terrificDir Path to Terrific's Frontend Directory. - * @throws DomainException Thrown if Configuration could not be loaded correctly. - */ - public function __construct($terrificDir) - { - $path = $terrificDir . '/' . self::FILE_NAME; - - if (is_readable($path)) { - try { - $this->config = json_decode(file_get_contents($path), true); - - if ($this->config === null && json_last_error() !== JSON_ERROR_NONE) { - throw new DomainException('Terrific Config could not be parsed.'); - } - } catch (DomainException $e) { - throw $e; - } + const FILE_NAME = 'config.json'; + + /** + * @var array + */ + private $config = NULL; + + /** + * ConfigReader constructor. + * + * @param string $terrificDir + * Path to Terrific's Frontend Directory. + * + * @throws DomainException Thrown if Configuration could not be loaded correctly. + */ + public function __construct($terrificDir) { + $path = $terrificDir . '/' . self::FILE_NAME; + + if (is_readable($path)) { + try { + $this->config = json_decode(file_get_contents($path), TRUE); + + if ($this->config === NULL && json_last_error() !== JSON_ERROR_NONE) { + throw new DomainException('Terrific Config could not be parsed.'); } + } + catch (DomainException $e) { + throw $e; + } } + } + + /** + * Returns the Configuration. + * + * @return array + */ + public function read() { + return $this->config; + } - /** - * Returns the Configuration. - * @return array - */ - public function read() - { - return $this->config; - } } diff --git a/src/Provider/ContextProviderInterface.php b/src/Provider/ContextProviderInterface.php index e7dc6c9..b00a8b3 100644 --- a/src/Provider/ContextProviderInterface.php +++ b/src/Provider/ContextProviderInterface.php @@ -1,35 +1,29 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Deniaz\Terrific\Provider; -use \Twig_Compiler; -use \Twig_Node; +use Twig_Compiler; +use Twig_Node; /** * Interface to describe a Context Provider. * - * Interface ContextProviderInterface + * Interface ContextProviderInterface. + * * @package Deniaz\Terrific\Provider */ -interface ContextProviderInterface -{ - /** - * Compiles the $tContext variable which is passed to the Twig Template. - * - * @param Twig_Compiler $compiler - * @param Twig_Node $component - * @param Twig_Node $dataVariant - * @param $only - * @return mixed - */ - public function compile(Twig_Compiler $compiler, Twig_Node $component, Twig_Node $dataVariant, $only); -} \ No newline at end of file +interface ContextProviderInterface { + + /** + * Compiles the $tContext variable which is passed to the Twig Template. + * + * @param \Twig_Compiler $compiler + * @param \Twig_Node $component + * @param \Twig_Node $dataVariant + * @param $only + * + * @return mixed + */ + public function compile(Twig_Compiler $compiler, Twig_Node $component, Twig_Node $dataVariant, $only); + +} diff --git a/src/Provider/TemplateInformationProviderInterface.php b/src/Provider/TemplateInformationProviderInterface.php index d255d3e..ca96b4d 100644 --- a/src/Provider/TemplateInformationProviderInterface.php +++ b/src/Provider/TemplateInformationProviderInterface.php @@ -1,34 +1,29 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Deniaz\Terrific\Provider; /** * Interface to describe Template Information. * - * Interface TemplateLocatorInterface + * Interface TemplateLocatorInterface. + * * @package Deniaz\Terrific * @see Deniaz\Terrific\Twig\LoaderTerrificLoader */ -interface TemplateInformationProviderInterface -{ - /** - * Returns a list of paths where templates might be stored. - * @return array - */ - public function getPaths(); +interface TemplateInformationProviderInterface { + + /** + * Returns a list of paths where templates might be stored. + * + * @return array + */ + public function getPaths(); + + /** + * Returns the template's file extension. + * + * @return string + */ + public function getFileExtension(); - /** - * Returns the template's file extension. - * @return string - */ - public function getFileExtension(); } diff --git a/src/Twig/Extension/TerrificExtension.php b/src/Twig/Extension/TerrificExtension.php index f1bec2b..1b21033 100644 --- a/src/Twig/Extension/TerrificExtension.php +++ b/src/Twig/Extension/TerrificExtension.php @@ -1,59 +1,50 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Deniaz\Terrific\Twig\Extension; use Deniaz\Terrific\Provider\ContextProviderInterface; use Deniaz\Terrific\Twig\TokenParser\ComponentTokenParser; -use \Twig_Extension; +use Twig_Extension; /** * TerrificExtension adds Terrific Features to the Twig Environment. Currently only the ComponentTokenParser is added, * which results in the additional component tag. * - * Class TerrificExtension + * Class TerrificExtension. + * * @package Deniaz\Terrific\Twig\Extension */ -final class TerrificExtension extends Twig_Extension -{ - /** - * @var ContextProviderInterface Context Variable Provider. - */ - private $ctxProvider; +final class TerrificExtension extends Twig_Extension { + /** + * @var \Deniaz\Terrific\Provider\ContextProviderInterfaceContextVariableProvider + */ + private $ctxProvider; + + /** + * TerrificExtension constructor. + * + * @param \Deniaz\Terrific\Provider\ContextProviderInterface $ctxProvider + * + * @TODO: Default Provider? + */ + public function __construct(ContextProviderInterface $ctxProvider) { + $this->ctxProvider = $ctxProvider; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'terrific'; + } - /** - * TerrificExtension constructor. - * @param ContextProviderInterface $ctxProvider - * @TODO: Default Provider? - */ - public function __construct(ContextProviderInterface $ctxProvider) - { - $this->ctxProvider = $ctxProvider; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'terrific'; - } + /** + * {@inheritdoc} + */ + public function getTokenParsers() { + return [ + new ComponentTokenParser($this->ctxProvider), + ]; + } - /** - * {@inheritdoc} - */ - public function getTokenParsers() - { - return [ - new ComponentTokenParser($this->ctxProvider), - ]; - } } diff --git a/src/Twig/Loader/TerrificLoader.php b/src/Twig/Loader/TerrificLoader.php index 5337499..e687344 100644 --- a/src/Twig/Loader/TerrificLoader.php +++ b/src/Twig/Loader/TerrificLoader.php @@ -1,74 +1,65 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Deniaz\Terrific\Twig\Loader; use Deniaz\Terrific\Provider\TemplateInformationProviderInterface; -use \Twig_Error_Loader; -use \Twig_Loader_Filesystem; +use Twig_Error_Loader; +use Twig_Loader_Filesystem; /** * TerrificLoader searches for templates on the filesystem within a terrific structure. Since the templates are stored * on the filesystem nonetheless, TerrificLoader extends Twig's Twig_Loader_Filesystem. * - * Class TerrificLoader + * Class TerrificLoader. + * * @package Deniaz\Terrific\Twig\Loader */ -final class TerrificLoader extends Twig_Loader_Filesystem -{ - /** - * @var string $fileExtension Template File Extension. - */ - private $fileExtension = 'html.twig'; +final class TerrificLoader extends Twig_Loader_Filesystem { + /** + * @var string + */ + private $fileExtension = 'html.twig'; - /** - * TerrificLoader constructor. - * @param TemplateInformationProviderInterface $locator - */ - public function __construct(TemplateInformationProviderInterface $locator) - { - parent::__construct($locator->getPaths()); - $this->fileExtension = $locator->getFileExtension(); - } + /** + * TerrificLoader constructor. + * + * @param \Deniaz\Terrific\Provider\TemplateInformationProviderInterface $locator + */ + public function __construct(TemplateInformationProviderInterface $locator) { + parent::__construct($locator->getPaths()); + $this->fileExtension = $locator->getFileExtension(); + } + + /** + * {@inheritdoc} + */ + protected function findTemplate($name) { + $name = $this->normalizeName($name); - /** - * {@inheritdoc} - */ - protected function findTemplate($name) - { - $name = $this->normalizeName($name); + if (isset($this->cache[$name])) { + return $this->cache[$name]; + } - if (isset($this->cache[$name])) { - return $this->cache[$name]; - } + if (isset($this->errorCache[$name])) { + throw new Twig_Error_Loader($this->errorCache[$name]); + } - if (isset($this->errorCache[$name])) { - throw new Twig_Error_Loader($this->errorCache[$name]); - } + $this->validateName($name); + $namespace = parent::MAIN_NAMESPACE; - $this->validateName($name); - $namespace = parent::MAIN_NAMESPACE; + $terrificPath = $name . '/' . strtolower($name) . '.' . $this->fileExtension; - $terrificPath = $name . '/' . strtolower($name) . '.' . $this->fileExtension; + foreach ($this->paths[$namespace] as $path) { + $fullPath = $path . '/' . $terrificPath; + $realPath = realpath($fullPath); + if (is_readable($fullPath) && $realPath !== FALSE) { + return $this->cache[$name] = $realPath; + } + } - foreach ($this->paths[$namespace] as $path) { - $fullPath = $path . '/' . $terrificPath; - $realPath = realpath($fullPath); - if (is_readable($fullPath) && $realPath !== false) { - return $this->cache[$name] = $realPath; - } - } + $this->errorCache[$name] = sprintf('Unable to find component "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); - $this->errorCache[$name] = sprintf('Unable to find component "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); + throw new Twig_Error_Loader($this->errorCache[$name]); + } - throw new Twig_Error_Loader($this->errorCache[$name]); - } } diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index f93f34b..78aeb1c 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -1,131 +1,133 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Deniaz\Terrific\Twig\Node; use Deniaz\Terrific\Provider\ContextProviderInterface; -use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\NameExpression; -use \Twig_Compiler; -use \Twig_Node; -use \Twig_NodeOutputInterface; -use \Twig_Node_Expression; -use \Twig_Node_Expression_Array; -use \Twig_Node_Expression_Constant; +use Twig_Compiler; +use Twig_Node; +use Twig_Node_Expression; +use Twig_NodeOutputInterface; /** * ComponentNode represents a component node. * - * Class ComponentNode + * Class ComponentNode. + * * @package Deniaz\Terrific\Twig\Node */ -final class ComponentNode extends Twig_Node implements Twig_NodeOutputInterface -{ - /** - * @var ContextProviderInterface Context Variable Provider - */ - private $ctxProvider; - - /** - * ComponentNode constructor. - * @param Twig_Node_Expression $component Expression representing the Component's Identifier. - * @param ContextProviderInterface $ctxProvider Context Provider. - * @param Twig_Node_Expression|null $data Expression representing the additional data. - * @param bool $only Whether a new Child-Context should be created. - * @param int $lineno Line Number. - * @param string $tag Tag name associated with the node. - */ - public function __construct( +final class ComponentNode extends Twig_Node implements Twig_NodeOutputInterface { + /** + * The context provider. + * + * @var \Deniaz\Terrific\Provider\ContextProviderInterfaceContextVariableProvider + */ + private $ctxProvider; + + /** + * ComponentNode constructor. + * + * @param \Twig_Node_Expression $component + * Expression representing the Component's Identifier. + * @param \Deniaz\Terrific\Provider\ContextProviderInterface $ctxProvider + * Context Provider. + * @param \Twig_Node_Expression|null $data + * Expression representing the additional data. + * @param bool $only + * Whether a new Child-Context should be created. + * @param int $lineno + * Line Number. + * @param string $tag + * Tag name associated with the node. + */ + public function __construct( Twig_Node_Expression $component, ContextProviderInterface $ctxProvider, - Twig_Node_Expression $data = null, - $only = false, + Twig_Node_Expression $data = NULL, + $only = FALSE, $lineno, - $tag = null) - { - parent::__construct( - ['component' => $component, 'data' => $data], - ['only' => (bool)$only], - $lineno, - $tag - ); - - $this->ctxProvider = $ctxProvider; + $tag = NULL) { + parent::__construct( + ['component' => $component, 'data' => $data], + ['only' => (bool) $only], + $lineno, + $tag + ); + + $this->ctxProvider = $ctxProvider; + } + + /** + * Compiles the component. + * + * @param \Twig_Compiler $compiler + * The Twig compiler. + */ + public function compile(Twig_Compiler $compiler) { + $compiler->addDebugInfo($this); + + // Create data. + $this->createTerrificContext($compiler); + + // Load component template. + $this->addGetTemplate($compiler); + + $compiler + ->raw('->display($tContext);') + ->raw("\n\n"); + + $compiler->addDebugInfo($this->getNode('component')); + } + + /** + * Makes the data for the component available to it. + * + * @param \Twig_Compiler $compiler + * The Twig compiler. + */ + protected function createTerrificContext(Twig_Compiler $compiler) { + $compiler + ->addIndentation() + ->raw('$tContext = $context;') + ->raw("\n"); + + $this->ctxProvider->compile( + $compiler, + $this->getNode('component'), + $this->getNode('data'), + $this->getAttribute('only') + ); + } + + /** + * Adds the first expression (Component Identifier). + * + * And compiles the template loading logic. + * IMPORTANT: Has to be executed after the Terrific context was created + * (ComponentNode::createTerrificContext). + * + * @param \Twig_Compiler $compiler + * The Twig compiler. + */ + protected function addGetTemplate(Twig_Compiler $compiler) { + $compiler->write('$this->loadTemplate('); + + /* If a variable is used for component name, use it's value (is inside Terrifc context "$tContext") for template name. */ + if ($this->getNode('component') instanceof NameExpression) { + $compiler->raw('$tContext["' . $this->getNode('component')->getAttribute('name') . '"]'); } - - /** - * @param Twig_Compiler $compiler - */ - public function compile(Twig_Compiler $compiler) - { - $compiler->addDebugInfo($this); - - // Create data. - $this->createTerrificContext($compiler); - - // Load component template. - $this->addGetTemplate($compiler); - - $compiler - ->raw('->display($tContext);') - ->raw("\n\n"); - - $compiler->addDebugInfo($this->getNode('component')); + /* If a static string (constant) is used for component name, compile it (prints it). */ + elseif ($this->getNode('component') instanceof ConstantExpression) { + $compiler->subcompile($this->getNode('component')); } - /** - * @param Twig_Compiler $compiler - */ - protected function createTerrificContext(Twig_Compiler $compiler) - { - $compiler - ->addIndentation() - ->raw('$tContext = $context;') - ->raw("\n"); - - $this->ctxProvider->compile( - $compiler, - $this->getNode('component'), - $this->getNode('data'), - $this->getAttribute('only') - ); - } + $compiler + ->raw(', ') + ->repr($compiler->getFilename()) + ->raw(', ') + ->repr($this->getLine()) + ->raw(')'); + } - /** - * Adds the first expression (Component Identifier) and compiles the template loading logic. - * - * IMPORTANT: Has to be executed after the Terrific context was created (ComponentNode::createTerrificContext). - * - * @param Twig_Compiler $compiler - * The Twig compiler. - */ - protected function addGetTemplate(Twig_Compiler $compiler) - { - $compiler->write('$this->loadTemplate('); - - // If a variable is used for component name, use it's value (is inside Terrifc context "$tContext") for template name. - if ($this->getNode('component') instanceof NameExpression) { - $compiler->raw('$tContext["' . $this->getNode('component')->getAttribute('name') . '"]'); - } - // If a static string (constant) is used for component name, compile it (prints it). - elseif ($this->getNode('component') instanceof ConstantExpression) { - $compiler->subcompile($this->getNode('component')); - } - - $compiler - ->raw(', ') - ->repr($compiler->getFilename()) - ->raw(', ') - ->repr($this->getLine()) - ->raw(')'); - } } diff --git a/src/Twig/TokenParser/ComponentTokenParser.php b/src/Twig/TokenParser/ComponentTokenParser.php index c008371..8586433 100644 --- a/src/Twig/TokenParser/ComponentTokenParser.php +++ b/src/Twig/TokenParser/ComponentTokenParser.php @@ -1,14 +1,5 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Deniaz\Terrific\Twig\TokenParser; use Deniaz\Terrific\Provider\ContextProviderInterface; @@ -19,78 +10,77 @@ /** * Includes a Terrific Component. * - * Class ComponentTokenParser + * Class ComponentTokenParser. + * * @package Deniaz\Terrific\Twig\TokenParser */ -final class ComponentTokenParser extends Twig_TokenParser -{ - /** - * @var ContextProviderInterface Context Variable Provider. - */ - private $ctxProvider; - - /** - * ComponentTokenParser constructor. - * @param ContextProviderInterface $ctxProvider - */ - public function __construct(ContextProviderInterface $ctxProvider) - { - $this->ctxProvider = $ctxProvider; +final class ComponentTokenParser extends Twig_TokenParser { + /** + * @var \Deniaz\Terrific\Provider\ContextProviderInterfaceContextVariableProvider + */ + private $ctxProvider; + + /** + * ComponentTokenParser constructor. + * + * @param \Deniaz\Terrific\Provider\ContextProviderInterface $ctxProvider + */ + public function __construct(ContextProviderInterface $ctxProvider) { + $this->ctxProvider = $ctxProvider; + } + + /** + * {@inheritdoc} + */ + public function parse(Twig_Token $token) { + $component = $this->parser->getExpressionParser()->parseExpression(); + list($data, $only) = $this->parseArguments(); + + return new ComponentNode($component, $this->ctxProvider, $data, $only, $token->getLine(), $this->getTag()); + } + + /** + * Tokenizes the component stream. + * + * @return array + */ + protected function parseArguments() { + $stream = $this->parser->getStream(); + + $data = NULL; + $only = FALSE; + + if ($stream->test(Twig_Token::BLOCK_END_TYPE)) { + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return [$data, $only]; } - /** - * {@inheritdoc} - */ - public function parse(Twig_Token $token) - { - $component = $this->parser->getExpressionParser()->parseExpression(); - list($data, $only) = $this->parseArguments(); + if ($stream->test(Twig_Token::NAME_TYPE, 'only')) { + $only = TRUE; + $stream->next(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); - return new ComponentNode($component, $this->ctxProvider, $data, $only, $token->getLine(), $this->getTag()); + return [$data, $only]; } - /** - * Tokenizes the component stream. - * @return array - */ - protected function parseArguments() - { - $stream = $this->parser->getStream(); - - $data = null; - $only = false; + $data = $this->parser->getExpressionParser()->parseExpression(); - if ($stream->test(Twig_Token::BLOCK_END_TYPE)) { - $stream->expect(Twig_Token::BLOCK_END_TYPE); - - return [$data, $only]; - } - - if ($stream->test(Twig_Token::NAME_TYPE, 'only')) { - $only = true; - $stream->next(); - $stream->expect(Twig_Token::BLOCK_END_TYPE); - - return [$data, $only]; - } - - $data = $this->parser->getExpressionParser()->parseExpression(); + if ($stream->test(Twig_Token::NAME_TYPE, 'only')) { + $only = TRUE; + $stream->next(); + } - if ($stream->test(Twig_Token::NAME_TYPE, 'only')) { - $only = true; - $stream->next(); - } + $stream->expect(Twig_Token::BLOCK_END_TYPE); - $stream->expect(Twig_Token::BLOCK_END_TYPE); + return [$data, $only]; + } - return [$data, $only]; - } + /** + * {@inheritdoc} + */ + public function getTag() { + return 'component'; + } - /** - * {@inheritdoc} - */ - public function getTag() - { - return 'component'; - } } diff --git a/test/Twig/Extension/TerrificExtensionTest.php b/test/Twig/Extension/TerrificExtensionTest.php index b4f41be..825fa7b 100644 --- a/test/Twig/Extension/TerrificExtensionTest.php +++ b/test/Twig/Extension/TerrificExtensionTest.php @@ -6,23 +6,31 @@ use Deniaz\Terrific\Twig\Extension\TerrificExtension; use Deniaz\Terrific\Twig\TokenParser\ComponentTokenParser; -class TerrificExtensionTest extends \PHPUnit_Framework_TestCase -{ - public function testReturnsExtensionName() - { - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - $ext = new TerrificExtension($stub); - $name = $ext->getName(); - - $this->assertEquals('terrific', $name); - } - - public function testReturnsComponentTokenParser() - { - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - $ext = new TerrificExtension($stub); - $parsers = $ext->getTokenParsers(); - - $this->assertContainsOnly(ComponentTokenParser::class, $parsers); - } +/** + * + */ +class TerrificExtensionTest extends \PHPUnit_Framework_TestCase { + + /** + * + */ + public function testReturnsExtensionName() { + $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + $ext = new TerrificExtension($stub); + $name = $ext->getName(); + + $this->assertEquals('terrific', $name); + } + + /** + * + */ + public function testReturnsComponentTokenParser() { + $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + $ext = new TerrificExtension($stub); + $parsers = $ext->getTokenParsers(); + + $this->assertContainsOnly(ComponentTokenParser::class, $parsers); + } + } diff --git a/test/Twig/Loader/TerrificLoaderTest.php b/test/Twig/Loader/TerrificLoaderTest.php index 4307943..39391c1 100644 --- a/test/Twig/Loader/TerrificLoaderTest.php +++ b/test/Twig/Loader/TerrificLoaderTest.php @@ -6,54 +6,62 @@ use Deniaz\Terrific\Twig\Loader\TerrificLoader; use Exception; -class TerrificLoaderTest extends \PHPUnit_Framework_TestCase -{ - public function testPaths() - { - $paths = [ - __DIR__ . '/Fixtures/atoms', - __DIR__ . '/Fixtures/molecules', - ]; - - $stub = $this - ->getMockBuilder(TemplateInformationProviderInterface::class) - ->getMock(); - - $stub - ->method('getPaths') - ->willReturn($paths); - - $stub - ->method('getFileExtension') - ->willReturn('html'); - - $loader = new TerrificLoader($stub); - - $this->assertEquals($paths, $loader->getPaths()); - - $source = $loader->getSource('molecule'); - $this->assertEquals("--molecule_content--\n", $source); - - $source = $loader->getSource('atom'); - $this->assertEquals("--atom_content--\n", $source); - - // Check if the cache is invoked - $source = $loader->getSource('atom'); - $this->assertEquals("--atom_content--\n", $source); - - try { - $loader->getSource('fake-component'); - } catch (Exception $e) { - $this->assertInstanceOf('Twig_Error_Loader', $e); - $this->assertContains('Unable to find component "fake-component"', $e->getMessage()); - } - - // Check if the cache is invoked - try { - $loader->getSource('fake-component'); - } catch (Exception $e) { - $this->assertInstanceOf('Twig_Error_Loader', $e); - $this->assertContains('Unable to find component "fake-component"', $e->getMessage()); - } +/** + * + */ +class TerrificLoaderTest extends \PHPUnit_Framework_TestCase { + + /** + * + */ + public function testPaths() { + $paths = [ + __DIR__ . '/Fixtures/atoms', + __DIR__ . '/Fixtures/molecules', + ]; + + $stub = $this + ->getMockBuilder(TemplateInformationProviderInterface::class) + ->getMock(); + + $stub + ->method('getPaths') + ->willReturn($paths); + + $stub + ->method('getFileExtension') + ->willReturn('html'); + + $loader = new TerrificLoader($stub); + + $this->assertEquals($paths, $loader->getPaths()); + + $source = $loader->getSource('molecule'); + $this->assertEquals("--molecule_content--\n", $source); + + $source = $loader->getSource('atom'); + $this->assertEquals("--atom_content--\n", $source); + + // Check if the cache is invoked. + $source = $loader->getSource('atom'); + $this->assertEquals("--atom_content--\n", $source); + + try { + $loader->getSource('fake-component'); } + catch (Exception $e) { + $this->assertInstanceOf('Twig_Error_Loader', $e); + $this->assertContains('Unable to find component "fake-component"', $e->getMessage()); + } + + // Check if the cache is invoked. + try { + $loader->getSource('fake-component'); + } + catch (Exception $e) { + $this->assertInstanceOf('Twig_Error_Loader', $e); + $this->assertContains('Unable to find component "fake-component"', $e->getMessage()); + } + } + } diff --git a/test/Twig/Node/ComponentTestNode.php b/test/Twig/Node/ComponentTestNode.php index cd846f3..72df92c 100644 --- a/test/Twig/Node/ComponentTestNode.php +++ b/test/Twig/Node/ComponentTestNode.php @@ -6,162 +6,164 @@ use Twig_Node_Expression_Constant; use Twig_Node_Expression_Array; -class ComponentNodeTest extends \Twig_Test_NodeTestCase -{ - public function testContructor() - { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, null, false, 1); - - $this->assertNull($node->getNode('data')); - $this->assertEquals($expr, $node->getNode('component')); - $this->assertFalse($node->getAttribute('only')); - - $data = new Twig_Node_Expression_Array([ - new Twig_Node_Expression_Constant('foo', 1), - new Twig_Node_Expression_Constant(true, 1), - ], 1); - $node = new ComponentNode($expr, $data, true, 1); - - $this->assertEquals($data, $node->getNode('data')); - $this->assertTrue($node->getAttribute('only')); - } - - public function getTests() - { - $tests = [ - $this->getDefaultTest(), - $this->getDefaultOnlyTest(), - $this->getDataObjectTest(), - $this->getDataObjectOnlyTest(), - $this->getVariantTest(), - $this->getVariantOnlyTest(), - ]; - - return $tests; - } - - /** - * Tests the following tag: {% component 'Example' %}. - * - * @return array - */ - private function getDefaultTest() - { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, null, false, 1); - - return [ - $node, +/** + * + */ +class ComponentNodeTest extends \Twig_Test_NodeTestCase { + + /** + * + */ + public function testContructor() { + $expr = new Twig_Node_Expression_Constant('Example', 1); + $node = new ComponentNode($expr, NULL, FALSE, 1); + + $this->assertNull($node->getNode('data')); + $this->assertEquals($expr, $node->getNode('component')); + $this->assertFalse($node->getAttribute('only')); + + $data = new Twig_Node_Expression_Array([ + new Twig_Node_Expression_Constant('foo', 1), + new Twig_Node_Expression_Constant(TRUE, 1), + ], 1); + $node = new ComponentNode($expr, $data, TRUE, 1); + + $this->assertEquals($data, $node->getNode('data')); + $this->assertTrue($node->getAttribute('only')); + } + + /** + * + */ + public function getTests() { + $tests = [ + $this->getDefaultTest(), + $this->getDefaultOnlyTest(), + $this->getDataObjectTest(), + $this->getDataObjectOnlyTest(), + $this->getVariantTest(), + $this->getVariantOnlyTest(), + ]; + + return $tests; + } + + /** + * Tests the following tag: {% component 'Example' %}. + * + * @return array + */ + private function getDefaultTest() { + $expr = new Twig_Node_Expression_Constant('Example', 1); + $node = new ComponentNode($expr, NULL, FALSE, 1); + + return [ + $node, <<loadTemplate("Example", null, 1)->display(\$context); EOF - ]; - } - - /** - * Tests the following tag: {% component 'Example' only %}. - * - * @return array - */ - private function getDefaultOnlyTest() - { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, null, true, 1); - - return [ - $node, + ]; + } + + /** + * Tests the following tag: {% component 'Example' only %}. + * + * @return array + */ + private function getDefaultOnlyTest() { + $expr = new Twig_Node_Expression_Constant('Example', 1); + $node = new ComponentNode($expr, NULL, TRUE, 1); + + return [ + $node, <<loadTemplate("Example", null, 1)->display([]); EOF - ]; - } - - /** - * Tests the following tag: {% component 'Example' { foo: 'bar' } %}. - * - * @return array - */ - private function getDataObjectTest() - { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $data = new Twig_Node_Expression_Array([ - new Twig_Node_Expression_Constant('foo', 1), - new Twig_Node_Expression_Constant('bar', 1), - ], 1); - $node = new ComponentNode($expr, $data, false, 1); - - return [ - $node, + ]; + } + + /** + * Tests the following tag: {% component 'Example' { foo: 'bar' } %}. + * + * @return array + */ + private function getDataObjectTest() { + $expr = new Twig_Node_Expression_Constant('Example', 1); + $data = new Twig_Node_Expression_Array([ + new Twig_Node_Expression_Constant('foo', 1), + new Twig_Node_Expression_Constant('bar', 1), + ], 1); + $node = new ComponentNode($expr, $data, FALSE, 1); + + return [ + $node, <<loadTemplate("Example", null, 1)->display(array_merge(\$context, array("foo" => "bar"))); EOF - ]; - } - - /** - * Tests the following tag: {% component 'Example' { foo: 'bar' } only %}. - * - * @return array - */ - private function getDataObjectOnlyTest() - { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $data = new Twig_Node_Expression_Array([ - new Twig_Node_Expression_Constant('foo', 1), - new Twig_Node_Expression_Constant('bar', 1), - ], 1); - $node = new ComponentNode($expr, $data, true, 1); - - return [ - $node, + ]; + } + + /** + * Tests the following tag: {% component 'Example' { foo: 'bar' } only %}. + * + * @return array + */ + private function getDataObjectOnlyTest() { + $expr = new Twig_Node_Expression_Constant('Example', 1); + $data = new Twig_Node_Expression_Array([ + new Twig_Node_Expression_Constant('foo', 1), + new Twig_Node_Expression_Constant('bar', 1), + ], 1); + $node = new ComponentNode($expr, $data, TRUE, 1); + + return [ + $node, <<loadTemplate("Example", null, 1)->display(array("foo" => "bar")); EOF - ]; - } - - /** - * Tests the following tag: {% component 'Example' 'example-variant' %}. - * - * @return array - */ - private function getVariantTest() - { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $data = new Twig_Node_Expression_Constant('example-foo', 1); - $node = new ComponentNode($expr, $data, false, 1); - - return [ - $node, + ]; + } + + /** + * Tests the following tag: {% component 'Example' 'example-variant' %}. + * + * @return array + */ + private function getVariantTest() { + $expr = new Twig_Node_Expression_Constant('Example', 1); + $data = new Twig_Node_Expression_Constant('example-foo', 1); + $node = new ComponentNode($expr, $data, FALSE, 1); + + return [ + $node, <<loadTemplate("Example", null, 1)->display(array_merge(\$context, array("data_source" => "example-foo"))); EOF - ]; - } - - /** - * Tests the following tag: {% component 'Example' 'example-variant' only %}. - * - * @TODO - */ - private function getVariantOnlyTest() - { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $data = new Twig_Node_Expression_Constant('example-foo', 1); - $node = new ComponentNode($expr, $data, true, 1); - - return [ - $node, + ]; + } + + /** + * Tests the following tag: {% component 'Example' 'example-variant' only %}. + * + * @TODO + */ + private function getVariantOnlyTest() { + $expr = new Twig_Node_Expression_Constant('Example', 1); + $data = new Twig_Node_Expression_Constant('example-foo', 1); + $node = new ComponentNode($expr, $data, TRUE, 1); + + return [ + $node, <<loadTemplate("Example", null, 1)->display(array("data_source" => "example-foo")); EOF - ]; - } + ]; + } + } diff --git a/test/Twig/TokenParser/ComponentTokenParserTest.php b/test/Twig/TokenParser/ComponentTokenParserTest.php index bedd7fc..64e8a03 100644 --- a/test/Twig/TokenParser/ComponentTokenParserTest.php +++ b/test/Twig/TokenParser/ComponentTokenParserTest.php @@ -10,22 +10,24 @@ * * @TODO Maybe implement test for the Parser. */ -class ComponentTokenParserTest extends \PHPUnit_Framework_TestCase -{ - public function testGetTag() - { - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - $parser = new ComponentTokenParser($stub); - $this->assertEquals('component', $parser->getTag()); - } +class ComponentTokenParserTest extends \PHPUnit_Framework_TestCase { -// public function testParse() -// { -// $tokenParser = new ComponentTokenParser(); -// $parser = $this->getMockBuilder('Twig_Parser')->getMock(); -// $token = $this->getMockBuilder('Twig_Token')->getMock(); -// -// $tokenParser->setParser($parser); -// $this->assertInstanceOf(ComponentNode::class, $tokenParser->parse($token)); -// } + /** + * + */ + public function testGetTag() { + $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + $parser = new ComponentTokenParser($stub); + $this->assertEquals('component', $parser->getTag()); + } + + // Public function testParse() + // { + // $tokenParser = new ComponentTokenParser(); + // $parser = $this->getMockBuilder('Twig_Parser')->getMock(); + // $token = $this->getMockBuilder('Twig_Token')->getMock(); + // + // $tokenParser->setParser($parser); + // $this->assertInstanceOf(ComponentNode::class, $tokenParser->parse($token)); + // } } From 3f2e63db505f38db928f5426b9c4b1fe53b41302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Thu, 31 Oct 2019 09:34:10 +0100 Subject: [PATCH 06/29] Replace usage of legacy Twig classes --- src/Provider/ContextProviderInterface.php | 12 ++++---- src/Twig/Extension/TerrificExtension.php | 4 +-- src/Twig/Loader/TerrificLoader.php | 10 +++---- src/Twig/Node/ComponentNode.php | 29 +++++++++---------- src/Twig/TokenParser/ComponentTokenParser.php | 20 ++++++------- 5 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/Provider/ContextProviderInterface.php b/src/Provider/ContextProviderInterface.php index b00a8b3..236e041 100644 --- a/src/Provider/ContextProviderInterface.php +++ b/src/Provider/ContextProviderInterface.php @@ -2,8 +2,8 @@ namespace Deniaz\Terrific\Provider; -use Twig_Compiler; -use Twig_Node; +use Twig\Compiler; +use Twig\Node\Node; /** * Interface to describe a Context Provider. @@ -17,13 +17,13 @@ interface ContextProviderInterface { /** * Compiles the $tContext variable which is passed to the Twig Template. * - * @param \Twig_Compiler $compiler - * @param \Twig_Node $component - * @param \Twig_Node $dataVariant + * @param \Twig\Compiler $compiler + * @param \Twig\Node\Node $component + * @param \Twig\Node\Node $dataVariant * @param $only * * @return mixed */ - public function compile(Twig_Compiler $compiler, Twig_Node $component, Twig_Node $dataVariant, $only); + public function compile(Compiler $compiler, Node $component, Node $dataVariant, $only); } diff --git a/src/Twig/Extension/TerrificExtension.php b/src/Twig/Extension/TerrificExtension.php index 1b21033..5dfbced 100644 --- a/src/Twig/Extension/TerrificExtension.php +++ b/src/Twig/Extension/TerrificExtension.php @@ -4,7 +4,7 @@ use Deniaz\Terrific\Provider\ContextProviderInterface; use Deniaz\Terrific\Twig\TokenParser\ComponentTokenParser; -use Twig_Extension; +use Twig\Extension\AbstractExtension; /** * TerrificExtension adds Terrific Features to the Twig Environment. Currently only the ComponentTokenParser is added, @@ -14,7 +14,7 @@ * * @package Deniaz\Terrific\Twig\Extension */ -final class TerrificExtension extends Twig_Extension { +final class TerrificExtension extends AbstractExtension { /** * @var \Deniaz\Terrific\Provider\ContextProviderInterfaceContextVariableProvider */ diff --git a/src/Twig/Loader/TerrificLoader.php b/src/Twig/Loader/TerrificLoader.php index e687344..b141962 100644 --- a/src/Twig/Loader/TerrificLoader.php +++ b/src/Twig/Loader/TerrificLoader.php @@ -3,8 +3,8 @@ namespace Deniaz\Terrific\Twig\Loader; use Deniaz\Terrific\Provider\TemplateInformationProviderInterface; -use Twig_Error_Loader; -use Twig_Loader_Filesystem; +use Twig\Error\LoaderError; +use Twig\Loader\FilesystemLoader; /** * TerrificLoader searches for templates on the filesystem within a terrific structure. Since the templates are stored @@ -14,7 +14,7 @@ * * @package Deniaz\Terrific\Twig\Loader */ -final class TerrificLoader extends Twig_Loader_Filesystem { +final class TerrificLoader extends FilesystemLoader { /** * @var string */ @@ -41,7 +41,7 @@ protected function findTemplate($name) { } if (isset($this->errorCache[$name])) { - throw new Twig_Error_Loader($this->errorCache[$name]); + throw new LoaderError($this->errorCache[$name]); } $this->validateName($name); @@ -59,7 +59,7 @@ protected function findTemplate($name) { $this->errorCache[$name] = sprintf('Unable to find component "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); - throw new Twig_Error_Loader($this->errorCache[$name]); + throw new LoaderError($this->errorCache[$name]); } } diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index 78aeb1c..634f211 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -3,12 +3,11 @@ namespace Deniaz\Terrific\Twig\Node; use Deniaz\Terrific\Provider\ContextProviderInterface; +use Twig\Compiler; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\NameExpression; -use Twig_Compiler; -use Twig_Node; -use Twig_Node_Expression; -use Twig_NodeOutputInterface; +use Twig\Node\Node; +use Twig\Node\NodeOutputInterface; /** * ComponentNode represents a component node. @@ -17,7 +16,7 @@ * * @package Deniaz\Terrific\Twig\Node */ -final class ComponentNode extends Twig_Node implements Twig_NodeOutputInterface { +final class ComponentNode extends Node implements NodeOutputInterface { /** * The context provider. * @@ -28,11 +27,11 @@ final class ComponentNode extends Twig_Node implements Twig_NodeOutputInterface /** * ComponentNode constructor. * - * @param \Twig_Node_Expression $component + * @param \Twig\Node\Node $component * Expression representing the Component's Identifier. * @param \Deniaz\Terrific\Provider\ContextProviderInterface $ctxProvider * Context Provider. - * @param \Twig_Node_Expression|null $data + * @param \Twig\Node\Node|null $data * Expression representing the additional data. * @param bool $only * Whether a new Child-Context should be created. @@ -42,9 +41,9 @@ final class ComponentNode extends Twig_Node implements Twig_NodeOutputInterface * Tag name associated with the node. */ public function __construct( - Twig_Node_Expression $component, + Node $component, ContextProviderInterface $ctxProvider, - Twig_Node_Expression $data = NULL, + Node $data = NULL, $only = FALSE, $lineno, $tag = NULL) { @@ -61,10 +60,10 @@ public function __construct( /** * Compiles the component. * - * @param \Twig_Compiler $compiler + * @param \Twig\Compiler $compiler * The Twig compiler. */ - public function compile(Twig_Compiler $compiler) { + public function compile(Compiler $compiler) { $compiler->addDebugInfo($this); // Create data. @@ -83,10 +82,10 @@ public function compile(Twig_Compiler $compiler) { /** * Makes the data for the component available to it. * - * @param \Twig_Compiler $compiler + * @param \Twig\Compiler $compiler * The Twig compiler. */ - protected function createTerrificContext(Twig_Compiler $compiler) { + protected function createTerrificContext(Compiler $compiler) { $compiler ->addIndentation() ->raw('$tContext = $context;') @@ -107,10 +106,10 @@ protected function createTerrificContext(Twig_Compiler $compiler) { * IMPORTANT: Has to be executed after the Terrific context was created * (ComponentNode::createTerrificContext). * - * @param \Twig_Compiler $compiler + * @param \Twig\Compiler $compiler * The Twig compiler. */ - protected function addGetTemplate(Twig_Compiler $compiler) { + protected function addGetTemplate(Compiler $compiler) { $compiler->write('$this->loadTemplate('); /* If a variable is used for component name, use it's value (is inside Terrifc context "$tContext") for template name. */ diff --git a/src/Twig/TokenParser/ComponentTokenParser.php b/src/Twig/TokenParser/ComponentTokenParser.php index 8586433..11a095f 100644 --- a/src/Twig/TokenParser/ComponentTokenParser.php +++ b/src/Twig/TokenParser/ComponentTokenParser.php @@ -4,8 +4,8 @@ use Deniaz\Terrific\Provider\ContextProviderInterface; use Deniaz\Terrific\Twig\Node\ComponentNode; -use Twig_Token; -use Twig_TokenParser; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; /** * Includes a Terrific Component. @@ -14,7 +14,7 @@ * * @package Deniaz\Terrific\Twig\TokenParser */ -final class ComponentTokenParser extends Twig_TokenParser { +final class ComponentTokenParser extends AbstractTokenParser { /** * @var \Deniaz\Terrific\Provider\ContextProviderInterfaceContextVariableProvider */ @@ -32,7 +32,7 @@ public function __construct(ContextProviderInterface $ctxProvider) { /** * {@inheritdoc} */ - public function parse(Twig_Token $token) { + public function parse(Token $token) { $component = $this->parser->getExpressionParser()->parseExpression(); list($data, $only) = $this->parseArguments(); @@ -50,28 +50,28 @@ protected function parseArguments() { $data = NULL; $only = FALSE; - if ($stream->test(Twig_Token::BLOCK_END_TYPE)) { - $stream->expect(Twig_Token::BLOCK_END_TYPE); + if ($stream->test(Token::BLOCK_END_TYPE)) { + $stream->expect(Token::BLOCK_END_TYPE); return [$data, $only]; } - if ($stream->test(Twig_Token::NAME_TYPE, 'only')) { + if ($stream->test(Token::NAME_TYPE, 'only')) { $only = TRUE; $stream->next(); - $stream->expect(Twig_Token::BLOCK_END_TYPE); + $stream->expect(Token::BLOCK_END_TYPE); return [$data, $only]; } $data = $this->parser->getExpressionParser()->parseExpression(); - if ($stream->test(Twig_Token::NAME_TYPE, 'only')) { + if ($stream->test(Token::NAME_TYPE, 'only')) { $only = TRUE; $stream->next(); } - $stream->expect(Twig_Token::BLOCK_END_TYPE); + $stream->expect(Token::BLOCK_END_TYPE); return [$data, $only]; } From c9789108b8237a8e39b3cadcd68e86405e942cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Thu, 31 Oct 2019 10:01:51 +0100 Subject: [PATCH 07/29] Add constant for Terrific context variable name --- src/Provider/ContextProviderInterface.php | 7 +++++++ src/Twig/Node/ComponentNode.php | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Provider/ContextProviderInterface.php b/src/Provider/ContextProviderInterface.php index 236e041..f114084 100644 --- a/src/Provider/ContextProviderInterface.php +++ b/src/Provider/ContextProviderInterface.php @@ -14,6 +14,13 @@ */ interface ContextProviderInterface { + /** + * The variable name of the Terrific Twig context. + * + * @var string + */ + public const TERRIFIC_CONTEXT_VARIABLE = '$tContext'; + /** * Compiles the $tContext variable which is passed to the Twig Template. * diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index 634f211..c2974aa 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -73,7 +73,7 @@ public function compile(Compiler $compiler) { $this->addGetTemplate($compiler); $compiler - ->raw('->display($tContext);') + ->raw('->display(' . ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ');') ->raw("\n\n"); $compiler->addDebugInfo($this->getNode('component')); @@ -88,7 +88,7 @@ public function compile(Compiler $compiler) { protected function createTerrificContext(Compiler $compiler) { $compiler ->addIndentation() - ->raw('$tContext = $context;') + ->raw(ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ' = $context;') ->raw("\n"); $this->ctxProvider->compile( From 18741f0671c607c10680a252a8134a2e9bac8178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Thu, 31 Oct 2019 15:57:23 +0100 Subject: [PATCH 08/29] Add comments --- src/Twig/Loader/TerrificLoader.php | 11 +++++++++-- src/Twig/TokenParser/ComponentTokenParser.php | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Twig/Loader/TerrificLoader.php b/src/Twig/Loader/TerrificLoader.php index b141962..9025476 100644 --- a/src/Twig/Loader/TerrificLoader.php +++ b/src/Twig/Loader/TerrificLoader.php @@ -7,15 +7,21 @@ use Twig\Loader\FilesystemLoader; /** - * TerrificLoader searches for templates on the filesystem within a terrific structure. Since the templates are stored - * on the filesystem nonetheless, TerrificLoader extends Twig's Twig_Loader_Filesystem. + * TerrificLoader searches for templates on the filesystem. + * + * Within a terrific structure. + * Since the templates are stored on the filesystem nonetheless, + * TerrificLoader extends Twig's Twig_Loader_Filesystem. * * Class TerrificLoader. * * @package Deniaz\Terrific\Twig\Loader */ final class TerrificLoader extends FilesystemLoader { + /** + * The template file extension to use. + * * @var string */ private $fileExtension = 'html.twig'; @@ -24,6 +30,7 @@ final class TerrificLoader extends FilesystemLoader { * TerrificLoader constructor. * * @param \Deniaz\Terrific\Provider\TemplateInformationProviderInterface $locator + * The template locator. */ public function __construct(TemplateInformationProviderInterface $locator) { parent::__construct($locator->getPaths()); diff --git a/src/Twig/TokenParser/ComponentTokenParser.php b/src/Twig/TokenParser/ComponentTokenParser.php index 11a095f..1592703 100644 --- a/src/Twig/TokenParser/ComponentTokenParser.php +++ b/src/Twig/TokenParser/ComponentTokenParser.php @@ -16,6 +16,8 @@ */ final class ComponentTokenParser extends AbstractTokenParser { /** + * The context provider. + * * @var \Deniaz\Terrific\Provider\ContextProviderInterfaceContextVariableProvider */ private $ctxProvider; @@ -24,6 +26,7 @@ final class ComponentTokenParser extends AbstractTokenParser { * ComponentTokenParser constructor. * * @param \Deniaz\Terrific\Provider\ContextProviderInterface $ctxProvider + * The context provider. */ public function __construct(ContextProviderInterface $ctxProvider) { $this->ctxProvider = $ctxProvider; @@ -43,6 +46,7 @@ public function parse(Token $token) { * Tokenizes the component stream. * * @return array + * Array containing data and only flag. */ protected function parseArguments() { $stream = $this->parser->getStream(); From 36e13b890474b7a2527ba81cc23f1466c532c370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Thu, 31 Oct 2019 15:59:54 +0100 Subject: [PATCH 09/29] Add support for using object/array keys for component name --- .../Data/VariableNameAndArrayKeysPair.php | 191 ++++++++++++++++ src/Twig/Node/ComponentNode.php | 134 +++++++++--- src/Twig/TerrificCompiler.php | 203 ++++++++++++++++++ src/Twig/TerrificCompilerInterface.php | 71 ++++++ src/Twig/Utility/ExpressionHandler.php | 124 +++++++++++ 5 files changed, 699 insertions(+), 24 deletions(-) create mode 100644 src/Twig/Data/VariableNameAndArrayKeysPair.php create mode 100644 src/Twig/TerrificCompiler.php create mode 100644 src/Twig/TerrificCompilerInterface.php create mode 100644 src/Twig/Utility/ExpressionHandler.php diff --git a/src/Twig/Data/VariableNameAndArrayKeysPair.php b/src/Twig/Data/VariableNameAndArrayKeysPair.php new file mode 100644 index 0000000..e3b376d --- /dev/null +++ b/src/Twig/Data/VariableNameAndArrayKeysPair.php @@ -0,0 +1,191 @@ +variableName = $variableName; + $this->arrayKeys = $arrayKeys; + } + + /** + * Instantiates a new object. + * + * @param string $variableName + * The variable name. + * @param array $arrayKeys + * Array of array keys for variable. + */ + public static function create(string $variableName, array $arrayKeys) { + return new self($variableName, $arrayKeys); + } + + /** + * Instantiates a new object for a variable without array keys. + * + * @param string $variableName + * The variable name. + */ + public static function createVariable(string $variableName) { + return new self($variableName); + } + + /** + * Returns the variable name. + * + * @return string + * The variable name. + */ + public function getVariableName(): string { + return $this->variableName; + } + + /** + * Sets the variable name. + * + * @param string $variableName + * The variable name. + */ + public function setVariableName(string $variableName): void { + $this->variableName = $variableName; + } + + /** + * Returns the array keys. + * + * @return string[] + * The array keys. + */ + public function getArrayKeys(): array { + return $this->arrayKeys; + } + + /** + * Sets the array keys. + * + * @param string[] $arrayKeys + * The array keys. + */ + public function setArrayKeys(array $arrayKeys): void { + $this->arrayKeys = $arrayKeys; + } + + /** + * Generates Twig context array keys. + * + * Of the variable the object represents inside the Twig context. + * + * @return string + * E.g. ['myVariable']['aKey']['anotherKey']. + * Which may be used to access the value + * of the variable from the Twig context when compiling: + * $context['myVariable']['aKey']['anotherKey']. + */ + public function toTwigContextArrayKeysString(): string { + if (!$this->isVariableNameEmpty()) { + $string = $this->formatAsArrayKeyString($this->getVariableName()); + } + else { + throw new Error('Cannot generate valid Twig variable string, variable name is empty.'); + } + + if (!$this->isArrayKeysEmpty()) { + foreach ($this->getArrayKeys() as $arrayKey) { + $string .= $this->formatAsArrayKeyString($arrayKey); + } + } + + return $string; + } + + /** + * Generates a string representation. + * + * Of the variable the object represents inside the Twig template. + * + * @return string + * E.g. myVariable['aKey']['anotherKey']. + */ + public function toTwigVariableString(): string { + if (!$this->isVariableNameEmpty()) { + $string = $this->getVariableName(); + } + else { + throw new Error('Cannot generate valid Twig variable string, variable name is empty.'); + } + + if (!$this->isArrayKeysEmpty()) { + foreach ($this->getArrayKeys() as $arrayKey) { + $string .= $this->formatAsArrayKeyString($arrayKey); + } + } + + return $string; + } + + /** + * Formats given string as an array key string. + * + * @param string $arrayKey + * The array key. + * + * @return string + * Array key formatted as array key string. + */ + protected function formatAsArrayKeyString(string $arrayKey): string { + return '["' . $arrayKey . '"]'; + } + + /** + * Checks if the variable name is empty. + * + * @return bool + * TRUE if empty. + */ + protected function isVariableNameEmpty(): bool { + return empty($this->getVariableName()); + } + + /** + * Checks if the array keys are empty. + * + * @return bool + * TRUE if empty. + */ + protected function isArrayKeysEmpty(): bool { + return empty($this->getArrayKeys()); + } + +} diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index c2974aa..2919474 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -3,8 +3,13 @@ namespace Deniaz\Terrific\Twig\Node; use Deniaz\Terrific\Provider\ContextProviderInterface; +use Deniaz\Terrific\Twig\TerrificCompiler; +use Deniaz\Terrific\Twig\TerrificCompilerInterface; +use Deniaz\Terrific\Twig\Utility\ExpressionHandler; use Twig\Compiler; +use Twig\Error\SyntaxError; use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\NameExpression; use Twig\Node\Node; use Twig\Node\NodeOutputInterface; @@ -24,6 +29,16 @@ final class ComponentNode extends Node implements NodeOutputInterface { */ private $ctxProvider; + /** + * The expression handler. + * + * Should not be accessed directly by methods. + * Except the getter. + * + * @var |Deniaz\Terrific\Twig\Utility\ExpressionHandler + */ + private $expressionHandler; + /** * ComponentNode constructor. * @@ -64,35 +79,37 @@ public function __construct( * The Twig compiler. */ public function compile(Compiler $compiler) { - $compiler->addDebugInfo($this); + $terrificCompiler = TerrificCompiler::create($compiler); + + $terrificCompiler->getTwigCompiler()->addDebugInfo($this); // Create data. - $this->createTerrificContext($compiler); + $this->createTerrificContext($terrificCompiler); // Load component template. - $this->addGetTemplate($compiler); + $this->addGetTemplate($terrificCompiler); - $compiler + $terrificCompiler->getTwigCompiler() ->raw('->display(' . ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ');') ->raw("\n\n"); - $compiler->addDebugInfo($this->getNode('component')); + $terrificCompiler->getTwigCompiler()->addDebugInfo($this->getNode('component')); } /** * Makes the data for the component available to it. * - * @param \Twig\Compiler $compiler - * The Twig compiler. + * @param \Deniaz\Terrific\Twig\TerrificCompilerInterface $terrificCompiler + * The Terrific Twig compiler. */ - protected function createTerrificContext(Compiler $compiler) { - $compiler + protected function createTerrificContext(TerrificCompilerInterface $terrificCompiler) { + $terrificCompiler->getTwigCompiler() ->addIndentation() ->raw(ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ' = $context;') ->raw("\n"); $this->ctxProvider->compile( - $compiler, + $terrificCompiler->getTwigCompiler(), $this->getNode('component'), $this->getNode('data'), $this->getAttribute('only') @@ -106,27 +123,96 @@ protected function createTerrificContext(Compiler $compiler) { * IMPORTANT: Has to be executed after the Terrific context was created * (ComponentNode::createTerrificContext). * - * @param \Twig\Compiler $compiler - * The Twig compiler. + * @param \Deniaz\Terrific\Twig\TerrificCompilerInterface $terrificCompiler + * The Terrific Twig compiler. */ - protected function addGetTemplate(Compiler $compiler) { - $compiler->write('$this->loadTemplate('); + protected function addGetTemplate(TerrificCompilerInterface $terrificCompiler) { + $terrificCompiler->getTwigCompiler()->write('$this->loadTemplate('); - /* If a variable is used for component name, use it's value (is inside Terrifc context "$tContext") for template name. */ - if ($this->getNode('component') instanceof NameExpression) { - $compiler->raw('$tContext["' . $this->getNode('component')->getAttribute('name') . '"]'); - } - /* If a static string (constant) is used for component name, compile it (prints it). */ - elseif ($this->getNode('component') instanceof ConstantExpression) { - $compiler->subcompile($this->getNode('component')); - } + $this->compileComponentName($terrificCompiler); - $compiler + $terrificCompiler->getTwigCompiler() ->raw(', ') - ->repr($compiler->getFilename()) + ->repr($terrificCompiler->getTwigCompiler()->getFilename()) ->raw(', ') ->repr($this->getLine()) ->raw(')'); } + /** + * Compiles the component name. + * + * @param \Deniaz\Terrific\Twig\TerrificCompilerInterface $terrificCompiler + * The Terrific Twig compiler. + * + * @throws \Twig\Error\SyntaxError + * If the node name uses an unsupported format. + */ + protected function compileComponentName(TerrificCompilerInterface $terrificCompiler): void { + /* If a variable is used for component name, + use it's value (is inside Terrifc context "$tContext") for template name. + E.g. {% component myTwigComponentName { dataItem: 'my string' } %} */ + if ($this->getNode('component') instanceof NameExpression) { + $terrificCompiler->compileNameExpressionAsContextVariable($this->getNode('component')); + } + /* If a static string (constant) is used for component name, + compile it (prints it). + E.g. {% component 'myComponent' { dataItem: 'my string' } %} */ + elseif ($this->getNode('component') instanceof ConstantExpression) { + $terrificCompiler->getTwigCompiler()->subcompile($this->getNode('component')); + } + /* If an object object/array used for component name, + use it's value (is inside Terrifc context "$tContext") for template name. + E.g. {% component myTwigObject.anObjectProperty.myTwigComponentName { dataItem: 'my string' } %} */ + elseif ($this->getNode('component') instanceof GetAttrExpression) { + $terrificCompiler->compileGetAttrExpressionAsContextVariable($this->getNode('component')); + } + else { + throw new SyntaxError('An unsupported data type was used for name to call a Twig Nitro component.'); + } + } + + /** + * Generates an array with the array keys. + * + * Pointing to the value location of given expression in the context. + * + * @param \Twig\Node\Expression\GetAttrExpression $expression + * The expression to get the context array keys for. + * + * @return string[] + * Array with array keys. + */ + protected function compileGetAttrExpressionComponentName(GetAttrExpression $expression): array { + if ($this->getExpressionHandler()->isNestedObject($expression)) { + $dataVariableName = $expression->getNode('attribute')->getAttribute('value'); + $childExpression = $expression->getNode('node'); + $dataVariableArrayKeys = $this->buildGetAttrExpressionArrayKeys($childExpression); + + $dataExpressionArrayKeys = $dataVariableArrayKeys; + $dataExpressionArrayKeys[] = $dataVariableName; + + return $dataExpressionArrayKeys; + } + else { + $dataVariableName = $expression->getNode('node')->getAttribute('name'); + $dataVariableArrayKey = $expression->getNode('attribute')->getAttribute('value'); + + $dataExpressionArrayKeys = [$dataVariableName, $dataVariableArrayKey]; + + return $dataExpressionArrayKeys; + } + } + + /** + * Returns the expression handler. + */ + protected function getExpressionHandler(): ExpressionHandler { + if (!$this->expressionHandler instanceof ExpressionHandler) { + $this->expressionHandler = ExpressionHandler::create(); + } + + return $this->expressionHandler; + } + } diff --git a/src/Twig/TerrificCompiler.php b/src/Twig/TerrificCompiler.php new file mode 100644 index 0000000..2ed5c2c --- /dev/null +++ b/src/Twig/TerrificCompiler.php @@ -0,0 +1,203 @@ +compiler = $compiler; + } + + /** + * Instantiates a new object. + * + * @param \Twig_CompilerInterface $compiler + * The Twig compiler. + */ + public static function create(CompilerInterface $compiler): self { + return new self($compiler); + } + + /** + * Compiles NameExpression as a variable to access. + * + * @param \Twig\Node\Expression\NameExpression $expression + * The expression to compile. + * @param string $contextVariable + * The PHP variable to use as base for adding the array keys. + * Defaults to the Terrific context. + */ + public function compileNameExpressionAsContextVariable(NameExpression $expression, string $contextVariable = ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE): void { + $variableName = $this->getExpressionHandler()->getVariableNameFromNameExpression($expression); + + $this->compileAsContextVariable(VariableNameAndArrayKeysPair::createVariable($variableName), $contextVariable); + } + + /** + * Compiles GetAttrExpression as a variable to access. + * + * @param \Twig\Node\Expression\GetAttrExpression $expression + * The expression to compile. + * @param string $contextVariable + * The PHP variable to use as base for adding the array keys. + * Defaults to the Terrific context. + */ + public function compileGetAttrExpressionAsContextVariable(GetAttrExpression $expression, string $contextVariable = ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE): void { + $variableAndArrayKeys = $this->getExpressionHandler()->buildGetAttrExpressionArrayKeyPair($expression); + + $this->compileAsContextVariable($variableAndArrayKeys, $contextVariable); + } + + /** + * Compiles given variable as a Twig context variable. + * + * @param \Deniaz\Terrific\Twig\Data\VariableNameAndArrayKeysPair $variableNameAndArrayKeysPair + * Object containing the variable name and optionally array keys. + * @param string $contextVariable + * The PHP variable to use as base for adding the array keys. + */ + protected function compileAsContextVariable(VariableNameAndArrayKeysPair $variableNameAndArrayKeysPair, string $contextVariable): void { + $contextArrayKeys = $variableNameAndArrayKeysPair->toTwigContextArrayKeysString(); + + $this->getTwigCompiler()->raw($contextVariable . $contextArrayKeys); + } + + /** + * Compiles and adds NameExpression as a variable to the Terrific Twig context. + * + * @param \Twig\Node\Expression\NameExpression $expression + * The expression to compile. + * @param string|null $variableDoesNotExistErrorMessage + * Custom error message that is used as exception message + * when the given variable does not exist. + */ + public function compileAndMergeNameExpressionToContext(NameExpression $expression, ?string $variableDoesNotExistErrorMessage = NULL): void { + $variableName = $this->getExpressionHandler()->getVariableNameFromNameExpression($expression); + + $this->compileAndMergeVariableToContext( + VariableNameAndArrayKeysPair::createVariable($variableName), + $variableDoesNotExistErrorMessage + ); + } + + /** + * Compiles and adds GetAttrExpression as a variable to the Terrific Twig context. + * + * @param \Twig\Node\Expression\GetAttrExpression $expression + * The expression to compile. + * @param string|null $variableDoesNotExistErrorMessage + * Custom error message that is used as exception message + * when the given variable does not exist. + */ + public function compileAndMergeGetAttrExpressionToContext(GetAttrExpression $expression, ?string $variableDoesNotExistErrorMessage = NULL): void { + $variableAndArrayKeys = $this->getExpressionHandler()->buildGetAttrExpressionArrayKeyPair($expression); + + $this->compileAndMergeVariableToContext($variableAndArrayKeys, $variableDoesNotExistErrorMessage); + } + + /** + * Checks if given variable exists, and adds it to the context. + * + * So it it's data is available in the compiled component. + * + * @param \Deniaz\Terrific\Twig\Data\VariableNameAndArrayKeysPair $variableNameAndArrayKeysPair + * Object containing the variable name and optionally array keys. + * @param string|null $variableDoesNotExistErrorMessage + * Custom error message that is used as exception message + * when the given variable does not exist. + */ + public function compileAndMergeVariableToContext(VariableNameAndArrayKeysPair $variableNameAndArrayKeysPair, ?string $variableDoesNotExistErrorMessage = NULL): void { + $contextArrayKeys = $variableNameAndArrayKeysPair->toTwigContextArrayKeysString(); + + if ($variableDoesNotExistErrorMessage === NULL) { + $variableDoesNotExistErrorMessage = addslashes('The variable ' . $variableNameAndArrayKeysPair->toTwigVariableString() . ' does not exist. Could not compile it to the Twig context.'); + } + + $this->getTwigCompiler() + ->raw("\n")->addIndentation() + ->raw('if (') + ->raw('isset('); + + $this->compileAsContextVariable($variableNameAndArrayKeysPair, '$context'); + + $this->getTwigCompiler() + ->raw(')') + ->raw(') {') + ->raw("\n")->addIndentation()->addIndentation() + ->raw(ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ' = array_merge(' . ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ', '); + + $this->compileAsContextVariable($variableNameAndArrayKeysPair, '$context'); + + $this->getTwigCompiler() + ->raw(');') + ->raw("\n")->addIndentation() + ->raw('} else {') + ->raw("\n")->addIndentation()->addIndentation() + ->raw('throw new \Twig\Error\Error("') + ->raw($variableDoesNotExistErrorMessage) + ->raw('");') + ->raw("\n")->addIndentation() + ->raw('}') + ->raw("\n\n"); + } + + /** + * Returns the expression handler. + * + * @return \Deniaz\Terrific\Twig\Utility\ExpressionHandler + * The expression handler. + */ + public function getExpressionHandler(): ExpressionHandler { + if (!$this->expressionHandler instanceof ExpressionHandler) { + $this->expressionHandler = ExpressionHandler::create(); + } + + return $this->expressionHandler; + } + + /** + * Returns the base Twig compiler. + * + * @return \Twig_CompilerInterface + * The Twig compiler. + */ + public function getTwigCompiler(): CompilerInterface { + return $this->compiler; + } + +} diff --git a/src/Twig/TerrificCompilerInterface.php b/src/Twig/TerrificCompilerInterface.php new file mode 100644 index 0000000..570cb69 --- /dev/null +++ b/src/Twig/TerrificCompilerInterface.php @@ -0,0 +1,71 @@ +buildGetAttrExpressionArrayKeys($expression); + + // Get the variable name, it's the first array item. + $arrayKeys = array_reverse($arrayKeys); + $variableName = array_pop($arrayKeys); + // Bring array back into original order. + $arrayKeys = array_reverse($arrayKeys); + + return VariableNameAndArrayKeysPair::create($variableName, $arrayKeys); + } + + /** + * Generates an array with the array keys. + * + * Pointing to the value location of given expression in the context. + * + * @param \Twig\Node\Expression\GetAttrExpression $expression + * The expression to get the context array keys for. + * + * @return string[] + * Array with array keys. + */ + public function buildGetAttrExpressionArrayKeys(GetAttrExpression $expression): array { + if ($this->isNestedObject($expression)) { + $dataVariableName = $expression->getNode('attribute')->getAttribute('value'); + $childExpression = $expression->getNode('node'); + $dataVariableArrayKeys = $this->buildGetAttrExpressionArrayKeys($childExpression); + + $dataExpressionArrayKeys = $dataVariableArrayKeys; + $dataExpressionArrayKeys[] = $dataVariableName; + + return $dataExpressionArrayKeys; + } + else { + $dataVariableName = $expression->getNode('node')->getAttribute('name'); + $dataVariableArrayKey = $expression->getNode('attribute')->getAttribute('value'); + + $dataExpressionArrayKeys = [$dataVariableName, $dataVariableArrayKey]; + + return $dataExpressionArrayKeys; + } + } + + /** + * Checks if given Twig expression is a nested object. + * + * @param \Twig\Node\Expression\GetAttrExpression $expression + * The Twig expression to check. + * + * @return bool + * TRUE if nested object. + * Otherwise FALSE. + */ + public function isNestedObject(GetAttrExpression $expression): bool { + $isNestedObject = $expression->hasNode('node') && $expression->getNode('node') instanceof GetAttrExpression; + + return $isNestedObject; + } + + /** + * Returns the context variable name from given expression. + * + * @param \Twig\Node\Expression\NameExpression $expression + * The Twig expression to check. + * + * @return string + * The variable name. + * + * @throws \Twig\Error\Error + * If the variable name value is not valid. + */ + public function getVariableNameFromNameExpression(NameExpression $expression): string { + $variableName = $expression->getAttribute('name'); + + if (!empty($variableName) && is_string($variableName)) { + return $variableName; + } + else { + throw new Error( + 'The variable name from NameExpression could not be read inside Twig node "' . $expression->getTemplateName() . '".', + $expression->getTemplateLine() + ); + } + } + +} From 6574b2e254eef3035d9064a56597ac9c9cb187c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Th=C3=B6ny?= Date: Thu, 19 Mar 2020 12:06:21 +0100 Subject: [PATCH 10/29] Do not use Twig Compiler interface, because it does not expose all methods that we use --- src/Twig/TerrificCompiler.php | 16 ++++++++-------- src/Twig/TerrificCompilerInterface.php | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Twig/TerrificCompiler.php b/src/Twig/TerrificCompiler.php index 2ed5c2c..2269bb7 100644 --- a/src/Twig/TerrificCompiler.php +++ b/src/Twig/TerrificCompiler.php @@ -5,9 +5,9 @@ use Deniaz\Terrific\Provider\ContextProviderInterface; use Deniaz\Terrific\Twig\Data\VariableNameAndArrayKeysPair; use Deniaz\Terrific\Twig\Utility\ExpressionHandler; +use Twig\Compiler; use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\NameExpression; -use Twig_CompilerInterface as CompilerInterface; /** * Extends the Twig compiler with additional functionality. @@ -19,7 +19,7 @@ class TerrificCompiler implements TerrificCompilerInterface { /** * The base Twig compiler. * - * @var \Twig_CompilerInterface + * @var \Twig\Compiler */ protected $compiler; @@ -36,20 +36,20 @@ class TerrificCompiler implements TerrificCompilerInterface { /** * TerrificCompiler constructor. * - * @param \Twig_CompilerInterface $compiler + * @param \Twig\Compiler $compiler * The Twig compiler. */ - protected function __construct(CompilerInterface $compiler) { + protected function __construct(Compiler $compiler) { $this->compiler = $compiler; } /** * Instantiates a new object. * - * @param \Twig_CompilerInterface $compiler + * @param \Twig\Compiler $compiler * The Twig compiler. */ - public static function create(CompilerInterface $compiler): self { + public static function create(Compiler $compiler): self { return new self($compiler); } @@ -193,10 +193,10 @@ public function getExpressionHandler(): ExpressionHandler { /** * Returns the base Twig compiler. * - * @return \Twig_CompilerInterface + * @return \Twig\Compiler * The Twig compiler. */ - public function getTwigCompiler(): CompilerInterface { + public function getTwigCompiler(): Compiler { return $this->compiler; } diff --git a/src/Twig/TerrificCompilerInterface.php b/src/Twig/TerrificCompilerInterface.php index 570cb69..8f05bdf 100644 --- a/src/Twig/TerrificCompilerInterface.php +++ b/src/Twig/TerrificCompilerInterface.php @@ -3,9 +3,9 @@ namespace Deniaz\Terrific\Twig; use Deniaz\Terrific\Twig\Utility\ExpressionHandler; +use Twig\Compiler; use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\NameExpression; -use Twig_CompilerInterface as CompilerInterface; /** * Extends the base Twig compiler. @@ -17,10 +17,10 @@ interface TerrificCompilerInterface { /** * Returns the base Twig compiler. * - * @return \Twig_CompilerInterface + * @return \Twig\Compiler * The Twig compiler. */ - public function getTwigCompiler(): CompilerInterface; + public function getTwigCompiler(): Compiler; /** * Compiles NameExpression as a variable to access. From f0ec879af180d64282236843433832574c61bdef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Th=C3=B6ny?= Date: Thu, 19 Mar 2020 18:28:20 +0100 Subject: [PATCH 11/29] Require PHP 7.2 or greater --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7545fc5..bd62f5f 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ } }, "require": { - "php": ">=5.4", + "php": ">=7.2", "twig/twig": "~1.24" } } From 71ca1c7557eb5a9a0aaa4dc3a71739b52e182b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Simb=C3=BCrger?= Date: Wed, 22 Jul 2020 15:46:04 +0200 Subject: [PATCH 12/29] Change namespace from 'Deniaz' to 'Namics' --- composer.json | 4 ++-- src/Config/ConfigReader.php | 4 ++-- src/Provider/ContextProviderInterface.php | 4 ++-- .../TemplateInformationProviderInterface.php | 6 ++--- .../Data/VariableNameAndArrayKeysPair.php | 4 ++-- src/Twig/Extension/TerrificExtension.php | 12 +++++----- src/Twig/Loader/TerrificLoader.php | 8 +++---- src/Twig/Node/ComponentNode.php | 24 +++++++++---------- src/Twig/TerrificCompiler.php | 18 +++++++------- src/Twig/TerrificCompilerInterface.php | 8 +++---- src/Twig/TokenParser/ComponentTokenParser.php | 12 +++++----- src/Twig/Utility/ExpressionHandler.php | 8 +++---- test/Twig/Extension/TerrificExtensionTest.php | 8 +++---- test/Twig/Loader/TerrificLoaderTest.php | 6 ++--- test/Twig/Node/ComponentTestNode.php | 4 ++-- .../TokenParser/ComponentTokenParserTest.php | 6 ++--- 16 files changed, 68 insertions(+), 68 deletions(-) diff --git a/composer.json b/composer.json index bd62f5f..3014fcb 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,8 @@ ], "autoload": { "psr-4": { - "Deniaz\\Terrific\\": "src/", - "Deniaz\\Test\\Terrific\\": "test/" + "Namics\\Terrific\\": "src/", + "Namics\\Test\\Terrific\\": "test/" } }, "require": { diff --git a/src/Config/ConfigReader.php b/src/Config/ConfigReader.php index cd59519..0741fbd 100644 --- a/src/Config/ConfigReader.php +++ b/src/Config/ConfigReader.php @@ -1,6 +1,6 @@ Date: Thu, 23 Jul 2020 08:27:27 +0200 Subject: [PATCH 13/29] Update type hints & variable comments --- src/Config/ConfigReader.php | 12 +++++++++--- src/Provider/ContextProviderInterface.php | 8 ++++++-- .../TemplateInformationProviderInterface.php | 8 +++++--- src/Twig/Extension/TerrificExtension.php | 5 ++++- src/Twig/Node/ComponentNode.php | 9 +++++++-- src/Twig/TerrificCompiler.php | 2 +- src/Twig/TokenParser/ComponentTokenParser.php | 3 ++- src/Twig/Utility/ExpressionHandler.php | 13 +++++++------ 8 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/Config/ConfigReader.php b/src/Config/ConfigReader.php index 0741fbd..29954f1 100644 --- a/src/Config/ConfigReader.php +++ b/src/Config/ConfigReader.php @@ -13,15 +13,20 @@ * @package Namics\Terrific\Config */ final class ConfigReader { + /** - * @const string FILE_NAME The Configuration's Filename. - */ + * The Configuration's Filename. + * + * @var string + */ const FILE_NAME = 'config.json'; /** + * The config. + * * @var array */ - private $config = NULL; + private $config = []; /** * ConfigReader constructor. @@ -52,6 +57,7 @@ public function __construct($terrificDir) { * Returns the Configuration. * * @return array + * The config. */ public function read() { return $this->config; diff --git a/src/Provider/ContextProviderInterface.php b/src/Provider/ContextProviderInterface.php index eb90ef5..165f5ad 100644 --- a/src/Provider/ContextProviderInterface.php +++ b/src/Provider/ContextProviderInterface.php @@ -25,12 +25,16 @@ interface ContextProviderInterface { * Compiles the $tContext variable which is passed to the Twig Template. * * @param \Twig\Compiler $compiler + * The Twig compiler. * @param \Twig\Node\Node $component + * The Twig node component. * @param \Twig\Node\Node $dataVariant - * @param $only + * The Twig node data variant. + * @param bool $only + * The only attribute. * * @return mixed */ - public function compile(Compiler $compiler, Node $component, Node $dataVariant, $only); + public function compile(Compiler $compiler, Node $component, Node $dataVariant, bool $only); } diff --git a/src/Provider/TemplateInformationProviderInterface.php b/src/Provider/TemplateInformationProviderInterface.php index 61ec414..e4ed9df 100644 --- a/src/Provider/TemplateInformationProviderInterface.php +++ b/src/Provider/TemplateInformationProviderInterface.php @@ -3,11 +3,11 @@ namespace Namics\Terrific\Provider; /** - * Interface to describe Template Information. + * Interface TemplateInformationProviderInterface. * - * Interface TemplateLocatorInterface. + * Interface to describe Template Information. * - * @package Namics\Terrific + * @package Namics\Terrific\Provider * @see Namics\Terrific\Twig\LoaderTerrificLoader */ interface TemplateInformationProviderInterface { @@ -16,6 +16,7 @@ interface TemplateInformationProviderInterface { * Returns a list of paths where templates might be stored. * * @return array + * The path. */ public function getPaths(); @@ -23,6 +24,7 @@ public function getPaths(); * Returns the template's file extension. * * @return string + * The file extension. */ public function getFileExtension(); diff --git a/src/Twig/Extension/TerrificExtension.php b/src/Twig/Extension/TerrificExtension.php index 1b3e1c2..265254a 100644 --- a/src/Twig/Extension/TerrificExtension.php +++ b/src/Twig/Extension/TerrificExtension.php @@ -15,8 +15,11 @@ * @package Namics\Terrific\Twig\Extension */ final class TerrificExtension extends AbstractExtension { + /** - * @var \Namics\Terrific\Provider\ContextProviderInterfaceContextVariableProvider + * The context provider. + * + * @var \Namics\Terrific\Provider\ContextProviderInterface */ private $ctxProvider; diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index e5e4606..7186801 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -22,10 +22,11 @@ * @package Namics\Terrific\Twig\Node */ final class ComponentNode extends Node implements NodeOutputInterface { + /** * The context provider. * - * @var \Namics\Terrific\Provider\ContextProviderInterfaceContextVariableProvider + * @var \Namics\Terrific\Provider\ContextProviderInterface */ private $ctxProvider; @@ -35,7 +36,7 @@ final class ComponentNode extends Node implements NodeOutputInterface { * Should not be accessed directly by methods. * Except the getter. * - * @var |Namics\Terrific\Twig\Utility\ExpressionHandler + * @var \Namics\Terrific\Twig\Utility\ExpressionHandler */ private $expressionHandler; @@ -177,6 +178,8 @@ protected function compileComponentName(TerrificCompilerInterface $terrificCompi * * Pointing to the value location of given expression in the context. * + * TODO: This method is never used. Check! + * * @param \Twig\Node\Expression\GetAttrExpression $expression * The expression to get the context array keys for. * @@ -187,6 +190,8 @@ protected function compileGetAttrExpressionComponentName(GetAttrExpression $expr if ($this->getExpressionHandler()->isNestedObject($expression)) { $dataVariableName = $expression->getNode('attribute')->getAttribute('value'); $childExpression = $expression->getNode('node'); + + // TODO: undefined method buildGetAttrExpressionArrayKeys(). Add DI if this method is necessary. $dataVariableArrayKeys = $this->buildGetAttrExpressionArrayKeys($childExpression); $dataExpressionArrayKeys = $dataVariableArrayKeys; diff --git a/src/Twig/TerrificCompiler.php b/src/Twig/TerrificCompiler.php index d586b55..74907ea 100644 --- a/src/Twig/TerrificCompiler.php +++ b/src/Twig/TerrificCompiler.php @@ -29,7 +29,7 @@ class TerrificCompiler implements TerrificCompilerInterface { * Should not be accessed directly by methods. * Except the getter. * - * @var |Namics\Terrific\Twig\Utility\ExpressionHandler + * @var \Namics\Terrific\Twig\Utility\ExpressionHandler */ private $expressionHandler; diff --git a/src/Twig/TokenParser/ComponentTokenParser.php b/src/Twig/TokenParser/ComponentTokenParser.php index 5189fcd..7090bc5 100644 --- a/src/Twig/TokenParser/ComponentTokenParser.php +++ b/src/Twig/TokenParser/ComponentTokenParser.php @@ -15,10 +15,11 @@ * @package Namics\Terrific\Twig\TokenParser */ final class ComponentTokenParser extends AbstractTokenParser { + /** * The context provider. * - * @var \Namics\Terrific\Provider\ContextProviderInterfaceContextVariableProvider + * @var \Namics\Terrific\Provider\ContextProviderInterface */ private $ctxProvider; diff --git a/src/Twig/Utility/ExpressionHandler.php b/src/Twig/Utility/ExpressionHandler.php index 266fc11..8d5c287 100644 --- a/src/Twig/Utility/ExpressionHandler.php +++ b/src/Twig/Utility/ExpressionHandler.php @@ -3,6 +3,7 @@ namespace Namics\Terrific\Twig\Utility; use Namics\Terrific\Twig\Data\VariableNameAndArrayKeysPair; +use Twig\Node\Node; use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\NameExpression; use Twig\Error\Error; @@ -29,13 +30,13 @@ public static function create(): self { /** * Generates a variable name and array key pair. * - * @param \Twig\Node\Expression\GetAttrExpression $expression + * @param \Twig\Node\Node $expression * The expression to get the pair for. * * @return \Namics\Terrific\Twig\Data\VariableNameAndArrayKeysPair * The pair. */ - public function buildGetAttrExpressionArrayKeyPair(GetAttrExpression $expression): VariableNameAndArrayKeysPair { + public function buildGetAttrExpressionArrayKeyPair(Node $expression): VariableNameAndArrayKeysPair { $arrayKeys = $this->buildGetAttrExpressionArrayKeys($expression); // Get the variable name, it's the first array item. @@ -52,13 +53,13 @@ public function buildGetAttrExpressionArrayKeyPair(GetAttrExpression $expression * * Pointing to the value location of given expression in the context. * - * @param \Twig\Node\Expression\GetAttrExpression $expression + * @param \Twig\Node\Node $expression * The expression to get the context array keys for. * * @return string[] * Array with array keys. */ - public function buildGetAttrExpressionArrayKeys(GetAttrExpression $expression): array { + public function buildGetAttrExpressionArrayKeys(Node $expression): array { if ($this->isNestedObject($expression)) { $dataVariableName = $expression->getNode('attribute')->getAttribute('value'); $childExpression = $expression->getNode('node'); @@ -82,14 +83,14 @@ public function buildGetAttrExpressionArrayKeys(GetAttrExpression $expression): /** * Checks if given Twig expression is a nested object. * - * @param \Twig\Node\Expression\GetAttrExpression $expression + * @param \Twig\Node\Node $expression * The Twig expression to check. * * @return bool * TRUE if nested object. * Otherwise FALSE. */ - public function isNestedObject(GetAttrExpression $expression): bool { + public function isNestedObject(Node $expression): bool { $isNestedObject = $expression->hasNode('node') && $expression->getNode('node') instanceof GetAttrExpression; return $isNestedObject; From d74f87f827be9224373cff570abeeb090dc76f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Simb=C3=BCrger?= Date: Thu, 23 Jul 2020 08:28:06 +0200 Subject: [PATCH 14/29] Update tests --- composer.json | 11 ++++- test/Twig/Extension/TerrificExtensionTest.php | 14 ++++-- test/Twig/Loader/TerrificLoaderTest.php | 9 +++- test/Twig/Node/ComponentTestNode.php | 48 ++++++++++++++----- .../TokenParser/ComponentTokenParserTest.php | 12 +++-- 5 files changed, 74 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index 3014fcb..7351cf1 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,13 @@ "name": "namics/twig-nitro-library", "description": "Extension to embrace the Terrific frontend methodology in Twig. Currently it adds a custom component tag to Twig which mimics Nitro's handlebars helper.", "type": "library", - "keywords": ["terrific", "nitro", "twig", "integration", "namics"], + "keywords": [ + "terrific", + "nitro", + "twig", + "integration", + "namics" + ], "homepage": "https://github.com/namics/twig-nitro-library", "license": "MIT", "authors": [ @@ -21,5 +27,8 @@ "require": { "php": ">=7.2", "twig/twig": "~1.24" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" } } diff --git a/test/Twig/Extension/TerrificExtensionTest.php b/test/Twig/Extension/TerrificExtensionTest.php index 659cb35..5d1d0f7 100644 --- a/test/Twig/Extension/TerrificExtensionTest.php +++ b/test/Twig/Extension/TerrificExtensionTest.php @@ -5,17 +5,23 @@ use Namics\Terrific\Provider\ContextProviderInterface; use Namics\Terrific\Twig\Extension\TerrificExtension; use Namics\Terrific\Twig\TokenParser\ComponentTokenParser; +use PHPUnit\Framework\TestCase; /** + * Class TerrificExtensionTest. * + * @package Namics\Test\Terrific\Twig\Extension + * @coversDefaultClass \Namics\Terrific\Twig\Extension\TerrificExtension */ -class TerrificExtensionTest extends \PHPUnit_Framework_TestCase { +class TerrificExtensionTest extends TestCase { /** - * + * Test extension name return value. */ public function testReturnsExtensionName() { + /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + /** @var \Namics\Terrific\Twig\Extension\TerrificExtension $ext */ $ext = new TerrificExtension($stub); $name = $ext->getName(); @@ -23,10 +29,12 @@ public function testReturnsExtensionName() { } /** - * + * Test component token parser. */ public function testReturnsComponentTokenParser() { + /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + /** @var \Namics\Terrific\Twig\Extension\TerrificExtension $ext */ $ext = new TerrificExtension($stub); $parsers = $ext->getTokenParsers(); diff --git a/test/Twig/Loader/TerrificLoaderTest.php b/test/Twig/Loader/TerrificLoaderTest.php index eaf81b0..0b7426c 100644 --- a/test/Twig/Loader/TerrificLoaderTest.php +++ b/test/Twig/Loader/TerrificLoaderTest.php @@ -5,14 +5,18 @@ use Namics\Terrific\Provider\TemplateInformationProviderInterface; use Namics\Terrific\Twig\Loader\TerrificLoader; use Exception; +use PHPUnit\Framework\TestCase; /** + * Class TerrificLoaderTest. * + * @package Namics\Test\Terrific\Twig\Loader + * @coversDefaultClass \Namics\Terrific\Twig\Loader\TerrificLoader */ -class TerrificLoaderTest extends \PHPUnit_Framework_TestCase { +class TerrificLoaderTest extends TestCase { /** - * + * Test the paths. */ public function testPaths() { $paths = [ @@ -32,6 +36,7 @@ public function testPaths() { ->method('getFileExtension') ->willReturn('html'); + /** @var \Namics\Terrific\Twig\Loader\TerrificLoader $loader */ $loader = new TerrificLoader($stub); $this->assertEquals($paths, $loader->getPaths()); diff --git a/test/Twig/Node/ComponentTestNode.php b/test/Twig/Node/ComponentTestNode.php index 9541ecf..9bd4e60 100644 --- a/test/Twig/Node/ComponentTestNode.php +++ b/test/Twig/Node/ComponentTestNode.php @@ -2,21 +2,29 @@ namespace Namics\Test\Terrific\Twig\Node; +use Namics\Terrific\Provider\ContextProviderInterface; use Namics\Terrific\Twig\Node\ComponentNode; use Twig_Node_Expression_Constant; use Twig_Node_Expression_Array; +use PHPUnit\Framework\TestCase; /** + * Class ComponentNodeTest. * + * @package Namics\Test\Terrific\Twig\Node + * @coversDefaultClass \Namics\Terrific\Twig\Node\ComponentNode */ -class ComponentNodeTest extends \Twig_Test_NodeTestCase { +class ComponentNodeTest extends TestCase { /** - * + * The the constructor. */ public function testContructor() { + /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ + $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, NULL, FALSE, 1); + $node = new ComponentNode($expr, $stub, NULL, TRUE, 0, NULL); $this->assertNull($node->getNode('data')); $this->assertEquals($expr, $node->getNode('component')); @@ -26,14 +34,14 @@ public function testContructor() { new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Constant(TRUE, 1), ], 1); - $node = new ComponentNode($expr, $data, TRUE, 1); + $node = new ComponentNode($expr, $stub, $data, TRUE, 0, NULL); $this->assertEquals($data, $node->getNode('data')); $this->assertTrue($node->getAttribute('only')); } /** - * + * Get the tests. */ public function getTests() { $tests = [ @@ -54,8 +62,11 @@ public function getTests() { * @return array */ private function getDefaultTest() { + /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ + $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, NULL, FALSE, 1); + $node = new ComponentNode($expr, $stub, NULL, TRUE, 0, NULL); return [ $node, @@ -72,8 +83,11 @@ private function getDefaultTest() { * @return array */ private function getDefaultOnlyTest() { + /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ + $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, NULL, TRUE, 1); + $node = new ComponentNode($expr, $stub, NULL, TRUE, 0, NULL); return [ $node, @@ -90,12 +104,15 @@ private function getDefaultOnlyTest() { * @return array */ private function getDataObjectTest() { + /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ + $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + $expr = new Twig_Node_Expression_Constant('Example', 1); $data = new Twig_Node_Expression_Array([ new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Constant('bar', 1), ], 1); - $node = new ComponentNode($expr, $data, FALSE, 1); + $node = new ComponentNode($expr, $stub, $data, TRUE, 0, NULL); return [ $node, @@ -112,12 +129,15 @@ private function getDataObjectTest() { * @return array */ private function getDataObjectOnlyTest() { + /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ + $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + $expr = new Twig_Node_Expression_Constant('Example', 1); $data = new Twig_Node_Expression_Array([ new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Constant('bar', 1), ], 1); - $node = new ComponentNode($expr, $data, TRUE, 1); + $node = new ComponentNode($expr, $stub, $data, TRUE, 0, NULL); return [ $node, @@ -134,9 +154,12 @@ private function getDataObjectOnlyTest() { * @return array */ private function getVariantTest() { + /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ + $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + $expr = new Twig_Node_Expression_Constant('Example', 1); $data = new Twig_Node_Expression_Constant('example-foo', 1); - $node = new ComponentNode($expr, $data, FALSE, 1); + $node = new ComponentNode($expr, $stub, $data, TRUE, 0, NULL); return [ $node, @@ -153,9 +176,12 @@ private function getVariantTest() { * @TODO */ private function getVariantOnlyTest() { + /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ + $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + $expr = new Twig_Node_Expression_Constant('Example', 1); $data = new Twig_Node_Expression_Constant('example-foo', 1); - $node = new ComponentNode($expr, $data, TRUE, 1); + $node = new ComponentNode($expr, $stub, $data, TRUE, 0, NULL); return [ $node, diff --git a/test/Twig/TokenParser/ComponentTokenParserTest.php b/test/Twig/TokenParser/ComponentTokenParserTest.php index a1b4d14..12d6f88 100644 --- a/test/Twig/TokenParser/ComponentTokenParserTest.php +++ b/test/Twig/TokenParser/ComponentTokenParserTest.php @@ -4,19 +4,25 @@ use Namics\Terrific\Provider\ContextProviderInterface; use Namics\Terrific\Twig\TokenParser\ComponentTokenParser; +use PHPUnit\Framework\TestCase; /** - * Class ComponentTokenParser. + * Class ComponentTokenParserTest. * * @TODO Maybe implement test for the Parser. + * + * @package Namics\Test\Terrific\Twig\TokenParser + * @coversDefaultClass \Namics\Terrific\Twig\TokenParser\ComponentTokenParser */ -class ComponentTokenParserTest extends \PHPUnit_Framework_TestCase { +class ComponentTokenParserTest extends TestCase { /** - * + * Test the tag. */ public function testGetTag() { + /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + /** @var \Namics\Terrific\Twig\TokenParser\ComponentTokenParser $parser */ $parser = new ComponentTokenParser($stub); $this->assertEquals('component', $parser->getTag()); } From 89177e4610bfc475b99716c041d7af5c9e2f917d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Simb=C3=BCrger?= Date: Thu, 23 Jul 2020 09:17:23 +0200 Subject: [PATCH 15/29] Replace deprecated methods --- src/Twig/Node/ComponentNode.php | 6 +++--- src/Twig/TerrificCompiler.php | 10 +++++----- test/Twig/Loader/TerrificLoaderTest.php | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index 7186801..4341690 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -105,7 +105,7 @@ public function compile(Compiler $compiler) { */ protected function createTerrificContext(TerrificCompilerInterface $terrificCompiler) { $terrificCompiler->getTwigCompiler() - ->addIndentation() + ->write('') ->raw(ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ' = $context;') ->raw("\n"); @@ -134,9 +134,9 @@ protected function addGetTemplate(TerrificCompilerInterface $terrificCompiler) { $terrificCompiler->getTwigCompiler() ->raw(', ') - ->repr($terrificCompiler->getTwigCompiler()->getFilename()) + ->repr($this->getTemplateName()) ->raw(', ') - ->repr($this->getLine()) + ->repr($this->getTemplateLine()) ->raw(')'); } diff --git a/src/Twig/TerrificCompiler.php b/src/Twig/TerrificCompiler.php index 74907ea..d10b59e 100644 --- a/src/Twig/TerrificCompiler.php +++ b/src/Twig/TerrificCompiler.php @@ -149,7 +149,7 @@ public function compileAndMergeVariableToContext(VariableNameAndArrayKeysPair $v } $this->getTwigCompiler() - ->raw("\n")->addIndentation() + ->raw("\n")->write('') ->raw('if (') ->raw('isset('); @@ -158,20 +158,20 @@ public function compileAndMergeVariableToContext(VariableNameAndArrayKeysPair $v $this->getTwigCompiler() ->raw(')') ->raw(') {') - ->raw("\n")->addIndentation()->addIndentation() + ->raw("\n")->write('')->write('') ->raw(ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ' = array_merge(' . ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ', '); $this->compileAsContextVariable($variableNameAndArrayKeysPair, '$context'); $this->getTwigCompiler() ->raw(');') - ->raw("\n")->addIndentation() + ->raw("\n")->write('') ->raw('} else {') - ->raw("\n")->addIndentation()->addIndentation() + ->raw("\n")->write('')->write('') ->raw('throw new \Twig\Error\Error("') ->raw($variableDoesNotExistErrorMessage) ->raw('");') - ->raw("\n")->addIndentation() + ->raw("\n")->write('') ->raw('}') ->raw("\n\n"); } diff --git a/test/Twig/Loader/TerrificLoaderTest.php b/test/Twig/Loader/TerrificLoaderTest.php index 0b7426c..ae7d5cf 100644 --- a/test/Twig/Loader/TerrificLoaderTest.php +++ b/test/Twig/Loader/TerrificLoaderTest.php @@ -24,6 +24,7 @@ public function testPaths() { __DIR__ . '/Fixtures/molecules', ]; + /** @var \Namics\Terrific\Provider\TemplateInformationProviderInterface|\PHPUnit\Framework\MockObject\MockObject $stub */ $stub = $this ->getMockBuilder(TemplateInformationProviderInterface::class) ->getMock(); From 54a4df9cf12e474f0e3375f26bd8a7076a6d8849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Simb=C3=BCrger?= Date: Thu, 23 Jul 2020 14:11:17 +0200 Subject: [PATCH 16/29] Refactoring --- src/Config/ConfigReader.php | 54 ++++++++++++------- .../TemplateInformationProviderInterface.php | 4 +- .../Data/VariableNameAndArrayKeysPair.php | 4 +- src/Twig/Extension/TerrificExtension.php | 4 +- src/Twig/Node/ComponentNode.php | 53 +++++++++++------- src/Twig/TerrificCompiler.php | 9 ++-- src/Twig/TokenParser/ComponentTokenParser.php | 15 ++++-- 7 files changed, 90 insertions(+), 53 deletions(-) diff --git a/src/Config/ConfigReader.php b/src/Config/ConfigReader.php index 29954f1..77709b3 100644 --- a/src/Config/ConfigReader.php +++ b/src/Config/ConfigReader.php @@ -5,24 +5,24 @@ use DomainException; /** - * ConfigReader reads Terrific Nitro's config.json and parses essential information such as component paths and the view - * file extension. - * * Class ConfigReader. * + * Reads Terrific Nitro's config.json and parses essential information such as component paths and the view + * file extension. + * * @package Namics\Terrific\Config */ final class ConfigReader { /** - * The Configuration's Filename. + * The Terrific Nitro's config file. * * @var string */ - const FILE_NAME = 'config.json'; + const TERRIFIC_NITRO_FILE_NAME = 'config.json'; /** - * The config. + * The Terrific Nitro's config. * * @var array */ @@ -32,19 +32,45 @@ final class ConfigReader { * ConfigReader constructor. * * @param string $terrificDir - * Path to Terrific's Frontend Directory. + * Path to Terrific's frontend directory. * * @throws DomainException Thrown if Configuration could not be loaded correctly. */ public function __construct($terrificDir) { - $path = $terrificDir . '/' . self::FILE_NAME; + $path = $terrificDir . '/' . self::TERRIFIC_NITRO_FILE_NAME; + $this->readConfig($path); + } + + /** + * @deprecated Use getConfig() instead. + */ + public function read(): array { + return $this->getConfig(); + } + + /** + * Returns Terrific Nitro's config. + * + * @return array + * Terrific Nitro config. + */ + public function getConfig(): array { + return $this->config; + } + /** + * Read Terrific's config. + * + * @param string $path + * The path to the config file. + */ + protected function readConfig(string $path) { if (is_readable($path)) { try { $this->config = json_decode(file_get_contents($path), TRUE); if ($this->config === NULL && json_last_error() !== JSON_ERROR_NONE) { - throw new DomainException('Terrific Config could not be parsed.'); + throw new DomainException('Terrific config could not be parsed.'); } } catch (DomainException $e) { @@ -53,14 +79,4 @@ public function __construct($terrificDir) { } } - /** - * Returns the Configuration. - * - * @return array - * The config. - */ - public function read() { - return $this->config; - } - } diff --git a/src/Provider/TemplateInformationProviderInterface.php b/src/Provider/TemplateInformationProviderInterface.php index e4ed9df..ec16aaf 100644 --- a/src/Provider/TemplateInformationProviderInterface.php +++ b/src/Provider/TemplateInformationProviderInterface.php @@ -18,7 +18,7 @@ interface TemplateInformationProviderInterface { * @return array * The path. */ - public function getPaths(); + public function getPaths(): array; /** * Returns the template's file extension. @@ -26,6 +26,6 @@ public function getPaths(); * @return string * The file extension. */ - public function getFileExtension(); + public function getFileExtension(): string; } diff --git a/src/Twig/Data/VariableNameAndArrayKeysPair.php b/src/Twig/Data/VariableNameAndArrayKeysPair.php index 66feb92..e2f9d29 100644 --- a/src/Twig/Data/VariableNameAndArrayKeysPair.php +++ b/src/Twig/Data/VariableNameAndArrayKeysPair.php @@ -48,7 +48,7 @@ protected function __construct(string $variableName, array $arrayKeys = []) { * @param array $arrayKeys * Array of array keys for variable. */ - public static function create(string $variableName, array $arrayKeys) { + public static function create(string $variableName, array $arrayKeys): self { return new self($variableName, $arrayKeys); } @@ -58,7 +58,7 @@ public static function create(string $variableName, array $arrayKeys) { * @param string $variableName * The variable name. */ - public static function createVariable(string $variableName) { + public static function createVariable(string $variableName): self { return new self($variableName); } diff --git a/src/Twig/Extension/TerrificExtension.php b/src/Twig/Extension/TerrificExtension.php index 265254a..c29d1d7 100644 --- a/src/Twig/Extension/TerrificExtension.php +++ b/src/Twig/Extension/TerrificExtension.php @@ -37,14 +37,14 @@ public function __construct(ContextProviderInterface $ctxProvider) { /** * {@inheritdoc} */ - public function getName() { + public function getName(): string { return 'terrific'; } /** * {@inheritdoc} */ - public function getTokenParsers() { + public function getTokenParsers(): array { return [ new ComponentTokenParser($this->ctxProvider), ]; diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index 4341690..7f6ad5f 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -62,7 +62,8 @@ public function __construct( Node $data = NULL, $only = FALSE, $lineno, - $tag = NULL) { + $tag = NULL + ) { parent::__construct( ['component' => $component, 'data' => $data], ['only' => (bool) $only], @@ -79,10 +80,14 @@ public function __construct( * @param \Twig\Compiler $compiler * The Twig compiler. */ - public function compile(Compiler $compiler) { + public function compile(Compiler $compiler): void { + /** @var \Namics\Terrific\Twig\TerrificCompiler $terrificCompiler */ $terrificCompiler = TerrificCompiler::create($compiler); - $terrificCompiler->getTwigCompiler()->addDebugInfo($this); + /** @var \Twig\Compiler $twigCompiler */ + $twigCompiler = $terrificCompiler->getTwigCompiler(); + + $twigCompiler->addDebugInfo($this); // Create data. $this->createTerrificContext($terrificCompiler); @@ -90,11 +95,11 @@ public function compile(Compiler $compiler) { // Load component template. $this->addGetTemplate($terrificCompiler); - $terrificCompiler->getTwigCompiler() + $twigCompiler ->raw('->display(' . ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ');') ->raw("\n\n"); - $terrificCompiler->getTwigCompiler()->addDebugInfo($this->getNode('component')); + $twigCompiler->addDebugInfo($this->getNode('component')); } /** @@ -103,17 +108,20 @@ public function compile(Compiler $compiler) { * @param \Namics\Terrific\Twig\TerrificCompilerInterface $terrificCompiler * The Terrific Twig compiler. */ - protected function createTerrificContext(TerrificCompilerInterface $terrificCompiler) { - $terrificCompiler->getTwigCompiler() + protected function createTerrificContext(TerrificCompilerInterface $terrificCompiler): void { + /** @var \Twig\Compiler $twigCompiler */ + $twigCompiler = $terrificCompiler->getTwigCompiler(); + + $twigCompiler ->write('') ->raw(ContextProviderInterface::TERRIFIC_CONTEXT_VARIABLE . ' = $context;') ->raw("\n"); $this->ctxProvider->compile( - $terrificCompiler->getTwigCompiler(), - $this->getNode('component'), - $this->getNode('data'), - $this->getAttribute('only') + $twigCompiler, + $this->getNode('component'), + $this->getNode('data'), + $this->getAttribute('only') ); } @@ -127,12 +135,15 @@ protected function createTerrificContext(TerrificCompilerInterface $terrificComp * @param \Namics\Terrific\Twig\TerrificCompilerInterface $terrificCompiler * The Terrific Twig compiler. */ - protected function addGetTemplate(TerrificCompilerInterface $terrificCompiler) { - $terrificCompiler->getTwigCompiler()->write('$this->loadTemplate('); + protected function addGetTemplate(TerrificCompilerInterface $terrificCompiler): void { + /** @var \Twig\Compiler $twigCompiler */ + $twigCompiler = $terrificCompiler->getTwigCompiler(); + + $twigCompiler->write('$this->loadTemplate('); $this->compileComponentName($terrificCompiler); - $terrificCompiler->getTwigCompiler() + $twigCompiler ->raw(', ') ->repr($this->getTemplateName()) ->raw(', ') @@ -150,23 +161,25 @@ protected function addGetTemplate(TerrificCompilerInterface $terrificCompiler) { * If the node name uses an unsupported format. */ protected function compileComponentName(TerrificCompilerInterface $terrificCompiler): void { + $node = $this->getNode('component'); + /* If a variable is used for component name, use it's value (is inside Terrifc context "$tContext") for template name. E.g. {% component myTwigComponentName { dataItem: 'my string' } %} */ - if ($this->getNode('component') instanceof NameExpression) { - $terrificCompiler->compileNameExpressionAsContextVariable($this->getNode('component')); + if ($node instanceof NameExpression) { + $terrificCompiler->compileNameExpressionAsContextVariable($node); } /* If a static string (constant) is used for component name, compile it (prints it). E.g. {% component 'myComponent' { dataItem: 'my string' } %} */ - elseif ($this->getNode('component') instanceof ConstantExpression) { - $terrificCompiler->getTwigCompiler()->subcompile($this->getNode('component')); + elseif ($node instanceof ConstantExpression) { + $terrificCompiler->getTwigCompiler()->subcompile($node); } /* If an object object/array used for component name, use it's value (is inside Terrifc context "$tContext") for template name. E.g. {% component myTwigObject.anObjectProperty.myTwigComponentName { dataItem: 'my string' } %} */ - elseif ($this->getNode('component') instanceof GetAttrExpression) { - $terrificCompiler->compileGetAttrExpressionAsContextVariable($this->getNode('component')); + elseif ($node instanceof GetAttrExpression) { + $terrificCompiler->compileGetAttrExpressionAsContextVariable($node); } else { throw new SyntaxError('An unsupported data type was used for name to call a Twig Nitro component.'); diff --git a/src/Twig/TerrificCompiler.php b/src/Twig/TerrificCompiler.php index d10b59e..87758b9 100644 --- a/src/Twig/TerrificCompiler.php +++ b/src/Twig/TerrificCompiler.php @@ -142,20 +142,23 @@ public function compileAndMergeGetAttrExpressionToContext(GetAttrExpression $exp * when the given variable does not exist. */ public function compileAndMergeVariableToContext(VariableNameAndArrayKeysPair $variableNameAndArrayKeysPair, ?string $variableDoesNotExistErrorMessage = NULL): void { + /** @var \Twig\Compiler $twigCompiler */ + $twigCompiler = $this->getTwigCompiler(); + $contextArrayKeys = $variableNameAndArrayKeysPair->toTwigContextArrayKeysString(); if ($variableDoesNotExistErrorMessage === NULL) { $variableDoesNotExistErrorMessage = addslashes('The variable ' . $variableNameAndArrayKeysPair->toTwigVariableString() . ' does not exist. Could not compile it to the Twig context.'); } - $this->getTwigCompiler() + $twigCompiler ->raw("\n")->write('') ->raw('if (') ->raw('isset('); $this->compileAsContextVariable($variableNameAndArrayKeysPair, '$context'); - $this->getTwigCompiler() + $twigCompiler ->raw(')') ->raw(') {') ->raw("\n")->write('')->write('') @@ -163,7 +166,7 @@ public function compileAndMergeVariableToContext(VariableNameAndArrayKeysPair $v $this->compileAsContextVariable($variableNameAndArrayKeysPair, '$context'); - $this->getTwigCompiler() + $twigCompiler ->raw(');') ->raw("\n")->write('') ->raw('} else {') diff --git a/src/Twig/TokenParser/ComponentTokenParser.php b/src/Twig/TokenParser/ComponentTokenParser.php index 7090bc5..a5d3846 100644 --- a/src/Twig/TokenParser/ComponentTokenParser.php +++ b/src/Twig/TokenParser/ComponentTokenParser.php @@ -36,7 +36,8 @@ public function __construct(ContextProviderInterface $ctxProvider) { /** * {@inheritdoc} */ - public function parse(Token $token) { + public function parse(Token $token): ComponentNode { + /** @var \Twig\Node\Expression\ConstantExpression $component */ $component = $this->parser->getExpressionParser()->parseExpression(); list($data, $only) = $this->parseArguments(); @@ -49,18 +50,21 @@ public function parse(Token $token) { * @return array * Array containing data and only flag. */ - protected function parseArguments() { - $stream = $this->parser->getStream(); - + protected function parseArguments(): array { $data = NULL; $only = FALSE; + /** @var \Twig\TokenStream $stream */ + $stream = $this->parser->getStream(); + + // BLOCK_END_TYPE = delimiter for blocks. if ($stream->test(Token::BLOCK_END_TYPE)) { $stream->expect(Token::BLOCK_END_TYPE); return [$data, $only]; } + // NAME_TYPE = name expression. if ($stream->test(Token::NAME_TYPE, 'only')) { $only = TRUE; $stream->next(); @@ -69,6 +73,7 @@ protected function parseArguments() { return [$data, $only]; } + /** @var \Twig\Node\Expression\ArrayExpression $data */ $data = $this->parser->getExpressionParser()->parseExpression(); if ($stream->test(Token::NAME_TYPE, 'only')) { @@ -84,7 +89,7 @@ protected function parseArguments() { /** * {@inheritdoc} */ - public function getTag() { + public function getTag(): string { return 'component'; } From e98c3cf43ed359cd85bbb530d92e4d09b1a472ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Simb=C3=BCrger?= Date: Thu, 23 Jul 2020 16:27:14 +0200 Subject: [PATCH 17/29] Refactor tests --- src/Provider/ContextProviderInterface.php | 2 +- src/Twig/Node/ComponentNode.php | 1 - test/Twig/Extension/TerrificExtensionTest.php | 34 +++--- test/Twig/Loader/TerrificLoaderTest.php | 79 ++++++++----- ...nentTestNode.php => ComponentNodeTest.php} | 104 ++++++++---------- .../TokenParser/ComponentTokenParserTest.php | 14 +-- test/Twig/TwigTestBase.php | 47 ++++++++ 7 files changed, 169 insertions(+), 112 deletions(-) rename test/Twig/Node/{ComponentTestNode.php => ComponentNodeTest.php} (58%) create mode 100644 test/Twig/TwigTestBase.php diff --git a/src/Provider/ContextProviderInterface.php b/src/Provider/ContextProviderInterface.php index 165f5ad..ed9fb85 100644 --- a/src/Provider/ContextProviderInterface.php +++ b/src/Provider/ContextProviderInterface.php @@ -35,6 +35,6 @@ interface ContextProviderInterface { * * @return mixed */ - public function compile(Compiler $compiler, Node $component, Node $dataVariant, bool $only); + public function compile(Compiler $compiler, Node $component, Node $dataVariant = NULL, bool $only); } diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index 7f6ad5f..484ef63 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -81,7 +81,6 @@ public function __construct( * The Twig compiler. */ public function compile(Compiler $compiler): void { - /** @var \Namics\Terrific\Twig\TerrificCompiler $terrificCompiler */ $terrificCompiler = TerrificCompiler::create($compiler); /** @var \Twig\Compiler $twigCompiler */ diff --git a/test/Twig/Extension/TerrificExtensionTest.php b/test/Twig/Extension/TerrificExtensionTest.php index 5d1d0f7..e8c89ea 100644 --- a/test/Twig/Extension/TerrificExtensionTest.php +++ b/test/Twig/Extension/TerrificExtensionTest.php @@ -5,7 +5,7 @@ use Namics\Terrific\Provider\ContextProviderInterface; use Namics\Terrific\Twig\Extension\TerrificExtension; use Namics\Terrific\Twig\TokenParser\ComponentTokenParser; -use PHPUnit\Framework\TestCase; +use Namics\Test\Terrific\Twig\TwigTestBase; /** * Class TerrificExtensionTest. @@ -13,32 +13,30 @@ * @package Namics\Test\Terrific\Twig\Extension * @coversDefaultClass \Namics\Terrific\Twig\Extension\TerrificExtension */ -class TerrificExtensionTest extends TestCase { +class TerrificExtensionTest extends TwigTestBase { /** * Test extension name return value. + * + * @covers ::getName */ - public function testReturnsExtensionName() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - /** @var \Namics\Terrific\Twig\Extension\TerrificExtension $ext */ - $ext = new TerrificExtension($stub); - $name = $ext->getName(); - - $this->assertEquals('terrific', $name); + public function testGetName(): void { + /** @var \Namics\Terrific\Twig\Extension\TerrificExtension $twigExtension */ + $twigExtension = $this->getTwigExtension(); + + $this->assertEquals('terrific', $twigExtension->getName(), 'Returned Twig extension name is not identical.'); } /** * Test component token parser. + * + * @covers ::getTokenParsers */ - public function testReturnsComponentTokenParser() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - /** @var \Namics\Terrific\Twig\Extension\TerrificExtension $ext */ - $ext = new TerrificExtension($stub); - $parsers = $ext->getTokenParsers(); - - $this->assertContainsOnly(ComponentTokenParser::class, $parsers); + public function testGetTokenParsers(): void { + /** @var \Namics\Terrific\Twig\Extension\TerrificExtension $twigExtension */ + $twigExtension = $this->getTwigExtension(); + + $this->assertContainsOnlyInstancesOf(ComponentTokenParser::class, $twigExtension->getTokenParsers(), 'Token parser does not include only class ComponentTokenParser.'); } } diff --git a/test/Twig/Loader/TerrificLoaderTest.php b/test/Twig/Loader/TerrificLoaderTest.php index ae7d5cf..5d2fbe2 100644 --- a/test/Twig/Loader/TerrificLoaderTest.php +++ b/test/Twig/Loader/TerrificLoaderTest.php @@ -5,6 +5,7 @@ use Namics\Terrific\Provider\TemplateInformationProviderInterface; use Namics\Terrific\Twig\Loader\TerrificLoader; use Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; /** @@ -16,57 +17,77 @@ class TerrificLoaderTest extends TestCase { /** - * Test the paths. + * The file paths. + * + * @var array */ - public function testPaths() { - $paths = [ - __DIR__ . '/Fixtures/atoms', - __DIR__ . '/Fixtures/molecules', - ]; + protected const PATHS = [ + __DIR__ . '/Fixtures/atoms', + __DIR__ . '/Fixtures/molecules', + ]; - /** @var \Namics\Terrific\Provider\TemplateInformationProviderInterface|\PHPUnit\Framework\MockObject\MockObject $stub */ - $stub = $this - ->getMockBuilder(TemplateInformationProviderInterface::class) + /** + * Get the template information provider mock. + * + * @return \Namics\Terrific\Provider\TemplateInformationProviderInterface + * The template information provider mock. + */ + protected function getTemplateInformationProviderMock() { + /** @var \Namics\Terrific\Provider\TemplateInformationProviderInterface|\PHPUnit\Framework\MockObject\MockObject $provider */ + $provider = $this->getMockBuilder(TemplateInformationProviderInterface::class) ->getMock(); + $provider->method('getPaths')->willReturn(self::PATHS); + $provider->method('getFileExtension')->willReturn('html'); - $stub - ->method('getPaths') - ->willReturn($paths); + return $provider; + } - $stub - ->method('getFileExtension') - ->willReturn('html'); + /** + * Get the terrific loader. + * + * @return \Namics\Terrific\Twig\Loader\TerrificLoader + */ + protected function getTerrificLoader(): TerrificLoader { + return new TerrificLoader($this->getTemplateInformationProviderMock()); + } + /** + * Test the paths. + */ + public function testPaths(): void { /** @var \Namics\Terrific\Twig\Loader\TerrificLoader $loader */ - $loader = new TerrificLoader($stub); + $loader = $this->getTerrificLoader(); - $this->assertEquals($paths, $loader->getPaths()); + $this->assertEquals(self::PATHS, $loader->getPaths(), 'Paths are not identical.'); - $source = $loader->getSource('molecule'); - $this->assertEquals("--molecule_content--\n", $source); + /** @var \Twig\Source $source */ + $source = $loader->getSourceContext('molecule'); + $this->assertEquals("--molecule_content--\n", $source->getCode(), 'Molecule content is not identical.'); - $source = $loader->getSource('atom'); - $this->assertEquals("--atom_content--\n", $source); + /** @var \Twig\Source $source */ + $source = $loader->getSourceContext('atom'); + $this->assertEquals("--atom_content--\n", $source->getCode(), 'Atom content is not identical.'); // Check if the cache is invoked. - $source = $loader->getSource('atom'); - $this->assertEquals("--atom_content--\n", $source); + /** @var \Twig\Source $source */ + $source = $loader->getSourceContext('atom'); + $this->assertEquals("--atom_content--\n", $source->getCode(), 'Cache for atom content was not invoked.'); try { - $loader->getSource('fake-component'); + $loader->getSourceContext('fake-component'); } catch (Exception $e) { - $this->assertInstanceOf('Twig_Error_Loader', $e); - $this->assertContains('Unable to find component "fake-component"', $e->getMessage()); + $this->assertInstanceOf('Twig_Error_Loader', $e, 'Exception "Twig_Error_Loader" was not thrown.'); + $this->assertContains('Unable to find component "fake-component"', $e->getMessage(), 'Exception message is not identical.'); } // Check if the cache is invoked. try { - $loader->getSource('fake-component'); + $loader->getSourceContext('fake-component'); } catch (Exception $e) { - $this->assertInstanceOf('Twig_Error_Loader', $e); - $this->assertContains('Unable to find component "fake-component"', $e->getMessage()); + $this->assertInstanceOf('Twig_Error_Loader', $e, 'Exception "Twig_Error_Loader" was not thrown.'); + $this->assertContains('Unable to find component "fake-component"', $e->getMessage(), 'Exception message is not identical.'); } } diff --git a/test/Twig/Node/ComponentTestNode.php b/test/Twig/Node/ComponentNodeTest.php similarity index 58% rename from test/Twig/Node/ComponentTestNode.php rename to test/Twig/Node/ComponentNodeTest.php index 9bd4e60..64ea437 100644 --- a/test/Twig/Node/ComponentTestNode.php +++ b/test/Twig/Node/ComponentNodeTest.php @@ -2,11 +2,14 @@ namespace Namics\Test\Terrific\Twig\Node; +use Drupal\Component\Uuid\Com; use Namics\Terrific\Provider\ContextProviderInterface; use Namics\Terrific\Twig\Node\ComponentNode; +use Namics\Test\Terrific\Twig\TwigTestBase; +use Twig\Compiler; +use Twig\Environment; use Twig_Node_Expression_Constant; use Twig_Node_Expression_Array; -use PHPUnit\Framework\TestCase; /** * Class ComponentNodeTest. @@ -14,37 +17,40 @@ * @package Namics\Test\Terrific\Twig\Node * @coversDefaultClass \Namics\Terrific\Twig\Node\ComponentNode */ -class ComponentNodeTest extends TestCase { +class ComponentNodeTest extends TwigTestBase { /** * The the constructor. + * + * @covers ::__construct */ public function testContructor() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); + /** @var \Namics\Terrific\Provider\ContextProviderInterface $ctxProvider */ + $ctxProvider = $this->getContextProviderMock(); - $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, $stub, NULL, TRUE, 0, NULL); + $expression = new Twig_Node_Expression_Constant('Example', 1); + $node = new ComponentNode($expression, $ctxProvider, NULL, FALSE, 0, NULL); - $this->assertNull($node->getNode('data')); - $this->assertEquals($expr, $node->getNode('component')); - $this->assertFalse($node->getAttribute('only')); + $this->assertNull($node->getNode('data'), 'The node data is not Null.'); + $this->assertEquals($expression, $node->getNode('component'), 'The expressions are not identical.'); + $this->assertFalse($node->getAttribute('only'), 'The "only" attribute is not FALSE.'); $data = new Twig_Node_Expression_Array([ new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Constant(TRUE, 1), ], 1); - $node = new ComponentNode($expr, $stub, $data, TRUE, 0, NULL); - $this->assertEquals($data, $node->getNode('data')); - $this->assertTrue($node->getAttribute('only')); + $node = new ComponentNode($expression, $ctxProvider, $data, TRUE, 1, NULL); + + $this->assertEquals($data, $node->getNode('data'), 'The data is not identical.'); + $this->assertTrue($node->getAttribute('only'), 'The "only" attribute is not TRUE.'); + + $this->getDefaultTest(); } - /** - * Get the tests. - */ - public function getTests() { - $tests = [ + public function testComponentVariants() { + // TODO: test it with a data provider. + $expectedResults = [ $this->getDefaultTest(), $this->getDefaultOnlyTest(), $this->getDataObjectTest(), @@ -53,7 +59,7 @@ public function getTests() { $this->getVariantOnlyTest(), ]; - return $tests; + // TODO: Write tests. } /** @@ -62,17 +68,17 @@ public function getTests() { * @return array */ private function getDefaultTest() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, $stub, NULL, TRUE, 0, NULL); + $node = new ComponentNode($expr, $this->getContextProviderMock(), NULL, FALSE, 1, NULL); return [ - $node, - << $node, + 'source' => <<loadTemplate("Example", null, 1)->display(\$context); +\$tContext = \$context; +\$this->loadTemplate("Example", null, 1)->display(\$tContext); + + EOF ]; } @@ -83,17 +89,16 @@ private function getDefaultTest() { * @return array */ private function getDefaultOnlyTest() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, $stub, NULL, TRUE, 0, NULL); + $node = new ComponentNode($expr, $this->getContextProviderMock(), NULL, TRUE, 1, NULL); return [ - $node, - << $node, + 'source' => <<loadTemplate("Example", null, 1)->display([]); + + EOF ]; } @@ -104,19 +109,16 @@ private function getDefaultOnlyTest() { * @return array */ private function getDataObjectTest() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - $expr = new Twig_Node_Expression_Constant('Example', 1); $data = new Twig_Node_Expression_Array([ new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Constant('bar', 1), ], 1); - $node = new ComponentNode($expr, $stub, $data, TRUE, 0, NULL); + $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, FALSE, 1, NULL); return [ - $node, - << $node, + 'source' => <<loadTemplate("Example", null, 1)->display(array_merge(\$context, array("foo" => "bar"))); EOF @@ -129,19 +131,16 @@ private function getDataObjectTest() { * @return array */ private function getDataObjectOnlyTest() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - $expr = new Twig_Node_Expression_Constant('Example', 1); $data = new Twig_Node_Expression_Array([ new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Constant('bar', 1), ], 1); - $node = new ComponentNode($expr, $stub, $data, TRUE, 0, NULL); + $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, TRUE, 1, NULL); return [ - $node, - << $node, + 'source' => <<loadTemplate("Example", null, 1)->display(array("foo" => "bar")); EOF @@ -154,16 +153,13 @@ private function getDataObjectOnlyTest() { * @return array */ private function getVariantTest() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - $expr = new Twig_Node_Expression_Constant('Example', 1); $data = new Twig_Node_Expression_Constant('example-foo', 1); - $node = new ComponentNode($expr, $stub, $data, TRUE, 0, NULL); + $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, FALSE, 1, NULL); return [ - $node, - << $node, + 'source' => <<loadTemplate("Example", null, 1)->display(array_merge(\$context, array("data_source" => "example-foo"))); EOF @@ -173,19 +169,15 @@ private function getVariantTest() { /** * Tests the following tag: {% component 'Example' 'example-variant' only %}. * - * @TODO */ private function getVariantOnlyTest() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); - $expr = new Twig_Node_Expression_Constant('Example', 1); $data = new Twig_Node_Expression_Constant('example-foo', 1); - $node = new ComponentNode($expr, $stub, $data, TRUE, 0, NULL); + $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, TRUE, 1, NULL); return [ - $node, - << $node, + 'source' => <<loadTemplate("Example", null, 1)->display(array("data_source" => "example-foo")); EOF diff --git a/test/Twig/TokenParser/ComponentTokenParserTest.php b/test/Twig/TokenParser/ComponentTokenParserTest.php index 12d6f88..24d8a20 100644 --- a/test/Twig/TokenParser/ComponentTokenParserTest.php +++ b/test/Twig/TokenParser/ComponentTokenParserTest.php @@ -4,29 +4,28 @@ use Namics\Terrific\Provider\ContextProviderInterface; use Namics\Terrific\Twig\TokenParser\ComponentTokenParser; -use PHPUnit\Framework\TestCase; +use Namics\Test\Terrific\Twig\TwigTestBase; /** * Class ComponentTokenParserTest. * - * @TODO Maybe implement test for the Parser. - * * @package Namics\Test\Terrific\Twig\TokenParser * @coversDefaultClass \Namics\Terrific\Twig\TokenParser\ComponentTokenParser */ -class ComponentTokenParserTest extends TestCase { +class ComponentTokenParserTest extends TwigTestBase { /** * Test the tag. + * + * @covers ::getTag */ public function testGetTag() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $stub */ - $stub = $this->getMockBuilder(ContextProviderInterface::class)->getMock(); /** @var \Namics\Terrific\Twig\TokenParser\ComponentTokenParser $parser */ - $parser = new ComponentTokenParser($stub); + $parser = $this->getComponentTokenParser(); $this->assertEquals('component', $parser->getTag()); } + // TODO: write test. // Public function testParse() // { // $tokenParser = new ComponentTokenParser(); @@ -36,4 +35,5 @@ public function testGetTag() { // $tokenParser->setParser($parser); // $this->assertInstanceOf(ComponentNode::class, $tokenParser->parse($token)); // } + } diff --git a/test/Twig/TwigTestBase.php b/test/Twig/TwigTestBase.php new file mode 100644 index 0000000..baa15e4 --- /dev/null +++ b/test/Twig/TwigTestBase.php @@ -0,0 +1,47 @@ +getMockBuilder(ContextProviderInterface::class)->getMock();; + } + + /** + * Get the Terrific Twig extension. + * + * @return \Namics\Terrific\Twig\Extension\TerrificExtension + * The Terrific Twig extension. + */ + protected function getTwigExtension(): TerrificExtension { + return new TerrificExtension($this->getContextProviderMock()); + } + + /** + * Get the component token parser. + * + * @return \Namics\Terrific\Twig\TokenParser\ComponentTokenParser + * The component token parser. + */ + protected function getComponentTokenParser(): ComponentTokenParser { + return new ComponentTokenParser($this->getContextProviderMock()); + } + +} From a503d025bc476323c14c62c71d3261b74c257b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Simb=C3=BCrger?= Date: Fri, 24 Jul 2020 14:45:55 +0200 Subject: [PATCH 18/29] Remove deprecation --- src/Provider/ContextProviderInterface.php | 2 +- src/Twig/Node/ComponentNode.php | 2 +- test/Twig/Node/ComponentNodeTest.php | 13 +++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Provider/ContextProviderInterface.php b/src/Provider/ContextProviderInterface.php index ed9fb85..165f5ad 100644 --- a/src/Provider/ContextProviderInterface.php +++ b/src/Provider/ContextProviderInterface.php @@ -35,6 +35,6 @@ interface ContextProviderInterface { * * @return mixed */ - public function compile(Compiler $compiler, Node $component, Node $dataVariant = NULL, bool $only); + public function compile(Compiler $compiler, Node $component, Node $dataVariant, bool $only); } diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index 484ef63..3bb457e 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -59,7 +59,7 @@ final class ComponentNode extends Node implements NodeOutputInterface { public function __construct( Node $component, ContextProviderInterface $ctxProvider, - Node $data = NULL, + Node $data, $only = FALSE, $lineno, $tag = NULL diff --git a/test/Twig/Node/ComponentNodeTest.php b/test/Twig/Node/ComponentNodeTest.php index 64ea437..5f3f07f 100644 --- a/test/Twig/Node/ComponentNodeTest.php +++ b/test/Twig/Node/ComponentNodeTest.php @@ -29,9 +29,12 @@ public function testContructor() { $ctxProvider = $this->getContextProviderMock(); $expression = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expression, $ctxProvider, NULL, FALSE, 0, NULL); + $data = new Twig_Node_Expression_Array([], 1); + $node = new ComponentNode($expression, $ctxProvider, $data, FALSE, 0, NULL); - $this->assertNull($node->getNode('data'), 'The node data is not Null.'); + /** @var \Twig\Node\Expression\ArrayExpression $nodeData */ + $nodeData = $node->getNode('data'); + $this->assertEquals($nodeData->count(), 0, 'The node data is not empty.'); $this->assertEquals($expression, $node->getNode('component'), 'The expressions are not identical.'); $this->assertFalse($node->getAttribute('only'), 'The "only" attribute is not FALSE.'); @@ -69,7 +72,8 @@ public function testComponentVariants() { */ private function getDefaultTest() { $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, $this->getContextProviderMock(), NULL, FALSE, 1, NULL); + $data = new Twig_Node_Expression_Array([], 1); + $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, FALSE, 1, NULL); return [ 'node' => $node, @@ -90,7 +94,8 @@ private function getDefaultTest() { */ private function getDefaultOnlyTest() { $expr = new Twig_Node_Expression_Constant('Example', 1); - $node = new ComponentNode($expr, $this->getContextProviderMock(), NULL, TRUE, 1, NULL); + $data = new Twig_Node_Expression_Array([], 1); + $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, TRUE, 1, NULL); return [ 'node' => $node, From c1873968ad2e40500de288b1ca636f281cdede5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Tue, 28 Jul 2020 11:33:40 +0200 Subject: [PATCH 19/29] Replace Travis CI with GithubActions --- .github/workflows/workflow.yml | 33 +++++++++++++++++++++++++++++++++ .travis.yml | 21 --------------------- README.md | 7 +++++++ 3 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/workflow.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..7e50a12 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,33 @@ +name: Twig Nitro Library + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + php_version: ['7.3', '7.4'] + + steps: + - name: Setup PHP ${{ matrix.php_version }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php_version }} + tools: composer + + - name: Checkout + uses: actions/checkout@v2 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Install dependencies + run: composer install --prefer-dist --dev --no-progress --no-suggest + + - name: Run tests + run: vendor/bin/phpunit diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d8882b3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: php -php: - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - hhvm - - nightly - -matrix: - allow_failures: - - php: nightly - - php: 5.4 - -install: - - composer install - -before_install: - - composer self-update - -sudo: false diff --git a/README.md b/README.md index 737da5f..b0bdb17 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,13 @@ TODO: More on that. ### ConfigReader Reads nitro's `config.json` and parses essential information such as the component paths and file extension. +### CI +This project uses GitHub actions. +#### Run locally +* Install [nektos/act](https://github.com/nektos/act). +* Open terminal, go to project directory. +* Run `act -P ubuntu-latest=shivammathur/node:latest` as described [here](https://github.com/shivammathur/setup-php#local-testing-setup). + ## Credits + [Twig Template Engine](http://twig.sensiolabs.org/) + [Terrific](http://terrifically.org/) From 776b1684e635b3d1eea930f431c3fc0b52d5d48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Tue, 28 Jul 2020 11:35:22 +0200 Subject: [PATCH 20/29] Require Twig ^2 & PHP >=7.3 --- README.md | 8 ++------ composer.json | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b0bdb17..6a73709 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,8 @@ $ composer require namics/terrific-twig ## Requirements The following versions of PHP are currently supported. - -+ ~~PHP 5.4~~ (**Deprecated**. Builds are failing since the tests are relying on [`::class`](http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.class).) -+ PHP 5.5 -+ PHP 5.6 -+ PHP 7 -+ HHVM +* 7.3 +* 7.4 ## Setup Step 1: Implement `TemplateInformationProvider` diff --git a/composer.json b/composer.json index 7351cf1..9f97695 100644 --- a/composer.json +++ b/composer.json @@ -25,8 +25,8 @@ } }, "require": { - "php": ">=7.2", - "twig/twig": "~1.24" + "php": ">=7.3", + "twig/twig": "^2.13" }, "require-dev": { "phpunit/phpunit": "^6.0" From 5c627b9f864a586fe075dc6d228958781b525fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Tue, 28 Jul 2020 11:42:56 +0200 Subject: [PATCH 21/29] Update documentation --- README.md | 49 ++++++++++++++++++++++++++++--------------------- composer.json | 10 ++++++++++ 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6a73709..58770a1 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -# Terrific Twig -[![Build Status](https://travis-ci.org/namics/terrific-twig.svg?branch=master)](https://travis-ci.org/namics/terrific-twig) -[![Latest Stable Version](https://poser.pugx.org/namics/terrific-twig/v/stable.svg)](https://packagist.org/packages/namics/terrific-twig) -[![Total Downloads](https://poser.pugx.org/namics/terrific-twig/downloads.svg)](https://packagist.org/packages/namics/terrific-twig) -[![License](https://poser.pugx.org/namics/terrific-twig/license.svg)](https://packagist.org/packages/namics/terrific-twig) +# Terrific Twigs +![Build Status](https://github.com/namics/twig-nitro-library/workflows/workflow/badge.svg) +![Latest version](https://img.shields.io/github/v/release/namics/twig-nitro-library) +![License](https://img.shields.io/github/license/namics/twig-nitro-library) +![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/namics/twig-nitro-library?color=%23787CB5) Extension to embrace the [Terrific](https://github.com/brunschgi/terrificjs) frontend methodology in [Twig](http://twig.sensiolabs.org/). -Currently it adds a custom `component` tag to Twig which mimics [Nitro](https://github.com/namics/generator-nitro)'s handlebars helper. +Adds a custom `component` tag to Twig which mimics the [Nitro](https://github.com/namics/generator-nitro) handlebars helper. ## Installation Using [composer](https://packagist.org/packages/namics/terrific-twig): -```bash +```shell script $ composer require namics/terrific-twig ``` @@ -31,7 +31,7 @@ class TemplateInformationProvider implements TemplateInformationProviderInterfac { return []; // List of path where Terrific Components can be found, e.g. (/var/www/example.com/frontend/components) } - + public function getFileExtension() { $fileExtension = 'html.twig'; @@ -68,28 +68,29 @@ Step 5: Profit! ## Usage ```twig -{# Includes the component, component's default data is merged with the context #} -{% component 'Example' %} - -{# Includes the component, the default data is injected as a child context #} -{% component 'Example' only %} - -{# Includes the component, but a variantion of the component data is merged with the context #} -{% component 'Example' 'example-variant' %} - -{# Includes the component, but a variantion of the component data is injected as a child context #} -{% component 'Example' 'example-variant' only %} - {# Includes the component, data object is merged with the context #} {% component 'Example' { title: 'Inject an Object' } %} {# Includes the component, data object is injected as a child context #} {% component 'Example' { title: 'Inject an Object' } only %} + +{# Includes the component, object variable data is merged with the context #} +{% set fooComponentData = { title: 'Inject an Object' } %} +{% component 'Example' fooComponentData %} + +{# Includes the component with name contained by string variable, data object is merged with the context #} +{% set fooComponentName = 'Example' %} +{% component fooComponentName { title: 'Inject an Object' } %} + +{# Includes the component with name contained by string variable, object variable data is merged with the context #} +{% set fooComponentName = 'Example' %} +{% set fooComponentData = { title: 'Inject an Object' } %} +{% component fooComponentName fooComponentData %} ``` ## Documentation ### Extension -The extension provides terrific extensions to Twig. Currently the extension provides the `ComponentTokenParser`. +The extension provides terrific extensions to Twig. Currently, the extension provides the `ComponentTokenParser`. ### Token Parser The token parser contains the parsing step for the component tag. It tokenizes the stream to different nodes (`component`, `data`) and an attribute (`only`). @@ -112,6 +113,12 @@ TODO: More on that. ### ConfigReader Reads nitro's `config.json` and parses essential information such as the component paths and file extension. +### Tests +Tests can be run by using the following command: +```shell script +composer run-scrip tests +``` + ### CI This project uses GitHub actions. #### Run locally diff --git a/composer.json b/composer.json index 9f97695..e61cf93 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,11 @@ "name": "Robert Vogt", "email": "robert.vogt@namics.com", "homepage": "https://github.com/deniaz" + }, + { + "name": "Namics", + "email": "drupal@namics.com", + "homepage": "https://github.com/namics" } ], "autoload": { @@ -30,5 +35,10 @@ }, "require-dev": { "phpunit/phpunit": "^6.0" + }, + "scripts": { + "tests": [ + "vendor/bin/phpunit" + ] } } From 127590ecd1ee5c40ea0f046ba8f9df855bc27021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Tue, 28 Jul 2020 11:43:44 +0200 Subject: [PATCH 22/29] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58770a1..1343c14 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Terrific Twigs +# Terrific Twig ![Build Status](https://github.com/namics/twig-nitro-library/workflows/workflow/badge.svg) ![Latest version](https://img.shields.io/github/v/release/namics/twig-nitro-library) ![License](https://img.shields.io/github/license/namics/twig-nitro-library) From 0baea588a4472d5534f9cb5fbe576c24781bf65a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Tue, 28 Jul 2020 11:44:07 +0200 Subject: [PATCH 23/29] Fix PHPCS issues --- src/Twig/TerrificCompiler.php | 9 ++++++--- src/Twig/TokenParser/ComponentTokenParser.php | 10 ++++++++-- test/Twig/Loader/TerrificLoaderTest.php | 6 +----- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Twig/TerrificCompiler.php b/src/Twig/TerrificCompiler.php index 87758b9..455bea5 100644 --- a/src/Twig/TerrificCompiler.php +++ b/src/Twig/TerrificCompiler.php @@ -98,7 +98,9 @@ protected function compileAsContextVariable(VariableNameAndArrayKeysPair $variab } /** - * Compiles and adds NameExpression as a variable to the Terrific Twig context. + * Compiles and adds NameExpression as a variable. + * + * To the Terrific Twig context. * * @param \Twig\Node\Expression\NameExpression $expression * The expression to compile. @@ -116,7 +118,9 @@ public function compileAndMergeNameExpressionToContext(NameExpression $expressio } /** - * Compiles and adds GetAttrExpression as a variable to the Terrific Twig context. + * Compiles & adds GetAttrExpression as a variable. + * + * To the Terrific Twig context. * * @param \Twig\Node\Expression\GetAttrExpression $expression * The expression to compile. @@ -142,7 +146,6 @@ public function compileAndMergeGetAttrExpressionToContext(GetAttrExpression $exp * when the given variable does not exist. */ public function compileAndMergeVariableToContext(VariableNameAndArrayKeysPair $variableNameAndArrayKeysPair, ?string $variableDoesNotExistErrorMessage = NULL): void { - /** @var \Twig\Compiler $twigCompiler */ $twigCompiler = $this->getTwigCompiler(); $contextArrayKeys = $variableNameAndArrayKeysPair->toTwigContextArrayKeysString(); diff --git a/src/Twig/TokenParser/ComponentTokenParser.php b/src/Twig/TokenParser/ComponentTokenParser.php index a5d3846..02428ce 100644 --- a/src/Twig/TokenParser/ComponentTokenParser.php +++ b/src/Twig/TokenParser/ComponentTokenParser.php @@ -34,7 +34,10 @@ public function __construct(ContextProviderInterface $ctxProvider) { } /** - * {@inheritdoc} + * Creates + * + * @return \Namics\Terrific\Twig\Node\ComponentNode + * The component node. */ public function parse(Token $token): ComponentNode { /** @var \Twig\Node\Expression\ConstantExpression $component */ @@ -87,7 +90,10 @@ protected function parseArguments(): array { } /** - * {@inheritdoc} + * Returns the component name. + * + * @return string + * The component name. */ public function getTag(): string { return 'component'; diff --git a/test/Twig/Loader/TerrificLoaderTest.php b/test/Twig/Loader/TerrificLoaderTest.php index 5d2fbe2..027671e 100644 --- a/test/Twig/Loader/TerrificLoaderTest.php +++ b/test/Twig/Loader/TerrificLoaderTest.php @@ -5,7 +5,6 @@ use Namics\Terrific\Provider\TemplateInformationProviderInterface; use Namics\Terrific\Twig\Loader\TerrificLoader; use Exception; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; /** @@ -46,6 +45,7 @@ protected function getTemplateInformationProviderMock() { * Get the terrific loader. * * @return \Namics\Terrific\Twig\Loader\TerrificLoader + * The Terrific loader. */ protected function getTerrificLoader(): TerrificLoader { return new TerrificLoader($this->getTemplateInformationProviderMock()); @@ -55,21 +55,17 @@ protected function getTerrificLoader(): TerrificLoader { * Test the paths. */ public function testPaths(): void { - /** @var \Namics\Terrific\Twig\Loader\TerrificLoader $loader */ $loader = $this->getTerrificLoader(); $this->assertEquals(self::PATHS, $loader->getPaths(), 'Paths are not identical.'); - /** @var \Twig\Source $source */ $source = $loader->getSourceContext('molecule'); $this->assertEquals("--molecule_content--\n", $source->getCode(), 'Molecule content is not identical.'); - /** @var \Twig\Source $source */ $source = $loader->getSourceContext('atom'); $this->assertEquals("--atom_content--\n", $source->getCode(), 'Atom content is not identical.'); // Check if the cache is invoked. - /** @var \Twig\Source $source */ $source = $loader->getSourceContext('atom'); $this->assertEquals("--atom_content--\n", $source->getCode(), 'Cache for atom content was not invoked.'); From d3215af28043efd24c7e4d41f2d03d06b36a6e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Tue, 28 Jul 2020 12:17:34 +0200 Subject: [PATCH 24/29] Allow manual CI run --- .github/workflows/workflow.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 7e50a12..4c56634 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -5,6 +5,7 @@ on: branches: [ master ] pull_request: branches: [ master ] + workflow_dispatch: jobs: test: From 71be5e7bf1ebff90207b2233e0ebc6f3a5cc268d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Tue, 28 Jul 2020 13:26:43 +0200 Subject: [PATCH 25/29] Make TerrificLoader Twig 2 compatible --- README.md | 21 ++--- .../TemplateInformationProviderInterface.php | 16 +--- src/Twig/Loader/TerrificLoader.php | 73 ++++++++--------- test/Twig/Extension/TerrificExtensionTest.php | 4 - .../atoms/atom/{atom.html => atom.twig} | 0 .../molecule/elements/molecule-element.twig | 1 + .../molecule/{molecule.html => molecule.twig} | 0 test/Twig/Loader/TerrificLoaderTest.php | 80 +++++++++++++------ test/Twig/Node/ComponentNodeTest.php | 46 +++++------ .../TokenParser/ComponentTokenParserTest.php | 22 +++-- 10 files changed, 134 insertions(+), 129 deletions(-) rename test/Twig/Loader/Fixtures/atoms/atom/{atom.html => atom.twig} (100%) create mode 100644 test/Twig/Loader/Fixtures/molecules/molecule/elements/molecule-element.twig rename test/Twig/Loader/Fixtures/molecules/molecule/{molecule.html => molecule.twig} (100%) diff --git a/README.md b/README.md index 1343c14..82de281 100644 --- a/README.md +++ b/README.md @@ -27,15 +27,16 @@ Step 1: Implement `TemplateInformationProvider` ```php class TemplateInformationProvider implements TemplateInformationProviderInterface { - public function getPaths() - { - return []; // List of path where Terrific Components can be found, e.g. (/var/www/example.com/frontend/components) - } - - public function getFileExtension() - { - $fileExtension = 'html.twig'; - return $fileExtension; + public function getPaths() { + /* List of path where Terrific Components can be found, e.g. + @code + [ + '/var/www/example.com/frontend/src/atoms', + '/var/www/example.com/frontend/src/molecules', + '/var/www/example.com/frontend/src/organisms' + ] + @endcode */ + return []; } } ``` @@ -101,7 +102,7 @@ The functionality is based on the fantastic `Twig_TokenParser_Include`. The Node compiles the tokenized tag to PHP. To see some of the output, check the [`ComponentTest`](https://github.com/namics/terrific-twig/blob/master/test/Twig/Node/ComponentTest.php). ### Loader -The `TerrificLoader` extends the `Twig_Loader_Filesystem` as it actually loads templates from the filesystem. An implementation of `TemplateLocatorInterface` provides the paths where the loader should search for templates. +The `TerrificLoader` loads templates contained inside the given paths. An implementation of `TemplateLocatorInterface` provides the paths where the loader should search for templates. Recursively loads any directories contained inside the given directories. ### Template Information Provider An implementation of `TemplateInformationProviderInterface` should return a list of paths where templates live. These should be in the form of `['frontend/components/atoms', 'frontend/components/molecules', 'frontend/components/organisms']`. The component directory will be provided by the `TerrificLoader` (`Example/example.[ext]`). diff --git a/src/Provider/TemplateInformationProviderInterface.php b/src/Provider/TemplateInformationProviderInterface.php index ec16aaf..5df049d 100644 --- a/src/Provider/TemplateInformationProviderInterface.php +++ b/src/Provider/TemplateInformationProviderInterface.php @@ -3,29 +3,19 @@ namespace Namics\Terrific\Provider; /** - * Interface TemplateInformationProviderInterface. - * * Interface to describe Template Information. * * @package Namics\Terrific\Provider - * @see Namics\Terrific\Twig\LoaderTerrificLoader + * @see \Namics\Terrific\Twig\Loader\TerrificLoader */ interface TemplateInformationProviderInterface { /** * Returns a list of paths where templates might be stored. * - * @return array - * The path. + * @return string[] + * Array of paths. */ public function getPaths(): array; - /** - * Returns the template's file extension. - * - * @return string - * The file extension. - */ - public function getFileExtension(): string; - } diff --git a/src/Twig/Loader/TerrificLoader.php b/src/Twig/Loader/TerrificLoader.php index ae23e0e..15d4f9b 100644 --- a/src/Twig/Loader/TerrificLoader.php +++ b/src/Twig/Loader/TerrificLoader.php @@ -3,29 +3,17 @@ namespace Namics\Terrific\Twig\Loader; use Namics\Terrific\Provider\TemplateInformationProviderInterface; -use Twig\Error\LoaderError; use Twig\Loader\FilesystemLoader; /** - * TerrificLoader searches for templates on the filesystem. + * Searches for templates inside given paths on the filesystem. * - * Within a terrific structure. - * Since the templates are stored on the filesystem nonetheless, - * TerrificLoader extends Twig's Twig_Loader_Filesystem. - * - * Class TerrificLoader. + * Includes all subdirectories of given paths recursively. * * @package Namics\Terrific\Twig\Loader */ final class TerrificLoader extends FilesystemLoader { - /** - * The template file extension to use. - * - * @var string - */ - private $fileExtension = 'html.twig'; - /** * TerrificLoader constructor. * @@ -33,40 +21,43 @@ final class TerrificLoader extends FilesystemLoader { * The template locator. */ public function __construct(TemplateInformationProviderInterface $locator) { - parent::__construct($locator->getPaths()); - $this->fileExtension = $locator->getFileExtension(); + parent::__construct( + $this->getPathSubdirectories($locator->getPaths()) + ); } /** - * {@inheritdoc} + * Returns all subdirectories of given directory recursively. + * + * @param string[] $componentDirectoryPaths + * Array of paths that contain components in any of their subdirectories. + * Or directly inside of them. + * + * @return string[] + * All subdirectories contained inside given directory. */ - protected function findTemplate($name) { - $name = $this->normalizeName($name); - - if (isset($this->cache[$name])) { - return $this->cache[$name]; - } - - if (isset($this->errorCache[$name])) { - throw new LoaderError($this->errorCache[$name]); - } - - $this->validateName($name); - $namespace = parent::MAIN_NAMESPACE; - - $terrificPath = $name . '/' . strtolower($name) . '.' . $this->fileExtension; - - foreach ($this->paths[$namespace] as $path) { - $fullPath = $path . '/' . $terrificPath; - $realPath = realpath($fullPath); - if (is_readable($fullPath) && $realPath !== FALSE) { - return $this->cache[$name] = $realPath; + private function getPathSubdirectories(array $componentDirectoryPaths): array { + $paths = []; + + foreach ($componentDirectoryPaths as $componentDirectoryPath) { + $pathIterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($componentDirectoryPath, \RecursiveDirectoryIterator::SKIP_DOTS), + \RecursiveIteratorIterator::SELF_FIRST, + // Ignore "Permission denied" errors. + \RecursiveIteratorIterator::CATCH_GET_CHILD + ); + + $paths[] = $componentDirectoryPath; + foreach ($pathIterator as $path => $fileInfo) { + /** @var \SplFileInfo $fileInfo */ + if ($fileInfo->isDir()) { + /** @var string $path */ + $paths[] = $path; + } } } - $this->errorCache[$name] = sprintf('Unable to find component "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); - - throw new LoaderError($this->errorCache[$name]); + return $paths; } } diff --git a/test/Twig/Extension/TerrificExtensionTest.php b/test/Twig/Extension/TerrificExtensionTest.php index e8c89ea..aa8bab5 100644 --- a/test/Twig/Extension/TerrificExtensionTest.php +++ b/test/Twig/Extension/TerrificExtensionTest.php @@ -2,8 +2,6 @@ namespace Namics\Test\Terrific\Twig\Extension; -use Namics\Terrific\Provider\ContextProviderInterface; -use Namics\Terrific\Twig\Extension\TerrificExtension; use Namics\Terrific\Twig\TokenParser\ComponentTokenParser; use Namics\Test\Terrific\Twig\TwigTestBase; @@ -21,7 +19,6 @@ class TerrificExtensionTest extends TwigTestBase { * @covers ::getName */ public function testGetName(): void { - /** @var \Namics\Terrific\Twig\Extension\TerrificExtension $twigExtension */ $twigExtension = $this->getTwigExtension(); $this->assertEquals('terrific', $twigExtension->getName(), 'Returned Twig extension name is not identical.'); @@ -33,7 +30,6 @@ public function testGetName(): void { * @covers ::getTokenParsers */ public function testGetTokenParsers(): void { - /** @var \Namics\Terrific\Twig\Extension\TerrificExtension $twigExtension */ $twigExtension = $this->getTwigExtension(); $this->assertContainsOnlyInstancesOf(ComponentTokenParser::class, $twigExtension->getTokenParsers(), 'Token parser does not include only class ComponentTokenParser.'); diff --git a/test/Twig/Loader/Fixtures/atoms/atom/atom.html b/test/Twig/Loader/Fixtures/atoms/atom/atom.twig similarity index 100% rename from test/Twig/Loader/Fixtures/atoms/atom/atom.html rename to test/Twig/Loader/Fixtures/atoms/atom/atom.twig diff --git a/test/Twig/Loader/Fixtures/molecules/molecule/elements/molecule-element.twig b/test/Twig/Loader/Fixtures/molecules/molecule/elements/molecule-element.twig new file mode 100644 index 0000000..7ab88bc --- /dev/null +++ b/test/Twig/Loader/Fixtures/molecules/molecule/elements/molecule-element.twig @@ -0,0 +1 @@ +--molecule-element_content-- diff --git a/test/Twig/Loader/Fixtures/molecules/molecule/molecule.html b/test/Twig/Loader/Fixtures/molecules/molecule/molecule.twig similarity index 100% rename from test/Twig/Loader/Fixtures/molecules/molecule/molecule.html rename to test/Twig/Loader/Fixtures/molecules/molecule/molecule.twig diff --git a/test/Twig/Loader/TerrificLoaderTest.php b/test/Twig/Loader/TerrificLoaderTest.php index 027671e..eab6aa1 100644 --- a/test/Twig/Loader/TerrificLoaderTest.php +++ b/test/Twig/Loader/TerrificLoaderTest.php @@ -6,9 +6,10 @@ use Namics\Terrific\Twig\Loader\TerrificLoader; use Exception; use PHPUnit\Framework\TestCase; +use Twig\Error\LoaderError; /** - * Class TerrificLoaderTest. + * Tests the TerrificLoader. * * @package Namics\Test\Terrific\Twig\Loader * @coversDefaultClass \Namics\Terrific\Twig\Loader\TerrificLoader @@ -16,15 +17,32 @@ class TerrificLoaderTest extends TestCase { /** - * The file paths. + * Paths returned by the Template Information Provider. * - * @var array + * @var string[] */ - protected const PATHS = [ + protected const TEMPLATE_INFORMATION_PROVIDER_PATHS = [ __DIR__ . '/Fixtures/atoms', __DIR__ . '/Fixtures/molecules', ]; + /** + * Paths generated by the Terrific loader. + * + * @var string[] + */ + protected const LOADER_PATHS = [ + __DIR__ . '/Fixtures/atoms', + __DIR__ . '/Fixtures/atoms/atom', + __DIR__ . '/Fixtures/molecules', + __DIR__ . '/Fixtures/molecules/molecule', + __DIR__ . '/Fixtures/molecules/molecule/elements', + ]; + + protected const FIXTURE_ATOM_CONTENT = "--atom_content--\n"; + protected const FIXTURE_MOLECULE_CONTENT = "--molecule_content--\n"; + protected const FIXTURE_MOLECULE_ELEMENT_CONTENT = "--molecule-element_content--\n"; + /** * Get the template information provider mock. * @@ -35,8 +53,7 @@ protected function getTemplateInformationProviderMock() { /** @var \Namics\Terrific\Provider\TemplateInformationProviderInterface|\PHPUnit\Framework\MockObject\MockObject $provider */ $provider = $this->getMockBuilder(TemplateInformationProviderInterface::class) ->getMock(); - $provider->method('getPaths')->willReturn(self::PATHS); - $provider->method('getFileExtension')->willReturn('html'); + $provider->method('getPaths')->willReturn(self::TEMPLATE_INFORMATION_PROVIDER_PATHS); return $provider; } @@ -52,39 +69,56 @@ protected function getTerrificLoader(): TerrificLoader { } /** - * Test the paths. + * Test the paths generated by the Loader. */ - public function testPaths(): void { + public function testPathGeneration(): void { $loader = $this->getTerrificLoader(); - $this->assertEquals(self::PATHS, $loader->getPaths(), 'Paths are not identical.'); + $this->assertEquals(self::LOADER_PATHS, $loader->getPaths(), 'Loader paths are not identical.'); + } - $source = $loader->getSourceContext('molecule'); - $this->assertEquals("--molecule_content--\n", $source->getCode(), 'Molecule content is not identical.'); + /** + * Test the paths. + */ + public function testTemplateLoading(): void { + $loader = $this->getTerrificLoader(); - $source = $loader->getSourceContext('atom'); - $this->assertEquals("--atom_content--\n", $source->getCode(), 'Atom content is not identical.'); + $source = $loader->getSourceContext('molecule.twig'); + $this->assertEquals(self::FIXTURE_MOLECULE_CONTENT, $source->getCode(), 'Molecule content is not identical.'); + + $source = $loader->getSourceContext('atom.twig'); + $this->assertEquals(self::FIXTURE_ATOM_CONTENT, $source->getCode(), 'Atom content is not identical.'); // Check if the cache is invoked. - $source = $loader->getSourceContext('atom'); - $this->assertEquals("--atom_content--\n", $source->getCode(), 'Cache for atom content was not invoked.'); + $source = $loader->getSourceContext('atom.twig'); + $this->assertEquals(self::FIXTURE_ATOM_CONTENT, $source->getCode(), 'Cache for atom content was not invoked.'); try { - $loader->getSourceContext('fake-component'); + $loader->getSourceContext('fake-component.twig'); } - catch (Exception $e) { - $this->assertInstanceOf('Twig_Error_Loader', $e, 'Exception "Twig_Error_Loader" was not thrown.'); - $this->assertContains('Unable to find component "fake-component"', $e->getMessage(), 'Exception message is not identical.'); + catch (Exception $exception) { + $this->assertInstanceOf(LoaderError::class, $exception, 'Exception "' . LoaderError::class . '" was not thrown.'); + $this->assertContains('Unable to find template "fake-component.twig"', $exception->getMessage(), 'Exception message is not identical.'); } // Check if the cache is invoked. try { - $loader->getSourceContext('fake-component'); + $loader->getSourceContext('fake-component.twig'); } - catch (Exception $e) { - $this->assertInstanceOf('Twig_Error_Loader', $e, 'Exception "Twig_Error_Loader" was not thrown.'); - $this->assertContains('Unable to find component "fake-component"', $e->getMessage(), 'Exception message is not identical.'); + catch (Exception $exception) { + $this->assertInstanceOf(LoaderError::class, $exception, 'Exception "' . LoaderError::class . '" was not thrown.'); + $this->assertContains('Unable to find template "fake-component.twig"', $exception->getMessage(), 'Exception message is not identical.'); } } + /** + * Test if templates in component subdirectories are loaded. + */ + public function testChildTemplateLoading(): void { + $loader = $this->getTerrificLoader(); + + $source = $loader->getSourceContext('molecule-element.twig'); + $this->assertEquals(self::FIXTURE_MOLECULE_ELEMENT_CONTENT, $source->getCode(), 'Child template content is not correct.'); + } + } diff --git a/test/Twig/Node/ComponentNodeTest.php b/test/Twig/Node/ComponentNodeTest.php index 5f3f07f..8460fba 100644 --- a/test/Twig/Node/ComponentNodeTest.php +++ b/test/Twig/Node/ComponentNodeTest.php @@ -2,12 +2,10 @@ namespace Namics\Test\Terrific\Twig\Node; -use Drupal\Component\Uuid\Com; -use Namics\Terrific\Provider\ContextProviderInterface; use Namics\Terrific\Twig\Node\ComponentNode; use Namics\Test\Terrific\Twig\TwigTestBase; -use Twig\Compiler; -use Twig\Environment; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; use Twig_Node_Expression_Constant; use Twig_Node_Expression_Array; @@ -24,8 +22,7 @@ class ComponentNodeTest extends TwigTestBase { * * @covers ::__construct */ - public function testContructor() { - /** @var \Namics\Terrific\Provider\ContextProviderInterface $ctxProvider */ + public function testConstructor() { $ctxProvider = $this->getContextProviderMock(); $expression = new Twig_Node_Expression_Constant('Example', 1); @@ -51,7 +48,7 @@ public function testContructor() { $this->getDefaultTest(); } - public function testComponentVariants() { + /* public function testComponentVariants() { // TODO: test it with a data provider. $expectedResults = [ $this->getDefaultTest(), @@ -63,7 +60,7 @@ public function testComponentVariants() { ]; // TODO: Write tests. - } + } */ /** * Tests the following tag: {% component 'Example' %}. @@ -71,8 +68,8 @@ public function testComponentVariants() { * @return array */ private function getDefaultTest() { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $data = new Twig_Node_Expression_Array([], 1); + $expr = new ConstantExpression('Example', 1); + $data = new ArrayExpression([], 1); $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, FALSE, 1, NULL); return [ @@ -93,8 +90,8 @@ private function getDefaultTest() { * @return array */ private function getDefaultOnlyTest() { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $data = new Twig_Node_Expression_Array([], 1); + $expr = new ConstantExpression('Example', 1); + $data = new ArrayExpression([], 1); $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, TRUE, 1, NULL); return [ @@ -114,10 +111,10 @@ private function getDefaultOnlyTest() { * @return array */ private function getDataObjectTest() { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $data = new Twig_Node_Expression_Array([ - new Twig_Node_Expression_Constant('foo', 1), - new Twig_Node_Expression_Constant('bar', 1), + $expr = new ConstantExpression('Example', 1); + $data = new ArrayExpression([ + new ConstantExpression('foo', 1), + new ConstantExpression('bar', 1), ], 1); $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, FALSE, 1, NULL); @@ -136,10 +133,10 @@ private function getDataObjectTest() { * @return array */ private function getDataObjectOnlyTest() { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $data = new Twig_Node_Expression_Array([ - new Twig_Node_Expression_Constant('foo', 1), - new Twig_Node_Expression_Constant('bar', 1), + $expr = new ConstantExpression('Example', 1); + $data = new ArrayExpression([ + new ConstantExpression('foo', 1), + new ConstantExpression('bar', 1), ], 1); $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, TRUE, 1, NULL); @@ -158,8 +155,8 @@ private function getDataObjectOnlyTest() { * @return array */ private function getVariantTest() { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $data = new Twig_Node_Expression_Constant('example-foo', 1); + $expr = new ConstantExpression('Example', 1); + $data = new ConstantExpression('example-foo', 1); $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, FALSE, 1, NULL); return [ @@ -173,11 +170,10 @@ private function getVariantTest() { /** * Tests the following tag: {% component 'Example' 'example-variant' only %}. - * */ private function getVariantOnlyTest() { - $expr = new Twig_Node_Expression_Constant('Example', 1); - $data = new Twig_Node_Expression_Constant('example-foo', 1); + $expr = new ConstantExpression('Example', 1); + $data = new ConstantExpression('example-foo', 1); $node = new ComponentNode($expr, $this->getContextProviderMock(), $data, TRUE, 1, NULL); return [ diff --git a/test/Twig/TokenParser/ComponentTokenParserTest.php b/test/Twig/TokenParser/ComponentTokenParserTest.php index 24d8a20..98013ff 100644 --- a/test/Twig/TokenParser/ComponentTokenParserTest.php +++ b/test/Twig/TokenParser/ComponentTokenParserTest.php @@ -2,8 +2,6 @@ namespace Namics\Test\Terrific\Twig\TokenParser; -use Namics\Terrific\Provider\ContextProviderInterface; -use Namics\Terrific\Twig\TokenParser\ComponentTokenParser; use Namics\Test\Terrific\Twig\TwigTestBase; /** @@ -20,20 +18,18 @@ class ComponentTokenParserTest extends TwigTestBase { * @covers ::getTag */ public function testGetTag() { - /** @var \Namics\Terrific\Twig\TokenParser\ComponentTokenParser $parser */ $parser = $this->getComponentTokenParser(); $this->assertEquals('component', $parser->getTag()); } - // TODO: write test. - // Public function testParse() - // { - // $tokenParser = new ComponentTokenParser(); - // $parser = $this->getMockBuilder('Twig_Parser')->getMock(); - // $token = $this->getMockBuilder('Twig_Token')->getMock(); - // - // $tokenParser->setParser($parser); - // $this->assertInstanceOf(ComponentNode::class, $tokenParser->parse($token)); - // } + /* TODO: Test + public function testParse() { + $tokenParser = $this->getComponentTokenParser(); + + $token = $this->getMockBuilder('Twig_Token')->getMock(); + + $tokenParser->setParser($tokenParser); + $this->assertInstanceOf(ComponentNode::class, $tokenParser->parse($token)); + } */ } From 0d97faff748b5583e6df6553d951ae8843eb2d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Wed, 29 Jul 2020 15:46:04 +0200 Subject: [PATCH 26/29] Update gitignore --- .gitignore | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5d0c1be..2591067 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,60 @@ -.idea +# OSX system files .DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db Thumbs.db + +# Git +*.git + +# Packages +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases +*.log +*.sql +*.sqlite + +# Temporary +tmp +.grunt + +# PHPStorm +.idea/ +.idea/* + +# NPM +node_modules/ + +# SASS # +######## +.sass-cache/ +*.css.map + +# Composer +composer.phar composer.lock vendor/ + + +# PHPUnit Tests +.phpunit.result.cache + +# Drush +drush.phar + +# Certificates +*.pem +*.crt +*.cert +*.key From 18179b8df4a01c4273639b30eefeca76181287d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Thu, 30 Jul 2020 17:38:55 +0200 Subject: [PATCH 27/29] PHPCS fixes, improve comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Orlando Thöny --- src/Provider/ContextProviderInterface.php | 4 +--- src/Twig/Extension/TerrificExtension.php | 18 ++++++++++-------- src/Twig/Node/ComponentNode.php | 23 ++++++++++------------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/Provider/ContextProviderInterface.php b/src/Provider/ContextProviderInterface.php index 165f5ad..278461e 100644 --- a/src/Provider/ContextProviderInterface.php +++ b/src/Provider/ContextProviderInterface.php @@ -32,9 +32,7 @@ interface ContextProviderInterface { * The Twig node data variant. * @param bool $only * The only attribute. - * - * @return mixed */ - public function compile(Compiler $compiler, Node $component, Node $dataVariant, bool $only); + public function compile(Compiler $compiler, Node $component, Node $dataVariant, bool $only): void; } diff --git a/src/Twig/Extension/TerrificExtension.php b/src/Twig/Extension/TerrificExtension.php index c29d1d7..f1b0b09 100644 --- a/src/Twig/Extension/TerrificExtension.php +++ b/src/Twig/Extension/TerrificExtension.php @@ -7,10 +7,7 @@ use Twig\Extension\AbstractExtension; /** - * TerrificExtension adds Terrific Features to the Twig Environment. Currently only the ComponentTokenParser is added, - * which results in the additional component tag. - * - * Class TerrificExtension. + * Adds Terrific features to the Twig Environment. * * @package Namics\Terrific\Twig\Extension */ @@ -27,22 +24,27 @@ final class TerrificExtension extends AbstractExtension { * TerrificExtension constructor. * * @param \Namics\Terrific\Provider\ContextProviderInterface $ctxProvider - * - * @TODO: Default Provider? + * The context provider. */ public function __construct(ContextProviderInterface $ctxProvider) { $this->ctxProvider = $ctxProvider; } /** - * {@inheritdoc} + * Returns the extension name. + * + * @return string + * Twig extension name. */ public function getName(): string { return 'terrific'; } /** - * {@inheritdoc} + * Returns the token parsers of this extension. + * + * @return \Twig\TokenParser\TokenParserInterface[] + * The token parsers of this extension. */ public function getTokenParsers(): array { return [ diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index 3bb457e..138ccbd 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -57,18 +57,18 @@ final class ComponentNode extends Node implements NodeOutputInterface { * Tag name associated with the node. */ public function __construct( - Node $component, - ContextProviderInterface $ctxProvider, - Node $data, - $only = FALSE, - $lineno, - $tag = NULL + Node $component, + ContextProviderInterface $ctxProvider, + Node $data, + $only = FALSE, + $lineno, + $tag = NULL ) { parent::__construct( - ['component' => $component, 'data' => $data], - ['only' => (bool) $only], - $lineno, - $tag + ['component' => $component, 'data' => $data], + ['only' => (bool) $only], + $lineno, + $tag ); $this->ctxProvider = $ctxProvider; @@ -83,7 +83,6 @@ public function __construct( public function compile(Compiler $compiler): void { $terrificCompiler = TerrificCompiler::create($compiler); - /** @var \Twig\Compiler $twigCompiler */ $twigCompiler = $terrificCompiler->getTwigCompiler(); $twigCompiler->addDebugInfo($this); @@ -108,7 +107,6 @@ public function compile(Compiler $compiler): void { * The Terrific Twig compiler. */ protected function createTerrificContext(TerrificCompilerInterface $terrificCompiler): void { - /** @var \Twig\Compiler $twigCompiler */ $twigCompiler = $terrificCompiler->getTwigCompiler(); $twigCompiler @@ -135,7 +133,6 @@ protected function createTerrificContext(TerrificCompilerInterface $terrificComp * The Terrific Twig compiler. */ protected function addGetTemplate(TerrificCompilerInterface $terrificCompiler): void { - /** @var \Twig\Compiler $twigCompiler */ $twigCompiler = $terrificCompiler->getTwigCompiler(); $twigCompiler->write('$this->loadTemplate('); From 39bfe05f26ea96aec1993d109abde9a48e5f400f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Thu, 30 Jul 2020 17:39:37 +0200 Subject: [PATCH 28/29] Add file extension to component name when loading template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Orlando Thöny --- src/Twig/Extension/TerrificExtension.php | 7 ++++++ src/Twig/Node/ComponentNode.php | 1 + src/Twig/TerrificCompiler.php | 27 ++++++++++++++++++++++++ src/Twig/TerrificCompilerInterface.php | 10 +++++++++ 4 files changed, 45 insertions(+) diff --git a/src/Twig/Extension/TerrificExtension.php b/src/Twig/Extension/TerrificExtension.php index f1b0b09..52275e1 100644 --- a/src/Twig/Extension/TerrificExtension.php +++ b/src/Twig/Extension/TerrificExtension.php @@ -13,6 +13,13 @@ */ final class TerrificExtension extends AbstractExtension { + /** + * The file extension of templates that this extension loads. + * + * @var string + */ + public const TEMPLATE_FILE_EXTENSION = '.twig'; + /** * The context provider. * diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index 138ccbd..beae2be 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -138,6 +138,7 @@ protected function addGetTemplate(TerrificCompilerInterface $terrificCompiler): $twigCompiler->write('$this->loadTemplate('); $this->compileComponentName($terrificCompiler); + $terrificCompiler->compileConcatFileExtensionString(); $twigCompiler ->raw(', ') diff --git a/src/Twig/TerrificCompiler.php b/src/Twig/TerrificCompiler.php index 455bea5..3ecec28 100644 --- a/src/Twig/TerrificCompiler.php +++ b/src/Twig/TerrificCompiler.php @@ -4,6 +4,7 @@ use Namics\Terrific\Provider\ContextProviderInterface; use Namics\Terrific\Twig\Data\VariableNameAndArrayKeysPair; +use Namics\Terrific\Twig\Extension\TerrificExtension; use Namics\Terrific\Twig\Utility\ExpressionHandler; use Twig\Compiler; use Twig\Node\Expression\GetAttrExpression; @@ -206,4 +207,30 @@ public function getTwigCompiler(): Compiler { return $this->compiler; } + /** + * Concatenates the template file extension to a string. + * + * Example output: + * @code + * . '.twig' + * @endcode + */ + public function compileConcatFileExtensionString(): void { + $this->getTwigCompiler()->raw(". '"); + $this->compileTemplateFileExtension(); + $this->getTwigCompiler()->raw("'"); + } + + /** + * Compiles the template file extension. + * + * Example output: + * @code + * .twig + * @endcode + */ + private function compileTemplateFileExtension(): void { + $this->getTwigCompiler()->raw(TerrificExtension::TEMPLATE_FILE_EXTENSION); + } + } diff --git a/src/Twig/TerrificCompilerInterface.php b/src/Twig/TerrificCompilerInterface.php index 538b8b7..ac6a587 100644 --- a/src/Twig/TerrificCompilerInterface.php +++ b/src/Twig/TerrificCompilerInterface.php @@ -68,4 +68,14 @@ public function compileAndMergeGetAttrExpressionToContext(GetAttrExpression $exp */ public function getExpressionHandler(): ExpressionHandler; + /** + * Concatenates the template file extension to a string. + * + * Example output: + * @code + * . '.twig' + * @endcode + */ + public function compileConcatFileExtensionString(): void; + } From 7cf56537ed872198636294d33872d376505f61c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orlando=20Tho=CC=88ny?= Date: Thu, 30 Jul 2020 17:41:00 +0200 Subject: [PATCH 29/29] Delete unused ComponentNode::compileGetAttrExpressionComponentName() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Orlando Thöny --- src/Twig/Node/ComponentNode.php | 36 --------------------------------- 1 file changed, 36 deletions(-) diff --git a/src/Twig/Node/ComponentNode.php b/src/Twig/Node/ComponentNode.php index beae2be..507bdab 100644 --- a/src/Twig/Node/ComponentNode.php +++ b/src/Twig/Node/ComponentNode.php @@ -183,42 +183,6 @@ protected function compileComponentName(TerrificCompilerInterface $terrificCompi } } - /** - * Generates an array with the array keys. - * - * Pointing to the value location of given expression in the context. - * - * TODO: This method is never used. Check! - * - * @param \Twig\Node\Expression\GetAttrExpression $expression - * The expression to get the context array keys for. - * - * @return string[] - * Array with array keys. - */ - protected function compileGetAttrExpressionComponentName(GetAttrExpression $expression): array { - if ($this->getExpressionHandler()->isNestedObject($expression)) { - $dataVariableName = $expression->getNode('attribute')->getAttribute('value'); - $childExpression = $expression->getNode('node'); - - // TODO: undefined method buildGetAttrExpressionArrayKeys(). Add DI if this method is necessary. - $dataVariableArrayKeys = $this->buildGetAttrExpressionArrayKeys($childExpression); - - $dataExpressionArrayKeys = $dataVariableArrayKeys; - $dataExpressionArrayKeys[] = $dataVariableName; - - return $dataExpressionArrayKeys; - } - else { - $dataVariableName = $expression->getNode('node')->getAttribute('name'); - $dataVariableArrayKey = $expression->getNode('attribute')->getAttribute('value'); - - $dataExpressionArrayKeys = [$dataVariableName, $dataVariableArrayKey]; - - return $dataExpressionArrayKeys; - } - } - /** * Returns the expression handler. */