Наследуемость полей у расширяемых 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, какого хрена??

В общем, дорогой читатель, такие дела. Надеюсь не сильно тебя утомил своими размышлениями и также надеюсь на твою помощь ибо в какую сторону копать — не представляю.
Алексей Карташов
12 ноября 2014, 22:52
modx.pro
12
2 918
+2

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

Алексей Карташов
13 ноября 2014, 01:52
0
.
    wld
    wld
    13 ноября 2014, 23:24
    +1
    // см. $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();
      wld
      wld
      13 ноября 2014, 23:51
      +2
      или в 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();
        Алексей Карташов
        15 ноября 2014, 10:04
        0
        Вот спасибо тебе, добрый человек!
        Ведь чувствовал же, что что-то где-то упускаю.

        Жирный плюс тебе в карму :-)
        Огромное количество времени и нервов съэкономлено)
          Илья Уткин
          15 ноября 2014, 13:30
          0
          Блин, так все просто… Спасибо!)))
            Алексей Карташов
            19 ноября 2014, 16:52
            2
            +1
            Т.к. не мне одному интересна данная тема и кто-то даже добавил этот тикет себе в избранное, то расскажу-ка я об ещё одной возникшей проблеме дабы сэкономить потомкам часы гугления и нервы.

            Вот такой вот нехитрый код:
            $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() тоже будет работать адекватно.
      Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
      6