Реляційна Active Record

Ми вже розглянули використання Active Record (AR) для вибору даних з однієї таблиці бази даних. У цьому розділі ми розповімо, як використовувати AR для обʼєднання декількох звʼязаних таблиць і отримати обʼєднаний набір даних.

Для використання реляційної AR рекомендується щоб усі звʼязки відношення первинний-зовнішній ключ були чітко визначені для таблиць, що обʼєднуються. Це допомагає підтримувати звʼязність та цілісність даних.

Для наочності прикладів у даному розділі ми будемо використовувати схему бази даних, представлену на цій діаграмі сутність-відношення (ER).

Діаграма ER

Діаграма ER

Інформація: Підтримка обмежень по зовнішньому ключу різна у різних СУБД. SQLite 3.6.19 та більш ранні версії не підтримують обмежень, але ви, тим не менш, можете їх оголосити при створенні таблиць. Движок MySQL MyISAM не підтримує зовнішні ключі.

1. Оголошення відношення

Перед тим, як використовувати AR для реляційних запитів, нам необхідно пояснити AR, як AR-класи звʼязані один з одним.

Відношення між двома AR-класами безпосередньо залежить від відносин між відповідними таблицями бази даних. З точки зору БД, відношення між таблицями A і В може бути трьох типів: один-до-багатьох (наприклад, tbl_user і tbl_post), один-до-одного (наприклад, tbl_user і tbl_profile) і багато-до-багатьох (наприклад, tbl_category і tbl_post). У AR існує чотири типи відношень:

  • BELONGS_TO: якщо відношення між А і В один-до-багатьох, значить В належить А (наприклад, Post належить User);

  • HAS_MANY: якщо відношення між таблицями А і В один-до-багатьох, значить у А є багато В (наприклад, у User є багато Post);

  • HAS_ONE: це окремий випадок HAS_MANY, де А може мати максимум одне В (наприклад, у User є тільки один Profile);

  • MANY_MANY: це відношення відповідає типу відношення багато-до-багатьох в БД. Оскільки багато СУБД не підтримують безпосередньо тип відношення багато-до-багатьох, потрібно асоційована таблиця для перетворення відношення багато-до-багатьох у відносини один-до-багатьох. У нашій схемі бази даних, для цієї мети є таблиця tbl_post_category. У термінології AR ставлення MANY_MANY можна описати як комбінацію BELONGS_TO та HAS_MANY. Наприклад, Post належить багатьом Category, а у Category є багато Post.

Існує пʼятий спеціальний тип, який виконує агреговані запити на звʼязаних записах - він називається STAT. Зверніться будь-ласка до розділу Статистичний запит за деталями.

Оголошуючи відношення в AR, ми перевизначаємо метод relations() класу CActiveRecord. Цей метод повертає масив з конфігурацією відношень. Кожен елемент масиву представляє один звʼязок у наступному форматі:

'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', …додаткові параметри)

де VarName - імʼя відношення, RelationType вказує на один із чотирьох типів відношення, ClassName - імʼя AR-класу, звʼязаного з даним AR-класом, а ForeignKey вказує один або кілька зовнішніх ключів, які використовуються для звʼязку. Крім того, можна вказати ряд додаткових параметрів, про які розповімо трохи пізніше.

У коді нижче показано, як оголосити відношення між класами User і Post.

class Post extends CActiveRecord
{public function relations()
    {
        return array(
            'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
            'categories'=>array(self::MANY_MANY, 'Category',
                'tbl_post_category(post_id, category_id)'),
        );
    }
}
 
class User extends CActiveRecord
{public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id'),
            'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
        );
    }
}

