commercetools-8.x-1.2-alpha1/src/CommercetoolsCustomers.php

src/CommercetoolsCustomers.php
<?php

namespace Drupal\commercetools;

use Commercetools\Exception\InvalidArgumentException;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Cache\UseCacheBackendTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Site\Settings;
use Drupal\user\UserInterface;
use GraphQL\Actions\Mutation;
use GraphQL\Actions\Query;
use GraphQL\Entities\Node;
use GraphQL\Entities\Variable;
use Symfony\Component\DependencyInjection\Exception\LogicException;

/**
 * The Commercetools customers service.
 */
class CommercetoolsCustomers {

  use UseCacheBackendTrait;

  const CACHE_CID_PREFIX_CUSTOMER_ID = 'commercetools_customer_id:';
  const CACHE_TAG_CUSTOMER_ID = 'commercetools_customer_id:';
  const CACHE_TAG_CUSTOMER_LIST = 'commercetools_customer_list';
  const SHIPPING_ADDRESS_KEY = 'shippingAddress';
  const BILLING_ADDRESS_KEY = 'billingAddress';
  const ADDRESSES_MULTIPLE_KEYS = [
    self::SHIPPING_ADDRESS_KEY => 'shippingAddresses',
    self::BILLING_ADDRESS_KEY => 'billingAddresses',
  ];
  const ADDRESS_FIELDS = [
    'id',
    'key',
    'title',
    'country',
    'state',
    'city',
    'postalCode',
    'streetName',
    'streetNumber',
    'phone',
    'firstName',
    'lastName',
  ];

  /**
   * The current customer id.
   *
   * @var string
   */
  protected string|null $currentCustomerId;

  /**
   * CommercetoolsContentCustomers constructor.
   *
   * @param \Drupal\Core\Session\AccountInterface $user
   *   The current user.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\commercetools\CommercetoolsApiServiceInterface $ctApi
   *   The Commercetools API service.
   * @param \Drupal\Core\Site\Settings $settings
   *   The settings instance.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend
   *   A cache backend.
   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cacheTagsInvalidator
   *   The cache tags invalidator.
   */
  public function __construct(
    protected readonly AccountInterface $user,
    protected readonly EntityTypeManagerInterface $entityTypeManager,
    protected readonly CommercetoolsApiServiceInterface $ctApi,
    protected readonly Settings $settings,
    CacheBackendInterface $cacheBackend,
    protected CacheTagsInvalidatorInterface $cacheTagsInvalidator,
  ) {
    $this->cacheBackend = $cacheBackend;
  }

  /**
   * Provides a customer ID field storage definitions.
   *
   * @return \Drupal\Core\Field\FieldStorageDefinitionInterface
   *   The customer ID field storage definitions.
   */
  public static function customerIdFieldDefinitions(): FieldStorageDefinitionInterface {
    return BaseFieldDefinition::create('string')
      ->setLabel('commercetools customer ID')
      ->setDescription('External commercetools customer ID for internal use.')
      ->setRevisionable(FALSE)
      ->setSetting('max_length', 86);
  }

  /**
   * Loads a Commercetools customer by ID.
   *
   * @param string $customerId
   *   The Commercetools customer ID.
   *
   * @return array
   *   An existing customer data from API.
   *
   * @throws \Throwable
   */
  public function load(string $customerId): array {
    $arguments = [
      'id' => new Variable('id', 'String!'),
    ];
    $query = new Query('customer', $arguments);
    $this->addFields($query);
    $variables = [
      'id' => $customerId,
    ];
    $response = $this->ctApi->executeGraphQlOperation($query, $variables);
    return (array) $response->getData()['customer'];
  }

