From 2578859eaf1bbc2786865692d4f93f9409bdf5dc Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Sun, 15 Mar 2026 11:44:33 +0100 Subject: [PATCH 1/8] Update GitHub Actions --- .github/workflows/coverage.yml | 33 +++++++++++++-------------------- .github/workflows/tests.yml | 9 ++++----- composer.json | 2 +- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9980088..5bdc82c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -13,13 +13,13 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.4'] + php: ['8.4'] setup: ['stable'] name: PHP steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -29,22 +29,13 @@ jobs: - name: Cache Composer packages id: composer-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: vendor key: ${{ runner.os }}-${{ matrix.setup }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-${{ matrix.setup }}-php-${{ matrix.php }}- - - name: Code Climate Test Reporter Preparation - if: ${{ env.CC_TEST_REPORTER_ID != '' }} - run: | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build - env: - CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} - - name: Install dependencies if: steps.composer-cache.outputs.cache-hit != 'true' run: composer update --prefer-dist --prefer-${{ matrix.setup }} --no-progress --no-interaction @@ -52,11 +43,13 @@ jobs: - name: Run test suite run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml - - name: Code Climate Test Reporter - if: ${{ env.CC_TEST_REPORTER_ID != '' }} - run: | - cp coverage.xml clover.xml - bash <(curl -s https://codecov.io/bash) - ./cc-test-reporter after-build --coverage-input-type clover --exit-code 0 - env: - CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} + - name: Coverage - Qltysh + uses: qltysh/qlty-action/coverage@v2 + with: + token: ${{ secrets.QLTY_COVERAGE_TOKEN }} + files: clover.xml + + - name: Coverage - Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4a729e7..172a142 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,14 +13,14 @@ jobs: strategy: fail-fast: false matrix: - php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] + php: ['5.5', '5.6', '7.0', '7.4', '8.0', '8.1', '8.4', '8.5'] renderer: ['talesoft/tale-pug:^1.5', 'pug-php/pug:^2.7.6', 'pug-php/pug:^3.4.1'] setup: ['lowest', 'stable'] name: PHP ${{ matrix.php }} - ${{ matrix.renderer }} - ${{ matrix.setup }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -30,7 +30,7 @@ jobs: - name: Cache Composer packages id: composer-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: vendor key: ${{ runner.os }}-${{ matrix.setup }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }} @@ -41,8 +41,7 @@ jobs: if: steps.composer-cache.outputs.cache-hit != 'true' run: | composer require ${{ matrix.renderer }} --no-update - ${{ matrix.php >= 8 && 'composer require --no-update phpunit/phpunit:^8.5.15 --no-interaction;' || '' }} - composer update --prefer-dist --prefer-${{ matrix.setup }} --no-progress --no-interaction ${{ matrix.php >= 8.1 && '--ignore-platform-req=php' || '' }} + composer update --prefer-dist --prefer-${{ matrix.setup }} --no-progress --no-interaction - name: Run test suite run: vendor/bin/phpunit --no-coverage --verbose diff --git a/composer.json b/composer.json index 258cc11..7c5a9b0 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "psr/http-message": "^1.0" }, "require-dev": { - "slim/slim": "^3.0", + "slim/slim": "^3.12.5 || ^4.15.1", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^8.5.15", "codeclimate/php-test-reporter": "^0.4.0" }, From f8131abfd0580baf3b1f976c4e66e4f9b997d963 Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Sun, 15 Mar 2026 11:46:31 +0100 Subject: [PATCH 2/8] Drop support for PHP < 7.1 --- .github/workflows/tests.yml | 2 +- src/PugRenderer.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 172a142..eb9acb7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['5.5', '5.6', '7.0', '7.4', '8.0', '8.1', '8.4', '8.5'] + php: ['7.1', '7.4', '8.0', '8.1', '8.4', '8.5'] renderer: ['talesoft/tale-pug:^1.5', 'pug-php/pug:^2.7.6', 'pug-php/pug:^3.4.1'] setup: ['lowest', 'stable'] diff --git a/src/PugRenderer.php b/src/PugRenderer.php index 6466d5f..3aac880 100644 --- a/src/PugRenderer.php +++ b/src/PugRenderer.php @@ -52,11 +52,12 @@ public function __construct($templatePath = null, $options = [], $attributes = [ * * @return App */ - public static function create(App $app = null, $templatePath = null, array $options = [], array $attributes = []) + public static function create(?App $app = null, $templatePath = null, array $options = [], array $attributes = []) { if (!$app) { $app = new App(); } + $container = $app->getContainer(); $templatePath = $templatePath ?: (isset($container['templates.path']) ? $container['templates.path'] : null); $container['renderer'] = new static($templatePath, $options, $attributes); From cb6b7ca8594858f2e114e217fc92259c7170ab83 Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Sun, 15 Mar 2026 11:48:03 +0100 Subject: [PATCH 3/8] Allow nodejs-php-fallback/nodejs-php-fallback plugin --- composer.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/composer.json b/composer.json index 7c5a9b0..f8fe85b 100644 --- a/composer.json +++ b/composer.json @@ -30,5 +30,10 @@ "psr-4": { "Slim\\Pug\\": "src" } + }, + "config": { + "allow-plugins": { + "nodejs-php-fallback/nodejs-php-fallback": true + } } } From 2d67c14b3abcc34aa17140ad7a03f09a927894c6 Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Sun, 15 Mar 2026 11:48:51 +0100 Subject: [PATCH 4/8] Remove codeclimate/php-test-reporter --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index f8fe85b..0fea0e9 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,7 @@ }, "require-dev": { "slim/slim": "^3.12.5 || ^4.15.1", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^8.5.15", - "codeclimate/php-test-reporter": "^0.4.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^8.5.15" }, "autoload": { "psr-4": { From 66ede7581757d38d03cbdbb9488bd123e7a293a5 Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Tue, 17 Mar 2026 14:38:13 +0100 Subject: [PATCH 5/8] Allow psr/http-message 2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0fea0e9..d601ddc 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ }, "require": { "pug-php/pug": "^2.7.6 || ^3.4.1", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { "slim/slim": "^3.12.5 || ^4.15.1", From 2c83379e33136207760514a4935c7323a1cd2fe6 Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Tue, 17 Mar 2026 18:41:52 +0100 Subject: [PATCH 6/8] Fix support for Slim 4 --- README.md | 26 ++++++++- composer.json | 2 + src/PugRenderer.php | 15 ++++-- tests/AbstractTestCase.php | 106 +++++++++++++++++++++++++++++++++++++ tests/PugRendererTest.php | 27 +--------- 5 files changed, 145 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 81a9206..532c253 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,29 @@ Install with [Composer](http://getcomposer.org): composer require pug/slim ``` +## Usage with Slim 4 + +**routes.php** +```php +use Slim\Pug\PugRenderer; + +// ... + +$app->get('/', function (Request $request, Response $response) { + $renderer = new PugRenderer([ + // The base folder containing the pug template files + // In this example we assume a home.pug file exist in it + 'basedir' => __DIR__ . '/../templates', + ]); + + $viewData = [ + 'name' => 'John', + ]; + + return $renderer->render($response, 'home.pug', $viewData); +}); +```` + ## Usage with Slim 3 ```php @@ -46,13 +69,12 @@ include 'vendor/autoload.php'; $app = PugRenderer::create(null, './templates'); ``` - ## Usage with any PSR-7 Project ```php //Construct the View $pugView = new PugRenderer('./path/to/templates', [ - 'option' => 'foobar', + // Here you can set options ]); //Render a Template diff --git a/composer.json b/composer.json index d601ddc..859b71e 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,8 @@ }, "require-dev": { "slim/slim": "^3.12.5 || ^4.15.1", + "slim/psr7": "*", + "php-di/php-di": "^5.4.6 || ^6.4", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^8.5.15" }, "autoload": { diff --git a/src/PugRenderer.php b/src/PugRenderer.php index 3aac880..310ee28 100644 --- a/src/PugRenderer.php +++ b/src/PugRenderer.php @@ -3,6 +3,7 @@ namespace Slim\Pug; use ArrayAccess; +use DI\Container; use Psr\Http\Message\ResponseInterface; use Pug\Pug; use Slim\App; @@ -59,6 +60,14 @@ public static function create(?App $app = null, $templatePath = null, array $opt } $container = $app->getContainer(); + + if ($container instanceof Container) { + $templatePath = $templatePath ?: ($container->has('templates.path') ? $container->get('templates.path') : null); + $container->set('renderer', new static($templatePath, $options, $attributes)); + + return $app; + } + $templatePath = $templatePath ?: (isset($container['templates.path']) ? $container['templates.path'] : null); $container['renderer'] = new static($templatePath, $options, $attributes); @@ -204,8 +213,8 @@ public function setTemplatePath($templatePath) * Fetches the template and wraps it in a response object. * * @param ResponseInterface $response - * @param string $template - * @param array $data + * @param string $template path (from basedir if present) to the Pug template file + * @param array $data variables for the view * * @throws \InvalidArgumentException if it contains template as a key * @throws \RuntimeException if `$templatePath . $template` does not exist @@ -235,7 +244,7 @@ public function fetch($template, array $data = []) { if (!method_exists($this->adapter, 'renderFile')) { $file = $this->getTemplatePath(); - $file = $file ? $file : ''; + $file = $file ?: ''; $lastChar = substr($file, -1); // @codeCoverageIgnoreStart if ($lastChar !== '/' && $lastChar !== '\\') { diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index f0b8e74..5384566 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -2,8 +2,21 @@ namespace Slim\Pug\Tests; +use DI\ContainerBuilder; use PHPUnit\Framework\TestCase; +use RuntimeException; +use Slim\Http\Body as Slim3Body; +use Slim\Http\Headers as Slim3Headers; +use Slim\Http\Request as Slim3Request; +use Slim\Http\Response as Slim3Response; +use Slim\Http\Stream as Slim3Stream; +use Slim\Http\Uri as Slim3Uri; use Slim\App; +use Slim\Factory\AppFactory; +use Slim\Psr7\Headers; +use Slim\Psr7\Request; +use Slim\Psr7\Stream; +use Slim\Psr7\Uri; use Slim\Pug\PugRenderer; abstract class AbstractTestCase extends TestCase @@ -49,6 +62,39 @@ protected function init() $options['renderer'] = '\\Tale\\Pug\\Renderer'; } + // Slim 4 + if (class_exists(AppFactory::class)) { + // Instantiate PHP-DI ContainerBuilder + $containerBuilder = new ContainerBuilder(); + + // Build PHP-DI Container instance + $container = $containerBuilder->build(); + + // Instantiate the app + AppFactory::setContainer($container); + $app = AppFactory::create(); + + PugRenderer::create($app, $options['templates.path']); + + $renderer = $app->getContainer()->get('renderer'); + + $this->app = $app; + $this->pug = $renderer; + + $app->get('/hello/{name}', function ($request, $response, $args) use ($renderer) { + return $renderer->render($response, '/home.pug', $args); + }); + + // Add Routing Middleware + $app->addRoutingMiddleware(); + + // Add Body Parsing Middleware + $app->addBodyParsingMiddleware(); + + return; + } + + // Slim 3 $app = PugRenderer::create(new App($options)); $app->get('/hello/{name}', function ($request, $response, $args) { @@ -58,4 +104,64 @@ protected function init() $this->app = $app; $this->pug = $app->getContainer()['renderer']; } + + protected function fetch(string $path): string + { + $app = $this->getApp(); + + $rand = mt_rand(0, 99999999); + $tempIn = sys_get_temp_dir() . '/streamIn-' . $rand . '.txt'; + touch($tempIn); + + // Slim 4 + if (class_exists(Headers::class)) { + // Run App & Emit Response + $response = $app->handle(new Request( + 'GET', + new Uri('https', 'domain.com', 443, $path), + new Headers(), + [], + [], + new Stream(fopen($tempIn, 'r+')), + )); + + $body = $response->getBody(); + + if ($body->isSeekable()) { + $body->rewind(); + } + + return $body->read($body->getSize()); + } + + // Slim 3 + $tempOut = sys_get_temp_dir() . '/streamOut-' . $rand . '.txt'; + touch($tempOut); + $headers = new Slim3Headers(); + $uri = Slim3Uri::createFromString($path); + $body = new Slim3Stream(fopen($tempIn, 'r')); + $container = $app->getContainer(); + $request = new Slim3Request('GET', $uri, $headers, [], [], $body); + /** @var \Slim\Router $router */ + $router = $container->get('router'); + $route = $router->lookupRoute('route0'); + $route->prepare($request, [ + 'name' => 'bob', + ]); + $response = new Slim3Response(200, null, new Slim3Body(fopen($tempOut, 'w'))); + $route->run($request, $response); + + return $this->getContents($path, $tempOut); + } + + private function getContents(string $path, string $file): string + { + $contents = file_get_contents($file); + + if ($contents === false) { + throw new RuntimeException("Unable to fetch '$path'"); + } + + return $contents; + } } diff --git a/tests/PugRendererTest.php b/tests/PugRendererTest.php index 9843a1e..34905c8 100644 --- a/tests/PugRendererTest.php +++ b/tests/PugRendererTest.php @@ -2,43 +2,18 @@ namespace Slim\Pug\Tests; -use Slim\Http\Body; -use Slim\Http\Headers; -use Slim\Http\Request; -use Slim\Http\Response; -use Slim\Http\Stream; -use Slim\Http\Uri; use Slim\Pug\PugRenderer; class PugRendererTest extends AbstractTestCase { public function testPugRenderer() { - $rand = mt_rand(0, 99999999); - $tempIn = sys_get_temp_dir() . '/streamIn-' . $rand . '.txt'; - $tempOut = sys_get_temp_dir() . '/streamOut-' . $rand . '.txt'; - touch($tempIn); - touch($tempOut); - $headers = new Headers(); - $uri = Uri::createFromString('/home/bob'); - $body = new Stream(fopen($tempIn, 'r')); - $container = $this->getApp()->getContainer(); - $request = new Request('GET', $uri, $headers, [], [], $body); - /** @var \Slim\Router $router */ - $router = $container->get('router'); - $route = $router->lookupRoute('route0'); - $route->prepare($request, [ - 'name' => 'bob', - ]); - $response = new Response(200, null, new Body(fopen($tempOut, 'w'))); - $route->run($request, $response); - self::assertSame( '' . 'Home page' . '

