Контроллеры
-
class Cake\Controller\Controller
Контроллеры - это то, что скрывается за буквой „C“ в понятии MVC. После того,
как срабатывает маршрутизация, и обнаруживается правильный котроллер, происходит
вызов нужного экшена. Ваш контроллер должен произвести верную интерпретацию данных
запроса, убедившись, что будет вызвана необходимая модель, и возвращен верный вид
и результат запроса. Контроллеры можно представить себе, как промежуточное звено
между Моделью и Видом. Старайтесь сохранять ваши контроллеры компактными,
помещая всю бизнес-логику внутри модели. Это сделает ваш код более гибким и
облегчит его повторное использование и тестирование.
Вцелом контроллер используется для построения логики в работе с конкретной моделью.
Например, если вы создаете сайт онлайн-пекарни, у вас вероятно будет контроллер
RecipesController, управляющий вашими рецептами, а также контроллер
IngredientsController управляющий вашими ингридиентами. Однако может быть и так,
что некоторые контроллеры могут работать более, чем с одной моделью. В CakePHP
контроллер именуется в соответствии с основной моделью, с которой он работает.
Все контроллеры вашего приложения наследуются от класса AppController
,
который в свою очередь наследуется от класса ядра Controller
.
Класс AppController
может быть объявлен в файле
src/Controller/AppController.php, и он должен содержать методы, которые
будут общими для всех контроллеров вашего приложения.
Контроллеры предоставляют методы, обрабатывающие запросы. Эти методы называются
экшены. По умолчанию кажды открытый (public) метод в контроллере является экшеном,
и он доступен из URL. Экшен отвечает за обработку запроса и возвращает его результат.
Как правило ответ возвращается с использованием вида, но существуют и другие способы
создания ответов.
Контроллер уровня приложения (AppController)
Как уже упоминалось во введении, класс AppController
- это родительский класс
для всех контроллеров вашего приложения. Сам по себе класс AppController
наследуется
от класса Cake\Controller\Controller
, входящего в состав CakePHP.
Класс AppController
объявлен в файле src/Controller/AppController.php
следующим образом:
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
}
Атрибуты и методы, прописанные в вашем классе AppController
будут доступны во
всех ваших контроллерах, наследующихся от данного класса. Компоненты (о которых будет
рассказано далее) - это самый лучший вариант для кода, использующегося во многих (но
необязательно во всех) контроллерах.
Вы можете использовать ваш AppController
для загрузки компонентов, которые будут
использоваться в каждом контроллере вашего приложения. CakePHP предоставляет метод
initialize()
, который вызывается в конце коструктора Контроллера:
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
public function initialize()
{
// Компонент CSRF всегда включен.
$this->loadComponent('Csrf');
}
}
В дополнение к методу initialize()
, более старое свойство $components
также позволит вам объявлять обязательные к загрузке компоненты. В то время,
как применяются стандартные правила наследования ООП, компоненты и хелперы,
используемые контроллером обрабатываются по-особому. В данном случае значения
свойств класса AppController
объединяются с массивами дочерних классов
контроллеров. Значения дочерних классов всегда переопределяют значения из
AppController
.
Поток запросов
Когда к приложению CakePHP делается запрос, классы CakePHP
Cake\Routing\Router
и Cake\Routing\Dispatcher
используют Подключение маршрутов для обнаружения и создания верного
экземпляра класса контроллера. Данные запроса инкапсулируются в объект запроса.
CakePHP помещает всю важную информацию из запроса в свойство $this->request
.
Для более подробной информации об объекте запроса смотрите раздел Запрос.
Экшены контроллера
Экшены контроллера ответственны за преобразование параметров запроса в ответ
для браузера/пользователя совершающего запрос. CakePHP использует соглашения
для автоматизации данного процесса и для устранения некоторого шаблонного кода,
который вам пришлось бы писать в противном случае.
По соглашению, CakePHP обрабатывает вид с именем, соответствующим имени экшена.
Возвращаясь к нашему примеру онлайн пекарни, наш контроллер может содержать
экшены view()
, share()
, и search()
. Контроллер может быть найден в
файле src/Controller/RecipesController.php и содержать следующий код:
// src/Controller/RecipesController.php
class RecipesController extends AppController
{
public function view($id)
{
// Здесь описывается вся логика экшена.
}
public function share($customerId, $recipeId)
{
// Здесь описывается вся логика экшена.
}
public function search($query)
{
// Здесь описывается вся логика экшена.
}
}
Файлы шаблонов для этих экшенов, назывались бы src/Template/Recipes/view.ctp,
src/Template/Recipes/share.ctp, и src/Template/Recipes/search.ctp. По
соглашениям имя файла вида соответствует имени экшена контроллера, записанном в
нижнем регистре с использованием подчеркиваний для разделения слов.
Экшены контроллера обычно используют метод Controller::set()
для создания
контекста, используемого View
для обработки слоя вида. Благодаря соглашениям,
используемым CakePHP, вам не нужно самим создавать и обрабатывать виды. Вместо этого
как только сработает экшен контроллера, CakePHP сам произведет обработку и передачу
Вида.
Если по каким-либо причинам вам захочется изменить стандартное поведение, вы можете
вернуть объект Cake\Http\Response
из экшена с полностью созданным
ответом.
Чтобы вы могли эффективно использовать контроллер в своем приложении, мы
охватим некоторые основные атрибуты и методы, предоставляемые контроллерами CakePHP.
Взаимодействие с видами
Контроллеры взаимодействуют с видами несколькими способами. Во-первых,
они способны передавать данные видам, используя метод Controller::set()
.
Вы также можете решить, какой класс вида использовать, какой файл вида должен быть
выведен контроллером.
Назначение переменных вида
-
Cake\Controller\Controller::set(string $var, mixed $value)
Метод Controller::set()
- это основной способ передачи данных от вашего контроллера
в ваш вид. Как только вы используете Controller::set()
, переменная становится
доступной в вашем виде:
// Сначала передаете данные от контроллера:
$this->set('color', 'розовая');
// После этого, в виде вы можете использовать данные:
?>
Вы выбрали <?= h($color) ?> глазурь для торта.
Метод Controller::set()
также принимает ассоциативный массив в качестве своего первого
параметра. Это часто может быть быстрым способом назначить набор сведений для
представления:
$data = [
'color' => 'pink',
'type' => 'sugar',
'base_price' => 23.95
];
// Делаем $color, $type, и $base_price
// доступными в виде:
$this->set($data);
Установка параметров вида
Если вы хотите настроить класс вида, пути макета/шаблона, хелперы или тему, которая
должна использоваться при выводе вида, вы можете использовать метод viewBuilder()
для получения билдера. Этот билдер может быть использован для определения свойств
вида перед его созданием:
$this->viewBuilder()
->helpers(['MyCustom'])
->theme('Modern')
->className('Modern.Admin');
Приведенный выше код показывает, как вы можете загрузить пользовательские
хелперы, установить тему и использовать нестандартный класс вида.
Добавлено в версии 3.1: ViewBuilder был добавлен в версии 3.1
Обработка вида
-
Cake\Controller\Controller::render(string $view, string $layout)
Метод Controller::render()
автоматически вызывается в конце каждого
запрошенного экшена контроллера. Этот метод выполняет всю логику вида
(используя данные, переданные методом Controller::set()
), помещает
вид в его макет View::$layout
, и отдает обратно пользователю.
Стандартный используемый файл вида определяется соглашениями.
Если запрашивается экшен search()
из RecipesController, будет
обработан файл вида src/Template/Recipes/search.ctp:
namespace App\Controller;
class RecipesController extends AppController
{
// ...
public function search()
{
// Обработка вида из src/Template/Recipes/search.ctp
$this->render();
}
// ...
}
Хотя CakePHP будет автоматически вызывать его после логики каждого экшена
(если вы не задали параметру $this->autoRender
значение false
),
вы можете использовать его для указания альтернативного файла вида, указав
имя файла в качестве первого аргумента метода Controller::render()
.
Если $view
начинается с „/“, предполагается, что путь к этому файлу
вида или элементу задан относительно папки src/Template. Это упрощает
обращение к элементам, к примеру в вызовах AJAX:
// Обработка элемента из src/Template/Element/ajaxreturn.ctp
$this->render('/Element/ajaxreturn');
Второй параметр $layout
метода Controller::render()
позволяет вам
определить макет внутри которого вид будет отображаться.
Обработка определенного шаблона
В вашем контроллере вы можете захотеть отобразить другой вид, отличный от стандартного.
Вы можете сделать это, вызвав Controller ::render()
напрямую. Как только вы
вызовете Controller::render()
, CakePHP не будет пытаться повторно отобразить вид:
namespace App\Controller;
class PostsController extends AppController
{
public function my_action()
{
$this->render('custom_file');
}
}
Это отобразило бы src/Template/Posts/custom_file.ctp вместо
src/Template/Posts/my_action.ctp.
Вы также можете отображать виды из плагинов следующим образом:
$this->render('НазваниеПлагина.КонтроллерПлагина/custom_file')
.
К примеру:
namespace App\Controller;
class PostsController extends AppController
{
public function my_action()
{
$this->render('Users.UserDetails/custom_file');
}
}
Это отобразило бы plugins/Users/src/Template/UserDetails/custom_file.ctp
Перенаправление на другие страницы
-
Cake\Controller\Controller::redirect(string|array $url, integer $status)
Метод управления потоком, который вы будете использовать чаще всего
это Controller::redirect()
. Этот метод принимает свой первый
параметр в виде CakePHP-относительного URL. Когда пользователь успешно
разместил заказ, вы можете перенаправить его на экран квитанции.
public function place_order()
{
// Логика для завершения заказа размещается здесь
if ($success) {
return $this->redirect(
['controller' => 'Orders', 'action' => 'thanks']
);
}
return $this->redirect(
['controller' => 'Orders', 'action' => 'confirm']
);
}
Метод вернет экземпляр ответа с соответствующим набором заголовков.
Вы должны вернуть экземпляр ответа из своего экшена, чтобы предотвратить
вывод вида и позволить диспетчеру обработать фактическое перенаправление.
Вы также можете использовать относительный или абсолютный URL в качестве
значения аргумента $url:
return $this->redirect('/orders/thanks');
return $this->redirect('http://www.example.com');
Также вы можете передавать данные в экшен:
return $this->redirect(['action' => 'edit', $id]);
Второй параметр метода Controller::redirect()
позволяет вам определять
код HTTP-статуса сопровождающий перенаправление. Возможно вы захотите
использовать код 301 (moved permanently) или 303 (see other), в зависимости
от природы перенапрвления.
Если вы хотите осуществить перенаправление на страницу-источник запроса,
вы можете сделать это следующим образом:
return $this->redirect($this->referer());
Пример использования строк запроса и хэш-ссылок:
return $this->redirect([
'controller' => 'Orders',
'action' => 'confirm',
'?' => [
'product' => 'pizza',
'quantity' => 5
],
'#' => 'top'
]);
Это создаст следующий URL:
http://www.example.com/orders/confirm?product=pizza&quantity=5#top
Перенаправление на другой экшен внутри того же контроллера
-
Cake\Controller\Controller::setAction($action, $args...)
Если вам нужно перенаправить текущий экшен на другой в том же
контроллере, вы можете использовать Controller::setAction()
для обновления объекта запроса, изменить шаблон вида, который
будет отображаться, и выполнить переадресацию к указанному экшену:
// Из экшена delete, вы можете вывести обновленную
// страницу списка.
$this->setAction('index');
Загрузка дополнительных моделей
-
Cake\Controller\Controller::loadModel(string $modelClass, string $type)
Функция loadModel()
удобна, когда вам нужно использовать
таблицу/коллекцию модели, которая не является стандартной для контроллера:
// В методе контроллера.
$this->loadModel('Articles');
$recentArticles = $this->Articles->find('all', [
'limit' => 5,
'order' => 'Articles.created DESC'
]);
Если вы используете поставщика таблиц, отличного от встроенной ORM,
вы можете привязать эту систему таблиц к контроллерам CakePHP,
подключив ее фабричный метод:
// В методе контроллера.
$this->modelFactory(
'ElasticIndex',
['ElasticIndexes', 'factory']
);
После этого вы также сможете использовать loadModel
для загрузки
экземпляров:
// В методе контроллера.
$this->loadModel('Locations', 'ElasticIndex');
Примечание
TableRegistry встроенной ORM подключена по умолчанию как
провайдер „Table“.
Пагинация модели
-
Cake\Controller\Controller::paginate()
Этот метод используется для разбивки на страницы результатов, полученных от ваших моделей.
Вы можете указать размеры страниц, условия поиска моделей и многое другое. Смотрите
pagination для более подробной информации о
том, как использовать метод paginate()
.
Атрибут $paginate
дает вам простой способ настройки поведения метода paginate()
:
class ArticlesController extends AppController
{
public $paginate = [
'Articles' => [
'conditions' => ['published' => 1]
]
];
}
Настройка загружаемых компонентов
-
Cake\Controller\Controller::loadComponent($name, $config = [])
В методе initialize()
вашего Контроллера вы можете определить любые
компоненты, которые вы хотите загрузить, а также любые данные об их
конфигурации:
public function initialize()
{
parent::initialize();
$this->loadComponent('Csrf');
$this->loadComponent('Comments', Configure::read('Comments'));
}
-
property Cake\Controller\Controller::$components
Свойство $components
в ваших контроллерах позволяет вам настроить
компоненты. Конфигурируемые компоненты и их зависимости будут созданы
CakePHP для вас. Прочтите раздел Настройка компонентов для более
подробной информации. Как упоминалось ранее, свойство $components
будет
объединено со свойством, определенным в каждом из родительских классов
вашего контроллера.
Настройка загружаемых хелперов
-
property Cake\Controller\Controller::$helpers
Давайте посмотрим, как сказать CakePHP, что мы хотим использовать
дополнительные классы MVC:
class RecipesController extends AppController
{
public $helpers = ['Form'];
}
Каждая из этих переменных объединяется с их унаследованными значениями,
поэтому нет необходимости (к примеру) объявлять заново FormHelper
или
что-либо, что объявлено в вашем AppController
.
Не рекомендуется, начиная с версии 3.0: Загрузка хелперов из контроллера предоставляется лишь в целях обратной
совместимости. Вы должны посмотреть Настройка хелперов для
ознакомления с тем, как загружать хелперы.
Коллбеки жизненного цикла запроса
Контроллеры CakePHP запускают несколько событий/коллбеков, которые можно
использовать для вставки логики вокруг жизненного цикла запроса:
Коллбеки контроллера
По умолчанию следующие методы обратного вызова связаны с соответствующими
событиями, если методы реализуются вашими контроллерами
-
Cake\Controller\Controller::beforeFilter(Event $event)
Вызывается во время события Controller.initialize
, которое происходит
перед каждым экшеном в контроллере. Это удобное место для проверки
активности сессии или проверки прав пользователя.
Примечание
Метод beforeFilter() будет вызываться для отсутствующих экшенов.
Возврат ответа от метода beforeFilter()
не будет препятствовать вызову других
слушателей того же события. Вы должны явно stop the event.
-
Cake\Controller\Controller::beforeRender(Event $event)
Вызывается во время события Controller.beforeRender
, которое происходит после
логики экшена контроллера, но перед визуализацией вида. Этот коллбек не
используется часто, но может потребоваться, если вы вызываете вручную метод
render()
перед окончанием экшена.
-
Cake\Controller\Controller::afterFilter(Event $event)
Вызывается во время события Controller.shutdown
, которое запускается после
каждого экшена контроллера и после завершения рендеринга. Это последний метод
контроллера для запуска.
В дополнение к коллбекам жизненного цикла контроллера Компоненты
также предоставляют схожий набор коллбеков.
Не забудьте вызвать коллбеки AppController
в коллбек-методах дочерних контроллеров
для лучших результатов:
//use Cake\Event\Event;
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
}