xero_sync-8.x-1.x-dev/src/XeroSyncSynchronizer.php

src/XeroSyncSynchronizer.php
<?php

namespace Drupal\xero_sync;

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\xero\TypedData\XeroComplexItemInterface;
use Drupal\xero_sync\Exception\SyncFailureException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Drupal\xero_sync\Event\XeroSyncEvents;
use Drupal\xero_sync\Event\XeroSyncPropertiesEvent;

/**
 * A class that synchronizes Drupal entities and Xero items.
 */
class XeroSyncSynchronizer implements XeroSyncSynchronizerInterface {

  /**
   * The Xero item finder.
   *
   * @var \Drupal\xero_sync\XeroSyncItemFinder
   */
  protected $itemFinder;

  /**
   * The Xero item manager.
   *
   * @var \Drupal\xero\XeroItemManagerInterface
   */
  protected $itemManager;

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * The typed data manager.
   *
   * @var \Drupal\Core\TypedData\TypedDataManagerInterface
   */
  protected $typedDataManager;

  /**
   * An array of entity types and ids, to check for recursion.
   *
   * @var array
   */
  protected static $recursionChecks;

  /**
   * Constructs a new XeroSyncSynchronizer object.
   *
   * @param \Drupal\xero_sync\XeroSyncItemFinderInterface $item_finder
   *   The Xero item finder.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   The event dispatcher.
   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
   *   The typed data manager.
   * @param \Drupal\xero\XeroItemManagerInterface|null $item_manager
   *   (optional) The Xero item manager.
   */
  public function __construct(XeroSyncItemFinderInterface $item_finder, EventDispatcherInterface $event_dispatcher, TypedDataManagerInterface $typed_data_manager, $item_manager = NULL) {
    $this->itemFinder = $item_finder;
    $this->eventDispatcher = $event_dispatcher;
    $this->typedDataManager = $typed_data_manager;
    $this->itemManager = $item_manager;
  }

  /**
   * {@inheritDoc}
   */
  public function syncEntityWithItem(
    EntityInterface $entity,
    XeroComplexItemInterface $item = NULL,
    $create = TRUE,
    $update = TRUE,
    $previousEntityState = NULL
    ) {
    if ($this->isRecursing($entity)) {
      return;
    }
    $this->setRecursionCheck($entity);

    try {
      if (is_null($item)) {
        $item = $this->itemFinder->getItem($entity, $create);
      }

      // Not finding an item is perfectly legitimate, as sometimes we might not
      // want to sync.
      if (!empty($item)) {
        if ($update) {
          try {
            // If the update fails, keep using the previous item.
            $item = $this->updateSyncedItemIfNecessary($entity, $item, FALSE, $previousEntityState);
          }
          // Catch in order to sync to the entity regardless of success.
          catch (\Throwable $caughtError) {
          }
        }

        $this->syncToEntityFromItem($entity, $item);
        if (isset($caughtError)) {
          throw $caughtError;
        }
      }
    }
    catch (\Throwable $e) {
      $this->unsetRecursionCheck($entity);
      throw $e;
    }

    $this->unsetRecursionCheck($entity);
    return $item;
  }

  /**
   * {@inheritDoc}
   */
  public function unsyncEntityWithItem(EntityInterface $entity, XeroComplexItemInterface $item = NULL) {
    if (is_null($item)) {
      $item = $this->itemFinder->getItem($entity, FALSE);
    }
    if (!is_null($item)) {
      $item = $this->updateSyncedItemIfNecessary($entity, $item, TRUE);
    }
    return $item;
  }

  /**
   * {@inheritDoc}
   */
  public function createSyncedItem(EntityInterface $entity, XeroComplexItemInterface $item = NULL, $xeroType = NULL) {
    if (is_null($item)) {
      if (is_null($xeroType)) {
        throw new \InvalidArgumentException("When creating a synced item, either an item must be provided or the Xero type must be specified.");
      }
      $definition = $this->typedDataManager->createDataDefinition($xeroType);
      $item = $this->typedDataManager->create($definition, []);
      $item->stub = TRUE;
    }

    $event = $this->dispatchEvent(XeroSyncEvents::SYNC_TO_ITEM_FROM_ENTITY, $entity, $item);
    $item = $event->getItem();
    $createResult = $this->getItemManager()->throwErrors()->createItem($item);
    if ($createResult) {
      return $createResult;
    }
    throw new SyncFailureException("Creating a Xero item failed");
  }

  /**
   * {@inheritDoc}
   */
  public function updateSyncedItemIfNecessary(EntityInterface $entity, XeroComplexItemInterface $item, $unsync = FALSE, $previousEntityState = NULL) {
    if (!is_bool($unsync)) {
      throw new \InvalidArgumentException("Third argument to updateSyncedItem must be boolean.");
    }
    $needingUpdate = $this->mutateItemToUpdatedState($entity, $item, $unsync, $previousEntityState);
    if ($needingUpdate) {
      $item = $this->getItemManager()->throwErrors()->updateItem($needingUpdate);
      if (!$item) {
        throw new SyncFailureException("Updating a Xero item failed");
      }
    }
    return $item;
  }

