Николай Ланец
31 июля 2013 г., 13:55

Кастомные классы в одной таблице и превращения объектов в xPDO

Такой странный тайтл, но кто предложит лучше, перепишу…
Итак, это новые знания, полученные в процессе написания modSociety.
Хочу рассказать про один интересный момент: как различные классы «уживаются» в одной таблице. Простой пример: у нас есть несколько классов: modDocument, modWebLink и т.п., то есть Документ, Ссылка и т.п. (всего 4 из коробки). При этом все эти 4 класса не просто флажочки типа «тип ресурса», а реально разные объекты. Их объединят только две вещи: 1. Общая таблица modx_site_content 2. Общий предок modResource Как же так получается, что все эти объекты мы получаем через метод $modx->getObject('modResource', $id);? И в ответ мы еще и получает реально разные объекты.
Тут есть тонкость. В нашем случае каждая запись в таблице имеет class_key с указанием класса. Суть в том, что таблица одна, но xPDO хитро построен: если в таблице имеется колонка class_key, то при получении объекта, он инициализирует именно этот класс, и в этот объект набивает данные.
То есть делаем запрос $d = $modx->getObject('modResource', 1); xPDO формирует запрос на основе класса, получает данные из БД, но прежде чем вернуть конечный объект с этими данными, он сначала проверяем ключ class_key в этих данных, и если есть такая колонка, то инициализирует именно указанный класс. То есть если там class_key=SocietyBlog, то в итоге он вернет объект SocietyBlog с данными из этой таблицы.
Все это происходит в методе xPDOObject::_loadInstance()
public static function _loadInstance(& $xpdo, $className, $criteria, $row) { $rowPrefix= ''; if (is_object($criteria) && $criteria instanceof xPDOQuery) { $alias = $criteria->getAlias(); $actualClass = $criteria->getClass(); } elseif (is_string($criteria) && !empty($criteria)) { $alias = $criteria; $actualClass = $className; } else { $alias = $className; $actualClass= $className; } if (isset ($row["{$alias}_class_key"])) { $actualClass= $row["{$alias}_class_key"]; $rowPrefix= $alias . '_'; } elseif (isset($row["{$className}_class_key"])) { $actualClass= $row["{$className}_class_key"]; $rowPrefix= $className . '_'; } elseif (isset ($row['class_key'])) { $actualClass= $row['class_key']; } $instance= $xpdo->newObject($actualClass); if (is_object($instance) && $instance instanceof xPDOObject) { $pk = $xpdo->getPK($actualClass); if ($pk) { if (is_array($pk)) $pk = reset($pk); if (isset($row["{$alias}_{$pk}"])) { $rowPrefix= $alias . '_'; } elseif ($actualClass !== $className && $actualClass !== $alias && isset($row["{$actualClass}_{$pk}"])) { $rowPrefix= $actualClass . '_'; } elseif ($className !== $alias && isset($row["{$className}_{$pk}"])) { $rowPrefix= $className . '_'; } } elseif (strpos(strtolower(key($row)), strtolower($alias . '_')) === 0) { $rowPrefix= $alias . '_'; } elseif (strpos(strtolower(key($row)), strtolower($className . '_')) === 0) { $rowPrefix= $className . '_'; } $parentClass = $className; $isSubPackage = strpos($className,'.'); if ($isSubPackage !== false) { $parentClass = substr($className,$isSubPackage+1); } if (!$instance instanceof $parentClass) { $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Instantiated a derived class {$actualClass} that is not a subclass of the requested class {$className}"); } $instance->_lazy= $actualClass !== $className ? array_keys($xpdo->getFieldMeta($actualClass)) : array_keys($instance->_fieldMeta); $instance->fromArray($row, $rowPrefix, true, true); $instance->_dirty= array (); $instance->_new= false; } return $instance; }
При этом ты можешь в запросе указывать любой класс, хоть $modx->getObject('SocietyBlog');, хоть $modx->getObject('modDocument');, он все равно вернет конечный объект тот, ключ которого указан.
Колонка class_key есть и в таблицах modx_users и modx_media_sources. Но самое интересно то, что вы и в своих таблицах можете использовать эту колонку, чтобы хранить данные разных объектов, все будет работать нативно.
Хотя нет, это еще не самое интересное… Вот такая идея возникла: посмотрите на запрос
$q = $modx->newQuery('modResource'); $q->where(array( 'modResource.id' => '1' )); $q->select(array( 'modResource.*', "'SocietyBlog' as class_key", "'SocietyBlog' as modresourceclass_key" )); $obj = $modx->getObject('modResource', $q); print_r($obj->toArray());
Как вы думаете, какой объект вернет функция $modx->getObject('modResource', $q);? Правильно — SocietyBlog. То есть получается, что на уровне селекта можно определить в какой объект в итоге все это выльется, при чем мало того, что мы можем переопределить значение колонки class_key, получается, мы этот фокус можем выполнить там, где этой колонки вообще нет. То есть на уровне простого запроса переопределить любой MODX-объект, который в принципе не предполагался расширяться. Правда надо еще более внимательно все это изучать, так как здесь идет проверка на наследственность объектов, но это потом. Основная мысль — можно использовать системные таблицы для хранения каких-то своих данных, и наоборот, данные из системных таблиц забивать в свои объекты. Я понимаю, что все это можно сделать и другими методами, но все-таки что-то в этом есть. Как минимум построение SQL-запросов, основываясь на связях MODX-объектов.

Добавить комментарий