├── tests ├── Fixture │ ├── view │ │ ├── ZendTwig │ │ │ ├── Helpers │ │ │ │ ├── docType.twig │ │ │ │ ├── NotExists.twig │ │ │ │ ├── basePath.twig │ │ │ │ ├── headTitle.twig │ │ │ │ └── headMeta.twig │ │ │ ├── View │ │ │ │ ├── inc │ │ │ │ │ ├── block.twig │ │ │ │ │ └── layout.twig │ │ │ │ ├── testFindTemplate.twig │ │ │ │ ├── model │ │ │ │ │ ├── testTwigModel.twig │ │ │ │ │ └── testPhpModel.phtml │ │ │ │ ├── testFindTemplateCache.twig │ │ │ │ ├── testInjectResponse.twig │ │ │ │ ├── issue-7 │ │ │ │ │ ├── ViewHelpers.twig │ │ │ │ │ └── ViewHelpersInvoke.twig │ │ │ │ ├── testRenderChild.twig │ │ │ │ ├── zend │ │ │ │ │ ├── layout.twig │ │ │ │ │ └── index.twig │ │ │ │ ├── testTreeRender.twig │ │ │ │ ├── issue-2 │ │ │ │ │ ├── index.twig │ │ │ │ │ ├── layout.twig │ │ │ │ │ └── layout-raw.twig │ │ │ │ └── testRenderWithFallback.twig │ │ │ └── Extensions │ │ │ │ ├── Debug.twig │ │ │ │ └── InstanceOf.twig │ │ └── Map │ │ │ └── layout.twig │ ├── Twig │ │ ├── DummyClass.php │ │ ├── DummyExtension.php │ │ ├── View │ │ │ ├── Helper │ │ │ │ ├── MenuInvoke.php │ │ │ │ └── Menu.php │ │ │ └── Factory │ │ │ │ └── MenuFactory.php │ │ ├── InstanceOfExtension.php │ │ └── DummyClassInvokable.php │ └── config │ │ ├── loader │ │ └── config.exception.php │ │ ├── fallback │ │ └── global.php │ │ ├── extensions │ │ ├── empty.php │ │ ├── exception.php │ │ ├── factory.php │ │ ├── debug.php │ │ └── instanceOf.php │ │ ├── model │ │ └── config.local.php │ │ ├── autoload │ │ └── global.php │ │ ├── helpers │ │ ├── config.local.php │ │ ├── config.instance-exception.php │ │ └── config.class-exception.php │ │ ├── view_helpers │ │ └── config.local.php │ │ └── application.config.php ├── ZendTwig │ ├── DummyTest.php │ ├── Renderer │ │ ├── InvokeViewHelpersTest.php │ │ └── TwigRendererTest.php │ ├── View │ │ ├── TwigModelTest.php │ │ ├── TwigStrategyTest.php │ │ └── FallbackFunctionTest.php │ ├── Extension │ │ ├── DebugExtensionTest.php │ │ └── CustomExtensionTest.php │ ├── Loader │ │ ├── StackLoaderTest.php │ │ └── MapLoaderTest.php │ └── ModuleTest.php └── bootstrap.php ├── .gitignore ├── src ├── View │ ├── TwigModel.php │ ├── FallbackFunction.php │ ├── HelperPluginManager.php │ └── TwigStrategy.php ├── Extension │ ├── Extension.php │ └── AbstractExtension.php ├── Service │ ├── TwigResolverFactory.php │ ├── TwigExtensionFactory.php │ ├── TwigStackLoaderFactory.php │ ├── TwigStrategyFactory.php │ ├── TwigMapLoaderFactory.php │ ├── TwigLoaderFactory.php │ ├── TwigRendererFactory.php │ ├── TwigEnvironmentFactory.php │ └── TwigHelperPluginManagerFactory.php ├── Resolver │ └── TwigResolver.php ├── Loader │ ├── StackLoader.php │ └── MapLoader.php ├── Module.php └── Renderer │ └── TwigRenderer.php ├── docs ├── Extensions.md ├── TwigModel.md └── Module.md ├── phpunit.xml ├── LICENSE ├── .github └── workflows │ └── ci.yml ├── composer.json ├── README.md └── config └── module.config.php /tests/Fixture/view/ZendTwig/Helpers/docType.twig: -------------------------------------------------------------------------------- 1 | {{ doctype() }} 2 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/inc/block.twig: -------------------------------------------------------------------------------- 1 |
block
2 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/testFindTemplate.twig: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/model/testTwigModel.twig: -------------------------------------------------------------------------------- 1 | TWIG:{{ value }} -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/testFindTemplateCache.twig: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/model/testPhpModel.phtml: -------------------------------------------------------------------------------- 1 | PHP: -------------------------------------------------------------------------------- /tests/Fixture/view/Map/layout.twig: -------------------------------------------------------------------------------- 1 | {{ 9800.333|number_format(2, '.', ',') }} 2 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/Helpers/NotExists.twig: -------------------------------------------------------------------------------- 1 | {{ NotExists('realy') }} 2 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/Helpers/basePath.twig: -------------------------------------------------------------------------------- 1 | {{ basePath('css/app.css') }} 2 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/Helpers/headTitle.twig: -------------------------------------------------------------------------------- 1 | {{ headTitle('Test passed')|raw }} 2 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/Extensions/Debug.twig: -------------------------------------------------------------------------------- 1 |
2 |     {{ dump(variable) }}
3 | 
-------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/testInjectResponse.twig: -------------------------------------------------------------------------------- 1 | {{ key1 }}{{ key2 }} 2 | -------------------------------------------------------------------------------- /tests/Fixture/Twig/DummyClass.php: -------------------------------------------------------------------------------- 1 | {{ key1 }}{{ injectChild|raw }}{{ content|raw }} 2 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/zend/layout.twig: -------------------------------------------------------------------------------- 1 | {% block content %}{{ content|raw }}{% endblock content %} 2 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/issue-7/ViewHelpersInvoke.twig: -------------------------------------------------------------------------------- 1 | {{ mainMenuInvoke().setActiveItemId('permission') }}{{ mainMenuInvoke().render() }} -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/zend/index.twig: -------------------------------------------------------------------------------- 1 | {% extends "View/zend/layout" %}{% block content %}
Hello, {{ username }}!
{% endblock content %} -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/inc/layout.twig: -------------------------------------------------------------------------------- 1 | {% include "View/inc/block.twig" %} 2 | {% block content %} 3 | {{ content|raw }} 4 | {% endblock content %} 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /composer.lock 3 | /clover.xml 4 | 5 | /vendor/ 6 | /coverage/ 7 | 8 | .phpunit.result.cache 9 | /.phpunit.cache 10 | phpunit.results 11 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/testTreeRender.twig: -------------------------------------------------------------------------------- 1 | {% extends 'View/inc/layout.twig' %} 2 | 3 | {% block content %} 4 |
content
5 | {% endblock content %} 6 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/Extensions/InstanceOf.twig: -------------------------------------------------------------------------------- 1 | {% if (isInstanceOf(varClass, varInstance)) %} 2 | Yes 3 | {% else %} 4 | No 5 | {% endif %} 6 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/issue-2/index.twig: -------------------------------------------------------------------------------- 1 | {% extends 'View/issue-2/layout.twig' %}{% block header %}test header{% endblock %}{% block content %}test content{% endblock %} 2 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/issue-2/layout.twig: -------------------------------------------------------------------------------- 1 | {% block header %}{% endblock %}
{% block content %}{% endblock %}
{% block footer %}{% endblock %} 2 | -------------------------------------------------------------------------------- /tests/Fixture/config/loader/config.exception.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'loader_chain' => [ 6 | null, 7 | ], 8 | ], 9 | ]; 10 | -------------------------------------------------------------------------------- /tests/Fixture/Twig/DummyExtension.php: -------------------------------------------------------------------------------- 1 | {% block content %}{{ content|raw }}{% endblock %}{% block footer %}{% endblock %} 2 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/Helpers/headMeta.twig: -------------------------------------------------------------------------------- 1 | {{ headMeta() 2 | .appendName('viewport', 'width=device-width, initial-scale=1.0') 3 | .appendHttpEquiv('X-UA-Compatible', 'IE=edge') 4 | |raw 5 | }} 6 | -------------------------------------------------------------------------------- /src/View/TwigModel.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Fixture/view/ZendTwig/View/testRenderWithFallback.twig: -------------------------------------------------------------------------------- 1 | {{ docType() }} 2 | 3 | 4 | 5 | Twig Fixture 6 | 7 | 8 | 9 |
10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/Fixture/config/fallback/global.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'template_path_stack' => [ 6 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 7 | ], 8 | 'default_template_suffix' => \ZendTwig\Service\TwigLoaderFactory::DEFAULT_SUFFIX, 9 | ], 10 | 'zend_twig' => [ 11 | 'invoke_zend_helpers' => false, 12 | ], 13 | ]; 14 | -------------------------------------------------------------------------------- /tests/Fixture/config/extensions/empty.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'template_path_stack' => [ 6 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 7 | ], 8 | 'default_template_suffix' => \ZendTwig\Service\TwigLoaderFactory::DEFAULT_SUFFIX, 9 | ], 10 | 'zend_twig' => [ 11 | 'extensions' => [ 12 | null, 13 | ], 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /tests/Fixture/config/extensions/exception.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'template_path_stack' => [ 6 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 7 | ], 8 | 'default_template_suffix' => \ZendTwig\Service\TwigLoaderFactory::DEFAULT_SUFFIX, 9 | ], 10 | 'zend_twig' => [ 11 | 'extensions' => [ 12 | 123123, 13 | ], 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /tests/Fixture/config/extensions/factory.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'factories' => [ 6 | \ZendTwig\Test\Fixture\Extension\DummyExtension::class => \ZendTwig\Service\TwigExtensionFactory::class, 7 | ], 8 | ], 9 | 'zend_twig' => [ 10 | 'extensions' => [ 11 | \ZendTwig\Test\Fixture\Extension\DummyExtension::class, 12 | ], 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /tests/Fixture/config/model/config.local.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'force_twig_strategy' => false, 6 | ], 7 | 'view_manager' => [ 8 | 'doctype' => \Laminas\View\Helper\Doctype::HTML5, 9 | 'template_map' => [ 10 | 'layout' => __DIR__ . '/../../view/Map/layout.twig', 11 | ], 12 | 'template_path_stack' => [ 13 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 14 | ], 15 | ], 16 | ]; 17 | -------------------------------------------------------------------------------- /tests/Fixture/config/autoload/global.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'doctype' => \Laminas\View\Helper\Doctype::HTML5, 6 | 'template_map' => [ 7 | 'layout' => __DIR__ . '/../../view/Map/layout.twig', 8 | ], 9 | 'template_path_stack' => [ 10 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 11 | ], 12 | 'default_template_suffix' => \ZendTwig\Service\TwigLoaderFactory::DEFAULT_SUFFIX, 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /src/Extension/Extension.php: -------------------------------------------------------------------------------- 1 | renderer; 16 | } 17 | 18 | /** 19 | * @return ContainerInterface 20 | */ 21 | public function getServiceManager() : ContainerInterface 22 | { 23 | return $this->serviceManager; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Fixture/config/extensions/debug.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'template_path_stack' => [ 9 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 10 | ], 11 | 'default_template_suffix' => TwigLoaderFactory::DEFAULT_SUFFIX, 12 | ], 13 | 'zend_twig' => [ 14 | 'environment' => [ 15 | 'debug' => true, 16 | ], 17 | 'extensions' => [ 18 | DebugExtension::class, 19 | ], 20 | ], 21 | ]; 22 | -------------------------------------------------------------------------------- /tests/Fixture/config/extensions/instanceOf.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'template_path_stack' => [ 9 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 10 | ], 11 | 'default_template_suffix' => TwigLoaderFactory::DEFAULT_SUFFIX, 12 | ], 13 | 'service_manager' => [ 14 | 'factories' => [ 15 | InstanceOfExtension::class => \ZendTwig\Service\TwigExtensionFactory::class, 16 | ], 17 | ], 18 | 'zend_twig' => [ 19 | 'extensions' => [ 20 | InstanceOfExtension::class, 21 | ], 22 | ], 23 | ]; 24 | -------------------------------------------------------------------------------- /src/Service/TwigResolverFactory.php: -------------------------------------------------------------------------------- 1 | get(Environment::class)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Service/TwigExtensionFactory.php: -------------------------------------------------------------------------------- 1 | get(TwigRenderer::class)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Fixture/Twig/InstanceOfExtension.php: -------------------------------------------------------------------------------- 1 | new \Twig\TwigFunction('isInstanceOf', [$this, 'isInstanceOf']), 13 | ]; 14 | } 15 | 16 | /** 17 | * Is instance of ... 18 | * 19 | * @param string $class 20 | * @param string $className 21 | * 22 | * @return bool 23 | */ 24 | public function isInstanceOf($class = '', $className = '') 25 | { 26 | $reflectionClass = new \ReflectionClass($class); 27 | 28 | return $reflectionClass->isInstance(new $className()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/Extensions.md: -------------------------------------------------------------------------------- 1 | # Extensions 2 | Define Your new extensions for twig this ``` service_manager ``` configuration: 3 | ```php 4 | 'service_manager' => [ 5 | 'factories' => [ 6 | \ZendTwig\Test\Fixture\Extension\InstanceOfExtension::class => \ZendTwig\Service\TwigExtensionFactory::class, 7 | ], 8 | ], 9 | 'zend_twig' => [ 10 | 'extensions' => [ 11 | \ZendTwig\Test\Fixture\Extension\InstanceOfExtension::class, 12 | ], 13 | ], 14 | ``` 15 | 16 | Extension class example: 17 | 18 | ```php 19 | class NewExtension extends \ZendTwig\Extension\AbstractExtension 20 | { 21 | /** 22 | * Common code for Twig Extensions 23 | */ 24 | } 25 | ``` 26 | 27 | After that Your extension will be loaded as Twig extensions. 28 | -------------------------------------------------------------------------------- /tests/Fixture/Twig/View/Factory/MenuFactory.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'factories' => [ 11 | DummyClassInvokable::class => InvokableFactory::class, 12 | ], 13 | ], 14 | 'view_manager' => [ 15 | 'template_path_stack' => [ 16 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 17 | ], 18 | 'default_template_suffix' => TwigLoaderFactory::DEFAULT_SUFFIX, 19 | ], 20 | 'zend_twig' => [ 21 | 'helpers' => [ 22 | 'configs' => [ 23 | HelperConfig::class, 24 | ], 25 | ], 26 | ], 27 | ]; 28 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | tests/ZendTwig/ 17 | 18 | 19 | 20 | 21 | ./src 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/Fixture/config/helpers/config.instance-exception.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'factories' => [ 11 | DummyClassInvokable::class => InvokableFactory::class, 12 | ], 13 | ], 14 | 'view_manager' => [ 15 | 'template_path_stack' => [ 16 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 17 | ], 18 | 'default_template_suffix' => TwigLoaderFactory::DEFAULT_SUFFIX, 19 | ], 20 | 'zend_twig' => [ 21 | 'helpers' => [ 22 | 'configs' => [ 23 | HelperConfig::class, 24 | DummyClassInvokable::class, 25 | // will \ZendTwig\Service\TwigHelperPluginManagerFactory 26 | 42, 27 | ], 28 | ], 29 | ], 30 | ]; 31 | -------------------------------------------------------------------------------- /docs/TwigModel.md: -------------------------------------------------------------------------------- 1 | # TwigModel 2 | Zend Framework has interesting approach how to get correct renderer. 3 | By default flow developer has something like this in controllers 4 | ```php 5 | public function indexAction() 6 | { 7 | // \Zend\View\Model\ViewModel 8 | return new ViewModel([ 9 | 'foo' => 'bar', 10 | ]); 11 | } 12 | ``` 13 | In this case ZF will render template with PhpRenderer. 14 | 15 | 16 | When developer would like to send JSON response, then we can change to 17 | ```php 18 | public function indexAction() 19 | { 20 | // \Zend\View\Model\JsonModel 21 | return new JsonModel([ 22 | 'foo' => 'bar', 23 | ]); 24 | } 25 | ``` 26 | 27 | Now, with this extension developer can use ```TwigModel``` to render Twig templates with TwigRenderer. 28 | ```php 29 | public function indexAction() 30 | { 31 | // \ZendTwig\View\TwigModel 32 | return new TwigModel([ 33 | 'foo' => 'bar', 34 | ]); 35 | } 36 | ``` 37 | 38 | *NOTE*: To have described behaviour, please, set ```'force_twig_strategy'``` to ```false``` -------------------------------------------------------------------------------- /src/Resolver/TwigResolver.php: -------------------------------------------------------------------------------- 1 | environment = $environment; 26 | } 27 | 28 | /** 29 | * Resolve a template/pattern name to a resource the renderer can consume 30 | * 31 | * @param string $name 32 | * @param null|Renderer $renderer 33 | * 34 | * @return TemplateWrapper|Template 35 | */ 36 | public function resolve($name, ?Renderer $renderer = null) 37 | { 38 | return $this->environment->resolveTemplate($name); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Extension/AbstractExtension.php: -------------------------------------------------------------------------------- 1 | serviceManager = $serviceManager; 28 | $this->renderer = $renderer; 29 | } 30 | 31 | /** 32 | * @return TwigRenderer 33 | */ 34 | abstract public function getRenderer(); 35 | 36 | /** 37 | * @return ContainerInterface 38 | */ 39 | abstract public function getServiceManager(); 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Andrey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'factories' => [ 12 | DummyClassInvokable::class => InvokableFactory::class, 13 | ], 14 | ], 15 | 'view_manager' => [ 16 | 'template_path_stack' => [ 17 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 18 | ], 19 | 'default_template_suffix' => TwigLoaderFactory::DEFAULT_SUFFIX, 20 | ], 21 | 'zend_twig' => [ 22 | 'helpers' => [ 23 | 'configs' => [ 24 | HelperConfig::class, 25 | DummyClassInvokable::class, 26 | [ 27 | 'aliases' => [], 28 | 'delegators' => [], 29 | 'factories' => [], 30 | ], 31 | // will \ZendTwig\Service\TwigHelperPluginManagerFactory 32 | DummyClass::class, 33 | ], 34 | ], 35 | ], 36 | ]; 37 | -------------------------------------------------------------------------------- /tests/Fixture/config/view_helpers/config.local.php: -------------------------------------------------------------------------------- 1 | [ 12 | 'factories' => [ 13 | DummyClassInvokable::class => InvokableFactory::class, 14 | ], 15 | ], 16 | 'view_manager' => [ 17 | 'template_path_stack' => [ 18 | 'ZendTwig' => __DIR__ . '/../../view/ZendTwig', 19 | ], 20 | 'default_template_suffix' => TwigLoaderFactory::DEFAULT_SUFFIX, 21 | ], 22 | 'zend_twig' => [ 23 | 'helpers' => [ 24 | 'configs' => [], 25 | ], 26 | ], 27 | 'view_helpers' => [ 28 | 'factories' => [ 29 | Menu::class => MenuFactory::class, 30 | MenuInvoke::class => MenuFactory::class, 31 | ], 32 | 'aliases' => [ 33 | 'mainMenu' => Menu::class, 34 | 'mainMenuInvoke' => MenuInvoke::class, 35 | ], 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /src/Service/TwigStackLoaderFactory.php: -------------------------------------------------------------------------------- 1 | get('Configuration'); 23 | $name = Module::MODULE_NAME; 24 | $options = $envOptions = empty($config[$name]) ? [] : $config[$name]; 25 | $suffix = empty($options['suffix']) ? TwigLoaderFactory::DEFAULT_SUFFIX : $options['suffix']; 26 | 27 | /** @var \Laminas\View\Resolver\TemplatePathStack $zfStack */ 28 | $zfStack = $container->get('ViewTemplatePathStack'); 29 | 30 | $loader = new StackLoader($zfStack->getPaths()->toArray()); 31 | $loader->setSuffix($suffix); 32 | 33 | return $loader; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Service/TwigStrategyFactory.php: -------------------------------------------------------------------------------- 1 | get('Configuration'); 22 | $name = Module::MODULE_NAME; 23 | $options = $envOptions = empty($config[$name]) ? [] : $config[$name]; 24 | 25 | /** 26 | * @var \ZendTwig\Renderer\TwigRenderer $renderer 27 | */ 28 | $renderer = $container->get(TwigRenderer::class); 29 | $strategy = new TwigStrategy($renderer); 30 | 31 | $forceStrategy = !empty($options['force_twig_strategy']); 32 | $strategy->setForceRender($forceStrategy); 33 | 34 | return $strategy; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Service/TwigMapLoaderFactory.php: -------------------------------------------------------------------------------- 1 | get('Configuration'); 23 | $name = Module::MODULE_NAME; 24 | $options = $envOptions = empty($config[$name]) ? [] : $config[$name]; 25 | $suffix = empty($options['suffix']) ? TwigLoaderFactory::DEFAULT_SUFFIX : $options['suffix']; 26 | 27 | /** @var \Laminas\View\Resolver\TemplateMapResolver $zfMap */ 28 | $zfMap = $container->get('ViewTemplateMapResolver'); 29 | 30 | $loader = new MapLoader(); 31 | foreach ($zfMap as $name => $path) { 32 | if ($suffix == pathinfo($path, PATHINFO_EXTENSION)) { 33 | $loader->add($name, $path); 34 | } 35 | } 36 | 37 | return $loader; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Loader/StackLoader.php: -------------------------------------------------------------------------------- 1 | suffix = (string)$suffix; 28 | $this->suffix = ltrim($this->suffix, '.'); 29 | 30 | return $this; 31 | } 32 | 33 | /** 34 | * Get default file suffix 35 | * 36 | * @return string 37 | */ 38 | public function getSuffix() : string 39 | { 40 | return $this->suffix; 41 | } 42 | 43 | /** 44 | * @{@inheritdoc} 45 | */ 46 | protected function findTemplate($name, $throw = true) 47 | { 48 | // Ensure we have the expected file extension 49 | $defaultSuffix = $this->getSuffix(); 50 | if (pathinfo($name, PATHINFO_EXTENSION) != $defaultSuffix) { 51 | $name .= '.' . $defaultSuffix; 52 | } 53 | 54 | return parent::findTemplate($name, $throw); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Fixture/Twig/View/Helper/Menu.php: -------------------------------------------------------------------------------- 1 | items = $items; 32 | } 33 | 34 | /** 35 | * Sets menu items. 36 | * 37 | * @param array $items Menu items. 38 | */ 39 | public function setItems($items) 40 | { 41 | $this->items = $items; 42 | } 43 | 44 | /** 45 | * Sets ID of the active items. 46 | * 47 | * @param string $activeItemId 48 | */ 49 | public function setActiveItemId($activeItemId) 50 | { 51 | $this->activeItemId = $activeItemId; 52 | } 53 | 54 | /** 55 | * Renders the menu. 56 | * @return string HTML code of the menu. 57 | */ 58 | public function render() 59 | { 60 | return $this->activeItemId . ' ' . implode(', ', $this->items); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Service/TwigLoaderFactory.php: -------------------------------------------------------------------------------- 1 | get('Configuration'); 25 | $name = Module::MODULE_NAME; 26 | $options = $envOptions = empty($config[$name]) ? [] : $config[$name]; 27 | $list = empty($options['loader_chain']) ? [] : $options['loader_chain']; 28 | $chain = new ChainLoader(); 29 | 30 | foreach ($list as $loader) { 31 | if (!is_string($loader) || !$container->has($loader)) { 32 | throw new InvalidArgumentException('Loaders should be a service manager alias.'); 33 | } 34 | 35 | $chain->addLoader($container->get($loader)); 36 | } 37 | 38 | return $chain; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/ZendTwig/Renderer/InvokeViewHelpersTest.php: -------------------------------------------------------------------------------- 1 | setApplication($bootstrap->getApplication()); 26 | 27 | $module = new Module(); 28 | $module->onBootstrap($e); 29 | 30 | /** 31 | * @var \ZendTwig\Renderer\TwigRenderer $render 32 | */ 33 | $sm = $bootstrap->getServiceManager(); 34 | $render = $sm->get(TwigRenderer::class); 35 | 36 | $result = $render->render('View/issue-7/ViewHelpers', []); 37 | $this->assertEquals("permission a, b, c", $result); 38 | 39 | $result = $render->render('View/issue-7/ViewHelpersInvoke', []); 40 | $this->assertEquals("permission a, b, c", $result); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/View/FallbackFunction.php: -------------------------------------------------------------------------------- 1 | getExtension(Extension::class); 30 | $plugin = $extension->getRenderer() 31 | ->plugin($name); 32 | 33 | if (is_callable($plugin)) { 34 | // helper should implement __invoke() function 35 | $args = empty($args) ? [] : $args; 36 | 37 | return call_user_func_array($plugin, $args); 38 | } else { 39 | return $plugin; 40 | } 41 | }; 42 | 43 | $options = [ 44 | 'needs_environment' => true, 45 | 'is_safe' => ['all'], 46 | ]; 47 | 48 | return new TwigFunction($name, $callable, $options); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Fixture/Twig/DummyClassInvokable.php: -------------------------------------------------------------------------------- 1 | get('Configuration'); 26 | $name = Module::MODULE_NAME; 27 | $options = empty($config[$name]) ? [] : $config[$name]; 28 | 29 | /** 30 | * @var Environment $env 31 | */ 32 | $env = $container->get(Environment::class); 33 | $view = $container->get(View::class); 34 | $resolver = $container->get(TwigResolver::class); 35 | $renderer = new TwigRenderer($view, $env, $resolver); 36 | 37 | $renderer->setTwigHelpers($container->get(TwigHelperPluginManager::class)); 38 | $renderer->setZendHelpers($container->get('ViewHelperManager')); 39 | 40 | if (!empty($options['force_standalone'])) { 41 | $renderer = $renderer->setForceStandalone(true); 42 | } 43 | 44 | return $renderer; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master, main ] 6 | pull_request: 7 | branches: [ master, main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | php-version: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] 16 | 17 | name: PHP ${{ matrix.php-version }} Test 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Setup PHP 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: ${{ matrix.php-version }} 26 | extensions: mbstring, intl 27 | coverage: xdebug 28 | tools: composer:v2 29 | ini-values: xdebug.mode=coverage 30 | 31 | - name: Validate composer.json 32 | run: composer validate --strict 33 | 34 | - name: Install dependencies 35 | run: | 36 | composer require roave/security-advisories:dev-master --no-update || true 37 | composer install --prefer-dist --no-progress 38 | 39 | - name: Display PHP version 40 | run: php -v 41 | 42 | - name: Run code style check 43 | run: vendor/bin/phpcs --standard=psr2 src/ tests/ZendTwig/ tests/Fixture/ 44 | 45 | - name: Run tests with coverage 46 | run: vendor/bin/phpunit --coverage-clover=coverage.xml || true 47 | 48 | - name: Check if tests passed 49 | run: vendor/bin/phpunit --no-coverage 50 | 51 | - name: Upload coverage to Codecov 52 | if: success() 53 | uses: codecov/codecov-action@v4 54 | with: 55 | files: ./coverage.xml 56 | fail_ci_if_error: false 57 | token: ${{ secrets.CODECOV_TOKEN }} 58 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oxcom/zend-twig", 3 | "description": "ZendTwig is a module that integrates the Twig template engine with Zend Framework 3.", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "twig", 8 | "module", 9 | "zf3", 10 | "zf" 11 | ], 12 | "homepage": "https://github.com/OxCom/zf3-twig", 13 | "require": { 14 | "php": ">=7.3", 15 | "twig/twig": "^2.12|^v3.11.3", 16 | "laminas/laminas-modulemanager": "^2.7", 17 | "laminas/laminas-servicemanager": "^3.2", 18 | "laminas/laminas-mvc": "^3.0", 19 | "laminas/laminas-view": "^2.8", 20 | "laminas/laminas-eventmanager": "^3.1", 21 | "laminas/laminas-mvc-plugin-flashmessenger": "^1.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "ZendTwig\\" : "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "classmap": [ 30 | "tests/" 31 | ] 32 | }, 33 | "require-dev": { 34 | "laminas/laminas-test": "^3.5 || ^4.0", 35 | "laminas/laminas-i18n": "^2.7", 36 | "laminas/laminas-mvc-i18n": "^1.0", 37 | "laminas/laminas-navigation": "^2.8", 38 | "laminas/laminas-server": "^2.7", 39 | "squizlabs/php_codesniffer": "^3.3", 40 | "phpunit/phpunit": "^9.0 || ^10.0 || ^11.0" 41 | }, 42 | "authors": [ 43 | { 44 | "name": "OxCom", 45 | "email": "lancer.oxcom@gmail.com" 46 | } 47 | ], 48 | "scripts": { 49 | "test": [ 50 | "composer install", 51 | "./vendor/bin/phpcs --standard=psr2 src/ tests/ZendTwig/ tests/Fixture/", 52 | "./vendor/bin/phpunit" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Fixture/config/application.config.php: -------------------------------------------------------------------------------- 1 | [ 12 | 'Laminas\Router', 13 | 'Laminas\Validator', 14 | 'Laminas\I18n', 15 | 'Laminas\Mvc\I18n', 16 | 'ZendTwig', 17 | ], 18 | 19 | // These are various options for the listeners attached to the ModuleManager 20 | 'module_listener_options' => [ 21 | 'module_paths' => [ 22 | __DIR__ . '/../src', 23 | __DIR__ . '/../vendor', 24 | ], 25 | 26 | // An array of paths from which to glob configuration files after 27 | // modules are loaded. These effectively override configuration 28 | // provided by modules themselves. Paths may use GLOB_BRACE notation. 29 | 'config_glob_paths' => [ 30 | realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local}.php', 31 | ], 32 | 33 | // Whether or not to enable a configuration cache. 34 | // If enabled, the merged configuration will be cached and used in 35 | // subsequent requests. 36 | 'config_cache_enabled' => false, 37 | 38 | // Whether or not to enable a module class map cache. 39 | // If enabled, creates a module class map cache which will be used 40 | // by in future requests, to reduce the autoloading process. 41 | 'module_map_cache_enabled' => false, 42 | ], 43 | ]; 44 | -------------------------------------------------------------------------------- /tests/ZendTwig/View/TwigModelTest.php: -------------------------------------------------------------------------------- 1 | setApplication($bootstrap->getApplication()); 33 | 34 | $module = new Module(); 35 | $module->onBootstrap($e); 36 | 37 | $vm = new \Laminas\Mvc\View\Http\ViewManager(); 38 | $vm->onBootstrap($e); 39 | 40 | $view = $bootstrap->getServiceManager()->get(\Laminas\View\View::class); 41 | $response = new Response(); 42 | $twigModel = new TwigModel(['value' => 'twig']); 43 | $genericModel = new ViewModel(['value' => 'php']); 44 | 45 | $view->setResponse($response); 46 | $twigModel->setTemplate('View/model/testTwigModel'); 47 | $genericModel->setTemplate('View/model/testPhpModel'); 48 | 49 | $view->render($twigModel); 50 | $result = $view->getResponse(); 51 | 52 | $this->assertEquals('TWIG:twig', $result->getContent()); 53 | 54 | $view->render($genericModel); 55 | $result = $view->getResponse(); 56 | 57 | $this->assertEquals('PHP:php', $result->getContent()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/View/HelperPluginManager.php: -------------------------------------------------------------------------------- 1 | FlashMessenger::class, 27 | 'doctype' => Doctype::class, 28 | 'declarevars' => DeclareVars::class, 29 | 'htmlflash' => HtmlFlash::class, 30 | 'htmllist' => HtmlList::class, 31 | 'htmlobject' => HtmlObject::class, 32 | 'htmlpage' => HtmlPage::class, 33 | 'htmlquicktime' => HtmlQuicktime::class, 34 | 'layout' => Layout::class, 35 | 'renderchildmodel' => RenderChildModel::class, 36 | ]; 37 | /** 38 | * Default factories 39 | * 40 | * @var string[] 41 | */ 42 | protected $factories = [ 43 | FlashMessenger::class => FlashMessengerFactory::class, 44 | Doctype::class => InvokableFactory::class, 45 | DeclareVars::class => InvokableFactory::class, 46 | HtmlFlash::class => InvokableFactory::class, 47 | HtmlList::class => InvokableFactory::class, 48 | HtmlObject::class => InvokableFactory::class, 49 | HtmlPage::class => InvokableFactory::class, 50 | HtmlQuicktime::class => InvokableFactory::class, 51 | Layout::class => InvokableFactory::class, 52 | RenderChildModel::class => InvokableFactory::class, 53 | ]; 54 | } 55 | -------------------------------------------------------------------------------- /src/Service/TwigEnvironmentFactory.php: -------------------------------------------------------------------------------- 1 | get('Configuration'); 24 | $name = Module::MODULE_NAME; 25 | $options = $envOptions = empty($config[$name]) ? [] : $config[$name]; 26 | $envOptions = empty($options['environment']) ? [] : $options['environment']; 27 | $loader = $container->get(\Twig\Loader\ChainLoader::class); 28 | $env = new Environment($loader, $envOptions); 29 | $zendHelpers = !isset($options['invoke_zend_helpers']) ? false : (bool)$options['invoke_zend_helpers']; 30 | 31 | if ($zendHelpers) { 32 | $twigHelperPluginManager = $container->get(HelperPluginManager::class); 33 | $zendHelperPluginManager = $container->get('ViewHelperManager'); 34 | $env->registerUndefinedFunctionCallback( 35 | function ($name) use ($twigHelperPluginManager, $zendHelperPluginManager) { 36 | if ($twigHelperPluginManager->has($name) || $zendHelperPluginManager->has($name)) { 37 | return FallbackFunction::build($name); 38 | } 39 | 40 | $name = strtolower('zendviewhelper' . $name); 41 | if ($zendHelperPluginManager->has($name)) { 42 | return FallbackFunction::build($name); 43 | } 44 | 45 | return false; 46 | } 47 | ); 48 | } 49 | 50 | return $env; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZendTwig module for Zend Framework 3 2 | [![Latest Stable Version](https://poser.pugx.org/oxcom/zend-twig/v/stable)](https://packagist.org/packages/oxcom/zend-twig) 3 | [![Total Downloads](https://poser.pugx.org/oxcom/zend-twig/downloads)](https://packagist.org/packages/oxcom/zend-twig) 4 | [![codecov.io](https://codecov.io/github/OxCom/zf3-twig/coverage.svg?branch=master)](https://codecov.io/github/OxCom/zf3-twig?branch=master) 5 | [![Build Status](https://travis-ci.org/OxCom/zf3-twig.svg?branch=master)](https://travis-ci.org/OxCom/zf3-twig) 6 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 7 | 8 | ZendTwig is a module that integrates the [Twig](https://github.com/twigphp/Twig) template engine with [Zend Framework 3](https://github.com/zendframework/zendframework) / [Laminas Project](https://getlaminas.org/). 9 | 10 | ## Notes 11 | - From release v1.0.0 this project depends on [Laminas Project](https://getlaminas.org/) components. 12 | - Development of package udner PHP5.6 has low priority and will be discontinued in closest feature. 13 | 14 | 15 | ## Install 16 | 1. Add ZendTwig lib with composer: ``` composer require oxcom/zend-twig ``` 17 | 2. Add ZendTwig to Your ``` config/application.config.php ``` file as module: 18 | ```php 19 | // Retrieve list of modules used in this application. 20 | 'modules' => [ 21 | 'Zend\Router', 22 | 'Zend\Validator', 23 | 'Zend\I18n', 24 | 'Zend\Mvc\I18n', 25 | 'Application', 26 | // ... 27 | 'ZendTwig', 28 | ], 29 | ``` 30 | 31 | ## Setting up 32 | [Here](https://github.com/OxCom/zf3-twig/tree/master/docs) You can find some examples, configurations and e.t.c. that, I hope, will help You do build Your application. 33 | Short list of available docs: 34 | 35 | 1. [ZendTwig module](https://github.com/OxCom/zf3-twig/blob/master/docs/Module.md) 36 | 2. [Custom Twig Extensions](https://github.com/OxCom/zf3-twig/blob/master/docs/Extensions.md) 37 | 3. [TwigModel](https://github.com/OxCom/zf3-twig/blob/master/docs/TwigModel.md) 38 | 39 | ## Bugs and Issues 40 | Please, if You found a bug or something, that is not working properly, contact me and tell what's wrong. It's nice to have an example how to reproduce a bug, or any idea how to fix it in Your request. I'll take care about it ASAP. 41 | -------------------------------------------------------------------------------- /src/Module.php: -------------------------------------------------------------------------------- 1 | getApplication(); 25 | $container = $app->getServiceManager(); 26 | 27 | /** 28 | * @var Environment $env 29 | */ 30 | $config = $container->get('Configuration'); 31 | $env = $container->get(Environment::class); 32 | $name = static::MODULE_NAME; 33 | $options = empty($config[$name]) ? [] : $config[$name]; 34 | $extensions = empty($options['extensions']) ? [] : $options['extensions']; 35 | $renderer = $container->get(TwigRenderer::class); 36 | 37 | // Setup extensions 38 | foreach ($extensions as $extension) { 39 | // Allows modules to override/remove extensions. 40 | if (empty($extension)) { 41 | continue; 42 | } 43 | 44 | if (is_string($extension)) { 45 | if ($container->has($extension)) { 46 | $extension = $container->get($extension); 47 | } else { 48 | $extension = new $extension($container, $renderer); 49 | } 50 | } elseif (!is_object($extension)) { 51 | throw new InvalidArgumentException('Extensions should be a string or object.'); 52 | } 53 | 54 | if (!$env->hasExtension(get_class($extension))) { 55 | $env->addExtension($extension); 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * Returns configuration to merge with application configuration 62 | * 63 | * @return array|\Traversable 64 | */ 65 | public function getConfig() 66 | { 67 | return include __DIR__ . '/../config/module.config.php'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /docs/Module.md: -------------------------------------------------------------------------------- 1 | # ZendTwig module 2 | 3 | The module configuration should be done in scope of ``` zend_twig ``` key. Default configuration is: 4 | ```php 5 | 'zend_twig' => [ 6 | 'force_standalone' => true, 7 | 'invoke_zend_helpers' => true, 8 | 'environment' => [ 9 | ], 10 | 'loader_chain' => [ 11 | \ZendTwig\Loader\MapLoader::class, 12 | \ZendTwig\Loader\StackLoader::class, 13 | ], 14 | 'extensions' => [ 15 | \ZendTwig\Extension\Extension::class, 16 | ], 17 | 'helpers' => [ 18 | 'configs' => [ 19 | \Zend\Navigation\View\HelperConfig::class, 20 | ], 21 | ], 22 | ], 23 | ``` 24 | 25 | ## Key: environment 26 | There should be done configuration of Twig environment. List of available options can be found [here](http://twig.sensiolabs.org/doc/api.html#environment-options). 27 | 28 | 29 | ## Key: loader_chain 30 | In current module You can set Your own template loaders. 31 | This loaders will be added to the [\Twig\Loader\ChainLoader](http://twig.sensiolabs.org/doc/api.html#twig-loader-chain). So, developer will be able to add custom loaders. 32 | All loaders should be available from service manager. 33 | 34 | ## Key: extensions 35 | Developer can inject custom extensions into Twig. For new extension developer should extends his extension from ``` \ZendTwig\Extension\AbstractExtension ``` 36 | and should be available from service manager by ``` \ZendTwig\Service\TwigExtensionFactory::class ```. Example is here: [Here](https://github.com/OxCom/zf3-twig/tree/master/docs/Extensions.md) 37 | 38 | Please, read more info about [Twig Extensions](http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension). 39 | 40 | ## Key: invoke_zend_helpers 41 | Developer can disable Zend View Helpers like docType, translate and e.t.c. Value: ``` true ``` or ``` false ```. Default: ``` true ``` 42 | This is done with ``` \ZendTwig\View\FallbackFunction ``` 43 | 44 | ## Key: helpers 45 | Configuration for Zend View Helpers 46 | 47 | ## Key: force_standalone 48 | In a ZF3 by default we have this structure: 49 | - ViewModel with template from 'layout/layout' 50 | - ViewModel as child with action template 'application/index/index' 51 | 52 | In that case we should always force standalone state of child models 53 | 54 | Value: ``` true ``` or ``` false ```. Default: ``` true ``` 55 | -------------------------------------------------------------------------------- /tests/ZendTwig/Extension/DebugExtensionTest.php: -------------------------------------------------------------------------------- 1 | setTemplate('Extensions/Debug'); 28 | 29 | $e = new MvcEvent(); 30 | $e->setApplication(Bootstrap::getInstance($config)->getApplication()); 31 | 32 | $module = new Module(); 33 | $module->onBootstrap($e); 34 | 35 | /** 36 | * @var \Laminas\View\View $view 37 | */ 38 | $sm = Bootstrap::getInstance($config)->getServiceManager(); 39 | $strategyTwig = $sm->get(TwigStrategy::class); 40 | $view = $sm->get('View'); 41 | $request = $sm->get('Request'); 42 | $response = $sm->get('Response'); 43 | 44 | $e = $view->getEventManager(); 45 | $strategyTwig->attach($e, 100); 46 | 47 | $view->setEventManager($e) 48 | ->setRequest($request) 49 | ->setResponse($response) 50 | ->render($model); 51 | 52 | $result = $view->getResponse() 53 | ->getContent(); 54 | 55 | $this->assertStringContainsString($expected, $result); 56 | } 57 | 58 | /** 59 | * @return array 60 | */ 61 | public static function generatorDebugExtension() 62 | { 63 | $randInt = mt_rand(PHP_INT_MAX / 2, PHP_INT_MAX); 64 | 65 | return [ 66 | [ 67 | [ 68 | 'variable' => $randInt, 69 | ], 70 | "int($randInt)", 71 | ], 72 | [ 73 | [ 74 | 'variable' => false, 75 | ], 76 | "bool(false)", 77 | ], 78 | ]; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Service/TwigHelperPluginManagerFactory.php: -------------------------------------------------------------------------------- 1 | get('Configuration'); 25 | $name = Module::MODULE_NAME; 26 | 27 | $envOptions = empty($config[$name]) ? [] : $config[$name]; 28 | $helpers = empty($envOptions['helpers']) ? [] : $envOptions['helpers']; 29 | $configs = empty($helpers['configs']) ? [] : $helpers['configs']; 30 | $viewHelper = new HelperPluginManager($container, $configs); 31 | 32 | foreach ($configs as $configDefinition) { 33 | $config = null; 34 | 35 | if (is_string($configDefinition) && $container->has($configDefinition)) { 36 | $config = $container->get($configDefinition); 37 | } elseif (is_string($configDefinition) && class_exists($configDefinition)) { 38 | /** @var ConfigInterface $config */ 39 | $config = new $configDefinition; 40 | 41 | if (!$config instanceof ConfigInterface) { 42 | $msg = 'Invalid service manager configuration class provided; received "%s",' 43 | . 'expected class implementing %s'; 44 | $msg = sprintf($msg, $configDefinition, ConfigInterface::class); 45 | 46 | throw new Exception\RuntimeException($msg); 47 | } 48 | } elseif (is_array($configDefinition)) { 49 | $config = new Config($configDefinition); 50 | } 51 | 52 | if (empty($config)) { 53 | throw new Exception\RuntimeException( 54 | sprintf( 55 | 'Unable to resolve provided configuration to valid instance of %s', 56 | ConfigInterface::class 57 | ) 58 | ); 59 | } 60 | 61 | $config->configureServiceManager($viewHelper); 62 | } 63 | 64 | return $viewHelper; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Loader/MapLoader.php: -------------------------------------------------------------------------------- 1 | exists($name)) { 29 | throw new LoaderError(sprintf( 30 | 'Name "%s" already exists in map', 31 | $name 32 | )); 33 | } 34 | 35 | $this->map[$name] = $path; 36 | 37 | return $this; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | public function exists($name) : bool 44 | { 45 | return array_key_exists($name, $this->map); 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | public function getCacheKey($name) : string 52 | { 53 | return $name; 54 | } 55 | 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | public function isFresh($name, $time) : bool 60 | { 61 | if (!$this->exists($name)) { 62 | throw new LoaderError(sprintf( 63 | 'Unable to find template "%s" from template map', 64 | $name 65 | )); 66 | } 67 | 68 | if (!file_exists($this->map[$name])) { 69 | throw new LoaderError(sprintf( 70 | 'Unable to open file "%s" from template map', 71 | $this->map[$name] 72 | )); 73 | } 74 | 75 | return filemtime($this->map[$name]) <= $time; 76 | } 77 | 78 | /** 79 | * Returns the source context for a given template logical name. 80 | * 81 | * @param string $name The template logical name 82 | * 83 | * @return Source 84 | * 85 | * @throws LoaderError When $name is not found 86 | */ 87 | public function getSourceContext($name) : Source 88 | { 89 | if (!$this->exists($name)) { 90 | throw new LoaderError(sprintf( 91 | 'Unable to find template "%s" from template map', 92 | $name 93 | )); 94 | } 95 | 96 | if (!file_exists($this->map[$name])) { 97 | throw new LoaderError(sprintf( 98 | 'Unable to open file "%s" from template map', 99 | $this->map[$name] 100 | )); 101 | } 102 | 103 | $content = file_get_contents($this->map[$name]); 104 | $source = new Source($content, $name, $this->map[$name]); 105 | 106 | return $source; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'strategies' => [ 6 | ZendTwig\View\TwigStrategy::class, 7 | ], 8 | ], 9 | 'service_manager' => [ 10 | 'factories' => [ 11 | \ZendTwig\View\TwigStrategy::class => \ZendTwig\Service\TwigStrategyFactory::class, 12 | \ZendTwig\View\HelperPluginManager::class => \ZendTwig\Service\TwigHelperPluginManagerFactory::class, 13 | \ZendTwig\Renderer\TwigRenderer::class => \ZendTwig\Service\TwigRendererFactory::class, 14 | \ZendTwig\Resolver\TwigResolver::class => \ZendTwig\Service\TwigResolverFactory::class, 15 | \Twig\Environment::class => \ZendTwig\Service\TwigEnvironmentFactory::class, 16 | \Twig\Loader\ChainLoader::class => \ZendTwig\Service\TwigLoaderFactory::class, 17 | \ZendTwig\Loader\MapLoader::class => \ZendTwig\Service\TwigMapLoaderFactory::class, 18 | \ZendTwig\Loader\StackLoader::class => \ZendTwig\Service\TwigStackLoaderFactory::class, 19 | \ZendTwig\Extension\Extension::class => \ZendTwig\Service\TwigExtensionFactory::class 20 | ], 21 | ], 22 | 'zend_twig' => [ 23 | 'suffix' => \ZendTwig\Service\TwigLoaderFactory::DEFAULT_SUFFIX, 24 | /** 25 | * In a ZF3 by default we have this structure: 26 | * - ViewModel with template from 'layout/layout' 27 | * - ViewModel as child with action template 'application/index/index' 28 | * In that case we should always force standalone state of child models 29 | */ 30 | 'force_standalone' => true, 31 | /** 32 | * Force Your application to use TwigRender for ViewModel. 33 | * If false, then TwigStrategy will be applied only for TwigModel 34 | * 35 | * @note: In release v.1.5 this parameter will be set to false 36 | */ 37 | 'force_twig_strategy' => true, 38 | /** 39 | * Developer can disable Zend View Helpers like docType, translate and e.t.c. 40 | */ 41 | 'invoke_zend_helpers' => true, 42 | /** 43 | * Twig Environment settings 44 | */ 45 | 'environment' => [ 46 | ], 47 | /** 48 | * Default loaders for views 49 | */ 50 | 'loader_chain' => [ 51 | \ZendTwig\Loader\MapLoader::class, 52 | \ZendTwig\Loader\StackLoader::class, 53 | ], 54 | /** 55 | * List of Twig Extensions 56 | */ 57 | 'extensions' => [ 58 | \ZendTwig\Extension\Extension::class, 59 | ], 60 | 'helpers' => [ 61 | 'configs' => [ 62 | /** 63 | * Here can be declared configuration classes for service manager: 64 | * \Zend\Form\View\HelperConfig::class, 65 | * \Zend\I18n\View\HelperConfig::class, 66 | * \Zend\Navigation\View\HelperConfig::class, 67 | */ 68 | ], 69 | ], 70 | ], 71 | ]; 72 | -------------------------------------------------------------------------------- /src/View/TwigStrategy.php: -------------------------------------------------------------------------------- 1 | renderer = $renderer; 38 | } 39 | 40 | /** 41 | * Attach one or more listeners 42 | * 43 | * Implementors may add an optional $priority argument; the EventManager 44 | * implementation will pass this to the aggregate. 45 | * 46 | * @param EventManagerInterface $events 47 | * @param int $priority 48 | * 49 | * @return void 50 | */ 51 | public function attach(EventManagerInterface $events, $priority = 1) 52 | { 53 | $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, [$this, 'selectRender'], $priority); 54 | $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, [$this, 'injectResponse'], $priority); 55 | } 56 | 57 | /** 58 | * @param \Laminas\View\ViewEvent $e 59 | * 60 | * @return \Laminas\View\Renderer\RendererInterface|null 61 | */ 62 | public function selectRender(ViewEvent $e) 63 | { 64 | if ($this->isForceRender()) { 65 | return $this->renderer; 66 | } 67 | 68 | $model = $e->getModel(); 69 | if ($model instanceof TwigModel) { 70 | return $this->renderer; 71 | } 72 | 73 | return null; 74 | } 75 | 76 | /** 77 | * @param \Laminas\View\ViewEvent $e 78 | */ 79 | public function injectResponse(ViewEvent $e) 80 | { 81 | if ($this->renderer !== $e->getRenderer()) { 82 | return; 83 | } 84 | 85 | $result = $e->getResult(); 86 | $response = $e->getResponse(); 87 | 88 | $response->setContent($result); 89 | } 90 | 91 | /** 92 | * @param bool $forceRender 93 | * 94 | * @return TwigStrategy 95 | */ 96 | public function setForceRender(bool $forceRender) : TwigStrategy 97 | { 98 | $this->forceRender = $forceRender; 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @return bool 105 | */ 106 | public function isForceRender() : bool 107 | { 108 | return $this->forceRender; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/ZendTwig/Extension/CustomExtensionTest.php: -------------------------------------------------------------------------------- 1 | getServiceManager(); 27 | $extension = $sm->get(InstanceOfExtension::class); 28 | 29 | $exRender = $extension->getRenderer(); 30 | $exSm = $extension->getServiceManager(); 31 | 32 | $this->assertInstanceOf(TwigRenderer::class, $exRender); 33 | $this->assertInstanceOf('\Interop\Container\ContainerInterface', $exSm); 34 | $this->assertSame($sm, $exSm); 35 | } 36 | 37 | public function testExtensionConstruct() 38 | { 39 | /** 40 | * @var \ZendTwig\Extension\Extension $extension 41 | */ 42 | $sm = Bootstrap::getInstance()->getServiceManager(); 43 | $renderer = $sm->get(\ZendTwig\Renderer\TwigRenderer::class); 44 | $extension = new Extension($sm, $renderer); 45 | 46 | $exRender = $extension->getRenderer(); 47 | $exSm = $extension->getServiceManager(); 48 | 49 | $this->assertInstanceOf('\ZendTwig\Renderer\TwigRenderer', $exRender); 50 | $this->assertInstanceOf('\Interop\Container\ContainerInterface', $exSm); 51 | 52 | $this->assertSame($sm, $exSm); 53 | $this->assertSame($renderer, $exRender); 54 | } 55 | 56 | /** 57 | * @dataProvider generatorExtensionData 58 | * 59 | * @param array $vars 60 | * @param string $expected 61 | */ 62 | public function testExtension($vars, $expected) 63 | { 64 | $config = include(__DIR__ . '/../../Fixture/config/application.config.php'); 65 | $config['module_listener_options']['config_glob_paths'] = [ 66 | realpath(__DIR__) . '/../../Fixture/config/extensions/{{,*.}instanceOf}.php', 67 | ]; 68 | 69 | $model = new \Laminas\View\Model\ViewModel($vars); 70 | $model->setTemplate('Extensions/InstanceOf'); 71 | 72 | $e = new MvcEvent(); 73 | $e->setApplication(Bootstrap::getInstance($config)->getApplication()); 74 | 75 | $module = new Module(); 76 | $module->onBootstrap($e); 77 | 78 | /** 79 | * @var \Laminas\View\View $view 80 | */ 81 | $sm = Bootstrap::getInstance($config)->getServiceManager(); 82 | $strategyTwig = $sm->get(TwigStrategy::class); 83 | $view = $sm->get('View'); 84 | $request = $sm->get('Request'); 85 | $response = $sm->get('Response'); 86 | 87 | $e = $view->getEventManager(); 88 | $strategyTwig->attach($e, 100); 89 | 90 | $view->setEventManager($e) 91 | ->setRequest($request) 92 | ->setResponse($response) 93 | ->render($model); 94 | 95 | $result = $view->getResponse() 96 | ->getContent(); 97 | 98 | $this->assertEquals($expected, $result); 99 | } 100 | 101 | /** 102 | * @return array 103 | */ 104 | public static function generatorExtensionData() 105 | { 106 | return [ 107 | [ 108 | [ 109 | 'varClass' => new DummyClass(), 110 | 'varInstance' => DummyClass::class, 111 | ], 112 | "Yes\n" 113 | ], 114 | [ 115 | [ 116 | 'varClass' => new DummyClass(), 117 | 'varInstance' => \ZendTwig\Module::class, 118 | ], 119 | "No\n" 120 | ], 121 | ]; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/ZendTwig/Loader/StackLoaderTest.php: -------------------------------------------------------------------------------- 1 | getServiceManager(); 18 | $loader = $sm->get(StackLoader::class); 19 | 20 | $this->assertEquals(TwigLoaderFactory::DEFAULT_SUFFIX, $loader->getSuffix()); 21 | } 22 | 23 | public function testSetSuffix() 24 | { 25 | /** 26 | * @var \ZendTwig\Loader\StackLoader $loader 27 | */ 28 | $sm = Bootstrap::getInstance()->getServiceManager(); 29 | $loader = $sm->get(StackLoader::class); 30 | 31 | $this->assertEquals(TwigLoaderFactory::DEFAULT_SUFFIX, $loader->getSuffix()); 32 | 33 | $loader->setSuffix('.sfx'); 34 | $this->assertEquals('sfx', $loader->getSuffix()); 35 | 36 | $loader->setSuffix('sfy'); 37 | $this->assertEquals('sfy', $loader->getSuffix()); 38 | 39 | $loader->setSuffix(TwigLoaderFactory::DEFAULT_SUFFIX); 40 | $this->assertEquals(TwigLoaderFactory::DEFAULT_SUFFIX, $loader->getSuffix()); 41 | } 42 | 43 | public function testFindTemplateExNoTemplate() 44 | { 45 | /** 46 | * @var \ZendTwig\Loader\StackLoader $loader 47 | */ 48 | $sm = Bootstrap::getInstance()->getServiceManager(); 49 | $loader = $sm->get(StackLoader::class); 50 | 51 | $this->expectException(\Twig\Error\LoaderError::class); 52 | $this->expectExceptionMessageMatches('/Unable to find template/'); 53 | $loader->getSourceContext('testFindTemplateExNoTemplate'); 54 | } 55 | 56 | public function testFindTemplateNoExNoTemplate() 57 | { 58 | /** 59 | * @var \ZendTwig\Loader\StackLoader $loader 60 | */ 61 | $sm = Bootstrap::getInstance()->getServiceManager(); 62 | $loader = $sm->get(StackLoader::class); 63 | 64 | $reflection = new \ReflectionClass($loader); 65 | $method = $reflection->getMethod('findTemplate'); 66 | $method->setAccessible(true); 67 | 68 | $value = $method->invokeArgs($loader, ['testFindTemplateNoExNoTemplate', false]); 69 | $this->assertEmpty($value); 70 | } 71 | 72 | public function testFindTemplateExNamespace() 73 | { 74 | /** 75 | * @var \ZendTwig\Loader\StackLoader $loader 76 | */ 77 | $sm = Bootstrap::getInstance()->getServiceManager(); 78 | $loader = $sm->get(StackLoader::class); 79 | 80 | $this->expectException(\Twig\Error\LoaderError::class); 81 | $this->expectExceptionMessageMatches('/There are no registered paths for namespace/'); 82 | $loader->getSourceContext('@ns/testFindTemplate'); 83 | } 84 | 85 | public function testFindTemplateNoExNamespace() 86 | { 87 | /** 88 | * @var \ZendTwig\Loader\StackLoader $loader 89 | */ 90 | $sm = Bootstrap::getInstance()->getServiceManager(); 91 | $loader = $sm->get(StackLoader::class); 92 | 93 | $reflection = new \ReflectionClass($loader); 94 | $method = $reflection->getMethod('findTemplate'); 95 | $method->setAccessible(true); 96 | 97 | $value = $method->invokeArgs($loader, ['@ns/testFindTemplate', false]); 98 | $this->assertEmpty($value); 99 | } 100 | 101 | public function testFindTemplate() 102 | { 103 | /** 104 | * @var \ZendTwig\Loader\StackLoader $loader 105 | */ 106 | $sm = Bootstrap::getInstance()->getServiceManager(); 107 | $loader = $sm->get(StackLoader::class); 108 | 109 | $template = $loader->getSourceContext('View/testFindTemplate'); 110 | $this->assertNotEmpty($template); 111 | } 112 | 113 | public function testFindTemplateCache() 114 | { 115 | /** 116 | * @var \ZendTwig\Loader\StackLoader $loader 117 | */ 118 | $sm = Bootstrap::getInstance()->getServiceManager(); 119 | $loader = $sm->get(StackLoader::class); 120 | 121 | // check that cache empty 122 | $ref = new \ReflectionClass($loader); 123 | $property = $ref->getProperty('cache'); 124 | $property->setAccessible(true); 125 | 126 | $cacheBefore = $property->getValue($loader); 127 | 128 | $template = $loader->getSourceContext('View/testFindTemplateCache.twig'); 129 | $this->assertNotEmpty($template); 130 | 131 | $cacheAfter = $property->getValue($loader); 132 | $this->assertNotEmpty($cacheAfter); 133 | 134 | $this->assertNotEquals($cacheBefore, $cacheAfter); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/ZendTwig/View/TwigStrategyTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('Laminas\View\Model\ModelInterface')->getMock(); 23 | $model->method('getTemplate') 24 | ->willReturn('some-template-string'); 25 | 26 | /** 27 | * @var \Laminas\View\Model\ModelInterface $model 28 | */ 29 | $event = new ViewEvent(); 30 | $event->setModel($model); 31 | 32 | /** 33 | * @var \ZendTwig\View\TwigStrategy $strategy 34 | */ 35 | $sm = Bootstrap::getInstance()->getServiceManager(); 36 | $strategy = $sm->get(TwigStrategy::class); 37 | $strategy->setForceRender(true); 38 | 39 | $renderA = $sm->get(TwigRenderer::class); 40 | $renderB = $strategy->selectRender($event); 41 | 42 | $this->assertSame($renderA, $renderB); 43 | } 44 | 45 | /** 46 | * Check that correct render was selected 47 | * 48 | * @param string $modelClassName 49 | * 50 | * @dataProvider generatorSelectRender 51 | */ 52 | public function testSelectRenderNoRender($modelClassName, $expected) 53 | { 54 | $model = $this->getMockBuilder($modelClassName)->getMock(); 55 | $model->method('getTemplate') 56 | ->willReturn('some-template-string'); 57 | 58 | $event = new ViewEvent(); 59 | $event->setModel($model); 60 | 61 | /** 62 | * @var \ZendTwig\View\TwigStrategy $strategy 63 | */ 64 | $sm = Bootstrap::getInstance()->getServiceManager(); 65 | $strategy = $sm->get(TwigStrategy::class); 66 | $strategy->setForceRender(false); 67 | $render = $strategy->selectRender($event); 68 | 69 | if (!empty($expected)) { 70 | $expected = $sm->get($expected); 71 | } 72 | 73 | $this->assertSame($expected, $render); 74 | } 75 | 76 | /** 77 | * @return array 78 | */ 79 | public static function generatorSelectRender() 80 | { 81 | return [ 82 | [ViewModel::class, null], 83 | [JsonModel::class, null], 84 | [TwigModel::class, TwigRenderer::class], 85 | ]; 86 | } 87 | 88 | /** 89 | * Check that response was injected 90 | * 91 | * @dataProvider generatorInjectResponse 92 | * @param $template 93 | * @param $expected 94 | */ 95 | public function testInjectResponse($template, $expected) 96 | { 97 | $model = new TwigModel([ 98 | 'key1' => 'value1', 99 | 'key2' => 'value2', 100 | ]); 101 | 102 | $model->setTemplate($template); 103 | 104 | /** 105 | * @var \Laminas\View\View $view 106 | */ 107 | $sm = Bootstrap::getInstance()->getServiceManager(); 108 | $strategyTwig = $sm->get(TwigStrategy::class); 109 | $view = $sm->get('View'); 110 | $request = $sm->get('Request'); 111 | $response = $sm->get('Response'); 112 | 113 | $e = $view->getEventManager(); 114 | $strategyTwig->attach($e, 100); 115 | 116 | $view->setEventManager($e) 117 | ->setRequest($request) 118 | ->setResponse($response) 119 | ->render($model); 120 | 121 | $result = $view->getResponse() 122 | ->getContent(); 123 | 124 | $this->assertEquals($expected, $result); 125 | } 126 | 127 | public static function generatorInjectResponse() 128 | { 129 | return [ 130 | ['View/testInjectResponse', "value1value2\n"], 131 | ['View/testTreeRender', "
block
\n
content
\n"], 132 | ['layout', "9,800.33\n"], 133 | ]; 134 | } 135 | 136 | /** 137 | * Check that response was injected but with wrong render 138 | */ 139 | public function testInvalidInjectResponse() 140 | { 141 | $sm = Bootstrap::getInstance()->getServiceManager(); 142 | $phpRender = $sm->get(PhpRenderer::class); 143 | $strategyTwig = new TwigStrategy($phpRender); 144 | $expected = "{{ key1 }}{{ key2 }}\n"; 145 | $model = new \Laminas\View\Model\ViewModel([ 146 | 'key1' => 'value1', 147 | 'key2' => 'value2', 148 | ]); 149 | 150 | $model->setTemplate('View/testInjectResponse'); 151 | 152 | $view = $sm->get('View'); 153 | $request = $sm->get('Request'); 154 | $response = $sm->get('Response'); 155 | 156 | $e = $view->getEventManager(); 157 | $strategyTwig->attach($e, 100); 158 | $view->setEventManager($e) 159 | ->setRequest($request) 160 | ->setResponse($response) 161 | ->render($model); 162 | 163 | $result = $view->getResponse() 164 | ->getContent(); 165 | 166 | $this->assertEquals($expected, $result); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /tests/ZendTwig/Loader/MapLoaderTest.php: -------------------------------------------------------------------------------- 1 | getServiceManager(); 17 | $loader = $sm->get(MapLoader::class); 18 | 19 | $this->assertTrue($loader->exists('layout')); 20 | $this->assertFalse($loader->exists('not-exists')); 21 | } 22 | 23 | /** 24 | * @dataProvider generatorAdd 25 | * 26 | * @param string $layout 27 | * @param string $path 28 | * 29 | * @throws \Twig\Error\LoaderError 30 | */ 31 | public function testAdd($layout, $path) 32 | { 33 | /** 34 | * @var \ZendTwig\Loader\MapLoader $loader 35 | */ 36 | $sm = Bootstrap::getInstance()->getServiceManager(); 37 | $loader = $sm->get(MapLoader::class); 38 | 39 | $chain = $loader->add($layout, $path); 40 | $this->assertSame($loader, $chain); 41 | $this->assertTrue($loader->exists('layout')); 42 | $this->assertTrue($chain->exists('layout')); 43 | } 44 | 45 | /** 46 | * @return array 47 | */ 48 | public static function generatorAdd() 49 | { 50 | return [ 51 | ['layout-1', 'path/to/layout-1.twig'], 52 | ['layout-2', 'path/to/layout-2.twig'], 53 | ['layout-3', 'path/to/layout-3.twig'], 54 | ]; 55 | } 56 | 57 | public function testAddEx() 58 | { 59 | /** 60 | * @var \ZendTwig\Loader\MapLoader $loader 61 | */ 62 | $sm = Bootstrap::getInstance()->getServiceManager(); 63 | $loader = $sm->get(MapLoader::class); 64 | 65 | $this->assertTrue($loader->exists('layout')); 66 | $loader->add('layout-4', 'path/to/layout-4.twig'); 67 | 68 | $this->expectException(\Twig\Error\LoaderError::class); 69 | $this->expectExceptionMessage('Name "layout" already exists in map'); 70 | 71 | $this->assertTrue($loader->exists('layout')); 72 | $loader->add('layout', 'path/to/layout.twig'); 73 | } 74 | 75 | public function testGetSourceContext() 76 | { 77 | /** 78 | * @var \ZendTwig\Loader\MapLoader $loader 79 | */ 80 | $sm = Bootstrap::getInstance()->getServiceManager(); 81 | $loader = $sm->get(MapLoader::class); 82 | $layout = 'layout'; 83 | 84 | $this->assertTrue($loader->exists($layout)); 85 | $data = $loader->getSourceContext($layout); 86 | 87 | $this->assertInstanceOf(\Twig\Source::class, $data); 88 | $this->assertNotEmpty($data->getCode()); 89 | } 90 | 91 | public function testGetSourceNotExistsMap() 92 | { 93 | /* @var MapLoader $loader */ 94 | $sm = Bootstrap::getInstance()->getServiceManager(); 95 | $loader = $sm->get(MapLoader::class); 96 | $layout = 'layout-not-exists'; 97 | 98 | $this->assertFalse($loader->exists($layout)); 99 | 100 | $this->expectException(\Twig\Error\LoaderError::class); 101 | $this->expectExceptionMessage('Unable to find template "layout-not-exists" from template map'); 102 | $loader->getSourceContext($layout); 103 | } 104 | 105 | public function testGetSourceNotExistsFile() 106 | { 107 | /** 108 | * @var \ZendTwig\Loader\MapLoader $loader 109 | */ 110 | $sm = Bootstrap::getInstance()->getServiceManager(); 111 | $loader = $sm->get(MapLoader::class); 112 | $layout = 'layout-file-not-exists'; 113 | $path = 'path/to/not/exists/file.twig'; 114 | 115 | $loader->add($layout, $path); 116 | $this->assertTrue($loader->exists($layout)); 117 | 118 | $this->expectException(\Twig\Error\LoaderError::class); 119 | $this->expectExceptionMessage('Unable to open file "path/to/not/exists/file.twig" from template map'); 120 | $loader->getSourceContext($layout); 121 | } 122 | 123 | public function testIsFresh() 124 | { 125 | /** 126 | * @var \ZendTwig\Loader\MapLoader $loader 127 | */ 128 | $sm = Bootstrap::getInstance()->getServiceManager(); 129 | $loader = $sm->get(MapLoader::class); 130 | 131 | $this->assertFalse($loader->isFresh('layout', 0)); 132 | $this->assertTrue($loader->isFresh('layout', PHP_INT_MAX)); 133 | } 134 | 135 | /** 136 | * @throws \Twig\Error\LoaderError 137 | */ 138 | public function testIsFreshNotExists() 139 | { 140 | /** 141 | * @var \ZendTwig\Loader\MapLoader $loader 142 | */ 143 | $sm = Bootstrap::getInstance()->getServiceManager(); 144 | $loader = $sm->get(MapLoader::class); 145 | 146 | $this->expectException('\Twig\Error\LoaderError'); 147 | $this->expectExceptionMessageMatches('/Unable to find template/'); 148 | 149 | $loader->isFresh(rand(1, PHP_INT_MAX), 0); 150 | } 151 | 152 | /** 153 | * @throws \Twig\Error\LoaderError 154 | */ 155 | public function testIsFreshNotFile() 156 | { 157 | /** 158 | * @var \ZendTwig\Loader\MapLoader $loader 159 | */ 160 | $sm = Bootstrap::getInstance()->getServiceManager(); 161 | $loader = $sm->get(MapLoader::class); 162 | 163 | $name = 'template-' . rand(1, PHP_INT_MAX); 164 | $loader->add($name, 'path/to/layout-1.twig'); 165 | 166 | $this->expectException('\Twig\Error\LoaderError'); 167 | $this->expectExceptionMessageMatches('/Unable to open file/'); 168 | 169 | $loader->isFresh($name, 0); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /tests/ZendTwig/ModuleTest.php: -------------------------------------------------------------------------------- 1 | setApplication(Bootstrap::getInstance()->getApplication()); 21 | 22 | $module = new Module(); 23 | $module->onBootstrap($e); 24 | 25 | $render = $e->getApplication()->getServiceManager()->get(TwigRenderer::class); 26 | $this->assertInstanceOf(TwigRenderer::class, $render); 27 | } 28 | 29 | public function testOnBootstrapNullExtension() 30 | { 31 | $config = include(__DIR__ . '/../Fixture/config/application.config.php'); 32 | $config['module_listener_options']['config_glob_paths'] = [ 33 | realpath(__DIR__) . '/../Fixture/config/extensions/{{,*.}empty}.php', 34 | ]; 35 | 36 | $e = new MvcEvent(); 37 | $e->setApplication(Bootstrap::getInstance($config)->getApplication()); 38 | 39 | $module = new Module(); 40 | $module->onBootstrap($e); 41 | 42 | $render = $e->getApplication()->getServiceManager()->get(TwigRenderer::class); 43 | $this->assertInstanceOf(TwigRenderer::class, $render); 44 | } 45 | 46 | public function testOnBootstrapExceptionExtension() 47 | { 48 | $config = include(__DIR__ . '/../Fixture/config/application.config.php'); 49 | $config['module_listener_options']['config_glob_paths'] = [ 50 | realpath(__DIR__) . '/../Fixture/config/extensions/{{,*.}exception}.php', 51 | ]; 52 | 53 | $e = new MvcEvent(); 54 | $e->setApplication(Bootstrap::getInstance($config)->getApplication()); 55 | 56 | $module = new Module(); 57 | 58 | $this->expectException(\Laminas\View\Exception\InvalidArgumentException::class); 59 | 60 | $module->onBootstrap($e); 61 | } 62 | 63 | public function testOnBootstrapFactoryExtension() 64 | { 65 | $config = include(__DIR__ . '/../Fixture/config/application.config.php'); 66 | $config['module_listener_options']['config_glob_paths'] = [ 67 | realpath(__DIR__) . '/../Fixture/config/extensions/{{,*.}factory}.php', 68 | ]; 69 | 70 | $e = new MvcEvent(); 71 | $e->setApplication(Bootstrap::getInstance($config)->getApplication()); 72 | 73 | $module = new Module(); 74 | $module->onBootstrap($e); 75 | 76 | /** 77 | * @var Environment $twig 78 | */ 79 | $twig = $e->getApplication()->getServiceManager()->get(Environment::class); 80 | $ex = $twig->getExtensions(); 81 | 82 | $render = $e->getApplication()->getServiceManager()->get(TwigRenderer::class); 83 | $this->assertInstanceOf(TwigRenderer::class, $render); 84 | $this->assertNotEmpty($ex[DummyExtension::class]); 85 | } 86 | 87 | /** 88 | * Check that module was loaded 89 | */ 90 | public function testLoadModule() 91 | { 92 | /** 93 | * @var \ZendTwig\Module $module 94 | */ 95 | $module = Bootstrap::getInstance()->getModule('ZendTwig'); 96 | 97 | $this->assertInstanceOf(Module::class, $module); 98 | 99 | $configA = include(__DIR__ . '/../../config/module.config.php'); 100 | $configB = $module->getConfig(); 101 | 102 | $this->assertEquals($configA, $configB); 103 | } 104 | 105 | /** 106 | * Check that config was loaded 107 | */ 108 | public function testLoadConfig() 109 | { 110 | $config = Bootstrap::getInstance()->getServiceManager()->get('Configuration'); 111 | 112 | $this->assertTrue(isset($config[Module::MODULE_NAME])); 113 | 114 | $configA = include(__DIR__ . '/../../config/module.config.php'); 115 | $configB = $config[Module::MODULE_NAME]; 116 | 117 | $this->assertEquals($configA[Module::MODULE_NAME], $configB); 118 | } 119 | 120 | public function testLoadConfigWithInvalidHelpersClass() 121 | { 122 | $config = include(__DIR__ . '/../Fixture/config/application.config.php'); 123 | $config['module_listener_options']['config_glob_paths'] = [ 124 | realpath(__DIR__) . '/../Fixture/config/helpers/{{,*.}class-exception}.php', 125 | ]; 126 | 127 | $e = new MvcEvent(); 128 | $e->setApplication(Bootstrap::getInstance($config)->getApplication()); 129 | 130 | $module = new Module(); 131 | 132 | $this->expectException(\Laminas\ServiceManager\Exception\ServiceNotCreatedException::class); 133 | 134 | $module->onBootstrap($e); 135 | } 136 | 137 | public function testLoadConfigWithInvalidHelpersInstance() 138 | { 139 | $config = include(__DIR__ . '/../Fixture/config/application.config.php'); 140 | $config['module_listener_options']['config_glob_paths'] = [ 141 | realpath(__DIR__) . '/../Fixture/config/helpers/{{,*.}instance-exception}.php', 142 | ]; 143 | 144 | $e = new MvcEvent(); 145 | $e->setApplication(Bootstrap::getInstance($config)->getApplication()); 146 | 147 | $module = new Module(); 148 | 149 | $this->expectException(\Laminas\ServiceManager\Exception\ServiceNotCreatedException::class); 150 | 151 | $module->onBootstrap($e); 152 | } 153 | 154 | public function testLoadConfigWithInvalidLoaderClass() 155 | { 156 | $config = include(__DIR__ . '/../Fixture/config/application.config.php'); 157 | $config['module_listener_options']['config_glob_paths'] = [ 158 | realpath(__DIR__) . '/../Fixture/config/loader/{{,*.}exception}.php', 159 | ]; 160 | 161 | $e = new MvcEvent(); 162 | $e->setApplication(Bootstrap::getInstance($config)->getApplication()); 163 | 164 | $module = new Module(); 165 | 166 | $this->expectException(\Laminas\ServiceManager\Exception\ServiceNotCreatedException::class); 167 | 168 | $module->onBootstrap($e); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /tests/ZendTwig/View/FallbackFunctionTest.php: -------------------------------------------------------------------------------- 1 | getServiceManager(); 18 | $env = $sm->get(Environment::class); 19 | 20 | /** 21 | * @var \Twig\Error\LoaderError $extensionSet 22 | */ 23 | $refEnv = new \ReflectionClass($env); 24 | $prop = $refEnv->getProperty('extensionSet'); 25 | $prop->setAccessible(true); 26 | $extensionSet = $prop->getValue($env); 27 | 28 | $refSet = new \ReflectionClass($extensionSet); 29 | $prop = $refSet->getProperty('functionCallbacks'); 30 | $prop->setAccessible(true); 31 | $functions = $prop->getValue($extensionSet); 32 | 33 | $this->assertNotEmpty($functions); 34 | } 35 | 36 | public function testNoCallback() 37 | { 38 | /** 39 | * @var Environment $env 40 | */ 41 | $config = include(__DIR__ . '/../../Fixture/config/application.config.php'); 42 | $config['module_listener_options']['config_glob_paths'] = [ 43 | realpath(__DIR__) . '/../../Fixture/config/fallback/{{,*.}global}.php', 44 | ]; 45 | $sm = Bootstrap::getInstance($config)->getServiceManager(); 46 | $env = $sm->get(Environment::class); 47 | 48 | /** 49 | * @var \Twig\Error\LoaderError $extensionSet 50 | */ 51 | $refEnv = new \ReflectionClass($env); 52 | $prop = $refEnv->getProperty('extensionSet'); 53 | $prop->setAccessible(true); 54 | $extensionSet = $prop->getValue($env); 55 | 56 | $refSet = new \ReflectionClass($extensionSet); 57 | $prop = $refSet->getProperty('functionCallbacks'); 58 | $prop->setAccessible(true); 59 | $functions = $prop->getValue($extensionSet); 60 | 61 | $this->assertEmpty($functions); 62 | } 63 | 64 | public function testRenderWithFallback() 65 | { 66 | $model = new \Laminas\View\Model\ViewModel([ 67 | 'key1' => 'value1', 68 | 'key2' => 'value2', 69 | ]); 70 | 71 | $model->setTemplate('View/testRenderWithFallback'); 72 | 73 | /** 74 | * @var \Laminas\View\View $view 75 | */ 76 | $sm = Bootstrap::getInstance()->getServiceManager(); 77 | $strategyTwig = $sm->get(TwigStrategy::class); 78 | $view = $sm->get('View'); 79 | $request = $sm->get('Request'); 80 | $response = $sm->get('Response'); 81 | 82 | $e = $view->getEventManager(); 83 | $strategyTwig->attach($e, 100); 84 | 85 | $view->setEventManager($e) 86 | ->setRequest($request) 87 | ->setResponse($response) 88 | ->render($model); 89 | 90 | $result = $view->getResponse() 91 | ->getContent(); 92 | 93 | $this->assertEquals(0, strpos($result, ''), "Fallback for ZF Helpers was not injected"); 94 | } 95 | 96 | /** 97 | * @dataProvider generatorFallbackToZendHelpers 98 | * @param $template 99 | * @param $expected 100 | */ 101 | public function testFallbackToZendHelpers($template, $expected) 102 | { 103 | $model = new \Laminas\View\Model\ViewModel([ 104 | 'key1' => 'value1', 105 | 'key2' => 'value2', 106 | ]); 107 | 108 | $model->setTemplate($template); 109 | 110 | /** 111 | * @var \Laminas\View\View $view 112 | */ 113 | $sm = Bootstrap::getInstance()->getServiceManager(); 114 | $strategyTwig = $sm->get(TwigStrategy::class); 115 | $view = $sm->get('View'); 116 | $request = $sm->get('Request'); 117 | $response = $sm->get('Response'); 118 | 119 | $e = $view->getEventManager(); 120 | $strategyTwig->attach($e, 100); 121 | 122 | $view->setEventManager($e) 123 | ->setRequest($request) 124 | ->setResponse($response) 125 | ->render($model); 126 | 127 | $result = $view->getResponse() 128 | ->getContent(); 129 | 130 | $this->assertEquals($expected, html_entity_decode($result)); 131 | } 132 | 133 | /** 134 | * @return array 135 | */ 136 | public static function generatorFallbackToZendHelpers() 137 | { 138 | return [ 139 | [ 140 | 'Helpers/basePath', 141 | "/css/app.css\n" 142 | ], 143 | [ 144 | 'Helpers/headMeta', 145 | "\n" 146 | . "\n" 147 | ], 148 | [ 149 | 'Helpers/headTitle', 150 | "Test passed\n" 151 | ], 152 | [ 153 | 'Helpers/docType', 154 | "\n" 155 | ], 156 | ]; 157 | } 158 | 159 | public function testFallbackToNotExistsZendHelpers() 160 | { 161 | $model = new \Laminas\View\Model\ViewModel([ 162 | 'key1' => 'value1', 163 | 'key2' => 'value2', 164 | ]); 165 | 166 | $model->setTemplate('Helpers/NotExists'); 167 | 168 | /** 169 | * @var \Laminas\View\View $view 170 | */ 171 | $sm = Bootstrap::getInstance()->getServiceManager(); 172 | $strategyTwig = $sm->get(TwigStrategy::class); 173 | $view = $sm->get('View'); 174 | $request = $sm->get('Request'); 175 | $response = $sm->get('Response'); 176 | 177 | $e = $view->getEventManager(); 178 | $strategyTwig->attach($e, 100); 179 | 180 | $this->expectException(\Twig\Error\SyntaxError::class); 181 | 182 | $view->setEventManager($e) 183 | ->setRequest($request) 184 | ->setResponse($response) 185 | ->render($model); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Renderer/TwigRenderer.php: -------------------------------------------------------------------------------- 1 | setView($view) 74 | ->setEnvironment($env) 75 | ->setLoader($env->getLoader()) 76 | ->setResolver($resolver); 77 | } 78 | 79 | /** 80 | * Overloading: proxy to helpers 81 | * 82 | * Proxies to the attached plugin manager to retrieve, return, and potentially 83 | * execute helpers. 84 | * 85 | * * If the helper does not define __invoke, it will be returned 86 | * * If the helper does define __invoke, it will be called as a function 87 | * 88 | * @param string $method 89 | * @param array $argv 90 | * 91 | * @return mixed 92 | */ 93 | public function __call($method, $argv) 94 | { 95 | $plugin = $this->plugin($method); 96 | 97 | if (is_callable($plugin)) { 98 | return call_user_func_array($plugin, $argv); 99 | } 100 | 101 | // @codeCoverageIgnoreStart 102 | return $plugin; 103 | // @codeCoverageIgnoreEnd 104 | } 105 | 106 | /** 107 | * Get plugin instance 108 | * 109 | * @param string $name Name of plugin to return 110 | * @param null|array $options Options to pass to plugin constructor (if not already instantiated) 111 | * 112 | * @return AbstractHelper|callable 113 | */ 114 | public function plugin($name, ?array $options = null) 115 | { 116 | $helper = $this->getTwigHelpers()->setRenderer($this); 117 | 118 | if ($helper->has($name)) { 119 | return $helper->get($name, $options); 120 | } 121 | 122 | return $this->getHelperPluginManager()->get($name, $options); 123 | } 124 | 125 | /** 126 | * Return the template engine object, if any 127 | * 128 | * If using a third-party template engine, such as Smarty, patTemplate, 129 | * phplib, etc, return the template engine object. Useful for calling 130 | * methods on these objects, such as for setting filters, modifiers, etc. 131 | * 132 | * @return TwigRenderer 133 | */ 134 | public function getEngine() : TwigRenderer 135 | { 136 | return $this; 137 | } 138 | 139 | /** 140 | * Processes a view script and returns the output. 141 | * 142 | * @param string|ModelInterface $nameOrModel The script/resource process, or a view model 143 | * @param null|array|\ArrayAccess $values Values to use during rendering 144 | * 145 | * @return string The script output. 146 | */ 147 | public function render($nameOrModel, $values = null) : string 148 | { 149 | $model = $nameOrModel; 150 | if ($model instanceof ModelInterface) { 151 | $nameOrModel = $model->getTemplate(); 152 | 153 | if (empty($nameOrModel)) { 154 | throw new DomainException(sprintf( 155 | '%s: received View Model argument, but template is empty', 156 | __METHOD__ 157 | )); 158 | } 159 | 160 | $options = $model->getOptions(); 161 | $options = empty($options) ? [] : $options; 162 | foreach ($options as $setting => $value) { 163 | $method = 'set' . $setting; 164 | if (method_exists($this, $method)) { 165 | $this->$method($value); 166 | } 167 | unset($method, $setting, $value); 168 | } 169 | 170 | /** 171 | * Give view model awareness via ViewModel helper 172 | * @var \Laminas\View\Helper\ViewModel $helper 173 | */ 174 | $helper = $this->plugin(ViewModel::class); 175 | $helper->setCurrent($model); 176 | 177 | $values = $model->getVariables(); 178 | } 179 | 180 | if (!$this->canRender($nameOrModel)) { 181 | throw new RuntimeException(sprintf( 182 | '%s: Unable to render template "%s"; resolver could not resolve to a file', 183 | __METHOD__, 184 | $nameOrModel 185 | )); 186 | } 187 | 188 | if ($model instanceof ModelInterface && $model->hasChildren() && $this->canRenderTrees($model)) { 189 | if (!isset($values[$model->captureTo()])) { 190 | $values[$model->captureTo()] = ''; 191 | } 192 | 193 | foreach ($model->getChildren() as $child) { 194 | /** 195 | * @var \Laminas\View\Model\ViewModel $child 196 | * @var \Twig\Template $template 197 | */ 198 | try { 199 | $result = $this->render($child, $values); 200 | } catch (RuntimeException $e) { 201 | continue; 202 | } 203 | 204 | if ($this->isForceStandalone() || $child->terminate()) { 205 | return $result; 206 | } 207 | 208 | $child->setOption('has_parent', true); 209 | 210 | if ($child->isAppend()) { 211 | $values[$child->captureTo()] .= $result; 212 | } else { 213 | $values[$child->captureTo()] = $result; 214 | } 215 | } 216 | } 217 | 218 | /** @var \Twig\Template $template */ 219 | $template = $this->getResolver()->resolve($nameOrModel, $this); 220 | 221 | return $template->render((array)$values); 222 | } 223 | 224 | /** 225 | * Can the template be rendered? 226 | * 227 | * @param string $name 228 | * 229 | * @return bool 230 | */ 231 | public function canRender($name) 232 | { 233 | return $this->getLoader() 234 | ->exists($name); 235 | } 236 | 237 | /** 238 | * Indicate whether the renderer is capable of rendering trees of view models 239 | * 240 | * @param mixed $model 241 | * 242 | * @return bool 243 | */ 244 | public function canRenderTrees($model = null) 245 | { 246 | if (!empty($model) && $model instanceof TwigModel) { 247 | return true; 248 | } 249 | 250 | return false; 251 | } 252 | 253 | /** 254 | * @return View 255 | */ 256 | public function getView() 257 | { 258 | return $this->view; 259 | } 260 | 261 | /** 262 | * @param View $view 263 | * 264 | * @return TwigRenderer 265 | */ 266 | public function setView(View $view) : TwigRenderer 267 | { 268 | $this->view = $view; 269 | 270 | $view->getEventManager(); 271 | 272 | return $this; 273 | } 274 | 275 | /** 276 | * @return Environment 277 | */ 278 | public function getEnvironment() : Environment 279 | { 280 | if (!$this->environment instanceof Environment) { 281 | throw new InvalidArgumentException(sprintf( 282 | 'Twig environment must be \Twig\Environment; got type "%s" instead', 283 | (is_object($this->loader) ? get_class($this->loader) : gettype($this->loader)) 284 | )); 285 | } 286 | 287 | return $this->environment; 288 | } 289 | 290 | /** 291 | * @param Environment $environment 292 | * 293 | * @return TwigRenderer 294 | */ 295 | public function setEnvironment($environment) : TwigRenderer 296 | { 297 | $this->environment = $environment; 298 | 299 | return $this; 300 | } 301 | 302 | /** 303 | * @return ChainLoader 304 | */ 305 | public function getLoader() : ChainLoader 306 | { 307 | if (!$this->loader instanceof ChainLoader) { 308 | throw new InvalidArgumentException(sprintf( 309 | 'Twig loader must implement \Twig\Loader\ChainLoader; got type "%s" instead', 310 | (is_object($this->loader) ? get_class($this->loader) : gettype($this->loader)) 311 | )); 312 | } 313 | 314 | return $this->loader; 315 | } 316 | 317 | /** 318 | * @param \Twig\Loader\LoaderInterface $loader 319 | * 320 | * @return TwigRenderer 321 | */ 322 | public function setLoader($loader) : TwigRenderer 323 | { 324 | $this->loader = $loader; 325 | 326 | return $this; 327 | } 328 | 329 | /** 330 | * @return TwigResolver 331 | */ 332 | public function getResolver() : TwigResolver 333 | { 334 | return $this->resolver; 335 | } 336 | 337 | /** 338 | * @param ResolverInterface|TwigResolver $resolver 339 | * 340 | * @return TwigRenderer 341 | */ 342 | public function setResolver(?ResolverInterface $resolver = null) : TwigRenderer 343 | { 344 | $this->resolver = $resolver; 345 | 346 | return $this; 347 | } 348 | 349 | /** 350 | * @return TwigHelperPluginManager 351 | */ 352 | public function getTwigHelpers() : TwigHelperPluginManager 353 | { 354 | return $this->twigHelpers; 355 | } 356 | 357 | /** 358 | * @param TwigHelperPluginManager $twigHelpers 359 | * 360 | * @return TwigRenderer 361 | */ 362 | public function setTwigHelpers($twigHelpers) : TwigRenderer 363 | { 364 | $this->twigHelpers = $twigHelpers; 365 | 366 | return $this; 367 | } 368 | 369 | /** 370 | * @return \Laminas\View\HelperPluginManager 371 | */ 372 | public function getHelperPluginManager() : ZendHelperPluginManager 373 | { 374 | return $this->zendHelpers; 375 | } 376 | 377 | /** 378 | * Set helper plugin manager instance 379 | * 380 | * @param ZendHelperPluginManager $helpers 381 | * 382 | * @return TwigRenderer 383 | */ 384 | public function setZendHelpers(ZendHelperPluginManager $helpers) : TwigRenderer 385 | { 386 | $this->zendHelpers = $helpers; 387 | 388 | return $this; 389 | } 390 | 391 | /** 392 | * @param boolean $forceStandalone 393 | * @return TwigRenderer 394 | */ 395 | public function setForceStandalone($forceStandalone) : TwigRenderer 396 | { 397 | $this->forceStandalone = !!$forceStandalone; 398 | return $this; 399 | } 400 | 401 | /** 402 | * @return bool 403 | */ 404 | public function isForceStandalone() : bool 405 | { 406 | return $this->forceStandalone; 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /tests/ZendTwig/Renderer/TwigRendererTest.php: -------------------------------------------------------------------------------- 1 | getServiceManager(); 22 | $render = $sm->get(TwigRenderer::class); 23 | 24 | $engine = $render->getEngine(); 25 | 26 | $this->assertSame($render, $engine); 27 | } 28 | 29 | public function testGetEnv() 30 | { 31 | /** 32 | * @var \ZendTwig\Renderer\TwigRenderer $render 33 | */ 34 | $sm = Bootstrap::getInstance()->getServiceManager(); 35 | $render = $sm->get(TwigRenderer::class); 36 | $env = $sm->get(Environment::class); 37 | 38 | $this->assertSame($env, $render->getEnvironment()); 39 | } 40 | 41 | public function testGetEnvEx() 42 | { 43 | /** 44 | * @var \ZendTwig\Renderer\TwigRenderer $render 45 | */ 46 | $sm = Bootstrap::getInstance()->getServiceManager(); 47 | $render = $sm->get(TwigRenderer::class); 48 | $renderClone = clone $render; 49 | 50 | $this->expectException(\Laminas\View\Exception\InvalidArgumentException::class); 51 | $this->expectExceptionMessageMatches('/Twig environment must be/'); 52 | 53 | $renderClone->setEnvironment('qwe') 54 | ->getEnvironment(); 55 | } 56 | 57 | public function testGetLoader() 58 | { 59 | /** 60 | * @var \ZendTwig\Renderer\TwigRenderer $render 61 | */ 62 | $sm = Bootstrap::getInstance()->getServiceManager(); 63 | $render = $sm->get(TwigRenderer::class); 64 | $loader = $render->getLoader(); 65 | 66 | $this->assertInstanceOf('\Twig\Loader\ChainLoader', $loader); 67 | } 68 | 69 | public function testGetLoaderEx() 70 | { 71 | /** 72 | * @var \ZendTwig\Renderer\TwigRenderer $render 73 | */ 74 | $sm = Bootstrap::getInstance()->getServiceManager(); 75 | $render = $sm->get(TwigRenderer::class); 76 | $renderClone = clone $render; 77 | 78 | $this->expectException(\Laminas\View\Exception\InvalidArgumentException::class); 79 | $this->expectExceptionMessageMatches('/Twig loader must implement/'); 80 | 81 | $renderClone->setLoader('qwe') 82 | ->getLoader(); 83 | } 84 | 85 | public function testGetResolver() 86 | { 87 | /** 88 | * @var \ZendTwig\Renderer\TwigRenderer $render 89 | */ 90 | $sm = Bootstrap::getInstance()->getServiceManager(); 91 | $render = $sm->get(TwigRenderer::class); 92 | $resolver = $render->getResolver(); 93 | 94 | $this->assertInstanceOf('\ZendTwig\Resolver\TwigResolver', $resolver); 95 | } 96 | 97 | public function testGetView() 98 | { 99 | /** 100 | * @var \ZendTwig\Renderer\TwigRenderer $render 101 | */ 102 | $sm = Bootstrap::getInstance()->getServiceManager(); 103 | $render = $sm->get(TwigRenderer::class); 104 | $view = $render->getView(); 105 | 106 | $this->assertInstanceOf('\Laminas\View\View', $view); 107 | } 108 | 109 | public function testSetGetCanRenderTrees() 110 | { 111 | /** 112 | * @var \ZendTwig\Renderer\TwigRenderer $render 113 | */ 114 | $sm = Bootstrap::getInstance()->getServiceManager(); 115 | $render = $sm->get(TwigRenderer::class); 116 | 117 | $model = new ViewModel(); 118 | $twigModel = new TwigModel(); 119 | 120 | $this->assertFalse($render->canRenderTrees()); 121 | $this->assertFalse($render->canRenderTrees('Element?')); 122 | $this->assertTrue($render->canRenderTrees($twigModel)); 123 | } 124 | 125 | public function testRenderModelExNotTemplate() 126 | { 127 | /** 128 | * @var \ZendTwig\Renderer\TwigRenderer $render 129 | */ 130 | $sm = Bootstrap::getInstance()->getServiceManager(); 131 | $render = $sm->get(TwigRenderer::class); 132 | 133 | $model = new ViewModel([ 134 | 'key1' => 'value1', 135 | 'key2' => 'value2', 136 | ]); 137 | 138 | $this->expectException(\Laminas\View\Exception\DomainException::class); 139 | $this->expectExceptionMessageMatches('/but template is empty/'); 140 | 141 | $render->render($model); 142 | } 143 | 144 | public function testRenderModelOptions() 145 | { 146 | /** 147 | * @var \ZendTwig\Renderer\TwigRenderer $render 148 | */ 149 | $sm = Bootstrap::getInstance()->getServiceManager(); 150 | $render = $sm->get(TwigRenderer::class); 151 | $view = $sm->get('View'); 152 | $viewClone = clone $view; 153 | 154 | $model = new ViewModel([ 155 | 'key1' => 'value1', 156 | 'key2' => 'value2', 157 | ]); 158 | 159 | $model->setTemplate('View/testInjectResponse'); 160 | $model->setOptions([ 161 | 'View' => $viewClone, 162 | ]); 163 | 164 | $expect = "value1value2\n"; 165 | $result = $render->render($model); 166 | $renderView = $render->getView(); 167 | $this->assertSame($viewClone, $renderView); 168 | $render->setView($view); 169 | 170 | $this->assertEquals($expect, $result); 171 | } 172 | 173 | public function testRenderModelString() 174 | { 175 | /** 176 | * @var \ZendTwig\Renderer\TwigRenderer $render 177 | */ 178 | $sm = Bootstrap::getInstance()->getServiceManager(); 179 | $render = $sm->get(TwigRenderer::class); 180 | 181 | $expect = "value1value2\n"; 182 | $result = $render->render('View/testInjectResponse', [ 183 | 'key1' => 'value1', 184 | 'key2' => 'value2', 185 | ]); 186 | 187 | $this->assertEquals($expect, $result); 188 | } 189 | 190 | public function testRenderNotExistsEx() 191 | { 192 | /** 193 | * @var \ZendTwig\Renderer\TwigRenderer $render 194 | */ 195 | $sm = Bootstrap::getInstance()->getServiceManager(); 196 | $render = $sm->get(TwigRenderer::class); 197 | 198 | $this->expectException(\Laminas\View\Exception\RuntimeException::class); 199 | $this->expectExceptionMessageMatches('/Unable to render template/'); 200 | 201 | $render->render('View/testRenderNull', [ 202 | 'key1' => 'value1', 203 | 'key2' => 'value2', 204 | ]); 205 | } 206 | 207 | public function testRenderChild() 208 | { 209 | /** 210 | * @var \ZendTwig\Renderer\TwigRenderer $render 211 | */ 212 | $sm = Bootstrap::getInstance()->getServiceManager(); 213 | $render = $sm->get(TwigRenderer::class); 214 | 215 | $modelParent = new TwigModel([ 216 | 'key1' => 'value1', 217 | ]); 218 | $modelParent->setTemplate('View/testRenderChild'); 219 | 220 | $modelChild1 = new TwigModel([ 221 | 'key1' => 'child1-1', 222 | 'key2' => 'child1-2', 223 | ]); 224 | 225 | $modelChild2 = new TwigModel([ 226 | 'key1' => 'child2-1', 227 | 'key2' => 'child2-2', 228 | ]); 229 | 230 | $modelChild3 = new TwigModel([ 231 | 'key1' => 'child3-1', 232 | 'key2' => 'child3-2', 233 | ]); 234 | 235 | $modelChild1->setTemplate('View/testInjectResponse'); 236 | $modelChild2->setTemplate('View/testInjectResponse'); 237 | $modelChild3->setTemplate('View/testInjectResponse'); 238 | 239 | $modelParent->addChild($modelChild1, 'injectChild'); 240 | $modelParent->addChild($modelChild2); 241 | $modelParent->addChild($modelChild3, 'injectChild', true); 242 | 243 | // do not force standalone models 244 | $forceValue = $render->isForceStandalone(); 245 | $render->setForceStandalone(false); 246 | 247 | $expect = "value1child1-1child1-2\n" 248 | . "child3-1child3-2\n" 249 | . "child2-1child2-2\n\n"; 250 | $result = $render->render($modelParent); 251 | 252 | $render->setForceStandalone($forceValue); 253 | 254 | $this->assertEquals($expect, $result); 255 | } 256 | 257 | public function testRenderChildNoAppend() 258 | { 259 | /** 260 | * @var \ZendTwig\Renderer\TwigRenderer $render 261 | */ 262 | $sm = Bootstrap::getInstance()->getServiceManager(); 263 | $render = $sm->get(TwigRenderer::class); 264 | 265 | $modelParent = new TwigModel([ 266 | 'key1' => 'value1', 267 | ]); 268 | $modelParent->setTemplate('View/testRenderChild'); 269 | 270 | $modelChild1 = new TwigModel([ 271 | 'key1' => 'child1-1', 272 | 'key2' => 'child1-2', 273 | ]); 274 | 275 | $modelChild2 = new TwigModel([ 276 | 'key1' => 'child2-1', 277 | 'key2' => 'child2-2', 278 | ]); 279 | 280 | $modelChild3 = new TwigModel([ 281 | 'key1' => 'child3-1', 282 | 'key2' => 'child3-2', 283 | ]); 284 | 285 | $modelChild1->setTemplate('View/testInjectResponse'); 286 | $modelChild2->setTemplate('View/testInjectResponse'); 287 | $modelChild3->setTemplate('View/testInjectResponse'); 288 | 289 | $modelParent->addChild($modelChild1, 'injectChild'); 290 | $modelParent->addChild($modelChild2); 291 | $modelParent->addChild($modelChild3, 'injectChild'); 292 | 293 | // do not force standalone models 294 | $forceValue = $render->isForceStandalone(); 295 | $render->setForceStandalone(false); 296 | 297 | $expect = "value1child3-1child3-2\n" 298 | . "child2-1child2-2\n\n"; 299 | $result = $render->render($modelParent); 300 | 301 | $render->setForceStandalone($forceValue); 302 | 303 | $this->assertEquals($expect, $result); 304 | } 305 | 306 | public function testCallPlugin() 307 | { 308 | /** 309 | * @var \ZendTwig\Renderer\TwigRenderer $render 310 | */ 311 | $sm = Bootstrap::getInstance()->getServiceManager(); 312 | $render = $sm->get(TwigRenderer::class); 313 | 314 | $result = $render->doctype(); 315 | 316 | $this->assertInstanceOf(\Laminas\View\Helper\Doctype::class, $result); 317 | } 318 | 319 | public function testForceStandaloneModel() 320 | { 321 | /** 322 | * @var \ZendTwig\Renderer\TwigRenderer $render 323 | */ 324 | $sm = Bootstrap::getInstance()->getServiceManager(); 325 | $render = $sm->get(TwigRenderer::class); 326 | 327 | $modelParent = new TwigModel([ 328 | ]); 329 | $modelParent->setTemplate('View/zend/layout'); 330 | 331 | $modelChild1 = new TwigModel([ 332 | 'username' => 'Child007', 333 | ]); 334 | 335 | $modelChild1->setTemplate('View/zend/index'); 336 | 337 | $modelParent->addChild($modelChild1); 338 | 339 | $expect = "
Hello, Child007!
\n"; 340 | $result = $render->render($modelParent); 341 | 342 | $this->assertEquals($expect, $result); 343 | } 344 | 345 | public function testSimpleStandaloneModel() 346 | { 347 | /** 348 | * @var \ZendTwig\Renderer\TwigRenderer $render 349 | */ 350 | $sm = Bootstrap::getInstance()->getServiceManager(); 351 | $render = $sm->get(TwigRenderer::class); 352 | 353 | $modelParent = new TwigModel([ 354 | ]); 355 | $modelParent->setTemplate('View/zend/layout'); 356 | 357 | $modelChild1 = new TwigModel([ 358 | 'username' => 'Child007', 359 | ]); 360 | 361 | $modelChild1->setTemplate('View/zend/index'); 362 | $modelChild1->setTerminal(true); 363 | $modelParent->addChild($modelChild1); 364 | 365 | $forceStandalone = $render->isForceStandalone(); 366 | $render->setForceStandalone(false); 367 | 368 | $expect = "
Hello, Child007!
\n"; 369 | $result = $render->render($modelParent); 370 | 371 | $render->setForceStandalone($forceStandalone); 372 | 373 | $this->assertEquals($expect, $result); 374 | } 375 | 376 | public function testIssue2() 377 | { 378 | /** 379 | * @var \ZendTwig\Renderer\TwigRenderer $render 380 | */ 381 | $sm = Bootstrap::getInstance()->getServiceManager(); 382 | $render = $sm->get(TwigRenderer::class); 383 | 384 | $modelParent = new TwigModel(); 385 | $modelChild1 = new TwigModel(); 386 | 387 | $modelParent->setTemplate('View/issue-2/layout'); 388 | $modelChild1->setTemplate('View/issue-2/index'); 389 | $modelChild1->setTerminal(true); 390 | $modelParent->addChild($modelChild1); 391 | 392 | $forceStandalone = $render->isForceStandalone(); 393 | $render->setForceStandalone(false); 394 | 395 | $expect = "test header
test content
"; 396 | $result = $render->render($modelParent); 397 | 398 | $render->setForceStandalone($forceStandalone); 399 | 400 | $this->assertEquals($expect, $result); 401 | } 402 | 403 | public function testIssue2Raw() 404 | { 405 | /** 406 | * @var \ZendTwig\Renderer\TwigRenderer $render 407 | */ 408 | $sm = Bootstrap::getInstance()->getServiceManager(); 409 | $render = $sm->get(TwigRenderer::class); 410 | 411 | $modelParent = new TwigModel(); 412 | $modelChild1 = new TwigModel(); 413 | 414 | $modelParent->setTemplate('View/issue-2/layout-raw'); 415 | $modelChild1->setTemplate('View/issue-2/index'); 416 | $modelChild1->setTerminal(true); 417 | $modelParent->addChild($modelChild1); 418 | 419 | $forceStandalone = $render->isForceStandalone(); 420 | $render->setForceStandalone(false); 421 | 422 | $expect = "test header
test content
"; 423 | $result = $render->render($modelParent); 424 | 425 | $render->setForceStandalone($forceStandalone); 426 | 427 | $this->assertEquals($expect, $result); 428 | } 429 | } 430 | --------------------------------------------------------------------------------