Большинство вёб-приложений используют стандартный шаблон дизайна для большинства или даже для всех своих страниц. Бо(')льшую часть времени их авторы просто пишут PHPView::make()
, а затем привязывают нужные данные к этому шаблону:
return View::make('layouts.default')->nest('content', 'application.home', array('data' => $data));
Если вы поступаете так же, то учитывайте, что такой подход не слишком гибок. Если у вас множество мест в коде, где вы используете основной шаблон («layout»), то вам придётся потратить много времени на их изменение.
Здесь на сцену выходит Laravel — посмотрим же на него в деле. И, пожалуйста, придержите аплодисменты до конца лекции...
Шаблоны контроллеров
По умолчанию класс PHPHome_Controller
наследует от PHPBase_Controller
, который, в свою очередь, наследует от PHPController
. Посмотрим на конструктор последнего класса:
// Файл: laravel/routing/controller.php
public function __construct()
{
// Если контроллер содержит указание на шаблон, использующийся при
// отрисовке шаблонов мы создадим класс этого шаблона и присвоим
// его свойству $this->layout, заменив строковое имя.
if (!is_null($this->layout)) {
$this->layout = $this->layout();
}
}
Как вы видите, если класс вашего контроллера содержит указание на имя шаблона, Laravel создаст его объект. Чем это полезно для нас? Давайте посмотрим на определение PHPBase_Controller
:
// Файл: application/controllers/base.php
// метод, срабатывающий, если контроллер не может обработать текущий запрос.
class Base_Controller extends Controller {
public function __call($method, $parameters) {
return Response::error('404');
}
}
Кроме «ловящего всё» («catch-all») метода в этом классе больше ничего нет. Но это именно то место, где мы можем сделать красивый трюк: мы можем определить свойство PHP$layout
— таким образом, все контроллеры, наследующие от PHPBase_Controller
, получат его автоматически:
public $layout = 'layouts.default';
Отлично! Теперь когда наш контроллер будет загружен, шаблон будет создан и мы сможем привязать к нему данные:
public function action_index() {
$this->layout->nest('content', 'application.home', array('data' => $data));
}
Это выглядит куда красивее. Заметьте, что наш метод действия не возвращает никакого значения — на самом деле это просто не нужно! Laravel понимает, что мы использовали наш шаблон и всё остальное сделает за нас. Конечно, если мы хотим переадресовать пользователя мы всё равно можем это сделать с помощью return:
return Redirect::home();
Внимание: запомните, что для добавления фильтров к контроллеру вам нужно зарегистрировать их в конструкторе. Кроме этого вам нужно не забыть вызвать родительский конструктор. Например, ваш может быть таким:
// Файл: application/controllers/home.php
class Home_Controller extends Base_Controller {
public function __construct() {
parent::__construct(); // $this->layout теперь определён.
$this->filter('before', 'auth')->only('logout');
}
}
Насколько просто теперь обращаться с шаблонами в контроллерах! Перейдём к маршрутам.
Шаблоны маршрутов
К сожалению, в маршрутах шаблоны не так хороши, как в контроллерах — но не пугайтесь, мы решим и эту задачу. Есть два способа: регистрация именного шаблона и использование фильтров.
Именные шаблы
Регистрация именного шаблона делается с помощью PHPView::name()
:
// Файл: application/start.php или application/routes.php
View::name('layouts.default', 'layout');
Не имеет особого значения, куда именно вы поместите этот вызов, хотя я сам обычно использую start.php.
Теперь, имея именной шаблон, мы можем создать его экземпляр в начале applications/routes.php:
$layout = View::of('layout');
Теперь при форматировании этого шаблона нам нужно использовать переменную PHP$layout
— в анонимных функциях («closure») маршрутов это делается через use:
// Файл: application/routes.php
Route::get('/', function () use ($layout)
{
return $layout->nest('content', 'application.home', array('data' => $data));
});
Недостаток этого подхода в том, что нужно писать PHPuse ($layout)
в методе каждого маршрута, который использует этот шаблон.
Фильтры
Второй вариант решения задачи использования общего шаблона в маршрутах был подсказан мне Филом Спарксом в одной из тем на форуме Laravel.com. Этот подход немного более элегантный и требует меньшего вмешательства в код маршрутов. Вам нужно определить фильтр after в файле application/routes.php:
Route::filter('layout', function ($response, $type = 'html') {
// переадресации не содержат содержимого, а ошибки должны форматироваться отдельным шаблоном.
if ($response->status > 300) return;
switch ($type) {
case 'html':
// ответ уже был подготовлен, поэтому нам просто нужно "обернуть" его в наш шаблон:
$response->content = View::make('layout', array(
'content' => $response->content,
))->render();
break;
}
});
Теперь ваши маршруты должны использовать этот фильтр:
// Файл: application/routes.php
Route::get('/', array('after' => 'layout:html', function () {
return View::make('application.home', array('data' => $data));
});
Вы можете задаваться вопросом, чем же этот подход лучше использования именных шаблонов — ведь нам всё равно нужно привязывать к маршрутам фильтр. Хорошая новость — в Laravel вы можете группировать маршруты с одинаковыми свойствами. Например:
// Файл: application/routes.php
Route::group(array('after' => 'layout'), function () {
Route::get('/', function () {
return View::make('application.home', array('data' => $data));
});
Route::get('about', function () {
return View::make('application.about');
});
});
Выглядит хорошо. Как вы могли заметить, я не определил type (тип) — параметр, передаваемый в фильтр. Это сделано из-за того, что по умолчанию он установлен в PHP'html'
, но вы можете сделать это для нужного маршрута следующим образом:
// Файл: application/routes.php
Route::get('api/posts.json', array('after' => 'layout:json', function () {
// получить из БД сообщения, которые нужно вернуть и вернём их в виде массива:
return $posts;
});
Я использую фильтр layout, которому передаётся тип PHP'json'
(после двоеточия в имени фильтра — прим. пер.), а сам маршрут возвращает обычный массив сообщений. Теперь мы изменим наш фильтр:
// Файл: application/routes.php
Route::filter('layout', function ($response, $type = 'html') {
// переадресации не содержат содержимого, а ошибки должны форматироваться отдельным шаблоном.
if ($response->status > 300) return;
switch ($type) {
case 'html':
// ответ уже был подготовлен, поэтому нам просто нужно "обернуть" его в наш шаблон:
$response->content = View::make('layout', array(
'content' => $response->content,
))->render();
break;
case 'json':
// установим заголовок Content-Type в нужное значение и закодируем ответ:
$response->header('content-type', File::mime('json'));
$response->content = json_encode($response->content);
break;
}
});
Замечательно! Теперь мы возвращаем верный Content-Type при JSON-запросах.
Итак, как вы видите, в зависимости от вашей задачи любое из вышеперечисленных решений может быть подходящим.
Заключение
Лично я предпочитаю использовать контроллеры, когда дело идёт о работе с шаблонами просто потому, что это намного проще. Поймите меня правильно — маршруты по прежнему отлично применимы и я пользуюсь их гибкостью в сочетании с контроллерами для создания мощных приложений.