Skip to content

Commit fdbf72e

Browse files
authored
ENGCOM-5510: Add Configurable Products to Cart Without Variant SKU #699
2 parents a9ceb1a + 8f6f780 commit fdbf72e

File tree

15 files changed

+526
-67
lines changed

15 files changed

+526
-67
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\ConfigurableProductGraphQl\Model\Cart\BuyRequest;
9+
10+
use Magento\Framework\Stdlib\ArrayManager;
11+
use Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestDataProviderInterface;
12+
13+
/**
14+
* DataProvider for building super attribute options in buy requests
15+
*/
16+
class SuperAttributeDataProvider implements BuyRequestDataProviderInterface
17+
{
18+
/**
19+
* @var ArrayManager
20+
*/
21+
private $arrayManager;
22+
23+
/**
24+
* @param ArrayManager $arrayManager
25+
*/
26+
public function __construct(
27+
ArrayManager $arrayManager
28+
) {
29+
$this->arrayManager = $arrayManager;
30+
}
31+
32+
/**
33+
* @inheritdoc
34+
*/
35+
public function execute(array $cartItemData): array
36+
{
37+
$superAttributes = $this->arrayManager->get('configurable_attributes', $cartItemData, []);
38+
39+
$superAttributesData = [];
40+
foreach ($superAttributes as $superAttribute) {
41+
$superAttributesData[$superAttribute['id']] = $superAttribute['value'];
42+
}
43+
44+
return ['super_attribute' => $superAttributesData];
45+
}
46+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\ConfigurableProductGraphQl\Model\Resolver;
9+
10+
use Magento\Framework\GraphQl\Config\Element\Field;
11+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
12+
use Magento\Framework\GraphQl\Query\ResolverInterface;
13+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
14+
use Magento\QuoteGraphQl\Model\Cart\AddProductsToCart;
15+
use Magento\QuoteGraphQl\Model\Cart\GetCartForUser;
16+
17+
/**
18+
* Add configurable products to cart GraphQl resolver
19+
* {@inheritdoc}
20+
*/
21+
class AddConfigurableProductsToCart implements ResolverInterface
22+
{
23+
/**
24+
* @var GetCartForUser
25+
*/
26+
private $getCartForUser;
27+
28+
/**
29+
* @var AddProductsToCart
30+
*/
31+
private $addProductsToCart;
32+
33+
/**
34+
* @param GetCartForUser $getCartForUser
35+
* @param AddProductsToCart $addProductsToCart
36+
*/
37+
public function __construct(
38+
GetCartForUser $getCartForUser,
39+
AddProductsToCart $addProductsToCart
40+
) {
41+
$this->getCartForUser = $getCartForUser;
42+
$this->addProductsToCart = $addProductsToCart;
43+
}
44+
45+
/**
46+
* @inheritdoc
47+
*/
48+
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
49+
{
50+
if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) {
51+
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
52+
}
53+
$maskedCartId = $args['input']['cart_id'];
54+
55+
if (!isset($args['input']['cart_items']) || empty($args['input']['cart_items'])
56+
|| !is_array($args['input']['cart_items'])
57+
) {
58+
throw new GraphQlInputException(__('Required parameter "cart_items" is missing'));
59+
}
60+
$cartItems = $args['input']['cart_items'];
61+
62+
$storeId = (int)$context->getExtensionAttributes()->getStore()->getId();
63+
$cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
64+
$this->addProductsToCart->execute($cart, $cartItems);
65+
66+
return [
67+
'cart' => [
68+
'model' => $cart,
69+
],
70+
];
71+
}
72+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\ConfigurableProductGraphQl\Model\Resolver;
9+
10+
use Magento\Catalog\Helper\Product\Configuration;
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Framework\GraphQl\Config\Element\Field;
13+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
14+
use Magento\Framework\GraphQl\Query\ResolverInterface;
15+
use Magento\Quote\Model\Quote\Item;
16+
17+
/**
18+
* @inheritdoc
19+
*/
20+
class ConfigurableCartItemOptions implements ResolverInterface
21+
{
22+
/**
23+
* @var Configuration
24+
*/
25+
private $configurationHelper;
26+
27+
/**
28+
* @param Configuration $configurationHelper
29+
*/
30+
public function __construct(
31+
Configuration $configurationHelper
32+
) {
33+
$this->configurationHelper = $configurationHelper;
34+
}
35+
36+
/**
37+
* Fetch and format configurable variants.
38+
*
39+
* @param Field $field
40+
* @param \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $context
41+
* @param ResolveInfo $info
42+
* @param array|null $value
43+
* @param array|null $args
44+
* @return array|\Magento\Framework\GraphQl\Query\Resolver\Value|mixed
45+
* @throws LocalizedException
46+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
47+
*/
48+
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
49+
{
50+
if (!isset($value['model'])) {
51+
throw new LocalizedException(__('"model" value should be specified'));
52+
}
53+
/** @var Item $cartItem */
54+
$cartItem = $value['model'];
55+
56+
$result = [];
57+
foreach ($this->configurationHelper->getOptions($cartItem) as $option) {
58+
$result[] = [
59+
'id' => $option['option_id'],
60+
'option_label' => $option['label'],
61+
'value_id' => $option['option_value'],
62+
'value_label' => $option['value'],
63+
];
64+
}
65+
66+
return $result;
67+
}
68+
}

app/code/Magento/ConfigurableProductGraphQl/composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"magento/module-catalog": "*",
88
"magento/module-configurable-product": "*",
99
"magento/module-catalog-graph-ql": "*",
10+
"magento/module-quote": "*",
11+
"magento/module-quote-graph-ql": "*",
1012
"magento/framework": "*"
1113
},
1214
"license": [

app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,11 @@
2222
</argument>
2323
</arguments>
2424
</type>
25+
<type name="Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestBuilder">
26+
<arguments>
27+
<argument name="providers" xsi:type="array">
28+
<item name="super_attribute" xsi:type="object">Magento\ConfigurableProductGraphQl\Model\Cart\BuyRequest\SuperAttributeDataProvider</item>
29+
</argument>
30+
</arguments>
31+
</type>
2532
</config>

app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Copyright © Magento, Inc. All rights reserved.
22
# See COPYING.txt for license details.
33
type Mutation {
4-
addConfigurableProductsToCart(input: AddConfigurableProductsToCartInput): AddConfigurableProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart")
4+
addConfigurableProductsToCart(input: AddConfigurableProductsToCartInput): AddConfigurableProductsToCartOutput @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\AddConfigurableProductsToCart")
55
}
66

77
type ConfigurableProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "ConfigurableProduct defines basic features of a configurable product and its simple product variants") {
@@ -50,13 +50,18 @@ type AddConfigurableProductsToCartOutput {
5050

5151
input ConfigurableProductCartItemInput {
5252
data: CartItemInput!
53-
variant_sku: String!
53+
configurable_attributes: [ConfigurableCartItemAttributesInput]!
5454
customizable_options:[CustomizableOptionInput!]
5555
}
5656

57+
input ConfigurableCartItemAttributesInput {
58+
id: Int!
59+
value: Int!
60+
}
61+
5762
type ConfigurableCartItem implements CartItemInterface {
5863
customizable_options: [SelectedCustomizableOption]!
59-
configurable_options: [SelectedConfigurableOption!]!
64+
configurable_options: [SelectedConfigurableOption!]! @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\ConfigurableCartItemOptions")
6065
}
6166

6267
type SelectedConfigurableOption {

app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,35 @@
1111
use Magento\Framework\Exception\NoSuchEntityException;
1212
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
1313
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
14+
use Magento\Framework\Stdlib\ArrayManager;
1415
use Magento\Quote\Model\Quote;
16+
use Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestBuilder;
1517

1618
/**
1719
* Add simple product to cart
1820
*/
1921
class AddSimpleProductToCart
2022
{
2123
/**
22-
* @var CreateBuyRequest
24+
* @var ProductRepositoryInterface
2325
*/
24-
private $createBuyRequest;
26+
private $productRepository;
2527

2628
/**
27-
* @var ProductRepositoryInterface
29+
* @var BuyRequestBuilder
2830
*/
29-
private $productRepository;
31+
private $buyRequestBuilder;
3032

3133
/**
3234
* @param ProductRepositoryInterface $productRepository
33-
* @param CreateBuyRequest $createBuyRequest
35+
* @param BuyRequestBuilder $buyRequestBuilder
3436
*/
3537
public function __construct(
3638
ProductRepositoryInterface $productRepository,
37-
CreateBuyRequest $createBuyRequest
39+
BuyRequestBuilder $buyRequestBuilder
3840
) {
3941
$this->productRepository = $productRepository;
40-
$this->createBuyRequest = $createBuyRequest;
42+
$this->buyRequestBuilder = $buyRequestBuilder;
4143
}
4244

4345
/**
@@ -53,8 +55,6 @@ public function __construct(
5355
public function execute(Quote $cart, array $cartItemData): void
5456
{
5557
$sku = $this->extractSku($cartItemData);
56-
$quantity = $this->extractQuantity($cartItemData);
57-
$customizableOptions = $cartItemData['customizable_options'] ?? [];
5858

5959
try {
6060
$product = $this->productRepository->get($sku);
@@ -63,7 +63,7 @@ public function execute(Quote $cart, array $cartItemData): void
6363
}
6464

6565
try {
66-
$result = $cart->addProduct($product, $this->createBuyRequest->execute($quantity, $customizableOptions));
66+
$result = $cart->addProduct($product, $this->buyRequestBuilder->build($cartItemData));
6767
} catch (\Exception $e) {
6868
throw new GraphQlInputException(
6969
__(
@@ -92,26 +92,4 @@ private function extractSku(array $cartItemData): string
9292
}
9393
return (string)$cartItemData['data']['sku'];
9494
}
95-
96-
/**
97-
* Extract quantity from cart item data
98-
*
99-
* @param array $cartItemData
100-
* @return float
101-
* @throws GraphQlInputException
102-
*/
103-
private function extractQuantity(array $cartItemData): float
104-
{
105-
if (!isset($cartItemData['data']['quantity'])) {
106-
throw new GraphQlInputException(__('Missed "qty" in cart item data'));
107-
}
108-
$quantity = (float)$cartItemData['data']['quantity'];
109-
110-
if ($quantity <= 0) {
111-
throw new GraphQlInputException(
112-
__('Please enter a number greater than 0 in this field.')
113-
);
114-
}
115-
return $quantity;
116-
}
11795
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\QuoteGraphQl\Model\Cart\BuyRequest;
9+
10+
use Magento\Framework\DataObject;
11+
use Magento\Framework\DataObjectFactory;
12+
13+
/**
14+
* Build buy request for adding products to cart
15+
*/
16+
class BuyRequestBuilder
17+
{
18+
/**
19+
* @var BuyRequestDataProviderInterface[]
20+
*/
21+
private $providers;
22+
23+
/**
24+
* @var DataObjectFactory
25+
*/
26+
private $dataObjectFactory;
27+
28+
/**
29+
* @param DataObjectFactory $dataObjectFactory
30+
* @param array $providers
31+
*/
32+
public function __construct(
33+
DataObjectFactory $dataObjectFactory,
34+
array $providers = []
35+
) {
36+
$this->dataObjectFactory = $dataObjectFactory;
37+
$this->providers = $providers;
38+
}
39+
40+
/**
41+
* Build buy request for adding product to cart
42+
*
43+
* @param array $cartItemData
44+
* @return DataObject
45+
*/
46+
public function build(array $cartItemData): DataObject
47+
{
48+
$requestData = [];
49+
foreach ($this->providers as $provider) {
50+
$requestData = array_merge($requestData, $provider->execute($cartItemData));
51+
}
52+
53+
return $this->dataObjectFactory->create(['data' => $requestData]);
54+
}
55+
}

0 commit comments

Comments
 (0)