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
84 changes: 84 additions & 0 deletions BREAKING_CHANGES_FOR_V6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Breaking change notice for version 6.0.0

## Removal of `ApiVersion::LATEST` constant

The `ApiVersion::LATEST` constant has been removed to prevent semantic versioning (semver) breaking changes. Previously, this constant would automatically update every quarter when new API versions were released, causing unintended breaking changes for apps.

### Migration Guide

**If you were using the constant directly:**

```php
// Before (v5 and earlier)
$apiVersion = ApiVersion::LATEST;

// After (v6+)
$apiVersion = '2025-07'; // Explicitly specify the version you want to use
```

**In your Context::initialize():**

The `apiVersion` parameter is now **required** in `Context::initialize()`. You must explicitly specify which API version you want to use:

```php
// Before (v5 and earlier)
Context::initialize(
apiKey: $_ENV['SHOPIFY_API_KEY'],
apiSecretKey: $_ENV['SHOPIFY_API_SECRET'],
scopes: $_ENV['SHOPIFY_APP_SCOPES'],
hostName: $_ENV['SHOPIFY_APP_HOST_NAME'],
sessionStorage: new FileSessionStorage('/tmp/php_sessions'),
// apiVersion was optional with default ApiVersion::LATEST
);

// After (v6+)
Context::initialize(
apiKey: $_ENV['SHOPIFY_API_KEY'],
apiSecretKey: $_ENV['SHOPIFY_API_SECRET'],
scopes: $_ENV['SHOPIFY_APP_SCOPES'],
hostName: $_ENV['SHOPIFY_APP_HOST_NAME'],
sessionStorage: new FileSessionStorage('/tmp/php_sessions'),
apiVersion: '2025-07', // Now required - explicitly specify the version
);
```

**Finding the right API version:**

You can reference the available API versions in the `ApiVersion` class:

```php
use Shopify\ApiVersion;

// Available constants (as of this release):
ApiVersion::UNSTABLE // "unstable"
ApiVersion::JULY_2025 // "2025-07"
ApiVersion::APRIL_2025 // "2025-04"
ApiVersion::JANUARY_2025 // "2025-01"
ApiVersion::OCTOBER_2024 // "2024-10"
// ... and older versions
```

Or you can use string literals directly:

```php
Context::initialize(
// ... other parameters
apiVersion: '2025-07',
);
```

**Why this change?**

By requiring explicit version specification, apps can:
- Control when they upgrade to new API versions
- Test thoroughly before upgrading
- Avoid unexpected breaking changes from automatic version updates
- Follow semantic versioning principles more accurately

**Recommended approach:**

