diff --git a/CHANGELOG.md b/CHANGELOG.md index be2af3864..069a23a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on Keep a Changelog. ### Changed - The `DemoWeb` and `Toolkit` module templates redirect back with `back()` instead of `redirect(get_referrer() ?? base_url())` and `redirectWith(get_referrer() ?? base_url(), $data)`, so generated code gets the same-origin check. Modules generated before keep their code (#541) +- Twig is a required dependency again: `twig/twig` `^3.27` is in `require` and no longer only suggested, which reverses the 3.0.3 change that made it optional while the framework supported PHP 8.0 (#532). Twig `^3.27` needs PHP 8.1, the minimum since 3.1.0. An application that pins an older Twig must allow `^3.27`. Removed `RendererException::twigNotInstalled()` and the check in `TwigAdapter` that threw it when Twig was missing, and the Twig tests no longer skip (#626) ## [3.1.0] - 2026-10-24 diff --git a/composer.json b/composer.json index 12595cb65..9858c62f4 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,8 @@ "ezyang/htmlpurifier": "^4.18", "povils/figlet": "^0.1.0", "ramsey/uuid": "^4.2", - "dragonmantank/cron-expression": "^3.0" + "dragonmantank/cron-expression": "^3.0", + "twig/twig": "^3.27" }, "require-dev": { "phpunit/phpunit": "^9.0", @@ -66,8 +67,7 @@ "ext-dom": "Required for XML/HTML processing", "ext-zip": "Required for ZIP file handling", "ext-memcached": "Required for Memcached support", - "ext-redis": "Required for Redis support", - "twig/twig": "Required for the optional Twig renderer; secure Twig ^3.27 releases require PHP 8.1 or later" + "ext-redis": "Required for Redis support" }, "autoload": { "psr-4": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 59480f89d..a1c84e59e 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,9 +2,6 @@ parameters: phpVersion: 80100 level: 8 - scanFiles: - - stubs/Twig.stub - paths: - src diff --git a/src/Renderer/Adapters/TwigAdapter.php b/src/Renderer/Adapters/TwigAdapter.php index b188d90c2..ee2a46f09 100644 --- a/src/Renderer/Adapters/TwigAdapter.php +++ b/src/Renderer/Adapters/TwigAdapter.php @@ -44,10 +44,6 @@ class TwigAdapter implements TemplateRendererInterface */ public function __construct(?array $configs = []) { - if (!class_exists(Environment::class)) { - throw RendererException::twigNotInstalled(); - } - $this->configs = $configs; $this->fs = FileSystemFactory::get(); diff --git a/src/Renderer/Exceptions/RendererException.php b/src/Renderer/Exceptions/RendererException.php index c6435bc6f..2f8b06e2e 100644 --- a/src/Renderer/Exceptions/RendererException.php +++ b/src/Renderer/Exceptions/RendererException.php @@ -19,12 +19,4 @@ */ class RendererException extends BaseException { - public static function twigNotInstalled(): self - { - return new self( - 'The Twig renderer requires the optional `twig/twig` package. ' . - 'Install a security-supported release separately; Twig ^3.27 requires PHP 8.1 or later: ' . - '`composer require twig/twig:^3.27`.' - ); - } } diff --git a/stubs/Twig.stub b/stubs/Twig.stub deleted file mode 100644 index 40f7855d0..000000000 --- a/stubs/Twig.stub +++ /dev/null @@ -1,58 +0,0 @@ - $options - */ - public function __construct(FilesystemLoader $loader, array $options = []) - { - } - - /** - * @param array $context - */ - public function render(string $name, array $context = []): string - { - } - - public function addFunction(TwigFunction $function): void - { - } -} - -class TwigFunction -{ - public function __construct(string $name, callable $callable) - { - } -} - -namespace Twig\Loader; - -class FilesystemLoader -{ - public function __construct(string $path) - { - } -} - -namespace Twig\Error; - -use Exception; - -class LoaderError extends Exception -{ -} - -class RuntimeError extends Exception -{ -} - -class SyntaxError extends Exception -{ -} diff --git a/tests/Unit/Renderer/Adapters/TwigAdapterTest.php b/tests/Unit/Renderer/Adapters/TwigAdapterTest.php index 6811b7a1f..292fc41eb 100644 --- a/tests/Unit/Renderer/Adapters/TwigAdapterTest.php +++ b/tests/Unit/Renderer/Adapters/TwigAdapterTest.php @@ -3,7 +3,6 @@ namespace Quantum\Tests\Unit\Renderer\Adapters; use Quantum\Renderer\Adapters\TwigAdapter; -use Quantum\Renderer\Exceptions\RendererException; use Quantum\Tests\Unit\AppTestCase; use Quantum\Router\MatchedRoute; use Quantum\Router\Route; @@ -30,10 +29,6 @@ public function setUp(): void public function testTwigAdapterRenderView(): void { - if (!class_exists('Twig\Environment')) { - $this->markTestSkipped('The optional twig/twig package is not installed.'); - } - $adapter = new TwigAdapter(); $output = $adapter->render('index.twig', ['name' => 'Tester']); @@ -42,20 +37,4 @@ public function testTwigAdapterRenderView(): void $this->assertSame('

