Skip to main content

How to Write Batch Resolver in Magento 2 GraphQl

By |December 1, 2022 December 5th, 2022No Comments

What is Resolver?

  A resolver performs GraphQL request processing. In general, it is responsible for constructing a query, fetching data and performing any calculations, then transforming the fetched and calculated data into a GraphQL array format. Finally, it returns the results wrapped by a callable function.

Batch Resolver

    Batch resolvers gather GraphQL requests for the same field until there is no way to process the tree further without resolving previous requests. Magento recommends using batch resolvers for queries because they improve performance by fetching information required to resolve multiple GraphQL requests with a single operation.

It provides a way to resolve multiple branches/leaves at once (known as batching).

We are creating a batch resolver with an example module. Our module extends the default Product query with adding a new field named product_tag_label (Field).

product_tag_label field will give response Trending (string) or empty string identified by product attribute ‘is_trending’.

Follow the steps below to create a batch resolver with example module in Magento (Adobe Commerce)

  1. Create module
  2. Create new product attribute (Optional if your implementing your logic)
  3. Create schema graphql file
  4. Write batch resolver class

Note: You can skip step 2 (Create new product attribute) if you’re implementing your logic.

Step 1: Create Module

   Create a Vendor/Module Folder. This is your root module folder. In our case the Vendor is DCKAP and the Module is TrendingProductGraphQl.
a) Create a module.xml file

path: Vendor/Module/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="DCKAP_TrendingProductGraphQl" setup_version="1.0.0">
        <sequence>
            <module name="Magento_GraphQl"/>
            <module name="Magento_CatalogGraphQl"/>
        </sequence>
    </module>
</config>

b) Create a registration.php file

path: Vendor/Module/registration.php

<?php
use \Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'DCKAP_TrendingProductGraphQl',
__DIR__
);

c) Create a composer.json file

path: Vendor/Module/etc/composer.json

{
    "name": "dckap/module-trendingproductgraphql",
    "description": "",
    "type": "magento2-module",
    "license": "proprietary",
    "authors": [
       {
          "email": "info@yourdomain.com",
          "name": "DEV"
       }
    ],
    "minimum-stability": "dev",
    "require": {},
    "autoload": {
       "files": [
        "registration.php"
       ],
       "psr-4": {
          "DCKAP\\TrendingProductGraphQl\\": ""
       }
    }
}

Step 2: Create new product attribute

Create a new product attribute to identify whether the product is trending or not using Data patch.

path: Vendor/Module/Setup/Patch/Data/IsTrendingProductAttribute.php

<?php
namespace DCKAP\TrendingProductGraphQl\Setup\Patch\Data;

use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Eav\Model\Entity\Attribute\Source\Boolean;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;

class IsTrendingProductAttribute implements DataPatchInterface, PatchRevertableInterface
{
const CODE = 'is_trending';

/**
* @var ModuleDataSetupInterface $moduleDataSetup
*/
private $moduleDataSetup;

/**
* @var EavSetupFactory
*/
private $eavSetupFactory;

/**
* @param ModuleDataSetupInterface $moduleDataSetup
* @param EavSetupFactory $eavSetupFactory
*/
public function __construct(
   ModuleDataSetupInterface $moduleDataSetup,
   EavSetupFactory $eavSetupFactory
) {
   $this->moduleDataSetup = $moduleDataSetup;
   $this->eavSetupFactory = $eavSetupFactory;
}

/**
* Do Upgrade
*
* @return void
*/
public function apply()
{
   $this->moduleDataSetup->getConnection()->startSetup();
   /** @var EavSetup $eavSetup */
   $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
   $eavSetup->addAttribute(
      Product::ENTITY,
      self::CODE,
      [
         'type' => 'int',
         'label' => 'Trending Product',
         'input' => 'boolean',
         'source' => Boolean::class,
         'frontend' => '',
         'required' => false,
         'backend' => '',
         'sort_order' => '100',
         'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
         'default' => 0,
         'visible' => true,
         'user_defined' => true,
         'searchable' => false,
         'filterable' => false,
         'comparable' => false,
         'visible_on_front' => true,
         'unique' => false,
         'apply_to' => '',
         'group' => 'General',
         'used_in_product_listing' => true,
         'is_used_in_grid' => true,
         'is_visible_in_grid' => false,
         'is_filterable_in_grid' => true,
      ]
   );

   $this->moduleDataSetup->getConnection()->endSetup();
}

public function revert()
{
   $this->moduleDataSetup->getConnection()->startSetup();
   /** @var EavSetup $eavSetup */
   $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
   $eavSetup->removeAttribute(Product::ENTITY, self::CODE);

   $this->moduleDataSetup->getConnection()->endSetup();
}

/**
* @inheritdoc
*/
public function getAliases()
{
   return [];
}

/**
* @inheritdoc
*/
public static function getDependencies()
{
   return [];
}
}
Magento 2 GraphQl

