├── 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 | Yii 4 | 5 |

Yii View Renderer

6 |
7 |

8 | 9 | [![Latest Stable Version](https://poser.pugx.org/yiisoft/yii-view-renderer/v)](https://packagist.org/packages/yiisoft/yii-view-renderer) 10 | [![Total Downloads](https://poser.pugx.org/yiisoft/yii-view-renderer/downloads)](https://packagist.org/packages/yiisoft/yii-view-renderer) 11 | [![Build status](https://github.com/yiisoft/yii-view-renderer/actions/workflows/build.yml/badge.svg)](https://github.com/yiisoft/yii-view-renderer/actions/workflows/build.yml) 12 | [![Code Coverage](https://codecov.io/gh/yiisoft/yii-view-renderer/graph/badge.svg?token=WBV13RDIPX)](https://codecov.io/gh/yiisoft/yii-view-renderer) 13 | [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fyiisoft%2Fyii-view-renderer%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/yii-view-renderer/master) 14 | [![static analysis](https://github.com/yiisoft/yii-view-renderer/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/yii-view-renderer/actions?query=workflow%3A%22static+analysis%22) 15 | [![type-coverage](https://shepherd.dev/github/yiisoft/yii-view-renderer/coverage.svg)](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 | [![Open Collective](https://img.shields.io/badge/Open%20Collective-sponsor-7eadf1?logo=open%20collective&logoColor=7eadf1&labelColor=555555)](https://opencollective.com/yiisoft) 336 | 337 | ## Follow updates 338 | 339 | [![Official website](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](https://www.yiiframework.com/) 340 | [![Twitter](https://img.shields.io/badge/twitter-follow-1DA1F2?logo=twitter&logoColor=1DA1F2&labelColor=555555?style=flat)](https://twitter.com/yiiframework) 341 | [![Telegram](https://img.shields.io/badge/telegram-join-1DA1F2?style=flat&logo=telegram)](https://t.me/yii3en) 342 | [![Facebook](https://img.shields.io/badge/facebook-join-1DA1F2?style=flat&logo=facebook&logoColor=ffffff)](https://www.facebook.com/groups/yiitalk) 343 | [![Slack](https://img.shields.io/badge/slack-join-1DA1F2?style=flat&logo=slack)](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 $injections 591 | */ 592 | private function setInjections(array $injections): void 593 | { 594 | $this->injections = $injections; 595 | $this->preparedInjections = null; 596 | } 597 | } 598 | --------------------------------------------------------------------------------