diff --git a/.gitattributes b/.gitattributes index 872b841b..c153ad6f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ +/templates export-ignore /tests export-ignore /.gitattributes export-ignore /.gitignore export-ignore diff --git a/README.md b/README.md index 82dd8135..e190c8da 100644 --- a/README.md +++ b/README.md @@ -147,8 +147,13 @@ final class MyHandler implements RequestHandlerInterface ### Formatters -We provide some basic formatters by default: `Json`, `StringCast`, and -`JmsSerializer` (this one requires you to also install `jms/serializer`, sure). +We provide some basic formatters by default: + +* `Json` +* `StringCast` +* `JmsSerializer` (requires you to also install and configure [`jms/serializer`](https://jmsyst.com/libs/serializer)) +* `Plates` (requires you to also install and configure [`league/plates`](http://platesphp.com)) +* `Twig` (requires you to also install and configure [`twig/twig`](https://twig.symfony.com)) If you want to create a customised formatter the only thing needed is to implement the `Formatter` interface: @@ -163,7 +168,7 @@ use Lcobucci\ContentNegotiation\Formatter; final class MyFancyFormatter implements Formatter { - public function format($content): string + public function format($content, array $attributes = []): string { // Performs all the magic with $content and creates $result with a // `string` containing the formatted data. diff --git a/composer.json b/composer.json index 538ab779..81084f8a 100644 --- a/composer.json +++ b/composer.json @@ -27,17 +27,21 @@ "doctrine/coding-standard": "^4.0", "infection/infection": "^0.8", "jms/serializer": "^1.11", + "league/plates": "^3.3", "middlewares/negotiation": "^1.0", "phpstan/phpstan": "^0.10@dev", "phpstan/phpstan-phpunit": "^0.10@dev", "phpstan/phpstan-strict-rules": "^0.10@dev", "phpunit/phpunit": "^7.0", "squizlabs/php_codesniffer": "^3.2", + "twig/twig": "^2.0", "zendframework/zend-diactoros": "^1.7" }, "suggest": { "jms/serializer": "For content formatting using a more flexible serializer", + "league/plates": "For content formatting using Plates as template engine", "middlewares/negotiation": "For acceptable format identification", + "twig/twig": "For content formatting using Twig as template engine", "zendframework/zend-diactoros": "For concrete implementation of PSR-7" }, "autoload": { diff --git a/src/Formatter/Plates.php b/src/Formatter/Plates.php new file mode 100644 index 00000000..b2ad8f12 --- /dev/null +++ b/src/Formatter/Plates.php @@ -0,0 +1,59 @@ +engine = $engine; + $this->attributeName = $attributeName; + } + + /** + * {@inheritdoc} + */ + public function format($content, array $attributes = []): string + { + try { + return $this->render($content, $attributes); + } catch (Throwable $exception) { + throw new ContentCouldNotBeFormatted( + 'An error occurred while formatting using plates', + $exception->getCode(), + $exception + ); + } + } + + /** + * @param mixed $content + * @param mixed[] $attributes + * + * @throws Throwable + */ + private function render($content, array $attributes = []): string + { + $template = $attributes[$this->attributeName] ?? ''; + + return $this->engine->render($template, ['content' => $content]); + } +} diff --git a/src/Formatter/Twig.php b/src/Formatter/Twig.php new file mode 100644 index 00000000..fb0a21d2 --- /dev/null +++ b/src/Formatter/Twig.php @@ -0,0 +1,61 @@ +environment = $environment; + $this->attributeName = $attributeName; + } + + /** + * {@inheritdoc} + */ + public function format($content, array $attributes = []): string + { + try { + return $this->render($content, $attributes); + } catch (Throwable $exception) { + throw new ContentCouldNotBeFormatted( + 'An error occurred while formatting using twig', + $exception->getCode(), + $exception + ); + } + } + + /** + * @param mixed $content + * @param mixed[] $attributes + * + * @throws Throwable + */ + private function render($content, array $attributes = []): string + { + $template = $attributes[$this->attributeName] ?? ''; + + return $this->environment->render($template, ['content' => $content]); + } +} diff --git a/tests/Formatter/templates/naive/person.html b/templates/naive/person.html similarity index 100% rename from tests/Formatter/templates/naive/person.html rename to templates/naive/person.html diff --git a/templates/plates/person.php b/templates/plates/person.php new file mode 100644 index 00000000..d494b12c --- /dev/null +++ b/templates/plates/person.php @@ -0,0 +1,9 @@ + +
+
+
Identifier
+
e($content->id)?>
+
Name
+
e($content->name)?>
+
+
diff --git a/templates/twig/person.twig b/templates/twig/person.twig new file mode 100644 index 00000000..b6e7e5e7 --- /dev/null +++ b/templates/twig/person.twig @@ -0,0 +1,8 @@ +
+
+
Identifier
+
{{ content.id }}
+
Name
+
{{ content.name }}
+
+
diff --git a/tests/Formatter/NaiveTemplateEngine.php b/tests/Formatter/NaiveTemplateEngine.php index fff5e125..4a1f75ee 100644 --- a/tests/Formatter/NaiveTemplateEngine.php +++ b/tests/Formatter/NaiveTemplateEngine.php @@ -14,7 +14,7 @@ final class NaiveTemplateEngine implements Formatter { - private const BASE_DIR = __DIR__ . '/templates/naive/'; + private const BASE_DIR = __DIR__ . '/../../templates/naive/'; private const EXTENSION = 'html'; /** diff --git a/tests/Formatter/PlatesTest.php b/tests/Formatter/PlatesTest.php new file mode 100644 index 00000000..c4867464 --- /dev/null +++ b/tests/Formatter/PlatesTest.php @@ -0,0 +1,79 @@ +engine = new Engine(dirname(__DIR__, 2) . '/templates/plates'); + } + + /** + * @test + * + * @covers ::__construct() + * @covers ::format() + * @covers ::render() + */ + public function formatShouldReturnContentFormattedByPlates(): void + { + $formatter = new Plates($this->engine); + $content = $formatter->format(new PersonDto(1, 'Testing'), ['template' => 'person']); + + self::assertContains('
1
', $content); + self::assertContains('
Testing
', $content); + } + + /** + * @test + * + * @covers ::__construct() + * @covers ::format() + * @covers ::render() + */ + public function formatShouldReadTemplateNameFromCustomAttribute(): void + { + $formatter = new Plates($this->engine, 'fancy!'); + $content = $formatter->format(new PersonDto(1, 'Testing'), ['fancy!' => 'person']); + + self::assertContains('
1
', $content); + self::assertContains('
Testing
', $content); + } + + /** + * @test + * + * @covers ::__construct() + * @covers ::format() + * @covers ::render() + */ + public function formatShouldConvertAnyPlatesException(): void + { + $formatter = new Plates($this->engine); + + $this->expectException(ContentCouldNotBeFormatted::class); + $this->expectExceptionMessage('An error occurred while formatting using plates'); + + $formatter->format(new PersonDto(1, 'Testing'), ['template' => 'no-template-at-all']); + } +} diff --git a/tests/Formatter/TwigTest.php b/tests/Formatter/TwigTest.php new file mode 100644 index 00000000..ba8f7f2e --- /dev/null +++ b/tests/Formatter/TwigTest.php @@ -0,0 +1,82 @@ +environment = new Twig_Environment( + new Twig_Loader_Filesystem('templates/twig', dirname(__DIR__, 2) . '/') + ); + } + + /** + * @test + * + * @covers ::__construct() + * @covers ::format() + * @covers ::render() + */ + public function formatShouldReturnContentFormattedByPlates(): void + { + $formatter = new Twig($this->environment); + $content = $formatter->format(new PersonDto(1, 'Testing'), ['template' => 'person.twig']); + + self::assertContains('
1
', $content); + self::assertContains('
Testing
', $content); + } + + /** + * @test + * + * @covers ::__construct() + * @covers ::format() + * @covers ::render() + */ + public function formatShouldReadTemplateNameFromCustomAttribute(): void + { + $formatter = new Twig($this->environment, 'fancy!'); + $content = $formatter->format(new PersonDto(1, 'Testing'), ['fancy!' => 'person.twig']); + + self::assertContains('
1
', $content); + self::assertContains('
Testing
', $content); + } + + /** + * @test + * + * @covers ::__construct() + * @covers ::format() + * @covers ::render() + */ + public function formatShouldConvertAnyTwigException(): void + { + $formatter = new Twig($this->environment); + + $this->expectException(ContentCouldNotBeFormatted::class); + $this->expectExceptionMessage('An error occurred while formatting using twig'); + + $formatter->format(new PersonDto(1, 'Testing'), ['template' => 'no-template-at-all']); + } +}