Рассмотрим связь вида один-к-одному на примере двух классов - человек (Person) и его социальный номер (SocialSecurity). Предположим, что человек имеет один и только один социальный номер (Person has one SocialSecurity).
Объекты этих классов хранятся в таблицах соответственно person и social_security:
CREATE TABLE `person` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) DEFAULT NULL, `social_security_id` BIGINT(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `social_security` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, `code` VARCHAR(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Обратите внимание на поле social_security_id в таблице person. Это внешний ключ на поле id таблицы social_security.
Связь один-к-одному в дочерних классах lmbActiveRecord описывается атрибутами $_has_one того класса, который является главным в связи и $_belongs_to того класса, который является подчиненным. В нашем случае классы Person и SocialSecurity будут выглядеть следующим образом:
class Person extends lmbActiveRecord { protected $_has_one = array('social_security' => array('field' => 'social_security_id', 'class' => 'SocialSecurity')); } class SocialSecurity extends lmbActiveRecord { protected $_belongs_to = array('person' => array('field' => 'social_security_id', 'class' => 'Person')); }
Эти атрибуты содержат массивы отношений, в которых находится класс с другими классами.
Одно отношение $_has_one описывается следующими полями:
Одно отношение $_belongs_to описывается следующими полями:
Обратите внимание, что social_security_id упоминается как в $_has_one, так и в $_belongs_to.
Связанные объекты доступны посредством set/get методов, которые соответствуют названию связи:
$person = new Person(); $person->setName('Jim'); $number = new SocialSecurity(); $number->setCode('099123'); $person->setSocialSecurity($number); $person->save();// Будет сохранен сам Person и связанный с ним SocialSecurity $person2 = lmbActiveRecord :: findById('Person', $person->getId()); echo $person2->getSocialSecurity()->getCode(); // Связанные объекты автоматически загружаются по требованию; Выведет '099123' $number2 = lmbActiveRecord :: findById('SocialSecurity', $number->getId()); echo $number2->get('person')->get('name'); // Выведет 'Jim'
Методы вида setPerson()/getPerson()/setSocialSecurity()/getSocialSecurity() автоматически поддерживаются классами, если для них есть соответствующие описания в атрибутах $_has_one и $_belongs_to.
При удалении родительского объекта, по-умолчанию, удаляется также подчиненный объект. Однако это не всегда необходимо. Например, у нас может быть репозиторий изображений и различные виды объектов, например, документы, новости, отчеты могут содержать ссылки на изображения из этого репозитория, в том числе в виде отношений один-к-одному. В этом случае, в описании $_has_one используйте параметр $cascade_delete со значением false, например:
class NewsItem extends lmbActiveRecord { protected $_has_one = array('node' => array('field' => 'node_id', 'class' => 'Node'), 'image' => array('field' => 'image_id', 'class' => 'Image', 'cascade_delete' => false, 'can_be_null' => true)); }
А что происходит, когда удаляется дочерний объект? Когда удаляется дочерний объект, тогда соответствующее поле в таблице родитеского класса получает значение null. То есть происходит автоматический nullify поля отношения в родительском классе.
Поэтому если ваш код предусматривает удаление дочерних классов без уведомления родителя, тогда использование can_be_null в описании родительского класса обязательно!
Связь один-ко-многим не обязательно делать двунаправленной, то есть описание $_belongs_to в подчиненном классе может отсутствовать. Например, в приведенном выше примере про репозиторий изображений, класс изображений вовсе необязательно должен иметь описание связи со всеми объектами, которые находятся с ним в отношении один-к-одному, например:
class NewsItem extends lmbActiveRecord { protected $_has_one = array('image' => array('field' => 'image_id', 'class' => 'Image', 'cascade_delete' => false, 'can_be_null' => true)); } class Event extends lmbActiveRecord { protected $_has_one = array('file' => array('field' => 'file_id', 'class' => 'File'), 'image' => array('field' => 'image_id', 'class' => 'Image', 'cascade_delete' => false, 'can_be_null' => true)); } class Image extends lmbActiveRecord { protected $_has_one = array('node' => array('field' => 'node_id', 'class' => 'Node')); }
Обратите внимание, что в Limb ACTIVE_RECORD связь хранится в поле таблицы родительского класса. В нашем примере - это поле social_security_id в таблице person. В Rails ситуация противоположная, и связь хранится в подчиненном объекте, то есть было бы person_id в таблице social_security_id. Почему в Limb так?
Дело в том, что практически в первом проекте, построенном на базе Limb3 мы столкнулись с ситуацией, подобной то, что мы описали про репозиторий изображений. Связь с изображением имело сначала 2, потом 3, потом 5 классов и для каждого из них нужно было создавать свое поле в таблице image. Если бы мы попробовали бы инвертировать связь (изображение имеет связь с этими объектами), тогда нам бы пришлось иметь дело со следующими неудобствами:
Поэтому мы решили инвертировать связь и пришли к текущему варианту.
Обсуждение