Fi1osof 31 июля 2013 1 0
Такой странный тайтл, но кто предложит лучше, перепишу…

Итак, это новые знания, полученные в процессе написания 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-объектов.
0 комментариев
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.