background

November 8, 2023

Shopware Store API explained

Yireo Blog Post

Shopware offers a Store API to allow for headless frontends to be built, amongst other things. The Store API is based upon Routes that receive request parameters and output JSON. If you follow the docs, you can make them work. But how are they actually working internally?

The controllers of the Store API

The first steps into understanding the Store API are surprisingly simple: If you know Symfony routing (as is being used with Storefront controllers and Admin API controllers), you know how the Store API controllers are working as well.

Symfony offers an architecture that allows you to declare controllers (aka routes) via service declaration, manually via routes.xml, via annotations (@Route) and via attributes (#[Route]). There is actually no magic separating the Store API from the other controllers, except for the fact that there is a scope identifier store-api to make sure certain tricks only apply to the Store API.

Return JSON

Each Store API controller (or as Shopware prefers to call them: Routes) returns a response (instance of \Symfony\Component\HttpFoundation\Response) as usual. However, each Store API Route is supposed to return an instance of \Shopware\Core\System\SalesChannel\StoreApiResponse (which extends \Symfony\Component\HttpFoundation\Response). The reason for this is quite simple.

As soon as a controller returns a response, this response flows back to the routing mechanism of Symfony. And in the end the HTTP kernel (which contains the routing flow) will trigger an event kernel.response right before returning data back to the client.

At that moment, an event listener \Shopware\Core\System\SalesChannel\Api\StoreApiResponseListener picks up on the response, checks whether it is an instance of StoreApiResponse and if so, turns it into JSON. This explains the output.

An example: ProductListRoute

As an example, let's focus upon the route store-api.product.search, served by the callback \Shopware\Core\Content\Product\SalesChannel\ProductListRoute::load(). It's signature mention two method arguments - $criteria and $context - and a return value of type ProductListResponse (which actually extends upon StoreApiResponse). When a GET or POST request is sent to /store-api/product, it is picked up by this class+method.

Within the method, the product repository is called upon with a search() and the result is transformed into \Shopware\Core\Content\Product\SalesChannel\ProductListResponse.

For further details, see https://shopware.stoplight.io/docs/store-api/c9b31e0cc1e70-fetch-a-list-of-products

Inputting arrays

What makes the ProductListRoute more interesting (apart from its output) is its input. Or more accurately, with the right input you can tune the JSON output. There are all kinds of request variables available: sort, filter, post-filter,associations, aggregations, grouping, fields, total-count-mode. Where does this come from?

Interestingly, all these input variables are hidden within the $criteria. Normally, the $criteria object allows you to specificy things in an object-oriented way. However, in the case of the method argument $criteria, the $criteria is actually constructed from array, originating from the incoming request.

Parsing input parameters

The magic here is that the method argument $criteria (or for that matter, any method argument in controllers) is picked upon by a service that is tagged controller.argument_value_resolver. For each method argument that you want to inject in your route method, a service tagged controller.argument_value_resolver must exist. Otherwise setter injection fails.

Specifically, the $criteria argument is picked up by \Shopware\Core\Framework\Routing\Annotation\CriteriaValueResolver which creates a new Criteria instance by using a \Shopware\Core\Framework\DataAbstractionLayer\Search\RequestCriteriaBuilder::parse(). Exactly there, in the RequestCriteriaBuilder class, the input array is transformed into an actual $criteria object. I have used examination of the code myself to determine what is supported and what is not.

Symfony to the max

I hope you found this useful. The Store API is something that you might have been using all along, but diving into it a bit deeper shows the internal workings of the Store API. I personally was also pleased by the fact that the Store API does not form a huge layer of logic of Shopware, but actually just uses straight-forward Symfony logic. And because of this, I found this quite easy to understand.

Posted on November 8, 2023

About the author

Author Jisse Reitsma

Jisse Reitsma is the founder of Yireo, extension developer, developer trainer and 3x Magento Master. His passion is for technology and open source. And he loves talking as well.

Sponsor Yireo

Looking for a training in-house?

Let's get to it!

We schrijven niet te commerciële dingen, we richten ons op de technologie (waar we dol op zijn) en we komen regelmatig met innovatieve oplossingen. Via onze nieuwsbrief kun je op de hoogte blijven van al deze coolness. Inschrijven kost maar een paar seconden.

Do not miss out on what we say

This will be the most interesting spam you have ever read

We schrijven niet te commerciële dingen, we richten ons op de technologie (waar we dol op zijn) en we komen regelmatig met innovatieve oplossingen. Via onze nieuwsbrief kun je op de hoogte blijven van al deze coolness. Inschrijven kost maar een paar seconden.