Fi1osof 26 сентября 2014 0 0
Вот здесь Илья Уткин уже писал про связи 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 игнорируются напрочь, и этот механизм барахлит… В общем, хотелось бы улучшений ядра.
0 комментариев
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.