it-bens/shopware-store-api-client

dev-main 2024-07-03 21:14 UTC

This package is auto-updated.

Last update: 2024-10-03 21:53:05 UTC


README

Shopware provides a Store API to access all the content that is normally provided via storefront in a headless way. The documentation can be found here: https://shopware.stoplight.io/docs/store-api/branches/v6.5/38777d33d92dc-quick-start-guide (this is for Shopware 6.5).

A JS library to use the Store API is provided directly by Shopware: https://shopware.stoplight.io/docs/store-api/branches/v6.5/90222a7851d2a-javascript-sd-ks

Currently, there is no PHP library provided by Shopware. Other libraries are partly or fully generated by the Store API documentation that can be found here: https://github.com/shopware/store-api-reference. However, the documentations contains a lot of errors: wrong response codes, wrong data structure documentation and a lack of nullability declarations. Although the obvious use code for a Store API client is to use the Store API with it, it also provides a type safe (but not necessarily complete and correct) documentation for data structures and response codes.

Installation

The client(s) can be installed via composer.

composer require it-bens/shopware-store-api-client

Usage

This package provides multiple clients for different parts of the Store API. They share the same dependencies during initialization, use the same processing flow and throw the same exceptions when something goes wrong. The clients are there functions are shown later.

Initialization

The client initialization will be shown with the CartClient as an example.

use ITB\ShopwareStoreApiClient\RequestBuilder;
use ITB\ShopwareStoreApiClient\Auth\AccessTokenProvider\WithStaticAccessToken as AccessTokenProviderWithStaticAccessToken;
use ITB\ShopwareStoreApiClient\Tests\E2E\Helper\Psr18Client;
use Symfony\Component\Serializer\Serializer;
use ITB\ShopwareStoreApiClient\CartClient;

$shopwareStoreUrl = 'https://example.shopware.com';
$requestBuilder = new RequestBuilder();
$accessTokenProvider = new AccessTokenProviderWithStaticAccessToken('WYFFSAEAFGWEG');
$httpClient = new Psr18Client();
$serializer = new Serializer(...); // The symfony serializer initialization is a lot more complex. It will be shown later.
$responseProcessor = new ResponseProcessor($serializer);

$cartsClient = new CartClient(
    $shopwareStoreUrl,
    $requestBuilder,
    $accessTokenProvider,
    $httpClient,
    $serializer,
    $responseProcessor
);

Access Token Provider

Shopware sales channels require an access token to be present in the request headers. AccessTokenProvider is an interface that aims to provide AccessToken objects at runtime. The WithStaticAccessToken class is the simplest implementation of this interface. The access token is just stored inside the object. Other implementations may fetch the access token from other sources.

The access token for a sales channel doesn't change by itself. So the "static" usage is usually sufficient.

HTTP Client

This package uses the PSR-18 standard for HTTP clients. The symfony version of this client is used in the E2E tests. However, every PSR-18 compatible implementation can be used. Feel free to use the one you like.

Serializer

The symfony serializer is used to deserialize the responses of the Store API into strictly types (as far as possible) data structures. In projects that use the symfony framework bundle, the serializer is already available. It just requires some custom normalizers from this package.

However, the serializer must be initialized by yourself if you don't use the symfony framework.

Click to see the symfony serializer initialization
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use ITB\ShopwareStoreApiClient\ModelNormalizer\CustomerAddressNormalizer;
use ITB\ShopwareStoreApiClient\ModelNormalizer\OrderStateNormalizer;
use ITB\ShopwareStoreApiClient\ModelNormalizer\DeliveryStateNormalizer;
use ITB\ShopwareStoreApiClient\ModelNormalizer\TransactionStateNormalizer;
use ITB\ShopwareStoreApiClient\ModelNormalizer\ContextSourceNormalizer;
use ITB\ShopwareStoreApiClient\ModelNormalizer\OrderCollectionNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Serializer;

$encoders = [new JsonEncoder()];

$phpDocExtractor = new PhpDocExtractor(); // required to detect array types
$reflectionExtractor = new ReflectionExtractor(); // required to detect constructor arguments
$propertyInfoExtractor = new PropertyInfoExtractor(
    listExtractors: [$reflectionExtractor],
    typeExtractors: [$phpDocExtractor, $reflectionExtractor],
    descriptionExtractors: [$phpDocExtractor],
    accessExtractors: [$reflectionExtractor],
    initializableExtractors: [$reflectionExtractor]
);

