Skip to content

Remove Context Superclass – Improve Modularity and Testability #399

@m1roff

Description

@m1roff

Overview/summary

The Context class in the Shopify PHP API library introduces global state, making the codebase harder to test, maintain, and extend. It violates multiple SOLID principles, specifically Single Responsibility Principle (SRP) and Dependency Inversion Principle (DIP). This issue proposes refactoring to remove Context and replace it with dependency injection.

Motivation

Currently, many classes depend on Context for critical API information like:
• API keys ($API_KEY, $API_SECRET_KEY)
• Session storage ($SESSION_STORAGE)
• HTTP Client Factory ($HTTP_CLIENT_FACTORY)
• API version ($API_VERSION)
• Retry logic ($RETRY_TIME_IN_SECONDS)

This leads to tightly coupled code, where:
1. Global state makes debugging difficult – Modifying Context affects all instances, leading to unpredictable behavior.
2. Unit testing is complicated – Tests must mock the static Context state rather than injecting dependencies.
3. Extensibility is restricted – Developers cannot easily replace components like HTTP clients or storage mechanisms.

Area

The Context dependency is spread across multiple classes. Some key examples:

  1. Graphql Client

Problem: Uses Context::$IS_PRIVATE_APP and Context::$API_VERSION, making it impossible to instantiate Graphql client without relying on Context.

if (!Context::$IS_PRIVATE_APP && empty($token)) {
    throw new MissingArgumentException('Missing access token when creating GraphQL client');
}

Proposed Fix: Inject Configuration object instead of relying on Context:

class Graphql {
    public function __construct(
        private readonly Configuration $config,
        private readonly HttpClient $httpClient,
        private readonly ?string $token = null
    ) { }
}
  1. Http Client

Problem: Uses Context::$HTTP_CLIENT_FACTORY and Context::$USER_AGENT_PREFIX to generate requests.

$client = Context::$HTTP_CLIENT_FACTORY->client();

Proposed Fix: Pass HttpClientFactory and UserAgentProvider via constructor:

class Http {
    public function __construct(
        private readonly HttpClientFactory $clientFactory,
        private readonly UserAgentProvider $userAgentProvider
    ) { }
}

Checklist

  • Refactor Context into a Configuration object that can be injected.
  • Update Graphql, Http, and other dependent classes to use dependency injection.
  • Ensure backward compatibility where possible.
  • Improve testability by allowing mock injection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions