Вот здесь Илья Уткин уже писал про связи aggregates и composites. aggregates — не жесткие связи, а composites — жесткие. Что это значит? Это значит, что если есть два xPDO-класса, между которыми описаны жесткие composites-связи, то при удалении главного объекта будет удален и дочерний объект.
Давайте рассмотрим простой и понятный пример. Вот есть у нас класс modUser (пользователь), и есть modUserProfile (профиль пользователя). Ведь абсолютно логично, что профиль пользователя не может существовать без самого пользователя. Потому логично, что если мы удаляем пользователя, то и профиль его должен тоже удаляться. При этом если вы выполните $modx->getObject('modUser', $id)->remove(), то удалится не только пользователь (modUser), но и его профиль (modUserProfile) (а так же настройки пользователя (UserSettings), и записи членства в группах (UserGroupMembers)). То есть, когда у нас правильно настроены все связи, то нам не приходится каждый раз писать лишний код, плюс исключается человеческий фактор.
Давайте посмотрим на описание composites-связей класса modUser:
'composites' => array ( 'Profile' => array ( 'class' => 'modUserProfile', 'local' => 'id', 'foreign' => 'internalKey', 'cardinality' => 'one', 'owner' => 'local', ), 'UserSettings' => array ( 'class' => 'modUserSetting', 'local' => 'id', 'foreign' => 'user', 'cardinality' => 'many', 'owner' => 'local', ), 'UserGroupMembers' => array ( 'class' => 'modUserGroupMember', 'local' => 'id', 'foreign' => 'member', 'cardinality' => 'many', 'owner' => 'local', ), ),
В общем, если кто-то уже создавал свои кастомные классы, наверняка пробовали расширять другие классы и в описаниях. То есть, если вы к примеру, расширяете modResource, вам нет нужды опять описывать это огромное количество колонок. Достаточно просто в описании класса указать 'extends' => 'modResource', и описания всех колонок будут унаследованы от modResource. Так же будут унаследованы и связи composites и aggregates. То есть вы создали свой класс myResource extends modResource, и легко можете делать так: $modx->getObject('myResource', $id)->getOne('Parent'), так как описание связи с Parent описано в modResource. Удобно? А то.
Но рассмотрим вот такой пример: мы расширили modResource, и для своего кастомного класса создали связанный объект, и связь жесткая. То есть если удалять объект вашего класса, то xPDO по связям поймет, что и связанный объект тоже надо удалять. Но здесь есть большое НО… А вот мы после того, как для этого документа были созданы связанные объекты взяли, и переключили класс документа опять в modDocument… А modDocument не расширяет наш класс, он не знает о связях нашего кастомного класса с другими пользовательскими классами… И все, связь нарушилась. После этого кто-то взял, удалил этот документ, а мусор после него остался.
И вот все это печально. Но как оказалось, и на это решение есть! — Метод xPDO::setPackageMeta(). Давайте посмотрим его код:
/** * Adds metadata information about a package and loads the xPDO::$classMap. * * @param string $pkg A package name to use when looking up classes/maps in xPDO. * @param string $path The root path for looking up classes in this package. * @return bool */ public function setPackageMeta($pkg, $path = '') { $set = false; if (is_string($pkg) && !empty($pkg)) { $pkgPath = str_replace('.', '/', $pkg); $mapFile = $path . $pkgPath . '/metadata.' . $this->config['dbtype'] . '.php'; if (file_exists($mapFile)) { $xpdo_meta_map = ''; include $mapFile; if (!empty($xpdo_meta_map)) { foreach ($xpdo_meta_map as $className => $extends) { if (!isset($this->classMap[$className])) { $this->classMap[$className] = array(); } $this->classMap[$className] = array_unique(array_merge($this->classMap[$className],$extends)); } $set = true; } } else { $this->log(xPDO::LOG_LEVEL_WARN, "Could not load package metadata for package {$pkg}."); } } else { $this->log(xPDO::LOG_LEVEL_ERROR, 'setPackageMeta called with an invalid package name.'); } return $set; }
В общем, когда выполняется метод xPDO::addPackage() (многие с ним наверняка сталкивались), то выполняется и этот метод, и он пытается найти в подключаемом пакете файл с мета-описанием пакета.
$mapFile = $path . $pkgPath . '/metadata.' . $this->config['dbtype'] . '.php';
То есть для MySQL в папке модели вашего пакета должен лежать файл metadata.mysql.php
Вот там мы можем описать наследование других классов нашими собственными. К примеру так:
<php $xpdo_meta_map = array ( 'xPDOSimpleObject' => 'modResource' => array ( 'myResource', ), );
И вот когда вот эти связи объектов указаны, тогда при инициализации конечного объекта он будет выполнять $this->_composites= $xpdo->getComposites($this->_class), и получать связи и дочерних классов.
Но здесь, как оказалось, есть очень паршивая штука… myResource наследует modResource, modDocument наследует modResource, но myResource не наследует modDocument. То есть здесь получается треугольник наследуемости и нет единой вертикали. По этой причине $modx->newObject('modResource'); будет иметь информацию о связях myResource-а (их связь описана в meta-файле), но $modx->newObject('modDocument'); не будет знать зависимостей myResource-а. Чтобы знал, придется указывать еще и
'modDocument' => array ( 'myResource', ),
Но ведь есть еще и modWebLink, modStaticResource и прочие, плюс еще могут и CRC появиться. Не перечислять же их все вручную… В общем я такую хитрость придумал: в meta-файле написать так:
<php $xpdo_meta_map = array (); foreach((array)$this->getDescendants('modResource') as $class){ $xpdo_meta_map[$class] = array('myResource'); }
Тогда xPDO получит все связанные классы modResource-а, и для всех их пропишет связь с моим классом. И тогда все эти связанные классы будут знать о зависимостях моего класса.
Но, во-первых, ни в коем случае не злоупотребляйте этим, и тем более моим хаком. Дело в том. что очень многое на этом завязано. И у наследуемых объектов могут быть переопределены колонки и т.п. Потому такие вещи лучше делать только для тех классов, которые не сильно отличаются от базовых. И в этом ключе вообще имеет смысл разработать какие-то дополнительные инструментарии для контроля таких связей. А то получается, что несколько классов лежат в одной таблице, у всех ID уникальные, появился CRC, появились новые связи, на всех база данных единая, но стоит поменять класс объекта, и все, целостность базы рушится… Удалил запись с другим class_key, и в базе осталось куча мусора. Но механизмы вторичных ключей на уровне БД в MODX игнорируются напрочь, и этот механизм барахлит… В общем, хотелось бы улучшений ядра.