Реляционная Active Record
=========================

Мы уже рассмотрели использование Active Record (AR) для выбора данных из одной таблицы базы данных.
В этом разделе мы расскажем, как использовать AR для соединения нескольких связанных таблиц и получения
набора связанных данных.

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

Для наглядности примеров в этом разделе мы будем использовать схему базы данных,
представленную на следующей диаграмме сущность-связь (ER).

![Диаграмма ER](er.png)

> Info|Информация: Поддержка ограничений внешних ключей различается в разных СУБД.
> SQLite 3.6.19 и более ранние версии не поддерживает ограничений, но вы, тем не менее, можете их объявить
> при создании таблиц. Движок MySQL MyISAM не поддерживает внешние ключи.

Установка связей между 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`. Более подробно с ним можно ознакомиться в разделе
[Статистический запрос](/doc/guide/database.arr#statistical-query).

Установка связей производится внутри метода [relations()|CActiveRecord::relations] класса [CActiveRecord].
Этот метод возвращает массив с конфигурацией связей. Каждый элемент массива представляет одну связь в следующем формате:

~~~
[php]
'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', …дополнительные параметры)
~~~

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

В приведённом ниже коде показано, как установить связь между классами `User` и `Post`.

~~~
[php]
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'),
		);
	}
}
~~~

> Info|Информация: Внешний ключ может быть составным, то есть состоять из двух и более столбцов таблицы. В этом случае
имена столбцов следует разделить запятыми и передать их либо в качестве строки, либо в виде массива `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 (или массив экземпляров для связей типа один-ко-многим и
многие-ко-многим). Например, если `$author` является экземпляром AR-класса `User`, то можно использовать `$author->posts`
для доступа к связанным экземплярам `Post`.

Выполнение реляционного запроса
-------------------------------

Самый простой способ выполнить реляционный запрос — использовать реляционное свойство AR-класса.
Если обращение к этому свойству производится впервые, то будет выполнен реляционный запрос, который соединит связанные таблицы
и оставит только данные, соответствующие первичному ключу текущего экземпляра AR.
Результат запроса будет сохранён в свойстве как экземпляр (или массив экземпляров) связанного класса.
Этот подход также известен как «отложенная загрузка» (lazy loading), при которой непосредственный запрос выполняется
только в момент первого обращения к связанным объектам. Ниже приведён пример использования этого подхода:

~~~
[php]
// получаем запись с ID=10
$post=Post::model()->findByPk(10);
// Получаем автора записи. Здесь будет выполнен реляционный запрос.
$author=$post->author;
~~~

> Info|Информация: Если связанные данные не найдены, то соответствующее
свойство примет значение 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.  Реализуется этот подход путем использования метода
 [with()|CActiveRecord::with] вместе с методом [find|CActiveRecord::find] или
 [findAll|CActiveRecord::findAll].
Например:
~~~
[php]
$posts=Post::model()->with('author')->findAll();
~~~

Приведённый код вернёт массив экземпляров `Post`. В отличие от отложенной загрузки, свойство `author` каждой записи будет
заполнено связанным экземпляром `User` ещё до обращения к этому свойству. Таким образом, вместо выполнения отдельного запроса
для каждой записи, жадная загрузка получит все записи вместе с их авторами в одном запросе!

В методе [with()|CActiveRecord::with] можно указать несколько связей, и жадная загрузка вернёт их за один раз.
Например, следующий код вернёт записи вместе с их авторами и категориями:

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

Кроме того, можно осуществлять вложенную жадную загрузку. Для этого вместо простого списка имён связей, мы передаем методу
[with()|CActiveRecord::with] имена связей, упорядоченных иерархически, как в следующем примере:

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

Пример выше вернёт нам все записи вместе с их авторами и категориями, а также профиль каждого автора и все его записи.

Жадная загрузка может быть выполнена путём указания свойства [CDbCriteria::with]:

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

или

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


Реляционный запрос без получения связанных моделей
--------------------------------------------------

Иногда требуется выполнить запрос с использованием связи, но при этом
не требуются данные из связанной модели. Допустим, есть пользователи (`User`),
которые публикуют множество записей (`Post`). Запись может быть опубликована,
а может быть черновиком. Этот факт определяется значением поля `published` модели `Post`.
Пусть нам необходимо получить всех пользователей, которые опубликовали хотя бы одну
запись, при этом сами записи нам не интересны. Сделать это можно следующим образом:

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


