Skip to content

Commit c8f2a3e

Browse files
committed
Lynx 311: GraphQL cart items pagination (#190)
* LYNX-311:GraphQL cart items pagination using repository returning paginated results * LYNX-311:Added CartItemPaginationInterface in di * LYNX-311: Create new pagination class for return cart items and products * LYNX-311:Fix review comments * LYNX-311:Updated review comments * LYNX-311:Updated review comments
1 parent af73eea commit c8f2a3e

File tree

4 files changed

+443
-0
lines changed

4 files changed

+443
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
/**
3+
* Copyright 2023 Adobe
4+
* All Rights Reserved.
5+
*
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Adobe and its suppliers, if any. The intellectual
8+
* and technical concepts contained herein are proprietary to Adobe
9+
* and its suppliers and are protected by all applicable intellectual
10+
* property laws, including trade secret and copyright laws.
11+
* Dissemination of this information or reproduction of this material
12+
* is strictly forbidden unless prior written permission is obtained from
13+
* Adobe.
14+
*/
15+
declare(strict_types=1);
16+
17+
namespace Magento\QuoteGraphQl\Model\CartItem;
18+
19+
use Magento\Catalog\Api\Data\ProductInterface;
20+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
21+
use Magento\Quote\Model\Quote;
22+
use Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory as ItemCollectionFactory;
23+
24+
/**
25+
* Fetch Cart items and product models corresponding to a cart
26+
*/
27+
class GetPaginatedCartItems
28+
{
29+
/**
30+
* @param ProductCollectionFactory $productCollectionFactory
31+
* @param ItemCollectionFactory $itemCollectionFactory
32+
*/
33+
public function __construct(
34+
private readonly ProductCollectionFactory $productCollectionFactory,
35+
private readonly ItemCollectionFactory $itemCollectionFactory
36+
) {
37+
}
38+
39+
/**
40+
* Get product models based on items in cart
41+
*
42+
* @param array $cartProductsIds
43+
* @return ProductInterface[]
44+
*/
45+
private function getCartProduct(array $cartProductsIds): array
46+
{
47+
if (empty($cartProductsIds)) {
48+
return [];
49+
}
50+
/** @var \Magento\Framework\Data\Collection $productCollection */
51+
$productCollection = $this->productCollectionFactory->create()
52+
->addAttributeToSelect('*')
53+
->addIdFilter($cartProductsIds)
54+
->setFlag('has_stock_status_filter', true);
55+
56+
return $productCollection->getItems();
57+
}
58+
59+
/**
60+
* Get visible cart items and product data for cart items
61+
*
62+
* @param Quote $cart
63+
* @param int $pageSize
64+
* @param int $offset
65+
* @return array
66+
*/
67+
public function execute(Quote $cart, int $pageSize, int $offset): array
68+
{
69+
$result = [];
70+
if (!$cart->getId()) {
71+
return $result;
72+
}
73+
/** @var \Magento\Framework\Data\Collection $itemCollection */
74+
$itemCollection = $this->itemCollectionFactory->create()
75+
->addFieldToFilter('parent_item_id', ['null' => true])
76+
->addFieldToFilter('quote_id', $cart->getId())
77+
->setCurPage($offset)
78+
->setPageSize($pageSize);
79+
80+
$items = [];
81+
$cartProductsIds = [];
82+
$itemDeletedCount = 0;
83+
/** @var \Magento\Quote\Model\Quote\Item $item */
84+
foreach ($itemCollection->getItems() as $item) {
85+
if (!$item->isDeleted()) {
86+
$items[] = $item;
87+
$cartProductsIds[] = $item->getProduct()->getId();
88+
} else {
89+
$itemDeletedCount++;
90+
}
91+
}
92+
$result['total'] = $itemCollection->getSize() - $itemDeletedCount;
93+
$result['items'] = $items;
94+
$result['products'] = $this->getCartProduct($cartProductsIds);
95+
return $result;
96+
}
97+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
/**
3+
* Copyright 2023 Adobe
4+
* All Rights Reserved.
5+
*
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Adobe and its suppliers, if any. The intellectual
8+
* and technical concepts contained herein are proprietary to Adobe
9+
* and its suppliers and are protected by all applicable intellectual
10+
* property laws, including trade secret and copyright laws.
11+
* Dissemination of this information or reproduction of this material
12+
* is strictly forbidden unless prior written permission is obtained from
13+
* Adobe.
14+
*/
15+
declare(strict_types=1);
16+
17+
namespace Magento\QuoteGraphQl\Model\Resolver;
18+
19+
use Magento\Catalog\Model\Product;
20+
use Magento\Framework\Exception\LocalizedException;
21+
use Magento\Framework\GraphQl\Config\Element\Field;
22+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
23+
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
24+
use Magento\Framework\GraphQl\Query\ResolverInterface;
25+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
26+
use Magento\Quote\Model\Quote;
27+
use Magento\QuoteGraphQl\Model\CartItem\GetPaginatedCartItems;
28+
use Magento\Framework\GraphQl\Query\Uid;
29+
30+
/**
31+
* @inheritdoc
32+
*/
33+
class CartItemsPaginated implements ResolverInterface
34+
{
35+
/**
36+
* @param GetPaginatedCartItems $pagination
37+
* @param Uid $uidEncoder
38+
*/
39+
public function __construct(
40+
private readonly GetPaginatedCartItems $pagination,
41+
private readonly Uid $uidEncoder
42+
) {
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($value['model'])) {
51+
throw new LocalizedException(__('"model" value should be specified'));
52+
}
53+
/** @var Quote $cart */
54+
$cart = $value['model'];
55+
56+
if (isset($args['currentPage']) && $args['currentPage'] < 1) {
57+
throw new GraphQlInputException(__('currentPage value must be greater than 0.'));
58+
}
59+
if (isset($args['pageSize']) && $args['pageSize'] < 1) {
60+
throw new GraphQlInputException(__('pageSize value must be greater than 0.'));
61+
}
62+
63+
$pageSize = $args['pageSize'];
64+
$currentPage = $args['currentPage'];
65+
$offset = ($currentPage - 1) * $pageSize;
66+
$itemsData = [];
67+
68+
try {
69+
$paginatedCartItems = $this->pagination->execute($cart, $pageSize, $offset);
70+
$cartProductsData = $this->getCartProductsData($paginatedCartItems['products']);
71+
72+
foreach ($paginatedCartItems['items'] as $cartItem) {
73+
$productId = $cartItem->getProduct()->getId();
74+
if (!isset($cartProductsData[$productId])) {
75+
$itemsData[] = new GraphQlNoSuchEntityException(
76+
__("The product that was requested doesn't exist. Verify the product and try again.")
77+
);
78+
continue;
79+
}
80+
$cartItem->setQuote($cart);
81+
$itemsData[] = [
82+
'id' => $cartItem->getItemId(),
83+
'uid' => $this->uidEncoder->encode((string) $cartItem->getItemId()),
84+
'quantity' => $cartItem->getQty(),
85+
'product' => $cartProductsData[$productId],
86+
'model' => $cartItem,
87+
];
88+
}
89+
90+
return [
91+
'items' => $itemsData,
92+
'total_count' => $paginatedCartItems['total'],
93+
'page_info' => [
94+
'page_size' => $pageSize,
95+
'current_page' => $currentPage,
96+
'total_pages' => (int)ceil($paginatedCartItems['total'] / $pageSize)
97+
],
98+
];
99+
} catch (\Exception $e) {
100+
throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
101+
}
102+
}
103+
104+
/**
105+
* Get product data for cart items
106+
*
107+
* @param Product[] $products
108+
* @return array
109+
*/
110+
private function getCartProductsData(array $products): array
111+
{
112+
$productsData = [];
113+
foreach ($products as $product) {
114+
$productsData[$product->getId()] = $product->getData();
115+
$productsData[$product->getId()]['model'] = $product;
116+
$productsData[$product->getId()]['uid'] = $this->uidEncoder->encode((string) $product->getId());
117+
}
118+
return $productsData;
119+
}
120+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ type PlaceOrderOutput @doc(description: "Contains the results of the request to
222222
type Cart @doc(description: "Contains the contents and other details about a guest or customer cart.") {
223223
id: ID! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\MaskedCartId") @doc(description: "The unique ID for a `Cart` object.")
224224
items: [CartItemInterface] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItems") @doc(description: "An array of products that have been added to the cart.")
225+
paginated_items(pageSize: Int = 20, currentPage: Int = 1): CartItems @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItemsPaginated")
225226
applied_coupon: AppliedCoupon @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\AppliedCoupon") @deprecated(reason: "Use `applied_coupons` instead.")
226227
applied_coupons: [AppliedCoupon] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\AppliedCoupons") @doc(description:"An array of `AppliedCoupon` objects. Each object contains the `code` text attribute, which specifies the coupon code.")
227228
email: String @resolver (class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartEmail") @doc(description: "The email address of the guest or customer.")
@@ -234,6 +235,12 @@ type Cart @doc(description: "Contains the contents and other details about a gue
234235
is_virtual: Boolean! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartIsVirtual") @doc(description: "Indicates whether the cart contains only virtual products.")
235236
}
236237

238+
type CartItems {
239+
items: [CartItemInterface]! @doc(description: "An array of products that have been added to the cart.")
240+
page_info: SearchResultPageInfo @doc(description: "Metadata for pagination rendering.")
241+
total_count: Int! @doc(description: "The number of returned cart items.")
242+
}
243+
237244
interface CartAddressInterface @typeResolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartAddressTypeResolver") {
238245
uid: String! @doc(description: "The unique id of the customer address.")
239246
firstname: String! @doc(description: "The first name of the customer or guest.")

0 commit comments

Comments
 (0)