Инструменты пользователя

Инструменты сайта


limb3:ru:packages:active_record:one_to_one

Поддержка отношений вида один-к-одному

Определение отношения

Рассмотрим связь вида один-к-одному на примере двух классов - человек (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.

Поэтому мы решили инвертировать связь и пришли к текущему варианту.

Обсуждение

Ваш комментарий. Вики-синтаксис разрешён:
  _____  ____  __  __ __  __  ____ 
 / ___/ / __ \ \ \/ / \ \/ / / __ \
/ /__  / /_/ /  \  /   \  / / /_/ /
\___/  \____/   /_/    /_/  \___\_\
 
limb3/ru/packages/active_record/one_to_one.txt · Последние изменения: 2010/11/10 10:02 (внешнее изменение)