Параметры реляционного запроса
------------------------------

Выше мы упоминали о том, что в реляционном запросе можно указать дополнительные параметры.
Эти параметры — пары имя-значение — используются для тонкой настройки реляционного запроса.
Список параметров представлен ниже.

   - `select`: список выбираемых полей для связанного AR-класса. По умолчанию значение параметра равно '*',
что соответствует всем полям таблицы. Для используемых столбцов должны быть разрешены конфликты имён.

   - `condition`: соответствует SQL оператору `WHERE`, по умолчанию значение параметра пустое.
Для используемых столбцов должны быть разрешены конфликты имён.

   - `params`: параметры для связывания в генерируемом SQL-выражении. Параметры передаются как массив пар имя-значение.

   - `on`: соответствует SQL оператору `ON`. Условие, указанное в этом параметре,
будет добавлено к основному условию соединения при помощи SQL оператора `AND`. Для используемых столбцов должны быть разрешены конфликты имён.
Данный параметр неприменим для связей типа `MANY_MANY`.

   - `order`: соответствует SQL оператору `ORDER BY`, по умолчанию значение параметра пустое.
Для используемых столбцов должны быть разрешены конфликты имён.

   - `with`: список дочерних связанных объектов, которые должны быть загружены с самим объектом.
Неправильное использование данной возможности может привести к бесконечному циклу.

   - `joinType`: тип соединения таблиц. По умолчанию значение параметра равно `LEFT
OUTER JOIN`;

   - `alias`: псевдоним таблицы, ассоциированной со связью. По умолчанию значение параметра
равняется null, что означает, что псевдоним соответствует имени связи.

   - `together`: параметр, устанавливающий необходимость принудительного соединения таблицы, ассоциированной с этой связью,
с другими таблицами. Этот параметр имеет смысл только для связей типов `HAS_MANY` и `MANY_MANY`. Если параметр не установлен или
равен false, тогда каждая связь `HAS_MANY` или `MANY_MANY` будет использовать отдельный SQL-запрос для связанных данных,
что может улучшить скорость выполнения запроса, т.к. уменьшается количество выбираемых данных.
Если параметр равен `true`, то зависимая таблица при выполнении запроса всегда будет
соединяться с основной, то есть будет выполнен один SQL-запрос даже в том случае, если
к основной таблице применяется постраничная разбивка. Если данный параметр не
задан, зависимая таблица будет соединена с основной только в случае, когда
к основной таблице не применяется постраничная разбивка. Более подробное описание
можно найти в разделе «производительность реляционного запроса».

   - `join`: дополнительный оператор `JOIN`. По умолчанию пуст. Этот параметр
доступен с версии 1.1.3.

   - `group`: соответствует SQL оператору `GROUP BY`, по умолчанию значение параметра пустое.
Для используемых столбцов должны быть разрешены конфликты имён.

   - `having`: соответствует SQL оператору `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.

Ниже мы изменим определение связи `posts` в модели `User`, добавив несколько вышеприведенных параметров:

~~~
[php]
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`, мы получим записи автора, отсортированные
в обратном порядке по времени их создания. Для каждой записи будут загружены
её категории.

Устранение конфликта имён столбцов
----------------------------------

При совпадении имён столбцов в двух и более соединяемых таблицах,
приходится разрешать конфликт имён. Это делается при помощи добавления
псевдонима таблицы к имени столбца.

В реляционном запросе псевдоним главной таблицы всегда равен `t`,
а псевдоним связанной таблицы по умолчанию равен имени связи.
В приведённом ниже коде псевдонимы таблиц для моделей `Post` и `Comment` будут соответственно
`t` и `comments`:

~~~
[php]
$posts=Post::model()->with('comments')->findAll();
~~~

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

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

