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


Такой метод одновременного создания двух объектов в корне неверен или я просто упустил что-то?
7 комментариев
Fi1osof1
Fi1osof 29 апреля 2015г в 16:04 #
Илья, привет.

Чаще всего такое происходит, когда в мап-файле не для всех колонок есть описания. Смотри ветку здесь.

На счет твоего сохранения двух объектов: как я понимаю, это на самом деле повторное сохранение одного и того же объекта, просто в первом случае он создается новый, а во втором он дергается из базы. В целом твой процессор логически не правильный. Исключение — это только если ты допускаешь, что созданный $secondOp может оставаться сохраненным в базе данных, даже если первичный объект не удалось сохранить. Надо же понимать, что в beforeSet() create-процессора $this->object еще не сохранен, и у него нет ID-шника. При попытке выполнения этого процессора у тебя еще в beforeSet() сохраняется вторичный объект, хотя в итоге этот метод может вернуть ошибку (а если не он, то еще есть beforeSave(), который так же может вернуть ошибку), и первичный объект не сохранится, а вторичный объект уже будет в БД. Все-таки гораздо правильней в подобных случаях через связанные объекты делать. То есть задай связи этим двум классам и в beforeSet() пропиши типа $this->object->secondOp = $this->modx->newObject('Operation'); И при сохранении первичного объекта у тебя автоматически и вторичный сохранится. Конечно и здесь есть логические ошибки в xPDO, ибо при сохранении первичного объекта сначала сохраняются вторичные объекты, и только потом первичный (а потом и опять вторичные), но здесь уже вероятность ошибок будет гораздо меньше. Плюс к этому ты на любом этапе до сохранения первичного объекта можешь устанавливать любые значения вторичному объекту, и он будет сохранен только при попытке сохранить первичный объект. Если до первичного объекта дело не дойдет, то и вторичный не будет записан в БД.
ilyautkin1
ilyautkin 29 апреля 2015г в 16:06 #
О, а точно, спасибо, переделаю через связанные объекты. Посмотрим, что будет))
Fi1osof1
Fi1osof 29 апреля 2015г в 16:12 #
Но это не решит твоей проблемы с лейзи. Проверяй мапу. Ссылку я дал.
ilyautkin1
ilyautkin 29 июня 2015г в 11:49 #
Клево. Переделал код так:
<?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',
    ),
// ...


По идее должно работать. Но почему-то не хочет…
Fi1osof1
Fi1osof 30 июня 2015г в 00:32 #
Вот теперь код более чистый. Правда рудимент видимо остался — $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',
    ),
// ...
ilyautkin1
ilyautkin 30 июня 2015г в 09:57 #
Да, строчка
$this->setProperty('related', $secondOp->id);

лишняя (related проставит сам MODX, когда будет сохранять основной объект, на основе связи).

И понял, что без afterSave не обойтись. У меня оба объекта равнозначны и должны быть связаны между собой. То есть
$first = $this->object;
$second = $this->object->Related;
$second->addOne($this->object);

в итоге это не одна связь, а две. Поэтому еще одно сохранение делать придется в любом случае)
Fi1osof1
Fi1osof 30 июня 2015г в 21:18 #
У тебя связь один-к-одному. Зачем тебе две равнозначные связи на два объекта? Ты через один всегда сможешь понять есть у тебя второй объект или нет. 98% у тебя не оптимальная структура.
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.