Attempt to save lazy object при повторном сохранении объекта в процессоре

Добрый день. Хотелось бы разобраться. Столкнулся с проблемкой — вот код процессора: https://gist.github.com/ilyautkin/c26550d7c010340db2f3 <?php class OperationCreateProcessor extends modObjectCreateProcessor {

/* 
 * Процессор создает "Операции" для управления финансами
 * Операция - это расход, приход или перевод со счета на счет
 */
 
public $classKey = 'Operation';
public $objectType = 'object';

public function beforeSet() {
    $amount = $this->getProperty('amount');
    switch ($this->getProperty('type')) {
        case 'charge':
            $this->setProperty('amount', -1 * $amount);
            break;
        case 'income': 
            $this->setProperty('amount', 1 * $amount);
            break;
        case 'transfer':
            /*
             * Если перевод, то создаем 2-ую операцию так как
             * с одного счета деньги ушли (операция с минусом)
             * а на другой счет деньги поступили (с плюсом)
             */
            $secondOp = $this->modx->newObject('Operation');
            /* Все поля в обеих операциях дублируются */
            $secondOp->fromArray($this->getProperties());
            /* Кроме знака в сумме операции - плюс или минус */
            $secondOp->set('amount', 1 * $amount);
            /* И счета, к которому операция относится */
            $secondOp->set('account', $this->getProperty('to'));
            $secondOp->save();
            
            $this->setProperty('amount', -1 * $amount);
            $this->setProperty('account', $this->getProperty('from'));
            
            /* 
             * Операции хоть и разные сущности, но перевод
             * это единый процесс и операции надо связать.
             * Сначала в одном объекте указываем id другого
             * (мы его уже знаем)
             */
            $this->setProperty('related', $secondOp->id);
            $this->setProperty('relation_type', $this->getProperty('type'));
            break;
        default:
            $this->modx->error->addField('type', 'Unknown type');
            return false;
    }
    return true;
}

public function afterSave() {
    if ($this->getProperty('type') == 'transfer') {
        /*
         * А потом в другом объекте указываем id первой операции.
         * Этот id мы можем узнать только в методе afterSave
         */
        if ($secondOp = $this->modx->getObject('Operation',
                                        $this->getProperty('related'))
                                    ) {
            $secondOp->set('related', $this->object->id);
            $secondOp->set('relation_type', $this->getProperty('type'));
            /*
             * И в момент повторного сохранения этого объекта
             * MODX выдает ошибку "Attempt to save lazy object"
             */
            $secondOp->save();
        }
    }
    
    return true;
}

}

return 'OperationCreateProcessor'; Такой метод одновременного создания двух объектов в корне неверен или я просто упустил что-то?

Илья, привет. Чаще всего такое происходит, когда в мап-файле не для всех колонок есть описания. Смотри ветку здесь. На счет твоего сохранения двух объектов: как я понимаю, это на самом деле повторное сохранение одного и того же объекта, просто в первом случае он создается новый, а во втором он дергается из базы. В целом твой процессор логически не правильный. Исключение — это только если ты допускаешь, что созданный $secondOp может оставаться сохраненным в базе данных, даже если первичный объект не удалось сохранить. Надо же понимать, что в beforeSet() create-процессора $this->object еще не сохранен, и у него нет ID-шника. При попытке выполнения этого процессора у тебя еще в beforeSet() сохраняется вторичный объект, хотя в итоге этот метод может вернуть ошибку (а если не он, то еще есть beforeSave(), который так же может вернуть ошибку), и первичный объект не сохранится, а вторичный объект уже будет в БД. Все-таки гораздо правильней в подобных случаях через связанные объекты делать. То есть задай связи этим двум классам и в beforeSet() пропиши типа $this->object->secondOp = $this->modx->newObject('Operation'); И при сохранении первичного объекта у тебя автоматически и вторичный сохранится. Конечно и здесь есть логические ошибки в xPDO, ибо при сохранении первичного объекта сначала сохраняются вторичные объекты, и только потом первичный (а потом и опять вторичные), но здесь уже вероятность ошибок будет гораздо меньше. Плюс к этому ты на любом этапе до сохранения первичного объекта можешь устанавливать любые значения вторичному объекту, и он будет сохранен только при попытке сохранить первичный объект. Если до первичного объекта дело не дойдет, то и вторичный не будет записан в БД.

