PHP事务管理器跨资源

In our Symfony2 project, we would like to ensure that modifications across resources are transactional. For example, something like:

namespace ...;
use .../TransactionManager;

class MyService {

    protected $tm;

    public function __construct(TransactionManager $tm)
    {
        $this->tm = $tm;
    }

    /**
     * @ManagedTransaction
     */
    public function doSomethingAcrossResources()
    {
        ...

        // where tm is the transaction manager
        // tm is exposing a Doctrine EntityManager adapter here
        $this->tm->em->persist($entity);

        ...

        // tm is exposing a redis adapter here
        $this->tm->redis->set('foo', 'bar');

        if ($somethingWentWrong) {
            throw new Exception('Something went terribly wrong');
        }
    }
}

So there are a couple things to note here:

  1. Every resource will need an adapter exposing it's API (e.g. a Doctrine adapter, Redis adapter, Memcache adapter, File adapter, etc.)
  2. In the case that something goes wrong (Exception thrown), nothing should get written to any managed resource (i.e. rollback everything).
  3. If nothing goes wrong, all resources will get updated as expected
  4. doSomethingAcrossResources function does not have to worry about un-doing changes it made to non-transactional resources like Files and Memcache for example. This is key, because otherwise, this code would likely become a tangled mess of only writing to redis at the appropriate time, etc.
  5. @ManagedTransacton annotation will take care of the rest (commiting / rolling back / starting the transactions required (based on the adapters), etc.)
  6. In the simplest implementation, the tm can simply manage a queue and dequeue all items serially. If an exception is thrown, it simply won't dequeue anything. So the adapters are the transaction manager's knowledge of how to commit each item in the queue.
  7. If an exception occurs during a dequeue, then the transaction manager will look to it's adapters for how to rollback the already dequeued items (probably placed in a rollback stack). This might get tricky for resources like the EntityManager that would need to manage a transaction internally in order to rollback the changes easily. However, redis adapter might cache a previous value during an update, or during an ADD simply issue a DELETE during a rollback.

Does a transaction manager like this already exist? Is there a better way of achieving these goals? Are there caveats that I may be overlooking?

Thanks!

It turns out that we ended up not needing to ensure atomicity across our resources. We do want to be atomic with our database interactions when multiple rows / tables are involved, but we decided to use an event driven architecture instead.

If, say, updating redis fails inside of an event listener, we will stop propagation, but it's not the end of the world -- allowing us to inform the user of a successful operation (even if side effects were not successful).

We can run background jobs to occasionally update redis as needed. This enables us to focus the core business logic in a service method, and then dispatch an event upon success allowing non-critical side effects (updating cache, sending emails, updating elastic search, etc.) to take place, isolated from one another, outside the main business logic.