Інформація: Зовнішній ключ може бути складеним, тобто складатися з двох і більше стовпців. У даному випадку імена стовпців слід розділити комами і передати їх або у якості рядка, або у вигляді масиву array('key1','key2'). Якщо потрібно вказати свій звʼязок первинного ключа із зовнішнім, задати її можна у вигляді array('fk'=>'pk'). Для складених ключів це буде array('fk_c1'=>'pk_c1','fk_c2'=>'pk_c2'). Для типу відношення MANY_MANY імʼя асоціативної таблиці також повинно бути зазначено в зовнішньому ключі. Наприклад, відношення categories у моделі Post позначено зовнішнім ключем tbl_post_category(post_id, category_id). При оголошенні відношення в AR-класі для кожного відношення у клас неявно додається властивість. Після виконання реляційного запиту відповідна властивість буде заповнена звʼязаним(-и) екземпляром(-ами) AR. Наприклад, якщо $author представляє AR-екземпляр User, то можна використовувати $author->posts для доступу до звʼязаних екземплярів Post.

2. Виконання реляційного запиту

Найпростіший спосіб виконати реляційний запит — зчитати реляційну властивість AR-класу. Якщо раніше до цієї властивості ніхто не звертався, то буде ініційований реляційний запит, який зʼєднає звʼязані таблиці і залишить тільки дані, що відповідають первинному ключу поточного екземпляра AR. Результат запиту буде збережено у властивості як екземпляр(-и) звʼязаного класу. Цей підхід також відомий, як «відкладене завантаження» (lazy loading), тобто реляційний запит здійснюється тільки у момент першого звернення до звʼязаних обʼєктів. Приклад нижче показує використання цього підходу:

// отримуємо запис з ID=10
$post=Post::model()->findByPk(10);
// отримуємо автора запису. Тут буде виконаний реляційний запит. 
$author=$post->author;

Інформація: Якщо для відношення не існує звʼязаного екземпляра, то відповідна властивість буде null для відношень BELONGS_TO та HAS_ONE або порожнім масивом для HAS_MANY та MANY_MANY. Варто зазначити, що відношення HAS_MANY та MANY_MANY повертають масиви обʼєктів і звертатися до їх властивостей необхідно в циклі, інакше можна отримати помилку «Trying to get property of non-object».

Спосіб відкладеного завантаження зручний, але не завжди ефективний. Наприклад, якщо ми захочемо отримати інформацію про автора N записів, використання відкладеного завантаження вимагатиме виконання N запитів для обʼєднання. У даній ситуації, нам допоможе метод «жадібного завантаження» (eager loading).

Цей підхід полягає у завантаженні всіх звʼязаних екземплярів AR одночасно з основним екземпляром AR. Реалізується цей підхід шляхом використання в AR методу with() у звʼязці із методом find або findAll. Наприклад:

$posts=Post::model()->with('author')->findAll();

Код вище поверне масив екземплярів Post. На відміну від відкладеного завантаження, властивість author у кожного запису заповнено звʼязаним екземпляром User ще до звернення до цієї властивості. Таким чином, замість виконання окремого запиту для кожного запису, жадібне завантаження отримає всі записи разом із їх авторами у одному запиті!

У методі with() можна вказати безліч імен відношень і жадібне завантаження поверне їх за один раз. Наприклад, наступний код поверне записи разом з їх авторами і категоріями:

$posts=Post::model()->with('author','categories')->findAll();

Крім того, можна здійснювати вкладене жадібне завантаження. Для цього замість простого списку імен відношень, ми передаємо методу with() імена відношень, упорядкованих ієрархічно, як у прикладі нижче:

$posts=Post::model()->with(
    'author.profile',
    'author.posts',
    'categories')->findAll();

Приклад вище поверне нам всі записи з їх авторами і категоріями, а також профіль кожного автора і всі його записи.

Жадібне завантаження може бути виконане шляхом зазначення властивості CDbCriteria::with:

$criteria=new CDbCriteria;
$criteria->with=array(
    'author.profile',
    'author.posts',
    'categories',
);
$posts=Post::model()->findAll($criteria);

або

$posts=Post::model()->findAll(array(
    'with'=>array(
        'author.profile',
        'author.posts',
        'categories',
    )
));

3. Реляційний запит без отримання звʼязаних моделей