> Tip|Подсказка: Псевдоним таблицы связи по умолчанию равен названию самой связи. Имейте ввиду,
> что при использовании одной связи внутри другой будет использовано название последней из них.
> При этом название родительской связи не будет использовано в качестве префикса. Например, псевдонимом
> связи 'author.group' является 'group', а не 'author.group'.
>
> ~~~
> [php]
> $posts=Post::model()->with('author', 'author.group')->findAll(array(
> 	'order'=>'group.name, author.name, t.title'
> ));
> ~~~
>
> Вы можете избежать конфликта псевдонимов таблиц задав свойство связи [alias|CActiveRelation::alias].
>
> ~~~
> [php]
> $comments=Comment::model()->with(
> 	'author',
> 	'post',
> 	'post.author'=>array('alias'=>'p_author'))->findAll(array(
> 	'order'=>'author.name, p_author.name, post.title'
> ));
> ~~~

Динамические параметры реляционного запроса
-------------------------------------------

Мы можем использовать динамические параметры как для метода
[with()|CActiveRecord::with], так и для параметра `with`. Динамические параметры переопределяют существующие
параметры в соответствии с описанием метода [relations()|CActiveRecord::relations]. К примеру, если для модели `User`, приведённой выше,
мы хотим воспользоваться жадной загрузкой для получения записей автора в порядке возрастания (параметр `order` в определении связи
задает убывающий порядок), можно сделать это следующим образом:

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

Динамические параметры в реляционных запросах можно использовать вместе с
отложенной загрузкой. Для этого необходимо вызвать метод с тем же именем, что и
имя связи, и передать параметры в качестве аргументов. К примеру, следующий код
вернёт публикации пользователя, у которых `status` равен&nbsp;1:

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

Производительность реляционного запроса
---------------------------------------

Как было сказано выше, жадная загрузка используется, главным образом, когда
требуется получить множество связанных объектов. В этом случае при соединении
всех таблиц генерируется сложный SQL-запрос. Такой запрос во многих случаях
является предпочтительным, т.к. упрощает фильтрацию по значению столбца связанной таблицы.
Тем не менее, в некоторых случаях такие запросы не являются эффективными.

Рассмотрим пример, в котором нам необходимо найти последние добавленные записи вместе с их комментариями.
Предположив, что у каждой записи 10 комментариев, при использовании одного большого SQL-запроса
мы получим множество лишних данных, так как каждая запись будет повторно выбираться с каждым
её комментарием. Теперь попробуем по-другому: сначала выберем последние записи, а затем комментарии к ним.
В этом случае нам необходимо выполнить два SQL-запроса. Плюс в том, что полученные данные
не будут содержать дублирующую информацию.

Какой же подход более эффективен? Однозначного ответа на этот вопрос нет.
Выполнение одного большого SQL-запроса может оказаться более эффективным, так как СУБД
не приходится лишний раз разбирать и выполнять дополнительные запросы.
С другой стороны, используя один SQL-запрос, мы получаем лишние данные, а значит нам требуется больше времени на их передачу и обработку.
По умолчанию Yii использует жадную загрузку, то есть генерирует один SQL-запрос
за исключением случая, когда к главной модели применяется `LIMIT`. Если выставить
опцию `together` в описании связи в `true`, то мы получим единственный SQL-запрос
даже если используется `LIMIT`. Если использовать `false`, то выборка из
некоторых таблиц будет производиться отдельными запросами.
Например, для того чтобы использовать отдельные SQL-запросы для выборки
последних записей и комментариев к ним, связь `comments` модели `Post` следует
описать следующим образом:

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

Для жадной загрузки мы можем задать этот параметр динамически:

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

Статистический запрос
---------------------

Помимо реляционных запросов, описанных выше, Yii также поддерживает так называемые статистические запросы (или запросы агрегирования).
Этот тип запросов используется для получения агрегированных данных, относящихся к связанным объектам (количество комментариев
к каждой записи, средний рейтинг для каждого наименования продукции и т.д.).
Статистические запросы могут быть использованы только для связей типа `HAS_MANY` (например, у записи есть много
комментариев) или `MANY_MANY` (например, запись принадлежит многим категориям, а категориия может относиться ко множеству записей).

Выполнение статистического запроса аналогично выполнению реляционного запроса. Первым делом необходимо
объявить статистический запрос в методе [relations()|CActiveRecord::relations] класса [CActiveRecord].

~~~
[php]
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-запрос.
Как мы уже говорили, это называется подходом *отложенной загрузки*. Можно также использовать *жадный* вариант загрузки, если необходимо
получить количество комментариев к нескольким записям:

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

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

По умолчанию статистический запрос считает количество с использованием выражения `COUNT`.
Его можно уточнить путём указания дополнительных параметров в момент объявления в методе [relations()|CActiveRecord::relations].
Доступные параметры перечислены ниже:

   - `select`: статистическое выражение, по умолчанию равно `COUNT(*)`, что соответствует количеству связанных объектов;

   - `defaultValue`: значение, которое присваивается в случае, если результат статистического запроса пуст.
Например, если запись не имеет ни одного комментария, то свойству `commentCount` будет присвоено это значение. По умолчанию значение
данного параметра равно 0;

   - `condition`: соответствует SQL оператору `WHERE`, по умолчанию значение параметра пустое;

   - `params`: параметры для связывания в генерируемом SQL-выражении. Параметры передаются
   как массив пар имя-значение;

   - `order`: соответствует SQL оператору `ORDER BY`, по умолчанию значение параметра пустое;

   - `group`: соответствует SQL оператору `GROUP BY`, по умолчанию значение параметра пустое;

   - `having`: соответствует SQL оператору `HAVING`, по умолчанию значение параметра пустое.

Реляционные запросы с именованными группами условий
---------------------------------------------------

В реляционном запросе [именованные группы условий](/doc/guide/database.ar#named-scopes)
могут быть использованы двумя способами. Их можно применить к основной модели и
к связанным моделям.

Следующий код иллюстрирует случай их применения к основной модели:

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

Данный код очень похож на нереляционный запрос. Единственное отличие состоит в том, что
присутствует вызов `with()` после вызовов групп условий. Данный запрос
вернёт недавно опубликованные записи вместе с комментариями к ним.

В следующем примере показано, как применить группы условий к связанным моделям:

~~~
[php]
$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`. Имя связи и группы условий разделяются двоеточием.

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

~~~
[php]
// имя связи comments повторяется два раза
$approvedComments = $post->comments('comments:approved');
~~~


Именованные группы могут быть использованы при описании связей модели в
методе [CActiveRecord::relations()] в параметре `with`. В следующем примере
при обращении к `$user->posts` вместе с публикациями будут получены все
*одобренные* комментарии.

~~~
[php]
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` можно следующим образом:


> Note|Примечание: до версии 1.1.7 именованные группы условий, применяемые к реляционным моделям,
> должны быть описаны в CActiveRecord::scopes. Поэтому они не могут быть параметризованы.


~~~
[php]
$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;
	}

	......
}
~~~

Реляционные запросы с through
-----------------------------

При использовании `through` определение связи должно выглядеть следующим образом:

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

В коде выше, а именно в `array('key1'=>'key2')`:

  - `key1` — ключ, определённый в связи, на которую указывает `through` (в нашем случае `posts`).
  - `key2` — ключ, определённый в модели, на которую указывает связь (в нашем случае `Comment`).

`through` может использоваться как с `HAS_ONE`, так и с `HAS_MANY`.

### `HAS_MANY` through

![HAS_MANY through ER](has_many_through.png)

Пример использования `HAS_MANY` с `through` — получение пользователей, состоящих
в определённой группе, если они записаны в группу через роли.

Более сложным примером является получение всех комментариев для всех пользователей
определённой группы. В этом случае необходимо использовать несколько связей
с `through` в одной модели:

~~~
[php]
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'),
       );
   }
}
~~~

#### Примеры

~~~
[php]
// получаем все группы с соответствующими им пользователями
$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.png)

Пример использования `HAS_ONE` с `through` — получение адреса пользователя в
случае, если пользователь связан с адресом через профиль. Все задействованные
сущности (пользователь, профиль и адрес) имеют соответствующие им модели:

~~~
[php]
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'),
       );
   }
}
~~~

#### Примеры

~~~
[php]
// получаем адрес пользователя с ID, равным 1
$user=User::model()->findByPk(1);
$address=$user->address;
~~~


### through с собой

`through` можно использовать для модели, связанной с собой через мост. В нашем
случае это пользователь, обучающий других пользователей:


![through self ER](through_self.png)


Связи для данного случая определяются следующим образом:

~~~
[php]
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'),
       );
   }
}
~~~

#### Примеры

~~~
[php]
// получаем всех студентов учителя с ID, равным 1
$teacher=User::model()->findByPk(1);
$students=$teacher->students;
~~~