├── src ├── Exception │ ├── ExceptionInterface.php │ ├── InvalidConfigException.php │ ├── InvalidExtensionException.php │ └── InvalidRuntimeLoaderException.php ├── ConfigProvider.php ├── TwigExtensionFactory.php ├── TwigRendererFactory.php ├── TwigRenderer.php ├── TwigExtension.php └── TwigEnvironmentFactory.php ├── LICENSE.md ├── composer.json ├── README.md └── CHANGELOG.md /src/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | $this->getDependencies(), 22 | 'templates' => $this->getTemplates(), 23 | ]; 24 | } 25 | 26 | public function getDependencies() : array 27 | { 28 | return [ 29 | 'aliases' => [ 30 | TemplateRendererInterface::class => TwigRenderer::class, 31 | Twig_Environment::class => Environment::class 32 | ], 33 | 'factories' => [ 34 | Environment::class => TwigEnvironmentFactory::class, 35 | TwigExtension::class => TwigExtensionFactory::class, 36 | TwigRenderer::class => TwigRendererFactory::class, 37 | ], 38 | ]; 39 | } 40 | 41 | public function getTemplates() : array 42 | { 43 | return [ 44 | 'extension' => 'html.twig', 45 | 'paths' => [], 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2019, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/TwigExtensionFactory.php: -------------------------------------------------------------------------------- 1 | has(ServerUrlHelper::class)) { 24 | throw new InvalidConfigException(sprintf( 25 | 'Missing required `%s` dependency.', 26 | ServerUrlHelper::class 27 | )); 28 | } 29 | 30 | if (! $container->has(UrlHelper::class)) { 31 | throw new InvalidConfigException(sprintf( 32 | 'Missing required `%s` dependency.', 33 | UrlHelper::class 34 | )); 35 | } 36 | 37 | $config = $container->has('config') ? $container->get('config') : []; 38 | $config = TwigRendererFactory::mergeConfig($config); 39 | 40 | return new TwigExtension( 41 | $container->get(ServerUrlHelper::class), 42 | $container->get(UrlHelper::class), 43 | $config['assets_url'] ?? '', 44 | $config['assets_version'] ?? '', 45 | $config['globals'] ?? [] 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zendframework/zend-expressive-twigrenderer", 3 | "description": "Twig integration for Expressive", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "expressive", 7 | "http", 8 | "middleware", 9 | "psr", 10 | "psr-7", 11 | "twig", 12 | "zf", 13 | "zendframework", 14 | "zend-expressive" 15 | ], 16 | "support": { 17 | "issues": "https://github.com/zendframework/zend-expressive-twigrenderer/issues", 18 | "source": "https://github.com/zendframework/zend-expressive-twigrenderer", 19 | "rss": "https://github.com/zendframework/zend-expressive-twigrenderer/releases.atom", 20 | "chat": "https://zendframework-slack.herokuapp.com", 21 | "forum": "https://discourse.zendframework.com/c/questions/expressive" 22 | }, 23 | "require": { 24 | "php": "^7.1", 25 | "psr/container": "^1.0", 26 | "twig/twig": "^1.34 || ^2.4 || ^3.0", 27 | "zendframework/zend-expressive-helpers": "^5.0", 28 | "zendframework/zend-expressive-router": "^3.0", 29 | "zendframework/zend-expressive-template": "^2.0" 30 | }, 31 | "require-dev": { 32 | "malukenho/docheader": "^0.1.5", 33 | "phpunit/phpunit": "^7.5.17 || ^8.4.3", 34 | "zendframework/zend-coding-standard": "~1.0.0" 35 | }, 36 | "conflict": { 37 | "container-interop/container-interop": "<1.2.0" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "Zend\\Expressive\\Twig\\": "src/" 42 | } 43 | }, 44 | "autoload-dev": { 45 | "psr-4": { 46 | "ZendTest\\Expressive\\Twig\\": "test/" 47 | } 48 | }, 49 | "config": { 50 | "sort-packages": true 51 | }, 52 | "extra": { 53 | "branch-alias": { 54 | "dev-master": "2.5.x-dev", 55 | "dev-develop": "2.6.x-dev" 56 | }, 57 | "zf": { 58 | "config-provider": "Zend\\Expressive\\Twig\\ConfigProvider" 59 | } 60 | }, 61 | "scripts": { 62 | "check": [ 63 | "@license-check", 64 | "@cs-check", 65 | "@test" 66 | ], 67 | "cs-check": "phpcs", 68 | "cs-fix": "phpcbf", 69 | "test": "phpunit --colors=always", 70 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", 71 | "license-check": "docheader check src/ test/" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/TwigRendererFactory.php: -------------------------------------------------------------------------------- 1 | has('config') ? $container->get('config') : []; 40 | $config = self::mergeConfig($config); 41 | $environment = $this->getEnvironment($container); 42 | 43 | return new TwigRenderer($environment, $config['extension'] ?? 'html.twig'); 44 | } 45 | 46 | /** 47 | * Merge expressive templating config with twig config. 48 | * 49 | * Pulls the `templates` and `twig` top-level keys from the configuration, 50 | * if present, and then returns the merged result, with those from the twig 51 | * array having precedence. 52 | * 53 | * @param array|ArrayObject $config 54 | * @throws Exception\InvalidConfigException if a non-array, non-ArrayObject 55 | * $config is received. 56 | */ 57 | public static function mergeConfig($config) : array 58 | { 59 | $config = $config instanceof ArrayObject ? $config->getArrayCopy() : $config; 60 | 61 | if (! is_array($config)) { 62 | throw new Exception\InvalidConfigException(sprintf( 63 | 'Config service MUST be an array or ArrayObject; received %s', 64 | is_object($config) ? get_class($config) : gettype($config) 65 | )); 66 | } 67 | 68 | $expressiveConfig = isset($config['templates']) && is_array($config['templates']) 69 | ? $config['templates'] 70 | : []; 71 | $twigConfig = isset($config['twig']) && is_array($config['twig']) 72 | ? $config['twig'] 73 | : []; 74 | 75 | return array_replace_recursive($expressiveConfig, $twigConfig); 76 | } 77 | 78 | /** 79 | * Retrieve and return the TwigEnvironment instance. 80 | * 81 | * If upgrading from a previous version of this package, developers will 82 | * not have registered the TwigEnvironment service yet; this method will 83 | * create it using the TwigEnvironmentFactory, but emit a deprecation 84 | * notice indicating the developer should update their configuration. 85 | * 86 | * If the service is registered, it is simply pulled and returned. 87 | * 88 | * @throws LoaderError 89 | */ 90 | private function getEnvironment(ContainerInterface $container) : Environment 91 | { 92 | if ($container->has(Environment::class)) { 93 | return $container->get(Environment::class); 94 | } 95 | 96 | trigger_error(sprintf( 97 | '%s now expects you to register the factory %s for the service %s; ' 98 | . 'please update your dependency configuration.', 99 | __CLASS__, 100 | TwigEnvironmentFactory::class, 101 | Environment::class 102 | ), E_USER_DEPRECATED); 103 | 104 | $factory = new TwigEnvironmentFactory(); 105 | return $factory($container); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/TwigRenderer.php: -------------------------------------------------------------------------------- 1 | createTemplate($this->getDefaultLoader()); 55 | } 56 | 57 | try { 58 | $loader = $template->getLoader(); 59 | } catch (LogicException $e) { 60 | $loader = $this->getDefaultLoader(); 61 | $template->setLoader($loader); 62 | } 63 | 64 | $this->template = $template; 65 | $this->twigLoader = $loader; 66 | $this->suffix = is_string($suffix) ? $suffix : 'html'; 67 | } 68 | 69 | /** 70 | * Create a default Twig environment 71 | * 72 | * @param FilesystemLoader $loader 73 | * 74 | * @return Environment 75 | */ 76 | private function createTemplate(FilesystemLoader $loader) : Environment 77 | { 78 | return new Environment($loader); 79 | } 80 | 81 | /** 82 | * Get the default loader for template 83 | */ 84 | private function getDefaultLoader() : FilesystemLoader 85 | { 86 | return new FilesystemLoader(); 87 | } 88 | 89 | /** 90 | * Render 91 | * 92 | * @param string $name 93 | * @param array|object $params 94 | * 95 | * @return string 96 | * @throws LoaderError 97 | * @throws RuntimeError 98 | * @throws SyntaxError 99 | */ 100 | public function render(string $name, $params = []) : string 101 | { 102 | // Merge parameters based on requested template name 103 | $params = $this->mergeParams($name, $this->normalizeParams($params)); 104 | 105 | $name = $this->normalizeTemplate($name); 106 | 107 | // Merge parameters based on normalized template name 108 | $params = $this->mergeParams($name, $params); 109 | 110 | return $this->template->render($name, $params); 111 | } 112 | 113 | /** 114 | * Add a path for template 115 | * 116 | * @param string $path 117 | * @param string|null $namespace 118 | * 119 | * @throws LoaderError 120 | */ 121 | public function addPath(string $path, string $namespace = null) : void 122 | { 123 | $namespace = $namespace ?: FilesystemLoader::MAIN_NAMESPACE; 124 | $this->twigLoader->addPath($path, $namespace); 125 | } 126 | 127 | /** 128 | * Get the template directories 129 | * 130 | * @return TemplatePath[] 131 | */ 132 | public function getPaths() : array 133 | { 134 | $paths = []; 135 | foreach ($this->twigLoader->getNamespaces() as $namespace) { 136 | $name = ($namespace !== FilesystemLoader::MAIN_NAMESPACE) ? $namespace : null; 137 | 138 | foreach ($this->twigLoader->getPaths($namespace) as $path) { 139 | $paths[] = new TemplatePath($path, $name); 140 | } 141 | } 142 | return $paths; 143 | } 144 | 145 | /** 146 | * Normalize namespaced template. 147 | * 148 | * Normalizes templates in the format "namespace::template" to 149 | * "@namespace/template". 150 | * 151 | * @param string $template 152 | * 153 | * @return string 154 | */ 155 | public function normalizeTemplate(string $template) : string 156 | { 157 | $template = preg_replace('#^([^:]+)::(.*)$#', '@$1/$2', $template); 158 | if (! preg_match('#\.[a-z]+$#i', $template)) { 159 | return sprintf('%s.%s', $template, $this->suffix); 160 | } 161 | 162 | return $template; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twig Integration for Expressive 2 | 3 | > ## Repository abandoned 2019-12-31 4 | > 5 | > This repository has moved to [mezzio/mezzio-twigrenderer](https://github.com/mezzio/mezzio-twigrenderer). 6 | 7 | [![Build Status](https://secure.travis-ci.org/zendframework/zend-expressive-twigrenderer.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-expressive-twigrenderer) 8 | [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-expressive-twigrenderer/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-expressive-twigrenderer?branch=master) 9 | 10 | Provides [Twig](http://twig.sensiolabs.org/) integration for 11 | [Expressive](https://docs.zendframework.com//zend-expressive/). 12 | 13 | ## Installation 14 | 15 | Install this library using composer: 16 | 17 | ```bash 18 | $ composer require zendframework/zend-expressive-twigrenderer 19 | ``` 20 | We recommend using a dependency injection container, and typehint against 21 | [container-interop](https://github.com/container-interop/container-interop). We 22 | can recommend the following implementations: 23 | 24 | - [zend-servicemanager](https://github.com/zendframework/zend-servicemanager): 25 | `composer require zendframework/zend-servicemanager` 26 | - [pimple-interop](https://github.com/moufmouf/pimple-interop): 27 | `composer require mouf/pimple-interop` 28 | - [Aura.Di](https://github.com/auraphp/Aura.Di): `composer require aura/di` 29 | 30 | ## Twig Extension 31 | 32 | The included Twig extension adds support for url generation. The extension is automatically activated if the 33 | [UrlHelper](https://github.com/zendframework/zend-expressive-helpers#urlhelper) and 34 | [ServerUrlHelper](https://github.com/zendframework/zend-expressive-helpers#serverurlhelper) 35 | are registered with the container. 36 | 37 | - ``path``: Render the relative path for a given route and parameters. If there 38 | is no route, it returns the current path. 39 | 40 | ```twig 41 | {{ path('article_show', {'id': '3'}) }} 42 | Generates: /article/3 43 | ``` 44 | 45 | ``path`` supports optional query parameters and a fragment identifier. 46 | 47 | ```twig 48 | {{ path('article_show', {'id': '3'}, {'foo': 'bar'}, 'fragment') }} 49 | Generates: /article/3?foo=bar#fragment 50 | ``` 51 | 52 | By default the current route result is used where applicable. To disable this 53 | the `reuse_result_params` option can be set. 54 | 55 | ```twig 56 | {{ path('article_show', {}, {}, null, {'reuse_result_params': false}) }} 57 | ``` 58 | 59 | - ``url``: Render the absolute url for a given route and parameters. If there is 60 | no route, it returns the current url. 61 | 62 | ```twig 63 | {{ url('article_show', {'slug': 'article.slug'}) }} 64 | Generates: http://example.com/article/article.slug 65 | ``` 66 | 67 | ``url`` also supports query parameters and a fragment identifier. 68 | 69 | ```twig 70 | {{ url('article_show', {'id': '3'}, {'foo': 'bar'}, 'fragment') }} 71 | Generates: http://example.com/article/3?foo=bar#fragment 72 | ``` 73 | 74 | By default the current route result is used where applicable. To disable this 75 | the `reuse_result_params` option can be set. 76 | 77 | ```twig 78 | {{ url('article_show', {}, {}, null, {'reuse_result_params': false}) }} 79 | ``` 80 | 81 | - ``absolute_url``: Render the absolute url from a given path. If the path is 82 | empty, it returns the current url. 83 | 84 | ```twig 85 | {{ absolute_url('path/to/something') }} 86 | Generates: http://example.com/path/to/something 87 | ``` 88 | 89 | - ``asset`` Render an (optionally versioned) asset url. 90 | 91 | ```twig 92 | {{ asset('path/to/asset/name.ext', version=3) }} 93 | Generates: path/to/asset/name.ext?v=3 94 | ``` 95 | 96 | To get the absolute url for an asset: 97 | 98 | ```twig 99 | {{ absolute_url(asset('path/to/asset/name.ext', version=3)) }} 100 | Generates: http://example.com/path/to/asset/name.ext?v=3 101 | ``` 102 | 103 | ## Configuration 104 | 105 | If you use the [zend-component-installer](https://github.com/zendframework/zend-component-installer) 106 | the factories are configured automatically for you when requiring this package 107 | with composer. Without the component installer, you need to 108 | include the [`ConfigProvider`](src/ConfigProvider.php) in your 109 | [`config/config.php`](https://github.com/zendframework/zend-expressive-skeleton/blob/master/config/config.php). 110 | Optional configuration can be stored in `config/autoload/templates.global.php`. 111 | 112 | ```php 113 | 'templates' => [ 114 | 'extension' => 'file extension used by templates; defaults to html.twig', 115 | 'paths' => [ 116 | // namespace / path pairs 117 | // 118 | // Numeric namespaces imply the default/main namespace. Paths may be 119 | // strings or arrays of string paths to associate with the namespace. 120 | ], 121 | ], 122 | 'twig' => [ 123 | 'cache_dir' => 'path to cached templates', 124 | 'assets_url' => 'base URL for assets', 125 | 'assets_version' => 'base version for assets', 126 | 'extensions' => [ 127 | // extension service names or instances 128 | ], 129 | 'runtime_loaders' => [ 130 | // runtime loaders names or instances 131 | ], 132 | 'globals' => [ 133 | // Global variables passed to twig templates 134 | 'ga_tracking' => 'UA-XXXXX-X' 135 | ], 136 | 'timezone' => 'default timezone identifier, e.g.: America/New_York', 137 | 'optimizations' => -1, // -1: Enable all (default), 0: disable optimizations 138 | 'autoescape' => 'html', // Auto-escaping strategy [html|js|css|url|false] 139 | 'auto_reload' => true, // Recompile the template whenever the source code changes 140 | 'debug' => true, // When set to true, the generated templates have a toString() method 141 | 'strict_variables' => true, // When set to true, twig throws an exception on invalid variables 142 | ], 143 | ``` 144 | 145 | ## Documentation 146 | 147 | See the Expressive [Twig documentation](https://docs.zendframework.com/zend-expressive/features/template/twig/). 148 | -------------------------------------------------------------------------------- /src/TwigExtension.php: -------------------------------------------------------------------------------- 1 | serverUrlHelper = $serverUrlHelper; 58 | $this->urlHelper = $urlHelper; 59 | $this->assetsUrl = $assetsUrl; 60 | $this->assetsVersion = $assetsVersion; 61 | $this->globals = $globals; 62 | } 63 | 64 | public function getGlobals() : array 65 | { 66 | return $this->globals; 67 | } 68 | 69 | /** 70 | * @return TwigFunction[] 71 | */ 72 | public function getFunctions() : array 73 | { 74 | return [ 75 | new TwigFunction('absolute_url', [$this, 'renderUrlFromPath']), 76 | new TwigFunction('asset', [$this, 'renderAssetUrl']), 77 | new TwigFunction('path', [$this, 'renderUri']), 78 | new TwigFunction('url', [$this, 'renderUrl']), 79 | ]; 80 | } 81 | 82 | /** 83 | * Render relative uri for a given named route 84 | * 85 | * Usage: {{ path('article_show', {'id': '3'}) }} 86 | * Generates: /article/3 87 | * 88 | * Usage: {{ path('article_show', {'id': '3'}, {'foo': 'bar'}, 'fragment') }} 89 | * Generates: /article/3?foo=bar#fragment 90 | * 91 | * @param null|string $route 92 | * @param array $routeParams 93 | * @param array $queryParams 94 | * @param null|string $fragmentIdentifier 95 | * @param array $options Can have the following keys: 96 | * - reuse_result_params (bool): indicates if the current 97 | * RouteResult parameters will be used, defaults to true 98 | * 99 | * @return string 100 | */ 101 | public function renderUri( 102 | ?string $route = null, 103 | array $routeParams = [], 104 | array $queryParams = [], 105 | ?string $fragmentIdentifier = null, 106 | array $options = [] 107 | ): string { 108 | 109 | return $this->urlHelper->generate($route, $routeParams, $queryParams, $fragmentIdentifier, $options); 110 | } 111 | 112 | /** 113 | * Render absolute url for a given named route 114 | * 115 | * Usage: {{ url('article_show', {'slug': 'article.slug'}) }} 116 | * Generates: http://example.com/article/article.slug 117 | * 118 | * Usage: {{ url('article_show', {'id': '3'}, {'foo': 'bar'}, 'fragment') }} 119 | * Generates: http://example.com/article/3?foo=bar#fragment 120 | * 121 | * @param null|string $route 122 | * @param array $routeParams 123 | * @param array $queryParams 124 | * @param null|string $fragmentIdentifier 125 | * @param array $options Can have the following keys: 126 | * - reuse_result_params (bool): indicates if the current 127 | * RouteResult parameters will be used, defaults to true 128 | * 129 | * @return string 130 | */ 131 | public function renderUrl( 132 | ?string $route = null, 133 | array $routeParams = [], 134 | array $queryParams = [], 135 | ?string $fragmentIdentifier = null, 136 | array $options = [] 137 | ): string { 138 | 139 | return $this->serverUrlHelper->generate( 140 | $this->renderUri($route, $routeParams, $queryParams, $fragmentIdentifier, $options) 141 | ); 142 | } 143 | 144 | /** 145 | * Render absolute url from a path 146 | * 147 | * Usage: {{ absolute_url('path/to/something') }} 148 | * Generates: http://example.com/path/to/something 149 | * 150 | * @param string|null $path 151 | * 152 | * @return string 153 | */ 154 | public function renderUrlFromPath(string $path = null) : string 155 | { 156 | return $this->serverUrlHelper->generate($path); 157 | } 158 | 159 | /** 160 | * Render asset url, optionally versioned 161 | * 162 | * Usage: {{ asset('path/to/asset/name.ext', version=3) }} 163 | * Generates: path/to/asset/name.ext?v=3 164 | * 165 | * @param string $path 166 | * @param string|null $version 167 | * 168 | * @return string 169 | */ 170 | public function renderAssetUrl(string $path, string $version = null) : string 171 | { 172 | $assetsVersion = $version !== null && $version !== '' ? $version : $this->assetsVersion; 173 | 174 | // One more time, in case $this->assetsVersion was null or an empty string 175 | $assetsVersion = $assetsVersion !== null && $assetsVersion !== '' ? '?v=' . $assetsVersion : ''; 176 | 177 | return $this->assetsUrl . $path . $assetsVersion; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/TwigEnvironmentFactory.php: -------------------------------------------------------------------------------- 1 | 41 | * 'debug' => boolean, 42 | * 'templates' => [ 43 | * 'extension' => 'file extension used by templates; defaults to html.twig', 44 | * 'paths' => [ 45 | * // namespace / path pairs 46 | * // 47 | * // Numeric namespaces imply the default/main namespace. Paths may be 48 | * // strings or arrays of string paths to associate with the namespace. 49 | * ], 50 | * ], 51 | * 'twig' => [ 52 | * 'cache_dir' => 'path to cached templates', 53 | * 'assets_url' => 'base URL for assets', 54 | * 'assets_version' => 'base version for assets', 55 | * 'extensions' => [ 56 | * // extension service names or instances 57 | * ], 58 | * 'runtime_loaders' => [ 59 | * // runtime loaders names or instances 60 | * ], 61 | * 'globals' => [ 62 | * // Global variables passed to twig templates 63 | * 'ga_tracking' => 'UA-XXXXX-X' 64 | * ], 65 | * 'timezone' => 'default timezone identifier, e.g.: America/New_York', 66 | * 'optimizations' => -1, // -1: Enable all (default), 0: disable optimizations 67 | * 'autoescape' => 'html', // Auto-escaping strategy [html|js|css|url|false] 68 | * 'auto_reload' => true, // recompile the template whenever the source code changes 69 | * ], 70 | * 71 | * 72 | * Note: the various keys in the `twig` configuration key can occur in either 73 | * that location, or under `templates` (which was the behavior prior to 0.3.0); 74 | * the two arrays are merged by the factory. 75 | */ 76 | class TwigEnvironmentFactory 77 | { 78 | /** 79 | * @param ContainerInterface $container 80 | * 81 | * @return Environment 82 | * @throws LoaderError 83 | */ 84 | public function __invoke(ContainerInterface $container) : Environment 85 | { 86 | $config = $container->has('config') ? $container->get('config') : []; 87 | 88 | if (! is_array($config) && ! $config instanceof ArrayObject) { 89 | throw new Exception\InvalidConfigException(sprintf( 90 | '"config" service must be an array or ArrayObject for the %s to be able to consume it; received %s', 91 | __CLASS__, 92 | (is_object($config) ? get_class($config) : gettype($config)) 93 | )); 94 | } 95 | 96 | $debug = (bool) ($config['debug'] ?? false); 97 | $config = TwigRendererFactory::mergeConfig($config); 98 | 99 | // Create the engine instance 100 | $loader = new FilesystemLoader(); 101 | $environment = new Environment($loader, [ 102 | 'cache' => $config['cache_dir'] ?? false, 103 | 'debug' => $config['debug'] ?? $debug, 104 | 'strict_variables' => $config['strict_variables'] ?? $debug, 105 | 'auto_reload' => $config['auto_reload'] ?? $debug, 106 | 'optimizations' => $config['optimizations'] ?? OptimizerNodeVisitor::OPTIMIZE_ALL, 107 | 'autoescape' => $config['autoescape'] ?? 'html', 108 | ]); 109 | 110 | if (isset($config['timezone'])) { 111 | $timezone = $config['timezone']; 112 | if (! is_string($timezone)) { 113 | throw new Exception\InvalidConfigException('"timezone" configuration value must be a string'); 114 | } 115 | try { 116 | $timezone = new DateTimeZone($timezone); 117 | } catch (\Exception $e) { 118 | throw new Exception\InvalidConfigException(sprintf('Unknown or invalid timezone: "%s"', $timezone)); 119 | } 120 | $environment->getExtension(CoreExtension::class)->setTimezone($timezone); 121 | } 122 | 123 | // Add expressive twig extension if requirements are met 124 | if ($container->has(TwigExtension::class) 125 | && $container->has(ServerUrlHelper::class) 126 | && $container->has(UrlHelper::class) 127 | ) { 128 | $environment->addExtension($container->get(TwigExtension::class)); 129 | } 130 | 131 | // Add debug extension 132 | if ($debug) { 133 | $environment->addExtension(new DebugExtension()); 134 | } 135 | 136 | // Add user defined extensions 137 | $extensions = isset($config['extensions']) && is_array($config['extensions']) 138 | ? $config['extensions'] 139 | : []; 140 | $this->injectExtensions($environment, $container, $extensions); 141 | 142 | // Add user defined runtime loaders 143 | $runtimeLoaders = isset($config['runtime_loaders']) && is_array($config['runtime_loaders']) 144 | ? $config['runtime_loaders'] 145 | : []; 146 | $this->injectRuntimeLoaders($environment, $container, $runtimeLoaders); 147 | 148 | // Add template paths 149 | $allPaths = isset($config['paths']) && is_array($config['paths']) ? $config['paths'] : []; 150 | foreach ($allPaths as $namespace => $paths) { 151 | $namespace = is_numeric($namespace) ? null : $namespace; 152 | $namespace = $namespace ?: FilesystemLoader::MAIN_NAMESPACE; 153 | foreach ((array) $paths as $path) { 154 | $loader->addPath($path, $namespace); 155 | } 156 | } 157 | 158 | // Inject environment 159 | return $environment; 160 | } 161 | 162 | /** 163 | * Inject extensions into the TwigEnvironment instance. 164 | * 165 | * @param Environment $environment 166 | * @param ContainerInterface $container 167 | * @param array $extensions 168 | */ 169 | private function injectExtensions( 170 | Environment $environment, 171 | ContainerInterface $container, 172 | array $extensions 173 | ) : void { 174 | foreach ($extensions as $extension) { 175 | $extension = $this->loadExtension($extension, $container); 176 | 177 | if (! $environment->hasExtension(get_class($extension))) { 178 | $environment->addExtension($extension); 179 | } 180 | } 181 | } 182 | 183 | /** 184 | * Load an extension. 185 | * 186 | * If the extension is a string service name, retrieves it from the container. 187 | * 188 | * If the extension is not an ExtensionInterface, raises an exception. 189 | * 190 | * @param string|ExtensionInterface $extension 191 | * 192 | * @param ContainerInterface $container 193 | * 194 | * @return ExtensionInterface 195 | */ 196 | private function loadExtension($extension, ContainerInterface $container): ExtensionInterface 197 | { 198 | // Load the extension from the container if present 199 | if (is_string($extension) && $container->has($extension)) { 200 | $extension = $container->get($extension); 201 | } 202 | 203 | if (! $extension instanceof ExtensionInterface) { 204 | throw new Exception\InvalidExtensionException(sprintf( 205 | 'Twig extension must be an instance of %s; "%s" given,', 206 | ExtensionInterface::class, 207 | is_object($extension) ? get_class($extension) : gettype($extension) 208 | )); 209 | } 210 | 211 | return $extension; 212 | } 213 | 214 | /** 215 | * Inject Runtime Loaders into the TwigEnvironment instance. 216 | * 217 | * @param Environment $environment 218 | * @param ContainerInterface $container 219 | * @param array $runtimes 220 | */ 221 | private function injectRuntimeLoaders( 222 | Environment $environment, 223 | ContainerInterface $container, 224 | array $runtimes 225 | ) : void { 226 | foreach ($runtimes as $runtimeLoader) { 227 | $runtimeLoader = $this->loadRuntimeLoader($runtimeLoader, $container); 228 | $environment->addRuntimeLoader($runtimeLoader); 229 | } 230 | } 231 | 232 | /** 233 | * @param string|RuntimeLoaderInterface $runtimeLoader 234 | * 235 | * @param ContainerInterface $container 236 | * 237 | * @return RuntimeLoaderInterface 238 | */ 239 | private function loadRuntimeLoader($runtimeLoader, ContainerInterface $container): RuntimeLoaderInterface 240 | { 241 | // Load the runtime loader from the container 242 | if (is_string($runtimeLoader) && $container->has($runtimeLoader)) { 243 | $runtimeLoader = $container->get($runtimeLoader); 244 | } 245 | 246 | if (! $runtimeLoader instanceof RuntimeLoaderInterface) { 247 | throw new Exception\InvalidRuntimeLoaderException(sprintf( 248 | 'Twig runtime loader must be an instance of %s; "%s" given,', 249 | RuntimeLoaderInterface::class, 250 | is_object($runtimeLoader) ? get_class($runtimeLoader) : gettype($runtimeLoader) 251 | )); 252 | } 253 | 254 | return $runtimeLoader; 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 2.5.1 - TBD 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 2.5.0 - 2019-11-26 28 | 29 | ### Added 30 | 31 | - [#60](https://github.com/zendframework/zend-expressive-twigrenderer/pull/60) adds compatibility with twig/twig `^3.0`. 32 | 33 | ### Changed 34 | 35 | - Nothing. 36 | 37 | ### Deprecated 38 | 39 | - Nothing. 40 | 41 | ### Removed 42 | 43 | - Nothing. 44 | 45 | ### Fixed 46 | 47 | - Nothing. 48 | 49 | ## 2.4.0 - 2019-06-12 50 | 51 | ### Added 52 | 53 | - [#59](https://github.com/zendframework/zend-expressive-twigrenderer/pull/59) adds Environment configuration options for 'cache', 'debug' and 'strict_variables'. 54 | 55 | - [#57](https://github.com/zendframework/zend-expressive-twigrenderer/pull/57) adds support for PHP 7.3. 56 | 57 | ### Changed 58 | 59 | - Nothing. 60 | 61 | ### Deprecated 62 | 63 | - Nothing. 64 | 65 | ### Removed 66 | 67 | - Nothing. 68 | 69 | ### Fixed 70 | 71 | - Nothing. 72 | 73 | ## 2.3.0 - 2019-01-23 74 | 75 | ### Added 76 | 77 | - [#53](https://github.com/zendframework/zend-expressive-twigrenderer/pull/53) adds support for the `auto_reload` option to the `TwigEnvironmentFactory`. 78 | When provided, Twig will recompile a template whenever it detects the source 79 | code has changed. This option should be **disabled** in production. 80 | 81 | ### Changed 82 | 83 | - Nothing. 84 | 85 | ### Deprecated 86 | 87 | - Nothing. 88 | 89 | ### Removed 90 | 91 | - Nothing. 92 | 93 | ### Fixed 94 | 95 | - Nothing. 96 | 97 | ## 2.2.0 - 2018-11-19 98 | 99 | ### Added 100 | 101 | - Nothing. 102 | 103 | ### Changed 104 | 105 | - [#50](https://github.com/zendframework/zend-expressive-twigrenderer/pull/50) updates an exception message thrown in the `TwigExtensionFactory` to be more consistent with other exceptions raised in the component. 106 | 107 | - [#51](https://github.com/zendframework/zend-expressive-twigrenderer/pull/51) updates the minimum support twig versions to `^1.34` and `^2.4`. 108 | 109 | - [#51](https://github.com/zendframework/zend-expressive-twigrenderer/pull/51) modifies all functionality to use namespaced versions of all Twig classes. 110 | Users may still reference classes using the previously supported 111 | pseudo-namespaces, as they are class aliases for the namespaced versions. 112 | 113 | ### Deprecated 114 | 115 | - Nothing. 116 | 117 | ### Removed 118 | 119 | - Nothing. 120 | 121 | ### Fixed 122 | 123 | - Nothing. 124 | 125 | ## 2.1.0 - 2018-04-09 126 | 127 | ### Added 128 | 129 | - [#46](https://github.com/zendframework/zend-expressive-twigrenderer/pull/46) 130 | adds two new configuration options (under the `twig` top-level configuration 131 | key) for the `TwigEnvironment`: 132 | 133 | - `optimizations` allows enabling or disabling optimizations, and may be the 134 | integer `-1` (enable all optimizations) or `0` (disable optimizations); the 135 | default is `-1`. 136 | 137 | - `autoescape` allows specyfing the auto-escaping strategy to use, and may be 138 | one of html, js, css, url, html_attr, or false; the default is "html". 139 | 140 | ### Changed 141 | 142 | - Nothing. 143 | 144 | ### Deprecated 145 | 146 | - Nothing. 147 | 148 | ### Removed 149 | 150 | - Nothing. 151 | 152 | ### Fixed 153 | 154 | - Nothing. 155 | 156 | ## 2.0.0 - 2018-03-15 157 | 158 | ### Added 159 | 160 | - [#36](https://github.com/zendframework/zend-expressive-twigrenderer/pull/36) and 161 | [#42](https://github.com/zendframework/zend-expressive-twigrenderer/pull/42) 162 | add support for zend-expressive-template v2, zend-expressive-router v3, and 163 | zend-expressive-helpers v5. 164 | 165 | - [#38](https://github.com/zendframework/zend-expressive-twigrenderer/pull/38) 166 | adds a `ConfigProvider` class with default service wiring and configuration 167 | for the component. It also updates `composer.json` to add 168 | `extra.zf.config-provider` configuration to notify zend-component-installer 169 | of the shipped `ConfigProvider` class, allowing the plugin to inject the 170 | `ConfigProvider` in your application configuration during initial 171 | installation. 172 | 173 | ### Changed 174 | 175 | - [#43](https://github.com/zendframework/zend-expressive-twigrenderer/pull/43) 176 | adds a TwigExtensionFactory so the TwigExtension can be customized. 177 | 178 | If you have the ConfigProvider in your `config\config.php` file, this factory 179 | is added for you. If you don't use the ConfigProvider, you need to add this 180 | to your config: 181 | 182 | ```php 183 | 'dependencies' => [ 184 | 'factories' => [ 185 | TwigExtension::class => TwigExtensionFactory::class, 186 | ], 187 | ], 188 | ``` 189 | 190 | - [#36](https://github.com/zendframework/zend-expressive-twigrenderer/pull/36) 191 | updates all code to add scalar and return type hints, including nullable types 192 | and void types, wherever they make sense. As such, if you are extending any 193 | classes from this component, you may need to check any signatures you 194 | override. 195 | 196 | - [#37](https://github.com/zendframework/zend-expressive-twigrenderer/pull/37) 197 | updates the package `ExceptionInterface` to extend from the 198 | `ExceptionInterface` provided in zend-expressive-template. 199 | 200 | ### Deprecated 201 | 202 | - Nothing. 203 | 204 | ### Removed 205 | 206 | - [#36](https://github.com/zendframework/zend-expressive-twigrenderer/pull/36) 207 | removes support for PHP versions prior to PHP 7.1. 208 | 209 | - [#36](https://github.com/zendframework/zend-expressive-twigrenderer/pull/36) 210 | removes support for zend-expressive-template releases prior to v2. 211 | 212 | - [#36](https://github.com/zendframework/zend-expressive-twigrenderer/pull/36) 213 | removes support for zend-expressive-router releases prior to v3. 214 | 215 | - [#36](https://github.com/zendframework/zend-expressive-twigrenderer/pull/36) 216 | removes support for zend-expressive-helpers releases prior to v5. 217 | 218 | ### Fixed 219 | 220 | - Nothing. 221 | 222 | ## 1.5.0 - 2017-08-12 223 | 224 | ### Added 225 | 226 | - [#30](https://github.com/zendframework/zend-expressive-twigrenderer/pull/30) 227 | adds support for the PSR-11 Container Interface. 228 | 229 | ### Changed 230 | 231 | - [#33](https://github.com/zendframework/zend-expressive-twigrenderer/pull/33) 232 | removes duplicate code. 233 | 234 | ### Deprecated 235 | 236 | - Nothing. 237 | 238 | ### Removed 239 | 240 | - Nothing. 241 | 242 | ### Fixed 243 | 244 | - Nothing. 245 | 246 | ## 1.4.0 - 2017-03-14 247 | 248 | ### Added 249 | 250 | - [#29](https://github.com/zendframework/zend-expressive-twigrenderer/pull/29) 251 | adds support for zend-expressive-helpers 4.0. 252 | 253 | ### Deprecated 254 | 255 | - Nothing. 256 | 257 | ### Removed 258 | 259 | - Nothing. 260 | 261 | ### Fixed 262 | 263 | - Nothing. 264 | 265 | ## 1.3.0 - 2017-03-02 266 | 267 | ### Added 268 | 269 | - [#28](https://github.com/zendframework/zend-expressive-twigrenderer/pull/28) 270 | adds support for Twig 2.1. If you upgrade and receive this version, please be 271 | aware that Twig 2.X no longer allows short name "aliases" for extensions. As 272 | an example, the following works in Twig 1.X: 273 | 274 | ```php 275 | $environment->getExtension('core'); 276 | ``` 277 | 278 | but does not work in Twig 2.X, where you would instead need to use the fully 279 | qualified class name: 280 | 281 | ```php 282 | $environment->getExtension(Twig_Extension_Core::class); 283 | ``` 284 | 285 | As the latter notation has worked across both versions, you may want to 286 | consider making that change before upgrading, if you were using the older 287 | notation. 288 | 289 | ### Deprecated 290 | 291 | - Nothing. 292 | 293 | ### Removed 294 | 295 | - Nothing. 296 | 297 | ### Fixed 298 | 299 | - Nothing. 300 | 301 | ## 1.2.1 - 2017-01-12 302 | 303 | ### Added 304 | 305 | - Nothing. 306 | 307 | ### Deprecated 308 | 309 | - Nothing. 310 | 311 | ### Removed 312 | 313 | - Nothing. 314 | 315 | ### Fixed 316 | 317 | - [#26](https://github.com/zendframework/zend-expressive-twigrenderer/pull/26) fixes 318 | the zend-expressive-router constrain in composer.json. 319 | 320 | ## 1.2.0 - 2017-01-11 321 | 322 | ### Added 323 | 324 | - [#12](https://github.com/zendframework/zend-expressive-twigrenderer/pull/12) 325 | adds the ability to provide a default timezone to use with Twig. Provide it 326 | via the `twig.timezone` setting: 327 | 328 | ```php 329 | return [ 330 | 'twig' => [ 331 | 'timezone' => 'America/Chicago', 332 | ], 333 | ]; 334 | ``` 335 | 336 | - [#15](https://github.com/zendframework/zend-expressive-twigrenderer/pull/15) 337 | extracts a new factory, `TwigEnvironmentFactory`, from the 338 | `TwigRendererFactory`. This new factory is now responsible for creating and 339 | configuring the `Twig_Environment` instance. 340 | 341 | While users may continue to use existing configuration, which omits 342 | registration of the `Twig_Environment` service with this new factory, doing so 343 | now emits a deprecation notice, indicating they should update their dependency 344 | configuration. 345 | 346 | This also means that users may override the `Twig_Environment` service to 347 | provide alternate instantiation of that class, or add delegator factories in 348 | order to further configure the Twig environment. 349 | 350 | - [#23](https://github.com/zendframework/zend-expressive-twigrenderer/pull/23) 351 | adds the ability to provide Twig runtime loaders via configuration. These may 352 | be provided as either instances or service names, under the 353 | `twig.runtime_loaders` setting: 354 | 355 | ```php 356 | return [ 357 | 'twig' => [ 358 | 'runtime_loaders' => [ 359 | // runtime loader service names or instances of 360 | // Twig_RuntimeLoaderInterface 361 | ], 362 | ], 363 | ]; 364 | ``` 365 | 366 | - [#18](https://github.com/zendframework/zend-expressive-twigrenderer/pull/18) 367 | adds support for zend-expressive-helpers 3.0. 368 | 369 | - [#18](https://github.com/zendframework/zend-expressive-twigrenderer/pull/18) 370 | adds support for zend-expressive-router 2.0. 371 | 372 | - [#18](https://github.com/zendframework/zend-expressive-twigrenderer/pull/18) 373 | adds new parameters to the included `path()` extension. It now accepts the 374 | following arguments: 375 | 376 | ```twig 377 | {{ path( 378 | 'route_name', 379 | {'route_param': 'substitution'}, 380 | {'query_param': 'value'}, 381 | 'fragment', 382 | {'reuse_result_params': false} 383 | ) }} 384 | ``` 385 | 386 | The new arguments are the query parameters, fragment, and router options. 387 | 388 | ### Deprecated 389 | 390 | - Nothing. 391 | 392 | ### Removed 393 | 394 | - This release removes support for PHP 5.5. 395 | 396 | ### Fixed 397 | 398 | - [#19](https://github.com/zendframework/zend-expressive-twigrenderer/pull/19) 399 | fixes how the factories test for prior registration of an extension. 400 | Previously, they pulled the extension name using the extension's `getName()` 401 | method; however, as of Twig 1.26, that method is deprecated from 402 | `Twig_ExtensionInterface`, and no longer used internally. This package's 403 | factories now use the class name of the extension to perform the checks. 404 | 405 | ## 1.1.1 - 2016-02-01 406 | 407 | ### Added 408 | 409 | - Nothing. 410 | 411 | ### Deprecated 412 | 413 | - Nothing. 414 | 415 | ### Removed 416 | 417 | - Nothing. 418 | 419 | ### Fixed 420 | 421 | - [#11](https://github.com/zendframework/zend-expressive-twigrenderer/pull/11) 422 | updates the `TwigExtension` class to implement `Twig_Extension_GlobalsInterface`, 423 | which is required starting with Twig 1.23 for forwards-compatibility. 424 | 425 | ## 1.1.0 - 2016-01-21 426 | 427 | ### Added 428 | 429 | - [#8](https://github.com/zendframework/zend-expressive-twigrenderer/pull/8) 430 | adds `zendframework/zend-expressive-helpers` as a dependency, in order to 431 | consume its `UrlHelper` and `ServerUrlHelper` implementations. 432 | 433 | Adds the `url` and `absolute_url` twig functions to generate 434 | absolute urls for a route and path. 435 | 436 | - [#10](https://github.com/zendframework/zend-expressive-twigrenderer/pull/10) 437 | adds config globals to pass to all twig templates. 438 | 439 | ```php 440 | 'twig' => [ 441 | 'cache_dir' => 'path to cached templates', 442 | 'assets_url' => 'base URL for assets', 443 | 'assets_version' => 'base version for assets', 444 | 'extensions' => [ 445 | // extension service names or instances 446 | ], 447 | 'globals' => [ 448 | // Global variables passed to twig templates 449 | 'ga_tracking' => 'UA-XXXXX-X' 450 | ], 451 | ], 452 | ``` 453 | 454 | ### Deprecated 455 | 456 | - Nothing. 457 | 458 | ### Removed 459 | 460 | - Nothing. 461 | 462 | ### Fixed 463 | 464 | - Nothing. 465 | 466 | ## 1.0.1 - 2016-01-21 467 | 468 | ### Added 469 | 470 | - Nothing. 471 | 472 | ### Deprecated 473 | 474 | - Nothing. 475 | 476 | ### Removed 477 | 478 | - Nothing. 479 | 480 | ### Fixed 481 | 482 | - [#9](https://github.com/zendframework/zend-expressive-twigrenderer/pull/9) 483 | fixes a skipped test, and updates the behavior of `TwigRendererFactory` to 484 | raise an exception if the 'config' service is not an array or `ArrayObject`. 485 | 486 | ## 1.0.0 - 2015-12-07 487 | 488 | First stable release. 489 | 490 | ### Added 491 | 492 | - Nothing. 493 | 494 | ### Deprecated 495 | 496 | - Nothing. 497 | 498 | ### Removed 499 | 500 | - Nothing. 501 | 502 | ### Fixed 503 | 504 | - Nothing. 505 | 506 | ## 0.3.1 - 2015-12-03 507 | 508 | ### Added 509 | 510 | - Nothing. 511 | 512 | ### Deprecated 513 | 514 | - Nothing. 515 | 516 | ### Removed 517 | 518 | - Nothing. 519 | 520 | ### Fixed 521 | 522 | - [#5](https://github.com/zendframework/zend-expressive-twigrenderer/pull/5) 523 | fixes an issue in the TwigRendererFactory whereby it failed if the 'config' 524 | service returned an `ArrayObject`. It now validates that it has a usable 525 | config value, raising an exception when it does not. 526 | 527 | ## 0.3.0 - 2015-12-02 528 | 529 | ### Added 530 | 531 | - [#1](https://github.com/zendframework/zend-expressive-twigrenderer/pull/1) 532 | adds the ability to inject additional Twig extensions via configuration. This 533 | can be done using the following configuration: 534 | 535 | ```php 536 | 'templates' => [ 537 | 'extension' => 'file extension used by templates; defaults to html.twig', 538 | 'paths' => [ 539 | // namespace / path pairs 540 | // 541 | // Numeric namespaces imply the default/main namespace. Paths may be 542 | // strings or arrays of string paths to associate with the namespace. 543 | ], 544 | ], 545 | 'twig' => [ 546 | 'cache_dir' => 'path to cached templates', 547 | 'assets_url' => 'base URL for assets', 548 | 'assets_version' => 'base version for assets', 549 | 'extensions' => [ 550 | // extension service names or instances 551 | ], 552 | ], 553 | ``` 554 | 555 | ### Deprecated 556 | 557 | - [#1](https://github.com/zendframework/zend-expressive-twigrenderer/pull/1) 558 | deprecates usage of the `cache_dir` and `assets_*` sub-keys under the 559 | `templates` top-level key, in favor of positioning them beneath a `twig` 560 | top-level key. As `templates` and `twig` values are merged, however, this 561 | change should not affect end-users. 562 | 563 | ### Removed 564 | 565 | - Nothing. 566 | 567 | ### Fixed 568 | 569 | - [#4](https://github.com/zendframework/zend-expressive-twigrenderer/pull/4) 570 | removes the dependency on zendframework/zend-expressive, and replaces it with 571 | zend-framework/zend-expressive-template and 572 | zendframework/zend-expressive-router. 573 | 574 | ## 0.2.1 - 2015-11-10 575 | 576 | ### Added 577 | 578 | - Nothing. 579 | 580 | ### Deprecated 581 | 582 | - Nothing. 583 | 584 | ### Removed 585 | 586 | - Nothing. 587 | 588 | ### Fixed 589 | 590 | - [#3](https://github.com/zendframework/zend-expressive-twigrenderer/pull/3) 591 | updates the `renderAssetUrl()` method of the `TwigExtension` to mask 592 | versioning if it's empty (while also allowing zero versions). 593 | 594 | ## 0.2.0 - 2015-10-20 595 | 596 | ### Added 597 | 598 | - Nothing. 599 | 600 | ### Deprecated 601 | 602 | - Nothing. 603 | 604 | ### Removed 605 | 606 | - Nothing. 607 | 608 | ### Fixed 609 | 610 | - Updated to zend-expressive RC1. 611 | - Added branch-alias of dev-master to 1.0-dev. 612 | 613 | ## 0.1.0 - 2015-10-10 614 | 615 | Initial release. 616 | 617 | ### Added 618 | 619 | - Nothing. 620 | 621 | ### Deprecated 622 | 623 | - Nothing. 624 | 625 | ### Removed 626 | 627 | - Nothing. 628 | 629 | ### Fixed 630 | 631 | - Nothing. 632 | --------------------------------------------------------------------------------