xero-8.x-2.x-dev/src/Normalizer/XeroNormalizer.php

src/Normalizer/XeroNormalizer.php
<?php

namespace Drupal\xero\Normalizer;

use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\ListDataDefinitionInterface;
use Drupal\Core\TypedData\ListInterface;
use Drupal\Core\TypedData\Plugin\DataType\ItemList;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\serialization\Normalizer\ComplexDataNormalizer;
use Drupal\xero\Plugin\DataType\XeroComplexItemBase;
use Drupal\xero\Plugin\DataType\XeroItemBase;
use Drupal\xero\Plugin\DataType\XeroItemList;
use Drupal\xero\TypedData\Definition\XeroDefinitionInterface;
use Drupal\xero\TypedData\XeroComplexItemInterface;
use Drupal\xero\TypedData\XeroItemInterface;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

/**
 * Implement denormalization for Xero complex data.
 */
class XeroNormalizer extends ComplexDataNormalizer implements DenormalizerInterface {

  /**
   * Typed Data manager.
   *
   * @var \Drupal\Core\TypedData\TypedDataManagerInterface
   */
  protected $typedDataManager;

  /**
   * Old-style check for interface or class support.
   *
   * @var string
   */
  protected $supportedInterfaceOrClass = 'Drupal\xero\TypedData\XeroItemInterface';

  /**
   * Initialization method.
   *
   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
   *   The typed_data.manager service.
   */
  public function __construct(TypedDataManagerInterface $typed_data_manager) {
    $this->typedDataManager = $typed_data_manager;
  }

  /**
   * {@inheritdoc}
   */
  public function supportsNormalization($data, ?string $format = NULL, array $context = []): bool {
    return $data instanceof XeroItemInterface;
  }

