MAXSITE.ORGКак сделать сайт своими руками

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

17-08-2019Время чтения ~ 5 мин.CodeIgniter 10069

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

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

Похожие записи
Комментарии (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 — это совсем уже микро-микро-фреймворк. В нём по сути только роутер. Всё остальное нужно прикручивать самостоятельно. Так что сравнивать просто нечего.

Оставьте комментарий!
Сайт использует cookie
Для улучшения работы сайта собирается анонимная статистика через Google Analytics. Разрешить использование аналитических cookie?