Такой странный тайтл, но кто предложит лучше, перепишу… Итак, это новые знания, полученные в процессе написания modSociety. Хочу рассказать про один интересный момент: как различные классы «уживаются» в одной таблице. Простой пример: у нас есть несколько классов: modDocument, modWebLink и т.п., то есть Документ, Ссылка и т.п. (всего 4 из коробки).
При этом все эти 4 класса не просто флажочки типа «тип ресурса», а реально разные объекты. Их объединят только две вещи:
- Общая таблица modx_site_content
- Общий предок 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-объектов.