Іноді потрібно виконати запит із використанням відношень, але, при цьому, не потрібні дані із звʼязаної моделі. Припустимо, є користувачі (User), які публікують безліч записів (Post). Запис може бути опублікований, а може бути чернеткою. Цей факт визначається полем моделі published. Нам необхідно отримати всіх користувачів, які опублікували хоча-б один запис. При цьому самі записи нам не цікаві. Зробити це можна так:

$users=User::model()->with(array(
    'posts'=>array(
        // записи нам не потрібні
        'select'=>false,
        // але потрібно вибрати тільки користувачів з опублікованими записами
        'joinType'=>'INNER JOIN',
        'condition'=>'posts.published=1',
    ),
))->findAll();

4. Параметри реляційного запиту

Вище ми згадували про те, що в реляційному запиті можна вказати додаткові параметри. Ці параметри — пари імʼя-значення — використовуються для тонкого налаштування реляційного запиту. Перелік параметрів представлений нижче.

  • select: список обраних полів для звʼязаного AR-класу. За замовчуванням значення параметра дорівнює '*', тобто вибираються всі поля таблиці. Для використовуваних стовпців повинні бути вирішені конфлікти імен;

  • condition: відповідає оператору WHERE, за замовчуванням значення параметра порожнє. Для використовуваних стовпців повинні бути вирішені конфлікти імен;

  • params: параметри для звʼязування у генерованому SQL-виразу. Параметри передаються як масив пар імʼя-значення;

  • on: відповідає оператору ON. Умова, що визначене в цьому параметрі, буде додано до умови обʼєднання з використанням оператора AND. Для використовуваних стовпців повинні бути вирішені конфлікти імен. Даний параметр непридатний для відношень типу MANY_MANY;

  • order: відповідає оператору ORDER BY, за замовчуванням значення параметра порожнє. Для використовуваних стовпців повинні бути вирішені конфлікти імен;

  • with: перелік дочірніх звʼязаних обʼєктів, які повинні бути завантажені із самим обʼєктом. Неправильне використання даної можливості може призвести до нескінченного циклу;

  • joinType: тип обʼєднання для відношень. За замовчуванням значення параметра дорівнює LEFT OUTER JOIN;

  • alias: псевдонім таблиці, асоційованої з відношенням. За замовчуванням значення параметра дорівнює null, що означає, що псевдонім відповідає імені відношення;

  • together: параметр, який встановлює необхідність примусового обʼєднання таблиці, асоційованої з цим відношенням, з іншими таблицями. Цей параметр має сенс тільки для відношень типів HAS_MANY та MANY_MANY. Якщо параметр не встановлений або дорівнює false, тоді кожне відношення HAS_MANY або MANY_MANY буде використовувати окремий SQL запит для звʼязаних даних, що може поліпшити швидкість виконання запиту, так як зменшується кількість даних, що вибирається. Якщо цей параметр дорівнює true, залежна таблиця при запиті буде завжди обʼєднуватися з основною, тобто буде зроблено один запит навіть у тому випадку, якщо до основної таблиці застосовується посторінкова розбивка. Якщо цей параметр не заданий, залежна таблиця буде обʼєднана з основною тільки у разі, коли не до основної таблиці не застосовується посторінкова розбивка. Більш докладний опис можна знайти в розділі «продуктивність реляційного запиту»;

  • join: додатковий оператор JOIN. За замовчуванням порожній. Цей параметр доступний з версії 1.1.3;

  • group: відповідає оператору GROUP BY, за замовчуванням значення параметра порожнє. Для використовуваних стовпців повинні бути вирішені конфлікти імен;

  • having: відповідає оператору HAVING, за замовчуванням значення параметра порожнє. Для використовуваних стовпців повинні бути вирішені конфлікти імен;

  • index: імʼя стовпця, значення якого повинні бути використані в якості ключів масиву, що зберігає звʼязані обʼєкти. Без установки цього параметра, масив звʼязаних обʼєктів використовує цілочисельний індекс, що починається з нуля. Параметр може бути встановлений тільки для відношень HAS_MANY та MANY_MANY;

  • scopes: групи умов, які необхідно застосувати. У випадку однієї групи може використовуватися як 'scopes'=>'scopeName'. Якщо ж груп кілька, то використовується як 'scopes'=>array('scopeName1','scopeName2'). Цей параметр доступний з версії 1.1.9.

