Skip to content

Commit bb16417

Browse files
Merge pull request #16 from contentstack/feat/dx-7285
feat: Add region-aware endpoint resolution via getContentstackEndpoint()
2 parents 79915a4 + fb46255 commit bb16417

10 files changed

Lines changed: 2013 additions & 4 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ build
22
vendor
33
phpcs.xml
44
.phpunit.result.cache
5-
*/.DS_Store
5+
*/.DS_Store
6+
src/assets/regions.json

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Changelog
2-
## [1.2.1](https://github.com/contentstack/contentstack-utils-php/tree/v1.2.1) (2024-03-02)
3-
- Support for the fragment tag in nested list
2+
## [1.3.0](https://github.com/contentstack/contentstack-utils-php/tree/v1.3.0) (2026-06-03)
3+
- Added `Endpoint::getContentstackEndpoint()` for dynamic region-aware URL resolution
4+
- Added `Utils::getContentstackEndpoint()` proxy for backward-compatible access
5+
- Bundled `regions.json` is now downloaded at `composer install` / `composer update` via `post-install-cmd`; the file is not committed to the repository
6+
- Added runtime fallback in `Endpoint::loadRegions()` — downloads `regions.json` on first use when the file is absent (e.g. when the package is installed as a dependency)
7+
- Added `composer refresh-regions` script to manually pull the latest regions from Contentstack
8+
- Supports 7 regions (AWS NA/EU/AU, Azure NA/EU, GCP NA/EU) and 18 service endpoint keys
9+
410
## [1.2.0](https://github.com/contentstack/contentstack-utils-php/tree/v1.2.0) (2023-06-27)
511
- Support for the br tag and support for nested assets in the the image
612
## [1.1.0](https://github.com/contentstack/contentstack-utils-php/tree/v1.1.0) (2021-07-16)

