
Продолжение предыдущего поста
Область ответственности модели и ее место в проекте
Модели предназначены только для оперирования данными (поиск, сохранение, удаление), а также связанными статическими файлами, так как они тоже неотъемлемая часть структуры данных (фотография, видеоролик). Естественно что при удалении записи, связанные с ней медиафайлы должны удаляться, для этого удобно использовать beforeDelete() или afterDelete().
Несложную бизнес-логику можно реализовать в модели, для сложной — лучше использовать сервисную прослойку (Service Layer) так вы избавите модель от массы зависимостей и не будете раздувать из нее божественный объект.
В моделях не должны появляться:
- HTML-верстка, CSS, javascript
- Суперглобальные массивы ($_GET, …)
- Глобальные переменные
- Проверка прав доступа (этот момент, возможно, спорный, практика показывает что контроль доступа к операции нужно проводить в контроллере)
- Текстовые строки не обернутые в Yii::t()
Модели должны иметь как можно меньше зависимостей от других компонентов, так мы сможем сделать ее переносимой, и использовать в других проектах. При этом компоненты от которых модель зависит должны устанавливаться сеттерами (например User::setEmailComponent(’email’)). Назначать компоненты можно и в User::init() но при этом обязательно сделать проверку на его наличие. Если компонент — второстепенный и недоступен — модель обязана работать без шума, однако если компонент первостепенный, то на стадии инициализации следует выбросить прерывание.
Эти несложные правила позволят поддерживать ваш код командой программистов, и достаточно гибко расширять функционал.
Проверка состояния статуса
Обычно проверку делают в разных местах, как в контроллерах так и в представлениях и проверка часто выглядит так:
// простой вариант if($model->status == 1){ ... } // более продвинутый вариант if($model->getStatus() === Model::STATUS_ACTIVE){ ... } // страшный вариант if($model->status !== 1 && $model->status !== 2 && $model->status !== 4){ ... } //да-да-да бывает и такое
Однако если создать метод isActive() который делает эту проверку, то код станет более контролируемым, особенно в условиях часто меняющегося ТЗ. Также иногда полезным окажется создание метода, в котором описана проверка на возможность перехода в иное состояние статус. Например: если запись имеет статус — «Спам», то перевести ее в статус «Активна» нельзя, однако ее можно перевести в статус «На проверку». Всю эту логику желательно размещать также в моделях. (Это может быть полезно при построении workflow систем)
Использование флаговых полей
Для экономии размера таблицы (числа колонок) и для того чтобы иметь более гибкий функционал расширения модели используют поля с битовыми флагами.
Такие поля как правило представлены в базе типом integer (но необязательно) и способны разместить довольно большое число флагов. Флаговые поля я уже затронул в предыдущей статье. Теперь покажу практическое применение. (Детально битовые маски описаны в этой статье).
Пример: Есть модель пользователя в которой для пользователя можно назначить несколько одновременных флагов — «лучший автор», «подтвержден телефон», «подтвержден email», «оставил запрос на пост модератора»
public $flags = 0; const FLAG_CONFIRM_EMAIL = 1; // 00000001 const FLAG_CONFIRM_PHONE = 2; // 00000010 const FLAG_BEST_AUTHOR = 4; // 00000100 const FLAG_BECOME_MODERATOR = 8; // 00001000 /** * Устанавливает флаг * @param integer $flag * @return \User */ public function setFlag($flag) { $this->flag += intval($flag); return $this; } /** * Снимает флаг * @param integer $flag * @return \User */ public function unsetFlag($flag) { $this->flag -= intval($flag); return $this; } /** * Проверяет наличие флага * @param integer $flag * @return boolean */ public function hasFlag($flag) { return ($this->flags & intval($flag)); } /** * Возвращает человеческие названия флагов * @return array */ public function getFlagsLabels(){ return array( self::FLAG_CONFIRM_EMAIL => Yii::t('User',"Email подтвержден"), self::FLAG_CONFIRM_PHONE => Yii::t('User',"Телефон подтвержден"), self::FLAG_BEST_AUTHOR => Yii::t('User',"Лучший автор"), self::FLAG_BECOME_MODERATOR => Yii::t('User',"Заявка на пост модератора"), ); } /** * Используется для выборки по флагам * @param integer $flag * @return \User */ public function withFlags($flag=0) { $this->getDbCriteria()->mergeWith(array( 'order'=>'flags & :flag', 'params' => array(':flag' => $flag), )); return $this; }
В запросе MySQL проверка примет вид:
SELECT * FROM `User` WHERE STATUS & 6
В примере создана именованная группа условий это позволит производить поиск как показано ниже:
User::model()->withFlags(User::FLAG_CONFIRM_EMAIL)->findAll();
Реляционные связи
Реляционные связи хорошо описаны в официальной документации.
На практике возникают такие нюансы.
- Если реляционная связь построена правильно с использованием FK то все должно работать четко, так как целостность данных обеспечена БД.
- Если реляционная связь построена только средствами YII ( и нет возможности использовать FK), в этом случае не следует использовать прямой доступ к переменной обозначающей связь, вместо этого следует использовать CActiveRecord::getRelated().
- Проверку правильности связей (правильные ID) необходимо контролировать при валидации и в beforeSave() обработчике.
- Удаление связанных данных необходимо реализовать в beforeDelete() обработчике.
Представления для атрибутов
В коде моделей иногда встречается HTML-код — ему там не место. Если есть необходимость вывести HTML-форматированный вид атрибута для этого следует использовать или виджет или создать хелпер для модели (класс со статическими методами). примеры показаны ниже.
class UserHelper{ /** * Возвращает гиперссылку на профиль пользователя * @return string */ public static function getProfileLink(User $model, $htmlOptions = array()) { return CHtml::link($model->getUserName(),$model->getProfileUrl(),$htmlOptions); } }
{
public $emptyPhotoUrl = "/static/images/no-photo.png";
public $model;
public $htmlOptions;
public function run(){
$avatar = $this->emptyPhotoUrl;
if($model->hasAvatar())
{
$avatar = $model->getAvatarUrl();
}
echo CHtml::image($avatar,$model->getUserName(),$this->htmlOptions);
}
}
Виджет и хелпер удобно использовать в представлениях, хелпер удобен при использовании внутри виджета CGridView.
Журналирование моделей
Для отладки моделей, особенно для отлова трудновоспроизводимой ошибки иногда полезно иметь возможность узнать хронологию событий.
Для этого в модель можно добавить методы журналирования, и на все операции производимые с данными вести журнал (лог-файл). Реализация этой несложной механики может быть любой, однако полезно вспомнить про компонент CLogRoute и настроить один из маршрутов для своей модели, лог можно вести как в базу (осторожно, снижает производительность) так и в простые файлы. Также в лог полезно писать данные пользователя от имени которого совершается операция, это упростит разбор полетов.
ссылка на оригинал статьи http://habrahabr.ru/post/212917/
Добавить комментарий