CodeIgniter 4. PSR-4. Произвольная MVC
17-08-2019Reading time ~ 5 min.CodeIgniter 8699
По умолчанию CodeIgniter все контролёры размещает в одном каталоге, что не всегда удобно с точки зрения организации файлов. В некоторых случаях удобней сгруппировать файлы в одном подкаталоге. Например подкаталог в App/Controllers
. Поскольку CodeIgniter поддерживает PSR-4, то сможет с ним корректно работать. Но нужно учитывать некоторые тонкости.
PSR-4 уже общепринятый стандарт, поэтому желательно сразу разобраться как с ним работать. Это очень просто. Есть некий базовый каталог, который в разных модулях или фреймворках указывает на начальную точку подключения файла класса. У класса всегда задаётся namescape, который ведет отсчет от базового каталога и до каталога файла.
Сам же файл класса всегда совпадает с именем класса (с учетом регистра). По стандарту форматирования php-кода имя класса начинается с большой буквы, соответственно и файл. Рассмотрим пример:
namespace App\Admin\users; class Profile ...
В данном случае namespace указывает на каталог App/Admin/users
и файл Profile.php
. В CodeIgniter базовый каталог считается от корня сайта, поэтому указывается «App».
Полное имя класса будет: App\Admin\users\Profile
. Оно указывается в роутинге, где последний сегмент указывает на имя файла:
$routes->add('admin/users/profile', 'App\Admin\users\Profile::index');
Что можно интерпретировать как App/Admin/users/Profile.php
.
Подкаталоги в Controllers
Начнём с роутинга. Добавим правило для адреса example.com/products/shoes
$routes->add('products/shoes', 'App\Controllers\products\Shoes::index');
Контролёр мы уже указываем полным именем (с его namespace). CodeIgniter поймёт, что это будет файл App/Controllers/products/Shoes.php
.
Создадим его:
namespace App\Controllers\products; class Shoes extends \App\Controllers\BaseController { public function index() { $data['title'] = 'Shoes'; $data['header'] = 'Shoes'; echo view('template/start', $data); echo view('about', $data); echo view('template/end', $data); } }
Мы расширяем \App\Controllers\BaseController
. Раньше, поскольку namespace указывал на один каталог, можно было указывать без полного имени. Здесь это не сработает.
Аналогичным образом можно вообще вынести контролёр за пределы каталога App/Controllers
. Например сделаем каталог App/Admin
и в нем файл Users.php
.
namespace App\Admin; class Users extends \App\Controllers\BaseController { public function index() { $data['title'] = 'Users'; $data['header'] = 'Users'; echo view('template/start', $data); echo view('about', $data); echo view('template/end', $data); } }
И добавим роутинг для адреса example.com/admin/users
$routes->add('admin/users', 'App\Admin\Users::index');
MVC в CodeIgniter
CodeIgniter придерживается своего понимания концепции MVC. Строго говоря это относится не только к CodeIgniter, но и большинству других php-фреймворков. Контролёр здесь выполняет роль связующего звена между моделью и представлением. Проблема в том, что модель в «настоящей» MVC (с пассивной Моделью) определяет всю бизнес-логику. Например валидацию формы мы делали в контролёре, хотя это задача модели. То есть модель должна принимать и обрабатывать все данные, а отдавать контролёру только «голый» результат.
В CodeIgniter модель жестко завязана на базу данных (как и во многих других php-фреймворках). С его точки зрения, модель содержит бизнес-логику, но только совместно с базой данных. Стандартный класс модели содержит множество методов для основных операций с БД, хотя в «настоящей» MVC модель может быть чем угодно и нормально работать без базы.
Однако при желании, можно организовать свой вариант MVC, который будет больше соответствовать «классическому» варианту. Рассмотрим этот вариант на примере.
Данный вариант я предлагаю для того, чтобы лучше понять работу CodeIgniter и посмотреть как это может работать в «правильном» исполнении.
Например, пусть в приложении будет несколько модулей. Расположим их в каталоге app/Modules
, каждый в своём подкаталоге (я специально усложняю задачу). Каждый модуль (юнит) будет содержать модель (свою) и представление в подкаталогах. Структура получится такой:
app/ Modules/ Unit1/ View/ View.php Model/ Model.php Unit1.php
В данном случае Unit1.php
— контролёр. Пропишем его в роутинге (example.com/unit1):
$routes->add('unit1', 'App\Modules\Unit1\Unit1::index');
Вначале просто проверим его работу. Файл Unit1.php
namespace App\Modules\Unit1; class Unit1 extends \CodeIgniter\Controller { public function index() { $data['title'] = 'Unit1'; $data['header'] = 'Unit1'; echo view('template/start', $data); echo view('about', $data); echo view('template/end', $data); } }
Пока мы использовали функцию view()
для того чтобы вывести данные. К сожалению, эта функция жестко завязана на каталог app/Views
, поэтому, чтобы подключить «вьюшку» из другого каталога нужно самостоятельно инициализировать класс View. Это не сложно. Также мы сразу создадим конструктор контролёра, чтобы единожды инстанцировать класс View.
namespace App\Modules\Unit1; class Unit1 extends \CodeIgniter\Controller { protected $view; public function __construct() { $this->view = new \CodeIgniter\View\View('', APPPATH . 'Modules/Unit1/View/'); } public function index() { $data['title'] = 'Unit1'; $data['header'] = 'Unit1'; $this->view->resetData(); $this->view->setData($data); echo $this->view->render('view'); } }
В конструкторе мы указываем путь к каталогу «вьюшек». Дальше мы просто выполняем методы View.
Файл app/Modules/Unit1/View/View.php
<!doctype html> <html><head> <title><?= esc($title) ?></title> </head> <body> <h1><?= $header ?></h1> </body> </html>
Он обрабатывается точно также как и любые другие «вьюшки».
Теперь перенесем получение данных в файл модели. Это будет наш самостоятельный класс, не завязанный на предлагаемую модель CodeIgniter. Файл app/Modules/Unit1/Model/Model.php
namespace App\Modules\Unit1\Model; class Model { public function getData() { $data['title'] = 'Unit1. Model data'; $data['header'] = 'Unit1. Мodel data'; return $data; } }
Поскольку мы используем автозагрузку классов, то указываем соответствующий namespace. Файл контролёра при этом меняется, чтобы использовать нашу модель.
namespace App\Modules\Unit1; class Unit1 extends \CodeIgniter\Controller { protected $view; protected $model; public function __construct() { $this->view = new \CodeIgniter\View\View('', APPPATH . 'Modules/Unit1/View/'); $this->model = new Model\Model; } public function index() { $data = $this->model->getData(); $this->view->resetData(); $this->view->setData($data); echo $this->view->render('view'); } }
Понятно, что подобный вариант позволяет организовать любую структуру каталогов и файлов. Например здесь мы расширяем стандартный класс View, хотя можно как и модель использовать свой вариант вывода.
Такой подход может пригодится для проектов, которые предполагают модульное расширение. В таких случаях удобно держать все файлы модуля в одном месте.
Познавательно, спасибо!
Именно эта договоренность - признак (правило) PSR-4, т.к. для PHP это не имеет значения.
Верно? Я просто пытаюсь для себя расставить акценты.
Ну да. По имени файла сразу понятно к какому классу он относится.
Отлично! Жду следующий цикл.
Максим, а как Вы сравните codeigniter со Slim framework? Типа тоже микрофреймворк..
Slim — это совсем уже микро-микро-фреймворк. В нём по сути только роутер. Всё остальное нужно прикручивать самостоятельно. Так что сравнивать просто нечего.