Такой странный тайтл, но кто предложит лучше, перепишу…
Итак, это новые знания, полученные в процессе написания 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-объектов.