Крім того, для відкладеного завантаження деяких типів відношень доступний ряд додаткових параметрів:

  • limit: параметр для обмеження кількості рядків у вибірці. Параметр не можна застосувати для відношень BELONGS_TO;

  • offset: параметр для зазначення початкового рядка вибірки. Параметр не можна застосувати для відношень BELONGS_TO;

  • through: імʼя відношення моделі, яке при отриманні даних буде використовуватися як міст. Параметр може бути встановлений тільки для відношень HAS_ONE та HAS_MANY. Цей параметр доступний з версії 1.1.7, у якій можна застосовувати його до HAS_ONE та HAS_MANY. Починаючи з версії 1.1.14, він може використовуватися із BELONGS_TO.

Нижче ми змінимо визначення відношення posts у моделі User, додавши кілька вищенаведених параметрів:

class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
                            'order'=>'posts.create_time DESC',
                            'with'=>'categories'),
            'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
        );
    }
}

Тепер при зверненні до $author->posts, ми отримаємо записи автора, відсортовані в зворотньому порядку за часом їх створення. Для кожного запису будуть завантажені її категорії.

5. Усунення конфлікту імен стовпців

При співпаданні імен стовпців у двох і більше зʼєднуваних таблиць, доводиться вирішувати конфлікт імен. Це робиться за допомогою додавання псевдоніма таблиці до імені стовпця.

У реляційному запиті псевдонім головної таблиці завжди дорівнює t, а імʼя псевдоніма таблиці, що відноситься до неї, за замовчуванням відповідає імені відношення. Наприклад, у коді нижче псевдоніми для Post та Comment відповідно t і comments:

$posts=Post::model()->with('comments')->findAll();

Припустимо, що і в Post і в Comment є стовпець create_time, у якому зберігається час створення запису або коментаря, і нам необхідно отримати записи разом з коментарями до них, відсортовані спочатку за часом створення запису, а потім за часом написання коментаря. Для цього нам знадобиться усунути конфлікт стовпців create_time наступним чином:

$posts=Post::model()->with('comments')->findAll(array(
    'order'=>'t.create_time, comments.create_time'
));

Підказка: Псевдонім таблиці звʼязку за замовчуванням дорівнює назві самого звʼязку. Майте на увазі, що при використанні одного звʼязку всередині іншого буде використано назву останнього із них. При цьому назва батьківського звʼязку не буде використана у якості префікса. Наприклад, псевдонімом звʼязку 'author.group' є 'group', а не 'author.group'.

$posts=Post::model()->with('author', 'author.group')->findAll(array(
  'order'=>'group.name, author.name, t.title'
));

Ви можете уникнути конфлікту псевдонімів таблиць задавши властивість звʼязку alias.

$comments=Comment::model()->with(
  'author',
  'post',
  'post.author'=>array('alias'=>'p_author'))->findAll(array(
  'order'=>'author.name, p_author.name, post.title'
));

6. Динамічні параметри реляційного запиту

Ми можемо використовувати динамічні параметри як для методу with(), так і для параметра with. Динамічні параметри перевизначають існуючі параметри відповідно до опису метода relations(). Наприклад, якщо для моделі User, наведеної вище, ми хочемо скористатися жадібним завантаженням для отримання записів автора у порядку зростання (параметр order у визначенні відношення задає регресний порядок), можна зробити це таким чином:

User::model()->with(array(
    'posts'=>array('order'=>'posts.create_time ASC'),
    'profile',
))->findAll();

