This repository has been archived by the owner on Dec 19, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 154
#141 add simple product to cart #170
Merged
magento-engcom-team
merged 22 commits into
2.3-develop
from
141-add-simple-product-to-cart
Oct 3, 2018
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
eea5cdd
#141 Added add simple product to cart mutation to quote-graphql module
50a928c
#141 Added required configurations
5105989
#141 Added hard dependencies
1734dfc
#141 Fixed issues from Travis
7e7e82c
#141 Running tests again
01404a0
#141 Added descriptions to classes and methods
1064854
#141 Performed decoupling of the resolvers
34e0106
#141 Added missed lines in phpdocs
e8011a8
#141 Removed redundant dependency
436dee7
#141 Restored config after patching the branch
4942c6f
#141 Decoupling the CustomOption data provider
e901431
Merge remote-tracking branch 'origin/2.3-develop' into 141-add-simple…
7990b83
GraphQL-141: [Mutations] Cart Operations > Add simple product to Cart
ada51dc
GraphQL-141: [Mutations] Cart Operations > Add simple product to Cart
484c65c
GraphQL-141: [Mutations] Cart Operations > Add simple product to Cart
6b7300a
GraphQL-141: [Mutations] Cart Operations > Add simple product to Cart
c21a016
GraphQL-141: [Mutations] Cart Operations > Add simple product to Cart
d39230e
GraphQL-141: [Mutations] Cart Operations > Add simple product to Cart
d43164b
GraphQL-141: [Mutations] Cart Operations > Add simple product to Cart
fa5940a
Merge remote-tracking branch 'origin/2.3-develop' into 141-add-simple…
c78b993
GraphQL-141: [Mutations] Cart Operations > Add simple product to Cart
7e3c023
GraphQL-141: [Mutations] Cart Operations > Add simple product to Cart
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
67 changes: 67 additions & 0 deletions
67
app/code/Magento/CatalogGraphQl/Model/Product/Option/DateType.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\CatalogGraphQl\Model\Product\Option; | ||
|
||
use Magento\Catalog\Model\Product\Option\Type\Date as ProductDateOptionType; | ||
use Magento\Framework\Exception\LocalizedException; | ||
use Magento\Framework\Stdlib\DateTime; | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
class DateType extends ProductDateOptionType | ||
{ | ||
/** | ||
* Make valid string as a value of date option type for GraphQl queries | ||
* | ||
* @param array $values All product option values, i.e. array (option_id => mixed, option_id => mixed...) | ||
* @return ProductDateOptionType | ||
*/ | ||
public function validateUserValue($values) | ||
{ | ||
if ($this->_dateExists() || $this->_timeExists()) { | ||
return parent::validateUserValue($this->formatValues($values)); | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Format date value from string to date array | ||
* | ||
* @param [] $values | ||
* @return [] | ||
* @throws LocalizedException | ||
*/ | ||
private function formatValues($values) | ||
{ | ||
if (isset($values[$this->getOption()->getId()])) { | ||
$value = $values[$this->getOption()->getId()]; | ||
$dateTime = \DateTime::createFromFormat(DateTime::DATETIME_PHP_FORMAT, $value); | ||
$values[$this->getOption()->getId()] = [ | ||
'date' => $value, | ||
'year' => $dateTime->format('Y'), | ||
'month' => $dateTime->format('m'), | ||
'day' => $dateTime->format('d'), | ||
'hour' => $dateTime->format('H'), | ||
'minute' => $dateTime->format('i'), | ||
'day_part' => $dateTime->format('a'), | ||
]; | ||
} | ||
|
||
return $values; | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public function useCalendar() | ||
{ | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
*/ | ||
--> | ||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> | ||
<preference for="Magento\Catalog\Model\Product\Option\Type\Date" type="Magento\CatalogGraphQl\Model\Product\Option\DateType" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks risky because we override the default behavior of catalog class. Why is this needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @paliarush It's needed for API purpose only (that's why it's defined in scope of graphql area) to bypass requirement for date values which Magento has on the frontend (date value should be an array). So by this, we made it possible to receive and validate data value in string format. Does it make sense? |
||
<type name="Magento\CatalogGraphQl\Model\ProductInterfaceTypeResolverComposite"> | ||
<arguments> | ||
<argument name="productTypeNameResolvers" xsi:type="array"> | ||
|
138 changes: 138 additions & 0 deletions
138
app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\QuoteGraphQl\Model\Cart; | ||
|
||
use Magento\Framework\Exception\NoSuchEntityException; | ||
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; | ||
use Magento\Framework\GraphQl\Exception\GraphQlInputException; | ||
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; | ||
use Magento\Framework\Message\AbstractMessage; | ||
use Magento\Quote\Api\CartRepositoryInterface; | ||
use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; | ||
use Magento\Quote\Model\Quote; | ||
use Magento\QuoteGraphQl\Model\Authorization\IsCartMutationAllowedForCurrentUser; | ||
|
||
/** | ||
* Add products to cart | ||
*/ | ||
class AddProductsToCart | ||
{ | ||
/** | ||
* @var MaskedQuoteIdToQuoteIdInterface | ||
*/ | ||
private $maskedQuoteIdToQuoteId; | ||
|
||
/** | ||
* @var CartRepositoryInterface | ||
*/ | ||
private $cartRepository; | ||
|
||
/** | ||
* @var IsCartMutationAllowedForCurrentUser | ||
*/ | ||
private $isCartMutationAllowedForCurrentUser; | ||
|
||
/** | ||
* @var AddSimpleProductToCart | ||
*/ | ||
private $addProductToCart; | ||
|
||
/** | ||
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId | ||
* @param CartRepositoryInterface $cartRepository | ||
* @param IsCartMutationAllowedForCurrentUser $isCartMutationAllowedForCurrentUser | ||
* @param AddSimpleProductToCart $addProductToCart | ||
*/ | ||
public function __construct( | ||
MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId, | ||
CartRepositoryInterface $cartRepository, | ||
IsCartMutationAllowedForCurrentUser $isCartMutationAllowedForCurrentUser, | ||
AddSimpleProductToCart $addProductToCart | ||
) { | ||
$this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId; | ||
$this->cartRepository = $cartRepository; | ||
$this->isCartMutationAllowedForCurrentUser = $isCartMutationAllowedForCurrentUser; | ||
$this->addProductToCart = $addProductToCart; | ||
} | ||
|
||
/** | ||
* Add products to cart | ||
* | ||
* @param string $cartHash | ||
* @param array $cartItems | ||
* @return Quote | ||
* @throws GraphQlInputException | ||
*/ | ||
public function execute(string $cartHash, array $cartItems): Quote | ||
{ | ||
$cart = $this->getCart($cartHash); | ||
|
||
foreach ($cartItems as $cartItemData) { | ||
$this->addProductToCart->execute($cart, $cartItemData); | ||
} | ||
|
||
if ($cart->getData('has_error')) { | ||
throw new GraphQlInputException( | ||
__('Shopping cart error: %message', ['message' => $this->getCartErrors($cart)]) | ||
); | ||
} | ||
|
||
$this->cartRepository->save($cart); | ||
return $cart; | ||
} | ||
|
||
/** | ||
* Get cart | ||
* | ||
* @param string $cartHash | ||
* @return Quote | ||
* @throws GraphQlNoSuchEntityException | ||
* @throws GraphQlAuthorizationException | ||
*/ | ||
private function getCart(string $cartHash): Quote | ||
{ | ||
try { | ||
$cartId = $this->maskedQuoteIdToQuoteId->execute($cartHash); | ||
$cart = $this->cartRepository->get($cartId); | ||
} catch (NoSuchEntityException $e) { | ||
throw new GraphQlNoSuchEntityException( | ||
__('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash]) | ||
); | ||
} | ||
|
||
if (false === $this->isCartMutationAllowedForCurrentUser->execute($cartId)) { | ||
throw new GraphQlAuthorizationException( | ||
__( | ||
'The current user cannot perform operations on cart "%masked_cart_id"', | ||
['masked_cart_id' => $cartHash] | ||
) | ||
); | ||
} | ||
|
||
/** @var Quote $cart */ | ||
return $cart; | ||
} | ||
|
||
/** | ||
* Collecting cart errors | ||
* | ||
* @param Quote $cart | ||
* @return string | ||
*/ | ||
private function getCartErrors(Quote $cart): string | ||
{ | ||
$errorMessages = []; | ||
|
||
/** @var AbstractMessage $error */ | ||
foreach ($cart->getErrors() as $error) { | ||
$errorMessages[] = $error->getText(); | ||
} | ||
|
||
return implode(PHP_EOL, $errorMessages); | ||
} | ||
} |
149 changes: 149 additions & 0 deletions
149
app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\QuoteGraphQl\Model\Cart; | ||
|
||
use Magento\Catalog\Api\ProductRepositoryInterface; | ||
use Magento\Framework\DataObject; | ||
use Magento\Framework\DataObjectFactory; | ||
use Magento\Framework\Exception\NoSuchEntityException; | ||
use Magento\Framework\GraphQl\Exception\GraphQlInputException; | ||
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; | ||
use Magento\Framework\Stdlib\ArrayManager; | ||
use Magento\Quote\Model\Quote; | ||
|
||
/** | ||
* Add simple product to cart | ||
* | ||
* TODO: should be replaced for different types resolver | ||
*/ | ||
class AddSimpleProductToCart | ||
{ | ||
/** | ||
* @var ArrayManager | ||
*/ | ||
private $arrayManager; | ||
|
||
/** | ||
* @var DataObjectFactory | ||
*/ | ||
private $dataObjectFactory; | ||
|
||
/** | ||
* @var ProductRepositoryInterface | ||
*/ | ||
private $productRepository; | ||
|
||
/** | ||
* @param ArrayManager $arrayManager | ||
* @param DataObjectFactory $dataObjectFactory | ||
* @param ProductRepositoryInterface $productRepository | ||
*/ | ||
public function __construct( | ||
ArrayManager $arrayManager, | ||
DataObjectFactory $dataObjectFactory, | ||
ProductRepositoryInterface $productRepository | ||
) { | ||
$this->arrayManager = $arrayManager; | ||
$this->dataObjectFactory = $dataObjectFactory; | ||
$this->productRepository = $productRepository; | ||
} | ||
|
||
/** | ||
* Add simple product to cart | ||
* | ||
* @param Quote $cart | ||
* @param array $cartItemData | ||
* @return void | ||
* @throws GraphQlNoSuchEntityException | ||
* @throws GraphQlInputException | ||
*/ | ||
public function execute(Quote $cart, array $cartItemData): void | ||
{ | ||
$sku = $this->extractSku($cartItemData); | ||
$qty = $this->extractQty($cartItemData); | ||
$customizableOptions = $this->extractCustomizableOptions($cartItemData); | ||
|
||
try { | ||
$product = $this->productRepository->get($sku); | ||
} catch (NoSuchEntityException $e) { | ||
throw new GraphQlNoSuchEntityException(__('Could not find a product with SKU "%sku"', ['sku' => $sku])); | ||
} | ||
|
||
$result = $cart->addProduct($product, $this->createBuyRequest($qty, $customizableOptions)); | ||
|
||
if (is_string($result)) { | ||
throw new GraphQlInputException(__($result)); | ||
} | ||
} | ||
|
||
/** | ||
* Extract SKU from cart item data | ||
* | ||
* @param array $cartItemData | ||
* @return string | ||
* @throws GraphQlInputException | ||
*/ | ||
private function extractSku(array $cartItemData): string | ||
{ | ||
$sku = $this->arrayManager->get('data/sku', $cartItemData); | ||
if (!isset($sku)) { | ||
throw new GraphQlInputException(__('Missing key "sku" in cart item data')); | ||
} | ||
return (string)$sku; | ||
} | ||
|
||
/** | ||
* Extract Qty from cart item data | ||
* | ||
* @param array $cartItemData | ||
* @return float | ||
* @throws GraphQlInputException | ||
*/ | ||
private function extractQty(array $cartItemData): float | ||
{ | ||
$qty = $this->arrayManager->get('data/qty', $cartItemData); | ||
if (!isset($qty)) { | ||
throw new GraphQlInputException(__('Missing key "qty" in cart item data')); | ||
} | ||
return (float)$qty; | ||
} | ||
|
||
/** | ||
* Extract Customizable Options from cart item data | ||
* | ||
* @param array $cartItemData | ||
* @return array | ||
*/ | ||
private function extractCustomizableOptions(array $cartItemData): array | ||
{ | ||
$customizableOptions = $this->arrayManager->get('customizable_options', $cartItemData, []); | ||
|
||
$customizableOptionsData = []; | ||
foreach ($customizableOptions as $customizableOption) { | ||
$customizableOptionsData[$customizableOption['id']] = $customizableOption['value']; | ||
} | ||
return $customizableOptionsData; | ||
} | ||
|
||
/** | ||
* Format GraphQl input data to a shape that buy request has | ||
* | ||
* @param float $qty | ||
* @param array $customOptions | ||
* @return DataObject | ||
*/ | ||
private function createBuyRequest(float $qty, array $customOptions): DataObject | ||
{ | ||
return $this->dataObjectFactory->create([ | ||
'data' => [ | ||
'qty' => $qty, | ||
'options' => $customOptions, | ||
], | ||
]); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this one needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason why it's needed is ProductDateOptionType wants to see date value as an associate array (like ['year' => '2018', 'month' => '1', 'day' => '1']). By this changes, we allow to use string as a date value. This meets our schema shape.
The same approach is used in REST API.