commerce_api-8.x-1.x-dev/src/Resource/CartAddResource.php

src/Resource/CartAddResource.php
<?php

namespace Drupal\commerce_api\Resource;

use Drupal\commerce\Context;
use Drupal\commerce\PurchasableEntityInterface;
use Drupal\commerce_api\EntityResourceShim;
use Drupal\commerce_cart\CartManagerInterface;
use Drupal\commerce_cart\CartProviderInterface;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_order\Entity\OrderItemInterface;
use Drupal\commerce_order\Exception\OrderVersionMismatchException;
use Drupal\commerce_order\OrderItemStorageInterface;
use Drupal\commerce_order\Resolver\ChainOrderTypeResolverInterface;
use Drupal\commerce_price\Resolver\ChainPriceResolverInterface;
use Drupal\commerce_store\CurrentStoreInterface;
use Drupal\commerce_store\Entity\StoreInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\jsonapi\Entity\EntityValidationTrait;
use Drupal\jsonapi\JsonApiResource\ResourceIdentifier;
use Drupal\jsonapi\JsonApiResource\ResourceObject;
use Drupal\jsonapi\JsonApiResource\ResourceObjectData;
use Drupal\jsonapi\ResourceResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

final class CartAddResource extends CartResourceBase {

  use EntityValidationTrait;
  use ResourceTypeHelperTrait;

