Recommended practices for configuring, running, and maintaining the PHP SDK.
Keep the SDK up to date
Always use a recent version of the SDK to ensure you have the latest features, performance improvements, and security fixes.
Store credentials securely
clientId and clientSecret in your source code. Always load them from environment variables or a secrets management service at runtime.$authConfig = new ClientCredentialsConfig(
new ClientCredentials(
getenv('CTP_CLIENT_ID'),
getenv('CTP_CLIENT_SECRET'),
getenv('CTP_SCOPES')
),
[],
getenv('CTP_AUTH_URL') . '/oauth/token'
);
Reuse the client
Building a client sets up an HTTP client, OAuth configuration, and any middleware you add. Create it once and reuse the same instance across requests instead of building a new client every time.
class ClientService
{
private ?ApiRequestBuilder $apiClient = null;
public function getApiClient(): ResourceByProjectKey
{
if (!$this->apiClient) {
$authConfig = new ClientCredentialsConfig(
new ClientCredentials(
getenv('CTP_CLIENT_ID'),
getenv('CTP_CLIENT_SECRET'),
getenv('CTP_SCOPES')
),
[],
getenv('CTP_AUTH_URL') . '/oauth/token'
);
$client = ClientFactory::of()->createGuzzleClient(
new Config([], getenv('CTP_API_URL')),
$authConfig
);
$this->apiClient = new ApiRequestBuilder($client);
}
return $this->apiClient->withProjectKey(getenv('CTP_PROJECT_KEY'));
}
}
If you're using a framework such as Laravel, register the class as a singleton so the container reuses one instance within the current application lifecycle:
$this->app->singleton(ClientService::class, function ($app) {
return new ClientService();
});
If your application runs as a long-lived worker process, such as Swoole or RoadRunner, instead of a traditional per-request PHP process, a cached client can reuse connections across requests and encounter the same kind of stale-connection errors as long-running services in other languages. Guzzle's cURL handler generally recovers from a reset connection automatically. If you see intermittent connection errors, recreate the client periodically or turn off connection reuse for the affected requests.
Configure default middleware
ClientCredentialsConfig, and logging middleware when you pass a PSR-3 logger as the third argument to createGuzzleClient. See the middleware reference for details.$client = ClientFactory::of()->createGuzzleClient(
new Config([], getenv('CTP_API_URL')),
$authConfig,
$logger
);
Logging
use Illuminate\Support\Facades\Log;
$client = ClientFactory::of()->createGuzzleClient(
new Config([], getenv('CTP_API_URL')),
$authConfig,
Log::getLogger()
);
Handle errors safely
4xx) and ApiServerException for server errors (HTTP status codes 5xx). Both extend Guzzle's ClientException and ServerException, so getResponse()->getStatusCode() returns the HTTP status, and getResult() returns the deserialized error body.Catch these exceptions and map them to your own response. Avoid returning the caught exception object directly, since it can expose internal details such as the original request to API consumers.
use Commercetools\Exception\ApiClientException;
use Commercetools\Exception\ApiServerException;
try {
$product = $apiRoot->products()->withId($id)->get()->execute();
} catch (ApiClientException | ApiServerException $e) {
return response()->json([
'message' => 'The commercetools API request failed.',
], $e->getResponse()->getStatusCode());
}
NotFoundException or ConcurrentModificationException.Retrieve the raw API response
execute() when you want the SDK to deserialize the response into a model. Use send() and getBody() when you need the raw JSON, for example to log the full payload or pass it through to another system unchanged.$response = $apiRoot
->products()
->get()
->send();
$rawJson = (string) $response->getBody();
Further resources
- Middleware reference: configure retry, logging, correlation ID, and authentication middleware
- Integration tests: working examples for common SDK operations