diff --git a/README.md b/README.md index 341816a..5e13957 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,89 @@ -# Dhii - Php Template - -[![Build Status](https://travis-ci.org/dhii/php-template.svg?branch=master)](https://travis-ci.org/dhii/php-template) -[![Code Climate](https://codeclimate.com/github/Dhii/php-template/badges/gpa.svg)](https://codeclimate.com/github/Dhii/php-template) -[![Test Coverage](https://codeclimate.com/github/Dhii/php-template/badges/coverage.svg)](https://codeclimate.com/github/Dhii/php-template/coverage) -[![Latest Stable Version](https://poser.pugx.org/dhii/php-template/version)](https://packagist.org/packages/dhii/php-template) -[![This package complies with Dhii standards](https://img.shields.io/badge/Dhii-Compliant-green.svg?style=flat-square)][Dhii] - -A concrete PHP (PHTML) template implementation. - -[Dhii]: https://github.com/Dhii/dhii +# Dhii - Php Template + +[![Build Status](https://travis-ci.org/dhii/php-template.svg?branch=develop)](https://travis-ci.org/dhii/php-template) +[![Code Climate](https://codeclimate.com/github/Dhii/php-template/badges/gpa.svg)](https://codeclimate.com/github/Dhii/php-template) +[![Test Coverage](https://codeclimate.com/github/Dhii/php-template/badges/coverage.svg)](https://codeclimate.com/github/Dhii/php-template/coverage) +[![Latest Stable Version](https://poser.pugx.org/dhii/php-template/version)](https://packagist.org/packages/dhii/php-template) + +A concrete PHP (PHTML) template implementation. + +## Details +This is an implementation of the [Dhii output renderer standard][dhii/output-renderer-interface]; +specifically, the template. It allows consuming file-based PHP templates using an abstract interface. +Now it's possible to outsource your rendering to standardized, stateless PHP template files, +and use them just like any other template, without knowing where the content comes from. + +Because this implementation is based on PHP files, it was decided to separate the concern +of rendering a template from the concern of evaluating a PHP file, because the latter +is useful on its own, and because it would make the template implementation thinner +and cleaner. + +### Usage +Below examples explain how a template factory could be configured, and used to produce a +standards-compliant template. Then that template is rendered with context. Please note the following: + +1. The file at path `template.php` is used to produce the output. +2. Context members are retrieved by `$c('key')`. +3. It is possible to use the `uc` function with `$f('uc')`. +4. The default context member `time` is present in the template, even though it was not explicitly supplied +at render time. + +#### Configuration, usually in a service definition +```php +use Dhii\Output\PhpEvaluator\FilePhpEvaluatorFactory; +use Dhii\Output\Template\PhpTemplate\FilePathTemplateFactory; +use Dhii\Output\Template\PathTemplateFactoryInterface; + +function (): PathTemplateFactoryInterface { + return new FilePathTemplateFactory( + new FilePhpEvaluatorFactory(), + [ // This will be available by default in all contexts of all templates made by this factory + 'time' => time(), // Let's assume it's 1586364371 + ], + [ // This will be available by default in all templates made by this factory + 'uc' => function (string $string) { + return strtoupper($string); + }, + ] + ); +}; +``` + +#### Consumption, usually somewhere in controller-level code +```php +use Dhii\Output\Template\PathTemplateFactoryInterface; +use Dhii\Output\Template\PhpTemplate\FilePathTemplateFactory; + +/* @var $fileTemplateFactory FilePathTemplateFactory */ +(function (PathTemplateFactoryInterface $factory) { + $template = $factory->fromPath('template.php'); + echo $template->render([ + 'username' => 'jcdenton', + 'password' => 'bionicman', + 'status' => 'defected', + ]); +})($fileTemplateFactory); // This is the factory created by above configuration +``` + +#### template.php +```php +/* @var $c callable */ +/* @var $f callable */ +?> + +
+
+ +``` + +#### Resulting output +```html +1586364371 +jcdenton
+bionicman
+DEFECTED +``` + + +[Dhii]: https://github.com/Dhii/dhii +[dhii/output-renderer-interface]: https://travis-ci.org/Dhii/output-renderer-interface diff --git a/src/Template/PhpTemplate/FilePathTemplateFactory.php b/src/Template/PhpTemplate/FilePathTemplateFactory.php new file mode 100644 index 0000000..8369b31 --- /dev/null +++ b/src/Template/PhpTemplate/FilePathTemplateFactory.php @@ -0,0 +1,54 @@ +evaluatorFactory = $evaluatorFactory; + $this->defaultContext = $defaultContext; + $this->functions = $functions; + } + + /** + * @inheritDoc + */ + public function fromPath(string $templatePath): TemplateInterface + { + $evaluator = $this->evaluatorFactory->fromFilePath($templatePath); + $template = new PhpTemplate($evaluator, $this->defaultContext, $this->functions); + + return $template; + } +} diff --git a/test/functional/Template/PhpTemplateTest.php b/test/functional/Template/PhpTemplateTest.php index 16c3ba0..81aa42e 100644 --- a/test/functional/Template/PhpTemplateTest.php +++ b/test/functional/Template/PhpTemplateTest.php @@ -4,8 +4,13 @@ use ArrayAccess; use ArrayObject; +use Dhii\Output\PhpEvaluator\FilePhpEvaluatorFactory; +use Dhii\Output\PhpEvaluator\FilePhpEvaluatorFactoryInterface; use Dhii\Output\PhpEvaluator\PhpEvaluatorInterface; +use Dhii\Output\Template\PhpTemplate\FilePathTemplateFactory; use Exception; +use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamDirectory; use PHPUnit\Framework\TestCase; use Dhii\Output\Template\PhpTemplate as TestSubject; use PHPUnit_Framework_MockObject_MockObject as MockObject; @@ -16,6 +21,15 @@ */ class PhpTemplateTest extends TestCase { + /** @var vfsStreamDirectory */ + protected $fs; + + public function setUp() + { + parent::setUp(); + $this->fs = vfsStream::setup(); + } + /** * Creates a new instance of the test subject. * @@ -96,6 +110,42 @@ public function createContainer(array $data): ContainerInterface return $mock; } + /** + * Creates a new evaluator factory mock instance. + * + * @return FilePhpEvaluatorFactory&MockObject The new instance. + */ + public function createEvaluatorFactory(): FilePhpEvaluatorFactory + { + $mock = $this->getMockBuilder(FilePhpEvaluatorFactory::class) + ->setMethods(null) + ->getMock(); + + return $mock; + } + + /** + * Creates a new template factory mock instance. + * + * @param FilePhpEvaluatorFactoryInterface $evaluatorFactory + * @param array $defaultContext + * @param array $functions + * + * @return FilePathTemplateFactory&MockObject The new factory mock. + */ + public function createFactory( + FilePhpEvaluatorFactoryInterface $evaluatorFactory, + array $defaultContext, + array $functions + ): FilePathTemplateFactory { + $mock = $this->getMockBuilder(FilePathTemplateFactory::class) + ->setMethods(null) + ->setConstructorArgs([$evaluatorFactory, $defaultContext, $functions]) + ->getMock(); + + return $mock; + } + /** * Provides data useful for testing rendering with different contexts. * @@ -116,6 +166,69 @@ public function renderDataProvider(): array ]; } + /** + * Creates a virtual file with content, and retrieves its path. + * + * @param string $content The content for the file. + * + * @return string The path to the file. + */ + public function getFilePath(string $content): string + { + $fileName = uniqid() . '.php'; + $filePath = $this->fs->url() . "/$fileName"; + + file_put_contents($filePath, $content); + + return $filePath; + } + + /** + * Tests that a factory creates a template that produces correct output. + * + * This is an end-to-end test which tests all of the functionality related to a PHP template together: + * + * - It uses a real evaluator factory and real evaluators, which evaluate a real PHP template file + * in a real, albeit virtualized, filesystem. + * - It uses a real PHP template implementation. + * - It uses a real template, which has + * * Explicit PHP output (by using `echo `). + * * Implicit PHP output (by just having content outside of PHP). + * * Retrieves a value from context explicitly provided at render time. + * * Retrieves a value from default context provided via the factory. + * * Passes value through a custom function provided via the factory. + * + * Pretty much, this tests the whole happy path from start to finish. + */ + public function testFactoryE2e() + { + { + $evalFactory = $this->createEvaluatorFactory(); + $defaultKey = uniqid('default-key'); + $defaultValue = uniqid('default-value'); + $contextKey = uniqid('context-key'); + $contextValue = uniqid('context-value'); + $defaultContext = [$defaultKey => $defaultValue]; + $funcName = uniqid('func-name'); + $func = function ($value) { return substr($value, 0, 15); }; + $functions = [$funcName => $func]; + $context = [$contextKey => $contextValue]; + $contentSeparator = uniqid('separator'); + $content = "$contentSeparator"; + $funcResult = $func($contextValue); + $expectedOutput = "{$defaultValue}{$contentSeparator}{$funcResult}"; + $subject = $this->createFactory($evalFactory, $defaultContext, $functions); + $path = $this->getFilePath($content); + } + + { + $result = $subject->fromPath($path); + $output = $result->render($context); + + $this->assertEquals($expectedOutput, $output); + } + } + /** * Tests that rendering works correctly. *