Fi1osof 26 марта 2016 5 5
Расширения кругозора пост. Просто для демонстрации того, что можно делать с помощью xPDO, без задействования процессоров.

Задача стоит такая: есть каталог с вложенностью Каталог-Раздел-Товар. У большинства товаров в ТВ-поле указан производитель. Надо пройтись по всем товарам во всех разделах, создать в родительском разделе документ-производитель и «переместиться» в него. Конечно же на каждой итерации надо проверять есть ли уже такой родитель-производитель или нет. Если есть, то просто в него «перемещаться». Предлагаю внимательно изучить представленный код.


$q = $modx->newQuery('modResource');
$alias = $q->getAlias();
$q->innerJoin('modTemplateVarResource', "tv", "tv.tmplvarid = 22 AND tv.contentid = {$alias}.id");
$q->innerJoin('modResource', 'brand', "brand.id = tv.value");
$q->innerJoin('modResource', 'Parent');

$q->where(array(
    "Parent.template" => 2,
));

$q->select(array(
    "brand.pagetitle as brand",
    "{$alias}.*",
));

$q->sortby("{$alias}.id");

foreach($modx->getIterator('modResource', $q) as $doc){
    
    $data = array(
        "parent"    => $doc->parent,
        "pagetitle" => $doc->brand,
        "template"  => 23,
    );
    
    // Пытаемся получить раздел-производитель, если уже есть
    if(!$parent = $modx->getObject('modResource', $data)){
        
        // Если раздел не был получен, создаем новый
        $data = array_merge($data, array(
            "alias" => $doc->brand,
            "isfolder"  => 1,
            "published" => 1,
            "publishedon" => time(),
            "publishedby" => $modx->user->id,
            "createdby" => $modx->user->id,
            "createdon" => time(),
        ));
        
        $parent = $modx->newObject('modResource', $data);
        
        // Устанавливаем id не null, чтобы не ловить нотис
        $parent->set('id', 0);
    }
    
    // Устанавливаем родителя для текущего документа
    $doc->Parent = $parent;
    
    $doc->save();
    // print_r($doc-> toArray());
    // print_r($doc->Parent-> toArray());
}


Обратите внимание, что сам по себе объект $parent здесь нигде не сохраняется, даже когда создается новый. Он просто назначается текущему документу в качестве свойства $doc->Parent = $parent. Такой синтаксис эквивалентен вызову $doc->addOne($parent, 'Parent');
Так как же у нас родитель сохраняется, да еще и перемещение документа выполняется? Давайте посмотрим мап-файл класса modResource. Для этого класса прописана связь с псевдонимом Parent.
'aggregates' =>
array (
'Parent' =>
array (
'class' => 'modResource',
'local' => 'parent',
'foreign' => 'id',
'cardinality' => 'one',
'owner' => 'foreign',
),

Рассмотрим детальней параметры этой связи:
class — класс связанного объекта.
local — название колонки текущего объекта, участвующей в связи.
foreign — название колонки внешнего объекта.
cardinality — тип связи объектов (в нашем случае один-к-одному, бывает еще один-ко-многим).
owner — владелец, то есть кто главный в этой связке.
Вот владелец — это для нас самое главное. Этот признак влияет на то, свойства какого объекта будут назначены какому объекту. В нашем случае, владельцем является именно родитель Parent (foreign означает — внешний) и вот не смотря на то, что мы сохраняем дочерний документ, механизм xPDO работает таким образом, что сначала сохраняются связанные объекты, а потом уже сам объект (на самом деле потом и связанные объекты еще раз сохраняются, но не суть). В итоге, сначала у нас создается родительский новый документ-раздел (в этот момент он уже получает из базы данных свой id), затем этот id устанавливается текущему документу в качестве значения колонки parent, и когда уже сохраняется текущий документ, он фактически попадает в новый раздел. А если документ-раздел сразу был получен (то есть он не новый), то при сохранении просто его id назначается текущему документу.

Зачем так извращаться, когда можно просто воспользоваться процессорами? Отвечу. Вопрос просто в производительности. Как выяснилось уже не вчера, не смотря на то, что в create/update-процессорах документов есть параметры syncsite и clearCache, которые сигнализируют, что не надо сбрасывать кеш, несколькими строчками выше кеш сайта все равно сбрасывается (смотрим раз и два). Таким образом, обновление двух тысяч ресурсов займет и время не малое, да еще и сервер помучает. В моем случае я знал, что у меня не будет дублей, мне не требуется выполнения плагинов и т.п., поэтому я с чистой совестью поработал тут с объектами напрямую. В итоге, две тысячи документов перелопатилось за 25 секунд (это при том, что хостинг имеет серьезные ограничения на файловые иопсы). На каталогах в десятки тысяч товаров и более такой прием может значительно съэкономить ресурсы.

Не смотря на то, что все очень запутано, советую все-таки внимательно изучить этот материал, так как его понимание значительно вам поможет в разработке.
5 комментариев
n
niibaca-nah 26 марта 2016г в 03:09 #
Потрясающе, Николай! Не думал, что такой короткий код сделает столько действий! Я бы наговнокодил в данной задаче, определённо! :)
Fi1osof1
Fi1osof 26 марта 2016г в 03:11 #
Скоро доберусь, сделаю-таки кнопочку «Избранное». Подобные материалы однозначно маст хэв в избранных :)
n
niibaca-nah 26 марта 2016г в 03:17 #
Согласен. Тут много таких публикаций, которые я иногда с удовольствием могу перечитать.
c
cosmocarrot 06 декабря 2016г в 16:35 #
Такой синтаксис эквивалентен вызову $doc->addOne('Parent', $parent);
Первым должен быть объект, а вторым алиас.
Fi1osof1
Fi1osof 06 декабря 2016г в 16:47 #
Верно подметили :) Поправил.
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.