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..eb9acb7 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: ['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'] 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/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 258cc11..859b71e 100644 --- a/composer.json +++ b/composer.json @@ -19,16 +19,22 @@ }, "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.0", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^8.5.15", - "codeclimate/php-test-reporter": "^0.4.0" + "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": { "psr-4": { "Slim\\Pug\\": "src" } + }, + "config": { + "allow-plugins": { + "nodejs-php-fallback/nodejs-php-fallback": true + } } } diff --git a/src/PugRenderer.php b/src/PugRenderer.php index 6466d5f..1dfba4e 100644 --- a/src/PugRenderer.php +++ b/src/PugRenderer.php @@ -3,9 +3,12 @@ namespace Slim\Pug; use ArrayAccess; +use DI\Container; use Psr\Http\Message\ResponseInterface; use Pug\Pug; +use RuntimeException; use Slim\App; +use Slim\Psr7\Factory\ResponseFactory; /** * Class PugRenderer. @@ -52,12 +55,21 @@ 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(); + $app = self::createApp(); } + $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); @@ -203,8 +215,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 @@ -234,7 +246,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 !== '\\') { @@ -257,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 f0b8e74..4f9652a 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\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\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..88c8875 100644 --- a/tests/PugRendererTest.php +++ b/tests/PugRendererTest.php @@ -2,56 +2,44 @@ 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 ArrayAccess; +use DI\Container; +use Slim\App; 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( '' . '