Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a Transaction object #634

Closed
wants to merge 50 commits into from

Conversation

BenMorel
Copy link
Contributor

This is a proposal for a Transaction object, as suggested by @guilhermeblanco and @beberlei in doctrine/orm#949; this is a foundation for the proposed ORM feature. This supersedes #571.

This PR makes the following changes to Connection:

  • Adds the following method:
    • getTransactionManager() : returns the TransactionManager
  • Deprecates the following methods, in favour of their counterparts on Transaction:
    • commit()
    • rollBack()
    • setRollbackOnly()
    • isRollbackOnly()
  • Deprecates the following methods, in favour of their counterparts on TransactionManager:
    • beginTransaction()
    • isTransactionActive()
    • getTransactionNestingLevel()
    • setNestTransactionsWithSavepoints()
    • getNestTransactionsWithSavepoints()

The new way of dealing with transactions is now:

$transactionManager = $connection->getTransactionManager();
$transaction = $transactionManager->beginTransaction();
// ...
$transaction->commit();

A fluent API is available to configure a transaction:

$transaction = $transactionManager->createTransaction()
    ->withIsolationLevel(Connection::TRANSACTION_SERIALIZABLE)
    ->begin();

Calls to commit() and rollback() are automatically propagated to nested transactions:

$transaction1 = $transactionManager->beginTransaction();
$transaction2 = $transactionManager->beginTransaction();
$transaction1->commit(); // will commit $transaction2 then $transaction1

Overall, it's not a complicated change, does not introduce any BC break, and passes all existing tests.

@doctrinebot
Copy link

Hello,

thank you for creating this pull request. I have automatically opened an issue
on our Jira Bug Tracker for you. See the issue link:

http://www.doctrine-project.org/jira/browse/DBAL-947

We use Jira to track the state of pull requests and the versions they got
included in.

@BenMorel BenMorel changed the title Transaction object definition Introduce a Transaction object, now with a TransactionDefinition Jul 21, 2014
@Ocramius
Copy link
Member

Ocramius commented Aug 4, 2014

@BenMorel this looks very clean! Nice work!

*/
protected function _getNestedTransactionSavePointName()
public function createTransaction()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be on the TransactionManager, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far, I've considered the TransactionManager to be a lower-level class, that we should not have to access directly, as I thought it might be cumbersome to have work with a reference to the TransactionManager in addition to the Connection in our apps. But I theoretically agree with you. What do others think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the API needs to be exposed here, that can be done later as well. Creating the API on the TransactionManager for now would avoid adding more public methods on the Connection...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. Let's remove it for now then!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there already is a createTransaction() method on the TransactionManager. This one does create the actual Transaction, whereas Connection::createTransaction() returns a TransactionDefinition that can later start the actual transaction using begin().

So there is a naming conflict here. I think TransactionManager::createTransaction() should be kept as is, because it creates the actual Transaction object from a TransactionDefinition:

public function createTransaction(TransactionDefinition $definition)

But I can't think of a good name for a method that would return the TransactionDefinition. Would something like prepareTransaction() fit?

$transactionManager->prepareTransaction()->begin();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could be a solution. Another suggestion could be buildTranslation (maybe renaming TransactionDefinition to TransactionBuilder as it is a builder object for the Transaction). this would match the naming used in the Symfony Validator 2.5 API for violations

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The builder is a good idea, I've actually extracted the TransactionBuilder out of the TransactionDefinition: the TransactionDefinition is still there but is now immutable, and can safely be shared between transactions without carrying the risk to be altered.

Re-reading the whole thing, I've come to realize that the existing TransactionManager::createTransaction() should actually be called beginTransaction(), so I've renamed it. This method can also now be called directly if we want to begin a transaction with the defaults parameters, without using the builder:

$transactionManager->beginTransaction();

TransactionManager::createTransaction() now returns the TransactionBuilder; I've chosen to keep the name createTransaction() for now, as it feels more natural to me:

$transactionManager->createTransaction()                       // TransactionBuilder
    ->withIsolationLevel(Connection::TRANSACTION_SERIALIZABLE) // TransactionBuilder
    ->begin();                                                 // Transaction

@stof What do you think?

@Ocramius
Copy link
Member

Ocramius commented Aug 5, 2014

@BenMorel just looking at this again: do you think we can extract the newly introduced methods into interfaces? Would it makes sense?

@BenMorel
Copy link
Contributor Author

BenMorel commented Aug 5, 2014

@Ocramius What's the motivation behind this? If there is a use case, why not, otherwise I'm happy to keep it simple as it is!

@BenMorel
Copy link
Contributor Author

As mentioned in doctrine/orm#949, I think the last change we could make here is to replace the explicit API in the TransactionBuilder:

->withIsolationLevel(Connection::TRANSACTION_SERIALIZABLE)

with a generic API like:

->with(Connection::ISOLATION_LEVEL, Connection::TRANSACTION_SERIALIZABLE);

We could now even further simplify the whole thing by replacing the TransactionDefinition with a simple associative array!

@stof
Copy link
Member

stof commented Aug 23, 2014

@BenMorel -1 for the generic API. The explicit API is much easier to use for people (decovrability, IDE completion, etc...).

Regarding the ORM stuff, @guilhermeblanco was suggesting to have an Doctrine\ORM\Transaction object handling ORM-specific stuff for the transactions (it would extend the DBAL one of course)

@BenMorel
Copy link
Contributor Author

@stof While I tend to agree with you on this point, I've weighted the pros and cons of both approaches, and I really like the simple configuration array. I've just pushed a commit that implements this, can you please have a look? I'll be happy to revert it if you are really against it.

@stof
Copy link
Member

stof commented Aug 23, 2014

the goal of having a builder opposed to providing a $options argument in the constructor is precisely about providing a better API for the developer, so that he knows what can be done.