$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); // required to detect discriminator mappings
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);

// custom normalizers / denormalizers (some of the require the serializer itself because they only prepare the data for the deserialization process)
$customerAddressNormalizer = new CustomerAddressNormalizer();
$orderStateNormalizer = new OrderStateNormalizer();
$deliveryStateNormalizer = new DeliveryStateNormalizer();
$transactionStateNormalizer = new TransactionStateNormalizer();
$contextSourceNormalizer = new ContextSourceNormalizer();
$orderCollectionNormalizer = new OrderCollectionNormalizer();

$objectNormalizer = new ObjectNormalizer(
    classMetadataFactory: $classMetadataFactory,
    propertyAccessor: PropertyAccess::createPropertyAccessor(),
    propertyTypeExtractor: $propertyInfoExtractor,
    classDiscriminatorResolver: $discriminator
);

$normalizers = [
    $customerAddressNormalizer,
    $orderStateNormalizer,
    $deliveryStateNormalizer,
    $transactionStateNormalizer,
    $contextSourceNormalizer,
    $orderCollectionNormalizer,
    new BackedEnumNormalizer(),
    new ArrayDenormalizer(),
    $objectNormalizer, // the object normalizer will try to denormalize everything without any specific logic, so it should be the last one in the list
];

$serializer = new Serializer($normalizers, $encoders);

$customerAddressNormalizer->setDenormalizer($serializer);
$customerAddressNormalizer->setNormalizer($serializer);
$orderStateNormalizer->setDenormalizer($serializer);
$deliveryStateNormalizer->setDenormalizer($serializer);
$transactionStateNormalizer->setDenormalizer($serializer);

API functions

Context Token Provider

The context token identifies a customer(session) in Shopware. It is transmitted as a header like the access token. Not all API functions require a context token. ContextTokenProvider is an interface that aims to provide context tokens at runtime. This package provides two implementations: WithAnonymousUser and WithAuthenticatedUser.

With Anonymous User

The WithAnonymousUser class uses the /store-api/context route to fetch or create a context token without any login. The provider stores the context token until the resetContextToken method is called.

With Authenticated User

The WithAuthenticatedUser class takes customer credentials at creation and uses the /store-api/account/login route to perform the login. The context token is returned by Shopware. The provider stores the context token until the resetContextToken method is called.

Search Criteria

Shopware provides a very powerful search via API (not the product search). The options are documented here: https://shopware.stoplight.io/docs/store-api/branches/v6.5/cf710bf73d0cd-search-queries. This packages depicts the search data structure with the SearchCriteria class.

It also contains two options that are inconsistently documented: fields and includes. While the fields option is documented in the Store API, the includes option is documented in the Admin API. They seem to have the same purpose but the format and the internal processing are working differently. The fields option restricts the fields that Shopware queries from the database (associations are considered). Restricting the queried fields can improve the performance a lot! The option takes a plain array of strings. Points are used as field separators. The includes option restricts the fields that are returned in the response (but they are still queried). The option takes a recursivly associative array. The keys are the API/entity aliases. The value can be a plain array of strings or another associative array. The combined usage of the fields and includes options grants the greatest performance improvements.

Address Client

Cart Client

All line item related functions require a LineItemCollection. It contains LineItem implementations. Currently, three LineItem implementations exist: ProductLineItem, PromotionLineItem and LineItemReference. The first two should be self-explanatory. The last one is used to reference a line item and can be used for the deletion of line items from the cart. A CustomLineItem implementation is planned. Shopware supports discount line items internally but they are not supported in the Store API and are mainly used via Plugins.

The Cart object contains an array of Error objects. The name is misleading because Shopware used them for errors, warning and notices. Only a few types are currently supported by this package. More will be added as soon as they appear in tests or usage in this package. Contributions and issues are welcome.

Context Client

The context contains a lot of information about the current customer(session). Updating the context is the designated way to change the addresses, the payment method and the shipping method that are used during the checkout process.

Order Client

The createOrderFromCart method will throw an exception if Shopware complains about something about the cart data. The thrown RequestExceptionWithHttpStatusCode provides a getErrors method the should provide all the errors that Shopware returned.

Contributing

I am really happy that the software developer community loves Open Source, like I do! ♥

That's why I appreciate every issue that is opened (preferably constructive) and every pull request that provides other or even better code to this package.

You are all breathtaking!