  /**
   * Creates a new Commercetools customer.
   *
   * @param array $draft
   *   The array of values which will be used for creating customer.
   *
   * @return array
   *   An added customer data from API.
   *
   * @throws \Commercetools\Exception\InvalidArgumentException
   * @throws \Throwable
   */
  public function create(array $draft = []): array {
    // Force password to be non-mandatory.
    $draft['authenticationMode'] = 'ExternalAuth';

    // Set current user's email if not provided.
    if (empty($draft['email'])) {
      $email = $this->getUserEmail();
      if ($email) {
        $draft['email'] = $email;
      }
      else {
        throw new InvalidArgumentException('An email is required to create a customer');
      }
    }

    $arguments = [
      'draft' => new Variable('draft', 'CustomerSignUpDraft!'),
    ];
    $mutation = new Mutation('customerSignUp', $arguments);
    $customer = $mutation->customer([]);
    $this->addFields($customer);
    $variables = [
      'draft' => $draft,
    ];
    $response = $this->ctApi->executeGraphQlOperation($mutation, $variables);
    return (array) $response->getData()['customerSignUp'];
  }

  /**
   * Deletes a Commercetools customer.
   *
   * @param string $customerId
   *   The Commercetools customer ID.
   * @param int $version
   *   The last seen version of the Commercetools customer.
   *
   * @return array
   *   A customer data being deleted.
   *
   * @throws \Throwable
   */
  public function delete(string $customerId, int $version): array {
    $arguments = [
      'id' => new Variable('id', 'String'),
      'version' => new Variable('version', 'Long!'),
    ];
    $mutation = new Mutation('deleteCustomer', $arguments);
    $this->addFields($mutation);
    $variables = [
      'id' => $customerId,
      'version' => $version,
    ];
    $response = $this->ctApi->executeGraphQlOperation($mutation, $variables);
    return (array) $response->getData()['deleteCustomer'];
  }

  /**
   * Retrieves a Commercetools customer for the given or current user.
   *
   * Conditionally store Customer ID into user on update.
   *
   * @param \Drupal\user\UserInterface|null $user
   *   The user to retrieve a customer by.
   *   Fallbacks to current user if not provided.
   * @param bool $seek
   *   Indicates whether to seek for existing customer by email.
   *   Updates the user entity with customer ID if found.
   * @param bool $create
   *   Indicates whether to create a new customer.
   *   Updates the user entity with created customer ID.
   * @param bool $store
   *   Indicates whether to store Customer ID into user.
   *
   * @return array|null
   *   A customer data retrieved.
   *
   * @throws \Throwable
   */
  public function getCustomerByUser(?UserInterface $user = NULL, bool $seek = TRUE, bool $create = TRUE, bool $store = TRUE): ?array {
    // First, try to load Customer by stored ID.
    $id = $this->getUserCustomerId($user);
    if ($id) {
      $data = $this->load($id);
      if (!empty($data['version'])) {
        return $data;
      }
    }

    $email = $this->getUserEmail($user);

    // Second, conditionally seek for Customer by email.
    if ($email && $seek) {
      try {
        $data = $this->query(['email' => $email]);
      }
      catch (\Exception) {
        // If the query fails, we return NULL to indicate no customer found.
        // @todo Rework this to cache and not repeat the checks.
        return NULL;
      }
      $customer = reset($data);
      if (!empty($customer['version'])) {
        if ($store) {
          $this->setUserCustomerId($customer['id'], $user);
        }
        return $customer;
      }
    }

    // Last, conditionally create a Customer.
    if ($email && $create) {
      $data = $this->create(['email' => $email]);
      if (!empty($data['version'])) {
        if ($store) {
          $this->setUserCustomerId($data['id'], $user);
        }
        return $data;
      }
    }

    return NULL;
  }

  /**
   * Removes a Commercetools customer for the given or current user.
   *
   * @param \Drupal\user\UserInterface|null $user
   *   The user to retrieve by.
   *   Fallbacks to current user if not provided.
   *
   * @return array|null
   *   Customer data being deleted.
   *
   * @throws \Throwable
   */
  public function deleteCustomerByUser(?UserInterface $user = NULL): ?array {
    /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */
    $id = $this->getUserCustomerId($user);
    $version = NULL;
    if ($id) {
      $customer = $this->load($id);
      $version = $customer['version'] ?? NULL;
    }
    return $id && $version ? $this->delete($id, $version) : NULL;
  }

