Первичный ключ xPDOObject
Как известно, при создании собственных таблиц в MODX принято наследовать или xPDOSimpleObject, или xPDOObject.
Отличие между ними ровно одно — в SimpleObject уже прописан первичный ключ id, а в Object — нет. То есть, если вы хотите, чтобы у вашей таблицы создавалось поле id с становилось primary key — нужно наследовать SimpleObject.
Я, однако, люблю простые таблицы ключ-значение, в которые добавляю первичным ключом два и более полей сразу. Например, в репозитории пакет может быть в нескольких категориях, значит нужно создать таблицу extraCategoryMember из двух полей category_id и package_id.
Ключ id мне здесь совершенно не нужен, ведь он будет расти при каждой операции добавления пакета в категорию, а таких операций может быть очень много. Конечно, вряд ли INT(10) скоро закончится, но зачем хранить лишнее?
Итак, вот моя схема:
Всё прекрасно работает, за исключением одного — при удалении пакета, такие связанные объекты остаются, а в логе ошибка:
При разработке MS2 я решил вопрос ручным удалением связанных объектов в методе msProductData::remove(), но в этот раз решил разобраться.
В каждом объекте есть метод getPK(), который возвращает поле, являющееся первичным ключом объекта. В случае xPDOSimpleObject возвращается строка id. В случае xPDOObject не возвращается ничего.
Оказывается, чтобы getPK() вернул нам массив, как и должен, нужно обязательно прописать в схеме у полей атрибут index=«pk», не смотря на то, что в схемах версии 1.1 индексы в атрибутах не указываются.
Моя схема теперь выглядит вот так:
И теперь getPK возвращает мне массив:
Ошибка из лога пропала, связанные объекты удаляются корректно.
Даже не знаю, ошибка это, или я чего-то не понял, но, несмотря на используемую версию схемы 1.1, указывать атрибут index=«pk» для полей-ключей необходимо.
Хотя в документации четко прописано, что:
У объекта xPDOSimpleObject этот параметр уже прописан, поэтому ошибки не возникает.
Отличие между ними ровно одно — в SimpleObject уже прописан первичный ключ id, а в Object — нет. То есть, если вы хотите, чтобы у вашей таблицы создавалось поле id с становилось primary key — нужно наследовать SimpleObject.
Я, однако, люблю простые таблицы ключ-значение, в которые добавляю первичным ключом два и более полей сразу. Например, в репозитории пакет может быть в нескольких категориях, значит нужно создать таблицу extraCategoryMember из двух полей category_id и package_id.
Ключ id мне здесь совершенно не нужен, ведь он будет расти при каждой операции добавления пакета в категорию, а таких операций может быть очень много. Конечно, вряд ли INT(10) скоро закончится, но зачем хранить лишнее?
Итак, вот моя схема:
<object class="extraCategoryMember" table="extras_category_members" extends="xPDOObject">
<field key="category_id" dbtype="integer" attributes="unsigned" precision="10" phptype="integer" null="false" default="0" />
<field key="package_id" dbtype="integer" attributes="unsigned" precision="10" phptype="integer" null="false" default="0" />
<index alias="category" name="category" primary="true" unique="true" type="BTREE">
<column key="category_id" length="" collation="A" null="false" />
<column key="package_id" length="" collation="A" null="false" />
</index>
</object>
Всё прекрасно работает, за исключением одного — при удалении пакета, такие связанные объекты остаются, а в логе ошибка:
[2013-05-11 15:49:49] (ERROR @ /assets/components/extras/connector.php) Error removing dependent object: Array
(
[category_id] => 10
[package_id] => 2
)
При разработке MS2 я решил вопрос ручным удалением связанных объектов в методе msProductData::remove(), но в этот раз решил разобраться.
xPDOObject::getPK()
В каждом объекте есть метод getPK(), который возвращает поле, являющееся первичным ключом объекта. В случае xPDOSimpleObject возвращается строка id. В случае xPDOObject не возвращается ничего.
Оказывается, чтобы getPK() вернул нам массив, как и должен, нужно обязательно прописать в схеме у полей атрибут index=«pk», не смотря на то, что в схемах версии 1.1 индексы в атрибутах не указываются.
Моя схема теперь выглядит вот так:
<object class="extraCategoryMember" table="extras_category_members" extends="xPDOObject">
<field key="category_id" dbtype="integer" attributes="unsigned" precision="10" phptype="integer" null="false" default="0" index="pk" />
<field key="package_id" dbtype="integer" attributes="unsigned" precision="10" phptype="integer" null="false" default="0" index="pk" />
<index alias="category" name="category" primary="true" unique="true" type="BTREE">
<column key="category_id" length="" collation="A" null="false" />
<column key="package_id" length="" collation="A" null="false" />
</index>
И теперь getPK возвращает мне массив:
array (size=2)
'category_id' => string 'category_id' (length=11)
'package_id' => string 'package_id' (length=10)
Ошибка из лога пропала, связанные объекты удаляются корректно.
Заключение
Даже не знаю, ошибка это, или я чего-то не понял, но, несмотря на используемую версию схемы 1.1, указывать атрибут index=«pk» для полей-ключей необходимо.
Хотя в документации четко прописано, что:
Schema Version 1.1
In 2.0.0-rc3, the schema was changed to implement a new model element that describes Table Indexes separately from the field element's index and index_group attributes.
…
Do not add version=«1.1» (leave off the version attribute or set it to 1.0) if you have not yet described your indexes in the new schema format or xPDO will create the tables with no indexes.
У объекта xPDOSimpleObject этот параметр уже прописан, поэтому ошибки не возникает.
Комментарии: 16
Это многое объясняет, спасибо!
А как создать 2 таблицы со связанными полями, чтобы при удалении графы из 1-й, удалялось информация из 2-й?
пример:
http://schemaviewer.dev.kenters.com/51aa188bce3e55.64196579
Я хочу сделать так, чтобы при удалении строки из таблицы doodles, удалялась соответствующая строка из doodlesCategories. Какие связи необходимо поставить?
пример:
<?xml version="1.0" encoding="UTF-8"?>
<model package="doodles" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" version="1.1">
<object class="doodles" table="doodles" extends="xPDOSimpleObject" >
<field key="title" dbtype="varchar" precision="255" phptype="string" null="false" default="" index="index" />
<field key="description" dbtype="text" phptype="string" />
<index alias="PRIMARY" name="PRIMARY" primary="true" unique="true">
<column key="id" collation="A" null="false" />
</index>
</object>
<object class="doodlesCategories" table="doodles_Categories" extends="xPDOSimpleObject">
<field key="cid" dbtype="int" precision="11" phptype="integer" null="false" index="index" />
<field key="did" dbtype="int" precision="11" phptype="integer" null="false" />
<index alias="cid" name="cid" primary="false" unique="false" type="BTREE" >
<column key="cid" length="" collation="A" null="false" />
<column key="did" length="" collation="A" null="false" />
</index>
<aggregate alias="Resource" class="modResource" local="cid" foreign="id" cardinality="one" owner="foreign" />
<aggregate alias="doodless" class="doodles" local="did" foreign="id" cardinality="one" owner="foreign" />
</object>
</model>
Схема: http://schemaviewer.dev.kenters.com/51aa188bce3e55.64196579
Я хочу сделать так, чтобы при удалении строки из таблицы doodles, удалялась соответствующая строка из doodlesCategories. Какие связи необходимо поставить?
Нужно прописать связь composite.
немендленный донат за такую полезность
На здоровье =)
А как прописать правильно джойны? если, мне к примеру выборку ресурсов, с ID №1,2,3 и присоединить к ним из таблицы doodles_Categories поле doodles_Categories.CID -> modResource.ID, и к томуже присоединить doodles_Categories.DID -> doodles.ID?
Посмотри вот тут, на реальном примере. А вообще, все есть в документации на rtfm.modx.com
ок. а то я уже запарился с этим rowboat
А он, по моему, так и не умеет.
Для таких выборок либо вручную, либо pdoTools.
Для таких выборок либо вручную, либо pdoTools.
При удалении ресурса можно удалять запись в своей таблице, привязанной к ID ресурса?
Пробовал объявить в схеме класс modResource — не помогает.
Пробовал объявить в схеме класс modResource — не помогает.
<object class="modResource" table="modx_site_content" extends="xPDOSimpleObject">
<field key="id" dbtype="int" precision="11" phptype="integer" null="false" index="index" />
<composite alias="Resource" class="PageItem" local="id" foreign="resource_id" cardinality="many" owner="local" />
</object>
Нужно не просто объявить, a перегенерировать модель по новой схеме.
И это все затрется при первом же обновлении MODX.
И это все затрется при первом же обновлении MODX.
День промучался, пока разобрался, почему не работает составной pk. Все оказалось просто. Спасибо.
Добавлю свои пять копеек, пусть я буду не прав, но может эта проблема разрешиться иным путем. В общем делаю плагин для MS2, сделал такую же схему указал несколько праймари ключей index=«pk» generated=«native и указал в объекте extends=»xPDOObject". Прописал агрегатные и композиционные связи. Объекты получаем (общим списком), изменяем, НО удалить или получать объект вот так: $modx->getObject('msSfiltersProduct', 123); уже нельзя, точнее мы получаем объект, но он совсем не тот что мы запрашивали,. Сработает только через $modx->getObject('msSfiltersProduct', array('id'=>27));
Что собственно и происходит при удалении через remove() или если мы используем процессор от modObjectRemoveProcessor. А проблема в следующем: так как во всех случаях прежде чем удалить объект, его нужно получить через getObject('msSfiltersProduct', $criteria) Где $criteria либо массив данных, если обратились через процессор, либо строка или целое. При этом вызове у нас вызывается $this->parseConditions($conditions, $conjunction); Вот этот метод и гадит нам. Дело в том что условие строиться на основе primary key и его типа, приведу вырезанный кусок данного метода:
Что собственно и происходит при удалении через remove() или если мы используем процессор от modObjectRemoveProcessor. А проблема в следующем: так как во всех случаях прежде чем удалить объект, его нужно получить через getObject('msSfiltersProduct', $criteria) Где $criteria либо массив данных, если обратились через процессор, либо строка или целое. При этом вызове у нас вызывается $this->parseConditions($conditions, $conjunction); Вот этот метод и гадит нам. Дело в том что условие строиться на основе primary key и его типа, приведу вырезанный кусок данного метода:
public function parseConditions($conditions, $conjunction = xPDOQuery::SQL_AND) {
$result= array ();
$pk= $this->xpdo->getPK($this->_class);
$pktype= $this->xpdo->getPKType($this->_class); // если у нас несколько primary key, то == Array
...
if (is_array($conditions)) {
// при remove и getObject - $conditions всегда integer или string
...
if (isset($conditions[0]) && is_scalar($conditions[0]) && !$this->isConditionalClause($conditions[0]) && is_array($pk) && count($conditions) == count($pk)) {
$iteration= 0;
// во прикол, кто так решил, что если у нас $conditions массив, то $pk обязательно массив,
// а что по другому быть не может? (дебильно конечно но все же)
foreach ($pk as $k) {...}
...
}
elseif ($this->isConditionalClause($conditions)) {
$result= new xPDOQueryCondition(array(
'sql' => $conditions
,'binding' => null
,'conjunction' => $conjunction
));
}
elseif (($pktype == 'integer' && is_numeric($conditions)) || ($pktype == 'string' && is_string($conditions) && $this->isValidClause($conditions))) {
if ($pktype == 'integer') {
$param_type= PDO::PARAM_INT;
} else {
$param_type= PDO::PARAM_STR;
}
$field['sql']= $this->xpdo->escape($alias) . '.' . $this->xpdo->escape($pk) . ' = ?';
$field['binding']= array ('value' => $conditions, 'type' => $param_type, 'length' => 0);
$field['conjunction']= $conjunction;
$result = new xPDOQueryCondition($field);
}
return $result;
Видно, что проверки на тип ключа как массива нет и $result будет всегда == NULL, и getObject вернет нам первый объект. Что же делать… как вариант не указывать несколько primary key, у всех кроме одного нужно указывать index=«index». В самом объекте указать индекс может выглядеть так:<index alias="PRIMARY" name="PRIMARY" primary="true" unique="true" type="BTREE">
<column key="id" length="" collation="A" null="false" />
<column key="res_id" length="" collation="A" null="false" />
</index>
Возможно я и ошибся где-то хз, пусть найдет добрый человек и поправит меня если я не прав.
«И снова здравствуйте!» Короче пока разбирался с проблемой удаления исправил XPDO и вроде как все удаляет нормально. Т.е. все удаляется с учетом наследования. Решил поглядеть исходник… мля оказалось что Оpengeek исправил этот косяк 18 апреля, не знаю выложили еще или нет это, но он исправил с учетом множественных PK, проверил, все также работает.
И еще, не проверял в новых версиях XPDO, но в текущей такое есть (xPDO 2.3.0-pl (July 15, 2014)):
если мы пропишем несколько полей с primary key в одном объекте (тип объекта не важен), то AUTO INCREMENT параметр не добавляется ни в одно поле, какой бы вы generator не прописывали. В результате будем всегда получать error.
И еще, не проверял в новых версиях XPDO, но в текущей такое есть (xPDO 2.3.0-pl (July 15, 2014)):
если мы пропишем несколько полей с primary key в одном объекте (тип объекта не важен), то AUTO INCREMENT параметр не добавляется ни в одно поле, какой бы вы generator не прописывали. В результате будем всегда получать error.
Странно, но у меня почему-то при создании таблицы из файла с вашим примером не создается составной первичный ключ. То есть PRIMARY KEY (`category_id`,`package_id`) нету, так и должно быть?
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.