Наследуемость полей у расширяемых xPDO-классов
        Всем привет!
Смотрите, когда в своих компонентах мы наследуемся от, допустим, modResource, то в схеме мы пропишем следущее:
Теперь то, что хочу сделать я.
У меня есть пара десятков сущностей (и, соответственно, таблиц), у каждой из которых должен быть определённый набор полей, вроде «кем создан», «когда создан», «активно», «удалено» и т.д.
Так вот по аналогии с расширением класса modResource подумалось мне сделать вот так:
Так вот. При генерации все таблицы создаются абсолютно правильно, со всеми нужными полями — и в my_object_one, и в my_object_two есть столбцы active, deleted, createdby, createdon, etc… Т.е. такой механизм наследования как бы предумотрен и работает. Отлично.
Получилось у нас 3 файла с классами, соответствующие им map-файлы и metadata.mysql.php.
Содержимое metadata.mysql.php:
Содержимое map-файла класса-«шаблона» (жалко нету спойлеров, такие портянки нужно прятать):
Т.е. это просто описание столбцов и их свойств для myObjectTemplate. Код этот привожу для того, чтобы показать, что всё правильно. Ведь правильно же?
Вот аналогичное описание для класса myObjectOne:
Напоминаю, что в примере с расширением класса modResource в map-файле унаследованного класса была бы точно такая же строчка
И класс Ticket спокойно наследует все поля, свойства и методы класса modResource. Все мы это знаем.
Т.е. в моём случае пока всё сгенерировано корректно.
Так вот к чему всё это? Развязка :-)
А вся проблема в том, что унаследованные поля в таблицу не записываются. От слова «совсем».
Т.е. выполнив этот код:
Вот такой код:
Этот же xPDO сперва физически создаёт эти столбцы в таблицах, унаследовав их как надо, потом как надо генерирует map-файлы, а в конечном счёте чихать он на них хотел.
К слову сказать. В файлах самих объектов myobjectone.class.php и myobjecttwo.class.php подключается файл-«шаблон»:
Первой же мыслью было — «надо, чтобы для myObjectTemplate создавалась таблица!». Прописал в xml-схеме table=«table_blabla», всё перегенировал и нифига. Не работает.
Да и в конце концов, чем способ унаследования класса с несуществующей таблицей отличается от унаследования xPDOSimpleObject, который так же физически (в виде таблицы) не существует? Да ни чем, в сущности. Однако, с моими классами этот фокус не работает.
И что интересно — столбец id-то унаследован! Т.е. в цепочке myObjectOne -> myObjectTemplate -> xPDOSimpleObject, myObjectOne наследует столбец от xPDOSimpleObject, но не наследует столбцы от myObjectTemplate, как бы пропуская его.
И вот тут я, честно говоря, не знаю что делать. Документация на эту тему молчит, гугление плодов не принесло, а в исходниках я могу закопаться на неделю :-(
Конечно, остался вариант прописывать все эти поля для каждой таблицы в отдельности (а таких полей 12-15 штук + плюс индексы к ним). Но это просто нереально раздует xml-схему и уже после 5-6 таблицы ориентироваться в этом xml-аду будет невозможно (а таблиц будет 20-25). Это не просто вопрос удобства, а реально во время разработки будет невозможно скролить по 5-10-15 экранов вверх-вниз, чтобы прописывать связи и что-то там редактировать.
Хотелось бы универсального подхода. Да и в конце концов, xPDO, какого хрена??
В общем, дорогой читатель, такие дела. Надеюсь не сильно тебя утомил своими размышлениями и также надеюсь на твою помощь ибо в какую сторону копать — не представляю.
    
    
                                                                                
            Смотрите, когда в своих компонентах мы наследуемся от, допустим, modResource, то в схеме мы пропишем следущее:
<model package="myPackage" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" phpdoc-package="myPackage" phpdoc-subpackage="" version="1.1">
  <object class="myObject" extends="modResource">
  </object>
</model>И, собственно, всё — объект myObject унаследует все поля, их свойства и методы от класса modResource.Теперь то, что хочу сделать я.
У меня есть пара десятков сущностей (и, соответственно, таблиц), у каждой из которых должен быть определённый набор полей, вроде «кем создан», «когда создан», «активно», «удалено» и т.д.
Так вот по аналогии с расширением класса modResource подумалось мне сделать вот так:
<model package="myPackage" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" phpdoc-package="myPackage" phpdoc-subpackage="" version="1.1">
  <object class="myObjectTemplate" extends="xPDOSimpleObject">
    <field key="active"                  dbtype="tinyint" precision="1"    attributes="unsigned" phptype="integer"   null="false" default="0" />
    <field key="deleted"                 dbtype="tinyint" precision="1"    attributes="unsigned" phptype="integer"   null="false" default="0" />
    <field key="createdby"               dbtype="int"     precision="10"   attributes="unsigned" phptype="integer"   null="false" default="0" />
    <field key="createdon"               dbtype="int"     precision="20"   attributes="unsigned" phptype="timestamp" null="false" default="0" />
    <field key="deletedby"               dbtype="int"     precision="10"   attributes="unsigned" phptype="integer"   null="false" default="0" />
    <field key="deletedon"               dbtype="int"     precision="20"   attributes="unsigned" phptype="timestamp" null="false" default="0" />
    <!-- в действительности будет ещё десяток таких полей + индексы к ним -->
  </object>
  <object class="myObjectOne" table="my_object_one" extends="myObjectTemplate">
    <field key="my_object_one_field"     dbtype="int"     precision="10"   attributes="unsigned" phptype="integer"   null="false" default="0" />
  </object>
  
  <object class="myObjectTwo" table="my_object_two" extends="myObjectTemplate">
    <field key="my_object_two_field"     dbtype="int"     precision="10"   attributes="unsigned" phptype="integer"   null="false" default="0" />
  </object>
</model>Таким образом, имеем объект-«шаблон», у которого прописаны определённые поля и не прописана таблица. И остальные классы, которые расширяют этот класс-«шаблон» и добавляют в себя собственные столбцы (коряво выразился, знаю).Так вот. При генерации все таблицы создаются абсолютно правильно, со всеми нужными полями — и в my_object_one, и в my_object_two есть столбцы active, deleted, createdby, createdon, etc… Т.е. такой механизм наследования как бы предумотрен и работает. Отлично.
Получилось у нас 3 файла с классами, соответствующие им map-файлы и metadata.mysql.php.
Содержимое metadata.mysql.php:
$xpdo_meta_map = array (
  'xPDOSimpleObject' => array (
    0 => 'myObjectTemplate',
  ),
  'myObjectTemplate' => array (
    0 => 'myObjectOne',
    1 => 'myObjectTwo',
  ),
);Как видим, наследование установлено верно.Содержимое map-файла класса-«шаблона» (жалко нету спойлеров, такие портянки нужно прятать):
$xpdo_meta_map['myObjectTemplate']= array (
  'package' => 'myPackage',
  'version' => '1.1',
  'extends' => 'xPDOSimpleObject',
  'fields' => array (
    'active' => 0,
    'deleted' => 0,
    'createdby' => 0,
    'createdon' => 0,
    'deletedby' => 0,
    'deletedon' => 0,
  ),
  'fieldMeta' => array (
    'active' => array (
      'dbtype' => 'tinyint',
      'precision' => '1',
      'attributes' => 'unsigned',
      'phptype' => 'integer',
      'null' => false,
      'default' => 0,
    ),
    'deleted' => array (
      'dbtype' => 'tinyint',
      'precision' => '1',
      'attributes' => 'unsigned',
      'phptype' => 'integer',
      'null' => false,
      'default' => 0,
    ),
// ну и так далее остальные поля
  ),
);Т.е. это просто описание столбцов и их свойств для myObjectTemplate. Код этот привожу для того, чтобы показать, что всё правильно. Ведь правильно же?
Вот аналогичное описание для класса myObjectOne:
$xpdo_meta_map['myObjectOne']= array (
  'package' => 'myPackage',
  'version' => '1.1',
  'table' => 'my_object_one',
  'extends' => 'myObjectTemplate',
  'fields' => array (
    'my_object_one_field' => 0,
  ),
  'fieldMeta' => array (
    'my_object_one_field' => array (
      'dbtype' => 'int',
      'precision' => '10',
      'attributes' => 'unsigned',
      'phptype' => 'integer',
      'null' => false,
      'default' => 0,
    ),
  ),
);Здесь видим описание того единственного столбца, который был указан в xml-схеме для этого класса, а так же строчку'extends' => 'myObjectTemplate',, которая говорит о том, что класс myObjectOne расширяется от myObjectTemplate. И снова всё верно. Ведь верно же?Напоминаю, что в примере с расширением класса modResource в map-файле унаследованного класса была бы точно такая же строчка
'extends' => 'modResource',Вот доказательства. Мукышщт :-)И класс Ticket спокойно наследует все поля, свойства и методы класса modResource. Все мы это знаем.
Т.е. в моём случае пока всё сгенерировано корректно.
Так вот к чему всё это? Развязка :-)
А вся проблема в том, что унаследованные поля в таблицу не записываются. От слова «совсем».
Т.е. выполнив этот код:
$o = $modx->newObject('myObjectOne');
$o->fromArray(array(
  'active' => 1,
  'createdon' => time(),
  'my_object_one_field' => 100500
));
// или так:
/*
$o->set('active', 1);
$o->set('createdon', time());
$o->set('my_object_one_field', 100500);
*/
$result = $o->save();в базу запишется только родное my_object_one_field поле и поле id с автоинкриментом. Унаследованные поля проигнорируются полностью!Вот такой код:
$o = $modx->newObject('myObjectOne');
print_r($o->toArray());
print_r($modx->getFields('myObjectOne'));выведет вот это:Array
(
    [id] => 
    [my_object_one_field] => 0
)
Array
(
    [id] => 
    [my_object_one_field] => 0
)И тут мы понимаем, что xPDO чихать хотел на поля, унаследованные от myObjectTemplate класса.Этот же xPDO сперва физически создаёт эти столбцы в таблицах, унаследовав их как надо, потом как надо генерирует map-файлы, а в конечном счёте чихать он на них хотел.
К слову сказать. В файлах самих объектов myobjectone.class.php и myobjecttwo.class.php подключается файл-«шаблон»:
<?php
require_once('myobjecttemplate.class.php');
class myObjectOne extends myObjectTemplate {}Т.е. проблема точно не в этом.Первой же мыслью было — «надо, чтобы для myObjectTemplate создавалась таблица!». Прописал в xml-схеме table=«table_blabla», всё перегенировал и нифига. Не работает.
Да и в конце концов, чем способ унаследования класса с несуществующей таблицей отличается от унаследования xPDOSimpleObject, который так же физически (в виде таблицы) не существует? Да ни чем, в сущности. Однако, с моими классами этот фокус не работает.
И что интересно — столбец id-то унаследован! Т.е. в цепочке myObjectOne -> myObjectTemplate -> xPDOSimpleObject, myObjectOne наследует столбец от xPDOSimpleObject, но не наследует столбцы от myObjectTemplate, как бы пропуская его.
И вот тут я, честно говоря, не знаю что делать. Документация на эту тему молчит, гугление плодов не принесло, а в исходниках я могу закопаться на неделю :-(
Конечно, остался вариант прописывать все эти поля для каждой таблицы в отдельности (а таких полей 12-15 штук + плюс индексы к ним). Но это просто нереально раздует xml-схему и уже после 5-6 таблицы ориентироваться в этом xml-аду будет невозможно (а таблиц будет 20-25). Это не просто вопрос удобства, а реально во время разработки будет невозможно скролить по 5-10-15 экранов вверх-вниз, чтобы прописывать связи и что-то там редактировать.
Хотелось бы универсального подхода. Да и в конце концов, xPDO, какого хрена??
В общем, дорогой читатель, такие дела. Надеюсь не сильно тебя утомил своими размышлениями и также надеюсь на твою помощь ибо в какую сторону копать — не представляю.
Комментарии: 6
                .            
                    // см. $modx->map['myObjectTemplate'];
$modx->loadClass('myObjectTemplate');
// а сейчас смотри см. $modx->map['myObjectTemplate'];
$o = $modx->newObject('myObjectOne');
$o->fromArray(array(
  'active' => 1,
  'createdon' => time(),
  'my_object_one_field' => 100500
));
$result = $o->save();            
                или в myobjectone.class.php дописать:
                    <?php
require_once('myobjecttemplate.class.php');
class myObjectOne extends myObjectTemplate {
	public function __construct(xPDO & $xpdo) {
		$xpdo->loadClass('myObjectTemplate',dirname(__FILE__).'/');
		parent::__construct($xpdo);
	}
}тогда не надо будет каждый раз при создании самому загружать класс:$o = $modx->newObject('myObjectOne');
$o->fromArray(array(
  'active' => 1,
  'createdon' => time(),
  'my_object_one_field' => 100500
));
$result = $o->save();            
                Вот спасибо тебе, добрый человек!
Ведь чувствовал же, что что-то где-то упускаю.
Жирный плюс тебе в карму :-)
Огромное количество времени и нервов съэкономлено)
                    Ведь чувствовал же, что что-то где-то упускаю.
