Доработка модели Post

Модель Post, сгенерированная при помощи Gii, нуждается в следующих изменениях:

  • метод rules(): задаёт правила валидации атрибутов модели;
  • метод relations(): задаёт связи с другими объектами.

Информация: Модель состоит из набора атрибутов, каждый из которых ассоциируется с соответствующим полем в таблице БД. Атрибуты могут быть описаны явно как переменные класса, либо использоваться без какого-либо описания.

Изменение метода rules() #

В первую очередь необходимо определить правила валидации, которые позволят убедиться в том, что данные, введённые пользователем, корректны до их сохранения в БД. К примеру, атрибут status модели Post должен быть целым числом, равным 1, 2 или 3. Консоль Gii генерирует правила валидации для каждой модели. При этом используется структура БД, поэтому некоторые правила могут оказаться неточными.

Основываясь на анализе требований, изменим метод rules() следующим образом:

public function rules()
{
    return array(
        array(
'title, content, status''required'),
        array(
'title''length''max'=>128),
        array(
'status''in''range'=>array(1,2,3)),
        array(
'tags''match''pattern'=>'/^[\w\s,]+$/',
            
'message'=>'В тегах можно использовать только буквы.'),
        array(
'tags''normalizeTags'),

        array(
'title, status''safe''on'=>'search'),
    );
}

В коде выше мы определили, что атрибуты title, content и status являются обязательными для заполнения. Длина title не должна превышать 128 символов. Значение status может быть 1 (черновик), 2 (опубликовано) или 3 (в архиве). В tags могут содержаться только буквы, запятые и пробелы. Вводимые пользователем теги дополнительно нормализуются при помощи normalizeTags. Это делается для того, чтобы теги были уникальными и правильно разделялись запятыми. Последнее правило используется поиском и будет описано позже.

Валидаторы, такие как required, length, in и match являются стандартными валидаторами Yii. Валидатор normalizeTags использует определённый в классе Post метод. За дополнительной информацией о том, как описывать правила валидации вы можете обратиться к полному руководству.

public function normalizeTags($attribute,$params)
{
    
$this->tags=Tag::array2string(array_unique(Tag::string2array($this->tags)));
}

где array2string и string2array - новые методы, которые мы должны определить в классе модели Tag:

public static function string2array($tags)
{
    return 
preg_split('/\s*,\s*/',trim($tags),-1,PREG_SPLIT_NO_EMPTY);
}

public static function 
array2string($tags)
{
    return 
implode(', ',$tags);
}

Правила, описанные в методе rules(), вызываются по очереди при вызове методов модели validate() или save().

Примечание: Важно помнить, что атрибуты, описываемые в rules() должны вводиться пользователем. Другие атрибуты модели Post, такие как id или create_time, заполняемые в коде или напрямую в БД не должны присутствовать в rules(). Подробнее это описано в разделе Безопасное присваивание значений атрибутам.

После того, как мы сделали описанные изменения, мы можем зайти на страницу создания записи и проверить, что новые правила валидации работают.

Изменение метода relations() #

Далее укажем в методе relations() связанные с записью объекты. После этого мы сможем использовать реляционную ActiveRecord (RAR) для получения связанных с записью данных, таких как информацию об авторе и комментарии. Сложные SQL запросы с JOIN в этом случае не потребуются.

Определим метод relations():

public function relations()
{
    return array(
        
'author' => array(self::BELONGS_TO'User''author_id'),
        
'comments' => array(self::HAS_MANY'Comment''post_id',
            
'condition'=>'comments.status='.Comment::STATUS_APPROVED,
            
'order'=>'comments.create_time DESC'),
        
'commentCount' => array(self::STAT'Comment''post_id',
            
'condition'=>'status='.Comment::STATUS_APPROVED),
    );
}

Также, в классе модели Comment мы описываем две константы, которые используются в приведённом выше методе:

class Comment extends CActiveRecord
{
    const 
STATUS_PENDING=1;
    const 
STATUS_APPROVED=2;
    ......
}

