Пользователь
0,0
рейтинг
14 апреля 2011 в 11:12

Разработка → Способ разделения frontend/backend-частей в Yii из песочницы

Yii*

О чем это я?


Хочу немного рассказать, как я разделяю фронтэнд/бекэнд-части сайта во всех своих проектах на Yii. Не претендую на авторство этого способа, просто хочу немного объяснить, что и как организуется и работает.

Собственно, почти всегда проект не может состоять только из фронтэнда (тоесть того, что видит посетитель). Очень часто требуется организовать административную часть, при этом иметь безболезненный доступ ко всем компонентам проекта (модели, расширения и т.п.).

Есть несколько способов добиться этого. Не буду вдаваться в подробности каждого из них, если вам интересно — можете поискать на форуме yiiframework.com.
Самым элегантным и удобным мне показался вариант с использованием своего “поведения” запуска приложения. Он позволяет очень удобно разграничить приложение в плане директорией, использовать разные конфиги для каждой части сайта и т.п.


Если вы знакомы с Yii, то должны знать, что в нем реализованы так называемые behaviors, которые позволяют изменять или дополнять функционал и поведение разных компонентов. Чаще всего они применяются с связке с моделями, но в нашем случае предлагается использовать поведения непосредственно для старта приложения.

Директории


Сначала давайте определимся с директориями. Сойдемся на том, что контроллеры пользовательской части сайта (фронтэнд) хранятся в папке “protected/controllers/frontend”. Логично, что контроллеры админки мы будем хранить в папке “protected/controllers/backend”.
Тоже самое с отображениями. Для фронтэнда — это папка “protected/views/frontend”, для бэкенда — “protected/views/backend”.
На деле это выглядит примерно так:
image

Контроллеры


Очевидно, что мы не хотим, чтобы контроллеры нашей админки работали также, как внешние контроллеры. Например, мы хотим поставить в админке фильтры доступа только для определенных ролей пользователей.
Чтобы не разводить лапшы и не повторяться в каждом контроллере с фильтрами (DRY же!) — создадим промежуточные контроллеры — FrontEndController и BackEndController, которые унаследуем от базового контроллера и поместим их в папку “protected/components”.

Выглядить эти контроллеры должны примерно так:

FrontEndController.php:
class FrontEndController extends BaseController
{
	// лейаут
	public $layout = 'application';
		
	// меню
	public $menu = array();
	
	// крошки
	public $breadcrumbs = array();
}


BackEndController.php:
class BackEndController extends BaseController
{

	// лейаут
	public $layout = 'application';
		
	// меню
	public $menu = array();
	
	// крошки
	public $breadcrumbs = array();
	
	
	/*
		Фильтры
	*/
	public function filters()
	{
		return array(
			'accessControl',
		);
	}
	
	
	/*
		Права доступа
	*/
	public function accessRules()
	{
		return array(
			// даем доступ только админам
			array(
				'allow',
				'roles'=>array('admin'),
			),
                                    // всем остальным разрешаем посмотреть только на страницу авторизации
			array(
				'allow',
        'actions'=>array('login'),
        'users'=>array('?'),
      ),
			// запрещаем все остальное
			array(
				'deny',
				'users'=>array('*'),
			), 
		);		
	}
}


Вообщем-то они идентичны, разве что BackEndController реализует фильтры, которые скрывают от любопытных глаз наш бекэнд, разрешая лишь попытаться пройти авторизацию. При этом они оба могут обладать общим функционалом и поведением, который при необходимости можно создать в BaseController, который они наследуют.

Лично я в BaseController делаю удобные шорткаты для разных ф-ий, которые очень часто используются:

BaseController.php:
class BaseController extends CController
{
	
	// флеш-нотис пользователю
	public function setNotice($message)
	{
		return Yii::app()->user->setFlash('notice', $message);		
	}
	
	// флеш-ошибка пользователю
	public function setError($message)
	{
		return Yii::app()->user->setFlash('error', $message);		
	}
	
}


Ну а дальше все понятно — все контроллеры, которые лежат в “protected/controllers/backend” — наследуют BackEndController. Те, что в “protected/controllers/frontend”FrontEndController.

Поведение


Теперь непосредственно расскажу, как же все это будет работать. Для этого мы создаем в папке “protected/behaviors” новое поведение — WebApplicationEndBehavior. Код его достаточно, как говорится, straightforward. Но в комментариях все-таки поясню, что и зачем.

WebApplicationEndBehavior.php
class WebApplicationEndBehavior extends CBehavior 
{