1. Review the [Shopify API Changelog](https://shopify.dev/changelog) to understand changes between versions
2. Choose the API version that best suits your app's needs
3. Explicitly specify that version in your `Context::initialize()` call
4. Test your app thoroughly with the chosen version
5. Upgrade to newer versions on your own schedule after proper testing
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased
- ⚠️ [Breaking] Remove `ApiVersion::LATEST` constant to prevent semver violations. The `apiVersion` parameter is now required in `Context::initialize()`. Developers must explicitly specify API versions. See the [migration guide](BREAKING_CHANGES_FOR_V6.md#removal-of-apiversionlatest-constant) for details.
- [#425](https://github.com/Shopify/shopify-api-php/pull/425) [Patch] Add compliance webhook topics
- [#433](https://github.com/Shopify/shopify-api-php/pull/433) [Minor] Add support for 2025-10 API version

Expand Down
2 changes: 1 addition & 1 deletion docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The first thing your app will need to do to use this library is to set up your c
| `scopes` | `string \| array` | Yes | - | App scopes |
| `hostName` | `string` | Yes | - | App host name e.g. `my-app.my-domain.ca`. You may optionally include `https://` or `http://` to determine which scheme to use |
| `sessionStorage` | `SessionStorage` | Yes | - | Session storage strategy. Read our [notes on session handling](issues.md#notes-on-session-handling) for more information |
| `apiVersion` | `string` | No | `ApiVersion::LATEST` | App API version, defaults to `ApiVersion::LATEST` |
| `apiVersion` | `string` | Yes | - | App API version. You must explicitly specify which API version to use (e.g., `'2025-07'`, `'2024-10'`, etc.) |
| `isEmbeddedApp` | `bool` | No | `true` | Whether the app is an embedded app |
| `isPrivateApp` | `bool` | No | `false` | Whether the app is a private app |
| `userAgentPrefix` | `string` | No | - | Prefix for user agent header sent with a request |
Expand Down
4 changes: 0 additions & 4 deletions src/ApiVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,4 @@ class ApiVersion
* @var string
*/
public const OCTOBER_2025 = "2025-10";
/**
* @var string
*/
public const LATEST = self::OCTOBER_2025;
}
5 changes: 3 additions & 2 deletions src/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Context
* @param string|array $scopes App scopes
* @param string $hostName App host name e.g. www.google.ca. May include scheme
* @param SessionStorage $sessionStorage Session storage strategy
* @param string $apiVersion App API key, defaults to unstable
* @param string $apiVersion App API version
* @param bool $isEmbeddedApp Whether the app is an embedded app, defaults to true
* @param bool $isPrivateApp Whether the app is a private app, defaults to false
* @param string|null $privateAppStorefrontAccessToken The Storefront API Access Token for a private app
Expand All @@ -77,7 +77,7 @@ public static function initialize(
$scopes,
string $hostName,
SessionStorage $sessionStorage,
string $apiVersion = ApiVersion::LATEST,
string $apiVersion,
bool $isEmbeddedApp = true,
bool $isPrivateApp = false,
?string $privateAppStorefrontAccessToken = null,
Expand All @@ -93,6 +93,7 @@ public static function initialize(
'apiSecretKey' => $apiSecretKey,
'scopes' => implode((array)$scopes),
'hostName' => $hostName,
'apiVersion' => $apiVersion,
];
$missing = array();
foreach ($requiredValues as $key => $value) {
Expand Down
7 changes: 7 additions & 0 deletions tests/BaseTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Http\Client\ClientInterface;
use Shopify\ApiVersion;
use Shopify\Clients\HttpClientFactory;
use Shopify\Context;
use Shopify\Exception\HttpRequestException;
Expand All @@ -18,6 +19,11 @@

class BaseTestCase extends TestCase
{
/**
* API version to use for tests. Uses the latest available version.
*/
protected const TEST_API_VERSION = ApiVersion::OCTOBER_2025;

/** @var string */
protected $domain = 'test-shop.myshopify.io';
/** @var string */
Expand All @@ -32,6 +38,7 @@ public function setUp(): void
scopes: ['sleepy', 'kitty'],
hostName: 'www.my-friends-cats.com',
sessionStorage: new MockSessionStorage(),
apiVersion: self::TEST_API_VERSION,
);
Context::$RETRY_TIME_IN_SECONDS = 0;
$this->version = require dirname(__FILE__) . '/../src/version.php';
Expand Down
12 changes: 9 additions & 3 deletions tests/ContextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public function testCanCreateContext()
scopes: ['sleepy', 'kitty'],
hostName: 'my-friends-cats',
sessionStorage: new MockSessionStorage(),
apiVersion: self::TEST_API_VERSION,
);

$this->assertEquals('ash', Context::$API_KEY);
Expand All @@ -52,6 +53,7 @@ public function testCanUpdateContext()
scopes: ['silly', 'doggo'],
hostName: 'yay-for-doggos',
sessionStorage: new MockSessionStorage(),
apiVersion: self::TEST_API_VERSION,
);

$this->assertEquals('tuck', Context::$API_KEY);
Expand All @@ -64,14 +66,16 @@ public function testThrowsIfMissingArguments()
{
$this->expectException(MissingArgumentException::class);
$this->expectExceptionMessage(
'Cannot initialize Shopify API Library. Missing values for: apiKey, apiSecretKey, scopes, hostName'
'Cannot initialize Shopify API Library. ' .
'Missing values for: apiKey, apiSecretKey, scopes, hostName, apiVersion'
);
Context::initialize(
apiKey: '',
apiSecretKey: '',
scopes: [],
hostName: '',
sessionStorage: new MockSessionStorage(),
apiVersion: '',
);
}

Expand All @@ -96,7 +100,7 @@ public function testThrowsIfPrivateApp()
scopes: ['sleepy', 'kitty'],
hostName: 'my-friends-cats',
sessionStorage: new MockSessionStorage(),
apiVersion: 'unstable',
apiVersion: self::TEST_API_VERSION,
isPrivateApp: true,
);
$this->expectException(PrivateAppException::class);
Expand Down Expand Up @@ -223,6 +227,7 @@ public function testCanSetHostScheme($host, $expectedScheme, $expectedHost)
scopes: ['sleepy', 'kitty'],
hostName: $host,
sessionStorage: new MockSessionStorage(),
apiVersion: self::TEST_API_VERSION,
);

$this->assertEquals($expectedHost, Context::$HOST_NAME);
Expand All @@ -249,6 +254,7 @@ public function testFailsOnInvalidHost()
scopes: ['sleepy', 'kitty'],
hostName: 'not-a-host-!@#$%^&*()',
sessionStorage: new MockSessionStorage(),
apiVersion: self::TEST_API_VERSION,
);
}

Expand All @@ -262,7 +268,7 @@ public function testCanSetCustomShopDomains()
scopes: ['sleepy', 'kitty'],
hostName: 'my-friends-cats',
sessionStorage: new MockSessionStorage(),
apiVersion: ApiVersion::LATEST,
apiVersion: self::TEST_API_VERSION,
isPrivateApp: false,
customShopDomains: $domains,
);
Expand Down