Динамічні параметри в реляційних запитах можна використовувати разом з відкладеним завантаженням. Для цього необхідно викликати метод з тим же імʼям, що й імʼя звʼязку, і передати параметри як його аргумент. Наприклад, наступний код поверне публікації користувача, у яких status дорівнює 1:

$user=User::model()->findByPk(1);
$posts=$user->posts(array('condition'=>'status=1'));

7. Продуктивність реляційного запиту

Як було описано вище, жадібне завантаження використовується, головним чином, коли потрібно отримати безліч звʼязаних обʼєктів. У цьому випадку зʼєднанням усіх таблиць генерується великий складний SQL-запит. Такий запит у багатьох випадках є кращим, так як спрощує фільтрацію за значенням стовпця звʼязаної таблиці. Тим не менш, в деяких випадках такі запити не є ефективними.

Розглянемо приклад, у якому нам потрібно знайти нові записи разом з їх коментарями. Враховуючи, що у кожному записі 10 коментарів, при використанні одного великого SQL-запиту ми отримаємо безліч зайвих даних так як кожен запис буде повторно вибиратися з кожним її коментарем. Тепер спробуємо по-іншому: спочатку виберемо останні записи, а потім коментарі до них. У даному випадку нам необхідно виконати два SQL запити. Плюс в тому, що в отриманих даних не буде нічого зайвого.

То який підхід більш ефективний? Абсолютно вірної відповіді на це питання немає. Виконання одного великого SQL запиту може бути більш ефективним так як СУБД не доводиться зайвий раз розбирати і виконувати додаткові запити. З іншого боку, використовуючи один SQL запит, ми отримуємо більше зайвих даних і відповідно нам потрібно більше часу на їх передачу і обробку. За замовчуванням Yii використовує "жадібне" завантаження, тобто генерує один SQL запит, крім того випадку, коли до головної моделі застосовується LIMIT. Якщо виставити опцію together в описі відношення в true, то ми отримаємо єдиний SQL запит навіть якщо використовується LIMIT. Якщо використовувати false, то вибірка з деяких таблиць проводитиметься окремими запитами. Приміром, для того, щоб використовувати окремі SQL запити для вибірки останніх записів і коментарів до них, відношення comments моделі Post слід описати таким чином:

public function relations()
{
    return array(
        'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false),
    );
}

Для жадібного завантаження ми можемо задати цю опцію динамічно:

$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll();

8. Статистичний запит

Крім реляційних запитів, описаних вище, Yii також підтримує так звані статистичні запити (або запити агрегування). Цей тип запитів використовується для отримання агрегованих даних, що відносяться до звʼязаних обʼєктів, наприклад кількість коментарів до кожного запису, середній рейтинг для кожного найменування продукції і т.д. Статистичні запити можуть бути використані тільки для обʼєктів, звʼязаних відношеннями HAS_MANY (наприклад, у запису є багато коментарів) або MANY_MANY (наприклад, запис належить багатьом категоріям, а категорія може відноситися ставитися до безлічі записів).

Виконання статистичного запиту аналогічно виконання реляційного запиту відповідно до опису вище. Передусім необхідно оголосити статистичний запит у методі relations() класа CActiveRecord.

class Post extends CActiveRecord
{
    public function relations()
    {
        return array(
            'commentCount'=>array(self::STAT, 'Comment', 'post_id'),
            'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'),
        );
    }
}

Вище ми оголосили два статистичних запита: commentCount підраховує кількість коментарів до запису, а categoryCount рахує кількість категорій, до яких відноситься запис. Зверніть увагу, що відношення між Post і Comment - типу HAS_MANY, а відношення між Post і Category - типу MANY_MANY (з використанням перетворюючої таблиці post_category). Як можна бачити, порядок оголошення дуже схожий з оголошенням відношень, описаних вище. Єдина відмінність полягає у тому, що у даному випадку тип відношення дорівнює STAT.