Связи, описанные в методе relations(), означают следующее:

  • Запись принадлежит автору(User), связь с которым устанавливается на основе поля записи author_id;
  • Запись может содержать много комментариев(Comment), связь с которыми устанавливается на основе поля комментария post_id. Комментарии сортируются по времени их создания.
  • Связь commentCount является особенной так как возвращает результат агрегации, то есть число комментариев записи.

Задав описанные выше связи, мы можем получить информацию об авторе и комментариях к записи следующим образом:

$author=$post->author;
echo 
$author->username;

$comments=$post->comments;
foreach(
$comments as $comment)
    echo 
$comment->content;

Более подробно использование и определение связей описано в полном руководстве.

Добавляем свойство url #

Каждой записи соответствует уникальный URL. Вместо повсеместного вызова CWebApplication::createUrl для формирования этого URL, мы можем добавить свойство url модели Post и повторно использовать код для генерации URL. Позже мы опишем, как получить красивые URL. Использование свойства модели позволит реализовать это максимально удобно.

Для того, чтобы добавить свойство url, мы добавляем геттер в класс Post:

class Post extends CActiveRecord
{
    public function 
getUrl()
    {
        return 
Yii::app()->createUrl('post/view', array(
            
'id'=>$this->id,
            
'title'=>$this->title,
        ));
    }
}

В дополнение к ID записи, в URL через GET-параметр мы выводим заголовок. Делается это главным образом для оптимизации под поисковые алгоритмы (SEO). Подробнее это будет описано в разделе «человекопонятные URL».

Так как CComponent является предком класса Post, геттер getUrl() позволяет нам писать код вроде $post->url. При обращении к $post->url будет вызван геттер и мы получим результат его выполнения. Более подробно это описано в полном руководстве.

Текстовое представление для статуса #

Так как статус записи хранится в БД в виде числа, нам необходимо получить его текстовое представление для отображения пользователям. Для больших систем такое требование является довольно типичным.

Для хранения связей между целыми числами и их текстовым представлением, необходимым другим объектам данных, мы используем таблицу tbl_lookup. Для более удобного получения текстовых данных изменим модель Lookup следующим образом:

class Lookup extends CActiveRecord
{
    


    
private static $_items=array();

    public static function 
items($type)
    {
        if(!isset(
self::$_items[$type]))
            
self::loadItems($type);
        return 
self::$_items[$type];
    }

    public static function 
item($type,$code)
    {
        if(!isset(
self::$_items[$type]))
            
self::loadItems($type);
        return isset(
self::$_items[$type][$code]) ? self::$_items[$type][$code] : false;
    }

    private static function 
loadItems($type)
    {
        
self::$_items[$type]=array();
        
$models=self::model()->findAll(array(
            
'condition'=>'type=:type',
            
'params'=>array(':type'=>$type),
            
'order'=>'position',
        ));
        foreach(
$models as $model)
            
self::$_items[$type][$model->code]=$model->name;
    }
}

Мы добавили два статичных метода: Lookup::items() и Lookup::item(). Первый возвращает список строк для заданного типа данных, второй — конкретную строку для заданного типа данных и значения.

В базе данных блога есть два типа данных: PostStatus и CommentStatus. Первый содержит возможные статусы записи, второй — статусы комментария.

Для того, чтобы сделать код более читаемым мы описываем константы, соответствующие целочисленным значениям статуса. Эти константы необходимо использовать в коде вместо соответствующих им целых значений.

class Post extends CActiveRecord
{
    const 
STATUS_DRAFT=1;
    const 
STATUS_PUBLISHED=2;
    const 
STATUS_ARCHIVED=3;
    ......
}

Следовательно, для получения списка всех возможных статусов записи (массива строк с ключами, равными соответствующим им значениям), мы можем воспользоваться кодом Lookup::items('PostStatus'). А для получения конкретной строки — кодом Lookup::item('PostStatus', Post::STATUS_PUBLISHED).


Зачем реклама?