docs/guide-ru/structure-views.md
Виды - это часть MVC архитектуры, это код, который отвечает за представление данных конечным пользователям. В веб приложениях виды создаются обычно в виде видов - шаблонов, которые суть PHP скрипты, в основном содержащие HTML код и код PHP, отвечающий за представление и внешний вид. Виды управляются компонентом приложения [[yii\web\View|view]], который содержит часто используемые методы для упорядочивания видов и их рендеринга. Для упрощения, мы будем называть виды - шаблоны просто видами.
Как мы упоминали ранее, вид - это просто PHP скрипт, состоящий из PHP и HTML кодa. В примере ниже - вид, который представляет форму авторизации. Как видите, PHP код здесь генерирует динамический контент, как, например, заголовок страницы и саму форму, тогда как HTML организует полученные данные в готовую html страницу.
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
/**
* @var \yii\web\View $this
* @var \yii\widgets\ActiveForm $form
* @var \app\models\LoginForm $model
*/
$this->title = 'Вход';
?>
<h1><?= Html::encode($this->title) ?></h1>
<p>Пожалуйста, заполните следующие поля для входа на сайт:</p>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'username') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<?= Html::submitButton('Login') ?>
<?php ActiveForm::end(); ?>
Внутри вида, вы можете использовать $this, которое представляет собой [[yii\web\View|компонент вид]], управляющий этим шаблоном и обеспечивающий
его рендеринг.
Кроме $this, в виде могут быть доступны другие переменные, такие как $form и $model из примера выше. Эти переменные представляют собой данные, которые передаются в вид контроллерами или другими объектами, которые вызывают рендеринг вида.
Совет: Переданные переменные могут быть перечислены в блоке комментария в начале скрипта, чтобы их смогли распознать IDE. К тому же, это хороший способ добавления документации в вид.
При создании видов, которые генерируют HTML страницы, важно кодировать и/или фильтровать данные, которые приходят от пользователей перед тем как их показывать. В противном случае ваше приложение может стать жертвой атаки типа межсайтовый скриптинг
Чтобы показать обычный текст, сначала кодируйте его с помощью [[yii\helpers\Html::encode()]]. В примере ниже имя пользователя кодируется перед выводом:
<?php
use yii\helpers\Html;
?>
<div class="username">
<?= Html::encode($user->name) ?>
</div>
Чтобы показать HTML содержимое, используйте [[yii\helpers\HtmlPurifier]] для того, чтобы отфильтровать потенциально опасное содержимое. В примере ниже содержимое поста фильтруется перед показом:
<?php
use yii\helpers\HtmlPurifier;
?>
<div class="post">
<?= HtmlPurifier::process($post->text) ?>
</div>
Tip: Несмотря на то, что HTMLPurifier отлично справляется с тем, чтобы сделать вывод безопасным, работает он довольно медленно. Если от приложения требуется высокая производительность, рассмотрите возможность кэширования отфильтрованного результата
Как и для контроллеров, и моделей, для видов тоже есть определенные соглашения в их организации.
@app/views/ControllerID, где ControllerID это ID контроллера . Например, если класс контроллера - PostController, то папка будет @app/views/post; если контроллер - PostCommentController, то папка будет @app/views/post-comment. В случае, если контроллер принадлежит модулю, папка будет views/ControllerID в [[yii\base\Module::basePath|подпапке модуля]].ПутьВиджета/views, где ПутьВиджета - это папка, которая содержит класс виджета.В контроллерах и виджетах вы можете изменить папки видов по умолчанию, переопределив метод [[yii\base\ViewContextInterface::getViewPath()]].
Вы можете рендерить виды в контроллерах, widgets, или из любого другого места, вызывая методы рендеринга видов. Методы вызываются приблизительно так, как это показано в примере ниже,
/**
* @param string $view название вида или путь файла, в зависимости от того, какой метод рендеринга используется
* @param array $params данные, которые передаются виду
* @return string результат рендеринга
*/
methodName($view, $params = [])
Внутри контроллеров можно вызывать следующие методы рендеринга видов:
Например,
namespace app\controllers;
use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
class PostController extends Controller
{
public function actionView($id)
{
$model = Post::findOne($id);
if ($model === null) {
throw new NotFoundHttpException;
}
// рендерит вид с названием `view` и применяет к нему шаблон
return $this->render('view', [
'model' => $model,
]);
}
}
Внутри виджетов, вы можете вызывать следующие методы для рендеринга видов.
Например,
namespace app\components;
use yii\base\Widget;
use yii\helpers\Html;
class ListWidget extends Widget
{
public $items = [];
public function run()
{
// рендерит вид с названием `list`
return $this->render('list', [
'items' => $this->items,
]);
}
}
Вы можете рендерить вид внутри другого вида используя методы, которые предоставляет [[yii\base\View|компонент вида]]:
Например, следующий код рендерит _overview.php файл вида, который находится в той же папке что и вид, который рендерится в текущий момент. Помните, что $this в виде - это [[yii\base\View|компонент вида]] (а не контроллер, как это было в Yii1):
<?= $this->render('_overview') ?>
Вы может получить доступ к [[yii\base\View|виду]] как компоненту приложения вот так:
Yii::$app->view, а затем вызвать вышеупомянутые методы, чтобы отрендерить вид. Например,
// показывает файл "@app/views/site/license.php"
echo \Yii::$app->view->renderFile('@app/views/site/license.php');
При рендеринге вида, вы можете указать нужный вид, используя как имя вида, так и путь к файлу/алиас. В большинстве случаев вы будете использовать первый вариант, т.к. он более нагляден и гибок. Мы называем виды, которые были вызваны с помощью сокращенного имени именованные виды.
Имя вида преобразуется в соответствующий ему путь файла в соответствии со следующими правилами:
.php. К примеру, имя вида about соответствует файлу about.php.//, соответствующий ему путь будет @app/views/ViewName.
Т.е. вид будет искаться в [[yii\base\Application::viewPath|папке видов приложения по умолчанию]]. Например, //site/about будет преобразован в @app/views/site/about.php./, то вид будет искаться в [[yii\base\Module::viewPath|папке видов по умолчанию]] текущего модуля . Если активного модуля на данный момент нет, будет использована папка видов приложения по умолчанию, т.е. вид будет искаться в @app/views, как в одном из примеров выше.about будет преобразован в @app/views/site/about.php если контекстом является контроллер SiteController.item будет преобразован в @app/views/post/item
если он рендерится из вида @app/views/post/index.php.В соответствии с вышесказанным, вызов $this->render('view') в контроллере app\controllers\PostController будет рендерить файл @app/views/post/view.php, а вызов $this->render('_overview') в этом виде будет рендерить файл @app/views/post/_overview.php.
Данные можно передавать в вид явно или подгружать их динамически, обращаясь к контексту из вида.
Передавая данные через второй параметр методов рендеринга вида, вы явно передаете данные в вид.
Данные должны быть представлены как обычный массив: ключ-значение. При рендеринге вида, php вызывает встроенную функцию PHP extract() на переданном массиве, чтобы переменные из массива "распаковались" в переменные вида. Например, следующий код в контроллере передаст две переменные виду report :
$foo = 1 и $bar = 2.
echo $this->render('report', [
'foo' => 1,
'bar' => 2,
]);
Другой подход, подход контекстного доступа, извлекает данные из [[yii\base\View|компонента вида]] или других объектов, доступных в виде (например через глобальный контейнер Yii::$app). Внутри вида вы можете вызывать объект контроллера таким образом: $this->context (см пример снизу), и, таким образом, получить доступ к его свойствам и методам, например, как указано в примере, вы можете получить ID контроллера:
ID контроллера: <?= $this->context->id ?>
Явная передача данных в вид обычно более предпочтительна, т.к. она делает виды независимыми от контекста. Однако, у нее есть недостаток - необходимость каждый раз вручную строить массив данных, что может быть довольно утомительно и привести к ошибкам, если вид рендерится в разных местах.
[[yii\base\View|Компонент вида]] имеет свойство [[yii\base\View::params|params]], которое вы можете использовать для обмена данными между видами.
Например, в виде about вы можете указать текущий сегмент хлебных крошек с помощью следующего кода.
$this->params['breadcrumbs'][] = 'О нас';
Затем, в шаблоне, который также является видом, вы можете отобразить хлебные крошки используя данные, переданные через [[yii\base\View::params|params]].
<?= yii\widgets\Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>
Шаблоны - особый тип видов, которые представляют собой общие части разных видов. Например, у большинства страниц веб приложений одинаковые верх и низ (хедер и футер). Можно, конечно, указать их и в каждом виде, однако лучше сделать это один раз, в шаблоне, и затем, при рендеринге, включать уже отрендеренный вид в заданное место шаблона.
Поскольку шаблоны это виды, их можно создавать точно так же, как и обычные виды. По умолчанию шаблоны хранятся в папке @app/views/layouts. Шаблоны, которые используются в конкретном модуле, хранятся в подпапке views/layouts [[yii\base\Module::basePath|папки модуля]]. Вы можете изменить папку шаблонов по умолчанию, используя свойство [[yii\base\Module::layoutPath]] приложения или модулей.
Пример ниже показывает как выглядит шаблон. Для лучшего понимания мы сильно упростили код шаблона. На практике, однако, в нем часто содержится больше кода, например, тэги <head>, главное меню и т.д.
<?php
use yii\helpers\Html;
/**
* @var \yii\web\View $this
* @var string $content
*/
?>
<?php $this->beginPage() ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<?= Html::csrfMetaTags() ?>
<title><?= Html::encode($this->title) ?></title>
<?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>
<header>Моя компания</header>
<?= $content ?>
<footer>Моя компания © 2014</footer>
<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>
Как видите, шаблон генерирует HTML тэги, которые присутствуют на всех страницах. Внутри секции <body>, шаблон выводит переменную $content, которая содержит результат рендеринга видов контента, который передается в шаблон, при работе метода [[yii\base\Controller::render()]].
Большинство шаблонов вызывают методы, аналогично тому, как это сделано в примере выше, чтобы скрипты и тэги, зарегистрированные в других местах приложения могли быть правильно отображены в местах вызова (например, в шаблоне).
<head> секции страницы html.
Он генерирует метку, которая будет заменена зарегистрированным ранее кодом HTML (тэги link, мета тэги), когда рендеринг страницы будет завершен.<body>.
Он вызывает событие [[yii\web\View::EVENT_BEGIN_BODY|EVENT_BEGIN_BODY]] и генерирует метку, которая будет заменена зарегистрированным HTML кодом (например, Javascript'ом), который нужно разместить в начале <body> страницы.<body>.
Он вызывает событие [[yii\web\View::EVENT_END_BODY|EVENT_END_BODY]] и генерирует метку, которая будет заменена зарегистрированным HTML кодом (например, Javascript'ом), который нужно разместить в конце <body> страницы.Внутри шаблона, у вас есть доступ к двум предопределенным переменным: $this и $content. Первая представляет собой
[[yii\base\View|вид]] компонент, как и в обычных видах, тогда как последняя содержит результат рендеринга вида, который рендерится при вызове метода [[yii\base\Controller::render()|render()]] в контроллерах.
Если вы хотите получить доступ к другим данным из шаблона, используйте метод явной передачи (он описан в секции Доступ к данным в видах настоящего документа). Если вы хотите передать данные из вида шаблону, вы можете использовать метод, описанный в передаче данных между видами.
Как было описано в секции Рендеринг в контроллерах, когда вы рендерите вид, вызывая метод [[yii\base\Controller::render()|render()]] из контроллера, к результату рендеринга будет применен шаблон. По умолчанию будет использован шаблон @app/views/layouts/main.php .
Вы можете использовать разные шаблоны, конфигурируя [[yii\base\Application::layout]] или [[yii\base\Controller::layout]].
Первый переопределяет шаблон, который используется по умолчанию всеми контроллерами, а второй переопределяет шаблон в отдельном контроллере.
Например, код внизу показывает, как можно сделать так, чтобы контроллер использовал шаблон @app/views/layouts/post.php при рендеринге вида. Другие контроллеры, если их свойство layout не переопределено, все еще будут использовать @app/views/layouts/main.php как шаблон.
namespace app\controllers;
use yii\web\Controller;
class PostController extends Controller
{
public $layout = 'post';
// ...
}
Для контроллеров, принадлежащих модулю, вы также можете переопределять свойство модуля [[yii\base\Module::layout|layout]], чтобы использовать особый шаблон для этих контроллеров.
Поскольку свойство layout может быть сконфигурировано на разных уровнях приложения (контроллеры, модули, само приложение),
Yii определяет какой шаблон использовать для контроллера в два этапа.
На первом этапе определяется значение шаблона и контекстный модуль.
null, используется оно, и [[yii\base\Controller::module|модуль]]
контроллера как контекстный модуль.null (не задано), происходит поиск среди родительских модулей контроллера, включая само приложение (которое по умолчанию является родительским модулем для контроллеров, не принадлежащих модулям) и
находится первый модуль, свойство [[yii\base\Module::layout|layout]] которого не равно null . Тогда используется найденное значение layout этого модуля
и сам модуль в качестве контекста. Если такой модуль не найден, значит шаблон применен не будет.На втором этапе определяется сам файл шаблона для рендеринга на основании значения layout и контекстного модуля.
Значением layout может быть:
@app/views/layouts/main)./main): значение layout начинается со слеша. Будет искаться шаблон из [[yii\base\Application::layoutPath|папки шаблонов]] приложения, по умолчанию это @app/views/layouts.main): Будет искаться шаблон из [[yii\base\Module::layoutPath|папки шаблонов контекстного модуля]], по умолчанию это views/layouts в [[yii\base\Module::basePath|папке модуля]].false: шаблон не будет применен.Если у значения layout нет расширения, будет использовано расширение по умолчанию - .php.
Иногда нужно вложить один шаблон в другой. Например, в разных разделах сайта используются разные шаблоны, но у всех этих шаблонов есть основная разметка, которая определяет HTML5 структуру страницы. Вы можете использовать вложенные шаблоны, вызывая [[yii\base\View::beginContent()|beginContent()]] и [[yii\base\View::endContent()|endContent()]] в дочерних шаблонах таким образом:
<?php $this->beginContent('@app/views/layouts/base.php'); ?>
...код дочернего шаблона...
<?php $this->endContent(); ?>
В коде выше дочерний шаблон заключается в [[yii\base\View::beginContent()|beginContent()]] и [[yii\base\View::endContent()|endContent()]]. Параметр, передаваемый в метод [[yii\base\View::beginContent()|beginContent()]] определяет родительский шаблон. Это может быть как путь к файлу, так и алиас.
Используя подход выше, вы можете вкладывать шаблоны друг в друга в несколько уровней.
Блоки позволяют "записывать" контент в одном месте, а показывать в другом. Они часто используются совместно с шаблонами. Например, вы определяете (записываете) блок в виде и отображаете его в шаблоне.
Для определения блока вызываются методы [[yii\base\View::beginBlock()|beginBlock()]] и [[yii\base\View::endBlock()|endBlock()]].
После определения, блок доступен через $view->blocks[$blockID], где $blockID - это уникальный ID, который вы присваиваете блоку
в начале определения.
В примере ниже показано, как можно использовать блоки, определенные в виде, чтобы динамически изменять фрагменты шаблона.
Сначала, в виде, вы записываете один или несколько блоков:
...
<?php $this->beginBlock('block1'); ?>
...содержимое блока 1...
<?php $this->endBlock(); ?>
...
<?php $this->beginBlock('block3'); ?>
...содержимое блока 3...
<?php $this->endBlock(); ?>
Затем, в шаблоне, рендерите блоки если они есть, или показываете контент по умолчанию, если блок не определен.
...
<?php if (isset($this->blocks['block1'])): ?>
<?= $this->blocks['block1'] ?>
<?php else: ?>
... контент по умолчанию для блока 1 ...
<?php endif; ?>
...
<?php if (isset($this->blocks['block2'])): ?>
<?= $this->blocks['block2'] ?>
<?php else: ?>
... контент по умолчанию для блока 2 ...
<?php endif; ?>
...
<?php if (isset($this->blocks['block3'])): ?>
<?= $this->blocks['block3'] ?>
<?php else: ?>
... контент по умолчанию для блока 3 ...
<?php endif; ?>
...
[[yii\base\View|Компоненты вида]] дают много возможностей. Несмотря на то, что существует возможность создавать индивидуальные экземпляры [[yii\base\View]] или дочерних классов, в большинстве случаев используется
сам компонент view приложения. Вы можете сконфигурировать компонент в конфигурации приложения таким образом:
[
// ...
'components' => [
'view' => [
'class' => 'app\components\View',
],
// ...
],
]
Компоненты вида предоставляют широкие возможности по работе с видами, они описаны в отдельных секциях документации:
Также удобно пользоваться мелкими, но удобными фичами при разработке веб страниц, которые приведены ниже.
У каждой страницы должен быть заголовок. Обычно заголовок выводится в шаблоне. Однако на практике заголовок часто определяется в видах, а не в шаблонах. Чтобы передать заголовок из вида в шаблон, используется свойство [[yii\web\View::title|title]].
В виде можно задать заголовок таким образом:
<?php
$this->title = 'Мой заголовок страницы';
?>
В шаблоне заголовок выводится следующим образом, (убедитесь, что в <head> у вас соответствующий код):
<title><?= Html::encode($this->title) ?></title>
На веб страницах обычно есть мета-тэги, которые часто используются различными сервисами. Как и заголовки страниц,
мета-тэги выводятся в <head> и обычно генерируются в шаблонах.
Если вы хотите указать, какие мета-тэги генерировать в видах, вы можете вызвать метод [[yii\web\View::registerMetaTag()]] в виде так, как в примере ниже:
<?php
$this->registerMetaTag(['name' => 'keywords', 'content' => 'yii, framework, php']);
?>
Этот код зарегистрирует мета тэг "keywords" в виде. Зарегистрированные мета тэги рендерятся после того, как закончен рендеринг шаблона. Они вставляются в то место, где в шаблоне вызван метод [[yii\web\View::head()]]. Результатом рендеринга мета тэгов является следующий код:
<meta name="keywords" content="yii, framework, php">
Обратите внимание, что при вызове метода [[yii\web\View::registerMetaTag()]] несколько раз мета тэги будут регистрироваться каждый раз без проверки на уникальн��сть.
Чтобы убедиться, что зарегистрирован только один экземпляр одного типа мета тэгов, вы можете указать ключ мета тэга в качестве второго параметра при вызове метода. К примеру, следующий код регистрирует два мета тэга "description", однако отрендерен будет только второй.
$this->registerMetaTag(['name' => 'description', 'content' => 'Мой сайт сделан с помощью Yii!'], 'description');
$this->registerMetaTag(['name' => 'description', 'content' => 'Это сайт о забавных енотах.'], 'description');
Как и мета тэги, link тэги полезны во многих случаях, как, например, задание уникальной favicon, указание на RSS фид или указание OpenID сервера для авторизации. С link тэгами можно работать аналогично работе с мета тэгами, вызывая метод [[yii\web\View::registerLinkTag()]]. Например, вы можете зарегистрировать link тэг в виде таким образом:
$this->registerLinkTag([
'title' => 'Сводка новостей по Yii',
'rel' => 'alternate',
'type' => 'application/rss+xml',
'href' => 'https://www.yiiframework.com/rss.xml/',
]);
Этот код выведет
<link title="Сводка новостей по Yii" rel="alternate" type="application/rss+xml" href="https://www.yiiframework.com/rss.xml/">
Как и в случае с [[yii\web\View::registerMetaTag()|registerMetaTag()]], вы можете указать ключ вторым параметром при вызове [[yii\web\View::registerLinkTag()|registerLinkTag()]] чтобы избежать дублирования link тэгов одного типа.
[[yii\base\View|Компонент вида]] вызывает несколько событий во время рендеринга. Вы можете задавать обработчики для этих событий чтобы добавлять контент в вид или делать пост-обработку результатов рендеринга до того, как они будут отправлены конечным пользователям.
false, чтобы отменить процесс рендеринга.Например, следующий код вставляет дату в конец body страницы:
\Yii::$app->view->on(View::EVENT_END_BODY, function () {
echo date('Y-m-d');
});
Статическими страницами мы считаем страницы, которые содержат в основном статические данные и для формирования которых не нужно строить динамические данные в контроллерах.
Вы можете выводить статические страницы, сохраняя их в видах, а затем используя подобный код в контроллере:
public function actionAbout()
{
return $this->render('about');
}
Если сайт содержит много статических страниц, описанный выше подход не вполне подходит - его использование приведет к многократному повторению похожего кода. Вместо этого вы можете использовать отдельное действие [[yii\web\ViewAction]] в контроллере. Например,
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
public function actions()
{
return [
'page' => [
'class' => 'yii\web\ViewAction',
],
];
}
}
Теперь, если вы создадите вид about в папке @app/views/site/pages, он будет отображаться по такому адресу:
http://localhost/index.php?r=site%2Fpage&view=about
GET параметр view сообщает [[yii\web\ViewAction]] какой вид затребован. Действие будет искать этот вид в папке @app/views/site/pages.
Вы можете сконфирурировать параметр [[yii\web\ViewAction::viewPrefix]] чтобы изменить папку в которой ищется вид.
Виды отвечают за представление данных моделей в формате, понятным конечным пользователям. В целом, виды
$_GET, $_POST. Разбором запроса должны заниматься контроллеры. Если
данные запросов нужны для построения вида, они должны явно передаваться в вид контроллерами.Чтобы сделать виды более управляемыми, избегайте создания видов, которые содержат слишком сложную логику или большое количество кода. Используйте следующие подходы для их упрощения: