Использование through в отношении MANY_MANY в Yii фреймворке.

С версии 1.1.7 в ActiveRecord Yii доступен новый параметр для настройки отношений в моделях. Имя этого параметра — through. В этой заметке хочу показать один из примеров для чего этот параметр можно использовать. Сразу скажу, что я сам еще не до конца разобрался с этим, так что могу что-то напутать и где-то ошибаться.

Предположим, что у нас есть модель «Сотрудник» — Employee, модель «Проект» — Project. Сотрудник может участвовать во многих проектах и в одном проекте может участвовать много сотрудников, следовательно между проектом и сотрудиком отношение «многие ко многим» (MANY_MANY в терминах Yii). Для простоты предположим, что сотрудник, как и проект имеет всего два поля: id и name.

Для связки сотрудников и проектов введем третью таблицу «Employee_to_Project», в которой будем хранить id сотрудника (emp_id) и id проекта (proj_id). Такой простой таблички достаточно для установки простой связи. Теперь предположим, что каждый сотрудник в рамках каждого проекта должен иметь какую-то должность (для простоты это будет просто текстовое поле). Для хранения должности, в таблицу-связку добавим еще одно поле «position». Таким образом таблица-связка будет состоять из 3-х полей: emp_id, proj_id, position. С таблицами разобрались, переходим к моделям. Для краткости буду показывать только необходимые для примера методы моделей.

Модель Employee:

<?php 
class Employee extends CActiveRecord 
{
    public function relations()
    {        
        return array(
            'projects' => array(self::MANY_MANY, 'Project','Employee_to_Project("emp_id","proj_id")'),            
        );
    }
}
?>

В этой моделе мы определили стандартное отношение MANY_MANY и теперь для получения пользователя со всеми его проектами необходимо выполнить вот такой код:

<?php  $user = Employee::model()->with('projects')->findByPk($id); ?>

"$user->projects" — будет содержать массив моделей Project (проектов, в которых участвует пользователь)

Казалось бы все хорошо, но возникает вопрос: как получить роль сотрудника в каждом из проектов? Роль хранится в таблице-связке, а массив «projects» содержит массив моделей Project — следовательно через модель Project эту самую роль получить не получится. Вот тут нам на помощь и приходит новый параметр through.

Изменим нашу модель следующим образом:

<?php 
class Employee extends CActiveRecord 
{
    public function relations()
    {        
        return array(
            'projectsRell' => array(self::HAS_MANY, 'Project','EmployeeToProject'),            
            'projects'     => array(self::HAS_MANY, 'Project','proj_id','through' => 'projectsRell'),            
        );
    }
}
?>

Что мы тут сделали:

1 Добавили отношение «projectsRell» (EmployeeToProject — модель для табличики Employee_to_Project)

2 В отношении projects тип отношения изменили с MANY_MANY на HAS_MANY

3 В отношении projects указали параметр «through» со значением «projectsRell» (projectsRell — отношение, определенное в пункте 1)

Обратите внимание на:

1 Вместо отношения MANY_MANY везде используется HAS_MANY.

2 В отношении projects, которое указывает на таблицу/модель Project в качестве ключа использовано поле «proj_id». Это поле в таблице Project отсутствует, но оно есть в таблице/моделе EmployeeToProject, на которую мы и указываем параметром «through» (через запись 'through' => 'projectsRell')

Как этим пользоваться?

<?php  $user = Employee::model()->with('projectsRell','projects')->findByPk($id); ?>

<?php foreach($user->projectsRell as $rel):?>
    <?php echo $rel->position; // получаем роли в проектах?>    
<?php endforeach;?>

<?php foreach($user->projects as $proj):?>
    <?php echo $proj->name; // получаем названия проектов ?>    
<?php endforeach;?>

Ссылки по теме:

www.yiiframework.com/doc/guide/1.1/en/database.arr#relational-query-with-through

www.yiiframework.ru/forum/viewtopic.php?f=6&t=3765

www.yiiframework.com/forum/index.php?/topic/22386-through-relational-query-problem/page__p__109539__hl__through

www.yiiframework.com/forum/index.php?/topic/22011-relational-query-with-through-explained/page__p__107745__hl__through