  /**
   * {@inheritDoc}
   */
  public function isItemUpdateNeeded(EntityInterface $entity, XeroComplexItemInterface $item, $previousEntityState = NULL) {
    $needingUpdate = $this->mutateItemToUpdatedState($entity, $item, FALSE, $previousEntityState);
    return ($needingUpdate !== FALSE);
  }

  /**
   * {@inheritDoc}
   */
  public function syncToEntityFromItem(EntityInterface $entity, XeroComplexItemInterface $item) {
    $event = $this->dispatchEvent(XeroSyncEvents::SYNC_TO_ENTITY_FROM_ITEM, $entity, $item);
    if ($event->isModified()) {
      // Ensure event subscribers can modify the entity.
      $entity = $event->getEntity();
      $entity->save();
    }
    return $entity;
  }

  /**
   * Update a typed data representation of a Xero item with needed changes.
   *
   * This method invokes XeroSync events that modify a supplied Xero item
   * based on the state of a supplied entity. The modified item is returned, or
   * FALSE if no modifications are needed.
   *
   * If the item is flagged as having been previously synced, and is a stub,
   * and a previous state of the entity is supplied, then only modifications
   * different from that previous state are considered.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to update the item from.
   * @param \Drupal\xero\TypedData\XeroComplexItemInterface $item
   *   The Xero item to be updated, possibly a stub.
   * @param bool $unsync
   *   (optional) Whether the item is being unsynced.
   * @param \Drupal\Core\Entity\EntityInterface|null $previousEntityState
   *   (optional) The previous state of the entity, before the changes that
   *   are now being synchronised. Often from $entity->original.
   *
   * @return bool|\Drupal\xero\TypedData\XeroComplexItemInterface
   *   An updated typed data representation of the item, ready to send to Xero,
   *   or FALSE if the item does not need updating.
   */
  protected function mutateItemToUpdatedState(EntityInterface $entity, XeroComplexItemInterface $item, $unsync = FALSE, $previousEntityState = NULL) {
    $eventType = XeroSyncEvents::SYNC_TO_ITEM_FROM_ENTITY;
    if ($unsync) {
      $eventType = XeroSyncEvents::UNSYNC_ITEM;
    }

    // If we don't have an up-to-date representation of the item as it currently
    // is on Xero, but we do know the previous state of the entity and that the
    // item has been synced in the past, then we should use the item suggested
    // by the previous state of the entity as the basis of comparison.
    $isStub = isset($item->stub) && $item->stub;
    $isSynced = isset($item->synced) && $item->synced;
    if (!$unsync && $isStub && $isSynced && $previousEntityState) {
      $event = $this->dispatchEvent($eventType, $previousEntityState, $item);
      $item = $event->getItem();
      $item->stub = FALSE;
    }

    $event = $this->dispatchEvent($eventType, $entity, $item);
    if ($event->isModified()) {
      $item = $event->getItem();
      return $item;
    }
    return FALSE;
  }

  /**
   * Detects whether this is a recursive attempts to synchronize an entity.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity being synchronized.
   */
  protected function isRecursing(EntityInterface $entity) {
    return isset(self::$recursionChecks[$entity->getEntityTypeId()]) && in_array($entity->id(), (self::$recursionChecks[$entity->getEntityTypeId()]));
  }

  /**
   * Record an attempt to synchronize an entity, for futire recursion checking.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity being synchronized.
   */
  protected function setRecursionCheck(EntityInterface $entity) {
    self::$recursionChecks[$entity->getEntityTypeId()][] = $entity->id();
  }

  /**
   * Remove a recursion check that is no longer needed.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity being synchronized.
   */
  protected function unsetRecursionCheck(EntityInterface $entity) {
    if (($key = array_search($entity->id(), self::$recursionChecks[$entity->getEntityTypeId()])) !== FALSE) {
      unset(self::$recursionChecks[$entity->getEntityTypeId()][$key]);
    }
  }

  /**
   * Dispatch an event to allow subscribers to participate.
   *
   * @param string $eventType
   *   The event type.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to set on the event.
   * @param \Drupal\xero\TypedData\XeroComplexItemInterface $item
   *   The item to set on the event.
   *
   * @return \Drupal\xero_sync\Event\XeroSyncPropertiesEvent
   *   The (possibly modified) event.
   */
  protected function dispatchEvent($eventType, EntityInterface $entity, XeroComplexItemInterface $item) {
    if (!isset($item->stub)) {
      $item->stub = FALSE;
    }
    $event = new XeroSyncPropertiesEvent($entity, $item);
    $eventId = $eventType . "." . $entity->getEntityTypeId();
    $event = $this->eventDispatcher->dispatch($event, $eventId);
    return $event;
  }

  /**
   * Lazily get the item manager service.
   *
   * We don't add this as a dependency in xero_sync.services.yml, because there
   * are valid uses of this service that don't depend on the item manager, and
   * the item manager is only available if Xero is configured.
   *
   * @return \Drupal\xero\XeroItemManagerInterface
   *   The Xero ItemManager service.
   */
  protected function getItemManager() {
    if (is_null($this->itemManager)) {
      $this->itemManager = \Drupal::service('xero.item_manager');
    }
    return $this->itemManager;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc