diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a05b4f8..16311a2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,12 @@ All notable changes to this project will be documented in this file, in reverse `Zend\Expressive\Template\Twig`. - `Zend\Expressive\Template\ZendViewRenderer`, replacing `Zend\Expressive\Template\ZendView`. +- [#143](https://github.com/zendframework/zend-expressive/pull/143) adds + the method `addDefaultParam($templateName, $param, $value)` to + `TemplateRendererInterface`, allowing users to specify global and + template-specific default parameters to use when rendering. To implement the + feature, the patch also provides `Zend\Expressive\Template\DefaultParamsTrait` + to simplify incorporating the feature in implementations. ### Deprecated diff --git a/composer.json b/composer.json index 99db9d27..78379cc7 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "container-interop/container-interop": "^1.1", "psr/http-message": "^1.0", "zendframework/zend-diactoros": "^1.1", + "zendframework/zend-stdlib": "^2.7", "zendframework/zend-stratigility": "^1.1" }, "require-dev": { diff --git a/doc/book/template/interface.md b/doc/book/template/interface.md index b452acb6..48c9b886 100644 --- a/doc/book/template/interface.md +++ b/doc/book/template/interface.md @@ -41,6 +41,27 @@ interface TemplateRendererInterface * @return TemplatePath[] */ public function getPaths(); + + /** + * Add a default parameter to use with a template. + * + * Use this method to provide a default parameter to use when a template is + * rendered. The parameter may be overridden by providing it when calling + * `render()`, or by calling this method again with a null value. + * + * The parameter will be specific to the template name provided. To make + * the parameter available to any template, pass the TEMPLATE_ALL constant + * for the template name. + * + * If the default parameter existed previously, subsequent invocations with + * the same template name and parameter name will overwrite. + * + * @param string $templateName Name of template to which the param applies; + * use TEMPLATE_ALL to apply to all templates. + * @param string $param Param name. + * @param mixed $value + */ + public function addDefaultParam($templateName, $param, $value); } ``` @@ -119,3 +140,56 @@ $content = $renderer->render('message', [ It is up to the underlying template engine to determine how to perform the injections. + +### Default params + +The `TemplateRendererInterface` defines the method `addDefaultParam()`. This +method can be used to specify default parameters to use when rendering a +template. The signature is: + +```php +public function addDefaultParam($templateName, $param, $value) +``` + +If you want a parameter to be used for *every* template, you can specify the +constant `TemplateRendererInterface::TEMPLATE_ALL` for the `$templateName` +parameter. + +When rendering, parameters are considered in the following order, with later +items having precedence over earlier ones: + +- Default parameters specified for all templates. +- Default parameters specified for the template specified at rendering. +- Parameters specified when rendering. + +As an example, if we did the following: + +```php +$renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'foo', 'bar'); +$renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'bar', 'baz'); +$renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'baz', 'bat'); + +$renderer->addDefaultParam('example', 'foo', 'template default foo'); +$renderer->addDefaultParam('example', 'bar', 'template default bar'); + +$content = $renderer->render('example', [ + 'foo' => 'override', +]); +``` + +Then we can expect the following substitutions will occur when rendering: + +- References to the "foo" variable will contain "override". +- References to the "bar" variable will contain "template default bar". +- References to the "baz" variable will contain "bat". + +> #### Support for default params +> +> The support for default params will often be renderer-specific. The reason is +> because the `render()` signature does not specify a type for `$params`, in +> order to allow passing alternative arguments such as view models. In such +> cases, the implementation will indicate its behavior when default parameters +> are specified, but a given `$params` argument does not support it. +> +> At the time of writing, each of the Plates, Twig, and zend-view +> implementations support the feature. diff --git a/src/Template/DefaultParamsTrait.php b/src/Template/DefaultParamsTrait.php new file mode 100644 index 00000000..d0c08a0d --- /dev/null +++ b/src/Template/DefaultParamsTrait.php @@ -0,0 +1,86 @@ +defaultParams[$templateName])) { + $this->defaultParams[$templateName] = []; + } + + $this->defaultParams[$templateName][$param] = $value; + } + + /** + * Returns merged global, template-specific and given params + * + * @param string $template + * @param array $params + * @return array + */ + private function mergeParams($template, array $params) + { + $globalDefaults = isset($this->defaultParams[TemplateRendererInterface::TEMPLATE_ALL]) + ? $this->defaultParams[TemplateRendererInterface::TEMPLATE_ALL] + : []; + + $templateDefaults = isset($this->defaultParams[$template]) + ? $this->defaultParams[$template] + : []; + + $defaults = ArrayUtils::merge($globalDefaults, $templateDefaults); + + return ArrayUtils::merge($defaults, $params); + } +} diff --git a/src/Template/PlatesRenderer.php b/src/Template/PlatesRenderer.php index 3f800cbe..b2e0b3b5 100644 --- a/src/Template/PlatesRenderer.php +++ b/src/Template/PlatesRenderer.php @@ -11,6 +11,7 @@ use League\Plates\Engine; use ReflectionProperty; +use Zend\Expressive\Exception; /** * Template implementation bridging league/plates @@ -88,6 +89,39 @@ public function getPaths() return $paths; } + /** + * {@inheritDoc} + * + * Proxies to the Plate Engine's `addData()` method. + * + * {@inheritDoc} + */ + public function addDefaultParam($templateName, $param, $value) + { + if (! is_string($templateName) || empty($templateName)) { + throw new Exception\InvalidArgumentException(sprintf( + '$templateName must be a non-empty string; received %s', + (is_object($templateName) ? get_class($templateName) : gettype($templateName)) + )); + } + + if (! is_string($param) || empty($param)) { + throw new Exception\InvalidArgumentException(sprintf( + '$param must be a non-empty string; received %s', + (is_object($param) ? get_class($param) : gettype($param)) + )); + } + + $params = [$param => $value]; + + if ($templateName === self::TEMPLATE_ALL) { + $templateName = null; + } + + $this->template->addData($params, $templateName); + } + + /** * Create a default Plates engine * diff --git a/src/Template/TemplateRendererInterface.php b/src/Template/TemplateRendererInterface.php index a98752b5..68e536c3 100644 --- a/src/Template/TemplateRendererInterface.php +++ b/src/Template/TemplateRendererInterface.php @@ -14,6 +14,11 @@ */ interface TemplateRendererInterface { + /** + * @const string Value indicating all templates; used with `addDefaultParam()`. + */ + const TEMPLATE_ALL = '*'; + /** * Render a template, optionally with parameters. * @@ -43,4 +48,25 @@ public function addPath($path, $namespace = null); * @return TemplatePath[] */ public function getPaths(); + + /** + * Add a default parameter to use with a template. + * + * Use this method to provide a default parameter to use when a template is + * rendered. The parameter may be overridden by providing it when calling + * `render()`, or by calling this method again with a null value. + * + * The parameter will be specific to the template name provided. To make + * the parameter available to any template, pass the TEMPLATE_ALL constant + * for the template name. + * + * If the default parameter existed previously, subsequent invocations with + * the same template name and parameter name will overwrite. + * + * @param string $templateName Name of template to which the param applies; + * use TEMPLATE_ALL to apply to all templates. + * @param string $param Param name. + * @param mixed $value + */ + public function addDefaultParam($templateName, $param, $value); } diff --git a/src/Template/TwigRenderer.php b/src/Template/TwigRenderer.php index 4e0350dc..0ff0e2e5 100644 --- a/src/Template/TwigRenderer.php +++ b/src/Template/TwigRenderer.php @@ -19,6 +19,7 @@ class TwigRenderer implements TemplateRendererInterface { use ArrayParametersTrait; + use DefaultParamsTrait; /** * @var string @@ -89,8 +90,14 @@ private function getDefaultLoader() */ public function render($name, $params = []) { + // Merge parameters based on requested template name + $params = $this->mergeParams($name, $this->normalizeParams($params)); + $name = $this->normalizeTemplate($name); - $params = $this->normalizeParams($params); + + // Merge parameters based on normalized template name + $params = $this->mergeParams($name, $params); + return $this->template->render($name, $params); } diff --git a/src/Template/ZendViewRenderer.php b/src/Template/ZendViewRenderer.php index 6b030c4b..a02808f7 100644 --- a/src/Template/ZendViewRenderer.php +++ b/src/Template/ZendViewRenderer.php @@ -29,6 +29,7 @@ class ZendViewRenderer implements TemplateRendererInterface { use ArrayParametersTrait; + use DefaultParamsTrait; /** * @var ViewModel @@ -118,8 +119,9 @@ public function __construct(RendererInterface $renderer = null, $layout = null) */ public function render($name, $params = []) { + $params = $this->mergeParams($name, $this->normalizeParams($params)); return $this->renderModel( - $this->createModel($name, $this->normalizeParams($params)), + $this->createModel($name, $params), $this->renderer ); } diff --git a/test/Template/PlatesRendererTest.php b/test/Template/PlatesRendererTest.php index c36bcac7..dc5aab51 100644 --- a/test/Template/PlatesRendererTest.php +++ b/test/Template/PlatesRendererTest.php @@ -184,4 +184,74 @@ public function testProperlyResolvesNamespacedTemplate() $this->assertSame($expected, $test); } + + public function testAddParameterToOneTemplate() + { + $renderer = new PlatesRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'Plates'; + $renderer->addDefaultParam('plates', 'name', $name); + $result = $renderer->render('plates'); + $content = file_get_contents(__DIR__ . '/TestAsset/plates.php'); + $content= str_replace('e($name)?>', $name, $content); + $this->assertEquals($content, $result); + + // @fixme hack to work around https://github.com/thephpleague/plates/issues/60, remove if ever merged + set_error_handler(function ($error, $message) { + $this->assertContains('Undefined variable: name', $message); + return true; + }, E_NOTICE); + $renderer->render('plates-2'); + restore_error_handler(); + + $content= str_replace('e($name)?>', '', $content); + $this->assertEquals($content, $result); + } + + public function testAddSharedParameters() + { + $renderer = new PlatesRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'Plates'; + $renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'name', $name); + $result = $renderer->render('plates'); + $content = file_get_contents(__DIR__ . '/TestAsset/plates.php'); + $content= str_replace('e($name)?>', $name, $content); + $this->assertEquals($content, $result); + $result = $renderer->render('plates-2'); + $content = file_get_contents(__DIR__ . '/TestAsset/plates-2.php'); + $content= str_replace('e($name)?>', $name, $content); + $this->assertEquals($content, $result); + } + + public function testOverrideSharedParametersPerTemplate() + { + $renderer = new PlatesRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'Plates'; + $name2 = 'Saucers'; + $renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'name', $name); + $renderer->addDefaultParam('plates-2', 'name', $name2); + $result = $renderer->render('plates'); + $content = file_get_contents(__DIR__ . '/TestAsset/plates.php'); + $content= str_replace('e($name)?>', $name, $content); + $this->assertEquals($content, $result); + $result = $renderer->render('plates-2'); + $content = file_get_contents(__DIR__ . '/TestAsset/plates-2.php'); + $content= str_replace('e($name)?>', $name2, $content); + $this->assertEquals($content, $result); + } + + public function testOverrideSharedParametersAtRender() + { + $renderer = new PlatesRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'Plates'; + $name2 = 'Saucers'; + $renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'name', $name); + $result = $renderer->render('plates', ['name' => $name2]); + $content = file_get_contents(__DIR__ . '/TestAsset/plates.php'); + $content= str_replace('e($name)?>', $name2, $content); + $this->assertEquals($content, $result); + } } diff --git a/test/Template/TestAsset/plates-2.php b/test/Template/TestAsset/plates-2.php new file mode 100644 index 00000000..571e5569 --- /dev/null +++ b/test/Template/TestAsset/plates-2.php @@ -0,0 +1,3 @@ +

This is a second template file for Plates

+ +

You are using e($name)?>!

diff --git a/test/Template/TestAsset/twig-2.html b/test/Template/TestAsset/twig-2.html new file mode 100644 index 00000000..68803930 --- /dev/null +++ b/test/Template/TestAsset/twig-2.html @@ -0,0 +1,3 @@ +

This is a second template file for Twig

+ +

You are using {{ name }}!

diff --git a/test/Template/TestAsset/zendview-2.phtml b/test/Template/TestAsset/zendview-2.phtml new file mode 100644 index 00000000..56d36182 --- /dev/null +++ b/test/Template/TestAsset/zendview-2.phtml @@ -0,0 +1,3 @@ +

This is a second template file for ZendView

+ +

You are using !

diff --git a/test/Template/TwigRendererTest.php b/test/Template/TwigRendererTest.php index 0081c2dc..4ecd0361 100644 --- a/test/Template/TwigRendererTest.php +++ b/test/Template/TwigRendererTest.php @@ -175,4 +175,66 @@ public function testResolvesNamespacedTemplateWithSuffix() $this->assertSame($expected, $test); } + + public function testAddParameterToOneTemplate() + { + $renderer = new TwigRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'Twig'; + $renderer->addDefaultParam('twig', 'name', $name); + $result = $renderer->render('twig'); + + $content = file_get_contents(__DIR__ . '/TestAsset/twig.html'); + $content = str_replace('{{ name }}', $name, $content); + $this->assertEquals($content, $result); + } + + public function testAddSharedParameters() + { + $renderer = new TwigRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'Twig'; + $renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'name', $name); + $result = $renderer->render('twig'); + $content = file_get_contents(__DIR__ . '/TestAsset/twig.html'); + $content = str_replace('{{ name }}', $name, $content); + $this->assertEquals($content, $result); + + $result = $renderer->render('twig-2'); + $content = file_get_contents(__DIR__ . '/TestAsset/twig-2.html'); + $content = str_replace('{{ name }}', $name, $content); + $this->assertEquals($content, $result); + } + + public function testOverrideSharedParametersPerTemplate() + { + $renderer = new TwigRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'Twig'; + $name2 = 'Template'; + $renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'name', $name); + $renderer->addDefaultParam('twig-2', 'name', $name2); + $result = $renderer->render('twig'); + $content = file_get_contents(__DIR__ . '/TestAsset/twig.html'); + $content = str_replace('{{ name }}', $name, $content); + $this->assertEquals($content, $result); + + $result = $renderer->render('twig-2'); + $content = file_get_contents(__DIR__ . '/TestAsset/twig-2.html'); + $content = str_replace('{{ name }}', $name2, $content); + $this->assertEquals($content, $result); + } + + public function testOverrideSharedParametersAtRender() + { + $renderer = new TwigRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'Twig'; + $name2 = 'Template'; + $renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'name', $name); + $result = $renderer->render('twig', ['name' => $name2]); + $content = file_get_contents(__DIR__ . '/TestAsset/twig.html'); + $content = str_replace('{{ name }}', $name2, $content); + $this->assertEquals($content, $result); + } } diff --git a/test/Template/ZendViewRendererTest.php b/test/Template/ZendViewRendererTest.php index 01b6eb32..18c5bf3b 100644 --- a/test/Template/ZendViewRendererTest.php +++ b/test/Template/ZendViewRendererTest.php @@ -250,4 +250,66 @@ public function testProperlyResolvesNamespacedTemplate() $this->assertSame($expected, $test); } + + public function testAddParameterToOneTemplate() + { + $renderer = new ZendViewRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'ZendView'; + $renderer->addDefaultParam('zendview', 'name', $name); + $result = $renderer->render('zendview'); + + $content = file_get_contents(__DIR__ . '/TestAsset/zendview.phtml'); + $content = str_replace('', $name, $content); + $this->assertEquals($content, $result); + } + + public function testAddSharedParameters() + { + $renderer = new ZendViewRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'ZendView'; + $renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'name', $name); + $result = $renderer->render('zendview'); + $content = file_get_contents(__DIR__ . '/TestAsset/zendview.phtml'); + $content = str_replace('', $name, $content); + $this->assertEquals($content, $result); + + $result = $renderer->render('zendview-2'); + $content = file_get_contents(__DIR__ . '/TestAsset/zendview-2.phtml'); + $content = str_replace('', $name, $content); + $this->assertEquals($content, $result); + } + + public function testOverrideSharedParametersPerTemplate() + { + $renderer = new ZendViewRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'Zend'; + $name2 = 'View'; + $renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'name', $name); + $renderer->addDefaultParam('zendview-2', 'name', $name2); + $result = $renderer->render('zendview'); + $content = file_get_contents(__DIR__ . '/TestAsset/zendview.phtml'); + $content = str_replace('', $name, $content); + $this->assertEquals($content, $result); + + $result = $renderer->render('zendview-2'); + $content = file_get_contents(__DIR__ . '/TestAsset/zendview-2.phtml'); + $content = str_replace('', $name2, $content); + $this->assertEquals($content, $result); + } + + public function testOverrideSharedParametersAtRender() + { + $renderer = new ZendViewRenderer(); + $renderer->addPath(__DIR__ . '/TestAsset'); + $name = 'Zend'; + $name2 = 'View'; + $renderer->addDefaultParam($renderer::TEMPLATE_ALL, 'name', $name); + $result = $renderer->render('zendview', ['name' => $name2]); + $content = file_get_contents(__DIR__ . '/TestAsset/zendview.phtml'); + $content = str_replace('', $name2, $content); + $this->assertEquals($content, $result); + } }