Skip to content

Commit

Permalink
Merge pull request #2 from Dhii/task/template-factory
Browse files Browse the repository at this point in the history
  • Loading branch information
XedinUnknown authored Jul 7, 2020
2 parents 8c8c27c + 2ead413 commit 7473f66
Show file tree
Hide file tree
Showing 3 changed files with 256 additions and 11 deletions.
100 changes: 89 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 */
?>
<span class="current-time"><?= $c('time') ?><span />
<span class="username"><?= $c('username') ?></span><br />
<span class="password"><?= $c('password') ?></span><br />
<span class="status"><?= $f('uc', $c('status')) ?></span>
```

#### Resulting output
```html
<span class="current-time">1586364371<span />
<span class="username">jcdenton</span><br />
<span class="password">bionicman</span><br />
<span class="status">DEFECTED</span>
```


[Dhii]: https://github.com/Dhii/dhii
[dhii/output-renderer-interface]: https://travis-ci.org/Dhii/output-renderer-interface
54 changes: 54 additions & 0 deletions src/Template/PhpTemplate/FilePathTemplateFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);

namespace Dhii\Output\Template\PhpTemplate;

use Dhii\Output\PhpEvaluator\FilePhpEvaluatorFactoryInterface;
use Dhii\Output\Template\PathTemplateFactoryInterface;
use Dhii\Output\Template\PhpTemplate;
use Dhii\Output\Template\TemplateInterface;

/**
* @inheritDoc
*/
class FilePathTemplateFactory implements PathTemplateFactoryInterface
{
/**
* @var FilePhpEvaluatorFactoryInterface
*/
protected $evaluatorFactory;
/**
* @var array
*/
protected $defaultContext;
/**
* @var array
*/
protected $functions;

/**
* @param FilePhpEvaluatorFactoryInterface $evaluatorFactory A factory that creates PHP file evaluators.
* @param array $defaultContext A map of keys to values that will be available in template context by default.
* @param array $functions A map of keys to callables that will be available to templates by default.
*/
public function __construct(
FilePhpEvaluatorFactoryInterface $evaluatorFactory,
array $defaultContext,
array $functions
) {
$this->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;
}
}
113 changes: 113 additions & 0 deletions test/functional/Template/PhpTemplateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand All @@ -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 = "<?php echo \$c('$defaultKey') ?>$contentSeparator<?php echo \$f('$funcName', \$c('$contextKey')) ?>";
$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.
*
Expand Down

0 comments on commit 7473f66

Please sign in to comment.