Первичный ключ xPDOObject

Как известно, при создании собственных таблиц в MODX принято наследовать или xPDOSimpleObject, или xPDOObject.

Отличие между ними ровно одно — в 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 этот параметр уже прописан, поэтому ошибки не возникает.
Василий Наумкин
11 мая 2013, 12:42
modx.pro
9
5 424
0

Комментарии: 16

Алексей
12 мая 2013, 00:00
0
Это многое объясняет, спасибо!
    Алексей
    01 июня 2013, 19:53
    0
    А как создать 2 таблицы со связанными полями, чтобы при удалении графы из 1-й, удалялось информация из 2-й?
    пример:
    <?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. Какие связи необходимо поставить?
    Алексей
    12 декабря 2013, 12:08
    0
    При удалении ресурса можно удалять запись в своей таблице, привязанной к ID ресурса?
    Пробовал объявить в схеме класс 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>
      Василий Наумкин
      12 декабря 2013, 12:12
      0
      Нужно не просто объявить, a перегенерировать модель по новой схеме.

      И это все затрется при первом же обновлении MODX.
      Сергей Шлоков
      27 июля 2014, 09:57
      +1
      День промучался, пока разобрался, почему не работает составной pk. Все оказалось просто. Спасибо.
        Степан Прищепенко
        29 мая 2015, 17:45
        0
        Добавлю свои пять копеек, пусть я буду не прав, но может эта проблема разрешиться иным путем. В общем делаю плагин для 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 и его типа, приведу вырезанный кусок данного метода:
        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>
        Возможно я и ошибся где-то хз, пусть найдет добрый человек и поправит меня если я не прав.
          Степан Прищепенко
          02 июня 2015, 11:51
          0
          «И снова здравствуйте!» Короче пока разбирался с проблемой удаления исправил XPDO и вроде как все удаляет нормально. Т.е. все удаляется с учетом наследования. Решил поглядеть исходник… мля оказалось что Оpengeek исправил этот косяк 18 апреля, не знаю выложили еще или нет это, но он исправил с учетом множественных PK, проверил, все также работает.

          И еще, не проверял в новых версиях XPDO, но в текущей такое есть (xPDO 2.3.0-pl (July 15, 2014)):
          если мы пропишем несколько полей с primary key в одном объекте (тип объекта не важен), то AUTO INCREMENT параметр не добавляется ни в одно поле, какой бы вы generator не прописывали. В результате будем всегда получать error.
          Aleksandr
          16 августа 2015, 12:52
          0
          Странно, но у меня почему-то при создании таблицы из файла с вашим примером не создается составной первичный ключ. То есть PRIMARY KEY (`category_id`,`package_id`) нету, так и должно быть?
            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
            16