Жирный плюс тебе в карму :-)
Огромное количество времени и нервов съэкономлено)
                Блин, так все просто… Спасибо!)))            
                    
                Т.к. не мне одному интересна данная тема и кто-то даже добавил этот тикет себе в избранное, то расскажу-ка я об ещё одной возникшей проблеме дабы сэкономить потомкам часы гугления и нервы.
Вот такой вот нехитрый код:
Дело в том, что такой вот унаследованный объект полученный методом $modx->getObject будет lazy (а вот через newObject всё хорошо). Не буду расписывать что это такое и почему. Факт в том, что сохраняться ничего не будет.
Чтобы всё работало как ожидается, надо в унаследованных классах переопределять метод set:
Лучше переопределить метод set и не заморачиваться. В этом случае $o->fromArray() тоже будет работать адекватно.
                    Вот такой вот нехитрый код:
$id = 1;
$o = $modx->getObject('myObjectOne', $id);
$o->fromArray(array(
  'active' => 0
));
// или так:
// $o->set('active', 0);
var_dump($o->save());будет приводить к вот такой вот ошибке (в modx-логе):Attempt to save lazy object: Array( <массив объекта> )
Дело в том, что такой вот унаследованный объект полученный методом $modx->getObject будет lazy (а вот через newObject всё хорошо). Не буду расписывать что это такое и почему. Факт в том, что сохраняться ничего не будет.
Чтобы всё работало как ожидается, надо в унаследованных классах переопределять метод set:
function set ($k, $v= null, $vType= '') {
  if ($this->isLazy()) $this->_loadFieldData($this->_lazy);
  return parent::set($k, $v, $vType);
}Либо перед изменением данных объекта делать $o->toArray();
// а потом что-то изменяем
$o->set('active', 0);Тогда тоже будет хорошо, но это не удобно.Лучше переопределить метод set и не заморачиваться. В этом случае $o->fromArray() тоже будет работать адекватно.
                            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.