	// имя нужной нам части сайта
	private $_endName;
	
	// геттер $_endName;
	public function getEndName()
	{
		return $this->_endName;
	}
	
	// запуск приложения
	public function runEnd($name)
	{
		$this->_endName = $name;
		
		// обрабатываем событие создания модуля
		$this->onModuleCreate = array($this, 'changeModulePaths');
		$this->onModuleCreate(new CEvent ($this->owner));
		
		$this->owner->run();		
	}
	
	// обработчик события onModuleCreate
	public function onModuleCreate($event)
	{
		$this->raiseEvent('onModuleCreate', $event);		
	}
	
	// подменяем пути к файлам
	protected function changeModulePaths($event)
	{
		// добавляем название части сайта (frontend или backend) в путь, по которому фреймворк будет искать контроллеры и вьюшки
		$event->sender->controllerPath .= DIRECTORY_SEPARATOR.$this->_endName;
		$event->sender->viewPath .= DIRECTORY_SEPARATOR.$this->_endName;
	}	
		
}


Весьма просто. Вызов функции runEnd($name) с названием части сайта (frontend/backend) вешает обработчик события onModuleCreate, затем в обработчике мы подменяем путь до контроллеров и отображений, добавив туда нужные нам “суффиксы”.
Осталось только добавить этот компонент в путь, по которому фреймворк будет искать классы для автозагрузки.
В «protected/config/main.php» добавьте:
...
// используемые приложением поведения
'behaviors'=>array(
  'runEnd'=>array(
  'class'=>'application.behaviors.WebApplicationEndBehavior',
  ),
),
...


Конфигурация


Весьма полезным будет отделить наши части сайта не только на уровне размещения папок, но и на уровне конфигурации приложения. Для этого создадим отдельные файлы конфигурации в папке “protected/config”backend.php и frontend.php.
Выглядят они примерно так:
backend.php
return CMap::mergeArray(

	require_once(dirname(__FILE__).'/main.php'),
	
	array(
		
		
		// стандартный контроллер
		'defaultController' => 'posts',
		
		// компоненты
		'components'=>array(
			
			// пользователь
			'user'=>array(
				'loginUrl'=>array('/users/login'),
			),

  		// mailer
  		'mailer'=>array(
    		'pathViews' => 'application.views.backend.email',
    		'pathLayouts' => 'application.views.email.backend.layouts'
  		),

		),
	)
);


С фронтендом точно также.
Вот например так выглядит папка с конфигами у меня:
image

Bootstrap


Теперь нам осталось только настроить наши входные скрипты. Здесь я обычно создаю два файла в корне приложения — index.php и admin.php. Код они содержат следующий:

index.php
// путь до фреймворка и нужного нам конфига
$yii = dirname(__FILE__).'/../yii/framework/yii.php';
$config = dirname(__FILE__).'/protected/config/frontend.php';
 
// включать дебаг?
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', 3);
 
// подключаем фреймворк
require_once($yii);
// стартуем приложение с помощью нашего WebApplicaitonEndBehavior, указав ему, что нужно загрузить фронтенд
Yii::createWebApplication($config)->runEnd('frontend');


admin.php
// путь до фреймворка и нужного нам конфига
$yii = dirname(__FILE__).'/../yii/framework/yii.php';
$config = dirname(__FILE__).'/protected/config/backend.php';
 
// включать дебаг?
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', 3);
 
// подключаем фреймворк
require_once($yii);
// стартуем приложение с помощью нашего WebApplicaitonEndBehavior, указав ему, что нужно загрузить бекэнд
Yii::createWebApplication($config)->runEnd('backend');


Все, теперь обращаясь к http://localhost/yourapp/index.php (или как вас там) — вы попадете на фронтэнд сайта, обращаясь к http://localhost/yourapp/admin.php — приложение загрузит административную часть.

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

Собственно, на этом все, что я хотел вам рассказать по этому очень часто возникающему у многих вопросу.

