Рассмотрим связь вида один-ко-многим на примере двух классов - курс (Course ) и набор лекций, из которых он состоит (Lecture). В итоге имеем отношение Course 1 - *(has many) Lectures.
Объекты этих классов хранятся в таблицах соответственно course и lecture:
CREATE TABLE `course` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `title` VARCHAR(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `lecture` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `title` VARCHAR(255) DEFAULT NULL, `course_id` INT(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Обратите внимание на поле course_id в таблице lecture. Это внешний ключ на поле id таблицы course. Получается, что поле, которые хранит связь, находится в таблице подчиненного объекта. В этом заключается отличие от связи "Один-к-одному", где связь хранится в поле таблицы главного (родительского) объекта.
Связь один-ко-многим в классах lmbActiveRecord описывается атрибутами $_has_many того класса, который является главным в связи и $_many_belongs_to того класса, который является подчиненным. В нашем случае классы Course и Lecture будут выглядеть следующим образом:
class Course extends lmbActiveRecord { protected $_has_many = array('lectures' => array('field' => 'course_id', 'class' => 'Lecture')); } class Lecture extends lmbActiveRecord { protected $_many_belongs_to = array('course' => array('field' => 'course_id', 'class' => 'Course')); }
Одно отношение $_has_many описывается следующими полями:
Одно отношение $_many_belongs_to описывается следующими полями:
Обратите внимание, что course_id упоминается как в $_has_many, так и в $_many_belongs_to.
Для добавления новых зависимых объектов по одному в родительский объект (содержащий $_has_many) используется метод addToRelationName($object), в нашем случае addToLectures($lecture):
$course = new Course(); $course->setTitle('Super course'); $l1 = new Lecture(); $l1->setTitle('Physics'); $l2 = new Lecture(); $l2->setTitle('Math'); $course->addToLectures($l1); $course->addToLectures($l2);
Можно также добавить сразу несколько объектов, используя метод setRelationName($array_or_iterator), в нашем случае setLectures($lectures). В качестве параметра передается массив или итератор с объектами.
$course = new Course(); $course->setTitle('Super course'); $l1 = new Lecture(); $l1->setTitle('Physics'); $l2 = new Lecture(); $l2->setTitle('Math'); $course->setLectures(array($l1, $l2)); $course->save(); // Сохранит курс и связанные с ним лекции
Обратите внимание, что при использовании метода setRelationName() набор связанных объектов полностью заменяется на новый, старые связанные объекты - удаляются.
Метод getRelationName() родительского класса применительно к связанным один-ко-многим объектам(в нашем случае это будет Course :: getLectures()) возвращает итератор (коллекцию) с дочерними объектами:
$course = lmbActiveRecord :: findById('Course', $course_id); $lectures = $course->getLectures(); foreach($lectures as $lecture) echo $lecture->getTitle() . "\n";
Получив коллекцию связанных объектов можно добавлять в нее элементы посредством метода add($object), что эквивалентно вызову addToRelationName($object) у родительского объекта:
$course = new Course(); $course->setTitle('Super course'); $l1 = new Lecture(); $l1->setTitle('Physics'); $l2 = new Lecture(); $l2->setTitle('Math'); $lectures = $course->getLectures(); $lectures->add($l1); $lectures->add($l2);
При помощи метода getRelationName() у подчиненного объекта (в нашем случае это метод Lecture :: getCourse()) можно получить родительский объект:
$lectures = lmbActiveRecord :: find('Lecture'); foreach($lectures as $lecture) echo "Lecture " . $lecture->getTitle() . " of " . $lecture->getCourse()->getTitle() . " course. \n";
В последнем примере мы сталкиваемся с проблемой n+1 выборок, то есть для каждой лекции будет сделан запрос на курс. Как решить эту проблему, мы расскажем позже.
При помощи метода removeAll() применительно к коллекции связанных объектов можно удалять связанные объекты, например:
$course = lmbActiveRecord :: findById('Course', $course_id); $course->getLectures()->removeAll();
Если вам нужно удалить все лишь один объект из коллекции, можете удалить его явно через метод lmbActiveRecord :: destroy().
removeAll() приводит к удалению связанных объектов по-одному с предварительной загрузкой их в память.
Обратите внимание, что коллекции, в частности методы add() и removeAll() ведут себя по-разному, в зависимости от того, сохранен родительский объект в момент работы с коллекцией или еще нет. Подробнее об этом, а также дополнительная информация по работе с коллекциями связанных объектов можно получить в разделе "Дополнительная информация по отношениям".
При удалении объекта, который имеет связь has_many по-умолчанию просходит вызов метода removeAll() для коллекции дочерних элементов. То есть по-умолчанию происходит каскадное удаление.
Однако has_many отношение поддерживает так называемый nullify, когда мы просто обнуляем значение соответствующего поля в таблице дочернего класса, например:
class Course extends lmbActiveRecord { protected $_has_many = array('lectures' => array('field' => 'course_id', 'class' => 'Lecture', 'nullify' => true)); } class Lecture extends lmbActiveRecord { protected $_many_belongs_to = array('course' => array('field' => 'course_id', 'class' => 'Course', 'can_be_null' => true)); }
Обратите внимание на то, что в описании many_belongs_to класса Lecture появилась опция can_be_null. Это обязательное условие, если мы используем nullify для Course.
Обсуждение