View source
<?php
namespace Drupal\transaction;
use Drupal\Component\Render\PlainTextOutput;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Utility\Token;
use Drupal\transaction\Event\TransactionExecutionEvent;
use Drupal\transaction\Exception\ExecutionTimeoutException;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Component\Datetime\Time;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class TransactorHandler implements TransactorHandlerInterface {
protected $transactionService;
protected $transactionStorage;
protected $timeService;
protected $currentUser;
protected $token;
protected $eventDispatcher;
protected $lock;
public function __construct(TransactionServiceInterface $transaction_service, EntityStorageInterface $transaction_storage, Time $time_service, AccountInterface $current_user, Token $token, EventDispatcherInterface $event_dispatcher, LockBackendInterface $lock) {
$this->transactionService = $transaction_service;
$this->transactionStorage = $transaction_storage;
$this->timeService = $time_service;
$this->currentUser = $current_user;
$this->token = $token;
$this->eventDispatcher = $event_dispatcher;
$this->lock = $lock;
}
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static($container
->get('transaction'), $container
->get('entity_type.manager')
->getStorage($entity_type
->id()), $container
->get('datetime.time'), $container
->get('current_user'), $container
->get('token'), $container
->get('event_dispatcher'), $container
->get('lock'));
}
public function doExecute(TransactionInterface $transaction, $save = TRUE, UserInterface $executor = NULL) {
if (!($execution_lock_name = $this
->executionLockAcquire($transaction))) {
throw new ExecutionTimeoutException('Unable to lock the transactional flow for transaction execution.');
}
if (!$transaction
->isPending()) {
$this->lock
->release($execution_lock_name);
throw new InvalidTransactionStateException('Cannot execute an already executed transaction.');
}
$transaction_type = $transaction
->getType();
$last_executed = $this->transactionService
->getLastExecutedTransaction($transaction
->getTargetEntityId(), $transaction_type);
$transactor = $transaction_type
->getPlugin();
if ($transactor
->executeTransaction($transaction, $last_executed)) {
if (!$transaction
->getResultCode()) {
$transaction
->setResultCode(TransactorPluginInterface::RESULT_OK);
}
$transaction
->setExecutionTime($this->timeService
->getRequestTime());
$transaction
->setExecutionSequence($last_executed ? $last_executed
->getExecutionSequence() + 1 : 1);
if (!$executor && $this->currentUser && $this->currentUser
->id()) {
$executor = User::load($this->currentUser
->id());
}
$transaction
->setExecutor($executor ?: User::getAnonymousUser());
$this->eventDispatcher
->dispatch(TransactionExecutionEvent::EVENT_NAME, new TransactionExecutionEvent($transaction));
if ($save) {
$transaction
->save();
}
$executed = TRUE;
}
else {
if (!$transaction
->getResultCode()) {
$transaction
->setResultCode(TransactorPluginInterface::RESULT_ERROR);
}
$executed = FALSE;
}
$this->lock
->release($execution_lock_name);
return $executed;
}
protected function executionLockAcquire(TransactionInterface $transaction) {
$lock_name = 'transaction_' . $transaction
->getTypeId() . '_' . $transaction
->getTargetEntityId();
$lock_time = ini_get('max_execution_time') ?: 3600;
if ($this->lock
->acquire($lock_name, $lock_time) || !$this->lock
->wait($lock_name) && $this->lock
->acquire($lock_name, $lock_time)) {
return $lock_name;
}
return FALSE;
}
public function composeResultMessage(TransactionInterface $transaction, $langcode = NULL) {
return $transaction
->getType()
->getPlugin()
->getResultMessage($transaction, $langcode);
}
public function composeDescription(TransactionInterface $transaction, $langcode = NULL) {
if ($operation = $transaction
->getOperation()) {
$token_options = [
'clear' => TRUE,
];
if ($langcode) {
$token_options['langcode'] = $langcode;
}
$target_entity = $transaction
->getTargetEntity();
$target_entity_type_id = $target_entity
->getEntityTypeId();
$token_data = [
'transaction' => $transaction,
TransactorHandler::getTokenContextFromEntityTypeId($target_entity_type_id) => $target_entity,
];
$description = PlainTextOutput::renderFromHtml($this->token
->replace($operation
->getDescription(), $token_data, $token_options));
}
else {
$description = $transaction
->getType()
->getPlugin()
->getTransactionDescription($transaction, $langcode);
}
return $description;
}
public function composeDetails(TransactionInterface $transaction, $langcode = NULL) {
$details = $transaction
->getType()
->getPlugin()
->getTransactionDetails($transaction, $langcode);
if ($operation = $transaction
->getOperation()) {
$token_options = [
'clear' => TRUE,
];
if ($langcode) {
$token_options['langcode'] = $langcode;
}
$target_entity = $transaction
->getTargetEntity();
$target_entity_type_id = $target_entity
->getEntityTypeId();
$token_data = [
'transaction' => $transaction,
TransactorHandler::getTokenContextFromEntityTypeId($target_entity_type_id) => $target_entity,
];
foreach ($operation
->getDetails() as $detail) {
$details[] = PlainTextOutput::renderFromHtml($this->token
->replace($detail, $token_data, $token_options));
}
}
return $details;
}
public function getPreviousTransaction(TransactionInterface $transaction) {
if ($transaction
->isPending()) {
throw new InvalidTransactionStateException('Cannot get the previously executed transaction to one that is pending execution.');
}
$result = $this->transactionStorage
->getQuery()
->condition('type', $transaction
->getTypeId())
->condition('target_entity.target_id', $transaction
->getTargetEntityId())
->condition('target_entity.target_type', $transaction
->getType()
->getTargetEntityTypeId())
->exists('executed')
->condition('executed', $transaction
->getExecutionTime(), '<')
->range(0, 1)
->sort('executed', 'DESC')
->execute();
return count($result) ? $this->transactionStorage
->load(array_pop($result)) : NULL;
}
public function getNextTransaction(TransactionInterface $transaction) {
if ($transaction
->isPending()) {
throw new InvalidTransactionStateException('Cannot get the next executed transaction to one that is pending execution.');
}
$result = $this->transactionStorage
->getQuery()
->condition('type', $transaction
->getTypeId())
->condition('target_entity.target_id', $transaction
->getTargetEntityId())
->condition('target_entity.target_type', $transaction
->getType()
->getTargetEntityTypeId())
->exists('executed')
->condition('executed', $transaction
->getExecutionTime(), '>')
->range(0, 1)
->sort('executed')
->execute();
return count($result) ? $this->transactionStorage
->load(array_pop($result)) : NULL;
}
public static function getTokenContextFromEntityTypeId($entity_type_id) {
switch ($entity_type_id) {
case 'taxonomy_term':
$context = 'term';
break;
case 'taxonomy_vocabulary':
$context = 'vocabulary';
break;
default:
$context = $entity_type_id;
break;
}
return $context;
}
}