├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── UPGRADE.md
├── composer-require-checker.json
├── composer.json
├── config
├── di-web.php
├── events-web.php
└── params.php
├── rector.php
└── src
├── CommonParametersInjectionInterface.php
├── Csrf.php
├── CsrfViewInjection.php
├── Debug
└── WebViewCollector.php
├── Exception
├── InvalidLinkTagException.php
└── InvalidMetaTagException.php
├── InjectionContainer
├── InjectionContainer.php
├── InjectionContainerInterface.php
└── StubInjectionContainer.php
├── LayoutParametersInjectionInterface.php
├── LayoutSpecificInjections.php
├── LinkTagsInjectionInterface.php
├── MetaTagsInjectionInterface.php
└── ViewRenderer.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Yii View Renderer Change Log
2 |
3 | ## 7.3.1 under development
4 |
5 | - Chg #139: Change PHP constraint in `composer.json` to `8.1 - 8.4` (@vjik)
6 |
7 | ## 7.3.0 December 25, 2024
8 |
9 | - Chg #138: Raise required `yiisoft/view` version to `^12` (@vjik)
10 | - Enh #135: Add `CsrfTokenMiddleware` support in `CsrfViewInjection` (@vjik)
11 | - Bug #137: Fix state leak in some combinations of `ViewRenderer` render methods (@vjik)
12 |
13 | ## 7.2.0 October 02, 2024
14 |
15 | - Enh #129: Add support for `yiisoft/view` version `^11` (@vjik)
16 | - Enh #130: Bump PHP version to `^8.1` and refactor code (@vjik)
17 |
18 | ## 7.1.0 July 01, 2024
19 |
20 | - Chg #122: Raise required `yiisoft/view` version to `^10.0` (@vjik)
21 |
22 | ## 7.0.0 June 22, 2024
23 |
24 | - Chg #115: Only a full path can now be used as a layout (@vjik)
25 | - Chg #116, #117, #118: Rename package to `yiisoft/yii-view-renderer` (@vjik)
26 | - Chg #119: Change package configuration parameters `viewPath` and `layout` to null (@vjik)
27 | - Chg #64: Refactor controller name extractor, use greedy search of namespace items with "controller(s)" postfix (@vjik)
28 |
29 | ## 6.1.1 June 06, 2024
30 |
31 | - Bug #112: Fix events configuration for Yii Debug (@vjik)
32 |
33 | ## 6.1.0 May 28, 2024
34 |
35 | - New #102: Add layout specific injections (@vjik)
36 | - Enh #107: Implement lazy loading for injections (@vjik)
37 | - Enh #79: Add debug collector for yiisoft/yii-debug (@xepozz)
38 | - Enh #99: Make `viewPath` in `ViewRenderer` constructor optional (@vjik)
39 | - Bug #82: Fix find for layout file due to compatibility with `yiisoft/view` (@rustamwin)
40 |
41 | ## 6.0.0 February 16, 2023
42 |
43 | - Chg #72: Adapt configuration group names to Yii conventions (@vjik)
44 | - Enh #73: Add support for `yiisoft/aliases` version `^3.0` (@vjik)
45 | - Enh #74: Add support for `yiisoft/csrf` version `^2.0` (@vjik)
46 | - Enh #76: Add support for `yiisoft/data-response` version `^2.0` (@vjik)
47 | - Enh #77: Add support for `yiisoft/view` version `^8.0` (@vjik)
48 |
49 | ## 5.0.1 December 07, 2022
50 |
51 | - Enh #70: Add support `yiisoft/view` of version `^7.0` and `yiisoft/html` of version `^3.0` (@vjik)
52 | - Bug #61: Fixed getting incorrect controller name based on controller instance (@vjik, @kamarton)
53 |
54 | ## 5.0.0 July 23, 2022
55 |
56 | - New #51: Add immutable method `ViewRenderer::withLocale()` that set locale (@thenotsoft)
57 |
58 | ## 4.0.3 February 04, 2022
59 |
60 | - Chg #50: Update the `yiisoft/view` dependency, added `^5.0` (@thenotsoft)
61 |
62 | ## 4.0.2 November 22, 2021
63 |
64 | - Chg #48: Update the `yiisoft/csrf` dependency to `^1.2` (@devanych)
65 |
66 | ## 4.0.1 October 25, 2021
67 |
68 | - Chg #47: Update the `yiisoft/view` dependency to `^4.0` (@vjik)
69 |
70 | ## 4.0.0 October 21, 2021
71 |
72 | - Chg #45: `CsrfInjection` now injects a stringable CSRF object with methods `getToken()`,
73 | `getParameterName()`, `getHeaderName()` and `hiddenInput()` instead of string token to common parameters (@vjik)
74 |
75 | ## 3.0.0 September 18, 2021
76 |
77 | - Chg: Replace interface `ContentParametersInjectionInterface` to `CommonParametersInjectionInterface` that inject
78 | parameters both to content and to layout (@vjik)
79 | - Bug #42: Fixed not passing common parameters setted in process content rendering to layout (@vjik)
80 |
81 | ## 2.0.1 September 14, 2021
82 |
83 | - Bug #40: Fixed not passing content and layout parameters injections to nested view rendering (@vjik)
84 |
85 | ## 2.0.0 August 24, 2021
86 |
87 | - Chg: Use yiisoft/html ^2.0 and yiisoft/view ^2.0 (@samdark)
88 |
89 | ## 1.0.0 July 05, 2021
90 |
91 | - Initial release.
92 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright © 2008 by Yii Software ()
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the
13 | distribution.
14 | * Neither the name of Yii Software nor the names of its
15 | contributors may be used to endorse or promote products derived
16 | from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Yii View Renderer
6 |
7 |
8 |
9 | [](https://packagist.org/packages/yiisoft/yii-view-renderer)
10 | [](https://packagist.org/packages/yiisoft/yii-view-renderer)
11 | [](https://github.com/yiisoft/yii-view-renderer/actions/workflows/build.yml)
12 | [](https://codecov.io/gh/yiisoft/yii-view-renderer)
13 | [](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/yii-view-renderer/master)
14 | [](https://github.com/yiisoft/yii-view-renderer/actions?query=workflow%3A%22static+analysis%22)
15 | [](https://shepherd.dev/github/yiisoft/yii-view-renderer)
16 |
17 | The package is an extension of the [Yii View](https://github.com/yiisoft/view/) rendering library. It adds
18 | WEB-specific functionality and compatibility with [PSR-7](https://www.php-fig.org/psr/psr-7/) interfaces.
19 |
20 | ## Requirements
21 |
22 | - PHP 8.1 or higher.
23 |
24 | ## Installation
25 |
26 | The package could be installed with [Composer](https://getcomposer.org):
27 |
28 | ```shell
29 | composer require yiisoft/yii-view-renderer
30 | ```
31 |
32 | ## General usage
33 |
34 | There are two ways to render a view:
35 |
36 | - Return an instance of the `Yiisoft\DataResponse\DataResponse` class with deferred rendering.
37 | - Render immediately and return the rendered result as a string.
38 |
39 | ### Rendering result as a PSR-7 response
40 |
41 | The `Yiisoft\DataResponse\DataResponse` class is an implementation of the `Psr\Http\Message\ResponseInterface`. For
42 | more information about this class, see the [yiisoft/data-response](https://github.com/yiisoft/data-response) package.
43 | You can get an instance of a response with deferred rendering as follows:
44 |
45 | ```php
46 | /**
47 | * @var \Yiisoft\Aliases\Aliases $aliases
48 | * @var \Yiisoft\DataResponse\DataResponseFactoryInterface $dataResponseFactory
49 | * @var \Yiisoft\View\WebView $webView
50 | */
51 |
52 | $viewRenderer = new \Yiisoft\Yii\View\Renderer\ViewRenderer(
53 | $dataResponseFactory,
54 | $aliases,
55 | $webView,
56 | '/path/to/views', // Full path to the directory of view templates or its alias.
57 | 'layouts/main.php', // Default is null, which means not to use a layout.
58 | );
59 |
60 | // Rendering a view with a layout.
61 | $response = $viewRenderer->render('site/page', [
62 | 'parameter-name' => 'parameter-value',
63 | ]);
64 | ```
65 |
66 | The rendering will be performed directly when calling `getBody()` or `getData()` methods of the
67 | `Yiisoft\DataResponse\DataResponse`. If a layout is set, but you need to render a view
68 | without the layout, you can use an immutable setter `withLayout()`:
69 |
70 | ```php
71 | $viewRenderer = $viewRenderer->withLayout(null);
72 |
73 | // Rendering a view without a layout.
74 | $response = $viewRenderer->render('site/page', [
75 | 'parameter-name' => 'parameter-value',
76 | ]);
77 | ```
78 |
79 | Or use `renderPartial()` method, which will call `withLayout(null)`:
80 |
81 | ```php
82 | // Rendering a view without a layout.
83 | $response = $viewRenderer->renderPartial('site/page', [
84 | 'parameter-name' => 'parameter-value',
85 | ]);
86 | ```
87 |
88 | ### Rendering result as a string
89 |
90 | To render immediately and return the rendering result as a string,
91 | use `renderAsString()` and `renderPartialAsString()` methods:
92 |
93 | ```php
94 | // Rendering a view with a layout.
95 | $result = $viewRenderer->renderAsString('site/page', [
96 | 'parameter-name' => 'parameter-value',
97 | ]);
98 |
99 | // Rendering a view without a layout.
100 | $result = $viewRenderer->renderPartialAsString('site/page', [
101 | 'parameter-name' => 'parameter-value',
102 | ]);
103 | ```
104 |
105 | ### Change view templates path
106 |
107 | You can change view templates path in runtime as follows:
108 |
109 | ```php
110 | $viewRenderer = $viewRenderer->withViewPath('/new/path/to/views');
111 | ```
112 |
113 | You can specify full path to the views directory or its alias. For more information about path aliases,
114 | see description of the [yiisoft/aliases](https://github.com/yiisoft/aliases) package.
115 |
116 | ### Use in the controller
117 |
118 | If the view renderer is used in a controller, you can either specify controller name explicitly using
119 | `withControllerName()` or determine name automatically by passing a controller instance to `withController()`.
120 | In this case the name is determined as follows:
121 |
122 | ```text
123 | App\Controller\FooBar\BazController -> foo-bar/baz
124 | App\Controllers\FooBar\BazController -> foo-bar/baz
125 | App\AllControllers\MyController\FooBar\BazController -> foo-bar/baz
126 | App\AllControllers\MyController\BazController -> baz
127 | Path\To\File\BlogController -> blog
128 | ```
129 |
130 | With this approach, you do not need to specify the directory name each time when rendering a view template:
131 |
132 | ```php
133 | use Psr\Http\Message\ResponseInterface;
134 | use Yiisoft\Yii\View\Renderer\ViewRenderer;
135 |
136 | class SiteController
137 | {
138 | private ViewRenderer $viewRenderer;
139 |
140 | public function __construct(ViewRenderer $viewRenderer)
141 | {
142 | // Specify the name of the controller:
143 | $this->viewRenderer = $viewRenderer->withControllerName('site');
144 | // or specify an instance of the controller:
145 | //$this->viewRenderer = $viewRenderer->withController($this);
146 | }
147 |
148 | public function index(): ResponseInterface
149 | {
150 | return $this->viewRenderer->render('index');
151 | }
152 |
153 | public function contact(): ResponseInterface
154 | {
155 | // Some actions.
156 | return $this->viewRenderer->render('contact', [
157 | 'parameter-name' => 'parameter-value',
158 | ]);
159 | }
160 | }
161 | ```
162 |
163 | This is very convenient if there are many methods (actions) in the controller.
164 |
165 | ### Injection of additional data to the views
166 |
167 | In addition to parameters passed directly when rendering the view template, you can set extra parameters that will be
168 | available in all views. In order to do it you need a class implementing at least one of the injection interfaces:
169 |
170 | ```php
171 | use Yiisoft\Yii\View\Renderer\CommonParametersInjectionInterface;
172 | use Yiisoft\Yii\View\Renderer\LayoutParametersInjectionInterface;
173 |
174 | final class MyParametersInjection implements
175 | CommonParametersInjectionInterface,
176 | LayoutParametersInjectionInterface
177 | {
178 | // Pass both to view template and to layout
179 | public function getCommonParameters(): array
180 | {
181 | return [
182 | 'common-parameter-name' => 'common-parameter-value',
183 | ];
184 | }
185 |
186 | // Pass only to layout
187 | public function getLayoutParameters(): array
188 | {
189 | return [
190 | 'layout-parameter-name' => 'layout-parameter-value',
191 | ];
192 | }
193 | }
194 | ```
195 |
196 | Link tags and meta tags should be organized in the same way.
197 |
198 | ```php
199 | use Yiisoft\Html\Html;
200 | use Yiisoft\View\WebView;
201 | use Yiisoft\Yii\View\Renderer\LinkTagsInjectionInterface;
202 | use Yiisoft\Yii\View\Renderer\MetaTagsInjectionInterface;
203 |
204 | final class MyTagsInjection implements
205 | LinkTagsInjectionInterface,
206 | MetaTagsInjectionInterface
207 | {
208 | public function getLinkTags(): array
209 | {
210 | return [
211 | Html::link()->toCssFile('/main.css'),
212 | 'favicon' => Html::link('/myicon.png', [
213 | 'rel' => 'icon',
214 | 'type' => 'image/png',
215 | ]),
216 | 'themeCss' => [
217 | '__position' => WebView::POSITION_END,
218 | Html::link()->toCssFile('/theme.css'),
219 | ],
220 | 'userCss' => [
221 | '__position' => WebView::POSITION_BEGIN,
222 | 'rel' => 'stylesheet',
223 | 'href' => '/user.css',
224 | ],
225 | ];
226 | }
227 |
228 | public function getMetaTags(): array
229 | {
230 | return [
231 | Html::meta()
232 | ->name('http-equiv')
233 | ->content('public'),
234 | 'noindex' => Html::meta()
235 | ->name('robots')
236 | ->content('noindex'),
237 | [
238 | 'name' => 'description',
239 | 'content' => 'This website is about funny raccoons.',
240 | ],
241 | 'keywords' => [
242 | 'name' => 'keywords',
243 | 'content' => 'yii,framework',
244 | ],
245 | ];
246 | }
247 | }
248 | ```
249 |
250 | You can pass instances of these classes as the sixth optional parameter to the constructor when
251 | creating a view renderer, or use the `withInjections()` and `withAddedInjections` methods.
252 |
253 | ```php
254 | $parameters = new MyParametersInjection();
255 | $tags = new MyTagsInjection();
256 |
257 | $viewRenderer = $viewRenderer->withInjections($parameters, $tags);
258 | // Or append it:
259 | $viewRenderer = $viewRenderer->withAddedInjections($parameters, $tags);
260 | ```
261 |
262 | The parameters passed to `render()` method have more priority
263 | and will overwrite the injected content parameters if their names match.
264 |
265 | #### Injections lazy loading
266 |
267 | You can use lazy loading for injections. Injections will be created by container that implements
268 | `Yiisoft\Yii\View\Renderer\InjectionContainerInterface`. Out of the box, it is available in `InjectionContainer` that is based on PSR-11 compatible
269 | container.
270 |
271 | 1. Add injection container to `ViewRenderer` constructor:
272 |
273 | ```php
274 | use Yiisoft\Yii\View\Renderer\ViewRenderer;
275 | use Yiisoft\Yii\View\Renderer\InjectionContainer\InjectionContainer;
276 |
277 | /**
278 | * @var Psr\Container\ContainerInterface $container
279 | */
280 |
281 | $viewRenderer = new ViewRenderer(
282 | injectionContainer: new InjectionContainer($container)
283 | )
284 | ```
285 |
286 | 2. Use injection class names instead of instances.
287 |
288 | ```php
289 | $viewRenderer->withInjections(MyParametersInjection::class, MyTagsInjection::class);
290 | ```
291 |
292 | ### Localize view file
293 |
294 | You can set a specific locale that will be used to localize view files with `withLocale()` method:
295 |
296 | ```php
297 | $viewRenderer = $viewRenderer->withLocale('de_DE');
298 | ```
299 |
300 | For more information about localization, see at the [localization](https://github.com/yiisoft/view/blob/master/docs/guide/en/basic-functionality.md#localization) section in [yiisoft/view](https://github.com/yiisoft/view) package.
301 |
302 | ### [Yii Config](https://github.com/yiisoft/config) parameters
303 |
304 | ```php
305 | 'yiisoft/yii-view-renderer' => [
306 | // The full path to the directory of views or its alias.
307 | // If null, relative view paths in `ViewRenderer::render()` is not available.
308 | 'viewPath' => null,
309 |
310 | // The full path to the layout file to be applied to views.
311 | // If null, the layout will not be applied.
312 | 'layout' => null,
313 |
314 | // The injection instances or class names.
315 | 'injections' => [],
316 | ],
317 | ```
318 |
319 | ## Documentation
320 |
321 | - [Internals](docs/internals.md)
322 |
323 | If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for
324 | that. You may also check out other [Yii Community Resources](https://www.yiiframework.com/community).
325 |
326 | ## License
327 |
328 | The Yii View Renderer is free software. It is released under the terms of the BSD License.
329 | Please see [`LICENSE`](./LICENSE.md) for more information.
330 |
331 | Maintained by [Yii Software](https://www.yiiframework.com/).
332 |
333 | ## Support the project
334 |
335 | [](https://opencollective.com/yiisoft)
336 |
337 | ## Follow updates
338 |
339 | [](https://www.yiiframework.com/)
340 | [](https://twitter.com/yiiframework)
341 | [](https://t.me/yii3en)
342 | [](https://www.facebook.com/groups/yiitalk)
343 | [](https://yiiframework.com/go/slack)
344 |
--------------------------------------------------------------------------------
/UPGRADE.md:
--------------------------------------------------------------------------------
1 | # Upgrading Instructions for Yii View Renderer
2 |
3 | This file contains the upgrade notes. These notes highlight changes that could break your
4 | application when you upgrade the package from one version to another.
5 |
6 | > **Important!** The following upgrading instructions are cumulative. That is, if you want
7 | > to upgrade from version A to version C and there is version B between A and C, you need
8 | > to following the instructions for both A and B.
9 |
10 | ## Upgrade from 6.x
11 |
12 | - Change layout value that passed to `ViewRenderer` constructor and `withLayout()` method to full path.
13 | - Change namespace `Yiisoft\Yii\View\*` to `Yiisoft\Yii\View\Renderer\*`.
14 | - Rename package configuration parameters key from "yiisoft/yii-view" to "yiisoft/yii-view-renderer".
15 |
16 | - Now configuration parameters `viewPath` and `layout` is null by default. If your application requires other values add
17 | them to the configuration parameters on application level. For example:
18 |
19 | ```php
20 | 'yiisoft/yii-view-renderer' => [
21 | 'viewPath' => '@views',
22 | 'layout' => '@layout/main.php',
23 | ],
24 | ```
25 |
26 | - Controller name extractor now uses greedy search of namespace items with "controller(s)" postfix. For example, for controller namespace `App\AllControllers\MyController\FooBar\BazController` previously,
27 | result was "controller/foo-bar/baz", now it is "foo-bar/baz". You can use `ViewRenderer::withControllerName()`
28 | instead of `ViewRenderer::withController()` to explicitly define controller name.
29 |
--------------------------------------------------------------------------------
/composer-require-checker.json:
--------------------------------------------------------------------------------
1 | {
2 | "symbol-whitelist" : [
3 | "Yiisoft\\Yii\\Debug\\Collector\\CollectorTrait",
4 | "Yiisoft\\Yii\\Debug\\Collector\\CollectorInterface"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yiisoft/yii-view-renderer",
3 | "type": "library",
4 | "description": "PSR-7 compatible view renderer",
5 | "keywords": [
6 | "yii",
7 | "view",
8 | "renderer"
9 | ],
10 | "homepage": "https://www.yiiframework.com/",
11 | "license": "BSD-3-Clause",
12 | "support": {
13 | "issues": "https://github.com/yiisoft/yii-view-renderer/issues?state=open",
14 | "source": "https://github.com/yiisoft/yii-view-renderer",
15 | "forum": "https://www.yiiframework.com/forum/",
16 | "wiki": "https://www.yiiframework.com/wiki/",
17 | "irc": "ircs://irc.libera.chat:6697/yii",
18 | "chat": "https://t.me/yii3en"
19 | },
20 | "funding": [
21 | {
22 | "type": "opencollective",
23 | "url": "https://opencollective.com/yiisoft"
24 | },
25 | {
26 | "type": "github",
27 | "url": "https://github.com/sponsors/yiisoft"
28 | }
29 | ],
30 | "require": {
31 | "php": "8.1 - 8.4",
32 | "psr/container": "^1.0 || ^2.0",
33 | "yiisoft/aliases": "^2.0 || ^3.0",
34 | "yiisoft/csrf": "^1.2 || ^2.0",
35 | "yiisoft/data-response": "^1.0 || ^2.0",
36 | "yiisoft/friendly-exception": "^1.0",
37 | "yiisoft/html": "^2.5 || ^3.0",
38 | "yiisoft/strings": "^2.0",
39 | "yiisoft/view": "^12"
40 | },
41 | "require-dev": {
42 | "httpsoft/http-message": "^1.1.6",
43 | "maglnet/composer-require-checker": "^4.7.1",
44 | "nyholm/psr7": "^1.8.2",
45 | "phpunit/phpunit": "^10.5.45",
46 | "rector/rector": "^2.0.10",
47 | "roave/infection-static-analysis-plugin": "^1.35",
48 | "spatie/phpunit-watcher": "^1.24",
49 | "vimeo/psalm": "^5.26.1 || ^6.9.6",
50 | "yiisoft/di": "^1.3",
51 | "yiisoft/psr-dummy-provider": "^1.0.2",
52 | "yiisoft/test-support": "^3.0.2",
53 | "yiisoft/yii-debug": "dev-master"
54 | },
55 | "autoload": {
56 | "psr-4": {
57 | "Yiisoft\\Yii\\View\\Renderer\\": "src"
58 | }
59 | },
60 | "autoload-dev": {
61 | "psr-4": {
62 | "Yiisoft\\Yii\\View\\Renderer\\Tests\\": "tests"
63 | }
64 | },
65 | "extra": {
66 | "config-plugin-options": {
67 | "source-directory": "config"
68 | },
69 | "config-plugin": {
70 | "params": "params.php",
71 | "di-web": "di-web.php",
72 | "events-web": "events-web.php"
73 | }
74 | },
75 | "config": {
76 | "sort-packages": true,
77 | "allow-plugins": {
78 | "infection/extension-installer": true,
79 | "composer/package-versions-deprecated": true,
80 | "yiisoft/config": false
81 | }
82 | },
83 | "scripts": {
84 | "test": "phpunit --testdox --no-interaction",
85 | "test-watch": "phpunit-watcher watch"
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/config/di-web.php:
--------------------------------------------------------------------------------
1 | InjectionContainer::class,
13 | ViewRenderer::class => [
14 | '__construct()' => [
15 | 'viewPath' => $params['yiisoft/yii-view-renderer']['viewPath'],
16 | 'layout' => $params['yiisoft/yii-view-renderer']['layout'],
17 | 'injections' => $params['yiisoft/yii-view-renderer']['injections'],
18 | ],
19 | ],
20 | ];
21 |
--------------------------------------------------------------------------------
/config/events-web.php:
--------------------------------------------------------------------------------
1 | [
14 | [WebViewCollector::class, 'collect'],
15 | ],
16 | ];
17 |
--------------------------------------------------------------------------------
/config/params.php:
--------------------------------------------------------------------------------
1 | [
9 | // The full path to the directory of views or its alias.
10 | // If null, relative view paths in `ViewRenderer::render()` is not available.
11 | 'viewPath' => null,
12 |
13 | // The full path to the layout file to be applied to views.
14 | // If null, the layout will not be applied.
15 | 'layout' => null,
16 |
17 | // The injection instances or class names.
18 | 'injections' => [],
19 | ],
20 | 'yiisoft/yii-debug' => [
21 | 'collectors.web' => [
22 | WebViewCollector::class,
23 | ],
24 | ],
25 | ];
26 |
--------------------------------------------------------------------------------
/rector.php:
--------------------------------------------------------------------------------
1 | withPaths([
12 | __DIR__ . '/src',
13 | __DIR__ . '/tests',
14 | ])
15 | ->withPhpSets(php81: true)
16 | ->withRules([
17 | InlineConstructorDefaultToPropertyRector::class,
18 | ])
19 | ->withSkip([
20 | ClosureToArrowFunctionRector::class,
21 | NewInInitializerRector::class,
22 | ]);
23 |
--------------------------------------------------------------------------------
/src/CommonParametersInjectionInterface.php:
--------------------------------------------------------------------------------
1 | 'something',
21 | * 'paramB' => 42,
22 | * ...
23 | * ]
24 | * ```
25 | *
26 | * @psalm-return array
27 | */
28 | public function getCommonParameters(): array;
29 | }
30 |
--------------------------------------------------------------------------------
/src/Csrf.php:
--------------------------------------------------------------------------------
1 | token;
23 | }
24 |
25 | public function getParameterName(): string
26 | {
27 | return $this->parameterName;
28 | }
29 |
30 | public function getHeaderName(): string
31 | {
32 | return $this->headerName;
33 | }
34 |
35 | public function hiddenInput(array $attributes = []): Input
36 | {
37 | $tag = Html::hiddenInput($this->parameterName, $this->token);
38 | return $attributes === [] ? $tag : $tag->addAttributes($attributes);
39 | }
40 |
41 | public function __toString(): string
42 | {
43 | return $this->getToken();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/CsrfViewInjection.php:
--------------------------------------------------------------------------------
1 | parameterName = $parameterName;
41 | return $new;
42 | }
43 |
44 | /**
45 | * Returns a new instance with the specified meta attribute name.
46 | *
47 | * @param string $metaAttributeName The meta attribute name.
48 | */
49 | public function withMetaAttributeName(string $metaAttributeName): self
50 | {
51 | $new = clone $this;
52 | $new->metaAttributeName = $metaAttributeName;
53 | return $new;
54 | }
55 |
56 | /**
57 | * @throws LogicException when CSRF token is not defined
58 | */
59 | public function getCommonParameters(): array
60 | {
61 | $csrf = new Csrf(
62 | $this->token->getValue(),
63 | $this->middleware->getParameterName(),
64 | $this->middleware->getHeaderName(),
65 | );
66 | return [$this->parameterName => $csrf];
67 | }
68 |
69 | /**
70 | * @throws LogicException when CSRF token is not defined
71 | */
72 | public function getMetaTags(): array
73 | {
74 | return [
75 | self::META_TAG_KEY => [
76 | 'name' => $this->metaAttributeName,
77 | 'content' => $this->token->getValue(),
78 | ],
79 | ];
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Debug/WebViewCollector.php:
--------------------------------------------------------------------------------
1 | renders;
20 | }
21 |
22 | public function collect(AfterRender $event): void
23 | {
24 | if (!$this->isActive()) {
25 | return;
26 | }
27 |
28 | $this->renders[] = [
29 | 'output' => $event->getResult(),
30 | 'file' => $event->getFile(),
31 | 'parameters' => $event->getParameters(),
32 | ];
33 | }
34 |
35 | private function reset(): void
36 | {
37 | $this->renders = [];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Exception/InvalidLinkTagException.php:
--------------------------------------------------------------------------------
1 | tag, true) . << 'stylesheet', 'href' => '/user.css']`,
37 | - as instance of `Yiisoft\Html\Tag\Link`: `Html::link()->toCssFile('/main.css')`.
38 |
39 | Optionally:
40 | - use array format and set the position in a page via `__position`.
41 | - use string keys of array as identifies the link tag.
42 |
43 | Example:
44 |
45 | ```php
46 | public function getLinkTags(): array
47 | {
48 | return [
49 | 'favicon' => Html::link('/myicon.png', [
50 | 'rel' => 'icon',
51 | 'type' => 'image/png',
52 | ]),
53 | 'themeCss' => [
54 | '__position' => \Yiisoft\View\WebView::POSITION_END,
55 | Html::link()->toCssFile('/theme.css'),
56 | ],
57 | 'userCss' => [
58 | '__position' => \Yiisoft\View\WebView::POSITION_BEGIN,
59 | 'rel' => 'stylesheet',
60 | 'href' => '/user.css',
61 | ],
62 | ];
63 | }
64 | ```
65 | SOLUTION;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Exception/InvalidMetaTagException.php:
--------------------------------------------------------------------------------
1 | tag, true) . << 'keywords', 'content' => 'yii,framework']`,
37 | - as instance of `Yiisoft\Html\Tag\Meta`:
38 |
39 | ```php
40 | Html::meta()
41 | ->name('keywords')
42 | ->content('yii,framework');
43 | ```
44 | Optionally, you may use string keys of array as identifies the meta tag.
45 |
46 | Example:
47 |
48 | ```php
49 | public function getMetaTags(): array
50 | {
51 | return [
52 | 'seo-keywords' => [
53 | 'name' => 'keywords',
54 | 'content' => 'yii,framework',
55 | ],
56 | Html::meta()
57 | ->name('description')
58 | ->content('Yii is a fast, secure, and efficient PHP framework.'),
59 | ];
60 | }
61 | ```
62 | SOLUTION;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/InjectionContainer/InjectionContainer.php:
--------------------------------------------------------------------------------
1 | container->get($id);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/InjectionContainer/InjectionContainerInterface.php:
--------------------------------------------------------------------------------
1 | 'something',
20 | * 'paramB' => 42,
21 | * ...
22 | * ]
23 | * ```
24 | *
25 | * @psalm-return array
26 | */
27 | public function getLayoutParameters(): array;
28 | }
29 |
--------------------------------------------------------------------------------
/src/LayoutSpecificInjections.php:
--------------------------------------------------------------------------------
1 | injections = $injections;
19 | }
20 |
21 | /**
22 | * @return object[]
23 | */
24 | public function getInjections(): array
25 | {
26 | return $this->injections;
27 | }
28 |
29 | public function getLayout(): string
30 | {
31 | return $this->layout;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/LinkTagsInjectionInterface.php:
--------------------------------------------------------------------------------
1 |
13 | * @psalm-type LinkTagsConfig = array
17 | */
18 | interface LinkTagsInjectionInterface
19 | {
20 | /**
21 | * Returns array of link tags for register via {@see \Yiisoft\View\WebView::registerLinkTag()}.
22 | * Optionally:
23 | * - use array format and set the position in a page via `__position`.
24 | * - use string keys of array as identifies the link tag.
25 | *
26 | * For example:
27 | *
28 | * ```php
29 | * [
30 | * Html::link()->toCssFile('/main.css'),
31 | * 'favicon' => Html::link('/myicon.png', [
32 | * 'rel' => 'icon',
33 | * 'type' => 'image/png',
34 | * ]),
35 | * 'themeCss' => [
36 | * '__position' => \Yiisoft\View\WebView::POSITION_END,
37 | * Html::link()->toCssFile('/theme.css'),
38 | * ],
39 | * 'userCss' => [
40 | * '__position' => \Yiisoft\View\WebView::POSITION_BEGIN,
41 | * 'rel' => 'stylesheet',
42 | * 'href' => '/user.css',
43 | * ],
44 | * ...
45 | * ]
46 | * ```
47 | *
48 | * @psalm-return LinkTagsConfig
49 | */
50 | public function getLinkTags(): array;
51 | }
52 |
--------------------------------------------------------------------------------
/src/MetaTagsInjectionInterface.php:
--------------------------------------------------------------------------------
1 | >
11 | */
12 | interface MetaTagsInjectionInterface
13 | {
14 | /**
15 | * Returns array of meta tags for register via {@see \Yiisoft\View\WebView::registerMetaTag()}.
16 | * Optionally, you may use string keys of array as identifies the meta tag.
17 | *
18 | * For example:
19 | *
20 | * ```php
21 | * [
22 | * Html::meta()
23 | * ->name('http-equiv')
24 | * ->content('public'),
25 | * 'noindex' => Html::meta()
26 | * ->name('robots')
27 | * ->content('noindex'),
28 | * [
29 | * 'name' => 'description',
30 | * 'content' => 'This website is about funny raccoons.',
31 | * ],
32 | * 'keywords' => [
33 | * 'name' => 'keywords',
34 | * 'content' => 'yii,framework',
35 | * ],
36 | * ...
37 | * ]
38 | * ```
39 | *
40 | * @return array
41 | *
42 | * @psalm-return MetaTagsConfig
43 | */
44 | public function getMetaTags(): array;
45 | }
46 |
--------------------------------------------------------------------------------
/src/ViewRenderer.php:
--------------------------------------------------------------------------------
1 | $injections
69 | */
70 | public function __construct(
71 | private DataResponseFactoryInterface $responseFactory,
72 | private Aliases $aliases,
73 | private WebView $view,
74 | ?string $viewPath = null,
75 | private ?string $layout = null,
76 | private array $injections = [],
77 | ?InjectionContainerInterface $injectionContainer = null,
78 | ) {
79 | $this->injectionContainer = $injectionContainer ?? new StubInjectionContainer();
80 | $this->setViewPath($viewPath);
81 | }
82 |
83 | /**
84 | * Returns a path to a base directory of view templates that is prefixed to the relative view name.
85 | *
86 | * If a controller name has been set {@see withController(), withControllerName()}, it will be appended to the path.
87 | *
88 | * @return string View templates base directory.
89 | */
90 | public function getViewPath(): string
91 | {
92 | if ($this->viewPath === null) {
93 | throw new LogicException('The view path is not set.');
94 | }
95 |
96 | return $this->aliases->get($this->viewPath) . ($this->name ? '/' . $this->name : '');
97 | }
98 |
99 | /**
100 | * Returns a response instance {@see DataResponse} that supports deferred rendering.
101 | *
102 | * Rendering will occur when calling {@see DataResponse::getBody()} or {@see DataResponse::getData()}.
103 | *
104 | * @param string $view The view name {@see WebView::render()}.
105 | * @param array $parameters The parameters (name-value pairs) that will be extracted
106 | * and made available in the view file.
107 | *
108 | * @psalm-param array $parameters
109 | *
110 | * @return DataResponse The response instance.
111 | */
112 | public function render(string $view, array $parameters = []): DataResponse
113 | {
114 | $commonParameters = $this->getCommonParameters();
115 | $layoutParameters = $this->getLayoutParameters();
116 | $metaTags = $this->getMetaTags();
117 | $linkTags = $this->getLinkTags();
118 |
119 | return $this->responseFactory->createResponse(fn (): string => $this->renderProxy(
120 | $view,
121 | $parameters,
122 | $commonParameters,
123 | $layoutParameters,
124 | $metaTags,
125 | $linkTags,
126 | ));
127 | }
128 |
129 | /**
130 | * Returns a response instance {@see DataResponse} that supports deferred
131 | * rendering {@see render()} without applying a layout.
132 | *
133 | * Rendering will occur when calling {@see DataResponse::getBody()} or {@see DataResponse::getData()}.
134 | *
135 | * @param string $view The view name {@see WebView::render()}.
136 | * @param array $parameters The parameters (name-value pairs) that will be extracted
137 | * and made available in the view file.
138 | *
139 | * @psalm-param array $parameters
140 | *
141 | * @return DataResponse The response instance.
142 | */
143 | public function renderPartial(string $view, array $parameters = []): DataResponse
144 | {
145 | if ($this->layout === null) {
146 | return $this->render($view, $parameters);
147 | }
148 |
149 | return $this
150 | ->withLayout(null)
151 | ->render($view, $parameters);
152 | }
153 |
154 | /**
155 | * Renders a view as a string.
156 | *
157 | * @param string $view The view name {@see WebView::render()}.
158 | * @param array $parameters The parameters (name-value pairs) that will be extracted
159 | * and made available in the view file.
160 | *
161 | * @psalm-param array $parameters
162 | *
163 | * @throws Throwable If an error occurred during rendering.
164 | * @throws ViewNotFoundException If the view file does not exist.
165 | * @throws RuntimeException If the view cannot be resolved.
166 | * @return string The rendering result.
167 | */
168 | public function renderAsString(string $view, array $parameters = []): string
169 | {
170 | return $this->renderProxy(
171 | $view,
172 | $parameters,
173 | $this->getCommonParameters(),
174 | $this->getLayoutParameters(),
175 | $this->getMetaTags(),
176 | $this->getLinkTags(),
177 | );
178 | }
179 |
180 | /**
181 | * Renders a view as string {@see renderAsString()} without applying a layout.
182 | *
183 | * @param string $view The view name {@see WebView::render()}.
184 | * @param array $parameters The parameters (name-value pairs) that will be extracted
185 | * and made available in the view file.
186 | *
187 | * @psalm-param array $parameters
188 | *
189 | * @throws Throwable If an error occurred during rendering.
190 | * @throws ViewNotFoundException If the view file does not exist.
191 | * @throws RuntimeException If the view cannot be resolved.
192 | * @return string The rendering result.
193 | */
194 | public function renderPartialAsString(string $view, array $parameters = []): string
195 | {
196 | if ($this->layout === null) {
197 | return $this->renderAsString($view, $parameters);
198 | }
199 |
200 | return $this
201 | ->withLayout(null)
202 | ->renderAsString($view, $parameters);
203 | }
204 |
205 | /**
206 | * Extracts the controller name and returns a new instance with the controller name.
207 | *
208 | * @param object $controller The controller instance.
209 | */
210 | public function withController(object $controller): self
211 | {
212 | $new = clone $this;
213 | $new->name = $this->extractControllerName($controller);
214 | return $new;
215 | }
216 |
217 | /**
218 | * Returns a new instance with the specified controller name.
219 | *
220 | * @param string $name The controller name.
221 | */
222 | public function withControllerName(string $name): self
223 | {
224 | $new = clone $this;
225 | $new->name = $name;
226 | return $new;
227 | }
228 |
229 | /**
230 | * Returns a new instance with the specified view path.
231 | *
232 | * @param string $viewPath The full path to the directory of views or its alias.
233 | */
234 | public function withViewPath(string $viewPath): self
235 | {
236 | $new = clone $this;
237 | $new->setViewPath($viewPath);
238 | return $new;
239 | }
240 |
241 | /**
242 | * Returns a new instance with the specified layout.
243 | *
244 | * @param string|null $layout The full path to the layout file to be applied to views. If null, the layout will
245 | * not be applied.
246 | */
247 | public function withLayout(?string $layout): self
248 | {
249 | $new = clone $this;
250 | $new->layout = $layout;
251 | return $new;
252 | }
253 |
254 | /**
255 | * Return a new instance with the appended specified injections.
256 | *
257 | * @param object|string ...$injections The injection instances or class names.
258 | */
259 | public function withAddedInjections(object|string ...$injections): self
260 | {
261 | $new = clone $this;
262 | $new->setInjections(array_merge($this->injections, $injections));
263 | return $new;
264 | }
265 |
266 | /**
267 | * Returns a new instance with the specified injections.
268 | *
269 | * @param object|string ...$injections The injection instances or class names.
270 | */
271 | public function withInjections(object|string ...$injections): self
272 | {
273 | $new = clone $this;
274 | $new->setInjections($injections);
275 | return $new;
276 | }
277 |
278 | /**
279 | * Returns a new instance with specified locale code.
280 | *
281 | * @param string $locale The locale code.
282 | */
283 | public function withLocale(string $locale): self
284 | {
285 | $new = clone $this;
286 | $new->locale = $locale;
287 | return $new;
288 | }
289 |
290 | /**
291 | * Renders a view as a string injecting parameters and tags into view context.
292 | *
293 | * @param string $view The view name {@see WebView::render()}.
294 | * @param array $contentParameters The content parameters to render view.
295 | * @param array $injectCommonParameters The common parameters to inject.
296 | * @param array $injectLayoutParameters The layout parameters to inject.
297 | * @param array $metaTags The meta tags to inject.
298 | * @param array $linkTags The link tags to inject.
299 | *
300 | * @psalm-param array $contentParameters
301 | * @psalm-param array $injectCommonParameters
302 | * @psalm-param array $injectLayoutParameters
303 | *
304 | * @throws RuntimeException If the view cannot be resolved.
305 | * @throws Throwable If an error occurred during rendering.
306 | * @throws ViewNotFoundException If the view file does not exist.
307 | * @return string The rendering result.
308 | */
309 | private function renderProxy(
310 | string $view,
311 | array $contentParameters,
312 | array $injectCommonParameters,
313 | array $injectLayoutParameters,
314 | array $metaTags,
315 | array $linkTags
316 | ): string {
317 | $currentView = $this->view->deepClone()->withContext($this);
318 |
319 | if ($this->locale !== null) {
320 | $currentView = $currentView->withLocale($this->locale);
321 | }
322 |
323 | $this->injectMetaTags($metaTags, $currentView);
324 | $this->injectLinkTags($linkTags, $currentView);
325 |
326 | $content = $currentView
327 | ->setParameters($injectCommonParameters)
328 | ->render($view, $contentParameters);
329 |
330 | if ($this->layout === null) {
331 | return $content;
332 | }
333 |
334 | $layout = $this->aliases->get($this->layout);
335 |
336 | $layoutParameters = array_filter(
337 | $injectLayoutParameters,
338 | /** @psalm-suppress MissingClosureParamType */
339 | static fn ($_value, string $key): bool => !$currentView->hasParameter($key),
340 | ARRAY_FILTER_USE_BOTH,
341 | );
342 |
343 | return $currentView
344 | ->setParameters($layoutParameters)
345 | ->render($layout, ['content' => $content]);
346 | }
347 |
348 | /**
349 | * Gets injection common parameters merged with parameters specified during rendering.
350 | *
351 | * The parameters specified during rendering have more priority and will
352 | * overwrite the injected common parameters if their names match.
353 | *
354 | * @return array The injection common parameters merged with the parameters specified during rendering.
355 | *
356 | * @psalm-return array
357 | */
358 | private function getCommonParameters(): array
359 | {
360 | $parameters = [];
361 | foreach ($this->getInjections($this->layout, CommonParametersInjectionInterface::class) as $injection) {
362 | $parameters[] = $injection->getCommonParameters();
363 | }
364 | return array_merge(...$parameters);
365 | }
366 |
367 | /**
368 | * Gets the merged injection layout parameters.
369 | *
370 | * @return array The merged injection layout parameters.
371 | *
372 | * @psalm-return array
373 | */
374 | private function getLayoutParameters(): array
375 | {
376 | $parameters = [];
377 | foreach ($this->getInjections($this->layout, LayoutParametersInjectionInterface::class) as $injection) {
378 | $parameters[] = $injection->getLayoutParameters();
379 | }
380 | return array_merge(...$parameters);
381 | }
382 |
383 | /**
384 | * Gets the merged injection meta tags.
385 | *
386 | * @return array The merged injection meta tags.
387 | */
388 | private function getMetaTags(): array
389 | {
390 | $tags = [];
391 | foreach ($this->getInjections($this->layout, MetaTagsInjectionInterface::class) as $injection) {
392 | $tags[] = $injection->getMetaTags();
393 | }
394 | return array_merge(...$tags);
395 | }
396 |
397 | /**
398 | * Gets the merged injection link tags.
399 | *
400 | * @return array The merged injection link tags.
401 | */
402 | private function getLinkTags(): array
403 | {
404 | $tags = [];
405 | foreach ($this->getInjections($this->layout, LinkTagsInjectionInterface::class) as $injection) {
406 | $tags[] = $injection->getLinkTags();
407 | }
408 | return array_merge(...$tags);
409 | }
410 |
411 | /**
412 | * @psalm-template T
413 | * @psalm-param class-string $injectionInterface
414 | * @psalm-return list
415 | */
416 | private function getInjections(?string $layout, string $injectionInterface): array
417 | {
418 | $result = [];
419 | foreach ($this->getPreparedInjections() as $injection) {
420 | if ($injection instanceof $injectionInterface) {
421 | $result[] = $injection;
422 | continue;
423 | }
424 | if ($injection instanceof LayoutSpecificInjections && $injection->getLayout() === $layout) {
425 | foreach ($injection->getInjections() as $layoutInjection) {
426 | if ($layoutInjection instanceof $injectionInterface) {
427 | $result[] = $layoutInjection;
428 | }
429 | }
430 | }
431 | }
432 | return $result;
433 | }
434 |
435 | /**
436 | * Injects meta tags to the view.
437 | *
438 | * @param array $tags The meta tags to inject.
439 | *
440 | * @see WebView::registerMeta()
441 | * @see WebView::registerMetaTag()
442 | */
443 | private function injectMetaTags(array $tags, WebView $view): void
444 | {
445 | foreach ($tags as $key => $tag) {
446 | $key = is_string($key) ? $key : null;
447 |
448 | if (is_array($tag)) {
449 | $view->registerMeta($tag, $key);
450 | continue;
451 | }
452 |
453 | if (!($tag instanceof Meta)) {
454 | throw new InvalidMetaTagException(
455 | sprintf(
456 | 'Meta tag in injection should be instance of %s or an array. Got %s.',
457 | Meta::class,
458 | $this->getType($tag),
459 | ),
460 | $tag
461 | );
462 | }
463 |
464 | $view->registerMetaTag($tag, $key);
465 | }
466 | }
467 |
468 | /**
469 | * Injects link tags to the view.
470 | *
471 | * @param array $tags The link tags to inject.
472 | *
473 | * @see WebView::registerLinkTag()
474 | */
475 | private function injectLinkTags(array $tags, WebView $view): void
476 | {
477 | foreach ($tags as $key => $tag) {
478 | if (is_array($tag)) {
479 | $position = $tag['__position'] ?? WebView::POSITION_HEAD;
480 | if (!is_int($position)) {
481 | throw new InvalidLinkTagException(
482 | sprintf(
483 | 'Link tag position in injection should be integer. Got %s.',
484 | $this->getType($position),
485 | ),
486 | $tag
487 | );
488 | }
489 |
490 | if (isset($tag[0]) && $tag[0] instanceof Link) {
491 | $tag = $tag[0];
492 | } else {
493 | unset($tag['__position']);
494 | $tag = Html::link()->addAttributes($tag);
495 | }
496 | } else {
497 | $position = WebView::POSITION_HEAD;
498 | if (!($tag instanceof Link)) {
499 | throw new InvalidLinkTagException(
500 | sprintf(
501 | 'Link tag in injection should be instance of %s or an array. Got %s.',
502 | Link::class,
503 | $this->getType($tag),
504 | ),
505 | $tag
506 | );
507 | }
508 | }
509 |
510 | $view->registerLinkTag($tag, $position, is_string($key) ? $key : null);
511 | }
512 | }
513 |
514 | /**
515 | * Returns a controller name based on controller instance.
516 | *
517 | * Name should be converted to "id" case without `controller` on the ending.
518 | *
519 | * If namespace does not contain `controller` or `controllers` then the method returns only classname without
520 | * `controller` at the end else it returns all sub-namespaces with `controller` (or `controllers`) at the end.
521 | *
522 | * @param object $controller The controller instance.
523 | *
524 | * @return string The controller name.
525 | *
526 | * @example App\Controller\FooBar\BazController -> foo-bar/baz
527 | * @example App\Controllers\FooBar\BazController -> foo-bar/baz
528 | * @example App\AllControllers\MyController\FooBar\BazController -> foo-bar/baz
529 | * @example App\AllControllers\MyController\BazController -> baz
530 | * @example Path\To\File\BlogController -> blog
531 | *
532 | * @see Inflector::pascalCaseToId()
533 | */
534 | private function extractControllerName(object $controller): string
535 | {
536 | /** @var string[] $cache */
537 | static $cache = [];
538 |
539 | $class = $controller::class;
540 | if (array_key_exists($class, $cache)) {
541 | return $cache[$class];
542 | }
543 |
544 | if (preg_match('/(?:.*controller\\\|.*controllers\\\)([\w\\\]+)controller$/iU', $class, $m) && !empty($m[1])) {
545 | $name = $m[1];
546 | } elseif (preg_match('/(\w+)controller$/iU', $class, $m) && !empty($m[1])) {
547 | $name = $m[1];
548 | } else {
549 | throw new RuntimeException('Cannot detect controller name.');
550 | }
551 |
552 | $name = str_replace('\\', '/', $name);
553 | return $cache[$class] = (new Inflector())->pascalCaseToId($name);
554 | }
555 |
556 | /**
557 | * Returns the value type.
558 | *
559 | * @param mixed $value The value to check.
560 | *
561 | * @return string The value type.
562 | */
563 | private function getType(mixed $value): string
564 | {
565 | return get_debug_type($value);
566 | }
567 |
568 | private function setViewPath(?string $path): void
569 | {
570 | $this->viewPath = $path === null ? null : rtrim($path, '/');
571 | }
572 |
573 | private function getPreparedInjections(): array
574 | {
575 | if ($this->preparedInjections !== null) {
576 | return $this->preparedInjections;
577 | }
578 |
579 | $this->preparedInjections = [];
580 | foreach ($this->injections as $injection) {
581 | $this->preparedInjections[] = is_object($injection)
582 | ? $injection
583 | : $this->injectionContainer->get($injection);
584 | }
585 |
586 | return $this->preparedInjections;
587 | }
588 |
589 | /**
590 | * @psalm-param array