...

воскресенье, 23 февраля 2014 г.

Yii обмен опытом: модели (окончание)



Продолжение предыдущего поста



Область ответственности модели и ее место в проекте



Модели предназначены только для оперирования данными (поиск, сохранение, удаление), а также связанными статическими файлами, так как они тоже неотъемлемая часть структуры данных (фотография, видеоролик). Естественно что при удалении записи, связанные с ней медиафайлы должны удаляться, для этого удобно использовать 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);
}

}






Пример виджета
class UserAvatar extends CWidget

{

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 и настроить один из маршрутов для своей модели, лог можно вести как в базу (осторожно, снижает производительность) так и в простые файлы. Также в лог полезно писать данные пользователя от имени которого совершается операция, это упростит разбор полетов.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий