├── 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:= $value; ?>
--------------------------------------------------------------------------------
/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 %}{% 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 | [](https://packagist.org/packages/oxcom/zend-twig)
3 | [](https://packagist.org/packages/oxcom/zend-twig)
4 | [](https://codecov.io/github/OxCom/zf3-twig?branch=master)
5 | [](https://travis-ci.org/OxCom/zf3-twig)
6 | [](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
\ncontent
\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 = "\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 = "\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";
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";
423 | $result = $render->render($modelParent);
424 |
425 | $render->setForceStandalone($forceStandalone);
426 |
427 | $this->assertEquals($expect, $result);
428 | }
429 | }
430 |
--------------------------------------------------------------------------------