  /**
   * {@inheritdoc}
   */
  public function supportsDenormalization($data, string $type, ?string $format = NULL, array $context = []): bool {
    if (isset($context['plugin_id'])) {
      $definition = $this->typedDataManager->getDefinition($context['plugin_id'], FALSE);
      return $definition && $definition['class'] === $type;
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getSupportedTypes(?string $format): array {
    return [
      XeroComplexItemBase::class => TRUE,
      XeroItemBase::class => TRUE,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function normalize($object, $format = NULL, array $context = []): \ArrayObject|array|string|int|float|bool|null {
    $context['top'] = FALSE;
    $ret = [];
    // In order to make it easier to update Xero items when we don't know
    // their full current state on Xero, we only want to send to Xero
    // information about properties that have been explicitly specified on
    // the typed data object, assuming that unspecified properties should
    // be left unchanged on Xero.
    /** @var \Drupal\Core\TypedData\TypedDataInterface $property */
    foreach ($object->getSpecifiedProperties() as $propertyName => $property) {
      $ret[$propertyName] = $this->serializer->normalize($property, $format, $context);
    }
    return $ret;
  }

  /**
   * {@inheritdoc}
   */
  public function denormalize($data, string $type, ?string $format = NULL, array $context = []): mixed {
    // The context array requires the Xero data type to be known. If not, then
    // cannot do anything. This is consistent with Entity normalization.
    if (empty($context['plugin_id'])) {
      throw new UnexpectedValueException('Plugin id parameter must be included in context.');
    }

    $plural_name = $type::getXeroProperty('plural_name');
    if (isset($data[$plural_name])) {
      return $this->denormalizeList($data[$plural_name], $type, $format, $context);
    }

    // This won't normally be reached when denormalizing Xero responses
    // as they always include a plural name at the top level.
    return $this->denormalizeItem($data, $type, $format, $context);
  }

  /**
   * Denormalizes a list of Xero items.
   *
   * @param mixed $data
   *   The list data.
   * @param string $class
   *   The expected class to instantiate the list items to.
   * @param string|null $format
   *   Format the given data was extracted from.
   * @param array $context
   *   Options available to the denormalizer.
   *
   * @return \Drupal\Core\TypedData\Plugin\DataType\ItemList|\Drupal\xero\Plugin\DataType\XeroItemList|\Drupal\Core\TypedData\ListInterface
   *   A typed data list object.
   *
   * @throws \Exception
   */
  protected function denormalizeList($data, string $class, ?string $format, array $context): ItemList|XeroItemList|ListInterface {
    $list_definition = $this->typedDataManager->createListDataDefinition($context['plugin_id']);
    /** @var \Drupal\Core\TypedData\ComplexDataDefinitionInterface $definition */
    $definition = $this->typedDataManager->createDataDefinition($context['plugin_id']);
    // Typed Data Manager's createListDataDefinition method is dumb and creates
    // lists of "any" by default so it needs to be set. DrupalWTF.
    $list_definition->setItemDefinition($definition);

    // Create an empty list and then populate each item.
    /** @var \Drupal\Core\TypedData\Plugin\DataType\ItemList $items */
    $items = $this->typedDataManager->create($list_definition, []);
    foreach ($data as $index => $item_data) {
      $item = $this->denormalizeItem($item_data, $class, $format, $context);
      // Set the value, not the Typed Data object. Might have performance
      // concerns about this.
      $items->offsetSet(NULL, $item->getValue());
    }

    return $items;
  }

  /**
   * Denormalizes a Xero item.
   *
   * @param mixed $data
   *   The item data.
   * @param string $class
   *   The expected Xero item class to instantiate.
   * @param string|null $format
   *   Format the given data was extracted from.
   * @param array $context
   *   Options available to the denormalizer.
   *
   * @return \Drupal\xero\TypedData\XeroComplexItemInterface|\Drupal\Core\TypedData\ComplexDataInterface
   *   A Xero item object.
   *
   * @throws \Exception
   */
  protected function denormalizeItem($data, string $class, ?string $format, array $context): XeroComplexItemInterface|ComplexDataInterface {
    /** @var \Drupal\Core\TypedData\ComplexDataDefinitionInterface $definition */
    $definition = $this->typedDataManager->createDataDefinition($context['plugin_id']);

    /** @var \Drupal\Core\TypedData\ComplexDataInterface $item */
    $item = $this->typedDataManager->create($definition, []);

    // Go through each property definition.
    foreach ($definition->getPropertyDefinitions() as $prop => $prop_definition) {
      if (isset($data[$prop])) {
        if ($prop_definition instanceof XeroDefinitionInterface) {
          // If the definition is a "xero" type.
          $prop_data = $this->denormalizeItem(
            $data[$prop],
            $prop_definition->getClass(),
            $format,
            ['plugin_id' => $prop_definition->getDataType()]
          );
          $item->set($prop, $prop_data->getValue(), TRUE);
        }
        elseif ($prop_definition instanceof ListDataDefinitionInterface) {
          // If the definition is a list of xero types.
          $prop_data = $this->denormalizeList(
            $data[$prop],
            $prop_definition->getItemDefinition()->getClass(),
            $format,
            ['plugin_id' => $prop_definition->getItemDefinition()->getDataType()]
          );
          $item->set($prop, $prop_data->getValue(), TRUE);
        }
        else {
          // Otherwise set the property directly.
          $value = $data[$prop];

          // Detect Xero datetime format and convert to ISO8601.
          if (!is_array($value) && preg_match('#^/Date\(([-]?[0-9]+)([0-9]{3})([+-][0-9]{4})?\)/$#', $value, $dateParts)) {
            // Xero API DateTime format examples:
            // "\/Date(1436961673000)\/" - unix timestamp (milliseconds).
            // "\/Date(1436961673000+0100)\/" - with timezone.
            // "\/Date(-1436961673000-0530)\/" - before the epoch.
            // Regex matches for "\/Date(1436961673090+0100)\/":
            // [1] = ([-]?[0-9]+)    "1436961673" epoch seconds.
            // [2] = ([0-9]{3})      "090"        milliseconds.
            // [3] = ([+-][0-9]{4})? "+0100"/""   optional timezone.
            $datetime = DrupalDateTime::createFromFormat('U.u', $dateParts[1] . '.' . $dateParts[2] . '000')
              ->setTimezone(new \DateTimeZone($dateParts[3] ?? '+0000'));
            $value = $datetime->format('c');
          }

          $item->set($prop, $value, TRUE);
        }
      }
    }

    return $item;
  }

}

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

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