Resolver Basics
Resolver Interface
<?php
namespace Magento\Framework\GraphQl\Resolver;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
interface ResolverInterface
{
/**
* @param Field $field
* @param mixed $context
* @param ResolveInfo $info
* @param array|null $value
* @param array|null $args
* @return mixed
*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
);
}
Basic Resolver
<?php
namespace Vendor\Module\GraphQl\Resolver\Query;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Resolver\ResolverInterface;
class ProductResolver implements ResolverInterface
{
public function __construct(
private \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
) {
}
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
) {
$sku = $args['sku'];
$product = $this->productRepository->get($sku);
return [
'id' => $product->getId(),
'name' => $product->getName(),
'sku' => $product->getSku(),
'price' => $product->getPrice()
];
}
}
Register Resolver
<!-- etc/schema.graphqls -->
<config>
<type name="Query">
<field name="product"
resolver="Vendor\Module\GraphQl\Resolver\Query\ProductResolver"
type="Product"/>
</type>
</config>
Data Loading
Collection Loading
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
) {
$collection = $this->collectionFactory->create();
// Apply filters
if (isset($args['filter'])) {
$this->filterApplier->applyFilters($collection, $args['filter']);
}
// Apply sorting
if (isset($args['sort'])) {
$this->sortApplier->applySorting($collection, $args['sort']);
}
// Apply pagination
$pageSize = $args['pageSize'] ?? 20;
$currentPage = $args['currentPage'] ?? 1;
$collection->setPageSize($pageSize);
$collection->setCurPage($currentPage);
$items = [];
foreach ($collection as $item) {
$items[] = $this->formatItem($item);
}
return [
'items' => $items,
'total_count' => $collection->getSize(),
'page_info' => [
'page_size' => $pageSize,
'current_page' => $currentPage
]
];
}
Repository Loading
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
) {
try {
$product = $this->productRepository->getById($args['id']);
return [
'id' => $product->getId(),
'name' => $product->getName(),
'sku' => $product->getSku()
];
} catch (\Exception $e) {
throw new \GraphQL\Error\GraphQLError(
__('Product not found'),
null,
null,
null,
['id' => $args['id']]
);
}
}
N+1 Prevention
The N+1 Problem
query {
products {
items {
name
category {
name # N+1: Separate query for each product's category
}
}
}
}
Solution: DataLoader
<?php
namespace Vendor\Module\GraphQl\DataLoader;
use GraphQL\Utils\Utils;
use Magento\Framework\GraphQl\Model\DataLoader\BatchResolverInterface;
class CategoryBatchResolver implements BatchResolverInterface
{
public function __construct(
private \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $collectionFactory,
) {
}
public function resolve(array $keys): array
{
$collection = $this->collectionFactory->create()
->addFieldToFilter('entity_id', ['in' => $keys])
->addFieldToSelect(['entity_id', 'name']);
$categories = [];
foreach ($collection as $category) {
$categories[$category->getId()] = [
'id' => $category->getId(),
'name' => $category->getName()
];
}
// Return in same order as keys
$result = [];
foreach ($keys as $key) {
$result[] = $categories[$key] ?? null;
}
return $result;
}
}
Use DataLoader in Resolver
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
) {
// Use DataLoader for related data
return $context->categoryLoader->load($value['category_id']);
}
Benefits
- Single query for all categories
- Reduces database queries
- Improves performance
Batching and Performance
Batch Loading
<?php
namespace Vendor\Module\GraphQl\Resolver\Product;
use Magento\Framework\GraphQl\Model\DataLoader\BatchResolverInterface;
class CategoryBatchResolver implements BatchResolverInterface
{
public function resolve(array $ids): array
{
// Single query for all IDs
$collection = $this->collectionFactory->create()
->addFieldToFilter('entity_id', ['in' => $ids])
->addFieldToSelect(['entity_id', 'name', 'url_path']);
$categories = [];
foreach ($collection as $category) {
$categories[$category->getId()] = $this->formatCategory($category);
}
// Return results in order
return array_map(fn($id) => $categories[$id] ?? null, $ids);
}
}
Query Complexity Analysis
<?php
namespace Vendor\Module\GraphQl\Model\Query\Complexity;
use Magento\Framework\GraphQl\Model\Query\Complexity\AbstractComplexity;
class ProductComplexity extends AbstractComplexity
{
public function getComplexity(
Field $field,
$childComplexity
): int {
return 10 + $childComplexity;
}
}
Query Depth Limiting
<!-- etc/graphQl.xml -->
<config>
<type name="Magento\Framework\GraphQl\Model\Query\Validator">
<arguments>
<argument name="validators" xsi:type="array">
<item name="depth_limit" xsi:type="object">
Magento\Framework\GraphQl\Model\Query\Validator\DepthLimit
</item>
</argument>
</arguments>
</type>
</config>
Performance Tips
- Use DataLoader for related entities
- Batch database queries instead of individual
- Limit query depth to prevent abuse
- Implement caching for frequently accessed data
- Use indexed fields in filters
Quiz
1. What is the N+1 problem?
2. How do you prevent N+1 queries?
3. What interface must resolvers implement?
Flashcards
Question
What is the N+1 problem?
Click to reveal answer
Answer
Too many individual queries for related data
Question
How to prevent N+1?
Click to reveal answer
Answer
Use DataLoader to batch related queries
Question
What interface do resolvers implement?
Click to reveal answer
Answer
ResolverInterface
Question
How do you register a resolver?
Click to reveal answer
Answer
In schema.graphqls with resolver attribute
Question
What is query complexity?
Click to reveal answer
Answer
A measure of how expensive a query is
Revision Notes
Key Takeaways
- 1. Resolvers provide data for GraphQL fields
- 2. N+1 problem occurs with individual related queries
- 3. DataLoader batches queries for efficiency
- 4. Query complexity limits prevent abuse
- 5. Cache frequently accessed data
Interview Tips
- • Explain the N+1 problem and solutions
- • Know how to create resolvers
- • Discuss performance optimization
- • Be ready to implement DataLoader
Cheat Sheet
Resolver:
implements ResolverInterface
resolve(Field, context, info, value, args)
N+1 Prevention:
Use DataLoader for batch loading
Single query for all related IDs
Registration:
<field resolver="Vendor\Resolver"/>
Performance:
- Limit query depth
- Use batch loading
- Cache results