diff --git a/.gitignore b/.gitignore index d1502b0..47d1cb8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ vendor/ +.phpunit.result.cache composer.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index f55537e..4d0d172 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog All notable changes to Shoot will be documented in this file. +## [3.0.0] - 2019-05-01 +### Changed +- Shoot now requires Twig v2.9. In addition, it's now also pinned to this minor version, as Twig doesn't seem to follow +SemVer with regards to non-breaking changes in its APIs. As this on its own is a breaking change for Shoot, this +warrants a major version bump. + +### Fixed +- Compatibility issues with Twig v2.9 have been fixed. + ## [2.0.0] - 2019-03-04 ### Added - Documentation on nesting presentation models and the `optional` tag. diff --git a/composer.json b/composer.json index a9d9472..c01968a 100644 --- a/composer.json +++ b/composer.json @@ -32,10 +32,10 @@ "psr/http-message": "^1.0", "psr/http-server-middleware": "^1.0", "psr/log": "^1.1", - "twig/twig": "^2.6" + "twig/twig": "~2.9.0" }, "require-dev": { - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^8.1" }, "autoload": { "psr-4": { diff --git a/src/Extension.php b/src/Extension.php index 0b78524..dd2d60a 100644 --- a/src/Extension.php +++ b/src/Extension.php @@ -6,12 +6,12 @@ use Shoot\Shoot\Twig\NodeVisitor\ModelNodeVisitor; use Shoot\Shoot\Twig\TokenParser\ModelTokenParser; use Shoot\Shoot\Twig\TokenParser\OptionalTokenParser; -use Twig_ExtensionInterface as ExtensionInterface; -use Twig_Filter as TwigFilter; -use Twig_Function as TwigFunction; -use Twig_NodeVisitorInterface as NodeVisitorInterface; -use Twig_Test as TwigTest; -use Twig_TokenParserInterface as TokenParserInterface; +use Twig\Extension\ExtensionInterface; +use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\TokenParser\TokenParserInterface; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\TwigTest; /** * This extension for Twig will enable the use of Shoot. diff --git a/src/Installer.php b/src/Installer.php index fd831fd..cddf04e 100644 --- a/src/Installer.php +++ b/src/Installer.php @@ -4,7 +4,7 @@ namespace Shoot\Shoot; use Shoot\Shoot\Twig\PatchingCompiler; -use Twig_Environment as Environment; +use Twig\Environment; /** * Installs Shoot in an instance of Twig. diff --git a/src/Pipeline.php b/src/Pipeline.php index f25951a..2f94a56 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -4,7 +4,6 @@ namespace Shoot\Shoot; use Psr\Http\Message\ServerRequestInterface; -use Shoot\Shoot\Middleware\SuppressionMiddleware; /** * The processing pipeline of Shoot. Holds the middleware that enables Shoot's functionality. It's called from the Twig diff --git a/src/Twig/Node/DisplayEndNode.php b/src/Twig/Node/DisplayEndNode.php index 7cba1e8..f3dd67b 100644 --- a/src/Twig/Node/DisplayEndNode.php +++ b/src/Twig/Node/DisplayEndNode.php @@ -6,9 +6,10 @@ use Shoot\Shoot\Extension; use Shoot\Shoot\SuppressedException; use Shoot\Shoot\View; -use Twig_Compiler as Compiler; -use Twig_Node as Node; -use Twig_Node_Module as ModuleNode; +use Twig\Compiler; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\Source; /** * This node is added to the bottom of the display method of a Twig template and is used by Shoot to wrap its contents @@ -30,7 +31,7 @@ public function __construct(ModuleNode $module) $this->module = $module; - $this->setTemplateName($module->getTemplateName()); + $this->setSourceContext($module->getSourceContext()); } /** @@ -44,9 +45,12 @@ public function compile(Compiler $compiler): void return; } + /** @var Source $source */ + $source = $this->getSourceContext(); + $templateName = $source->getName(); + $extensionClass = Extension::class; $suppressedExceptionClass = SuppressedException::class; - $templateName = $this->getTemplateName(); $viewClass = View::class; $compiler diff --git a/src/Twig/Node/DisplayStartNode.php b/src/Twig/Node/DisplayStartNode.php index dda4fc1..d0aa950 100644 --- a/src/Twig/Node/DisplayStartNode.php +++ b/src/Twig/Node/DisplayStartNode.php @@ -3,9 +3,9 @@ namespace Shoot\Shoot\Twig\Node; -use Twig_Compiler as Compiler; -use Twig_Node as Node; -use Twig_Node_Module as ModuleNode; +use Twig\Compiler; +use Twig\Node\ModuleNode; +use Twig\Node\Node; /** * This node is added to the top of the display method of a Twig template and is used by Shoot to wrap the method's @@ -32,8 +32,7 @@ public function __construct(ModuleNode $module, FindPresentationModelInterface $ $this->module = $module; $this->findPresentationModel = $findPresentationModel; - $this->setTemplateName($module->getTemplateName()); - + $this->setSourceContext($module->getSourceContext()); } /** @@ -47,7 +46,7 @@ public function compile(Compiler $compiler): void return; } - $presentationModel = $this->findPresentationModel->for($this->getTemplateName()); + $presentationModel = $this->findPresentationModel->for($this->getSourceContext()); $compiler ->write("\$presentationModel = new $presentationModel(\$context);\n") diff --git a/src/Twig/Node/FindPresentationModelInterface.php b/src/Twig/Node/FindPresentationModelInterface.php index 6c9f975..b00eb94 100644 --- a/src/Twig/Node/FindPresentationModelInterface.php +++ b/src/Twig/Node/FindPresentationModelInterface.php @@ -3,6 +3,8 @@ namespace Shoot\Shoot\Twig\Node; +use Twig\Source; + /** * Finds the presentation model for a given view. * @@ -13,9 +15,9 @@ interface FindPresentationModelInterface /** * Returns the presentation model for the given view. * - * @param string $view + * @param Source $source * * @return string */ - public function for(string $view): string; + public function for(Source $source): string; } diff --git a/src/Twig/Node/ModelNode.php b/src/Twig/Node/ModelNode.php index 44a1131..b3da027 100644 --- a/src/Twig/Node/ModelNode.php +++ b/src/Twig/Node/ModelNode.php @@ -3,7 +3,7 @@ namespace Shoot\Shoot\Twig\Node; -use Twig_Node as Node; +use Twig\Node\Node; /** * Represents the model tag used to assign a presentation model to a view. diff --git a/src/Twig/Node/OptionalNode.php b/src/Twig/Node/OptionalNode.php index f5499aa..300d93c 100644 --- a/src/Twig/Node/OptionalNode.php +++ b/src/Twig/Node/OptionalNode.php @@ -4,9 +4,9 @@ namespace Shoot\Shoot\Twig\Node; use Shoot\Shoot\SuppressedException; -use Twig_Compiler as Compiler; -use Twig_Error_Runtime as RuntimeError; -use Twig_Node as Node; +use Twig\Compiler; +use Twig\Error\RuntimeError; +use Twig\Node\Node; /** * Represents the optional tag used to suppress runtime exceptions in templates. diff --git a/src/Twig/NodeVisitor/ModelNodeVisitor.php b/src/Twig/NodeVisitor/ModelNodeVisitor.php index 412c787..d9fa4d7 100644 --- a/src/Twig/NodeVisitor/ModelNodeVisitor.php +++ b/src/Twig/NodeVisitor/ModelNodeVisitor.php @@ -3,17 +3,18 @@ namespace Shoot\Shoot\Twig\NodeVisitor; -use LogicException; use Shoot\Shoot\ModelAlreadyAssignedException; use Shoot\Shoot\PresentationModel; use Shoot\Shoot\Twig\Node\DisplayEndNode; use Shoot\Shoot\Twig\Node\DisplayStartNode; use Shoot\Shoot\Twig\Node\FindPresentationModelInterface; use Shoot\Shoot\Twig\Node\ModelNode; -use Twig_Environment as Environment; -use Twig_Node as Node; -use Twig_Node_Module as ModuleNode; -use Twig_NodeVisitorInterface as NodeVisitorInterface; +use SplObjectStorage; +use Twig\Environment; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\Source; /** * Walks through model tags to assign presentation models to templates. @@ -22,8 +23,13 @@ */ final class ModelNodeVisitor implements FindPresentationModelInterface, NodeVisitorInterface { - /** @var string[] */ - private $presentationModels = []; + /** @var SplObjectStorage */ + private $presentationModels; + + public function __construct() + { + $this->presentationModels = new SplObjectStorage(); + } /** * @param Node $node @@ -34,7 +40,7 @@ final class ModelNodeVisitor implements FindPresentationModelInterface, NodeVisi public function enterNode(Node $node, Environment $environment): Node { if ($node instanceof ModelNode) { - $this->assign($node->getTemplateName(), $node->getAttribute('presentation_model')); + $this->assign($node->getSourceContext(), $node->getAttribute('presentation_model')); } return $node; @@ -68,13 +74,17 @@ public function leaveNode(Node $node, Environment $environment): Node /** * Returns the presentation model for the given view. * - * @param string $view + * @param Source $source * * @return string */ - public function for(string $view): string + public function for(Source $source): string { - return $this->presentationModels[$view] ?? PresentationModel::class; + if (isset($this->presentationModels[$source])) { + return (string)$this->presentationModels[$source]; + } + + return PresentationModel::class; } /** @@ -87,19 +97,19 @@ public function getPriority(): int } /** - * @param string $view + * @param Source $source * @param string $presentationModel * * @return void * - * @throws LogicException + * @throws ModelAlreadyAssignedException */ - private function assign(string $view, string $presentationModel): void + private function assign(Source $source, string $presentationModel): void { - if (isset($this->presentationModels[$view])) { - throw new ModelAlreadyAssignedException("A presentation model has already been assigned to {$view}"); + if (isset($this->presentationModels[$source])) { + throw new ModelAlreadyAssignedException("A presentation model has already been assigned to {$source->getName()}"); } - $this->presentationModels[$view] = $presentationModel; + $this->presentationModels[$source] = $presentationModel; } } diff --git a/src/Twig/PatchingCompiler.php b/src/Twig/PatchingCompiler.php index 7650287..1c1d307 100644 --- a/src/Twig/PatchingCompiler.php +++ b/src/Twig/PatchingCompiler.php @@ -3,7 +3,7 @@ namespace Shoot\Shoot\Twig; -use Twig_Compiler as Compiler; +use Twig\Compiler; /** * This compiler patches a few crucial lines in some core Twig classes. It allows Shoot to be used with extend, embed, diff --git a/src/Twig/TokenParser/ModelTokenParser.php b/src/Twig/TokenParser/ModelTokenParser.php index f2ced0d..d2c2613 100644 --- a/src/Twig/TokenParser/ModelTokenParser.php +++ b/src/Twig/TokenParser/ModelTokenParser.php @@ -4,9 +4,10 @@ namespace Shoot\Shoot\Twig\TokenParser; use Shoot\Shoot\Twig\Node\ModelNode; -use Twig_Node as Node; -use Twig_Token as Token; -use Twig_TokenParser as AbstractTokenParser; +use Twig\Error\SyntaxError; +use Twig\Node\Node; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; /** * Parses model tags in the token stream. @@ -19,6 +20,8 @@ final class ModelTokenParser extends AbstractTokenParser * @param Token $token * * @return Node + * + * @throws SyntaxError */ public function parse(Token $token): Node { diff --git a/src/Twig/TokenParser/OptionalTokenParser.php b/src/Twig/TokenParser/OptionalTokenParser.php index 662ffb7..e3a03ab 100644 --- a/src/Twig/TokenParser/OptionalTokenParser.php +++ b/src/Twig/TokenParser/OptionalTokenParser.php @@ -4,9 +4,10 @@ namespace Shoot\Shoot\Twig\TokenParser; use Shoot\Shoot\Twig\Node\OptionalNode; -use Twig_Node as Node; -use Twig_Token as Token; -use Twig_TokenParser as AbstractTokenParser; +use Twig\Error\SyntaxError; +use Twig\Node\Node; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; /** * Parses optional tags in the token stream. @@ -19,6 +20,8 @@ final class OptionalTokenParser extends AbstractTokenParser * @param Token $token * * @return Node + * + * @throws SyntaxError */ public function parse(Token $token): Node { diff --git a/src/View.php b/src/View.php index d0f81de..cdfe7c0 100644 --- a/src/View.php +++ b/src/View.php @@ -35,11 +35,7 @@ final class View */ public function __construct(string $name, PresentationModel $presentationModel, callable $callback) { - if ($name === '') { - throw new InvalidArgumentException('The name of a view cannot be empty'); - } - - $this->name = $name; + $this->name = $name !== '' ? $name : 'unknown template'; $this->presentationModel = $presentationModel; $this->callback = $callback; } diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 982f4bc..6176f57 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -14,8 +14,8 @@ use Shoot\Shoot\Middleware\PresenterMiddleware; use Shoot\Shoot\MiddlewareInterface; use Shoot\Shoot\Pipeline; -use Twig_Environment as Environment; -use Twig_Loader_Filesystem as FilesystemLoader; +use Twig\Environment; +use Twig\Loader\FilesystemLoader; abstract class IntegrationTestCase extends TestCase { diff --git a/tests/Unit/ExtensionTest.php b/tests/Unit/ExtensionTest.php index a2ee7e8..3c6e675 100644 --- a/tests/Unit/ExtensionTest.php +++ b/tests/Unit/ExtensionTest.php @@ -11,8 +11,8 @@ use Shoot\Shoot\Pipeline; use Shoot\Shoot\PresentationModel; use Shoot\Shoot\Tests\Fixtures\ViewFactory; -use Twig_Filter as TwigFilter; -use Twig_Test as TwigTest; +use Twig\TwigFilter; +use Twig\TwigTest; final class ExtensionTest extends TestCase { diff --git a/tests/Unit/ViewTest.php b/tests/Unit/ViewTest.php index 0a2ad12..17d6141 100644 --- a/tests/Unit/ViewTest.php +++ b/tests/Unit/ViewTest.php @@ -3,28 +3,13 @@ namespace Shoot\Shoot\Tests\Unit; -use InvalidArgumentException; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Shoot\Shoot\PresentationModel; use Shoot\Shoot\Tests\Fixtures\ViewFactory; -use Shoot\Shoot\View; use stdClass; final class ViewTest extends TestCase { - public function testShouldNotAllowEmptyNames(): void - { - $presentationModel = new PresentationModel(); - $callback = function () { - // noop - }; - - $this->expectException(InvalidArgumentException::class); - - new View('', $presentationModel, $callback); - } - public function testShouldExecuteCallback(): void { /** @var callable|MockObject $callback */