Идея для архитектуры приложения на yii. Часть 1. Введение и конфиги

Добрый день. Сегодня я хотел бы поделиться простым рецептом для архитектуры приложений малого и среднего размера на Yii. Идея навеяна разработчиком 2GIS.API Алексеем Спиридоновым. Видео с конференции: www.youtube.com/watch?v=GyX5uAnApKg&feature=plcp

Основная проблема начинающих разработчиков в том, что они не понимают принципа MVC. Приведу толкование MVC с точки зрения википедии в миллионный раз:
Model-view-controller (MVC, «Модель-представление-поведение», «Модель-представление-контроллер») — схема использования нескольких шаблонов проектирования, с помощью которых модель данных приложения, пользовательский интерфейс и взаимодействие с пользователем разделены на три отдельных компонента так, что модификация одного из компонентов оказывает минимальное воздействие на остальные.

Итак, что мы должны сделать согласно этой схеме? Правильно, отделить логику от представления. Но это еще не все, логику приложения необходимо разделить на сервисные слои. В этом и заключается вся сила mvc.

Для начала определим, что же должен делать каждый слой MVC.

M. Модель
Модель данных. То есть все что у нас хранится в базах данных (mysql, sqlite, couch, redis и тд). Работа с данными происходит только внутри модели.

V. Представление
Представление данных. Сценарии, описывающие, как данные моделей будут представлены конечному пользователю. Это может быть html страница, json массив, qr код и т.д.

C. Контроллер
Это сердце приложение. Контроллер определяет как ведёт себя приложение. Какие модели и представления использует. Тут-то чаще всего и городят разработчики полную ересь. Начинают обновлять данные в базе данных, генерируют html сущности, и это только самые безобидные примеры.

Собственно основная мысль архитектуры
Для своих приложений я придумал универсальную структуру. Т.к. у приложения зачастую есть администраторская часть, то должно быть 2 точки входа. Для пользователя (frontend) и для администратора (backend).

Директорию protected обязательно выносим за пределы document root. Стандартная структура выглядит следующим образом:
  • /framework/ — код фреймворка Yii
  • /www/index.php — точка входа обычного пользователя
  • /www/backend.php — точка входа администратора
  • /protected/assets/ — статические файлы приложения (js,css, изображения)
  • /protected/components/ — компоненты, расширяющие стандартные компоненты Yii (например CClientScript, CHttpRequest, CHtml, CWebUser и т.д.)
  • /protected/config/main.php — основной конфиг для всех приложений
  • /protected/config/frontend.php — конфиг фронтэнда (наследуется от конфига main)
  • /protected/config/backend.php — конфиг бекенда (наследуется от конфига main)
  • /protected/controllers/frontend/ — контроллеры фронтэнда
  • /protected/controllers/backend/ — контроллеры бекенда
  • /protected/forms/frontend/ — модели форм для фронтэнда
  • /protected/forms/backend/ — модели форм для бекенда
  • /protected/helpers/ — вспомогательные классы
  • /protected/services/ — cервисы приложения. Тут вся магия. Об этом в следующей части.
  • /protected/vendors/ — модули и скрипты от сторонних разработчиков (например phpmailer, twitteroauth и т.д.)
  • /protected/views/emails/ — шаблоны писем почтовых уведомлений
  • /protected/views/frontend/ — шаблоны фронтэнда
  • /protected/views/backend/ — шаблоны бекенда
  • /protected/widgets/ — виджеты

Точки входа
У нашего приложения есть 2 точки входа. Они стандартные. Различаются только подключаемыми конфигами. Код:
<?

// для фронтэнда
$configFile = 'frontend.php';
// для бекенда
$configFile = 'backend.php';

$yii = realpath(__DIR__ . '/../framework/YiiBase.php');
$config = realpath(__DIR__ . '/../protected/config/' . $configFile);

defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', 3);

require_once($yii);

