CodeIgniter 4. PSR-4. Произвольная MVC
17-08-2019Время чтения ~ 5 мин.CodeIgniter 9393
По умолчанию 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 — это совсем уже микро-микро-фреймворк. В нём по сути только роутер. Всё остальное нужно прикручивать самостоятельно. Так что сравнивать просто нечего.