If you know that option id is 1 so you doing all right. But if your question is "why is SKU does not contains the suffix from selected custom option" the answer is: SKU from response in GraphQL just a product SKU (not a quote-item-sku), so it is ok. When you place your order the order item would has correct SKU with all suffixes from the selected custom options (in case they exists).
Here is my example with product configuration (custom options), request, response and order item (after placing order through GraphQL):
Product configuration in admin (custom option with id 5 has SKU "text" so result SKU would be "G-text" for order item):
Request:
mutation addSimpleProductsToCart($magentoCartId: String!) {
addSimpleProductsToCart(
input: {
cart_id: $magentoCartId
cart_items: [
{
data: {
quantity: 1
sku: "G"
}
customizable_options: [
{
id: 5
value_string: "Hello GraphQL! :)"
}
]
}
]
}
) {
cart {
items {
... on SimpleCartItem {
product {
name
sku
}
quantity
customizable_options {
label
type
values {
label
value
price {
value
}
}
}
}
}
}
}
}
Response (this cart already has two products without custom options):
{
"data": {
"addSimpleProductsToCart": {
"cart": {
"items": [
{
"product": {
"name": "A",
"sku": "A"
},
"quantity": 1,
"customizable_options": []
},
{
"product": {
"name": "B",
"sku": "B"
},
"quantity": 1,
"customizable_options": []
},
{
"product": {
"name": "G",
"sku": "G"
},
"quantity": 3,
"customizable_options": [
{
"label": "Text",
"type": "field",
"values": [
{
"label": "",
"value": "Hello GraphQL! :)",
"price": {
"value": 33
}
}
]
}
]
}
]
}
}
}
}
And finally order items, where the las item has correct SKU - "G-text":
More detailed investigation
Let's go deeper and check how it is going in the resolver class. First point is vendor/magento/module-quote-graph-ql/etc/schema.graphqls
where the mutation was defined:
addSimpleProductsToCart(input: AddSimpleProductsToCartInput @doc(description: "An input object that defines which simple products to add to the cart.")): AddSimpleProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart") @doc(description:"Add one or more simple products to the specified cart. We recommend using `addProductsToCart` instead.")
From there we get the AddSimpleProductsToCartOutput
output which is returning our cart data. Let's check it's definition (in same graphql schema):
type AddSimpleProductsToCartOutput @doc(description: "Contains details about the cart after adding simple or group products.") {
cart: Cart! @doc(description: "The cart after adding products.")
}
It is returning the Cart
type which containing cart items:
type Cart @doc(description: "Contains the contents and other details about a guest or customer cart.") {
id: ID! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\MaskedCartId") @doc(description: "The unique ID for a `Cart` object.")
items: [CartItemInterface] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItems") @doc(description: "An array of products that have been added to the cart.")
...
}
and finally each cart item contains a ProductInterface
(it is a product and totally not a quote item):
interface CartItemInterface @typeResolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CartItemTypeResolver") @doc(description: "An interface for products in a cart.") {
id: String! @deprecated(reason: "Use `uid` instead.")
...
product: ProductInterface! @doc(description: "Details about an item in the cart.")
}
which was defined in the catalog graphql module (vendor/magento/module-catalog-graph-ql/etc/schema.graphqls
) and has target field sku
:
interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "Contains fields that are common to all types of products.") {
id: Int @deprecated(reason: "Use the `uid` field instead.") @doc(description: "The ID number assigned to the product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToId")
...
sku: String @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.")
...
}
Theoretically this is the answer - SKU from response is the pure product entity SKU, which has no modifications from selected custom options or from any additional modifications. To confirm that we must go deeper and inspect how the resolver class works.
Problem view from `PHP` side:
We must check from where the product data comes to the response. As per definition of the Cart
type (which is a main part of our output model) we get the CartItems
array for which the vendor/magento/module-quote-graph-ql/Model/Resolver/CartItems.php
resolver was used. What inside? The resolve
method unpack all quote items from the quote and one by one get corresponding product and unpack it's data to the result object ($itemsData
):
$cartProductsData = $this->getCartProductsData($cart);
$cartItems = $cart->getAllVisibleItems();
/** @var QuoteItem $cartItem */
foreach ($cartItems as $cartItem) {
$productId = $cartItem->getProduct()->getId();
if (!isset($cartProductsData[$productId])) {
$itemsData[] = new GraphQlNoSuchEntityException(
__("The product that was requested doesn't exist. Verify the product and try again.")
);
continue;
}
$productData = $cartProductsData[$productId];
$itemsData[] = [
'id' => $cartItem->getItemId(),
'uid' => $this->uidEncoder->encode((string) $cartItem->getItemId()),
'quantity' => $cartItem->getQty(),
'product' => $productData,
'model' => $cartItem,
];
}
The SKU field was inside the $productData
array which comes from the getCartProductsData
method:
/**
* Get product data for cart items
*
* @param Quote $cart
* @return array
*/
private function getCartProductsData(Quote $cart): array
{
$products = $this->getCartProducts->execute($cart);
$productsData = [];
foreach ($products as $product) {
$productsData[$product->getId()] = $product->getData();
$productsData[$product->getId()]['model'] = $product;
$productsData[$product->getId()]['uid'] = $this->uidEncoder->encode((string) $product->getId());
}
return $productsData;
}
where we obtain product for the cart from the Magento\QuoteGraphQl\Model\Cart\GetCartProducts
model. All this class is doing is getting all product ids from the $cart
(argument) and use it as a filter for the product repository:
/**
* Get product models based on items in cart
*
* @param Quote $cart
* @return ProductInterface[]
*/
public function execute(Quote $cart): array
{
$cartItems = $cart->getAllVisibleItems();
if (empty($cartItems)) {
return [];
}
$cartItemIds = \array_map(
function ($item) {
return $item->getProduct()->getId();
},
$cartItems
);
$searchCriteria = $this->searchCriteriaBuilder->addFilter('entity_id', $cartItemIds, 'in')->create();
$products = $this->productRepository->getList($searchCriteria)->getItems();
return $products;
}
As a result we see that a product data which contains a SKU just a raw product data from the catalog product repository. It cant know the actual information about the selected custom options and quote item SKU.
To resolve such a problem I'll recommend you to extend the CartItemInterface
and add a custom field cart_item_sku
with own resolver class which should simply take the cart item (quote item) and return it's SKU as a string.
How to resolve?
You could create a custom module for extending the regular CartItemInterface
purpose and add the cart_item_sku
to it.
Example module on GitHub
Create new module with general files like registration.php
, etc/module.xml
, composer.json
. In my example on GitHub module has the name MageWorx_ExtendedCartGraphQl
.
Create schema where we can extend CartItemInterface
adding new field with custom resolver:
etc/schema.graphqls
# Add cart item sku with suffixes to the regular cat item.
interface CartItemInterface {
cart_item_sku: String! @resolver(class: "MageWorx\\ExtendedCartGraphQl\\Model\\Resolver\\CartItemSku") @doc(description: "SKU of cart item with suffixes from selected custom options or other modifications")
}
Create the resolver class for our new field as defined in the schema:
Model/Resolver/CartItemSku.php
<?php
/**
* Copyright © MageWorx. All rights reserved.
* See LICENSE.txt for license details.
*/
declare(strict_types=1);
namespace MageWorx\ExtendedCartGraphQl\Model\Resolver;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Quote\Model\Quote\Item as QuoteItem;
class CartItemSku implements \Magento\Framework\GraphQl\Query\ResolverInterface
{
/**
* Fetches the data from persistence models and format it according to the GraphQL schema.
*
* @param \Magento\Framework\GraphQl\Config\Element\Field $field
* @param ContextInterface $context
* @param ResolveInfo $info
* @param array|null $value
* @param array|null $args
* @return mixed|Value
* @throws \Exception
*/
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
if (!isset($value['model'])) {
throw new LocalizedException(__('"model" value should be specified'));
}
/** @var QuoteItem $cartItem */
$cartItem = $value['model'];
// Just take sku from quote item :)
return $cartItem->getSku();
}
}
Deploy to your server, run setup:upgrade
and setup:di:compile
. Thats all. Now we can test our new field using GraphQL:
Request:
{
customerCart {
id
items {
id
cart_item_sku
product {
name
sku
}
quantity
}
}
}
Response:
{
"data": {
"customerCart": {
"id": "LI0zUyH5YVKT25fbyl5wRB3UEqRz9JnZ",
"items": [
{
"id": "46",
"cart_item_sku": "G-text",
"product": {
"name": "G",
"sku": "G"
},
"quantity": 1
},
{
"id": "47",
"cart_item_sku": "A",
"product": {
"name": "A",
"sku": "A"
},
"quantity": 1
}
]
}
}
}
Screenshot from postman:
As you can see the product with selected custom option now has the correct cart_item_sku
: "G-text"
Happy coding :)