README.md

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,243 @@ use Contentstack\Utils\Model\Option;
169169
...
170170
$render_html_text = GQL::jsonToHtml($entry->rich_text_content,, new Option());
171171
...
172+
```
173+
174+
---
175+
176+
## Endpoint Resolution
177+
178+
The SDK ships with a built-in endpoint resolver that returns the correct Contentstack API URL for any region and any service — no hardcoded URLs needed.
179+
180+
### How `regions.json` is provisioned
181+
182+
`regions.json` is **not committed** to your project. It is downloaded automatically:
183+
184+
| When | How |
185+
|---|---|
186+
| `composer install` or `composer update` | `post-install-cmd` runs `scripts/download-regions.php` |
187+
| First call to `getContentstackEndpoint()` when file is missing | Runtime fallback downloads and caches the file |
188+
| Manual refresh | `composer refresh-regions` |
189+
190+
```sh
191+
# Refresh when Contentstack adds new regions or services
192+
composer refresh-regions
193+
```
194+
195+
---
196+
197+
### `getContentstackEndpoint()`
198+
199+
Available on both `Endpoint` and `Utils` (identical behaviour):
200+
201+
```php
202+
use Contentstack\Utils\Endpoint;
203+
use Contentstack\Utils\Utils;
204+
205+
Endpoint::getContentstackEndpoint(string $region, string $service, bool $omitHttps): string|array
206+
Utils::getContentstackEndpoint(string $region, string $service, bool $omitHttps): string|array
207+
```
208+
209+
| Parameter | Type | Default | Description |
210+
|---|---|---|---|
211+
| `$region` | `string` | `'us'` | Region ID or any accepted alias (see table below) |
212+
| `$service` | `string` | `''` | Service key. When empty, all endpoints for the region are returned as an array |
213+
| `$omitHttps` | `bool` | `false` | When `true`, strips `https://` from the returned URL(s) |
214+
215+
---
216+
217+
### Supported Regions
218+
219+
| Region | Canonical ID | Accepted Aliases |
220+
|---|---|---|
221+
| AWS North America | `na` | `us`, `aws-na`, `aws_na`, `NA`, `US`, `AWS-NA`, `AWS_NA` |
222+
| AWS Europe | `eu` | `aws-eu`, `aws_eu`, `EU`, `AWS-EU`, `AWS_EU` |
223+
| AWS Australia | `au` | `aws-au`, `aws_au`, `AU`, `AWS-AU`, `AWS_AU` |
224+
| Azure North America | `azure-na` | `azure_na`, `AZURE-NA`, `AZURE_NA` |
225+
| Azure Europe | `azure-eu` | `azure_eu`, `AZURE-EU`, `AZURE_EU` |
226+
| GCP North America | `gcp-na` | `gcp_na`, `GCP-NA`, `GCP_NA` |
227+
| GCP Europe | `gcp-eu` | `gcp_eu`, `GCP-EU`, `GCP_EU` |
228+
229+
Alias matching is **case-insensitive** and accepts both `-` and `_` separators.
230+
231+
---
232+
233+
### Available Service Keys
234+
235+
| Key | Description |
236+
|---|---|
237+
| `contentDelivery` | Content Delivery API (CDN) — for fetching published entries and assets |
238+
| `contentManagement` | Content Management API — for creating and updating content |
239+
| `graphqlDelivery` | GraphQL Delivery API |
240+
| `graphqlPreview` | GraphQL Live Preview |
241+
| `preview` | REST Live Preview |
242+
| `auth` | Authentication API |
243+
| `application` | Contentstack web application URL |
244+
| `images` | Image Delivery |
245+
| `assets` | Asset Delivery |
246+
| `automate` | Workflow Automation API |
247+
| `launch` | Contentstack Launch API |
248+
| `developerHub` | Developer Hub API |
249+
| `brandKit` | Brand Kit API |
250+
| `genAI` | Generative AI / Knowledge Vault |
251+
| `personalizeManagement` | Personalization Management API |
252+
| `personalizeEdge` | Personalization Edge API |
253+
| `composableStudio` | Composable Studio API |
254+
| `assetManagement` | Asset Management API |
255+
256+
---
257+
258+
### Examples
259+
260+
#### Get a single service URL
261+
262+
```php
263+
use Contentstack\Utils\Endpoint;
264+
265+
// AWS North America — Content Delivery
266+
$url = Endpoint::getContentstackEndpoint('na', 'contentDelivery');
267+
// → "https://cdn.contentstack.io"
268+
269+
// AWS Europe — Content Management
270+
$url = Endpoint::getContentstackEndpoint('eu', 'contentManagement');
271+
// → "https://eu-api.contentstack.com"
272+
273+
// Azure North America — GraphQL Delivery
274+
$url = Endpoint::getContentstackEndpoint('azure-na', 'graphqlDelivery');
275+
// → "https://azure-na-graphql.contentstack.com"
276+
277+
// GCP Europe — Auth
278+
$url = Endpoint::getContentstackEndpoint('gcp-eu', 'auth');
279+
// → "https://gcp-eu-auth-api.contentstack.com"
280+
```
281+
282+
#### Use an alias instead of the canonical ID
283+
284+
```php
285+
// All of these return the same NA content delivery URL
286+
Endpoint::getContentstackEndpoint('us', 'contentDelivery'); // → https://cdn.contentstack.io
287+
Endpoint::getContentstackEndpoint('na', 'contentDelivery'); // → https://cdn.contentstack.io
288+
Endpoint::getContentstackEndpoint('aws-na', 'contentDelivery'); // → https://cdn.contentstack.io
289+
Endpoint::getContentstackEndpoint('AWS_NA', 'contentDelivery'); // → https://cdn.contentstack.io
290+
```
291+
292+
#### Get a URL without the `https://` scheme
293+
294+
Pass `true` as the third argument when you need just the hostname (e.g. for `Stack::setHost()`):
295+
296+
```php
297+
$host = Endpoint::getContentstackEndpoint('eu', 'contentDelivery', true);
298+
// → "eu-cdn.contentstack.com"
299+
```
300+
301+
#### Get all endpoints for a region
302+
303+
Omit the `$service` argument to receive the full associative array:
304+
305+
```php
306+
$endpoints = Endpoint::getContentstackEndpoint('au');
307+
// → [
308+
// 'contentDelivery' => 'https://au-cdn.contentstack.com',
309+
// 'contentManagement' => 'https://au-api.contentstack.com',
310+
// 'graphqlDelivery' => 'https://au-graphql.contentstack.com',
311+
// 'auth' => 'https://au-auth-api.contentstack.com',
312+
// ...17 more keys
313+
// ]
314+
315+
// With omitHttps
316+
$hosts = Endpoint::getContentstackEndpoint('au', '', true);
317+
// → [
318+
// 'contentDelivery' => 'au-cdn.contentstack.com',
319+
// 'contentManagement' => 'au-api.contentstack.com',
320+
// ...
321+
// ]
322+
```
323+
324+
#### Via `Utils` (same result, no import change needed)
325+
326+
```php
327+
use Contentstack\Utils\Utils;
328+
329+
$url = Utils::getContentstackEndpoint('gcp-na', 'contentDelivery');
330+
// → "https://gcp-na-cdn.contentstack.com"
331+
```
332+
333+
---
334+
335+
### Integration with the PHP Delivery SDK
336+
337+
Use `getContentstackEndpoint()` to resolve the host dynamically, then pass it to `Stack::setHost()`:
338+
339+
```php
340+
use Contentstack\Contentstack;
341+
use Contentstack\Utils\Endpoint;
342+
343+
$region = 'eu'; // change this one value to switch regions
344+
345+
// Resolve the content delivery host for the chosen region
346+
$host = Endpoint::getContentstackEndpoint($region, 'contentDelivery', true);
347+
// → "eu-cdn.contentstack.com"
348+
349+
// Initialise the delivery SDK
350+
$stack = Contentstack::Stack('<API_KEY>', '<DELIVERY_TOKEN>', '<ENVIRONMENT>');
351+
$stack->setHost($host);
352+
353+
// Fetch entries — all requests now go to the EU CDN
354+
$result = $stack->ContentType('<CONTENT_TYPE_UID>')->Query()->toJSON()->find();
355+
```
356+
357+
#### Switching regions without changing any other code
358+
359+
```php
360+
$regions = ['na', 'eu', 'au', 'azure-na', 'azure-eu', 'gcp-na', 'gcp-eu'];
361+
362+
foreach ($regions as $region) {
363+
$host = Endpoint::getContentstackEndpoint($region, 'contentDelivery', true);
364+
$stack = Contentstack::Stack('<API_KEY>', '<DELIVERY_TOKEN>', '<ENVIRONMENT>');
365+
$stack->setHost($host);
366+
367+
$result = $stack->ContentType('<CONTENT_TYPE_UID>')->Query()->toJSON()->find();
368+
echo "{$region}: " . count($result[0]) . " entries\n";
369+
}
370+
```
371+
372+
---
373+
374+
### Error Handling
375+
376+
```php
377+
use Contentstack\Utils\Endpoint;
378+
379+
// Empty region
380+
try {
381+
Endpoint::getContentstackEndpoint('');
382+
} catch (\InvalidArgumentException $e) {
383+
echo $e->getMessage();
384+
// → "Empty region provided. Please put valid region."
385+
}
386+
387+
// Unknown region
388+
try {
389+
Endpoint::getContentstackEndpoint('unknown-region', 'contentDelivery');
390+
} catch (\InvalidArgumentException $e) {
391+
echo $e->getMessage();
392+
// → "Invalid region: unknown-region"
393+
}
394+
395+
// Unknown service
396+
try {
397+
Endpoint::getContentstackEndpoint('na', 'unknownService');
398+
} catch (\InvalidArgumentException $e) {
399+
echo $e->getMessage();
400+
// → "Service "unknownService" not found for region "na""
401+
}
402+
403+
// regions.json missing and no network access
404+
try {
405+
Endpoint::getContentstackEndpoint('na', 'contentDelivery');
406+
} catch (\RuntimeException $e) {
407+
echo $e->getMessage();
408+
// → "contentstack/utils: regions.json not found and could not be downloaded.
409+
// Run "composer install" or "composer refresh-regions" and ensure network access."
410+
}
172411
```

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,12 @@
4141
}
4242
},
4343
"scripts": {
44+
"post-install-cmd": ["@php scripts/download-regions.php"],
45+
"post-update-cmd": ["@php scripts/download-regions.php"],
4446
"test": "phpunit",
4547
"check-style": "phpcs src tests",
46-
"fix-style": "phpcbf src tests"
48+
"fix-style": "phpcbf src tests",
49+
"refresh-regions": "@php scripts/download-regions.php"
4750
},
4851
"extra": {
4952
"branch-alias": {

0 commit comments

Comments
 (0)