Transactions (Unit of Work)

Every persist operation for an entity must be performed via the Transaction object. This object will rely on Heap associated with the given ORM and will request each entity mapper and relation to issue a set of persist commands in the form of a command chain.

All transactions are treated as disposable, you can create and delete them as you need.

TransactionInterface

ORM provides a convenient class to manage transactions, however, it is recommended to couple your code with an underlying interface for the simplicity and easier testing going forward:

interface TransactionInterface
{
    // how to store/delete entity
    public const MODE_CASCADE     = 0;
    public const MODE_ENTITY_ONLY = 1;

    /**
     * Persist the entity.
     *
     * @param object $entity
     * @param int    $mode
     */
    public function persist($entity, int $mode = self::MODE_CASCADE);

    /**
     * Delete entity from the database.
     *
     * @param object $entity
     * @param int    $mode
     */
    public function delete($entity, int $mode = self::MODE_CASCADE);

    /**
     * Execute all nested commands in transaction, if failed - transaction MUST automatically
     * rollback and exception instance MUST be thrown.
     *
     * @throws \Throwable
     */
    public function run();
}

Example Usage

To persist your entity simply add it to the transaction using the persist method and call the method run after that.

$t = new Transaction($orm);
$t->persist($e);
$t->run();

Do not forget to handle Spiral\Database\Exception\DatabaseException.

You can handle multiple transactions within one scope. They can overlap or be responsible for separate entities:

$t = new Transaction($orm);
$t->persist($e);
$t->run();

$t2 = new Transaction($orm);
$t2->persist($e2);
$t2->run();

After execution the Transaction will be cleared and can be re-used for the next batch of commands. Please note, ORM Heap would not be reset after the transaction, you must clean it manually if needed.

$t = new Transaction($orm);

for ($i=0; $i<1000; $i++) {
    $t->persist($e);
    $t->run();
}

$orm->getHeap()->clean();

Cascade persisting

By default, Transaction will create a command chain to store all entity relations unless the opposite is specified in the relation schema. If you would like to store only entity content without it's relations use the persist option MODE_ENTITY_ONLY:

$t = new Transaction($orm);
$t->persist($e, Transaction::MODE_ENTITY_ONLY);
$t->run();

Transaction Runner

In some cases, you might want to implement your own persist logic or sorting for issued commands. You can create your own Transaction implementation or provide the second argument to the transaction to handle actual command execution:

interface RunnerInterface extends \Countable
{
    /**
     * @param CommandInterface $command
     */
    public function run(CommandInterface $command);

    /**
     * Complete/commit all executed changes. Must clean the state of the runner.
     */
    public function complete();

    /**
     * Rollback all executed changes. Must clean the state of the runner.
     */
    public function rollback();
}

For example, we can log the order of which commands are being executed by wrapping the original transaction Runner:

class LogRunner implements RunnerInterface
{
    private $runner;

    public function __construct()
    {
        $this->runner = new Cycle\ORM\Transaction\Runner();
    }

    public function run(CommandInterface $command)
    {
        print_r($command);
        $this->runner->run($command);
    }

    public function complete()
    {
        $this->runner->complete();
    }

    public function rollback()
    {
        $this->runner->rollback();

    }
}

To use it:

$t = new Transaction($orm, new LogRunner());
$t->persist($e);
$t->run();

Reusing same Transaction

The Transaction is clean after the run invocation. You must assemble a new transaction to retry the same set of entities. Since the transaction is clean you are able to reuse the same transaction over and over again (make sure to keep persist, delete and run operations within one method).

Edit this page