Добрый день. Хотелось бы разобраться. Столкнулся с проблемкой — вот код процессора: 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% у тебя не оптимальная структура.