0 follower

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

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

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

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

1. Изменение метода 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(). Подробнее это описано в разделе Безопасное присваивание значений атрибутам.

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

2. Изменение метода 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;

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

3. Добавляем свойство 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 будет вызван геттер и мы получим результат его выполнения. Более подробно это описано в полном руководстве.

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

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

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

Found a typo or you think this page needs improvement?
Edit it on github !