diff --git a/README.md b/README.md index 26ba841..54f8896 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,18 @@ $app->get('/hi/{name}', function ($request, $response, $args) { // Run app $app->run(); ``` +## Two ways to use the render() method + +The first: +```php +$view->render($response, 'example.html', ['name' => 'Josh']) +```` +The second requires you to set any implementation of the PSR-17 response factory otherwise an exception will be thrown: +```php +$view->setResponseFactory($someResponseFactory); + +$view->render('example.html', ['name' => 'Josh']) +``` ## Custom template functions diff --git a/composer.json b/composer.json index 77d1c7c..dce5a59 100644 --- a/composer.json +++ b/composer.json @@ -20,14 +20,14 @@ "require": { "php": "^7.3 || ^8.0", "psr/http-message": "^1.0", + "psr/http-factory": "^1.0", "slim/slim": "^4.9", - "twig/twig": "^3.3" + "twig/twig": "^3.3", }, "require-dev": { "phpunit/phpunit": "^9.3.8", "phpspec/prophecy-phpunit": "^2.0", "phpstan/phpstan": "^0.12.99", - "psr/http-factory": "^1.0", "squizlabs/php_codesniffer": "^3.6" }, "autoload": { diff --git a/src/Twig.php b/src/Twig.php index 43af32d..592b291 100644 --- a/src/Twig.php +++ b/src/Twig.php @@ -11,6 +11,8 @@ use ArrayAccess; use ArrayIterator; +use InvalidArgumentException; +use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use RuntimeException; @@ -55,6 +57,13 @@ class Twig implements ArrayAccess */ protected $defaultVariables = []; + /** + * PSR-17 response factory + * + * @var ResponseFactoryInterface + */ + protected $responseFactory; + /** * @param ServerRequestInterface $request * @param string $attributeName @@ -129,6 +138,14 @@ public function addRuntimeLoader(RuntimeLoaderInterface $runtimeLoader): void $this->environment->addRuntimeLoader($runtimeLoader); } + /** + * @param ResponseFactoryInterface $responseFactory + */ + public function setResponseFactory(ResponseFactoryInterface $responseFactory): void + { + $this->responseFactory = $responseFactory; + } + /** * Fetch rendered template * @@ -189,9 +206,9 @@ public function fetchFromString(string $string = '', array $data = []): string /** * Output rendered template * - * @param ResponseInterface $response - * @param string $template Template pathname relative to templates directory - * @param array $data Associative array of template variables + * @param ResponseInterface|string $responseOrTemplate Response or template pathname relative to templates dir. + * @param string|array $templateOrData Template pathname or template variables + * @param array $data Associative array of template variables * * @throws LoaderError When the template cannot be found * @throws SyntaxError When an error occurred during compilation @@ -199,11 +216,27 @@ public function fetchFromString(string $string = '', array $data = []): string * * @return ResponseInterface */ - public function render(ResponseInterface $response, string $template, array $data = []): ResponseInterface + public function render($responseOrTemplate, $templateOrData = [], array $data = []): ResponseInterface { - $response->getBody()->write($this->fetch($template, $data)); + if ($responseOrTemplate instanceof ResponseInterface && is_string($templateOrData)) { + $responseOrTemplate->getBody()->write($this->fetch($templateOrData, $data)); - return $response; + return $responseOrTemplate; + } elseif (is_string($responseOrTemplate) && is_array($templateOrData)) { + if (!isset($this->responseFactory)) { + throw new RuntimeException('Response factory is not defined'); + } + + $response = $this->responseFactory->createResponse(); + $response->getBody()->write($this->fetch($responseOrTemplate, $templateOrData)); + + return $response; + } else { + throw new InvalidArgumentException(sprintf( + 'Invalid arguments were passed to the %s method', + __METHOD__ + )); + } } /** diff --git a/tests/TwigTest.php b/tests/TwigTest.php index 0356abc..a58c270 100644 --- a/tests/TwigTest.php +++ b/tests/TwigTest.php @@ -14,11 +14,9 @@ use Psr\Http\Message\ServerRequestInterface; use RuntimeException; use Slim\Views\Twig; -use Slim\Views\TwigContext; use Twig\Extension\ExtensionInterface; use Twig\Extension\RuntimeExtensionInterface; use Twig\Loader\ArrayLoader; -use Twig\Loader\FilesystemLoader; use Twig\Loader\LoaderInterface; use Twig\RuntimeLoader\RuntimeLoaderInterface; @@ -98,7 +96,7 @@ public function testAddRuntimeLoader() // Mock a runtime loader. $runtimeLoader = $this->getMockBuilder(RuntimeLoaderInterface::class) - ->setMethods(['load']) + ->onlyMethods(['load']) ->getMock(); // The method `load` should be called once and should return the mocked runtime extension. @@ -267,11 +265,15 @@ public function testMultipleDirectoriesWithoutNamespaces() $this->assertEquals("

Hi, my name is Peter and I am male.

\n", $multiDirectory); } - public function testRender() + /** + * The auxiliary method for the testRender...() methods + */ + private function getViewAndMocksForRender(): array { $loader = new ArrayLoader([ 'example.html' => "

Hi, my name is {{ name }}.

\n" ]); + $view = new Twig($loader); $mockBody = $this->getMockBuilder('Psr\Http\Message\StreamInterface') @@ -291,12 +293,59 @@ public function testRender() ->method('getBody') ->willReturn($mockBody); - $response = $view->render($mockResponse, 'example.html', [ - 'name' => 'Josh' - ]); + $mockResponseFactory = $this->getMockBuilder('Psr\Http\Message\ResponseFactoryInterface') + ->disableOriginalConstructor() + ->getMock(); + + return [$view, $mockResponse, $mockResponseFactory]; + } + + public function testRenderWithoutResponseFactory() + { + [$view, $mockResponse] = $this->getViewAndMocksForRender(); + + $response = $view->render($mockResponse, 'example.html', ['name' => 'Josh']); $this->assertInstanceOf(ResponseInterface::class, $response); } + public function testRenderWithResponseFactory() + { + [$view, $mockResponse, $mockResponseFactory] = $this->getViewAndMocksForRender(); + + $mockResponseFactory->expects($this->once()) + ->method('createResponse') + ->willReturn($mockResponse); + + $view->setResponseFactory($mockResponseFactory); + + $response = $view->render('example.html', ['name' => 'Josh']); + $this->assertInstanceOf(ResponseInterface::class, $response); + } + + public function testInvalidArgumentInRender() + { + $loader = new ArrayLoader([ + 'example.html' => "

Hi, my name is {{ name }}.

\n" + ]); + $view = new Twig($loader); + + $this->expectExceptionMessage('Invalid arguments were passed to the Slim\Views\Twig::render method'); + + $view->render('example.html', 'example.html'); + } + + public function testNotDefinedResponseFactoryInRender() + { + $loader = new ArrayLoader([ + 'example.html' => "

Hi, my name is {{ name }}.

\n" + ]); + $view = new Twig($loader); + + $this->expectExceptionMessage('Response factory is not defined'); + + $view->render('example.html', ['name' => 'Josh']); + } + public function testGetLoader() { $loader = $this->createMock(LoaderInterface::class);