7

I'm writing an application which supports multiple units of measurement. In the rare event a user wanted to change their system of measurement, I need to run a query which applies a multiplier to scale every unit column in the application to the correct system of measurement. To make sure all the data stays sane in the event something in this operation goes wrong, I need to run the queries inside a transaction.

Is it possible in Cake to perform a transaction which contains queries covering multiple models?

All I've found so far is DataSource::begin/commit/rollback(), but that only supports queries against single models.

tereško
  • 58,060
  • 25
  • 98
  • 150
Brad Koch
  • 19,267
  • 19
  • 110
  • 137
  • 1
    Possible duplicate of http://stackoverflow.com/questions/3013347/transaction-management-with-multiple-models-using-single-transaction-commit-and-r – 8vius Aug 29 '11 at 21:17
  • 1
    Ugh, you're right. Same fundamental question, and apparently there's no solution judging by the lack of an appropriate answer. – Brad Koch Aug 29 '11 at 21:35
  • If what you want is to save several related models you can just use the saveAll() method – 8vius Aug 29 '11 at 21:39
  • Hmm.. I am fortunate enough to have them all linked together under the model for the user's account, but I want to do the update on the database server (UPDATE SET field = field * 10) rather than pulling the data, doing the multiplication application side, and then pushing the result back. Which is forcing me to use the Model::query() method, so saveAll is out. I wonder if I'd be able to start and stop the transaction on the parent model though and achieve the desired result.. I'll check. – Brad Koch Aug 29 '11 at 21:55
  • Good news- if it all falls underneath the same parent model, the query sequence works out properly. That's all fixed now; just need to sort out this questions' duplicate status. – Brad Koch Aug 30 '11 at 01:23
  • Good to hear, well you could just delete the question :P – 8vius Aug 30 '11 at 04:25

1 Answers1

8

yes it does. I have this in my app model to make transactions easy.

https://github.com/infinitas/infinitas/blob/dev/Model/AppModel.php#L677

    /**
     * @brief wrapper for transactions
     *
     * Allow you to easily call transactions manually if you need to do saving
     * of lots of data, or just nested relations etc.
     *
     * @code
     *  // start a transaction
     *  $this->transaction();
     *
     *  // rollback if things are wrong (undo)
     *  $this->transaction(false);
     *
     *  // commit the sql if all is good
     *  $this->transaction(true);
     * @endcode
     *
     * @access public
     *
     * @param mixed $action what the command should do
     *
     * @return see the methods for tranasactions in cakephp dbo
     */
    public function transaction($action = null) {
        $this->__dataSource = $this->getDataSource();
        $return = false;
        if($action === null) {
            $return = $this->__dataSource->begin($this);
        } else if($action === true) {
            $return = $this->__dataSource->commit($this);
        } else if($action === false) {
            $return = $this->__dataSource->rollback($this);
        }
        return $return;
    }

then you can do something like this:

$saved = true;
$this->transaction();
$saved = $saved && $this->save($data);
$saved = $saved && $this->SomeOtherModel->save($data2);
$saved = $saved && $this->AnotherModel->save($data3);

if($saved){
$this->transaction(true);
    return $this->id;
}
$this->transaction(false);
return false;

you can also do more complex things like the following:

function save1(){

    $saved = true;
    $this->transaction();
    $saved = $saved && $this->save($data);
    $saved = $saved && $this->save2($data);


    if($saved){
        $this->transaction(true);
        return $this->id;
    }

    $this->transaction(false);
    return false;
}

cake does not support nested transactions, but you can sort of fake them

// this will use transactions if its called directly, but will allow a calling method to

// create and manage the transaction.

function save2($data){
    $saved = true;
    $transaction = $this->transaction(); // will only be true if not already started
    $saved = $saved && $this->save($data);

    if($transaction){ // if it was started here, finish it
        if($saved){
            $this->transaction(true);
            return true;
        }

        $this->transaction(false);
        return false;
    }

    return $saved; // return just the status so the other model will finish the transaction
}

just to be clear, you could be doing something like ClassRegistry::init('SomeRandomModel')->save2(). the transactions are not limited to the current model, or related models. its for any model.

dogmatic69
  • 7,574
  • 4
  • 31
  • 49
  • Hmm, you're right. I had thought the DboSource::begin() function was doing some model specific actions since Dbo it requires the model as a parameter, but apparently it doesn't actually use it for anything. https://github.com/cakephp/cakephp/blob/1.3/cake/libs/model/datasources/dbo_source.php#L1799. – Brad Koch Aug 30 '11 at 13:45
  • pretty much all methods in dbo and behaviors take a model as the first param. I would think the method dispatching the calls does this and thus even when its not needed you get it. – dogmatic69 Sep 05 '11 at 14:05
  • 1
    Cake actually does support nested transactions in 2.X. See http://book.cakephp.org/2.0/en/models/transactions.html#nested-transactions – jeffslofish May 24 '13 at 23:19
  • 1
    This is actually pretty great - shouldn't that be a standard functionality? It makes transactions in cake a lot easier. – Nikel Weis Nov 25 '14 at 15:53