  /**
   * Sets the user's customer ID.
   *
   * @param string $customerId
   *   The Commercetools customer ID.
   * @param \Drupal\user\UserInterface|null $user
   *   The user entity to update. Fallbacks to current user if not provided.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   * @throws \Drupal\Core\Entity\EntityMalformedException
   */
  public function setUserCustomerId(string $customerId, ?UserInterface $user = NULL): void {
    if (empty($user)) {
      $user = $this->getCurrentUser();
    }
    if ($user->isAuthenticated()) {
      $this->cacheSet(self::CACHE_CID_PREFIX_CUSTOMER_ID . $user->id(), $customerId, tags: [
        self::CACHE_TAG_CUSTOMER_ID . $customerId,
        self::CACHE_TAG_CUSTOMER_LIST,
      ]);
    }
  }

  /**
   * Returns the user's customer ID if set.
   *
   * @param \Drupal\user\UserInterface|null $user
   *   The user to retrieve by. Fallbacks to current user if not provided.
   *
   * @return ?string
   *   A user customer ID if available.
   *
   * @throws \Drupal\Core\Entity\EntityMalformedException
   */
  private function getUserCustomerId(?UserInterface $user = NULL): ?string {
    if (empty($user)) {
      $user = $this->getCurrentUser();
    }
    if ($user->isAuthenticated()) {
      $cache = $this->cacheGet(self::CACHE_CID_PREFIX_CUSTOMER_ID . $user->id());
      return empty($cache) ? NULL : $cache->data;
    }
    return NULL;
  }

  /**
   * Returns the current user's customer ID if available.
   *
   * @param bool $reset
   *   If the current customer id should be reset.
   *
   * @return ?string
   *   A customer ID if available.
   *
   * @throws \Drupal\Core\Entity\EntityMalformedException
   * @throws \Throwable
   */
  public function getCurrentUserCustomerId(bool $reset = FALSE): ?string {
    if (isset($this->currentCustomerId) && !$reset) {
      return $this->currentCustomerId;
    }

    // First, quickly check if customer ID is set for a user account.
    $this->currentCustomerId = $this->getUserCustomerId();
    // If no, try to determine a customer ID via API call.
    if (!$this->currentCustomerId && ($customer = $this->getCustomerByUser())) {
      $this->currentCustomerId = $customer['id'];
    }

    return $this->currentCustomerId;
  }

  /**
   * Removes all customer IDs previously assigned to Users.
   *
   * @param array $uids
   *   The User IDs.
   */
  public function unsetCustomerIdForUsers(array $uids = []): void {
    $tags = empty($uids) ? [self::CACHE_TAG_CUSTOMER_LIST] : array_map(function ($uid) {
      return self::CACHE_TAG_CUSTOMER_ID . $uid;
    }, array_unique($uids));
    $this->cacheTagsInvalidator->invalidateTags($tags);
  }

  /**
   * Returns the user's addresses.
   */
  public function getAddressDataByCustomer(?UserInterface $user = NULL): ?array {
    $customerId = $this->getUserCustomerId($user);

    $arguments = [
      'id' => new Variable('id', 'String!'),
    ];
    $query = new Query('customer', $arguments);
    $this->addFields($query);
    $variables = [
      'id' => $customerId,
    ];

    $query->use(
      'defaultBillingAddressId',
      'defaultShippingAddressId',
    );

    $query->shippingAddresses([])->use(...static::ADDRESS_FIELDS);
    $query->billingAddresses([])->use(...static::ADDRESS_FIELDS);

    $response = $this->ctApi->executeGraphQlOperation($query, $variables);
    return (array) $response->getData()['customer'];
  }

