====== Поддержка отношений вида один-к-одному ====== ===== Определение отношения ===== Рассмотрим связь вида один-к-одному на примере двух классов - человек (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** описывается следующими полями: * **field** - указывает на поле в таблице текущего класса (в нашем случае person), в котором хранится значение идентификатора связанного (подчиненного объекта). * **class** - указывает на название класса подчиненного объекта. * **cascade_delete** - указывает, нужно ли удалять подчиненный объект при удалении родительского. Об этом чуть ниже. * **can_be_null** - указывает, может ли подчиненный элемент отсутствовать. По умолчанию lmbActiveRecord всегда пытается загружать подчиненный объект, если его затребовали в клиентском коде из родительского объекта, и, если подчиненный объект не был ранее связан с родителем, произойдет ошибка. Одно отношение **$_belongs_to** описывается следующими полями: * **field** - указывает на поле в таблице родительского класса (в нашем случае person), в котором хранится значение идентификатора связанного (подчиненного объекта). * **class** - указывает на название класса родительского объекта. Обратите внимание, что 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')); } ===== Отличия от ActiveRecord в RubyOnRails ===== Обратите внимание, что в Limb ACTIVE_RECORD связь хранится в поле таблицы родительского класса. В нашем примере - это поле social_security_id в таблице person. В Rails ситуация противоположная, и связь хранится в подчиненном объекте, то есть было бы person_id в таблице social_security_id. Почему в Limb так? Дело в том, что практически в первом проекте, построенном на базе Limb3 мы столкнулись с ситуацией, подобной то, что мы описали про репозиторий изображений. Связь с изображением имело сначала 2, потом 3, потом 5 классов и для каждого из них нужно было создавать свое поле в таблице image. Если бы мы попробовали бы инвертировать связь (изображение имеет связь с этими объектами), тогда нам бы пришлось иметь дело со следующими неудобствами: * Фраза "Изображение имеет один документ" звучит как-то натянуто. * Необходимо модифицировать класс Image каждый раз при возникновении новой или удалении существущей связи. * Некоторые классы имели связь сразу с двумя объектами класса Image в виде small_image и large_image. Поэтому мы решили инвертировать связь и пришли к текущему варианту.