CodeIgniter 4. PSR-4. Произвольная MVC

По умолчанию 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, хотя можно как и модель использовать свой вариант вывода.

Такой подход может пригодится для проектов, которые предполагают модульное расширение. В таких случаях удобно держать все файлы модуля в одном месте.

ps Это последняя статья первого цикла о CodeIgniter 4. В следующем цикле рассмотрим другие возможности CodeIgniter.

Комментариев: 1 RSS

Оставьте комментарий!

Комментарий будет опубликован после проверки. Вы соглашаетесь с правилами сайта.

(обязательно)