Hello Tester, this is rendered twig view

', $output); } - - public function testTwigAdapterThrowsActionableExceptionWhenTwigIsMissing(): void - { - if (class_exists('Twig\Environment')) { - $this->markTestSkipped('The optional twig/twig package is installed.'); - } - - $this->expectException(RendererException::class); - $this->expectExceptionMessage( - 'The Twig renderer requires the optional `twig/twig` package. ' . - 'Install a security-supported release separately; Twig ^3.27 requires PHP 8.1 or later: ' . - '`composer require twig/twig:^3.27`.' - ); - - new TwigAdapter(); - } } diff --git a/tests/Unit/Renderer/Factories/RendererFactoryTest.php b/tests/Unit/Renderer/Factories/RendererFactoryTest.php index 19b1d7da9..3e8251184 100644 --- a/tests/Unit/Renderer/Factories/RendererFactoryTest.php +++ b/tests/Unit/Renderer/Factories/RendererFactoryTest.php @@ -48,10 +48,6 @@ public function testRendererFactoryGetHtmlAdapter(): void public function testRendererFactoryTwigAdapter(): void { - if (!class_exists('Twig\Environment')) { - $this->markTestSkipped('The optional twig/twig package is not installed.'); - } - $renderer = RendererFactory::get(RendererType::TWIG); $this->assertInstanceOf(TemplateRendererInterface::class, $renderer->getAdapter()); diff --git a/tests/Unit/Renderer/RendererTest.php b/tests/Unit/Renderer/RendererTest.php index 6b21186b6..f0dee9364 100644 --- a/tests/Unit/Renderer/RendererTest.php +++ b/tests/Unit/Renderer/RendererTest.php @@ -42,10 +42,6 @@ public function testRendererGetHtmlAdapter(): void public function testRendererGetTwigAdapter(): void { - if (!class_exists('Twig\Environment')) { - $this->markTestSkipped('The optional twig/twig package is not installed.'); - } - $renderer = new Renderer(new TwigAdapter()); $this->assertInstanceOf(TwigAdapter::class, $renderer->getAdapter()); diff --git a/tests/Unit/View/ViewTest.php b/tests/Unit/View/ViewTest.php index 9b3ce6c79..7d24627b0 100644 --- a/tests/Unit/View/ViewTest.php +++ b/tests/Unit/View/ViewTest.php @@ -184,10 +184,6 @@ public function testRenderPartial(): void public function testRenderViewWithTwig(): void { - if (!class_exists('Twig\Environment')) { - $this->markTestSkipped('The optional twig/twig package is not installed.'); - } - $factory = Di::get(ViewFactory::class); $this->setPrivateProperty($factory, 'instance', null);