Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure fetchTemplate method always returns string #107

Merged
merged 5 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,5 +199,7 @@ Hello <?= html($name) ?>

## Exceptions

* `\RuntimeException` - If template does not exist
* `\Slim\Views\Exception\PhpTemplateNotFoundException` - If template layout does not exist
* `\Slim\Views\Exception\PhpTemplateNotFoundException` - If template does not exist
* `\RuntimeException` - If the template output could not be fetched
* `\InvalidArgumentException` - If $data contains 'template'
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
parameters:
level: 5
level: 8
paths:
- src
- tests
52 changes: 28 additions & 24 deletions src/PhpRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,24 @@

use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
use Slim\Views\Exception\PhpTemplateNotFoundException;
use Throwable;

class PhpRenderer
{
protected string $templatePath;

/**
* @var array<string, mixed>
*/
protected array $attributes;

protected string $layout;

/**
* @param string $templatePath
* @param array $attributes
* @param array<string, mixed> $attributes
* @param string $layout
*/
public function __construct(string $templatePath = '', array $attributes = [], string $layout = '')
Expand All @@ -37,12 +41,12 @@ public function __construct(string $templatePath = '', array $attributes = [], s

/**
* @param ResponseInterface $response
* @param string $template
* @param array $data
*
* @return ResponseInterface
* @param string $template
* @param array<string, mixed> $data
*
* @throws Throwable
*
* @return ResponseInterface
*/
public function render(ResponseInterface $response, string $template, array $data = []): ResponseInterface
{
Expand All @@ -59,16 +63,12 @@ public function getLayout(): string
return $this->layout;
}

/**
* @param string $layout
*/

/**
* @param string $layout
*
* @return void
*
* @throws PhpTemplateNotFoundException
*
* @return void
*/
public function setLayout(string $layout): void
{
Expand All @@ -80,15 +80,15 @@ public function setLayout(string $layout): void
}

/**
* @return array
* @return array<string, mixed>
*/
public function getAttributes(): array
{
return $this->attributes;
}

/**
* @param array $attributes
* @param array<string, mixed> $attributes
*
* @return void
*/
Expand All @@ -99,7 +99,7 @@ public function setAttributes(array $attributes): void

/**
* @param string $key
* @param $value
* @param mixed $value
*
* @return void
*/
Expand Down Expand Up @@ -140,12 +140,12 @@ public function setTemplatePath(string $templatePath): void

/**
* @param string $template
* @param array $data
* @param bool $useLayout
*
* @return string
* @param array<string, mixed> $data
* @param bool $useLayout
*
* @throws Throwable
*
* @return string
*/
public function fetch(string $template, array $data = [], bool $useLayout = false): string
{
Expand All @@ -160,11 +160,11 @@ public function fetch(string $template, array $data = [], bool $useLayout = fals

/**
* @param string $template
* @param array $data
*
* @return string
* @param array<string, mixed> $data
*
* @throws Throwable
*
* @return string
*/
public function fetchTemplate(string $template, array $data = []): string
{
Expand All @@ -173,15 +173,19 @@ public function fetchTemplate(string $template, array $data = []): string
}

if (!$this->templateExists($template)) {
throw new PhpTemplateNotFoundException('View cannot render "' . $template
. '" because the template does not exist');
throw new PhpTemplateNotFoundException(
'View cannot render "' . $template . '" because the template does not exist'
);
}

$data = array_merge($this->attributes, $data);
try {
ob_start();
$this->protectedIncludeScope($this->templatePath . $template, $data);
$output = ob_get_clean();
if ($output === false) {
throw new RuntimeException('Failed to fetch the template output');
}
} catch (Throwable $e) {
ob_end_clean();
throw $e;
Expand All @@ -205,7 +209,7 @@ public function templateExists(string $template): bool

/**
* @param string $template
* @param array $data
* @param array<string, mixed> $data
*
* @return void
*/
Expand Down
59 changes: 39 additions & 20 deletions tests/PhpRendererTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@
use Slim\Views\Exception\PhpTemplateNotFoundException;
use Slim\Views\PhpRenderer;
use Throwable;
use UnexpectedValueException;

class PhpRendererTest extends TestCase
{
public function testRenderer(): void
{
$renderer = new PhpRenderer(__DIR__ . '/_files/');
$headers = new Headers();
$body = new Stream(fopen('php://temp', 'r+'));
$body = $this->createStream();
$response = new Response(200, $headers, $body);
$newResponse = $renderer->render($response, 'template.phtml', ['hello' => 'Hi']);
$newResponse->getBody()->rewind();
Expand All @@ -36,7 +37,7 @@ public function testRenderConstructor(): void
{
$renderer = new PhpRenderer(__DIR__ . '/_files');
$headers = new Headers();
$body = new Stream(fopen('php://temp', 'r+'));
$body = $this->createStream();
$response = new Response(200, $headers, $body);
$newResponse = $renderer->render($response, 'template.phtml', ['hello' => 'Hi']);
$newResponse->getBody()->rewind();
Expand All @@ -45,12 +46,11 @@ public function testRenderConstructor(): void

public function testAttributeMerging(): void
{

$renderer = new PhpRenderer(__DIR__ . '/_files/', [
'hello' => 'Hello'
]);
$headers = new Headers();
$body = new Stream(fopen('php://temp', 'r+'));
$body = $this->createStream();
$response = new Response(200, $headers, $body);
$newResponse = $renderer->render($response, 'template.phtml', [
'hello' => 'Hi'
Expand All @@ -63,12 +63,12 @@ public function testExceptionInTemplate(): void
{
$renderer = new PhpRenderer(__DIR__ . '/_files/');
$headers = new Headers();
$body = new Stream(fopen('php://temp', 'r+'));
$body = $this->createStream();
$response = new Response(200, $headers, $body);
try {
$newResponse = $renderer->render($response, 'exception_layout.phtml');
} catch (Throwable $t) {
// Simulates an error template
// Simulates an error template
$newResponse = $renderer->render($response, 'template.phtml', [
'hello' => 'Hi'
]);
Expand All @@ -82,7 +82,7 @@ public function testExceptionForTemplateInData(): void
{
$renderer = new PhpRenderer(__DIR__ . '/_files/');
$headers = new Headers();
$body = new Stream(fopen('php://temp', 'r+'));
$body = $this->createStream();
$response = new Response(200, $headers, $body);
$this->expectException(InvalidArgumentException::class);
$renderer->render($response, 'template.phtml', [
Expand All @@ -94,7 +94,7 @@ public function testTemplateNotFound(): void
{
$renderer = new PhpRenderer(__DIR__ . '/_files/');
$headers = new Headers();
$body = new Stream(fopen('php://temp', 'r+'));
$body = $this->createStream();
$response = new Response(200, $headers, $body);
$this->expectException(PhpTemplateNotFoundException::class);
$renderer->render($response, 'adfadftemplate.phtml', []);
Expand All @@ -105,37 +105,43 @@ public function testLayout(): void
$renderer = new PhpRenderer(__DIR__ . '/_files/', ['title' => 'My App']);
$renderer->setLayout('layout.phtml');
$headers = new Headers();
$body = new Stream(fopen('php://temp', 'r+'));
$body = $this->createStream();
$response = new Response(200, $headers, $body);
$newResponse = $renderer->render($response, 'template.phtml', ['title' => 'Hello - My App', 'hello' => 'Hi']);
$newResponse->getBody()->rewind();
$this->assertEquals('<html><head><title>Hello - My App</title></head><body>Hi<footer>This is the footer'
. '</footer></body></html>', $newResponse->getBody()->getContents());
$this->assertEquals(
'<html><head><title>Hello - My App</title></head><body>Hi<footer>This is the footer'
. '</footer></body></html>',
$newResponse->getBody()->getContents()
);
}

public function testLayoutConstructor(): void
{
$renderer = new PhpRenderer(__DIR__ . '/_files', ['title' => 'My App'], 'layout.phtml');
$headers = new Headers();
$body = new Stream(fopen('php://temp', 'r+'));
$body = $this->createStream();
$response = new Response(200, $headers, $body);
$newResponse = $renderer->render($response, 'template.phtml', ['title' => 'Hello - My App', 'hello' => 'Hi']);
$newResponse->getBody()->rewind();
$this->assertEquals('<html><head><title>Hello - My App</title></head><body>Hi<footer>This is the footer'
. '</footer></body></html>', $newResponse->getBody()->getContents());
$this->assertEquals(
'<html><head><title>Hello - My App</title></head><body>Hi<footer>This is the footer'
. '</footer></body></html>',
$newResponse->getBody()->getContents()
);
}

public function testExceptionInLayout(): void
{
$renderer = new PhpRenderer(__DIR__ . '/_files/');
$renderer->setLayout('exception_layout.phtml');
$headers = new Headers();
$body = new Stream(fopen('php://temp', 'r+'));
$body = $this->createStream();
$response = new Response(200, $headers, $body);
try {
$newResponse = $renderer->render($response, 'template.phtml');
} catch (Throwable $t) {
// PHP 7+
// PHP 7+
// Simulates an error template
$renderer->setLayout('');
$newResponse = $renderer->render($response, 'template.phtml', [
Expand All @@ -159,22 +165,35 @@ public function testContentDataKeyShouldBeIgnored(): void
$renderer = new PhpRenderer(__DIR__ . '/_files/');
$renderer->setLayout('layout.phtml');
$headers = new Headers();
$body = new Stream(fopen('php://temp', 'r+'));
$body = $this->createStream();
$response = new Response(200, $headers, $body);
$newResponse = $renderer->render(
$response,
'template.phtml',
['title' => 'Hello - My App', 'hello' => 'Hi', 'content' => 'Ho']
);
$newResponse->getBody()->rewind();
$this->assertEquals('<html><head><title>Hello - My App</title></head><body>Hi<footer>This is the footer'
. '</footer></body></html>', $newResponse->getBody()->getContents());
$this->assertEquals(
'<html><head><title>Hello - My App</title></head><body>Hi<footer>This is the footer'
. '</footer></body></html>',
$newResponse->getBody()->getContents()
);
}

public function testTemplateExists()
public function testTemplateExists(): void
{
$renderer = new PhpRenderer(__DIR__ . '/_files/');
$this->assertTrue($renderer->templateExists('layout.phtml'));
$this->assertFalse($renderer->templateExists('non-existant-template'));
}

private function createStream(): Stream
{
$resource = fopen('php://temp', 'r+');
if ($resource === false) {
throw new UnexpectedValueException();
}

return new Stream($resource);
}
}
Loading