- Introduction
- Package and Installation
- Technical Overview
- Getting Started
- Configuration
- Authentication
- Middlewares
- Querying
- Products and ProductTypes
- Serialization
- Migration Guidelines From SDK v1
- Observability
- Documentation
- License
This repository contains the PHP SDK generated from the Composable Commerce API reference.
Client and Request Builder for making API requests against Commercetools.
composer require commercetools/commercetools-sdk
Package | Version |
---|---|
commercetools SDK |
The SDK consists of the following projects:
lib/commercetools-base/src
: Contains Client which communicate with Composable Commerce to execute requests, it contains also the classes related to the client like tokens, middlewares and handlers, and mappers and exceptions.lib/commercetools-api/src
: Contains all generated models and request builders to communicate with Composable Commerce HTTP API.lib/commercetools-import/src
: Contains all generated models and request builders to communicate with the Import API.lib/commercetools-history/src
: Contains all generated models and request builders to communicate with the Change History API.
In addition, the SDK has the following directories:
examples/
: One Dockerized Symfony app per APM (New Relic, Datadog, Dynatrace, OpenTelemetry) to demo PHP SDK usage.test/integration
: Integration Tests for the SDK. A good way for anyone using the PHP SDK to understand it further.test/unit
: Unit Tests forlib/commercetools-api-tests
: generated unit test for each class for the api folderlib/commercetools-history-tests
: generated unit test for each class for the history folderlib/commercetools-import-tests
: generated unit test for each class for the import folder
The PHP SDK utilizes various standard interfaces and components to ensure consistency and interoperability:
- JSON serializer
- PSR-3 - LoggerInterface
- PSR-4 - Autoloader
- PSR-6 - CachingInterface
- PSR-7 - HTTP Message Interface
- PSR-16 - Common Interface for Caching Libraries
- PSR-18 - HTTP Client
- PHP Date API:
Example code in this guide uses placeholders that should be replaced with the following values.
If you do not have an API Client, follow our Get your API Client guide.
Placeholder | Replace with | From |
---|---|---|
{projectKey} |
project_key | your API Client |
{clientID} |
client_id | your API Client |
{clientSecret} |
secret | your API Client |
{scope} |
scope | your API Client |
{region} |
your Region | Hosts |
The example below shows how to create a client with customized URIs passed in the creation of the Client itself. You will find the same classes in the Import API folder.
namespace Commercetools;
use Commercetools\Api\Client\ClientCredentialsConfig;
use Commercetools\Api\Client\Config;
use Commercetools\Client\ClientCredentials;
use Commercetools\Client\ClientFactory;
require_once __DIR__ . '/vendor/autoload.php';
/** @var string $clientId */
/** @var string $clientSecret */
/** @var string $scope
* Provide the scope when you want to request a specific ones for the client.
* Can be omitted to use all scopes of the oauth client.
* Format: `<the scope name>:<the project key>`.
* Example: `manage_products:project1`. $authConfig
*/
$authConfig = new ClientCredentialsConfig(
new ClientCredentials('{clientID}', '{clientSecret}', '{scope}'),
[],
'https://auth.{region}.commercetools.com/oauth/token'
);
$client = ClientFactory::of()->createGuzzleClient(
new Config([], 'https://api.{region}.commercetools.com'),
$authConfig
);
By default, the library uses api.europe-west1.gcp.commercetools.com
endpoint. If you use a different region, you can configure the client to use a custom endpoint. Here is an example for the us-central1
region:
$authConfig = new ClientCredentialsConfig(
new ClientCredentials('{clientId}', '{clientSecret}'),
[],
'https://auth.us-central1.gcp.commercetools.com/oauth/token'
);
$config = new Config([], 'https://api.us-central1.gcp.commercetools.com');
$client = ClientFactory::of()->createGuzzleClient(
$config,
$authConfig,
);
Note that the auth endpoint should contain the /oauth/token
suffix, but the API endpoint - don't.
Detailed information of all available methods for the product API can be found here
Information for the Import API can be found here.
Examples to retrieve project information
use Commercetools\Api\Client\ApiRequestBuilder;
use GuzzleHttp\ClientInterface;
/** @var ClientInterface $client */
$builder = new ApiRequestBuilder($client);
$request = $builder->withProjectKey('{projectKey}')->get();
To avoid specifying the project key for every request built it's possible to use the ones in the Commercetools\Client
namespace instead
use Commercetools\Client\ApiRequestBuilder;
use Commercetools\Client\ImportRequestBuilder;
use GuzzleHttp\ClientInterface;
/** @var ClientInterface $client */
$builder = new ApiRequestBuilder('{projectKey}', $client);
$request = $builder->categories()->get();
$importBuilder = new ImportRequestBuilder('{projectKey}', $client);
$request = $importBuilder->importSinks()->get();
These are some examples about how to execute a request:
use Commercetools\Client\ApiRequestBuilder;
use GuzzleHttp\ClientInterface;
/** @var ClientInterface $client */
$builder = new ApiRequestBuilder('{projectKey}', $client);
$request = $builder->with()->get();
// executing the request and mapping the response directly to a domain model
$project = $request->execute();
// send the request to get the response object
$response = $request->send();
// map the response to a domain model
$project = $request->mapFromResponse($response);
// send the request asynchronously
$promise = $request->sendAsync();
// map the response to a domain model
$project = $request->mapFromResponse($promise->wait());
// send the request using a client instance
$response = $client->send($request);
$project = $request->mapFromResponse($response);
The PHP SDK utilizes various standard interfaces and components to ensure consistency and interoperability:
$authHandler = HandlerStack::create();
$authHandler->push(
MiddlewareFactory::createLoggerMiddleware(new Logger('auth', [new StreamHandler('./logs/requests.log')]))
);
$authConfig = new ClientCredentialsConfig(new ClientCredentials($clientId, $clientSecret), [
'handler' => $authHandler,
]);
$logger = new Logger('client', [new StreamHandler('./logs/requests.log')]);
$client = ClientFactory::of()->createGuzzleClientForHandler(
new Config(['maxRetries' => 3]),
OAuthHandlerFactory::ofAuthConfig($authConfig),
$logger
);
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
$filesystemCache = new FilesystemAdapter();
$config = new Config(['timeout' => 30]);
$client = ClientFactory->createGuzzleClientForHandler(
$config,
OAuthHandlerFactory::ofAuthConfig($authConfig, $cache)
);
//set up the client something like the examples before
// create a guzzle request
/** @var CategoryBuilder $category */
$request = $client->with()->categories()->withId($category->getId())->get()->withExpand('parent');
$result = $request->execute();
$request = new \GuzzleHttp\Psr7\Request('GET', '{projectKey}/categories/{ID}');
$response = $client->send($request);
use Symfony\Component\Cache\Simple\FilesystemCache;
use Symfony\Component\Cache\Psr16Cache;
$filesystemCache = new FilesystemAdapter();
$cache = new Psr16Cache($filesystemCache);
$config = new Config(['timeout' => 30]);
$client = ClientFactory->createGuzzleClientForHandler(
$config,
OAuthHandlerFactory::ofAuthConfig($authConfig, $cache)
);
The error handle is already provided with the ExceptionFactory class. The methods contained in this class encapsulate the logic for converting Guzzle exceptions into your custom exceptions based on the HTTP response status codes. Which can be called directly in the request or can be called by a dedicated middleware for the error handling. Direct invocation in a request handling or directly handled in a middleware:
if ($e->getCode() >= 500) {
throw ExceptionFactory::createServerException($e, $apiRequest, $response, $result);
} else {
throw ExceptionFactory::createClientException($e, $apiRequest, $response, $result);
}
The factory class ProviderFactory is for managing authentication and token handling.
To generate a TokenStorageProvider that manages tokens using different flows: Refresh Flow and Anonymous Flow, you can use ProviderFactory::createTokenStorageProvider($anonTokenUrl, $refreshTokenUrl, $clientCredentials, $client, $tokenStorage, $anonymousIdProvider);
.
The ProviderFactory::createPasswordFlowProvider($passwordTokenUrl, $clientCredentials, $client, $tokenStorage);
method, creates a PasswordFlowTokenProvider for authenticating users with username and password, acquiring tokens securely.
The createAnonymousFlowProvider($anonTokenUrl, $clientCredentials, $client, $refreshFlowTokenProvider, $anonymousIdProvider);
method builds an AnonymousFlowTokenProvider to manage tokens for anonymous users, integrating with the API's anonymous token endpoint.
The createRefreshFlowProvider($refreshTokenUrl, $clientCredentials, $client, $tokenStorage)
method sets up a RefreshFlowTokenProvider to handle token refresh operations seamlessly, ensuring continuous access to API resources.
We introduced middleware to add functionalities to the requests and the responses in the PHP SDK.
You can add middleware when creating the PHP SDK client. Multiple middlewares can be added using the array of middlewares.
The scope of the MiddlewareFactory which is a Factory pattern is to handle all the available middleware and have the chance to have them customized.
The methods that are contained in this class, are meant to create an array of middlewares.
The method createDefaultMiddlewares creates an array with default values of OAuth Handler, Authentication, Logger, Retry and Correlation ID.
$authConfig = new ClientCredentialsConfig(new ClientCredentials($clientId, $clientSecret), [
'handler' => $authHandler,
]);
$oauthHandler = OAuthHandlerFactory::ofAuthConfig($authConfig),
$logger = new Logger('client', [new StreamHandler('./logs/requests.log')]);
$maxRetries = 3;
$correlationIdProvider = new DefaultCorrelationIdProvider();
$middlewares = MiddlewareFactory::createDefaultMiddlewares(
$oauthHandler,
$logger,
$maxRetries,
$correlationIdProvider
);
The method createCorrelationIdMiddleware creates a middleware that adds a correlation ID to the headers of HTTP requests.
$correlationIdProvider = new DefaultCorrelationIdProvider();
$correlationIdMiddleware = MiddlewareFactory::createCorrelationIdMiddleware(
$correlationIdProvider
);
The method createRetryNAMiddleware is designed to create middleware that retries HTTP requests under certain conditions are met. This middleware is particularly useful in scenarios where transient errors, such as temporary server unavailability, may occur.
$maxRetries = 3;
$retryMiddleware = MiddlewareFactory::createRetryNAMiddleware($maxRetries);
The method createMiddlewareForOAuthHandler creates a middleware for handling OAuth2 authentication ensuring to include the necessary OAuth credentials.
$tokenProvider = new YourTokenProvider();
$oauthHandler = OAuthHandlerFactory::ofProvider($tokenProvider),
$oauthMiddleware = MiddlewareFactory::createMiddlewareForOAuthHandler($oauthHandler);
The method createLoggerMiddleware creates a middleware for logging HTTP requests and responses.
$logger = new Logger('auth');
$logger->pushHandler(new StreamHandler('./logs/requests.log', Logger::DEBUG));
$loggerMiddleware = MiddlewareFactory::createLoggerMiddleware($logger);
The method createReauthenticateMiddleware creates a middleware that automatically reauthenticates HTTP requests when an invalid token error (HTTP 401) is encountered. It uses an OAuth2Handler to refresh the token and retry the request up to a specified number of times.
$authConfig = new ClientCredentialsConfig(new ClientCredentials($clientId, $clientSecret), [
'handler' => $authHandler,
]);
$oauthHandler = OAuthHandlerFactory::ofAuthConfig($authConfig),
//maxRetries have the default value 1 as a second parameter of the function
$reauthMiddleware = MiddlewareFactory::createReauthenticateMiddleware($oauthHandler);
For the examples that we are mentioning below we are setting the $builder
like here:
use Commercetools\Client\ApiRequestBuilder;
use GuzzleHttp\ClientInterface;
/** @var ClientInterface $client */
$builder = new ApiRequestBuilder('{projectKey}', $client);
Since the most of the variables to pass in the with()
method are scalars, this means that we can pass arrays in the related parameter of the method like in the examples below.
The system allows the use of predicates when querying the API. Predicates are added as query parameter string to the request itself. The following example shows the usage of input variables:
$builder
->customers()
->get()
->withWhere('lastName=:lastName')
->withPredicateVar("lastName", $customerSignIn->getCustomer()->getLastName());
It's also possible to use array values in predicates in case of a varying number of parameters.
$builder
->productProjections()
->get()
->withWhere('masterVariant(sku in :skus)')
->withPredicateVar("skus", ["foo", "bar"]);
$builder
->productProjections()
->withId('test_id')
->get();
$builder
->productProjections()
->withKey('test_key')
->get();
See Sort for details.
Sorting using one parameter:
$builder
->products()
->get()
->withSort("masterData.current.name.en asc");
Sorting using multiple parameters:
$builder
->products()
->get()
->withSort(["masterData.current.name.en asc", "id asc"]);
Limiting the number of the returned documents or page size:
$builder
->products()
->get()
->withLimit(4)
->withOffset(4);
A ProductType is like a schema that defines how the product attributes are structured.
ProductType
contains a list of AttributeDefinition which corresponds to the name and type of each attribute, along with some additional information. Each name/type pair must be unique across a Project, so if you create an attribute "foo" of type String, you cannot create another ProductType where "foo" has another type (e.g. LocalizedString). If you do it anyway you get an error message like:
"The attribute with name 'foo' has a different type on product type 'exampleproducttype'."
In this scenario we provide two ProductTypes
book and t-shirt.
The book product type contains the following attributes:
$isbn as String, International Standard Book Number The t-shirt product type contains the following attributes:
$color as AttributeLocalizedEnumValue with the colors green and red and their translations in German and English. $size as AttributePlainEnumValue with S, M and X. $laundrySymbols as set of AttributeLocalizedEnumValue with temperature and tumble drying. $matchingProducts as set of ProductReference, which can point to products that are similar to the current product. $rrp as Money containing the recommended retail price. $availableSince as DateTime which contains the date since when the product is available for the customer in the shop. All available attribute types you can find here: AttributeType in "All Known Implementing Classes".
The code for the creation of the book ProductType:
$isbn = AttributeDefinitionBuilder::of()
->withType(AttributeTextTypeBuilder::of()->build())
->withName(self::ISBN_ATTR_NAME)
->withLabel(LocalizedStringBuilder::of("ISBN")->build())
->withIsRequired(false)
->build();
$productType = ProductTypeBuilder::of()
->withName(self::BOOK_PRODUCT_TYPE_NAME)
->withDescription("books")
->withAttributes(AttributeDefinitionCollection::of()->add($isbn))
->build();
$builder = new ApiRequestBuilder('{projectKey}', $client);
$request = $builder
->productTypes()
->withId($productType->getId())
->get();
$productTypeQueryResponse = $request->execute();
See the Test Code
The code for the creation of the t-shirt ProductType:
$green = AttributeLocalizedEnumValueBuilder::of()
->withKey("green")
->withLabel(LocalizedStringBuilder::fromArray(["en" => "green", "de" => "grĂĽn"])->build())
->build();
$red = AttributeLocalizedEnumValueBuilder::of()
->withKey("red")
->withLabel(LocalizedStringBuilder::fromArray(["en" => "red", "de" => "rot"])->build())
->build();
$color = AttributeDefinitionDraftBuilder::of()
->withName(self::COLOR_ATTR_NAME)
->withLabel(LocalizedStringBuilder::fromArray(["en" => "color"])->build())
->withType(AttributeLocalizedEnumTypeBuilder::of()
->withValues(AttributeLocalizedEnumValueCollection::fromArray([$green, $red]))
->build())
->withIsRequired(true)
->build();
$small = AttributePlainEnumValueBuilder::of()
->withKey("S")
->withLabel("S")
->build();
$medium = AttributePlainEnumValueBuilder::of()
->withKey("M")
->withLabel("M")
->build();
$sizeX = AttributePlainEnumValueBuilder::of()
->withKey("X")
->withLabel("X")
->build();
$size = AttributeDefinitionDraftBuilder::of()
->withName(self::SIZE_ATTR_NAME)
->withLabel(LocalizedStringBuilder::fromArray(["en" => "Size"])->build())
->withType(AttributeEnumTypeBuilder::of()
->withValues(AttributePlainEnumValueCollection::fromArray([$small, $medium, $sizeX]))
->build())
->withIsRequired(true)
->build();
$cold = AttributeLocalizedEnumValueBuilder::of()
->withKey("cold")
->withLabel(LocalizedStringBuilder::fromArray(["en" => "Wash at or below 30°C ", "de" => "30°C"])->build())
->build();
$hot = AttributeLocalizedEnumValueBuilder::of()
->withKey("hot")
->withLabel(LocalizedStringBuilder::fromArray(["en" => "Wash at or below 60°C", "de" => "60°C"])->build())
->build();
$tumbleDrying = AttributeLocalizedEnumValueBuilder::of()
->withKey("tumbleDrying")
->withLabel(LocalizedStringBuilder::fromArray(["en" => "Tumble Drying", "de" => "Trommeltrocknen"])->build())
->build();
$noTumbleDrying = AttributeLocalizedEnumValueBuilder::of()
->withKey("noTumbleDrying")
->withLabel(LocalizedStringBuilder::fromArray(["en" => "no tumble drying", "de" => "Nicht im Trommeltrockner trocknen"])->build())
->build();
$laundryLabelType = AttributeSetTypeBuilder::of()
->withElementType(AttributeLocalizedEnumTypeBuilder::of()
->withValues(AttributeLocalizedEnumValueCollection::fromArray([$cold, $hot, $tumbleDrying, $noTumbleDrying]))
->build())
->build();
$laundrySymbols = AttributeDefinitionDraftBuilder::of()
->withType($laundryLabelType)
->withName(self::LAUNDRY_SYMBOLS_ATTR_NAME)
->withLabel(LocalizedStringBuilder::fromArray(["en" => "washing labels"])->build())
->withIsRequired(false)
->build();
$matchingProducts = AttributeDefinitionDraftBuilder::of()
->withName(self::MATCHING_PRODUCTS_ATTR_NAME)
->withLabel(LocalizedStringBuilder::fromArray(["en" => "matching products"])->build())
->withType(AttributeSetTypeBuilder::of()
->withElementType(AttributeReferenceTypeBuilder::of()
->withReferenceTypeId("product")
->build())
->build())
->withIsRequired(false)
->build();
$rrp = AttributeDefinitionDraftBuilder::of()
->withName(self::RRP_ATTR_NAME)
->withLabel(LocalizedStringBuilder::fromArray(["en" => "recommended retail price"])->build())
->withType(AttributeMoneyTypeBuilder::of()->build())
->withIsRequired(false)
->build();
$availableSince = AttributeDefinitionDraftBuilder::of()
->withName(self::AVAILABLE_SINCE_ATTR_NAME)
->withLabel(LocalizedStringBuilder::fromArray(["en" => "available since"])->build())
->withType(AttributeDateTimeTypeBuilder::of()->build())
->withIsRequired(false)
->build();
$attributes = AttributeDefinitionDraftCollection::fromArray([$color, $size, $laundrySymbols, $matchingProducts, $rrp, $availableSince]);
$productTypeDraft = ProductTypeDraftBuilder::of()
->withKey(ProductTypeFixture::uniqueProductTypeString())
->withName(self::PRODUCT_TYPE_NAME)
->withDescription("a 'T' shaped cloth")
->withAttributes($attributes)
->build();
$productType = $builder
->with()
->productTypes()
->post($productTypeDraft)
->execute();
See the Test Code
ProductTypes have a key (String) which can be used as key to logically identify ProductTypes. The key has an unique constraint.
To create a product you need to reference the product type. Since the ProductType ID of the development system will not be the ID of the production system it is necessary to find the product type by name:
$productType = $builder
->with()
->productTypes()
->get()
->withQueryParam('where', 'name="' . $name . '"')
->execute();
return $productType->getResults()->current() ?: null;
See the Test Code
The simplest way of adding attributes to a ProductVariant is to use php ProductVariantDraftBuilder::of()->withAttributes($attributes)
which enables you to directly put the value of the attribute to the draft. But it cannot check if you put the right objects and types in it.
A book example:
$attributes = AttributeCollection::of()
->add(
AttributeBuilder::of()
->withName(self::ISBN_ATTR_NAME)
->withValue("978-3-86680-192-9")
->build());
$productVariantDraft = ProductVariantDraftBuilder::of()
->withAttributes($attributes)
->build();
$productTypeResourceIdentifier = ProductTypeResourceIdentifierBuilder::of()
->withId($productType->getId())
->build();
$productDraft = ProductDraftBuilder::of()
->withProductType($productTypeResourceIdentifier)
->withName(LocalizedStringBuilder::of()->put("en", "a book")->build())
->withSlug(LocalizedStringBuilder::of()->put("en", ProductTypeFixture::uniqueProductTypeString())->build())
->withMasterVariant($productVariantDraft)
->build();
$product = $builder->products()
->post($productDraft)
->execute();
See the Test Code
A T-shirt example:
$referenceableProduct = ProductFixture::referenceableProduct($builder);
$productType = ProductTypeFixture::fetchProductTypeByName($builder, self::PRODUCT_TYPE_NAME);
if (!$productType) {
$productType = ProductTypeFixture::createProductType($builder, self::PRODUCT_TYPE_NAME);
}
$productReference = ProductReferenceBuilder::of()->withId($referenceableProduct->getId())->build();
$datetime = new \DateTime('2015-02-02');
$datetime = $datetime->format(\DateTime::ATOM);
$attributes = AttributeCollection::of()
->add(AttributeBuilder::of()->withName(self::COLOR_ATTR_NAME)->withValue("green")->build())
->add(AttributeBuilder::of()->withName(self::SIZE_ATTR_NAME)->withValue("S")->build())
->add(AttributeBuilder::of()->withName(self::LAUNDRY_SYMBOLS_ATTR_NAME)->withValue(["cold", "tumbleDrying"])->build())
->add(AttributeBuilder::of()->withName(self::RRP_ATTR_NAME)->withValue(MoneyBuilder::of()->withCentAmount(300)->withCurrencyCode("EUR")->build())->build())
->add(AttributeBuilder::of()->withName(self::AVAILABLE_SINCE_ATTR_NAME)->withValue($datetime)->build())
->add(AttributeBuilder::of()->withName(self::MATCHING_PRODUCTS_ATTR_NAME)->withValue([$productReference])->build());
$productVariantDraft = ProductVariantDraftBuilder::of()
->withAttributes($attributes)
->build();
$productTypeResourceIdentifier = ProductTypeResourceIdentifierBuilder::of()
->withId($productType->getId())
->build();
$productDraft = ProductDraftBuilder::of()
->withProductType($productTypeResourceIdentifier)
->withKey(ProductFixture::uniqueProductString())
->withName(LocalizedStringBuilder::of()->put('en', 'basic shirt')->build())
->withSlug(LocalizedStringBuilder::of()->put('en', ProductFixture::uniqueProductString())->build())
->withMasterVariant($productVariantDraft)
->build();
$product = $builder->products()
->post($productDraft)
->execute();
See the Test Code
A wrong value for a field or an invalid type will cause a BadRequestException with an error code of "InvalidField".
$productType = $builder->productTypes()
->post($productTypeDraft)
->execute();
$productVariantDraft = ProductVariantDraftBuilder::of()
->withAttributes(AttributeCollection::of()
->add(AttributeBuilder::of()
->withName(self::COLOR_ATTR_NAME)
->withValue(1) //1 is of illegal type and of illegal key
->build()))
->build();
$productTypeResourceIdentifier = ProductTypeResourceIdentifierBuilder::of()
->withId($productType->getId())
->build();
$productDraft = ProductDraftBuilder::of()
->withProductType($productTypeResourceIdentifier)
->withName(LocalizedStringBuilder::of()->put("en", "basic shirt")->build())
->withSlug(LocalizedStringBuilder::of()->put("en", ProductTypeFixture::uniqueProductTypeString())->build())
->withMasterVariant($productVariantDraft)
See the Test Code
As alternative, you could declare your attributes at the same place and use these to read and write attribute values:
$green = AttributeLocalizedEnumValueBuilder::of()
->withKey("green")
->withLabel(LocalizedStringBuilder::of()->put("en", "green ")->put("de", "grĂĽn")->build())
->build();
$cold = AttributeLocalizedEnumValueBuilder::of()
->withKey("cold")
->withLabel(LocalizedStringBuilder::of()->put("en", "Wash at or below 30°C ")->put("de", "30°C")->build())
->build();
$tumbleDrying = AttributeLocalizedEnumValueBuilder::of()
->withKey("tumbleDrying")
->withLabel(LocalizedStringBuilder::of()->put("en", "tumble drying")->put("de", "Trommeltrocknen")->build())
->build();
$productReference = ProductReferenceBuilder::of()->withId($referenceableProduct->getId())->build();
$attributes = AttributeCollection::of()
->add(AttributeBuilder::of()->withName(self::COLOR_ATTR_NAME)->withValue("green")->build())
->add(AttributeBuilder::of()->withName(self::SIZE_ATTR_NAME)->withValue("S")->build())
->add(AttributeBuilder::of()->withName(self::LAUNDRY_SYMBOLS_ATTR_NAME)->withValue(["cold", "tumbleDrying"])->build())
->add(AttributeBuilder::of()->withName(self::RRP_ATTR_NAME)->withValue(MoneyBuilder::of()->withCentAmount(300)->withCurrencyCode("EUR")->build())->build())
->add(AttributeBuilder::of()->withName(self::AVAILABLE_SINCE_ATTR_NAME)->withValue($datetime)->build())
->add(AttributeBuilder::of()->withName(self::MATCHING_PRODUCTS_ATTR_NAME)->withValue([$productReference])->build());
$productVariantDraft = ProductVariantDraftBuilder::of()
->withAttributes($attributes)
->build();
$productTypeResourceIdentifier = ProductTypeResourceIdentifierBuilder::of()
->withId($productType->getId())
->build();
$productDraft = ProductDraftBuilder::of()
->withProductType($productTypeResourceIdentifier)
->withKey(ProductFixture::uniqueProductString())
->withName(LocalizedStringBuilder::of()->put('en', 'basic shirt')->build())
->withSlug(LocalizedStringBuilder::of()->put('en', ProductFixture::uniqueProductString())->build())
->withMasterVariant($productVariantDraft)
->build();
$product = $builder->products()
->post($productDraft)
->execute();
$masterVariant = $product->getMasterData()->getStaged()->getMasterVariant();
foreach ($masterVariant->getAttributes() as $attribute) {
if ($attribute->getName() === self::COLOR_ATTR_NAME) {
assertEquals($attribute->getValue()->key, "green");
}
if ($attribute->getName() === self::SIZE_ATTR_NAME) {
assertEquals($attribute->getValue()->key, "S");
}
See the Test Code
The simplest way to get the value of the attribute is to use getValue() methods of Attribute, like php $attribute->getValue()
:
$product = $this->createProduct();
$masterVariant = $product->getMasterData()->getStaged()->getMasterVariant();
foreach ($masterVariant->getAttributes() as $attribute) {
if ($attribute->getName() === self::SIZE_ATTR_NAME) {
assertEquals($attribute->getValue()->key, "S");
}
}
See the Test Code
You might also use the php getValueAs()
method as a conversion for the attribute, like you have a EnumValue but extract it as boolean because these methods cast the values passed:
$product = $builder->products()->post($productDraft)->execute();
$masterVariant = $product->getMasterData()->getStaged()->getMasterVariant();
$result = null;
foreach ($masterVariant->getAttributes() as $attribute) {
if ($attribute->getName() === self::SIZE_ATTR_NAME) {
/** @var AttributeAccessor $attrAccessor */
$attrAccessor = $attribute->with(AttributeAccessor::of());
$result = $attrAccessor->getValueAsBool();
}
}
$this->assertIsBool($result);
See the Test Code
Here below some examples about setting attribute values is like a product creation:
Example for books:
$product = $this->createBookProduct();
$masterVariantId = 1;
$productUpdate = ProductUpdateBuilder::of()
->withVersion($product->getVersion())
->withActions(
ProductUpdateActionCollection::fromArray([
ProductSetAttributeActionBuilder::of()
->withVariantId($masterVariantId)
->withName(self::ISBN_ATTR_NAME)
->withValue("978-3-86680-192-8")
->build()
])
)->build();
$productUpdated = $builder
->products()
->withId($product->getId())
->post($productUpdate)
->execute();
$masterVariant = $productUpdated->getMasterData()->getStaged()->getMasterVariant();
$attribute = ProductTypeFixture::findAttributes($masterVariant->getAttributes(), self::ISBN_ATTR_NAME);
assertEquals($attribute->getValue(), "978-3-86680-192-8");
See the Test Code
Example for T-shirts:
$masterVariantId = 1;
$productUpdatedAction = ProductUpdateBuilder::of()
->withVersion($product->getVersion())
->withActions(
ProductUpdateActionCollection::fromArray([
ProductSetAttributeActionBuilder::of()
->withVariantId($masterVariantId)
->withName(self::COLOR_ATTR_NAME)
->withValue("red")
->build(),
ProductSetAttributeActionBuilder::of()
->withVariantId($masterVariantId)
->withName(self::SIZE_ATTR_NAME)
->withValue("M")
->build(),
ProductSetAttributeActionBuilder::of()
->withVariantId($masterVariantId)
->withName(self::LAUNDRY_SYMBOLS_ATTR_NAME)
->withValue(["cold"])
->build(),
ProductSetAttributeActionBuilder::of()
->withVariantId($masterVariantId)
->withName(self::RRP_ATTR_NAME)
->withValue(MoneyBuilder::of()->withCurrencyCode("EUR")->withCentAmount(2000)->build())
->build(),
])
)->build();
$productUpdated = $builder
->with()
->products()
->withId($product->getId())
->post($productUpdatedAction)
->execute();
$attributesUpdatedProduct = $productUpdated->getMasterData()->getStaged()->getMasterVariant()->getAttributes();
self::assertEquals(ProductTypeFixture::findAttribute($attributesUpdatedProduct, self::SIZE_ATTR_NAME)->getValue()->key, "M");
self::assertEquals(ProductTypeFixture::findAttribute($attributesUpdatedProduct, self::COLOR_ATTR_NAME)->getValue()->key, "red");
self::assertEquals(ProductTypeFixture::findAttribute($attributesUpdatedProduct, self::LAUNDRY_SYMBOLS_ATTR_NAME)->getValue()[0]->key, "cold");
self::assertEquals(ProductTypeFixture::findAttribute($attributesUpdatedProduct, self::RRP_ATTR_NAME)->getValue()->centAmount, 2000);
See the Test Code
Importing attribute values for orders works different from updating products. In orders you provide the full value for enum-like types instead of just the key as done for all other types. This makes it possible to create a new enum value on the fly. The other attributes behave as expected.
Example:
$product = $this->createProduct($builder);
$attributes = AttributeCollection::of()
->add(AttributeBuilder::of()->withName(self::COLOR_ATTR_NAME)->withValue("yellow")->build())
->add(AttributeBuilder::of()->withName(self::RRP_ATTR_NAME)->withValue(MoneyBuilder::of()->withCurrencyCode("EUR")->withCentAmount(30)->build())->build());
$productVariantImportDraft = ProductVariantImportDraftBuilder::of()
->withId(1)
->withAttributes($attributes)
->build();
$lineItemImportDraft = LineItemImportDraftBuilder::of()
->withProductId($product->getId())
->withVariant($productVariantImportDraft)
->withQuantity(1)
->withPrice(ProductFixture::priceDraft())
->withName(LocalizedStringBuilder::of()->put("en", "product name")->build())
->build();
$orderImportDraft = OrderImportDraftBuilder::of()
->withLineItems(LineItemImportDraftCollection::of()->add($lineItemImportDraft))
->withTotalPrice(MoneyBuilder::of()->withCentAmount(20)->withCurrencyCode("EUR")->build())
->withOrderState(OrderState::COMPLETE)
->build();
$order = $builder->orders()
->importOrder()
->post($orderImportDraft)
->execute();
$productVariant = $order->getLineItems()->current()->getVariant();
$colorAttribute = ProductTypeFixture::findAttribute($productVariant->getAttributes(), self::COLOR_ATTR_NAME);
assertEquals("yellow", $colorAttribute->getValue());
$rrpAttribute = ProductTypeFixture::findAttribute($productVariant->getAttributes(), self::RRP_ATTR_NAME);
assertEquals(30, $rrpAttribute->getValue()->centAmount);
See the Test Code
In the PHP SDK some classes implement the JsonSerializable interface, and they have a customized jsonSerialize()
method to convert the instance of the class to a JSON string easily.
This mean that when the method json_encode()
will be called, the object will be correctly converted and formatted to a JSON string.
See the example below:
$messagePayload = new MessageDeliveryPayloadModel(
"{projectKey}",
null, // Replace with an actual Reference object if needed
null, // Replace with an actual UserProvidedIdentifiers object if needed
"uniqueId456", // ID
1, // The version
new DateTimeImmutable("2024-08-06T12:34:56+00:00"), // CreatedAt
new DateTimeImmutable("2024-08-06T12:34:56+00:00"), // LastModifiedAt
42, // SequenceNumber
1, // Resource version
null, // Replace with an actual PayloadNotIncluded object if needed
"Message" // notification type
);
$messagePayloadJSON = json_encode($messagePayload);
To migrate from the 1.x to the 2.x, there is a guideline below:
To monitor and observe the SDK, see the official documentation Observability, there is a Demo application which shows how to monitor the PHP SDK with New Relic, Datadog, Dynatrace and Open Telemetry.
To monitor and observe the SDK, refer to the official documentation on Observability.
The Demo application demonstrates how to monitor the PHP SDK with the following APMs:
- New Relic
- Datadog
- Dynatrace
- OpenTelemetry (configured to work with New Relic for distributed tracing) Each APM integration is implemented through configuration and can be easily enabled using the provided instructions in the demo app for each platform.
MIT