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.
About the author
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.