Skip to content

Shopify GraphQL Product Data Fetching #59941

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 76 commits into from
Aug 4, 2025

Conversation

naman03malhotra
Copy link
Contributor

@naman03malhotra naman03malhotra commented Jul 23, 2025

Submission Review Guidelines:

Changes proposed in this Pull Request:

This PR implements GraphQL product data fetching for the WooCommerce CLI Migrator's Shopify platform support, building upon the existing REST API product counting functionality. This enhancement enables fetching detailed product information with cursor-based pagination for efficient large-scale migrations.

Key Features:

GraphQL API Integration: Added comprehensive GraphQL support to ShopifyClient for querying Shopify's Admin API
Product Data Fetching: Implemented fetch_batch() method in ShopifyFetcher to retrieve detailed product information
Cursor Pagination: Full support for GraphQL cursor-based pagination to handle large product catalogs
Rich Product Data: Fetches products with variants, images, collections, metadata, and more
CLI Commands: Enhanced ProductsCommand with --fetch, --limit, and --after flags

Components Enhanced:

  1. ShopifyClient.php: Added graphql_request() method with proper error handling
  2. ShopifyFetcher.php: Implemented real fetch_batch() method using comprehensive GraphQL query
  3. ProductsCommand.php: Added fetch functionality with pagination and rich output formatting
  4. Test Coverage: Added extensive unit tests for all new GraphQL functionality

Screenshots or screen recordings:

Before (Stubs Only):

wp wc migrate products --fetch
# Would return: WP_Error - fetch_batch not implemented

After (Full GraphQL Implementation):

wp wc migrate products --fetch --limit=3
# Fetches 3 products with full details:
# Product ID: gid://shopify/Product/123456789
# Title: Awesome T-Shirt  
# Status: ACTIVE
# Variants: 2

How to test the changes in this Pull Request:

Using the WooCommerce Testing Instructions Guide, include your detailed testing instructions:

Prerequisites

  1. Set up WooCommerce CLI Migrator with Shopify credentials
  2. Ensure you have access to a Shopify store with products (test store recommended)

Basic Product Fetching Tests

  1. Test basic product fetching:

    wp wc migrate products --fetch --limit=3
    • Verify: Should display 3 products with details (ID, title, status, variant count)
    • Verify: Should show pagination command if more products exist
  2. Test cursor pagination:
    Cursor is printed at the end of the command.

    wp wc migrate products --fetch --limit=5 --after=CURSOR_FROM_PREVIOUS_COMMAND
    • Verify: Should fetch next batch starting from the cursor
    • Verify: Should display different products than the first batch
  3. Test various limit sizes:

    wp wc migrate products --fetch --limit=1    # Minimum
    wp wc migrate products --fetch --limit=10   # Medium batch
    wp wc migrate products --fetch --limit=50   # Large batch
    • Verify: All commands work without errors
    • Verify: Returned product count matches requested limit (or remaining products)

Error Handling Tests

  1. Test with invalid credentials:

    • Temporarily invalidate Shopify credentials
    • Run: wp wc migrate products --fetch
    • Verify: Should show clear error message about credentials
  2. Test with invalid cursor:

    wp wc migrate products --fetch --after=invalid_cursor
    • Verify: Should show GraphQL error message

Integration Tests

  1. Test backward compatibility:
    wp wc migrate products --count              # REST API (existing)
    wp wc migrate products --count --status=active
    • Verify: Count functionality still works as before
    • Verify: Fetch and count can be used together in sequence

Changelog entry

  • Automatically create a changelog entry from the details below.
Changelog Entry Details

Significance

  • Patch
  • Minor
  • Major

Type

  • Fix - Fixes an existing bug
  • Add - Adds functionality
  • Update - Update existing functionality
  • Dev - Development related task
  • Tweak - A minor adjustment to the codebase
  • Performance - Address performance issues
  • Enhancement - Improvement to existing functionality

Message

Add GraphQL product data fetching with cursor pagination to WooCommerce CLI Migrator for Shopify platform. Enables fetching detailed product information including variants, images, and metadata through new --fetch command with --limit and --after flags.

Copy link
Member

@xristos3490 xristos3490 left a comment

Choose a reason for hiding this comment

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

Fetching overall looks good and retrieves the product information as expected. I did notice an issue with pagination, though. Check this out:
Screenshot 2025-07-28 at 3 00 48 PM

It's a warning like:

Warning: Undefined property: stdClass::$has_next_page in /Users/chris/Repos/woocommerce/plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcher.php on line 217

and while the shop has over 1000 products, I can't continue the process. I also left a comment on how the optimal process should be below.

@naman03malhotra
Copy link
Contributor Author

Thanks for the review. I've fixed the warning here: ff95efc

Copy link
Member

@xristos3490 xristos3490 left a comment

Choose a reason for hiding this comment

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

Thanks for fixing the warning and sharing the plans for re-looping the batches, @naman03malhotra!

Did another test and everything works as expected on my end!

Screenshot 2025-07-31 at 11 16 08 AM

Base automatically changed from migrator/shopify-count to trunk August 4, 2025 07:12
Copy link
Contributor

coderabbitai bot commented Aug 4, 2025

📝 Walkthrough

Walkthrough