And having constants whch are a mix of config values and of config keys is not a good DX either (they can't even look at the available constants to know which settings are available).

so for me, such generic builder API is a big -1. It totally defeats its purpose

@BenMorel
Copy link
Contributor Author

Ok, back to the drawing board then. I'll see what can be done without adding too much complexity...

@stof
Copy link
Member

stof commented Aug 23, 2014

Well, I think we might drop the Configuration class and use an array here (taking care of the default values when being passed to the constructor though, which is not done in your code). But the builder is much nicer IMO

@BenMorel
Copy link
Contributor Author

We don't need to add default values in the constructor, as every code consuming the transaction configuration will check if the given configuration is set, like in the TransactionManager:

if (isset($configuration[Connection::ISOLATION_LEVEL])) {
    $this->connection->setTransactionIsolation($configuration[Connection::ISOLATION_LEVEL]);
}

@BenMorel
Copy link
Contributor Author

Thinking about it again, the solution that would check all the boxes would then be to keep the configuration array for its simplicity (we could still call TransactionManager::beginTransaction() directly with a configuration array if we know what we're doing), but to have TransactionBuilder exhibit an explicit API with methods such as withIsolationLevel() and internally populating the configuration array.

We would provide a base TransactionBuilder in the DBAL, accessible through the TransactionManager:

$transaction = $transactionManager->createTransaction()
    ->withIsolationLevel(Connection::TRANSACTION_SERIALIZABLE)
    ->begin();

In the ORM, we would create a ORM\TransactionBuilder extending the DBAL\TransactionBuilder, accessible through the EntityManager, and adding ORM-specific methods:

$transaction = $entityManager->createTransaction()
    ->withIsolationLevel(Connection::TRANSACTION_SERIALIZABLE)
    ->withDefaultLockMode(LockMode::PESSIMISTIC_WRITE)
    ->begin();

Is that OK for you, @stof ?

@stof
Copy link
Member

stof commented Aug 23, 2014

yeah, looks good (and please don't introduce constants for the array keys)

@BenMorel
Copy link
Contributor Author

Great then, I'll give it a go. I won't introduce constants in the Connection, but I think it would be better to have constants for all available configuration keys in the TransactionBuilder classes, otherwise we will have to repeat the configuration key strings in different places, with the risk of mistake it carries.

@BenMorel
Copy link
Contributor Author

Here it is. It's a simple modification after all, and creating the ORM\TransactionBuilder should be as simple as:

class TransactionBuilder extends \Doctrine\DBAL\TransactionBuilder
{
    const DEFAULT_LOCK_MODE = 'orm.default-lock-mode';

    public function withDefaultLockMode($lockMode)
    {
        return $this->with(self::DEFAULT_LOCK_MODE, $lockMode);
    }
}

The generic with() method is now protected, so the consumer of the TransactionBuilder will only see the explicit methods.

@BenMorel BenMorel force-pushed the transaction-object-definition branch from 6a33b6a to a6985cb Compare August 23, 2014 11:22
@BenMorel BenMorel changed the title Introduce a Transaction object, now with a TransactionDefinition Introduce a Transaction object Aug 23, 2014
@BenMorel
Copy link
Contributor Author

Any update on this PR? If anyone has reserves about it, I would be happy to further discuss this, otherwise it would be great if we could merge this one and doctrine/orm#949.

This would really be a useful addition to Doctrine.

@Ocramius
Copy link
Member

@BenMorel it won't land in 2.5.0 for now, we'll have to delay the merge.

@BenMorel
Copy link
Contributor Author

@Ocramius When is 2.5 supposed to be released?

@Ocramius
Copy link
Member

@BenMorel as soon as possible :-\

@stof
Copy link
Member

stof commented Oct 4, 2014

When is 2.5 supposed to be released?

We are already in RC2, meaning it is feature-frozen

@deeky666 deeky666 added this to the 2.6 milestone Oct 23, 2014
@BenMorel
Copy link
Contributor Author

BenMorel commented Nov 3, 2019

@morozov Has this been implemented elsewhere?

@morozov
Copy link
Member

morozov commented Nov 3, 2019

No. GitHub closed this PR because I removed the develop branch.

@guilhermeblanco
Copy link
Member

@morozov by removing the develop branch you just broke ORM master... =(

@Ocramius
Copy link
Member

Ocramius commented Nov 4, 2019 via email

@guilhermeblanco
Copy link
Member

What about the work done there?

@morozov
Copy link
Member

morozov commented Nov 4, 2019

@guilhermeblanco develop just got merged into master. All the work is there.

@greg0ire
Copy link
Member

greg0ire commented Nov 4, 2019

So this should be retargetted to master, then reopened? Or should develop be re-created from master?

@morozov morozov reopened this Nov 4, 2019
@morozov morozov changed the base branch from develop to master November 4, 2019 14:32
@morozov
Copy link
Member

morozov commented Nov 4, 2019

So this should be retargetted to master, then reopened? Or should develop be re-created from master?

Apparently, in the reverse order. Only open PR can be retargeted. I've created the develop branch temporarily to identify existing PRs and retarget them. Apart from that, develop is not needed until 3.0.0 is released.

@morozov morozov removed the Incomplete label Dec 4, 2020
Base automatically changed from master to 4.0.x January 22, 2021 07:43
@morozov
Copy link
Member

morozov commented Oct 26, 2021

Closing as stale.

@morozov morozov closed this Oct 26, 2021
@BenMorel
Copy link
Contributor Author

@morozov Is there still any interest in this? I'm still interested in pushing this idea, but never managed to get enough traction.

@morozov
Copy link
Member

morozov commented Oct 26, 2021

I still don't understand what problem it solves. So it would make sense to resume the discussion with an RFC.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.