Step 3: Create a schema graphql file

This is where we are going to add a new field for product query. In this schema file we need our new field with a resolver class path. We are adding a field in Product Interface so this field is accessible to all places that use Product Interface in GraphQL Response.

path: Vendor/Module/etc/schema.graphqls

interface ProductInterface {
product_tag_label: String @resolver(class: "DCKAP\\TrendingProductGraphQl\\Model\\Resolver\\Batch\\ProductTagLabel")
    @doc(description: "product tag label")
}

Step 4: Write batch resolver class

Once scheme.graphqls file completed next step we need to write batch resolver class that implements BatchResoverInterface.

path: Vendor/Module/Model/Resolver/Batch/ProductTagLabel.php

<?php
namespace DCKAP\TrendingProductGraphQl\Model\Resolver\Batch;

use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\Resolver\BatchRequestItemInterface;
use Magento\Framework\GraphQl\Query\Resolver\BatchResolverInterface;
use Magento\Framework\GraphQl\Query\Resolver\BatchResponse;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;

class ProductTagLabel implements BatchResolverInterface
{

/**
* @var ProductCollectionFactory
*/
private ProductCollectionFactory $productCollectionFactory;

public function __construct(
  ProductCollectionFactory $productCollectionFactory
)
{
  $this->productCollectionFactory = $productCollectionFactory;
}

public function resolve(ContextInterface $context, Field $field, array $requests): BatchResponse
{
  $store=$context->getExtensionAttributes()->getStore();

  $productIds=[];
  foreach ($requests as $request) {
    if (empty($request->getValue()['model'])) {
      throw new LocalizedException(__('"model" value should be specified'));
    }
    $product=$request->getValue()['model'];
    $productIds[] = $product->getId();
  }

  //preparing tag label for product ids
  $tagLabelsList=$this->getTagLabelForProducts($productIds, $store);

  //Matching requests with responses.
  $response = new BatchResponse();

  foreach ($requests as $request) {
    /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
    $product = $request->getValue()['model'];
    $result = [];
    if (array_key_exists($product->getId(), $tagLabelsList)) {
      $result =$tagLabelsList[$product->getId()];
    }
    $response->addResponse($request, $result);
  }
  return $response;

}

private function getTagLabelForProducts($productIds, $store){

  $tagLabelsList=[];
  $productCollection=$this->productCollectionFactory->create();
  $productCollection->addFieldToSelect(
    [
      'entity_id',
      'is_trending'
    ]
  );
  $productCollection->addIdFilter($productIds);
  $items=$productCollection->getItems();

  foreach ($items as $product) {
    $label="";
    $isTrending=$product->getIsTrending();

    if($isTrending==1){
      $label=__("Trending");
    }

    $tagLabelsList[$product->getId()] = $label;
  }

  return $tagLabelsList;
}
}

Here is the main function request processes the request public function resolve(ContextInterface $context, Field $field, array $requests): BatchResponse

resolve function has 3 params

  • $context – we can get information about store and customer id (Magento\Framework\GraphQl\Query\Resolver\ContextInterface)
  • $field – we can get information about fields requested
  • $requests – Array that contains resolved request item data.

Here, we are gathering productIds for a single request to prepare a response from another function.

            $product=$request->getValue()[‘model’];

         $productIds[] = $product->getId();

Once productIds are collected, we are using ProductCollection with an IN filter to prepare results.

To prepare the results, we need to attach those results in batch resolver response $response->addResponse($request, $result); add Response function that accepts two params one is the request object second one is result value.

That it, We completed the Batch resolver.

After we need to run commands

php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento module:status DCKAP_TrendingProductGraphQl

Result:

Request

{
  products(
filter:{
  category_id:{
    eq:"21"
  }
}
  ){
items{
  sku
  product_tag_label
}
  }
}

Response:

{
 "data": {
"products": {
 "items": [
   {
    "sku": "WT09",
    "product_tag_label": "Trending"
   },
   {
    "sku": "WT08",
    "product_tag_label": "Trending"
   },
   {
    "sku": "WT07",
    "product_tag_label": ""
   },
   {
    "sku": "WS05",
    "product_tag_label": "Trending"
   },
   {
    "sku": "WJ12",
    "product_tag_label": ""
   }

Screen

Browser extension for GraphQL Testing

Link :  Altair GraphQL Client

For further reading about Resolver

Prabhakaran R

Author Prabhakaran R

Prabhakaran is a Software Engineer and Magento Developer at DCKAP. He is a curious about technologies, and loves listening to music and watching series during his free time.

More posts by Prabhakaran R

Discover What You’re Missing

Get the weekly email full of actionable ideas and insights you can use at work and home.