- PVSM.RU - https://www.pvsm.ru -
Продолжение предыдущего поста [1]
Модели предназначены только для оперирования данными (поиск, сохранение, удаление), а также связанными статическими файлами, так как они тоже неотъемлемая часть структуры данных (фотография, видеоролик). Естественно что при удалении записи, связанные с ней медиафайлы должны удаляться, для этого удобно использовать beforeDelete() или afterDelete().
Несложную бизнес-логику можно реализовать в модели, для сложной — лучше использовать сервисную прослойку (Service Layer) так вы избавите модель от массы зависимостей и не будете раздувать из нее божественный объект.
В моделях не должны появляться:
Модели должны иметь как можно меньше зависимостей от других компонентов, так мы сможем сделать ее переносимой, и использовать в других проектах. При этом компоненты от которых модель зависит должны устанавливаться сеттерами (например 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 (но необязательно) и способны разместить довольно большое число флагов. Флаговые поля я уже затронул в предыдущей статье. Теперь покажу практическое применение. (Детально битовые маски описаны в этой статье [2]).
Пример: Есть модель пользователя в которой для пользователя можно назначить несколько одновременных флагов — «лучший автор», «подтвержден телефон», «подтвержден 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
В примере создана именованная группа условий [3] это позволит производить поиск как показано ниже:
User::model()->withFlags(User::FLAG_CONFIRM_EMAIL)->findAll();
Реляционные связи хорошо описаны в официальной документации [4].
На практике возникают такие нюансы.
В коде моделей иногда встречается HTML-код — ему там не место. Если есть необходимость вывести HTML-форматированный вид атрибута для этого следует использовать или виджет или создать хелпер для модели (класс со статическими методами). примеры показаны ниже.
class UserHelper{
/**
* Возвращает гиперссылку на профиль пользователя
* @return string
*/
public static function getProfileLink(User $model, $htmlOptions = array())
{
return CHtml::link($model->getUserName(),$model->getProfileUrl(),$htmlOptions);
}
}
public function run(){
$avatar = $this->emptyPhotoUrl;
if($model->hasAvatar())
{
$avatar = $model->getAvatarUrl();
}
echo CHtml::image($avatar,$model->getUserName(),$this->htmlOptions);
}
}
Виджет и хелпер удобно использовать в представлениях, хелпер удобен при использовании внутри виджета CGridView.
Для отладки моделей, особенно для отлова трудновоспроизводимой ошибки иногда полезно иметь возможность узнать хронологию событий.
Для этого в модель можно добавить методы журналирования, и на все операции производимые с данными вести журнал (лог-файл). Реализация этой несложной механики может быть любой, однако полезно вспомнить про компонент CLogRoute и настроить один из маршрутов для своей модели, лог можно вести как в базу (осторожно, снижает производительность) так и в простые файлы. Также в лог полезно писать данные пользователя от имени которого совершается операция, это упростит разбор полетов.
Автор: Geos
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/55680
Ссылки в тексте:
[1] предыдущего поста: http://habrahabr.ru/post/212681/
[2] в этой статье: http://habrahabr.ru/post/134557/
[3] именованная группа условий: http://www.yiiframework.com/doc/guide/1.1/ru/database.ar#sec-11
[4] официальной документации: http://www.yiiframework.com/doc/guide/1.1/ru/database.arr
[5] CActiveRecord::getRelated(): http://www.yiiframework.com/doc/api/1.1/CActiveRecord/#getRelated-detail
[6] Источник: http://habrahabr.ru/post/212917/
Нажмите здесь для печати.