class Yii extends YiiBase
{
	// тут можно добавить свои любимые методы, я например добавляю сюда вызов ошибки 404
	/**
	 * @throws CHttpException
	 */
	public static function error_404()
	{
		throw new CHttpException(404, Yii::t('error', 'Страница не найдена.'));
	}
}

Yii::createWebApplication($config)->run();


Конфиги
Конфиги так же в основном схожи, их разделение позволяет настраивать фронтэнд и бекенд с определёнными различиями. Пример config/frontend.php
<?

return CMap::mergeArray(
	include(__DIR__ . '/main.php'),
	array(
		'controllerPath' => realpath(__DIR__ . '/../controllers/frontend'),
		'viewPath' => realpath(__DIR__ . '/../views/frontend'),
		'defaultController' => 'site',
		'import' => array(
			'application.forms.frontend.*',
		),
		'components' => array(
			'sMail' => array(
				'viewsPath' => 'application.views.frontend.emails'
			),
			'urlManager' => array(
				'urlFormat' => 'path',
				'showScriptName' => false,
				'rules' => array(
					'comment/add' => 'comment/add',
					'<id:\w+>' => 'page/show',
				),
			),
			'errorHandler' => array(
				'errorAction' => 'site/error',
			),
		)
	)
);


Пример config/backend.php
<?

return CMap::mergeArray(
	include(__DIR__ . '/main.php'),
	array(
		'controllerPath' => realpath(__DIR__ . '/../controllers/backend'),
		'viewPath' => realpath(__DIR__ . '/../views/backend'),
		'defaultController' => 'dash',
		'import' => array(
			'application.forms.backend.*',
			'application.helpers.bootstrap.*',
			'application.helpers.bootstrap.widgets.*',
		),
		'modules' => array(
			'gii' => array(
				'class' => 'system.gii.GiiModule',
				'password' => YII_DEBUG ? '1' : '*********',
				'ipFilters' => array('127.0.0.1', '::1'),
			),
		),
		'components' => array(
			'sBackup' => array(
				'class' => 'BackupService',
				'backups_path' => realpath(__DIR__ . '/../../backups')
			),
			'errorHandler' => array(
				'errorAction' => 'dash/error',
			),
			'urlManager' => array(
				'baseUrl' => '/backend.php',
				'urlFormat' => 'get',
			),
		)
	)
);


На этом пока что остановлюсь. Продолжение в следующей части.

8 комментариев

avatar
Когда вторая часть будет уже?
avatar
rmrevin , почему не используешь YiiBoilerplate?

Возможно он немного перегружен для работы с маленькими проектами, но когда в бэкенде надо сделать что-нибудь сложнее CRUD, имхо, он вполне оправдывает свое предназначение.

И, плюсадин, когда вторая часть?
avatar
Действительно, бойлерплейт слишком сложен для малых проектов. Да и в средних тоже бывает излишен. Мне в нем не понравилась слишком глубокая иерархия директорий. В моей архитектуре сущности (контроллеры модели вьюхи) не так сильно удалены друг от друга. Когда над фронтендом и бекендом работает один и тот же человек|команда, это намного ускоряет внедрение изменений в разных частях системы.

Но все же если у Вас крупный проект, с командой больше одного программиста, я бы посоветовал бойлерплейт.
avatar
Я в своих проектах последнее время стал вешать админку на поддомен, тем самым немного уменьшая вероятность попадания «злоумышленником» в точку входа бекенд приложения. В вашем варианте это можно реализовать, фактически разделив корни приложений на уровне файловой системы.
www/backend/index.php
avatar
Я изначально так делал, но почему-то от этого отказался. Точно не вспомню сейчас почему. Ну и все таки никто не мешает реврайтить поддомен на backend.php и запретить прямой вход.
avatar
П.с. по поводу второй части. Она завалялась в черновиках, в ближайшее время постараюсь дописать её и опубликовать.
avatar
Действительно неплохо. Знаком с boilerplate и еще парочкой шаблонов, но твой – очень удобен для небольших проектов с разделением backend/frontend.
avatar
А какие есть ещё «парочка шаблонов»? На самом деле никогда не интересовался данным вопросом.

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