  /**
   * Update customer.
   */
  public function updateCustomer(array $actions, ?UserInterface $user = NULL): array {
    $customer = $this->getCustomerByUser($user);
    $arguments = [
      'id' => new Variable('id', 'String'),
      'version' => new Variable('version', 'Long!'),
      'actions' => new Variable('actions', '[CustomerUpdateAction!]!'),
    ];
    $updateCustomer = new Mutation('updateCustomer', $arguments);
    $variables = [
      'id' => $customer['id'],
      'version' => $customer['version'],
      'actions' => $actions,
    ];
    $this->addFields($updateCustomer);
    $updateCustomer->addresses([])->use(...static::ADDRESS_FIELDS);
    $response = $this->ctApi->executeGraphQlOperation($updateCustomer, $variables);
    return $response->getData();
  }

  /**
   * Update address customer.
   */
  public function addAddress(array $addressData, ?string $type = NULL, ?bool $setDefault = NULL, ?UserInterface $user = NULL) {
    $actions = [
      [
        'addAddress' => ['address' => $addressData],
      ],
    ];

    $responseData = $this->updateCustomer($actions, $user);

    $addressesIds = array_column($responseData['updateCustomer']['addresses'], 'id');
    $addressId = !empty($addressesIds) ? end($addressesIds) : NULL;

    if ($type) {
      if ($type !== static::SHIPPING_ADDRESS_KEY && $type !== static::BILLING_ADDRESS_KEY) {
        throw new LogicException('Invalid address type.');
      }

      $action = $type === static::SHIPPING_ADDRESS_KEY
        ? 'addShippingAddressId'
        : 'addBillingAddressId';

      $actions = [
        [
          $action => ['addressId' => $addressId],
        ],
      ];

      if ($setDefault) {
        $action = $type === static::SHIPPING_ADDRESS_KEY
          ? 'setDefaultShippingAddress'
          : 'setDefaultBillingAddress';

        $actions[][$action] = [
          'addressId' => $addressId,
        ];
      }

      $this->updateCustomer($actions, $user);
    }

    return end($responseData['updateCustomer']['addresses']);
  }

  /**
   * Adds customer fields to request node.
   *
   * @param \GraphQL\Entities\Node $query
   *   The node to which the fields will be added.
   */
  protected function addFields(Node $query): void {
    $query->use('id', 'version', 'email');
  }

  /**
   * Query a Commercetools customers.
   *
   * @param array $where
   *   The where clause array.
   *
   * @return array
   *   A customers dataset queried from API.
   *
   * @throws \Throwable
   */
  private function query(array $where): array {
    $arguments = [
      'limit' => 1,
      'where' => new Variable('where', 'String'),
    ];
    $query = new Query('customers', $arguments);
    $result = $query->results([]);
    $this->addFields($result);
    $variables = [
      'where' => CommercetoolsService::whereToString($where),
    ];
    $response = $this->ctApi->executeGraphQlOperation($query, $variables);
    return (array) $response->getData()['customers']['results'];
  }

  /**
   * Loads current user entity.
   *
   * @return \Drupal\user\UserInterface
   *   The current user entity.
   */
  private function getCurrentUser(): UserInterface {
    /** @var \Drupal\user\UserInterface $user */
    $user = $this->entityTypeManager->getStorage('user')
      ->load($this->user->id());
    return $user;
  }

  /**
   * Returns email of given or currently logged-in user if any.
   *
   * @param \Drupal\user\UserInterface|null $user
   *   The user entity.
   *
   * @return string|null
   *   An email address if any.
   */
  private function getUserEmail(?UserInterface $user = NULL): ?string {
    if ($user) {
      return $user->getEmail();
    }
    elseif ($this->user->isAuthenticated()) {
      return $this->user->getEmail();
    }

    return NULL;
  }

}

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

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