====== Действия контроллеров ====== Контроллер представляет из себя класс, наследуемый от системного класса **cmsFrontend** или **cmsBackend** для публичной и административной части сайта соответственно. Здесь и далее мы будем рассматривать все на примере frontend-контроллера. Особенности реализации backend-контроллера рассмотрены в [[dev:controllers:backend:controller|отдельной статье]]. ===== Класс контроллера ===== Класс определяется в файле **/system/controllers/{имя компонента}/frontend.php**. В простейшем случае класс выглядит так: class shop extends cmsFrontend { } где **shop** - название компонента, совпадающие с названием его папки. Все **публичные** методы, объявленные в этом классе доступны через контекст $this во внешних экшенах и хуках. В методах контроллера вы можете подключать и объекты других контроллеров. Есть два способа. Первый способ: // получаем объект контроллера Контент $content = cmsCore::getController('content'); // получаем субъекты правил $perms = $content->getPermissionsSubjects(); Второй способ (InstantCMS выше 2.8.2 версии): // получаем субъекты правил $perms = $this->controller_content->getPermissionsSubjects(); Важно отметить, что эти два действия отличаются контекстом создания объекта контроллера. У метода getController есть второй параметр - ''$request'', где можно передать [[dev:controllers:request|объект запроса]]. Во втором способе объект запроса передаётся тот же, что и у текущего контроллера, в данном случае объекта класса shop. Т.е. если переписать первый способ вот так: // получаем объект контроллера Контент $content = cmsCore::getController('content', $this->request); // получаем субъекты правил $perms = $content->getPermissionsSubjects(); то примеры по сути будут идентичны с разницей лишь в удобстве использования. ===== Определение действий ===== Действие контроллера (экшен) - это дочерний метод класса контроллера, обрабатывающий запросы пользователя через URL. Например, пользователь открывает страницу **site.ru/shop/catalog/view/123**. Роутер InstantCMS произведет разбор данного адреса на составные части: * **shop** - компонент, frontend-контроллер которого нужно запустить * **catalog** - экшен контроллера, который нужно вызвать * все что далее, в нашем случае **view** и **123** - параметры, которые нужно передать экшену Таким образом, чтобы обработать подобный URL мы должны создать файл **/system/controllers/shop/frontend.php** со следующим содержимым: class shop extends cmsFrontend { public function actionCatalog($do, $id){ dump("Hello, do = {$do}, id = {$id}"); } } В результате, после открытия URL **site.ru/shop/catalog/view/123** пользователь увидит в браузере текст: Hello, do = view, id = 123 Разумеется, в реальности экшен не должен совершать вывод на экран с помощью dump() или echo. Вместо этого используются шаблоны, работа с которыми будет рассмотрена в следующих разделах. ===== Индексное действие ===== В случаях, когда в URL содержится только название компонента, без указания экшена будет вызван метод **actionIndex()**. ===== Названия действий ===== Название метода каждого экшена в контроллере формируется по принципу: **action{Name}**, где {Name} - полученный из URL идентификатор экшена. В URL название экшена может состоять из нескольких слов, разделенных знаком подчеркивания (_). В этом случае название метода формируется по принципу: **action{ManyWordsName}**. Каждая заглавная буква в {ManyWordsName} соответствует знаку подчеркивания в URL. Примеры преобразования имен: ^ URL ^ Контроллер ^ Метод контроллера ^ | site.ru/shop | shop | actionIndex() | | site.ru/shop/catalog | shop | actionCatalog() | | site.ru/shop/view_order | shop | actionViewOrder() | | site.ru/shop/add_many_items | shop | actionAddManyItems() | ===== Действия во внешних файлах ===== Экшены могут быть определены как внутри класса контроллера (в файле frontend.php), так и в собственных отдельных файлах. В случаях, когда контроллер содержит большое количество объемных экшенов целесообразнее вынести их в отдельные файлы. Файлы хранятся в папке **/system/controllers/{имя компонента}/actions**. Название файла должно совпадать с названием экшена в URL страницы. Например, рассмотренный выше экшен **actionCatalog()** для компонента **shop** может быть вынесен в файл **/system/controllers/shop/actions/catalog.php**. Внутри файла должен быть определен класс **action{Имя компонента}{Название экшена}**, наследуемый от системного класса cmsAction: class actionShopCatalog extends cmsAction { } Внутри класса должен быть определен метод **run()**: class actionShopCatalog extends cmsAction { public function run($do, $id){ dump("Hello from file! Do = {$do}, id = {$id}"); } } Метод **run()** принимает параметры экшена из URL, аналогично с тем, как это делал сам экшен когда был определен в frontend.php. Важным моментом в данном случае является то, что внутри метода **run()** доступны все методы и свойства самого контроллера (класса, определенного в frontend.php) через $this. Например, **frontend.php**: class example extends cmsFrontend { public $my_name = 'John'; public function sayHello($your_name) { dump("Hello, {$your_name}!", false); } } **actions/hello.php**: class actionExampleHello extends cmsAction { public function run(){ $this->sayHello('Vasiliy'); dump("My name is {$this->my_name}"); } } В этом случае обращение по URL **site.ru/example/hello** выведет: Hello, Vasiliy! My name is John Таким образом, код который должен быть доступен внутри любого экшена (не зависимо от его расположения) можно размещать внутри класса контроллера. Иногда, по каким-то причинам разработки, необходимо написать экшен, который будет вызываться из других контроллеров или иным способом, но не напрямую через URL. Для того, чтобы защититься от прямых вызовов таких экшенов существует свойство ''$lock_explicit_call''. Установите его в ''true''. class actionUsersProfile extends cmsAction { public $lock_explicit_call = true; public function run(){ // PHP code } } ===== Действия before и after ===== Иногда может потребоваться выполнить одинаковый код в начале или конце каждого экшена (например, провести какую-либо инициализацию или очистку). В этом случае нет необходимости дублировать код в каждом экшене. Вместо этого можно объявить специальные методы **before()** и **after()** в теле контроллера. Пример: class shop extends cmsFrontend { public function before($action_name){ // Эта строка обязательна parent::before($action_name); // Код внутри этого метода будет выполнен // ПЕРЕД запуском любого экшена контроллера dump("Before: {$action_name}", false); return true; } public function after($action_name){ // Эта строка обязательна parent::after($action_name); // Код внутри этого метода будет выполнен // ПОСЛЕ запуска любого экшена контроллера dump("After: {$action_name}"); } public function actionCatalog($do, $id){ dump("do = {$do}, id = {$id}", false); } } Методы **before()** и **after()** имеют входящий параметр - название экшена до/после которого они вызываются. Это позволяет разграничить логику для каждого конкретного экшена. Таким образом, для нашего примера, если пользователь откроет URL **site.ru/shop/catalog/view/123** он получит вывод: Before: catalog do = view, id = 123 After: catalog Метод **before()** должен возвращать //true// или //false//. Если он вернет //false// - вызываемый экшен не будет выполнен, но никаких ошибок при этом также не будет показано. Если внутри **before()** необходимо выдать 404 ошибку, то необходимо использовать метод ядра: cmsCore::error404(); ===== Изменение роутинга ===== Иногда может потребоваться изменить системную логику роутинга экшенов. Например, мы захотим чтобы адреса профилей пользователей на сайте имели вид: **site.ru/users/{id}**, где {id} - идентификатор пользователя в базе. В этом случае мы получим множество разных адресов: * site.ru/users/1 * site.ru/users/24 * site.ru/users/6 * и так далее Стандартный роутер при обращении, например, к адресу **site.ru/users/24** будет пытаться вызвать метод **action24()** в контроллере компонента **users**. Нам же в данном случае нужно, чтобы второй сегмент в URL отвечал не за название экшена, а за параметр какого-либо из них. Мы можем определить собственный метод для роутинга, внутри контроллера: class users extends cmsFrontend { public function routeAction($action_name){ // Если название вызываемого экшена состоит НЕ из одних цифр, // то возвращаем исходное название, как ни в чем не бывало if (!is_numeric($action_name)){ return $action_name; } // иначе, понимаем что в $action_name на самом деле id пользователя $user_id = $action_name; // сохраняем полученный ID как первый параметр для того экшена, // на который будет перенаправлять работу array_unshift($this->current_params, $action_name); // возвращаем название нужного экшена return 'view_profile'; } public function actionViewProfile($id) { if (!is_numeric($id)) { cmsCore::error404(); } dump("Это профиль пользователя ID = {$id}"); } } Метод **routeAction()** запускается до вызова указанного в URL экшена (но после **before()**). В качестве аргумента он принимает название экшена, определенное системным роутером. Далее этот метод может совершать любые нужные проверки. В итоге он должен вернуть название экшена который будет вызван. ---- [[dev:controllers|Вернуться к оглавлению]]