Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions src/Adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,22 +375,51 @@ $query = $builder->getQuery();
$variables = $builder->getVariables();
```

### Custom GraphQL Query
### GraphQL Query Templates

The `MagentoProductQuery` class provides static methods to generate GraphQL queries with shared item body definitions, ensuring consistency across different query types.

```php
use BradSearch\SyncSdk\Magento\MagentoProductQuery;

// Use the default query template
$query = MagentoProductQuery::DEFAULT_QUERY;
// Default paginated query with full item fields
$query = MagentoProductQuery::getDefaultQuery();
$variables = [
'filter' => ['category_id' => ['eq' => '2']],
'pageSize' => 100,
'currentPage' => 1
];

// Query by specific product IDs (uses same item fields as default)
$query = MagentoProductQuery::getByIdsQuery();
$variables = ['ids' => ['325465', '1924192', '1924190']];

// Minimal query for faster fetching on large catalogs
$query = MagentoProductQuery::getMinimalQuery();

// Or use minimal query for faster fetching
$query = MagentoProductQuery::MINIMAL_QUERY;
// Incremental sync query (id, sku, updated_at only)
$query = MagentoProductQuery::getIncrementalQuery();

// Or set a custom query
// Use with query builder
$builder = new MagentoQueryBuilder();
$builder->setQuery($customQuery);
```

#### Available Query Methods

| Method | Variables | Item Fields | Use Case |
|--------|-----------|-------------|----------|
| `getDefaultQuery()` | `$filter`, `$pageSize`, `$currentPage` | Full (all fields) | Standard product sync |
| `getByIdsQuery()` | `$ids: [String!]` | Full (all fields) | Fetch specific products by ID |
| `getMinimalQuery()` | `$filter`, `$pageSize`, `$currentPage` | Minimal (basic fields) | Fast sync for large catalogs |
| `getIncrementalQuery()` | `$filter`, `$pageSize`, `$currentPage` | Incremental (id, sku, updated_at) | Check for updates |

#### Item Body Types

- **Full**: All product fields including attributes, descriptions, categories, prices, stock status
- **Minimal**: Basic fields only (id, sku, name, url, stock, price, image, categories)
- **Incremental**: Sync-only fields (id, sku, updated_at)

### Field Mapping

The MagentoAdapter performs **minimal transformation**:
Expand Down Expand Up @@ -455,7 +484,7 @@ foreach ($result['errors'] as $error) {
| `MagentoConfig` | Connection configuration (URL, token, timeout, SSL) |
| `MagentoGraphQLClient` | cURL-based GraphQL HTTP client |
| `MagentoQueryBuilder` | Fluent filter and pagination builder |
| `MagentoProductQuery` | Static GraphQL query templates |
| `MagentoProductQuery` | GraphQL query builder with shared item bodies |
| `MagentoPaginatedFetcher` | Automatic pagination with generator support |

## Shared Utilities (AdapterUtils)
Expand Down
179 changes: 134 additions & 45 deletions src/Magento/MagentoProductQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,45 @@
namespace BradSearch\SyncSdk\Magento;

/**
* Static GraphQL query templates for Magento product fetching
* GraphQL query builder for Magento product fetching.
*
* Provides static methods to generate GraphQL queries with shared item body definitions
* to ensure consistency across different query types.
*
* Available query methods:
* - getDefaultQuery(): Paginated query with filter support and full item fields
* - getByIdsQuery(): Query by specific product IDs with full item fields
* - getMinimalQuery(): Paginated query with minimal item fields (faster for large catalogs)
* - getIncrementalQuery(): Paginated query for sync checks (id, sku, updated_at only)
*
* Item body types:
* - Full: All product fields including attributes, descriptions, categories
* - Minimal: Basic fields for performance (id, sku, name, url, stock, price, image, categories)
* - Incremental: Sync-only fields (id, sku, updated_at)
*
* @example Default paginated query
* ```php
* $query = MagentoProductQuery::getDefaultQuery();
* $variables = [
* 'filter' => ['category_id' => ['eq' => '2']],
* 'pageSize' => 100,
* 'currentPage' => 1
* ];
* ```
*
* @example Query by product IDs
* ```php
* $query = MagentoProductQuery::getByIdsQuery();
* $variables = ['ids' => ['325465', '1924192', '1924190']];
* ```
*/
final class MagentoProductQuery
{
/**
* Default GraphQL query for fetching products with all common fields
*
* This query uses variables for filter, pageSize, and currentPage.
* Variables should be passed as:
* - $filter: ProductAttributeFilterInput (e.g., {"category_id": {"eq": "2"}})
* - $pageSize: Int (e.g., 100)
* - $currentPage: Int (e.g., 1)
* Full item body with all product fields.
* Used by getDefaultQuery() and getByIdsQuery().
*/
public const DEFAULT_QUERY = <<<'GRAPHQL'
query GetProducts($filter: ProductAttributeFilterInput, $pageSize: Int, $currentPage: Int) {
products(filter: $filter, pageSize: $pageSize, currentPage: $currentPage) {
total_count
page_info {
current_page
page_size
total_pages
}
items {
private const FULL_ITEMS_BODY = <<<'GRAPHQL'
id
sku
name
Expand Down Expand Up @@ -64,24 +80,13 @@ final class MagentoProductQuery
path
}
stock_status
}
}
}
GRAPHQL;

/**
* Minimal query for basic product data (faster for large catalogs)
* Minimal item body for faster queries on large catalogs.
* Used by getMinimalQuery().
*/
public const MINIMAL_QUERY = <<<'GRAPHQL'
query GetProducts($filter: ProductAttributeFilterInput, $pageSize: Int, $currentPage: Int) {
products(filter: $filter, pageSize: $pageSize, currentPage: $currentPage) {
total_count
page_info {
current_page
page_size
total_pages
}
items {
private const MINIMAL_ITEMS_BODY = <<<'GRAPHQL'
id
sku
name
Expand All @@ -100,34 +105,118 @@ final class MagentoProductQuery
level
path
}
}
}
}
GRAPHQL;

/**
* Query for incremental sync (check updated products)
* Incremental sync item body (minimal fields for checking updates).
* Used by getIncrementalQuery().
*/
public const INCREMENTAL_QUERY = <<<'GRAPHQL'
query GetProducts($filter: ProductAttributeFilterInput, $pageSize: Int, $currentPage: Int) {
products(filter: $filter, pageSize: $pageSize, currentPage: $currentPage) {
total_count
private const INCREMENTAL_ITEMS_BODY = <<<'GRAPHQL'
id
sku
updated_at
GRAPHQL;

/**
* Page info fragment for paginated queries.
*/
private const PAGE_INFO = <<<'GRAPHQL'
page_info {
current_page
page_size
total_pages
}
GRAPHQL;

private function __construct()
{
// Prevent instantiation
}

/**
* Get the default paginated query with full item fields.
*
* Variables:
* - $filter: ProductAttributeFilterInput (e.g., {"category_id": {"eq": "2"}})
* - $pageSize: Int (e.g., 100)
* - $currentPage: Int (e.g., 1)
*/
public static function getDefaultQuery(): string
{
return self::buildPaginatedQuery(self::FULL_ITEMS_BODY);
}

/**
* Get query for fetching products by their IDs with full item fields.
*
* Variables:
* - $ids: [String!] (e.g., ["325465", "1924192", "1924190"])
*/
Comment on lines +152 to +154

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The documentation for variables should be updated to include $pageSize. The caller needs to be aware that they must provide this parameter to prevent unexpected pagination from Magento, which could lead to incomplete data being returned when fetching a large number of products by ID.

     * Variables:
     * - $ids: [String!] (e.g., ["325465", "1924192", "1924190"])
     * - $pageSize: Int (e.g., the number of IDs, to fetch all products at once)
     */

public static function getByIdsQuery(): string
{
return self::buildByIdsQuery(self::FULL_ITEMS_BODY);
}

/**
* Get minimal paginated query for faster performance on large catalogs.
*
* Variables:
* - $filter: ProductAttributeFilterInput (e.g., {"category_id": {"eq": "2"}})
* - $pageSize: Int (e.g., 100)
* - $currentPage: Int (e.g., 1)
*/
public static function getMinimalQuery(): string
{
return self::buildPaginatedQuery(self::MINIMAL_ITEMS_BODY);
}

/**
* Get incremental sync query for checking updated products.
*
* Variables:
* - $filter: ProductAttributeFilterInput (e.g., {"category_id": {"eq": "2"}})
* - $pageSize: Int (e.g., 100)
* - $currentPage: Int (e.g., 1)
*/
public static function getIncrementalQuery(): string
{
return self::buildPaginatedQuery(self::INCREMENTAL_ITEMS_BODY);
}

/**
* Build a paginated query with the given items body.
*/
private static function buildPaginatedQuery(string $itemsBody): string
{
$pageInfo = self::PAGE_INFO;

return <<<GRAPHQL
query GetProducts(\$filter: ProductAttributeFilterInput, \$pageSize: Int, \$currentPage: Int) {
products(filter: \$filter, pageSize: \$pageSize, currentPage: \$currentPage) {
total_count
{$pageInfo}
items {
id
sku
updated_at
{$itemsBody}
}
}
}
GRAPHQL;
}

private function __construct()
/**
* Build a query by IDs with the given items body.
*/
private static function buildByIdsQuery(string $itemsBody): string
{
// Prevent instantiation
return <<<GRAPHQL
query GetProductsByIds(\$ids: [String!]) {
products(filter: { entity_id: { in: \$ids } }) {
Comment on lines +212 to +213

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The products query in Magento is paginated by default, typically returning 20 items per page. When fetching by a list of IDs, if more than 20 IDs are provided, only the first 20 products will be returned, leading to silent data loss. To ensure all requested products are fetched, you should add a $pageSize variable to the query. The caller will then be responsible for providing this value, which should ideally be the count of the IDs being requested.

query GetProductsByIds(\$ids: [String!], \$pageSize: Int) {
    products(filter: { entity_id: { in: \$ids } }, pageSize: \$pageSize) {

total_count
items {
{$itemsBody}
}
}
}
GRAPHQL;
}
}
Loading