  /**
   * Constructs a new CartAddResource object.
   *
   * @param \Drupal\commerce_cart\CartProviderInterface $cartProvider
   *   The cart provider.
   * @param \Drupal\commerce_cart\CartManagerInterface $cartManager
   *   The cart manager.
   * @param \Drupal\commerce_api\EntityResourceShim $inner
   *   The JSON:API controller shim.
   * @param \Drupal\commerce_order\Resolver\ChainOrderTypeResolverInterface $chainOrderTypeResolver
   *   The chain order type resolver.
   * @param \Drupal\commerce_store\CurrentStoreInterface $currentStore
   *   The current store.
   * @param \Drupal\commerce_price\Resolver\ChainPriceResolverInterface $chainPriceResolver
   *   The chain price resolver.
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entityRepository
   *   The entity repository.
   * @param \Drupal\Core\Session\AccountInterface $currentUser
   *   The current user.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer.
   * @param \Drupal\Core\Database\Connection $connection
   *   The database connection.
   */
  public function __construct(protected CartProviderInterface $cartProvider, protected CartManagerInterface $cartManager, private EntityResourceShim $inner, private ChainOrderTypeResolverInterface $chainOrderTypeResolver, private CurrentStoreInterface $currentStore, private ChainPriceResolverInterface $chainPriceResolver, protected EntityRepositoryInterface $entityRepository, private AccountInterface $currentUser, private RendererInterface $renderer, private Connection $connection) {
    parent::__construct($cartProvider, $cartManager);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('commerce_cart.cart_provider'),
      $container->get('commerce_cart.cart_manager'),
      $container->get('commerce_api.jsonapi_controller_shim'),
      $container->get('commerce_order.chain_order_type_resolver'),
      $container->get('commerce_store.current_store'),
      $container->get('commerce_price.chain_price_resolver'),
      $container->get('entity.repository'),
      $container->get('current_user'),
      $container->get('renderer'),
      $container->get('database'),
    );
  }

  /**
   * Process the request.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   * @param array $_purchasable_entity_resource_types
   *   The purchasable entity resource types.
   *
   * @return \Drupal\jsonapi\ResourceResponse
   *   The response.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function process(Request $request, array $_purchasable_entity_resource_types = []): ResourceResponse {
    $resource_type = $this->getGeneralizedOrderResourceType($_purchasable_entity_resource_types);
    /** @var \Drupal\jsonapi\JsonApiResource\ResourceIdentifier[] $resource_identifiers */
    $resource_identifiers = $this->inner->deserialize($resource_type, $request, ResourceIdentifier::class, 'order_items');

    $context = new RenderContext();
    $order_items = $this->renderer->executeInRenderContext($context, function () use ($resource_identifiers) {
      // Because we support adding multiple items at once, ensure that if
      // an order version mismatch exception is thrown halfway through none of
      // the order items are actually inserted.
      $transaction = $this->connection->startTransaction();
      $order_items = [];
      $order_item_storage = $this->entityTypeManager->getStorage('commerce_order_item');
      assert($order_item_storage instanceof OrderItemStorageInterface);
      foreach ($resource_identifiers as $resource_identifier) {
        $meta = $resource_identifier->getMeta();

        $purchased_entity = $this->getEntityFromResourceIdentifier($resource_identifier, PurchasableEntityInterface::class);
        if (!$purchased_entity instanceof PurchasableEntityInterface) {
          throw new UnprocessableEntityHttpException(sprintf('The entity %s does not exist.', $resource_identifier->getId()));
        }
        $store = $this->selectStore($purchased_entity);

        $order_item = $order_item_storage->createFromPurchasableEntity($purchased_entity, $meta);
        // Populate and resolve price.
        if (!$order_item->isUnitPriceOverridden()) {
          $context = new Context($this->currentUser, $store);
          $resolved_price = $this->chainPriceResolver->resolve($purchased_entity, $order_item->getQuantity(), $context);
          $order_item->setUnitPrice($resolved_price);
        }
        // @todo If processing multiple items, this could fail halfway through.
        // Determine if we should collect a grouping of errors and return them.
        // We set the order_id to the cart object for any constraint validators.
        // @todo https://www.drupal.org/project/commerce/issues/3101651
        $cart = $this->getCartForOrderItem($order_item, $store);
        $order_item->set('order_id', $cart);

        // Get order item field definitions:
        $field_definitions = $order_item->getFieldDefinitions();

        // Calculate invalid fields by checking, whether the provided meta
        // fields actually exist on the order item entity:
        $invalid_fields = array_diff_key($meta, $field_definitions);
        // "combine" and "arity" are special meta properties that are not field
        // definitions.
        // Remove them from the list of invalid fields:
        unset($invalid_fields['combine'], $invalid_fields['arity']);
        if (!empty($invalid_fields)) {
          $fields_string = implode(', ', array_keys($invalid_fields));
          throw new UnprocessableEntityHttpException(strip_tags("The given meta fields '$fields_string' are not valid entity fields."));
        }

        // Check meta for any base fields. We shouldn't allow to modify these:
        $matched_field_definitions = array_intersect_key($field_definitions, $meta);
        foreach ($matched_field_definitions as $field_definition) {
          if ($field_definition instanceof BaseFieldDefinition && $field_definition->getName() !== 'quantity') {
            throw new UnprocessableEntityHttpException("You are not allowed to change base field values.");
          }
        }
        $fields_to_validate = array_keys($matched_field_definitions);
        // We always need to validate the purchased_entity field additionally:
        $fields_to_validate[] = 'purchased_entity';

        // Validate the matched fields:
        static::validate($order_item, $fields_to_validate);
        try {
          $order_item = $this->cartManager->addOrderItem($cart, $order_item, $meta['combine'] ?? TRUE);
        }
        catch (EntityStorageException $exception) {
          // Special handling for order version mismatch exceptions to instruct
          // the client to retry.
          if ($exception->getPrevious() instanceof OrderVersionMismatchException) {
            $transaction->rollback();
            throw new ConflictHttpException($exception->getMessage(), $exception);
          }
          throw $exception;
        }
        // Reload the order item as the cart has refreshed.
        // @todo remove after https://www.drupal.org/node/3038342
        $order_item = $order_item_storage->load($order_item->id());
        // If the order item could get loaded, add it to the list of order
        // items:
        if ($order_item) {
          $order_items[] = ResourceObject::createFromEntity($this->resourceTypeRepository->get($order_item->getEntityTypeId(), $order_item->bundle()), $order_item);
        }
      }
      return $order_items;
    });

    $primary_data = new ResourceObjectData($order_items);
    return $this->createJsonapiResponse($primary_data, $request);
  }

  /**
   * Gets the proper cart for a order item in the user's session.
   *
   * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
   *   The order item.
   * @param \Drupal\commerce_store\Entity\StoreInterface $store
   *   The store.
   *
   * @return \Drupal\commerce_order\Entity\OrderInterface
   *   The cart.
   */
  private function getCartForOrderItem(OrderItemInterface $order_item, StoreInterface $store): OrderInterface {
    $order_type_id = $this->chainOrderTypeResolver->resolve($order_item);
    $cart = $this->cartProvider->getCart($order_type_id, $store);
    if (!$cart) {
      $cart = $this->cartProvider->createCart($order_type_id, $store);
    }
    return $cart;
  }

  /**
   * Selects the store for the given purchasable entity.
   *
   * If the entity is sold from one store, then that store is selected.
   * If the entity is sold from multiple stores, and the current store is
   * one of them, then that store is selected.
   *
   * @param \Drupal\commerce\PurchasableEntityInterface $entity
   *   The entity being added to cart.
   *
   * @throws \Exception
   *   When the entity can't be purchased from the current store.
   *
   * @return \Drupal\commerce_store\Entity\StoreInterface
   *   The selected store.
   */
  private function selectStore(PurchasableEntityInterface $entity): StoreInterface {
    $stores = $entity->getStores();
    if (count($stores) === 0) {
      // Malformed entity.
      throw new UnprocessableEntityHttpException('The given entity is not assigned to any store.');
    }
    $store = $this->currentStore->getStore();
    if (!in_array($store, $stores, TRUE)) {
      // Indicates that the site listings are not filtered properly.
      throw new UnprocessableEntityHttpException("The given entity can't be purchased from the current store.");
    }

    return $store;
  }

}

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

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