Сайт вебмастера

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

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

Related Posts
Comments (5) RSS
1 Аноним 2020-02-09 10:57:58

Познавательно, спасибо!


2 Виктор 2020-05-26 14:03:44
Сам же файл класса всегда совпадает с именем класса (с учетом регистра).

Именно эта договоренность - признак (правило) PSR-4, т.к. для PHP это не имеет значения.

Верно? Я просто пытаюсь для себя расставить акценты.


3 Admin 2020-05-26 15:34:22 admin

Ну да. По имени файла сразу понятно к какому классу он относится.


4 Владимир 2021-03-13 07:25:25

Отлично! Жду следующий цикл.

Максим, а как Вы сравните codeigniter со Slim framework? Типа тоже микрофреймворк..


5 Admin 2021-03-13 08:36:38 admin

Slim — это совсем уже микро-микро-фреймворк. В нём по сути только роутер. Всё остальное нужно прикручивать самостоятельно. Так что сравнивать просто нечего.