Pull to refresh

Backend в проекте на Yii

Reading time 4 min
Views 13K
В сети очень мало информации по созданию административных страничек в приложениях и наверно каждый использует свои решения. Именно сейчас хотел бы рассказать к чему я дошел за пол года разработки сайтов на Yii (статья только для тех, кто разбирается во фремворке).

При постановке цели всегда следует подумать, что действительно нужно. В случает с backend-частью сайта это:
  • Минимум кода и как следствие некоторое обобщение данных
  • Возможность расширения для особенных разделов
  • Отсутствие прямой связи frontend и backend частей сайта


Минимум кода

Думаю многие разработчики, которые генерировали CRUD с помощь встроенного генератора Yii, знают, что создается практически одинаковый код, что раздражает и немного противоречит принципам ООП. Дополнительно к этому можно сказать, что действия Create и Update различаются только наличием id (у новой записи id ещё не создан), поэтому можем исключить действие Create.

Некоторое обобщение данных

В данный момент единственное обобщение данных, которое я использую — это статус “В корзине” для записи в таблице. Если рассказать об этом очень кратко, то получится, что почти во всех таблицах у меня есть поле status (tinyint, index) со значениями 0 — обычное и 1 — удаленное состояние.

Возможность расширения для особенных разделов

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

Отсутствие прямой связи frontend с backend

Вackend является дополнительным функционалом и должен безболезненно удалятся из приложения. В моём случае это безболезненное удаление одноименного модуля.

Практика в 7 шагов


1. Создаем модуль «backend» стандартными средствами Gii (я не буду это подробно описывать, для этого есть официальная документация).
2. Создаем базовую модель, которая в умеет случае умеет работать с корзиной.
abstract class TModel extends CActiveRecord
{
	const STATUS_DEFAULT = 0;
	const STATUS_REMOVED = 1;
	
	public function defaultScope()
	{
		return array(
			'condition' => 'status=' . self::STATUS_DEFAULT
		);
	}
	
	public function removed()
	{
		$this->resetScope()->getDbCriteria()->mergeWith(array(
			'condition' => 'status=' . self::STATUS_REMOVED
		));
		
		return $this;
	}
	
	public function restore()
	{
		if($this->getIsNewRecord())
			throw new CDbException(Yii::t('yii','The active record cannot be deleted because it is new.'));
		
		if($this->status != self::STATUS_REMOVED)
			return false;
		
		$this->status = self::STATUS_DEFAULT;
		$this->save(false, array('status'));
		
		return true;
	}
	
	public function beforeDelete()
	{
		if($this->status == self::STATUS_DEFAULT)
		{
			$this->status = self::STATUS_REMOVED;
			$this->save(false, array('status'));
		
			return false;
		}
		
		return parent::beforeDelete();
	}
}

3. Создаем базовый контроллер для CRUD.
class BackendController extends CController
{
	public $defaultAction = 'list';

	public function actions()
	{
		return array(
			'list' => 'backend.actions.ListAction',
			'update' => 'backend.actions.UpdateAction',
			'delete' => 'backend.actions.DeleteAction',
			'restore' => 'backend.actions.RestoreAction',
		);
	}
}

4. Создаем базовое действие, которое «догадывается» с какой моделью и представлением работает (обычно не комментирую код, но тут специально для статьи сделал несколько комментариев).
abstract class BackendAction extends CAction
{
	private $_modelName;
	private $_view;
	
	/**
	 * Упрощенная переадресация по действиям контроллера
	 * По-умолчанию переходим на основное действие контроллера
	*/
	public function redirect($actionId = null)
	{
		if($actionId === null)
			$actionId = $this->controller->defaultAction;
		
		$this->controller->redirect(array($actionId));
	}

	/**
	 * Рендер представление.
	 * По-умолчанию рендерим одноименное представление
	*/
	public function render($data, $return = false)
	{
		if($this->_view === null)
			$this->_view = $this->id;
		
		return $this->controller->render($this->_view, $data, $return);
	}
	
	/**
	 * Возвращаем новую модель или пытаемся найти ранее
	 * созданную запись, если известен id
	*/
	public function getModel($scenario = 'insert')
	{
		if(($id = Yii::app()->request->getParam('id')) === null)
			$model = new $this->modelName($scenario);
		else if(($model = CActiveRecord::model($this->modelName)->resetScope()->findByPk($id)) === null)
			throw new CHttpException(404, Yii::t('base', 'The specified record cannot be found.'));
		
		return $model;
	}
	
	/**
	 * Возвращает имя модели, с которой работает контроллер
	 * По-умолчанию имя модели совпадает с именем контроллера
	*/
	public function getModelName()
	{
		if($this->_modelName === null)
			$this->_modelName = ucfirst($this->controller->id);
		
		return $this->_modelName;
	}
	
	public function setView($value)
	{
		$this->_view = $value;
	}
	
	public function setModelName($value)
	{
		$this->_modelName = $value;
	}
}

5. Создаем базовое действие List.
class ListAction extends BackendAction
{
	public function run($showRemoved = null)
	{
		$model = $this->getModel('search');

		if($showRemoved !== null)
			$model->removed();
		
		if(isset($_GET[$this->modelName]))
			$model->attributes = $_GET[$this->modelName];
		
		$this->render(array(
			'model' => $model,
			'showRemoved' => $showRemoved,
		));
	}
}

6. Создаем базовое действие Update.
class UpdateAction extends BackendAction
{
	public function run()
	{
		$model = $this->getModel();
		
		if(isset($_POST[$this->modelName]))
		{
			$model->attributes = $_POST[$this->modelName];
			
			if($model->save())
				$this->redirect();
		}
		
		$this->render(array('model' => $model));
	}
}

6. Создаем базовое действие Delete.
class DeleteAction extends BackendAction
{
	public function run()
	{
		$this->getModel()->delete();
		$this->redirect();
	}
}

7. Создаем базовое действие Restore(восстановление).
class RestoreAction extends BackendAction
{
	public function run()
	{
		$this->getModel()->restore();
		$this->redirect();
	}
}


Проверка


Для проверки всего вышенаписанного создадим модель и контроллер Article.
class Article extends TModel
{
	public static function model($className=__CLASS__)
	{
		return parent::model($className);
	}

	public function tableName()
	{
		return 'article';
	}
	
	public function rules()
	{
		return array(
			array('name, content, createTime', 'required'),
		);
	}
}

class ArticleController extends BackendController
{
	//Сейчас все довольно стандартно, поэтому ничего более не пишем
}


Итог


В простейшем варианте получается, что для создание нового CRUD нужно создать пустой контроллер и два представления (list, update). Если нужно дополнительный функционал просто его дописываем или переписываем текущий.

Примечание


Я не буду описывать код представлений, ибо он скорей всего будет уникален для каждого разработчика, да и его можно взять из стандартного CRUD.
Tags:
Hubs:
+28
Comments 10
Comments Comments 10

Articles