This change introduces a new Shopify product-fetching feature to the WooCommerce CLI Migrator, enabling product retrieval via Shopify's GraphQL API with cursor-based pagination. The CLI command gains --fetch, --limit, and --after options. Supporting logic, error handling, and extensive unit tests for the new functionality are included.

Changes

Cohort / File(s) Change Summary
Changelog Documentation
plugins/woocommerce/changelog/59941-migrator-shopify-product-fetch
Documents the new Shopify product-fetching feature, CLI options, and pagination support.
CLI Command Extension
plugins/woocommerce/src/Internal/CLI/Migrator/Commands/ProductsCommand.php
Adds --fetch, --limit, and --after CLI options; implements fetch logic and updates help text and examples.
Platform Fetcher Interface
plugins/woocommerce/src/Internal/CLI/Migrator/Interfaces/PlatformFetcherInterface.php
Updates PHPDoc for fetch_batch to use has_next_page key for pagination.
Shopify GraphQL Client
plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClient.php
Adds graphql_request method and supporting helpers for Shopify GraphQL API requests and error handling.
Shopify Fetcher Implementation
plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcher.php
Implements product fetching via GraphQL, including query, pagination, and error handling. Adds helper for building query variables.
Products Command Tests
plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Commands/ProductsCommandTest.php
Adds unit tests for private fetch and count handlers, covering success, error, and pagination scenarios.
Shopify Client Tests
plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClientTest.php
Adds tests for graphql_request, covering success, variable handling, HTTP and GraphQL errors, invalid JSON, and missing credentials.
Shopify Fetcher Tests
plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcherTest.php
Adds tests for fetch_batch, covering success, cursor pagination, error cases, variable building, and edge cases. Updates key assertions to has_next_page.
Mock WP CLI Enhancements
plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Mocks/MockWPCLI.php
Extends mock with tracking of last success message and all log messages to support new test assertions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI_Command
    participant PlatformRegistry
    participant ShopifyFetcher
    participant ShopifyClient
    participant ShopifyGraphQL

    User->>CLI_Command: Run `products` command with --fetch, --limit, --after
    CLI_Command->>PlatformRegistry: get_fetcher('shopify')
    PlatformRegistry-->>CLI_Command: ShopifyFetcher
    CLI_Command->>ShopifyFetcher: fetch_batch({limit, after})
    ShopifyFetcher->>ShopifyClient: graphql_request(query, variables)
    ShopifyClient->>ShopifyGraphQL: POST /graphql.json (query, variables)
    ShopifyGraphQL-->>ShopifyClient: GraphQL response (data or errors)
    ShopifyClient-->>ShopifyFetcher: Decoded data or WP_Error
    ShopifyFetcher-->>CLI_Command: {items, end_cursor, has_next_page}
    CLI_Command->>User: Output product info and pagination instructions
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~35 minutes

Possibly related PRs

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 874584a and e89d1f3.

📒 Files selected for processing (1)
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Mocks/MockWPCLI.php (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/code-quality.mdc)

**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable

Files:

  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Mocks/MockWPCLI.php
