Доопрацювання моделі Post ========================= Модель `Post`, згенерована за допомогою `Gii`, потребує наступних змін: - метод `rules()`: задає правила валідації атрибутів моделі; - метод `relations()`: задає звʼязки з іншими обʼєктами. > Info|Інформація: [Модель](/doc/guide/uk/basics.model) складається із набору атрибутів, кожен з яких асоціюється з відповідним полем у таблиці БД. Атрибути можуть бути описані явно як змінні класу, або використовуватися без будь-якого опису. Зміна методу `rules()` ---------------------- У першу чергу необхідно визначити правила валідації, які дозволять переконатися в тому, що дані, введені користувачем, коректні до їх збереження в БД. Наприклад, атрибут `status` моделі `Post` повинен бути цілим числом, рівним 1, 2 або 3. Консоль `Gii` генерує правила валідації для кожної моделі. При цьому використовується структура БД, тому деякі правила можуть виявитися неточними. Грунтуючись на аналізі вимог, змінимо метод `rules ()` наступним чином: ~~~ [php] 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`. За додатковою інформацією про те, як описувати правила валідації ви можете звернутися до [повного керівництва](/doc/guide/uk/form.model#declaring-validation-rules). ~~~ [php] public function normalizeTags($attribute,$params) { $this->tags=Tag::array2string(array_unique(Tag::string2array($this->tags))); } ~~~ де `array2string` та `string2array` - нові методи, які ми повинні визначити у класі моделі `Tag`: ~~~ [php] 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()|CModel::validate] або [save()|CActiveRecord::save]. > Note|Примітка: Важливо памʼятати, що атрибути, описувані у `rules()` повинні вводитися користувачем. Інші атрибути моделі `Post`, такі як `id` або `create_time`, що заповнюються у коді або безпосередньо у БД, не повинні бути присутніми в `rules()`. Детальніше це описано у розділі [Безпечне присвоювання значень атрибутів](/doc/guide/uk/form.model#securing-attribute-assignments). Після того, як ми зробили описані зміни, ми можемо зайти на сторінку створення запису і перевірити, що нові правила валідації працюють. Зміна методу `relations()` -------------------------- Далі вкажемо у методі `relations()` звʼязані із записом обʼєкти. Після цього ми зможемо використовувати [реляційну ActiveRecord (RAR)](/doc/guide/uk/database.arr) для отримання звʼязаних із записом даних, таких як інформацію про автора та коментарі. Складні SQL запити з JOIN в цьому випадку не потрібні. Визначимо метод `relations()`: ~~~ [php] 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` ми описуємо дві константи, які використовуються у наведеному вище методі: ~~~ [php] class Comment extends CActiveRecord { const STATUS_PENDING=1; const STATUS_APPROVED=2; ...... } ~~~ Звʼязки, описані у методі `relations ()`, означають наступне: * Запис належить автору (`User`), звʼязок із яким встановлюється на основі поля запису `author_id`; * Запис може містити багато коментарів (`Comment`), звʼязок із якими встановлюється на основі поля коментаря `post_id`. Коментарі сортуються за часом їх створення; * Звʼязок `commentCount` є особливим, оскільки повертає результат агрегації, тобто число коментарів запису. Задавши описані вище звʼязки, ми можемо отримати інформацію про автора та коментарі до запису наступним чином: ~~~ [php] $author=$post->author; echo $author->username; $comments=$post->comments; foreach($comments as $comment) echo $comment->content; ~~~ Більш докладно використання та визначення звʼязків описано у [повному керівництві](/doc/guide/uk/database.arr). Додаємо властивість `url` ------------------------- Кожному запису відповідає унікальний URL. Замість повсюдного виклику [CWebApplication::createUrl] для формування цього URL, ми можемо додати властивість `url` моделі `Post` і повторно використовувати код для генерації URL. Пізніше ми опишемо, як отримати гарні URL. Використання властивості моделі дозволить реалізувати це максимально зручно. Для того, щоб додати властивість `url`, ми додаємо геттер у клас `Post`: ~~~ [php] 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](/doc/blog/final.url)». Так як [CComponent] є предком класу `Post`, геттер `getUrl()` дозволяє нам писати код на зразок `$post->url`. При зверненні до `$post->url` буде викликаний геттер і ми отримаємо результат його виконання. Більш докладно це описано у [повному керівництві](/doc/guide/uk/basics.component). Текстове представлення для статусу ---------------------------------- Так як статус запису зберігається у БД у вигляді числа, нам необхідно отримати його текстове представлення для відображення користувачам. Для великих систем така вимога є досить типовою. Для зберігання звʼязків між цілими числами і їх текстовим поданням, необхідним іншим обʼєктам даних, ми використовуємо таблицю `tbl_lookup`. Для більш зручного отримання текстових даних змінимо модель `Lookup` наступним чином: ~~~ [php] 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`. Перший містить можливі статуси запису, другий - статуси коментаря. Для того, щоб зробити код більш читабельним ми описуємо константи, відповідні цілочисловим значенням статусу. Ці константи необхідно використовувати у коді замість відповідних їм цілих значень. ~~~ [php] 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)`.