О, а точно, спасибо, переделаю через связанные объекты. Посмотрим, что будет))

Но это не решит твоей проблемы с лейзи. Проверяй мапу. Ссылку я дал.

Клево. Переделал код так: <?php class OperationCreateProcessor extends modObjectCreateProcessor {

/* 
 * Процессор создает "Операции" для управления финансами
 * Операция - это расход, приход или перевод со счета на счет
 */
 
public $classKey = 'Operation';
public $objectType = 'object';

public function beforeSet() {
    $amount = $this->getProperty('amount');
    switch ($this->getProperty('type')) {
        case 'charge':
            $this->setProperty('amount', -1 * $amount);
            break;
        case 'income': 
            $this->setProperty('amount', 1 * $amount);
            break;
        case 'transfer':
            $this->object->Related = $this->modx->newObject('Operation');
            $this->object->Related->fromArray($this->getProperties());
            $this->object->Related->set('amount', 1 * $amount);
            $this->object->Related->set('account', $this->getProperty('to'));
            $this->object->Related->set('relation_type', $this->getProperty('type'));

            $this->setProperty('amount', -1 * $amount);
            $this->setProperty('account', $this->getProperty('from'));
            $this->setProperty('related', $secondOp->id);
            $this->setProperty('relation_type', $this->getProperty('type'));
            break;
        default:
            $this->modx->error->addField('type', 'Unknown type');
            return false;
    }
    return true;
}

public function afterSave() {
    if ($this->getProperty('type') == 'transfer') {
        if ($this->object->Related) {
            $this->object->Related->set('related', $this->object->id);
            $this->object->Related->save();
        }
    }
    
    return true;
}

}

return 'OperationCreateProcessor'; И вот здесь опять вопрос. Вот есть строка, где связанные объекты сохраняются второй раз и в них передаются ключи основного объекта для сохранения связи. Однако у меня без блока afterSave у связанного объекта поле related оказывается незаполненным. Вот описание связи: // ... 'Related' => array ( 'class' => 'Operation', 'local' => 'related', 'foreign' => 'id', 'cardinality' => 'one', 'owner' => 'foreign', ), // ... По идее должно работать. Но почему-то не хочет…

Вот теперь код более чистый. Правда рудимент видимо остался — $this->setProperty('related', $secondOp->id); Объекта $secondOp нет у тебя в том методе. По идее должно работать. Но почему-то не хочет… У тебя связь неправильно прописана (если я не ошибаюсь, что эта связь прописана для мапы $this->object). У тебя в ней сказано, что owner — foreign, то есть будет использоваться значение из внешнего объекта (из его колонки id (ключ foreign)). То есть это описание для изменения локальной колонки $this->object->related, которая примет значение от $this->object->Related->id, а не наоборот. Если ты хочешь менять колонку внешнего объекта, то мапа должна быть такой: // ... 'Related' => array ( 'class' => 'Operation', 'local' => 'id', 'foreign' => 'related', 'cardinality' => 'one', 'owner' => 'local', ), // ...

Да, строчка $this->setProperty('related', $secondOp->id); лишняя (related проставит сам MODX, когда будет сохранять основной объект, на основе связи). И понял, что без afterSave не обойтись. У меня оба объекта равнозначны и должны быть связаны между собой. То есть $first = $this->object; $second = $this->object->Related; $second->addOne($this->object); в итоге это не одна связь, а две. Поэтому еще одно сохранение делать придется в любом случае)

У тебя связь один-к-одному. Зачем тебе две равнозначные связи на два объекта? Ты через один всегда сможешь понять есть у тебя второй объект или нет. 98% у тебя не оптимальная структура.