За рахунок оголошених відношень ми можемо отримати кількість коментарів для запису, використовуючи вираз $post->commentCount. У момент першого звернення до даної властивості для отримання відповідного результату неявним чином виконується SQL-вираз. Як ми вже говорили, це називається підходом відкладеного завантаження. Можна також використовувати жадібний варіант завантаження, якщо необхідно отримати кількість коментарів до кількох записів:

$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();

Вираз вище виконує три SQL-запити для отримання всіх записів разом із значеннями кількості коментарів до них і кількості категорій. У разі відкладеного завантаження нам би знадобилося виконати 2*N+1 SQL-запитів для N записів.

За замовчуванням статистичний запит рахує кількість з використанням виразу COUNT. Його можна уточнити шляхом зазначення додаткових параметрів у момент оголошення в методі relations(). Доступні параметри перераховані нижче:

  • select: статистичний вираз, за замовчуванням дорівнює COUNT(*), що відповідає кількості дочірніх обʼєктів;

  • defaultValue: значення, яке надається у випадку, якщо результат статистичного запиту для запису відʼємний. Наприклад, якщо запис не має жодного коментаря, то властивості commentCount буде присвоєно це значення. За замовчуванням значення даного параметра дорівнює 0;

  • condition: відповідає оператору WHERE, за замовчуванням значення параметра порожнє;

  • params: параметри для звʼязування у генерованому SQL-виразі. Параметри передаються як масив пар імʼя-значення;

  • order: відповідає оператору ORDER BY, за замовчуванням значення параметра порожнє;

  • group: відповідає оператору GROUP BY, за замовчуванням значення параметра порожнє;

  • having: відповідає оператору HAVING, за замовчуванням значення параметра порожнє.

9. Реляційні запити з іменованими групами умов

У реляційному запиті іменовані групи умов можуть бути використані двома способами. Їх можна застосувати до основної моделі і до звʼязаних моделей.

Наступний код показує роботу з основною моделлю:

$posts=Post::model()->published()->recently()->with('comments')->findAll();

Даний код дуже схожий на нереляційні запити. Єдина відмінність у тому, що у нас присутній виклик with() після викликів груп умов. Даний запит поверне недавно опубліковані записи разом з коментарями до них.

У наступному прикладі показано, як застосувати групи умов до звʼязаних моделей:

$posts=Post::model()->with('comments:recently:approved')->findAll();
// або, починаючи з версії 1.1.7
$posts=Post::model()->with(array(
    'comments'=>array(
        'scopes'=>array('recently','approved')
    ),
))->findAll();
// або, починаючи з версії 1.1.7
$posts=Post::model()->findAll(array(
    'with'=>array(
        'comments'=>array(
            'scopes'=>array('recently','approved')
        ),
    ),
));

Цей запит поверне всі записи разом із ухваленими коментарями. Тут comments відноситься до імені відношення. recently та approved - іменовані групи, описані у моделі Comment. Імʼя відношення і групи параметрів розділяються двокрапкою.

Вам може знадобитися використовувати замість жадібної вибірки відкладену для звʼязку із групою умов. Синтаксис для цього такий:

// імʼя звʼязку comments повторюється два рази
$approvedComments = $post->comments('comments:approved');

Іменовані групи можуть бути використані при описі відношень моделі у методі CActiveRecord::relations() у параметрі with. У наступному прикладі при зверненні до $user->posts разом з публікаціями будуть отримані всі ухвалені коментарі.

class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
                'with'=>'comments:approved'),
        );
    }
}
// або, починаючи з версії 1.1.7
class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
                'with'=>array(
                    'comments'=>array(
                        'scopes'=>'approved'
                    ),
                ),
            ),
        );
    }
}

З версії 1.1.7 стало можливо передати параметри іменованим групам умов відношення. Приміром, якщо у Post є іменована група умов rated, яка приймає мінімальний рейтинг запису, використовувати її у User можна так:

Примітка: до 1.1.7 іменовані групи умов, що застосовуються до реляційних моделей, повинні бути описані в CActiveRecord::scopes. Тому вони не можуть бути параметризовані.

