commerce_api-8.x-1.x-dev/src/Resource/CartUpdateItemResource.php
src/Resource/CartUpdateItemResource.php
<?php
namespace Drupal\commerce_api\Resource;
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\Component\Serialization\Json;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\jsonapi\Entity\EntityValidationTrait;
use Drupal\jsonapi\Exception\EntityAccessDeniedHttpException;
use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel;
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\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
final class CartUpdateItemResource extends CartResourceBase {
use EntityValidationTrait;
/**
* Constructs a new CartUpdateItemResource 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\Core\Render\RendererInterface $renderer
* The renderer.
*/
public function __construct(protected CartProviderInterface $cartProvider, protected CartManagerInterface $cartManager, protected EntityResourceShim $inner, private RendererInterface $renderer) {
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('renderer')
);
}
/**
* Update an order item from a cart.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
* @param \Drupal\commerce_order\Entity\OrderInterface $commerce_order
* The order.
* @param \Drupal\commerce_order\Entity\OrderItemInterface $commerce_order_item
* The order item.
*
* @return \Drupal\jsonapi\ResourceResponse
* The response.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function process(Request $request, OrderInterface $commerce_order, OrderItemInterface $commerce_order_item): ResourceResponse {
$resource_type = $this->resourceTypeRepository->get($commerce_order_item->getEntityTypeId(), $commerce_order_item->bundle());
$parsed_entity = $this->inner->deserialize($resource_type, $request, JsonApiDocumentTopLevel::class);
assert($parsed_entity instanceof OrderItemInterface);
$body = Json::decode($request->getContent());
$data = $body['data'];
if ($data['id'] !== $commerce_order_item->uuid()) {
throw new BadRequestHttpException(sprintf('The selected entity (%s) does not match the ID in the payload (%s).', $commerce_order_item->uuid(), $data['id']));
}
$data += ['attributes' => [], 'relationships' => []];
$data_field_names = array_merge(array_keys($data['attributes']), array_keys($data['relationships']));
// Prevent modifying `purchased_entity` on existing order items.
// We make this check explicitly here and not through field access to ensure
// that the field value is always validated.
if (in_array('purchased_entity', $data_field_names, TRUE)) {
throw new EntityAccessDeniedHttpException($commerce_order_item, AccessResult::forbidden(), '/data/attributes/purchased_entity', 'The `purchased_entity` field cannot be modified.');
}
// Ensure `purchased_entity` is always validated.
$field_names = ['purchased_entity'];
foreach ($data_field_names as $data_field_name) {
$field_name = $resource_type->getInternalName($data_field_name);
$parsed_field_item = $parsed_entity->get($field_name);
$original_field_item = $commerce_order_item->get($field_name);
if ($this->inner->checkPatchFieldAccess($parsed_field_item, $original_field_item)) {
$commerce_order_item->set($field_name, $parsed_field_item->getValue());
}
$field_names[] = $field_name;
}
static::validate($commerce_order_item, $field_names);
$render_context = new RenderContext();
$this->renderer->executeInRenderContext($render_context, function () use ($commerce_order, $commerce_order_item) {
try {
$this->cartManager->updateOrderItem($commerce_order, $commerce_order_item);
}
catch (EntityStorageException $exception) {
if ($exception->getPrevious() instanceof OrderVersionMismatchException) {
throw new ConflictHttpException($exception->getMessage(), $exception);
}
throw $exception;
}
});
$order_item_storage = $this->entityTypeManager->getStorage('commerce_order_item');
assert($order_item_storage instanceof OrderItemStorageInterface);
// Reload the order item as the cart has refreshed.
$commerce_order_item = $order_item_storage->load($commerce_order_item->id());
$resource_object = ResourceObject::createFromEntity($this->resourceTypeRepository->get($commerce_order_item->getEntityTypeId(), $commerce_order_item->bundle()), $commerce_order_item);
$primary_data = new ResourceObjectData([$resource_object], 1);
$response = $this->createJsonapiResponse($primary_data, $request);
if (!$render_context->isEmpty() &&
$response instanceof CacheableResponseInterface) {
$response->addCacheableDependency($render_context->pop());
}
return $response;
}
}