Home page

Hello bob
Bye
' . '', - str_replace(["\r", "\n"], '', file_get_contents($tempOut)) + str_replace(["\r", "\n"], '', $this->fetch('/hello/bob')) ); } From cbd124a173eaccf0320f3cd1e2f84ec353317115 Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Tue, 17 Mar 2026 20:14:05 +0100 Subject: [PATCH 7/8] Fix compatibility --- src/PugRenderer.php | 5 ++++- tests/AbstractTestCase.php | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/PugRenderer.php b/src/PugRenderer.php index 310ee28..73d7844 100644 --- a/src/PugRenderer.php +++ b/src/PugRenderer.php @@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface; use Pug\Pug; use Slim\App; +use Slim\Psr7\Factory\ResponseFactory; /** * Class PugRenderer. @@ -56,7 +57,9 @@ public function __construct($templatePath = null, $options = [], $attributes = [ public static function create(?App $app = null, $templatePath = null, array $options = [], array $attributes = []) { if (!$app) { - $app = new App(); + $app = defined(App::class . '::VERSION') && ((int) App::VERSION) >= 4 + ? new App(new ResponseFactory()) + : new App(); } $container = $app->getContainer(); diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 5384566..5ee538e 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -5,14 +5,14 @@ use DI\ContainerBuilder; use PHPUnit\Framework\TestCase; use RuntimeException; +use Slim\App; +use Slim\Factory\AppFactory; use Slim\Http\Body as Slim3Body; use Slim\Http\Headers as Slim3Headers; use Slim\Http\Request as Slim3Request; use Slim\Http\Response as Slim3Response; use Slim\Http\Stream as Slim3Stream; use Slim\Http\Uri as Slim3Uri; -use Slim\App; -use Slim\Factory\AppFactory; use Slim\Psr7\Headers; use Slim\Psr7\Request; use Slim\Psr7\Stream; From e8a2615707301218e7959a5f5047b9166c435018 Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Tue, 17 Mar 2026 22:44:02 +0100 Subject: [PATCH 8/8] Update tests --- src/PugRenderer.php | 21 ++++++++++++++++++--- tests/AbstractTestCase.php | 2 +- tests/PugRendererTest.php | 17 +++++++++++++++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/PugRenderer.php b/src/PugRenderer.php index 73d7844..1dfba4e 100644 --- a/src/PugRenderer.php +++ b/src/PugRenderer.php @@ -6,6 +6,7 @@ use DI\Container; use Psr\Http\Message\ResponseInterface; use Pug\Pug; +use RuntimeException; use Slim\App; use Slim\Psr7\Factory\ResponseFactory; @@ -57,9 +58,7 @@ public function __construct($templatePath = null, $options = [], $attributes = [ public static function create(?App $app = null, $templatePath = null, array $options = [], array $attributes = []) { if (!$app) { - $app = defined(App::class . '::VERSION') && ((int) App::VERSION) >= 4 - ? new App(new ResponseFactory()) - : new App(); + $app = self::createApp(); } $container = $app->getContainer(); @@ -270,4 +269,20 @@ public function fetch($template, array $data = []) return $this->adapter->renderFile($template, $data); } + + private static function createApp(): App + { + if (!defined(App::class . '::VERSION') || ((int) App::VERSION) < 4) { + return new App(); + } + + if (!class_exists(ResponseFactory::class)) { + throw new RuntimeException( + 'You need to install or update slim/psr7 to create an instance of ' . self::class . + ' without passing an app context' + ); + } + + return new App(new ResponseFactory()); + } } diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 5ee538e..4f9652a 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -122,7 +122,7 @@ protected function fetch(string $path): string new Headers(), [], [], - new Stream(fopen($tempIn, 'r+')), + new Stream(fopen($tempIn, 'r+')) )); $body = $response->getBody(); diff --git a/tests/PugRendererTest.php b/tests/PugRendererTest.php index 34905c8..88c8875 100644 --- a/tests/PugRendererTest.php +++ b/tests/PugRendererTest.php @@ -2,6 +2,9 @@ namespace Slim\Pug\Tests; +use ArrayAccess; +use DI\Container; +use Slim\App; use Slim\Pug\PugRenderer; class PugRendererTest extends AbstractTestCase @@ -19,14 +22,24 @@ public function testPugRenderer() public function testCreate() { - self::assertInstanceOf(PugRenderer::class, PugRenderer::create()->getContainer()->renderer); + self::assertInstanceOf(App::class, PugRenderer::create()); } public function testGetTemplatePath() { $path = rtrim($this->getPug()->getTemplatePath(), DIRECTORY_SEPARATOR); + $container = $this->getApp()->getContainer(); + $v4 = (defined(App::class . '::VERSION') && ((int) App::VERSION) >= 4); - self::assertSame($path, $this->getApp()->getContainer()['templates.path']); + self::assertSame(__DIR__ . '/templates', $path); + self::assertInstanceOf( + $v4 ? Container::class : ArrayAccess::class, + $container + ); + self::assertSame( + __DIR__ . '/templates', + $v4 ? $container->get('renderer')->getOption('path') : $container['templates.path'] + ); } public function testAttributes()