В сети очень мало информации по созданию административных страничек в приложениях и наверно каждый использует свои решения. Именно сейчас хотел бы рассказать к чему я дошел за пол года разработки сайтов на Yii (статья только для тех, кто разбирается во фремворке).
При постановке цели всегда следует подумать, что действительно нужно. В случает с backend-частью сайта это:
Думаю многие разработчики, которые генерировали CRUD с помощь встроенного генератора Yii, знают, что создается практически одинаковый код, что раздражает и немного противоречит принципам ООП. Дополнительно к этому можно сказать, что действия Create и Update различаются только наличием id (у новой записи id ещё не создан), поэтому можем исключить действие Create.
В данный момент единственное обобщение данных, которое я использую — это статус “В корзине” для записи в таблице. Если рассказать об этом очень кратко, то получится, что почти во всех таблицах у меня есть поле status (tinyint, index) со значениями 0 — обычное и 1 — удаленное состояние.
Наверно во всех правилах есть исключения и их мы должны обязательно предусмотреть их решение. В случае с backend это возможность переопределить любое стандарное поведение базового функционала.
Вackend является дополнительным функционалом и должен безболезненно удалятся из приложения. В моём случае это безболезненное удаление одноименного модуля.
1. Создаем модуль «backend» стандартными средствами Gii (я не буду это подробно описывать, для этого есть официальная документация).
2. Создаем базовую модель, которая в умеет случае умеет работать с корзиной.
3. Создаем базовый контроллер для CRUD.
4. Создаем базовое действие, которое «догадывается» с какой моделью и представлением работает (обычно не комментирую код, но тут специально для статьи сделал несколько комментариев).
5. Создаем базовое действие List.
6. Создаем базовое действие Update.
6. Создаем базовое действие Delete.
7. Создаем базовое действие Restore(восстановление).
Для проверки всего вышенаписанного создадим модель и контроллер Article.
В простейшем варианте получается, что для создание нового CRUD нужно создать пустой контроллер и два представления (list, update). Если нужно дополнительный функционал просто его дописываем или переписываем текущий.
Я не буду описывать код представлений, ибо он скорей всего будет уникален для каждого разработчика, да и его можно взять из стандартного CRUD.
При постановке цели всегда следует подумать, что действительно нужно. В случает с 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.