$users=User::model()->findAll(array(
    'with'=>array(
        'posts'=>array(
            'scopes'=>array(
                'rated'=>5,
            ),
        ),
    ),
));
 
class Post extends CActiveRecord
{
    ......
 
    public function rated($rating)
    {
        $this->getDbCriteria()->mergeWith(array(
            'condition'=>'rating=:rating',
            'params'=>array(':rating'=>$rating),
        ));
        return $this;
    }
 
    ......
}

10. Реляційні запити з through

При використанні through визначення відношення має виглядати наступним чином:

'comments'=>array(self::HAS_MANY,'Comment',array('key1'=>'key2'),'through'=>'posts'),

У коді вище, а саме у array('key1'=>'key2'):

  • key1 — ключ, визначений у відношенні, на яке вказує through (у нашому випадку posts).
  • key2 — ключ, визначений в моделі, на яку вказує відношення (у нашому випадку Comment).

through може використовуватися з HAS_ONE, BELONGS_TO та HAS_MANY.

HAS_MANY through

HAS_MANY through ER

HAS_MANY through ER

Приклад використання HAS_MANY з through - отримання користувачів, які перебувають у певній групі, якщо вони записані до групи через ролі.

Більш складним прикладом є отримання усіх коментарів для всіх користувачів певної групи. У цьому випадку необхідно використовувати декілька відношень з through в одній моделі:

class Group extends CActiveRecord
{
   ...
   public function relations()
   {
       return array(
           'roles'=>array(self::HAS_MANY,'Role','group_id'),
           'users'=>array(self::HAS_MANY,'User',array('user_id'=>'id'),'through'=>'roles'),
           'comments'=>array(self::HAS_MANY,'Comment',array('id'=>'user_id'),'through'=>'users'),
       );
   }
}

Приклади

// отримуємо всі групи з відповідними їм користувачами
$groups=Group::model()->with('users')->findAll();
 
// отримуємо всі групи з відповідними їм користувачами і ролями
$groups=Group::model()->with('roles','users')->findAll();
 
// отримуємо всіх користувачів і ролі для групи з ID, рівним 1
$group=Group::model()->findByPk(1);
$users=$group->users;
$roles=$group->roles;
 
// отримуємо всі коментарі для групи з ID, рівним 1
$group=Group::model()->findByPk(1);
$comments=$group->comments;

HAS_ONE through

HAS_ONE through ER

HAS_ONE through ER

Приклад використання HAS_ONE з through - отримання адреси користувача у випадку, якщо користувач звʼязаний з адресою через профіль. Всі задіяні сутності (користувач, профіль і адреса) мають відповідні їм моделі:

class User extends CActiveRecord
{
   ...
   public function relations()
   {
       return array(
           'profile'=>array(self::HAS_ONE,'Profile','user_id'),
           'address'=>array(self::HAS_ONE,'Address',array('id'=>'profile_id'),'through'=>'profile'),
       );
   }
}

Приклади

// отримуємо адресу користувача з ID, рівним 1
$user=User::model()->findByPk(1);
$address=$user->address;

through із собою

through можна використовувати для моделі, звʼязаної із собою через міст. У нашому випадку це користувач, який навчає інших користувачів:

through self ER

through self ER

Відношення для даного випадку визначаються таким чином:

class User extends CActiveRecord
{
   ...
   public function relations()
   {
       return array(
           'mentorships'=>array(
               self::HAS_MANY, 
               'Mentorship', 
               'teacher_id', 
               'joinType'=>'INNER JOIN'
           ),
           'students'=>array(
               self::HAS_MANY, 
               'User', 
               array('student_id'=>'id'), 
               'through'=>'mentorships', 
               'joinType'=>'INNER JOIN'
           ),
       );
   }
}

Приклади

// отримуємо всіх студентів вчителя з ID, рівним 1
$teacher=User::model()->findByPk(1);
$students=$teacher->students;

Be the first person to leave a comment

Please to leave your comment.