**/*.{php,js,ts,jsx,tsx}

⚙️ CodeRabbit Configuration File

**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:

  • Guards against unexpected inputs.
  • Sanitizes and validates any potentially dangerous inputs.
  • Is backwards compatible.
  • Is readable and intuitive.
  • Has unit or E2E tests where applicable.

Files:

  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Mocks/MockWPCLI.php
🧠 Learnings (2)
📚 Learning: test mocks should accurately simulate the behavior of the functions they replace, including return v...
Learnt from: vladolaru
PR: woocommerce/woocommerce#59160
File: plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPayments/WooPaymentsServiceTest.php:360-373
Timestamp: 2025-06-25T15:39:25.166Z
Learning: Test mocks should accurately simulate the behavior of the functions they replace, including return value semantics. For WordPress functions like `update_option`, the mock should properly return `false` when the value hasn't changed and `true` when it has, to avoid masking logic errors in tests.

Applied to files:

  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Mocks/MockWPCLI.php
📚 Learning: vladolaru prefers to keep both direct value comparison (===) and serialized string comparison (maybe...
Learnt from: vladolaru
PR: woocommerce/woocommerce#59966
File: plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPayments/WooPaymentsServiceTest.php:5515-5574
Timestamp: 2025-07-24T10:44:40.385Z
Learning: vladolaru prefers to keep both direct value comparison (===) and serialized string comparison (maybe_serialize) in WordPress function mocks like update_option, as they may differ in edge cases and provide more comprehensive test coverage rather than just mirroring core WordPress internal behavior.

Applied to files:

  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Mocks/MockWPCLI.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Core e2e tests 6/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.2] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: Metrics - @woocommerce/plugin-woocommerce [performance]
  • GitHub Check: Core e2e tests 1/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Core e2e tests 2/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.2] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: Core API tests - @woocommerce/plugin-woocommerce [api]
  • GitHub Check: Core e2e tests 4/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: PHP: 8.4 WP: latest [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: Core e2e tests 5/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Core e2e tests 3/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: PHP: 8.4 WP: latest [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: Lint - @woocommerce/plugin-woocommerce
  • GitHub Check: build
🔇 Additional comments (4)
plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Mocks/MockWPCLI.php (4)

44-49: LGTM! Consistent with existing pattern.

The new $last_success_message property follows the established naming convention and documentation style of other message capture properties in the class.


51-56: LGTM! Useful enhancement for comprehensive testing.

The $all_log_messages array property enables testing scenarios where multiple log messages need to be verified in sequence, complementing the existing "last message" capture pattern.


82-83: LGTM! Backward compatible enhancement.

The modified log() method maintains existing functionality while adding message accumulation, ensuring existing tests continue to work while enabling comprehensive message sequence testing.


95-102: LGTM! Completes the CLI message capture functionality.

The new success() method follows the established pattern of other message capture methods in the class, properly simulating WP_CLI::success() behavior for testing purposes.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch migrator/shopify-product-fetch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClient.php (1)

218-221: Remove unnecessary phpcs:disable comment

The $variables parameter is used in the compact() call on line 221, so the phpcs:disable comment for unused parameters is not needed.

-	 *
-	 * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
	 */
	private function build_graphql_request_args( string $access_token, string $query, array $variables ): array {
plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcherTest.php (1)

473-530: Consider making the helper method protected instead of private.

While using reflection to test private methods breaks encapsulation, the current approach is acceptable given the importance of testing the GraphQL variable building logic. However, consider making build_graphql_variables protected instead of private to improve testability without reflection.

The test coverage itself is excellent, validating null filtering and different parameter combinations.

📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6aca03f and c611d91.

📒 Files selected for processing (8)
  • plugins/woocommerce/changelog/59941-migrator-shopify-product-fetch (1 hunks)
  • plugins/woocommerce/src/Internal/CLI/Migrator/Commands/ProductsCommand.php (3 hunks)
  • plugins/woocommerce/src/Internal/CLI/Migrator/Interfaces/PlatformFetcherInterface.php (1 hunks)
  • plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClient.php (3 hunks)
  • plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcher.php (2 hunks)
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Commands/ProductsCommandTest.php (2 hunks)
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClientTest.php (2 hunks)
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcherTest.php (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/code-quality.mdc)

**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable

Files:

  • plugins/woocommerce/src/Internal/CLI/Migrator/Commands/ProductsCommand.php
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClientTest.php
  • plugins/woocommerce/src/Internal/CLI/Migrator/Interfaces/PlatformFetcherInterface.php
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcherTest.php
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Commands/ProductsCommandTest.php
  • plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcher.php
  • plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClient.php
**/*.{php,js,ts,jsx,tsx}

⚙️ CodeRabbit Configuration File

**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:

  • Guards against unexpected inputs.
  • Sanitizes and validates any potentially dangerous inputs.
  • Is backwards compatible.
  • Is readable and intuitive.
  • Has unit or E2E tests where applicable.

Files:

  • plugins/woocommerce/src/Internal/CLI/Migrator/Commands/ProductsCommand.php
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClientTest.php
  • plugins/woocommerce/src/Internal/CLI/Migrator/Interfaces/PlatformFetcherInterface.php
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcherTest.php
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Commands/ProductsCommandTest.php
  • plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcher.php
  • plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClient.php
🧠 Learnings (3)
📚 Learning: test mocks should accurately simulate the behavior of the functions they replace, including return v...
Learnt from: vladolaru
PR: woocommerce/woocommerce#59160
File: plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPayments/WooPaymentsServiceTest.php:360-373
Timestamp: 2025-06-25T15:39:25.166Z
Learning: Test mocks should accurately simulate the behavior of the functions they replace, including return value semantics. For WordPress functions like `update_option`, the mock should properly return `false` when the value hasn't changed and `true` when it has, to avoid masking logic errors in tests.

Applied to files:

  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcherTest.php
  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Commands/ProductsCommandTest.php
📚 Learning: in woocommerce core repository, changelog entries for all prs live in `plugins/woocommerce/changelog...
Learnt from: jorgeatorres
PR: woocommerce/woocommerce#59675
File: .github/workflows/release-bump-as-requirement.yml:48-65
Timestamp: 2025-07-15T15:39:21.856Z
Learning: In WooCommerce core repository, changelog entries for all PRs live in `plugins/woocommerce/changelog/` directory and are processed during releases, not at the repository root level.

Applied to files:

  • plugins/woocommerce/changelog/59941-migrator-shopify-product-fetch
📚 Learning: run woocommerce phpunit tests for specific files or directories using the command: pnpm run test:php...
Learnt from: CR
PR: woocommerce/woocommerce#0
File: .cursor/rules/woo-phpunit.mdc:0-0
Timestamp: 2025-07-18T14:55:07.629Z
Learning: Run WooCommerce PHPUnit tests for specific files or directories using the command: pnpm run test:php:env {relative_path} --verbose, and the command must be run in the plugins/woocommerce directory.

Applied to files:

  • plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Commands/ProductsCommandTest.php
🪛 PHPMD (2.15.0)
plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClientTest.php

369-369: Avoid unused parameters such as '$preempt'. (Unused Code Rules)

(UnusedFormalParameter)


415-415: Avoid unused parameters such as '$preempt'. (Unused Code Rules)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Core e2e tests 6/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Metrics - @woocommerce/plugin-woocommerce [performance]
  • GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.2] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: Core API tests - @woocommerce/plugin-woocommerce [api]
  • GitHub Check: Core e2e tests 4/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Core e2e tests 5/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Core e2e tests 2/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Core e2e tests 1/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Core e2e tests 3/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: PHP: 8.4 WP: latest [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: PHP: 8.4 WP: latest [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.2] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: Lint - @woocommerce/plugin-woocommerce
  • GitHub Check: build
🔇 Additional comments (36)
plugins/woocommerce/src/Internal/CLI/Migrator/Interfaces/PlatformFetcherInterface.php (1)

22-22: LGTM!

The PHPDoc update from 'hasNextPage' to 'has_next_page' correctly aligns the interface documentation with the snake_case naming convention used in the implementation.

plugins/woocommerce/changelog/59941-migrator-shopify-product-fetch (1)

1-4: Changelog entry looks good!

The changelog correctly documents this feature addition with appropriate significance level and clear description of the GraphQL product fetching functionality.

plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClient.php (4)

65-84: GraphQL request method implementation looks solid!

The method properly handles credentials validation, delegates to appropriate helper methods, and maintains consistency with the existing REST request pattern.


198-208: URL construction is consistent and correct!

The GraphQL URL builder follows the same pattern as the REST URL builder, ensuring protocol presence and using the consistent API version.


234-273: Excellent error handling in GraphQL response processing!

The method comprehensively handles all error scenarios including HTTP errors, JSON parsing failures, GraphQL-specific errors, and validates the response structure.


2-8: File header addition follows standards!

The PHPDoc file header properly documents the file with the correct package namespace.

plugins/woocommerce/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcher.php (3)

25-144: Well-designed comprehensive GraphQL query!

The query efficiently fetches all necessary product data including variants, images, collections, and metadata in a single request. Good use of fragments and query variables for flexibility.


168-220: Robust implementation of batch fetching!

The method properly handles GraphQL requests, error scenarios, cursor extraction for pagination, and returns the expected array structure. Good defensive programming with validation of response structure.


222-243: Clean GraphQL variable builder!

Good implementation with sensible defaults and proper null value filtering to prevent GraphQL issues.

plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Commands/ProductsCommandTest.php (2)

94-205: Comprehensive test coverage for count requests!

The tests properly cover success, filtered, and error scenarios for the count functionality. Good use of mocks and appropriate assertions.


207-460: Excellent test coverage for fetch functionality!

The tests comprehensively cover all fetch scenarios including success cases, pagination, errors, and empty results. The mock data correctly represents the GraphQL edge/node structure, and assertions properly validate the expected output.

plugins/woocommerce/src/Internal/CLI/Migrator/Commands/ProductsCommand.php (4)

54-72: LGTM!

The CLI documentation is clear and comprehensive, covering all new options and providing helpful usage examples.


103-107: LGTM!

The fetch request handling follows the same pattern as the existing count handler, maintaining consistency in the codebase.


54-72: LGTM! Clear CLI parameter documentation.

The new CLI parameters are well-documented with appropriate defaults and clear examples showing both basic and advanced usage patterns.


103-107: LGTM! Consistent request handling pattern.

The fetch request detection follows the same clean pattern as the existing count request handling.

plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyClientTest.php (9)

327-565: Excellent comprehensive test coverage for GraphQL functionality.

The new test methods provide thorough coverage of the graphql_request method, including:

  • Successful GraphQL queries with proper request validation
  • Variable handling and request body construction
  • Error scenarios (HTTP errors, GraphQL errors, invalid JSON)
  • Credential validation
  • Proper mocking and cleanup

The tests follow established patterns and testing best practices.


369-369: Static analysis false positive - parameters required by WordPress filter signature.

The $preempt parameter is required by WordPress's pre_http_request filter signature, even when unused. Removing it would break the callback contract.

Also applies to: 415-415


2-45: LGTM! Proper test class structure and setup.

The test class follows standard PHPUnit conventions with proper dependency mocking and initialization.


327-394: LGTM! Comprehensive GraphQL success test.

This test thoroughly validates the GraphQL request flow including URL construction, headers, request method, and response parsing with appropriate assertions.


396-437: LGTM! Proper GraphQL variables test.

The test correctly validates that GraphQL variables are included in the request body. The static analysis warning about unused $preempt parameter is a false positive - it's required by the WordPress filter callback signature.


439-472: LGTM! Proper HTTP error handling test.

The test correctly validates error handling for HTTP-level failures with appropriate error code and message assertions.


474-516: LGTM! Comprehensive GraphQL error handling test.

The test properly validates GraphQL-specific error handling with realistic error structures and appropriate assertions.


518-550: LGTM! Proper invalid JSON handling test.

The test correctly validates error handling for malformed JSON responses with appropriate error code assertions.


552-565: LGTM! Proper credentials validation test.

The test correctly validates error handling when credentials are missing with appropriate error assertions.

plugins/woocommerce/tests/php/src/Internal/CLI/Migrator/Platforms/Shopify/ShopifyFetcherTest.php (12)

230-233: LGTM!

The key naming update to snake_case (has_next_page) aligns with PHP/WordPress conventions and maintains consistency with the interface changes.


284-468: Comprehensive test coverage for GraphQL batch fetching.

The new test methods provide excellent coverage of the fetch_batch functionality:

  • Success scenarios with proper data validation
  • Cursor-based pagination handling
  • Error handling and resilience to malformed responses
  • Edge cases like empty results

The tests properly mock the GraphQL client and validate both happy path and error scenarios.


532-612: LGTM!

The edge case tests provide valuable coverage of boundary conditions:

  • Large batch sizes for bulk operations
  • Minimum batch size validation
  • Proper response handling in both scenarios

These tests ensure the fetcher behaves correctly across the expected range of input values.


223-234: LGTM! Consistent key naming convention.

The update to use has_next_page (snake_case) is consistent with PHP naming conventions and aligns with the interface changes.


284-356: LGTM! Comprehensive batch fetch success test.

This test thoroughly validates the GraphQL product fetching functionality with realistic mock data structures, proper request parameter validation, and comprehensive response parsing assertions.


358-403: LGTM! Proper cursor pagination test.

The test correctly validates cursor-based pagination with appropriate parameter passing to GraphQL and proper end-of-results handling.


405-421: LGTM! Proper GraphQL error handling test.

The test correctly validates error handling in batch fetching with appropriate fallback to empty results and proper error response structure.


423-442: LGTM! Proper invalid response structure test.

The test correctly validates defensive programming against unexpected API response structures with appropriate fallback behavior.


444-468: LGTM! Proper empty results test.

The test correctly validates handling of empty result sets with appropriate pagination state and response structure.


470-530: LGTM! Comprehensive GraphQL variables helper test.

The test appropriately uses reflection to validate the internal variable building logic with comprehensive scenarios including null filtering and parameter mapping.


532-572: LGTM! Proper large limit test.

The test correctly validates handling of large batch sizes with appropriate mock data generation and proper request parameter validation.


574-612: LGTM! Proper minimum limit edge case test.

The test correctly validates the edge case of minimum batch size (limit=1) with appropriate single-item result handling and assertions.

Comment on lines +151 to +202
private function handle_fetch_request( string $platform, array $assoc_args ): void {
$limit = (int) ( $assoc_args['limit'] ?? 5 );
$cursor = $assoc_args['after'] ?? null;

WP_CLI::log( "Fetching {$limit} products from {$platform}..." );

$fetcher = $this->platform_registry->get_fetcher( $platform );
if ( ! $fetcher ) {
WP_CLI::error( "Could not get fetcher for platform '{$platform}'" );
return;
}

// Build fetch arguments.
$fetch_args = array(
'limit' => $limit,
'after_cursor' => $cursor,
);

$result = $fetcher->fetch_batch( $fetch_args );

if ( empty( $result['items'] ) ) {
WP_CLI::log( 'No products found or unable to fetch products.' );
return;
}

WP_CLI::success( sprintf( 'Successfully fetched %d products.', count( $result['items'] ) ) );

// Display basic product information.
foreach ( $result['items'] as $item ) {
$product = $item->node ?? null;
if ( ! $product ) {
continue;
}

$title = $product->title ?? 'Unknown Title';
$id = $product->id ?? 'Unknown ID';
$status = $product->status ?? 'Unknown Status';
$variants_count = isset( $product->variants->edges ) ? count( $product->variants->edges ) : 0;

WP_CLI::log( "- {$title} (ID: {$id}, Status: {$status}, Variants: {$variants_count})" );
}

// Display pagination information.
if ( $result['has_next_page'] && $result['cursor'] ) {
WP_CLI::log( '' );
WP_CLI::log( 'More products available. To fetch next batch, use:' );
WP_CLI::log( "wp wc migrate products --fetch --limit={$limit} --after={$result['cursor']}" );
} else {
WP_CLI::log( '' );
WP_CLI::log( 'No more products to fetch.' );
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation and sanitization for security.

Several security and validation concerns need to be addressed:

  1. Validate limit parameter bounds - The (int) cast doesn't prevent negative or excessively large values
  2. Sanitize cursor parameter - User input is used directly without validation
  3. Add null safety for object property access - Lines 180-188 access object properties without null checks
  4. Sanitize output - The cursor value is output directly on line 197

Apply these security improvements:

 private function handle_fetch_request( string $platform, array $assoc_args ): void {
-	$limit  = (int) ( $assoc_args['limit'] ?? 5 );
-	$cursor = $assoc_args['after'] ?? null;
+	$limit  = max( 1, min( 250, (int) ( $assoc_args['limit'] ?? 5 ) ) );
+	$cursor = isset( $assoc_args['after'] ) ? sanitize_text_field( $assoc_args['after'] ) : null;

	WP_CLI::log( "Fetching {$limit} products from {$platform}..." );

	$fetcher = $this->platform_registry->get_fetcher( $platform );
	if ( ! $fetcher ) {
		WP_CLI::error( "Could not get fetcher for platform '{$platform}'" );
		return;
	}

	// Build fetch arguments.
	$fetch_args = array(
		'limit'        => $limit,
		'after_cursor' => $cursor,
	);

	$result = $fetcher->fetch_batch( $fetch_args );

	if ( empty( $result['items'] ) ) {
		WP_CLI::log( 'No products found or unable to fetch products.' );
		return;
	}

	WP_CLI::success( sprintf( 'Successfully fetched %d products.', count( $result['items'] ) ) );

	// Display basic product information.
	foreach ( $result['items'] as $item ) {
-		$product = $item->node ?? null;
+		$product = isset( $item->node ) ? $item->node : null;
		if ( ! $product ) {
			continue;
		}

-		$title          = $product->title ?? 'Unknown Title';
-		$id             = $product->id ?? 'Unknown ID';
-		$status         = $product->status ?? 'Unknown Status';
-		$variants_count = isset( $product->variants->edges ) ? count( $product->variants->edges ) : 0;
+		$title          = isset( $product->title ) ? sanitize_text_field( $product->title ) : 'Unknown Title';
+		$id             = isset( $product->id ) ? sanitize_text_field( $product->id ) : 'Unknown ID';
+		$status         = isset( $product->status ) ? sanitize_text_field( $product->status ) : 'Unknown Status';
+		$variants_count = isset( $product->variants, $product->variants->edges ) && is_array( $product->variants->edges ) ? count( $product->variants->edges ) : 0;

		WP_CLI::log( "- {$title} (ID: {$id}, Status: {$status}, Variants: {$variants_count})" );
	}

	// Display pagination information.
	if ( $result['has_next_page'] && $result['cursor'] ) {
		WP_CLI::log( '' );
		WP_CLI::log( 'More products available. To fetch next batch, use:' );
-		WP_CLI::log( "wp wc migrate products --fetch --limit={$limit} --after={$result['cursor']}" );
+		WP_CLI::log( "wp wc migrate products --fetch --limit={$limit} --after=" . sanitize_text_field( $result['cursor'] ) );
	} else {
		WP_CLI::log( '' );
		WP_CLI::log( 'No more products to fetch.' );
	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private function handle_fetch_request( string $platform, array $assoc_args ): void {
$limit = (int) ( $assoc_args['limit'] ?? 5 );
$cursor = $assoc_args['after'] ?? null;
WP_CLI::log( "Fetching {$limit} products from {$platform}..." );
$fetcher = $this->platform_registry->get_fetcher( $platform );
if ( ! $fetcher ) {
WP_CLI::error( "Could not get fetcher for platform '{$platform}'" );
return;
}
// Build fetch arguments.
$fetch_args = array(
'limit' => $limit,
'after_cursor' => $cursor,
);
$result = $fetcher->fetch_batch( $fetch_args );
if ( empty( $result['items'] ) ) {
WP_CLI::log( 'No products found or unable to fetch products.' );
return;
}
WP_CLI::success( sprintf( 'Successfully fetched %d products.', count( $result['items'] ) ) );
// Display basic product information.
foreach ( $result['items'] as $item ) {
$product = $item->node ?? null;
if ( ! $product ) {
continue;
}
$title = $product->title ?? 'Unknown Title';
$id = $product->id ?? 'Unknown ID';
$status = $product->status ?? 'Unknown Status';
$variants_count = isset( $product->variants->edges ) ? count( $product->variants->edges ) : 0;
WP_CLI::log( "- {$title} (ID: {$id}, Status: {$status}, Variants: {$variants_count})" );
}
// Display pagination information.
if ( $result['has_next_page'] && $result['cursor'] ) {
WP_CLI::log( '' );
WP_CLI::log( 'More products available. To fetch next batch, use:' );
WP_CLI::log( "wp wc migrate products --fetch --limit={$limit} --after={$result['cursor']}" );
} else {
WP_CLI::log( '' );
WP_CLI::log( 'No more products to fetch.' );
}
}
private function handle_fetch_request( string $platform, array $assoc_args ): void {
// Validate and bound the limit (1–250) and sanitize the cursor.
$limit = max( 1, min( 250, (int) ( $assoc_args['limit'] ?? 5 ) ) );
$cursor = isset( $assoc_args['after'] )
? sanitize_text_field( $assoc_args['after'] )
: null;
WP_CLI::log( "Fetching {$limit} products from {$platform}..." );
$fetcher = $this->platform_registry->get_fetcher( $platform );
if ( ! $fetcher ) {
WP_CLI::error( "Could not get fetcher for platform '{$platform}'" );
return;
}
// Build fetch arguments.
$fetch_args = array(
'limit' => $limit,
'after_cursor' => $cursor,
);
$result = $fetcher->fetch_batch( $fetch_args );
if ( empty( $result['items'] ) ) {
WP_CLI::log( 'No products found or unable to fetch products.' );
return;
}
WP_CLI::success( sprintf( 'Successfully fetched %d products.', count( $result['items'] ) ) );
// Display basic product information.
foreach ( $result['items'] as $item ) {
$product = isset( $item->node ) ? $item->node : null;
if ( ! $product ) {
continue;
}
$title = isset( $product->title )
? sanitize_text_field( $product->title )
: 'Unknown Title';
$id = isset( $product->id )
? sanitize_text_field( $product->id )
: 'Unknown ID';
$status = isset( $product->status )
? sanitize_text_field( $product->status )
: 'Unknown Status';
$variants_count = isset( $product->variants, $product->variants->edges )
&& is_array( $product->variants->edges )
? count( $product->variants->edges )
: 0;
WP_CLI::log( "- {$title} (ID: {$id}, Status: {$status}, Variants: {$variants_count})" );
}
// Display pagination information.
if ( $result['has_next_page'] && $result['cursor'] ) {
WP_CLI::log( '' );
WP_CLI::log( 'More products available. To fetch next batch, use:' );
WP_CLI::log(
"wp wc migrate products --fetch --limit={$limit} --after="
. sanitize_text_field( $result['cursor'] )
);
} else {
WP_CLI::log( '' );
WP_CLI::log( 'No more products to fetch.' );
}
}
🤖 Prompt for AI Agents
In plugins/woocommerce/src/Internal/CLI/Migrator/Commands/ProductsCommand.php
between lines 151 and 202, add validation to ensure the 'limit' parameter is
within a reasonable positive range, for example, clamp it between 1 and a
maximum allowed value. Sanitize the 'after' cursor parameter to ensure it
contains only expected characters or format before use. Add null checks before
accessing properties on $product and its nested objects to avoid errors if any
are missing. Sanitize the cursor value before outputting it in the WP_CLI::log
call to prevent injection or formatting issues.

⚠️ Potential issue

Add input validation and error handling to guard against unexpected inputs.

The method needs better input validation and error handling as required by the coding guidelines:

  1. Validate limit bounds - Line 152 should validate the limit is within reasonable bounds
  2. Sanitize cursor input - Line 153 should validate/sanitize the cursor parameter
  3. Add error handling around fetch_batch - Lines 169 should be wrapped in try-catch
  4. Validate response structure - Lines 180-188 and 194 access object properties without validation

Apply this diff to improve input validation and error handling:

 private function handle_fetch_request( string $platform, array $assoc_args ): void {
-	$limit  = (int) ( $assoc_args['limit'] ?? 5 );
-	$cursor = $assoc_args['after'] ?? null;
+	$limit  = max( 1, min( 250, (int) ( $assoc_args['limit'] ?? 5 ) ) );
+	$cursor = isset( $assoc_args['after'] ) ? sanitize_text_field( $assoc_args['after'] ) : null;

 	WP_CLI::log( "Fetching {$limit} products from {$platform}..." );

 	$fetcher = $this->platform_registry->get_fetcher( $platform );
 	if ( ! $fetcher ) {
 		WP_CLI::error( "Could not get fetcher for platform '{$platform}'" );
 		return;
 	}

+	if ( ! method_exists( $fetcher, 'fetch_batch' ) ) {
+		WP_CLI::error( "Fetcher for platform '{$platform}' does not support batch fetching" );
+		return;
+	}

 	// Build fetch arguments.
 	$fetch_args = array(
 		'limit'        => $limit,
 		'after_cursor' => $cursor,
 	);

-	$result = $fetcher->fetch_batch( $fetch_args );
+	try {
+		$result = $fetcher->fetch_batch( $fetch_args );
+	} catch ( Exception $e ) {
+		WP_CLI::error( "Failed to fetch products: " . $e->getMessage() );
+		return;
+	}

-	if ( empty( $result['items'] ) ) {
+	if ( ! is_array( $result ) || empty( $result['items'] ) ) {
 		WP_CLI::log( 'No products found or unable to fetch products.' );
 		return;
 	}

 	WP_CLI::success( sprintf( 'Successfully fetched %d products.', count( $result['items'] ) ) );

 	// Display basic product information.
 	foreach ( $result['items'] as $item ) {
-		$product = $item->node ?? null;
+		$product = isset( $item->node ) && is_object( $item->node ) ? $item->node : null;
 		if ( ! $product ) {
 			continue;
 		}

-		$title          = $product->title ?? 'Unknown Title';
-		$id             = $product->id ?? 'Unknown ID';
-		$status         = $product->status ?? 'Unknown Status';
-		$variants_count = isset( $product->variants->edges ) ? count( $product->variants->edges ) : 0;
+		$title          = isset( $product->title ) ? sanitize_text_field( $product->title ) : 'Unknown Title';
+		$id             = isset( $product->id ) ? sanitize_text_field( $product->id ) : 'Unknown ID';
+		$status         = isset( $product->status ) ? sanitize_text_field( $product->status ) : 'Unknown Status';
+		$variants_count = ( isset( $product->variants->edges ) && is_array( $product->variants->edges ) ) ? count( $product->variants->edges ) : 0;

 		WP_CLI::log( "- {$title} (ID: {$id}, Status: {$status}, Variants: {$variants_count})" );
 	}

 	// Display pagination information.
-	if ( $result['has_next_page'] && $result['cursor'] ) {
+	if ( ! empty( $result['has_next_page'] ) && ! empty( $result['cursor'] ) ) {
 		WP_CLI::log( '' );
 		WP_CLI::log( 'More products available. To fetch next batch, use:' );
-		WP_CLI::log( "wp wc migrate products --fetch --limit={$limit} --after={$result['cursor']}" );
+		WP_CLI::log( "wp wc migrate products --fetch --limit={$limit} --after=" . sanitize_text_field( $result['cursor'] ) );
 	} else {
 		WP_CLI::log( '' );
 		WP_CLI::log( 'No more products to fetch.' );
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private function handle_fetch_request( string $platform, array $assoc_args ): void {
$limit = (int) ( $assoc_args['limit'] ?? 5 );
$cursor = $assoc_args['after'] ?? null;
WP_CLI::log( "Fetching {$limit} products from {$platform}..." );
$fetcher = $this->platform_registry->get_fetcher( $platform );
if ( ! $fetcher ) {
WP_CLI::error( "Could not get fetcher for platform '{$platform}'" );
return;
}
// Build fetch arguments.
$fetch_args = array(
'limit' => $limit,
'after_cursor' => $cursor,
);
$result = $fetcher->fetch_batch( $fetch_args );
if ( empty( $result['items'] ) ) {
WP_CLI::log( 'No products found or unable to fetch products.' );
return;
}
WP_CLI::success( sprintf( 'Successfully fetched %d products.', count( $result['items'] ) ) );
// Display basic product information.
foreach ( $result['items'] as $item ) {
$product = $item->node ?? null;
if ( ! $product ) {
continue;
}
$title = $product->title ?? 'Unknown Title';
$id = $product->id ?? 'Unknown ID';
$status = $product->status ?? 'Unknown Status';
$variants_count = isset( $product->variants->edges ) ? count( $product->variants->edges ) : 0;
WP_CLI::log( "- {$title} (ID: {$id}, Status: {$status}, Variants: {$variants_count})" );
}
// Display pagination information.
if ( $result['has_next_page'] && $result['cursor'] ) {
WP_CLI::log( '' );
WP_CLI::log( 'More products available. To fetch next batch, use:' );
WP_CLI::log( "wp wc migrate products --fetch --limit={$limit} --after={$result['cursor']}" );
} else {
WP_CLI::log( '' );
WP_CLI::log( 'No more products to fetch.' );
}
}
private function handle_fetch_request( string $platform, array $assoc_args ): void {
// Enforce reasonable bounds on limit and sanitize cursor input
$limit = max( 1, min( 250, (int) ( $assoc_args['limit'] ?? 5 ) ) );
$cursor = isset( $assoc_args['after'] ) ? sanitize_text_field( $assoc_args['after'] ) : null;
WP_CLI::log( "Fetching {$limit} products from {$platform}..." );
$fetcher = $this->platform_registry->get_fetcher( $platform );
if ( ! $fetcher ) {
WP_CLI::error( "Could not get fetcher for platform '{$platform}'" );
return;
}
// Ensure the fetcher actually implements batch fetching
if ( ! method_exists( $fetcher, 'fetch_batch' ) ) {
WP_CLI::error( "Fetcher for platform '{$platform}' does not support batch fetching" );
return;
}
// Build fetch arguments.
$fetch_args = array(
'limit' => $limit,
'after_cursor' => $cursor,
);
// Guard against exceptions from the remote platform
try {
$result = $fetcher->fetch_batch( $fetch_args );
} catch ( Exception $e ) {
WP_CLI::error( "Failed to fetch products: " . $e->getMessage() );
return;
}
// Validate that we got a well-formed array before proceeding
if ( ! is_array( $result ) || empty( $result['items'] ) ) {
WP_CLI::log( 'No products found or unable to fetch products.' );
return;
}
WP_CLI::success( sprintf( 'Successfully fetched %d products.', count( $result['items'] ) ) );
// Display basic product information.
foreach ( $result['items'] as $item ) {
$product = isset( $item->node ) && is_object( $item->node ) ? $item->node : null;
if ( ! $product ) {
continue;
}
$title = isset( $product->title ) ? sanitize_text_field( $product->title ) : 'Unknown Title';
$id = isset( $product->id ) ? sanitize_text_field( $product->id ) : 'Unknown ID';
$status = isset( $product->status ) ? sanitize_text_field( $product->status ) : 'Unknown Status';
$variants_count = ( isset( $product->variants->edges ) && is_array( $product->variants->edges ) )
? count( $product->variants->edges )
: 0;
WP_CLI::log( "- {$title} (ID: {$id}, Status: {$status}, Variants: {$variants_count})" );
}
// Display pagination information.
if ( ! empty( $result['has_next_page'] ) && ! empty( $result['cursor'] ) ) {
WP_CLI::log( '' );
WP_CLI::log( 'More products available. To fetch next batch, use:' );
WP_CLI::log(
"wp wc migrate products --fetch --limit={$limit}"
. " --after=" . sanitize_text_field( $result['cursor'] )
);
} else {
WP_CLI::log( '' );
WP_CLI::log( 'No more products to fetch.' );
}
}
🤖 Prompt for AI Agents
In plugins/woocommerce/src/Internal/CLI/Migrator/Commands/ProductsCommand.php
between lines 151 and 202, improve input validation and error handling by first
validating that the 'limit' parameter is an integer within a reasonable range
(e.g., positive and below a max threshold) and sanitizing the 'after' cursor
parameter to ensure it is a valid string or null. Then, wrap the call to
fetch_batch in a try-catch block to handle potential exceptions gracefully.
Finally, before accessing properties on the fetched result and product objects,
add checks to confirm the expected structure and presence of those properties to
avoid errors from undefined or null values.

@naman03malhotra naman03malhotra merged commit 2085079 into trunk Aug 4, 2025
26 checks passed
@naman03malhotra naman03malhotra deleted the migrator/shopify-product-fetch branch August 4, 2025 09:59
@github-actions github-actions bot added this to the 10.2.0 milestone Aug 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin: woocommerce Issues related to the WooCommerce Core plugin.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Migrator]: Implement Product Data Fetching [Migrator]: Implement Shopify GraphQL Client
2 participants