Transactions
The n2n API allows you to start transactions that span multiple resources. For example, the API enables safe working with multiple database connections or FileManagers. In addition, it is much easier to control transactions via the n2n API than, for example, via a PDO object.
Transactions in the Controller
The simplest way is to control transactions in the controller using the methods ControllerAdapter::beginTransaction(), ControllerAdapter::commit() and ControllerAdapter::rollBack().
class ExampleController extends ControllerAdapter {
public function doOrm(ExampleDao $exampleDao) {
$this->beginTransaction();
$exampleDao->saveArticle(new Article('lorem ipsum'));
$this->commit();
}
}
If an exception is thrown or a fatal error is triggered between ControllerAdapter::beginTransaction() and ControllerAdapter::commit(), the transaction is rolled back.
public function doOrm(ExampleDao $exampleDao) {
$this->beginTransaction();
$exampleDao->saveArticle(new Article('lorem ipsum'));
throw new IllegalStateException('Something went wrong.'); // leads to rollback
$this->commit();
}
In the example above, the new article is not saved because the transaction is rolled back.
Exceptions of type n2n\web\http\StatusException are an exception. These lead to a commit.
public function doArticle($id, ExampleDao $exampleDao) {
$this->beginTransaction();
$article = $exampleDao->getArticleById($id);
if ($article === null) {
$exampleDao->createNotFoundLogEntry($id);
throw new PageNotFoundException();
}
$this->commit();
}
If a n2n\web\http\PageNotFoundException is thrown in the example above (it extends n2n\web\http\StatusException), the transaction is still committed. If ExampleDao::createNotFoundLogEntry() writes a log entry to the database, it is therefore saved.
Transactions that are started with ControllerAdapter::beginTransaction() but not explicitly ended are also committed.
Transaction Manager
Outside of controllers, you can control transactions via the magic type n2n\core\container\TransactionManager.
TransactionManager::createTransaction() starts a transaction and returns it as an object of type n2n\core\container\Transaction. The transaction can then be ended with Transaction::commit() or Transaction::rollBack().
class ExampleDao implements RequestScoped {
private $em;
private $tm;
private function _init(EntityManager $em, TransactionManager $tm) {
$this->em = $em;
$this->tm = $tm;
}
public function saveArticle(Article $article) {
$tx = $this->tm->createTransaction();
$this->em->persist($article);
$tx->commit();
}
}
Transaktionen
Tags: Transaktionen
The n2n API allows you to start transactions that span multiple resources. For example, the API enables safe working with multiple database connections or FileManagers. In addition, it is much easier to control transactions via the n2n API than, for example, via a Pdo object.
Transactions in the Controller
The simplest way is to control transactions in the controller using the methods ControllerAdapter::beginTransaction(), ControllerAdapter::commit() and ControllerAdapter::rollBack().
class ExampleController extends ControllerAdapter {
public function doOrm(ExampleDao $exampleDao) {
$this->beginTransaction();
$exampleDao->saveArticle(new Article('lorem ipsum'));
$this->commit();
}
}
If an exception is thrown or a fatal error is triggered between ControllerAdapter::beginTransaction() and ControllerAdapter::commit(), the transaction is rolled back.
public function doOrm(ExampleDao $exampleDao) {
$this->beginTransaction();
$exampleDao->saveArticle(new Article('lorem ipsum'));
throw new IllegalStateException('Something went wrong.'); // leads to rollback
$this->commit();
}
In the example above, the new article is not saved and is rolled back (rollback).
Exceptions of type n2n\web\http\StatusException are an exception (see the StatusException section in the StatusException article). These lead to a commit.
public function doArticle($id, ExampleDao $exampleDao) {
$this->beginTransaction();
$article = $exampleDao->getArticleById($id);
if ($article === null) {
$exampleDao->createNotFoundLogEntry($id);
throw new PageNotFoundException();
}
$this->commit();
}
If a n2n\web\http\PageNotFoundException (it extends n2n\web\http\StatusException) is thrown in the example above, the transaction is still committed. Assuming that ExampleDao::createNotFoundLogEntry() writes a log entry to the database, it would be created.
Transactions that are started with ControllerAdapter::beginTransaction() but not ended are also committed.
Transaction Manager
Outside of controllers, you can control transactions via the magic type n2n\core\container\TransactionManager.
TransactionManager::createTransaction() starts a transaction and returns it in the form of an object n2n\core\container\Transaction. You can end the transaction with Transaction::commit() or Transaction::rollBack().
class ExampleDao implements RequestScoped {
private $em;
private $tm;
private function _init(EntityManager $em, TransactionManager $tm) {
$this->em = $em;
$this->tm = $tm;
}
public function saveArticle(Article $article) {
$tx = $this->tm->createTransaction();
$this->em->persist($article);
$tx->commit();
}
}
If an exception is thrown or a fatal error is triggered during a transaction that was started via the TransactionManager, the transaction is rolled back (rollback). If you do not end the transaction, it is also rolled back.
Nested Transactions
Another advantage of n2n transactions is that you can nest them. The actual commit or rollback on the resources only happens when the root transaction (first opened transaction) is ended.
class ExampleController extends ControllerAdapter {
public function doArticle($id, ExampleDao $exampleDao) {
$this->beginTransaction();
$exampleDao->saveArticle(new Article('test'));
$this->commit();
}
}
class ExampleDao implements RequestScoped {
private $em;
private $tm;
private function _init(EntityManager $em, TransactionManager $tm) {
$this->em = $em;
$this->tm = $tm;
}
public function saveArticle(Article $article) {
$tx = $this->tm->createTransaction();
$this->em->persist($article);
$tx->commit();
}
}
If an exception is thrown or a fatal error is triggered during an active child transaction, the root transaction along with all its child transactions is rolled back.
If a child transaction is actively rolled back using Transaction::rollBack(), all of its parent transactions (including the root transaction) must also be ended with Transaction::rollBack(), otherwise a n2n\core\container\TransactionStateException is thrown.
Read-Only Transaction
If you pass true as the first parameter of ControllerAdapter::beginTransaction() or TransactionManager::createTransaction(), you start a read-only transaction.
This means that the resources cannot be written to during such a transaction. This improves performance because, for example, an EntityManager does not have to check at the end of the transaction whether entities have been changed.
public function doArticle($id, ExampleDao $exampleDao) {
$this->beginTransaction(true);
$article = $exampleDao->getArticleById($id);
if ($article === null) {
throw new PageNotFoundException();
}
$this->commit();
$this->forward('view/article.html', array(
'article' => $article
));
}
In principle, it is a good idea to keep transactions short and to end them before rendering the view.
However, this can lead to problems if you pass entities with relationship properties to the view for which FetchType::Lazy applies and that have not yet been loaded.
If you access these properties in the view, they are only loaded at that point and therefore outside the transaction (leads to an exception with the transactional EntityManager).
In these cases, it makes sense to end the transaction only after ControllerAdapter::forward().
If the root transaction is read-only in nested transactions, all of its child transactions must also be read-only.
Extended / transactional EntityManager
The two EntityManager types extended and transactional differ in their lifetime. In contrast to the extended, the transactional EntityManager is bound to a transaction. It can only be requested within a transaction and closes (EntityManager::close()) automatically as soon as the transaction is ended. So you get a new EntityManager for each transaction and therefore also a new Persistence Context. This also means that relationship properties for which FetchType::LAZY applies can no longer be loaded after the end of the transaction. In this case, an exception is thrown.
The extended EntityManager can continue to be used freely after the end of the transaction. Entities that were loaded in the ended transaction still have the Status MANAGED. You can have the extended EntityManager injected into magic methods or request it via EntityManagerFactory::getExtended().
#[Inject]
private EntityManager $em;
You only get the transactional EntityManager via EntityManagerFactory::getTransactional(). If no transaction is active, this method throws an exception.
class ExampleDao implements RequestScoped {
private $emf;
private function _init(EntityManagerFactory $emf) {
$this->emf = $emf;
}
/**
* @param int $id
* @return atusch\bo\Article
*/
public function getArticleById($id) {
return $this->emf->getTransactional()
->find(Article::getClass(), $id);
}
}
In the example above, we do not know whether a transaction is active when _init() is called. Furthermore, ExampleDao::getArticleById() can be called multiple times in different transactions.
That is why we only magically load the EntityManagerFactory and fetch the transactional EntityManager of the active transaction only when ExampleDao::getArticleById() is called.