0 follower

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

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

  • метод rules(): задаёт правила валидации атрибутов модели;
  • метод relations(): задаёт отношения с связанными объектами;
  • метод safeAttributes(): определяет, какие атрибуты могут быть назначены пакетно (в основном используется при передаче пользовательского ввода в модель);

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

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

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

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

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

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

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

Информация: Правила валидации используются при вызове методов модели validate() или save(). За более подробной информацией о правилах валидации обратитесь к полному руководству.

2. Изменение метода safeAttributes()

Перейдём к изменению метода safeAttributes(). Нам необходимо указать, какие атрибуты могут быть назначены пакетно. При передаче пользовательского ввода модели мы часто используем пакетное назначение для того, чтобы упростить код:

$post->attributes=$_POST['Post'];

Без использования данной возможности нам бы пришлось написать:

$post->title=$_POST['Post']['title'];
$post->content=$_POST['Post']['content'];
// и т.д.

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

Для того, чтобы этого не допустить, мы должны разрешить пакетное назначение только атрибутам title, content, status и tags. Для этого необходимо реализовать метод safeAttributes() следующим образом:

public function safeAttributes()
{
    return array('title', 'content', 'status', 'tags');
}

Подсказка: Самый простой способ узнать, какие атрибуты должны быть перечислены в safeAttributes — ознакомиться с HTML-формой, которая используется для ввода данных. Те атрибуты, которые присутствуют в форме, могут считаться безопасными. Так как эти атрибуты заполняются пользователем, чаще всего для них определены правила валидации.

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

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

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

public function relations()
{
    return array(
        'author'=>array(self::BELONGS_TO, 'User', 'authorId'),
        'comments'=>array(self::HAS_MANY, 'Comment', 'postId',
            'order'=>'??.createTime'),
        'tagFilter'=>array(self::MANY_MANY, 'Tag', 'PostTag(postId, tagId)',
            'together'=>true,
            'joinType'=>'INNER JOIN',
            'condition'=>'??.name=:tag'),
    );
}

Выше описано следующее:

  • Запись принадлежит автору(User), связь с которым устанавливается на основе поля записи authorId;
  • Запись может содержать много комментариев(Comment), связь с которыми устанавливается на основе поля комменария postId. Комментарии сортируются по времени их создания.

Отношение tagFilter немного сложнее. Оно используется для явного пересечения таблицы Post с таблицей Tag и выбора только строк с определённым тегом. Мы покажем, как использовать это отношение, когда будем реализовывать отображение записей.

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

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

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

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

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

class Post extends CActiveRecord
{
    const STATUS_DRAFT=0;
    const STATUS_PUBLISHED=1;
    const STATUS_ARCHIVED=2;
 
    ......
 
    public function getStatusOptions()
    {
        return array(
            self::STATUS_DRAFT=>'Черновик',
            self::STATUS_PUBLISHED=>'Опубликовано',
            self::STATUS_ARCHIVED=>'В архиве',
        );
    }
 
    public function getStatusText()
    {
        $options=$this->statusOptions;
        return isset($options[$this->status]) ? $options[$this->status]
            : "unknown ({$this->status})";
    }
}

В приведённом коде мы определили константы класса для представления возможных значений статуса. Эти константы используются для улучшения читаемости кода. Был определён метод getStatusOptions(), возвращающий соответствия числовых значений статуса и его текстовых представлений. Также мы реализовали метод getStatusText(), который возвращает текстовое представление статуса текущей записи.