Этот потрясающий метод xPDO::setPackageMeta()

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