====== Поддержка отношений вида один-к-одному ======
===== Определение отношения =====
Рассмотрим связь вида один-к-одному на примере двух классов - человек (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.
Поэтому мы решили инвертировать связь и пришли к текущему варианту.