srustamov / gcore-php
PHP client for the Gcore API (CDN, DNS, FastEdge)
Requires
- php: ^8.2
- ext-json: *
- guzzlehttp/guzzle: ^7.8
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
- symfony/yaml: ^7.0
README
PHP client for the Gcore API.
Gcore publishes an official Python SDK but no PHP one. This package covers CDN, DNS and FastEdge. The API surface is generated from Gcore's published OpenAPI specifications; the transport, retry, and hydration layers are hand-written.
Requirements
- PHP 8.2 or newer
- ext-json
Installation
composer require srustamov/gcore-php
Getting started
use Gcore\Client; $gcore = new Client(apiKey: getenv('GCORE_API_KEY')); $zone = $gcore->dns->zones->get('example.com'); echo $zone->name;
The API key falls back to the GCORE_API_KEY environment variable when the
argument is omitted.
Pagination
List endpoints return a lazy Paginator. Iterating it walks every page, so you
never deal with offsets yourself.
foreach ($gcore->dns->zones->list() as $zone) { echo $zone->name, PHP_EOL; }
Pagination is driven through the returned object, not through the call that creates it:
$paginator = $gcore->dns->zones->list(); $firstPage = $paginator->page(limit: 20); // one page $everything = $paginator->all(); // materialize all pages $total = $paginator->count(); // server-reported total
Filters, on the other hand, are arguments. Array filters repeat the query key, which is what the API expects:
$gcore->dns->zones->list(orderBy: 'name', id: [1, 2]); // GET /dns/v2/zones?order_by=name&id=1&id=2
Working with records
$gcore->dns->rrsets->create('example.com', 'www.example.com', 'A', [ 'ttl' => 300, 'resource_records' => [ ['content' => ['1.2.3.4']], ], ]); foreach ($gcore->dns->rrsets->list('example.com') as $rrset) { echo $rrset->name, ' ', $rrset->ttl, PHP_EOL; }
Request bodies are associative arrays. Their shapes are documented as
array{...} PHPDoc on each method, so PhpStorm and PHPStan can check them
without the library shipping a parameter class per endpoint.
Undocumented fields
Models expose typed properties for everything the specification documents, and
raw() for everything else. A field Gcore adds after this release is still
reachable without waiting for a new version.
$zone = $gcore->dns->zones->get('example.com'); $zone->name; // typed $zone->raw()['brand_new_field']; // whatever the API actually returned
toArray() and jsonSerialize() return the same raw payload, so a model
round-trips exactly what the server sent.
Error handling
Every failure is a Gcore\Exception\GcoreException. Non-2xx responses raise a
subclass of ApiException chosen by status code.
use Gcore\Exception\NotFoundException; use Gcore\Exception\RateLimitException; try { $gcore->dns->zones->get('missing.example'); } catch (NotFoundException $e) { echo $e->statusCode; // 404 echo $e->rawBody; // the response body, verbatim } catch (RateLimitException $e) { // already retried per the retry policy before reaching you }
| Status | Exception |
|---|---|
| 400 | BadRequestException |
| 401 | AuthenticationException |
| 403 | PermissionDeniedException |
| 404 | NotFoundException |
| 409 | ConflictException |
| 422 | UnprocessableEntityException |
| 429 | RateLimitException |
| 5xx | ServerException |
| network, timeout | ConnectionException |
The three Gcore products in scope document no shared error body, so the message
is taken from whichever of message, error, detail, or errors is present,
falling back to the HTTP reason phrase. rawBody is always kept.
Configuration
$gcore = new Client( apiKey: '...', baseUrl: 'https://api.gcore.com', timeout: 30.0, maxRetries: 2, headers: ['X-Trace' => 'abc'], );
Requests retry on 408, 409, 429, and 5xx responses, and on connection errors,
with exponential backoff plus jitter capped at 8 seconds. A numeric
Retry-After header wins over the computed delay. Any single call can override
the defaults:
use Gcore\RequestOptions; $gcore->dns->zones->get('example.com', new RequestOptions(timeout: 5.0, maxRetries: 0));
Coverage
| Product | Resources | Models | Accessor |
|---|---|---|---|
| CDN | 18 | 220 | $gcore->cdn |
| DNS | 10 | 50 | $gcore->dns |
| FastEdge | 7 | 30 | $gcore->fastedge |
| Cloud | — | — | Not planned for v1 |
Development
This section is for working in the repository. The generator is not part of the
installed package — composer require ships src/ and nothing else.
src/Cdn, src/Dns and src/Fastedge are generated. Do not edit them by hand —
change the emitters under codegen/ and regenerate:
php codegen/bin/generate.php dns # or cdn, or fastedge
vendor/bin/php-cs-fixer fix
vendor/bin/phpunit
Formatting is a required step, not a tidy-up: the emitters produce output that php-cs-fixer then normalizes, so the zero-diff check below only holds once it has run.
Where the naming rules cannot produce a good name — CDN puts three separate
collections under one Logs uploader tag, for instance — codegen/overrides.php
maps the path to the name it should have.
Regenerating an unchanged specification produces no diff, and CI enforces that, so a hand-edit under a generated directory fails the build rather than being silently erased by the next run.
php codegen/bin/check-drift.php reports when a vendored specification has
fallen behind the published one.
License
MIT