Skip to content

Commit

Permalink
Merge pull request #206 from lchrusciel/add-multiple-items
Browse files Browse the repository at this point in the history
[Cart] Add multiple items
  • Loading branch information
pjedrzejewski authored Aug 22, 2017
2 parents 92f2fdf + 9ed49ee commit 10a92d1
Show file tree
Hide file tree
Showing 15 changed files with 681 additions and 32 deletions.
39 changes: 39 additions & 0 deletions doc/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,39 @@ paths:
description: "Invalid input, validation failed."
schema:
$ref: "#/definitions/GeneralError"
/carts/{token}/multiple-items:
parameters:
- name: "token"
in: "path"
description: "Cart identifier."
required: true
type: "string"
post:
tags:
- "cart"
summary: "Add multiple items to your cart."
description: "This endpoint will allow you to add a new item to your cart."
operationId: "cartPutItems"
consumes:
- "application/json"
produces:
- "application/json"
parameters:
- in: "body"
name: "content"
description: "Description of items. The same rules applied to each of the array values as to the previous point."
required: true
schema:
$ref: "#/definitions/PutItemsToCartRequest"
responses:
201:
description: "Item has been added to the cart"
schema:
$ref: "#/definitions/Cart"
400:
description: "Invalid input, validation failed."
schema:
$ref: "#/definitions/GeneralError"
/carts/{token}/items/{identifier}:
parameters:
- name: "token"
Expand Down Expand Up @@ -844,6 +877,12 @@ definitions:
additionalProperties:
type: "string"
example: "HAT_SIZE_S"
PutItemsToCartRequest:
type: "array"
description: "Body of request used to put item to the cart."
items:
type: "object"
$ref: "#/definitions/PutItemToCartRequest"
ChangeItemQuantityRequest:
type: "object"
description: "Body of request used to change quantity of an item."
Expand Down
6 changes: 3 additions & 3 deletions src/Controller/Cart/PutItemToCartAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,15 @@ public function __invoke(Request $request): Response
private function provideCommandRequest(Request $request)
{
if (!$request->request->has('variantCode') && !$request->request->has('options')) {
return new PutSimpleItemToCartRequest($request);
return PutSimpleItemToCartRequest::fromRequest($request);
}

if ($request->request->has('variantCode') && !$request->request->has('options')) {
return new PutVariantBasedConfigurableItemToCartRequest($request);
return PutVariantBasedConfigurableItemToCartRequest::fromRequest($request);
}

if (!$request->request->has('variantCode') && $request->request->has('options')) {
return new PutOptionBasedConfigurableItemToCartRequest($request);
return PutOptionBasedConfigurableItemToCartRequest::fromRequest($request);
}

throw new NotFoundHttpException('Variant not found for given configuration');
Expand Down
144 changes: 144 additions & 0 deletions src/Controller/Cart/PutItemsToCartAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

declare(strict_types=1);

namespace Sylius\ShopApiPlugin\Controller\Cart;

use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use League\Tactician\CommandBus;
use Sylius\ShopApiPlugin\Factory\ValidationErrorViewFactoryInterface;
use Sylius\ShopApiPlugin\View\ValidationErrorView;
use Sylius\ShopApiPlugin\ViewRepository\CartViewRepositoryInterface;
use Sylius\ShopApiPlugin\Request\PutOptionBasedConfigurableItemToCartRequest;
use Sylius\ShopApiPlugin\Request\PutSimpleItemToCartRequest;
use Sylius\ShopApiPlugin\Request\PutVariantBasedConfigurableItemToCartRequest;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

final class PutItemsToCartAction
{
/** @var ViewHandlerInterface */
private $viewHandler;

/** @var CommandBus */
private $bus;

/** @var ValidatorInterface */
private $validator;

/** @var CartViewRepositoryInterface */
private $cartQuery;

/** @var string */
private $validationErrorViewClass;

public function __construct(
ViewHandlerInterface $viewHandler,
CommandBus $bus,
ValidatorInterface $validator,
CartViewRepositoryInterface $cartQuery,
string $validationErrorViewClass
) {
$this->viewHandler = $viewHandler;
$this->bus = $bus;
$this->validator = $validator;
$this->cartQuery = $cartQuery;
$this->validationErrorViewClass = $validationErrorViewClass;
}

public function __invoke(Request $request): Response
{
/** @var ConstraintViolationListInterface[] $validationResults */
$validationResults = [];
$commandRequests = [];
$commandsToExecute = [];
$token = $request->attributes->get('token');

foreach ($request->request->all() as $item) {
$item['token'] = $token;
$commandRequests[] = $this->provideCommandRequest($item);
}

foreach ($commandRequests as $commandRequest) {
$validationResult = $this->validator->validate($commandRequest);

if (0 === count($validationResult)) {
$commandsToExecute[] = $commandRequest->getCommand();
}

$validationResults[] = $validationResult;
}

if (!$this->isValid($validationResults)) {
/** @var ValidationErrorView $errorMessage */
$errorMessage = new $this->validationErrorViewClass();

$errorMessage->code = Response::HTTP_BAD_REQUEST;
$errorMessage->message = 'Validation failed';

foreach ($validationResults as $validationResult) {
$errors = [];

/** @var ConstraintViolationInterface $result */
foreach ($validationResult as $result) {
$errors[$result->getPropertyPath()][] = $result->getMessage();
}

$errorMessage->errors[] = $errors;
}

return $this->viewHandler->handle(View::create($errorMessage, Response::HTTP_BAD_REQUEST));
}

foreach ($commandsToExecute as $commandToExecute) {
$this->bus->handle($commandToExecute);
}

try {
return $this->viewHandler->handle(
View::create($this->cartQuery->getOneByToken($token), Response::HTTP_CREATED)
);
} catch (\InvalidArgumentException $exception) {
throw new BadRequestHttpException($exception->getMessage());
}
}

/**
* @param array $item
*
* @return PutOptionBasedConfigurableItemToCartRequest|PutSimpleItemToCartRequest|PutVariantBasedConfigurableItemToCartRequest
*/
private function provideCommandRequest(array $item)
{
if (!isset($item['variantCode']) && !isset($item['options'])) {
return PutSimpleItemToCartRequest::fromArray($item);
}

if (isset($item['variantCode']) && !isset($item['options'])) {
return PutVariantBasedConfigurableItemToCartRequest::fromArray($item);
}

if (!isset($item['variantCode']) && isset($item['options'])) {
return PutOptionBasedConfigurableItemToCartRequest::fromArray($item);
}

throw new NotFoundHttpException('Variant not found for given configuration');
}

private function isValid(array $validationResults): bool
{
foreach ($validationResults as $validationResult) {
if (0 !== count($validationResult)) {
return false;
}
}

return true;
}
}
23 changes: 15 additions & 8 deletions src/Request/PutOptionBasedConfigurableItemToCartRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,22 @@ final class PutOptionBasedConfigurableItemToCartRequest
*/
private $quantity;

/**
* @param Request $request
*/
public function __construct(Request $request)
private function __construct($token, $productCode, $options, $quantity)
{
$this->token = $token;
$this->productCode = $productCode;
$this->options = $options;
$this->quantity = $quantity;
}

public static function fromArray(array $item)
{
return new self($item['token'] ?? null, $item['productCode'] ?? null, $item['options'] ?? null, $item['quantity'] ?? null);
}

public static function fromRequest(Request $request)
{
$this->token = $request->attributes->get('token');
$this->productCode = $request->request->get('productCode');
$this->options = $request->request->get('options');
$this->quantity = $request->request->getInt('quantity');
return new self($request->attributes->get('token'), $request->request->get('productCode'), $request->request->get('options'), $request->request->get('quantity'));
}

/**
Expand Down
21 changes: 14 additions & 7 deletions src/Request/PutSimpleItemToCartRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,21 @@ final class PutSimpleItemToCartRequest
*/
private $quantity;

/**
* @param Request $request
*/
public function __construct(Request $request)
private function __construct($token, $productCode, $quantity)
{
$this->token = $token;
$this->productCode = $productCode;
$this->quantity = $quantity;
}

public static function fromArray(array $item)
{
return new self($item['token'] ?? null, $item['productCode'] ?? null, $item['quantity'] ?? null);
}

public static function fromRequest(Request $request)
{
$this->token = $request->attributes->get('token');
$this->productCode = $request->request->get('productCode');
$this->quantity = $request->request->get('quantity');
return new self($request->attributes->get('token'), $request->request->get('productCode'), $request->request->get('quantity'));
}

/**
Expand Down
23 changes: 15 additions & 8 deletions src/Request/PutVariantBasedConfigurableItemToCartRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,22 @@ final class PutVariantBasedConfigurableItemToCartRequest
*/
private $quantity;

/**
* @param Request $request
*/
public function __construct(Request $request)
private function __construct($token, $productCode, $variantCode, $quantity)
{
$this->token = $token;
$this->productCode = $productCode;
$this->variantCode = $variantCode;
$this->quantity = $quantity;
}

public static function fromArray(array $item)
{
return new self($item['token'] ?? null, $item['productCode'] ?? null, $item['variantCode'] ?? null, $item['quantity'] ?? null);
}

public static function fromRequest(Request $request)
{
$this->token = $request->attributes->get('token');
$this->productCode = $request->request->get('productCode');
$this->variantCode = $request->request->get('variantCode');
$this->quantity = $request->request->get('quantity');
return new self($request->attributes->get('token'), $request->request->get('productCode'), $request->request->get('variantCode'), $request->request->get('quantity'));
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/Resources/config/routing/cart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ shop_api_add_to_cart:
defaults:
_controller: sylius.shop_api_plugin.controller.cart.put_item_to_cart_action

shop_api_add_multiple_items_to_cart:
path: /carts/{token}/multiple-items
methods: [POST]
defaults:
_controller: sylius.shop_api_plugin.controller.cart.put_items_to_cart_action

shop_api_drop_cart:
path: /carts/{token}
methods: [DELETE]
Expand Down
10 changes: 10 additions & 0 deletions src/Resources/config/services/actions/cart.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,15 @@
<argument type="service" id="sylius.shop_api_plugin.factory.validation_error_view_factory" />
<argument type="service" id="sylius.shop_api_plugin.view_repository.cart_view_repository" />
</service>

<service id="sylius.shop_api_plugin.controller.cart.put_items_to_cart_action"
class="Sylius\ShopApiPlugin\Controller\Cart\PutItemsToCartAction"
>
<argument type="service" id="fos_rest.view_handler" />
<argument type="service" id="tactician.commandbus" />
<argument type="service" id="validator" />
<argument type="service" id="sylius.shop_api_plugin.view_repository.cart_view_repository" />
<argument type="string">%sylius.shop_api.view.validation_error.class%</argument>
</service>
</services>
</container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>

<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->

<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\ShopApiPlugin\Request\PutOptionBasedConfigurableItemToCartRequest">
<property name="token">
<constraint name="NotNull">
<option name="message">sylius.shop_api.token.not_null</option>
</constraint>
<constraint name="Sylius\ShopApiPlugin\Validator\Constraints\CartExists" />
</property>
<property name="quantity">
<constraint name="Type">
<option name="type">integer</option>
<option name="message">sylius.shop_api.quantity.type_integer</option>
</constraint>

<constraint name="GreaterThan">
<option name="value">0</option>
<option name="message">sylius.shop_api.quantity.greater_than_0</option>
</constraint>

<constraint name="NotNull">
<option name="message">sylius.shop_api.quantity.not_null</option>
</constraint>
</property>
<property name="productCode">
<constraint name="NotNull">
<option name="message">sylius.shop_api.product.not_null</option>
</constraint>
<constraint name="Sylius\ShopApiPlugin\Validator\Constraints\ProductExists" />
<constraint name="Sylius\ShopApiPlugin\Validator\Constraints\ConfigurableProduct" />
</property>
</class>
</constraint-mapping>
Loading

0 comments on commit 10a92d1

Please sign in to comment.