Вообще, на хабре довольно не важно освещен Yii. Хотя, лично для меня — это явный номер №1 среди php-фреймворков. Так что если вам интересены статьи о Yii — намекните в комментариях.
Alexander Tipugin @tipugin
карма
19,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (52)

  • +3
    Статьи по Yii интересны, давно руки тянутся к этому фреймворку. И еще. Use
    <source lang="php"></source>
    Luke
    • +3
      Спасибо, текст поправил. Обыскался хайлайта =)
    • +3
      Могу вам порекомендовать еще книгу Jeffrey Winesett — Agile Web Application Development with Yii 1.1 and PHP5. В ней достаточно подробно рассматривается работа с Yii на примере создания конкретного веб-приложения, много внимания уделяется TDD.
      • 0
        Про книгу знаю, лежит на винте довольно давно, спасибо за отзыв про нее.
      • 0
        Да, отличнейшая книга!
  • +2
    Спасибо за статью, познавательно. Не подскажите как настроить urlmanager для административной части, чтобы ссылки выглядели например так:
    localhost/yourapp/post/admin/
    • +3
      Можно например в htaccess сделать
      RewriteRule ^admin admin.php


      а дальше в protected/config/backend.php:
      'urlManager'=>array(
          'urlFormat'=>'path',
          'showScriptName'=>false,
          'rules'=>array(
              'admin'=>'admin/index',
              'admin/<_c>'=>'<_c>',
              'admin/<_c>/<_a>'=>'<_c>/<_a>',
          ),
      ),
      
    • 0
      Если yourapp — это контроллер, post — действие, а admin просто для красоты, то правило будет выглядеть так:
      '/<controller>/<action>/admin'=>'admin/<controller>/<action>',

      Это для случая, когда контроллеры админки лежат в поддиректории, как я описал ниже.
  • 0
    Разные базовые классы для фронт- и бэкэнда это правильно. У меня в них еще живут пара полезных методов loadOr404 (если модели с нужным id не нашлось, то 404) и flashAndRedirect (ставим flash сообщение и редиректим).

    А вот рисовать дополнительный behavior не обязательно. Можно без него все разделить. Контроллеры админки (у меня cp, от control panel) кладутся в поддиректорию /controllers/cp/ а контроллеры фронтэнда в корень, т.е. просто в /controllers/. Также и с views. Route, в терминах yii, к админке выглядит так array('cp/controller/action');, что наглядно. Да и в пути к фронтэнду лишних директорий нет.

    Теперь все запросы приходят на общий index.php. Если необходимо разделить конфиги — если у админки много обвеса и правил для маршрутизации, то мысль здравая — можно во входном скрипте по первому сегменту url определять нужный конфиг.

    Я вот еще подумываю у моделей разделить подключаемые поведения на «для админки» и «для фронтэнда», ибо для фронтэнда их надо на порядок меньше, а объекты поведений создаются и инициализируются сразу все, без ленивой загрузки.
    • 0
      Да, можно и без behavior обойтись, это один из вариантов.
    • +1
      а с моделями может проще просто наследовать модели друг от друга? Например для фронтенда — «диетический» Posts с минимумом всего. Для админки — более упитанный AdminPosts.
      • 0
        Да, пожалуй так и попробую.
  • +8
    А чем не подошло решение сделать админскую часть отдельным модулем? На мой взгляд более гибко и удобно. Все в одном месте, у Вас же все будет раскидано по куче папок в корне (views, components и т.д.).
  • +2
    Можно админку вынести на поддомен и сделать в index.php так:
    
    if(($sd = explode('.',$_SERVER['HTTP_HOST'])) && $sd[0] == 'admin')
        Yii::createWebApplication('./protected/config/admin.php')->runEnd('admin');
    else 
        Yii::createWebApplication('./protected/config/www.php')->runEnd('www');
    
    • 0
      Можно даже на веб-сервере скопировать настройки хоста на поддомен и заменить в настройках индекс-файл на admin.php
      • 0
        А если нужно больше приложений? Под каждый поддомен отдельный конфиг и индексный файл? Куда проще две строчки в index.php дописать.
  • 0
    Подход интересный, но он может иметь место если вы не используете сторонние модули в своих проектах. Отказ от огромной базы расширений Yii, имеющих собственный backend все же — не самый верный шаг.

    Хотя, если вы знаете универсальный путь, как подружить такой подход с 3rd party extensions — мне было бы очень интересно почитать.
    • 0
      ну вообще если экстеншен сделан модулем — проблем с ним возникнуть не должно
      • 0
        Должно. К примеру потому что контроллеры backend'а этого модуля не наследуются от предложенного вами BackEndController и не лежат директории /controllers, а не /controllers/backend
        • 0
          Пардон, совсем опечатался еще и Хабр теги скушал, надеюсь можно понять то, что я имел в виду.
          • 0
            да вы правы, с путями возникнут траблы. я просто почти не использую такие расширения (в виде модулей), поэтому особо не натыкался на это.
  • +4
    Вообще-то именно для этого в Yii существуют модули, вариант с выделением backend в отдельный модуль кажется мне более логичным.
    • 0
      Тоже раньше использовал отдельный модуль, но часто неудобно бывает использовать внешние модели и компоненты.
      • 0
        Почему «внешние»? Речь о собственном модуле.
    • 0
      Модуль подразумевает использование собственных моделей, а ведь они и фронтэнду пригодятся.
      • 0
        вот вот
      • +2
        Модели, разделяемые бэкэндом и фронтэндом должны лежать на фронтэнде.
        • 0
          А вот как кршерно сделать такое: админка выделена в отдельный модуль, модели пользует от фронтэнда, но вот контроллеры нужно сделать так, чтобы админский контроллер наследовал фронтэндский. Как это сделать правильно в yii (ибо часть функционала контроллеров совпадает)? Не очень могу понять пока, как это правильно сделать
    • +5
      к тому же, оф. сайт использует схожу структуру, соотв. разработчики фреймворка продпочитают этот подход модулям прочитать можно здесь
      • 0
        А вот это интересно, за ссылку спасибо
      • 0
        По Вашей ссылке другая структура. Несколько отдельных приложений (frontend, backend, console), которые используют общие библиотеки (models и т.д.), которые лежат в common.

        Автор топика другой вариант предлагает.
      • 0
        Интересная заметка, спасибо за ссылку. Я так понял модули там не используются, только разбиение по директориям, так?
        • 0
          именно. хотя конечно там очень поверхносто раскрыли суть, показали только структуру
    • 0
      Соглашаюсь и поддерживаю
  • +1
    (придираюсь) это не фронт/бек—END, а фронт/бек—OFFICE. ENDы — более технические, в то время как OFFICEы — более человеческие. Nginx — техническое, Админка — человеческое :)
    • +2
      согласен, но привычка =)
  • 0
    Этому подходу уже год )
    я как раз столько им пользуюсь, отличный метод.
  • 0
    Статья супер. Советы очень помогли.
  • 0
    С почином, в стане Yiiводов прибыло.
    • 0
      Спасибо :)
      • 0
        Для хороших людей ничего не жалко.
  • 0
    А как при таком разбиении ведет себя Gii? Пользуетесь ним?

    Я пока что использую админку в качестве модуля.
    • 0
      ведет себя отлично, localhost/index.php/gii или localhost/admin.php/gii велкам
      • 0
        Ок. В следующем проекте попробую с такой структурой поработать, посмотрю, насколько удобнее будет.

        Спасибо за статью.
  • 0
    а смысл в бекенде вообще? почему бы не держать управление неким компонентом в контроллере/модуле этого компонента. Я вообще пытаюсь максимум управления выносить в само приложение
    • 0
      ну например часто заказчики просят отделять управление от самого сайта
      • 0
        заказчики часто хотят чегото непонятного. им наверное льстит что у них есть доступ к папочке, куда больше никто не имеет доступ :) я вот не вижу ни одного плюса такого разделнния. Даже редактирование страници можно организовать на самой странице, спасибо contenteditable, и не надо перелогиниваться/переходить в какието бекоффисы. к томуже тогда компонент будет не размазан по всей системе а в идеале лежать в одной папочке.
        • 0
          все зависит от конкретных задач приложения, не надо обобщать
  • +1
    Отличный перевод,
    приятно видеть свой код в статьях хабра :)
  • 0
    Спасибо автору за статью.
    Подскажите пожалуйста один вопросик. Сделал структуру приложения как в статье, однако при заходе localhost/youapp/ мне выбрасывает сообщение.
    Unable to resolve the request "site/error".

    Очевидно бтблиотека не может найти controller site, поскольку в config/main.php указано
    'errorHandler'=>array(
    // use 'site/error' action to display errors
    'errorAction'=>'site/error',
    ),


    Ок, добавил контроллер site в директорию front и изменил вышеупомянутый конфиг так:
    'errorHandler'=>array(
    // use 'site/error' action to display errors
    'errorAction'=>'front/error',
    ),


    Все работает однако получается, что для backenda нужно сделать тоже самое?
    Можно ли как-нибудь для обработок ошибок (ну и возможно для других настроек) все-таки использовать дефалтовый контроллер site?
  • 0
    При необходимости разделить на frontend/backend компоненты можно сделать так:

    /components
    ---/backend
    ---/frontend
    ---/common

    Импорт в конфиге main.php
    'application.components.common.*',

    Импорт в конфиге backend.php
    'application.components.backend.*',

    Импорт в конфиге frontend.php
    'application.components.frontend.*',

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.