Илья
29 апр. 2015 г., 12:46

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% у тебя не оптимальная структура.

Добавить комментарий