Introduction
Hello tech geeks, today we will learn how to add caching in your custom store-api routes in shopware. For that we will start by looking at how to add a custom store-api route then we will see how to add a caching layer to it.
Why need caching in API?
Caching in APIs improves response times and reduces server load by storing and reusing frequently requested data, enhancing overall system efficiency and user experience.
Prerequisites
Before you start caching, it’s essential to have:
- A running instance of Shopware 6
- Basic knowledge of RESTful API and HTTP methods
- Basics of Symfony framework like dependency injection, annotations, decorate services, etc
- Basic knowledge of services and controller registration in shopware.
Step 1: How to add store-api route?
Begin by adding an abstract class that your route will extend.
<plugin Root>/src/Core/Content/Example/SalesChannel/AbstractExampleRoute.php
<?php declare(strict_types=1);
namespace Webkul\BasicExample\Core\Content\Example\SalesChannel;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
abstract class AbstractExampleRoute
{
abstract public function getDecorated(): AbstractExampleRoute;
abstract public function load(Criteria $criteria, SalesChannelContext $context): ExampleRouteResponse;
}
Add route class by extending this abstract class and write methods as shown below,
<plugin Root>/src/Core/Content/Example/SalesChannel/ExampleRoute.php
<?php declare(strict_types=1);
namespace Webkul\BasicExample\Core\Content\Example\SalesChannel;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
use Shopware\Core\Framework\Routing\Annotation\Entity;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(defaults={"_routeScope"={"store-api"}})
*/
class ExampleRoute extends AbstractExampleRoute
{
protected EntityRepository $exampleRepository;
public function __construct(EntityRepository $exampleRepository)
{
$this->exampleRepository = $exampleRepository;
}
public function getDecorated(): AbstractExampleRoute
{
throw new DecorationPatternException(self::class);
}
/**
* @Entity("blog_example")
* @Route("/store-api/example", name="store-api.example.search", methods={"GET", "POST"})
*/
public function load(Criteria $criteria, SalesChannelContext $context): ExampleRouteResponse
{
$results = $this->exampleRepository->search($criteria, $context->getContext());
return new ExampleRouteResponse($results );
}
}
Don’t forget to register your route in your services.xml file.
<services>
<service id="Webkul\BasicExample\Core\Content\Example\SalesChannel\ExampleRoute" >
<argument type="service" id="blog_example.repository"/>
</service>
</services>
Now we have successfully set up our store-api route let’s see how we can add caching to it. For that, we will use the decorated class and implement our caching to it.
Step 2: How to add a caching layer in store-API route?
In Shopware, the caching is pre-configured for us; we simply need to utilize the appropriate classes.
We will start by adding a new class name cachedExampleRoute. This class will decorate our ExampleRoute class we just created above.
<?php declare(strict_types=1);
namespace Webkul\BasicExample\Core\Content\Example\SalesChannel;
use Psr\Log\LoggerInterface;
use Shopware\Core\Framework\Adapter\Cache\CacheStateSubscriber;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\Framework\Adapter\Cache\AbstractCacheTracer;
use Shopware\Core\Framework\Adapter\Cache\CacheCompressor;
use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\FieldSerializer\JsonFieldSerializer;
use Shopware\Core\Framework\Routing\Annotation\Entity;
use Symfony\Component\Routing\Annotation\Route;
use Webkul\Core\Content\Blog\SalesChannel\BlogRouteResponse;
/**
* @Route(defaults={"_routeScope"={"store-api"}})
*/
class CachedExampleRoute extends AbstractExampleRoute
{
private AbstractExampleRoute $decorated;
private TagAwareAdapterInterface $cache;
private EntityCacheKeyGenerator $generator;
private AbstractCacheTracer $tracer;
private array $states;
private LoggerInterface $logger;
public function __construct(
AbstractExampleRoute $decorated,
TagAwareAdapterInterface $cache,
EntityCacheKeyGenerator $generator,
AbstractCacheTracer $tracer,
LoggerInterface $logger
) {
$this->decorated = $decorated;
$this->cache = $cache;
$this->generator = $generator;
$this->tracer = $tracer;
// declares that this route can not be cached if the customer is logged in
$this->states = [CacheStateSubscriber::STATE_LOGGED_IN];
$this->logger = $logger;
}
public function getDecorated(): AbstractExampleRoute
{
return $this->decorated;
}
/**
* @Entity("blog_example")
* @Route("/store-api/example", name="store-api.example.search", methods={"GET", "POST"})
*/
public function load(Criteria $criteria, SalesChannelContext $context): ExampleRouteResponse
{
// The context is provided with a state where the route cannot be cached
if ($context->hasState(...$this->states)) {
return $this->getDecorated()->load($criteria, $context);
}
// Fetch item from the cache pool
$item = $this->cache->getItem(
$this->generateKey($context, $criteria)
);
try {
if ($item->isHit() && $item->get()) {
// Use cache compressor to uncompress the cache value
return CacheCompressor::uncompress($item);
}
} catch (\Throwable $e) {
// Something went wrong when uncompress the cache item - we log the error and continue to overwrite the invalid cache item
$this->logger->error($e->getMessage());
}
$name = self::buildName();
// start tracing of nested cache tags and system config keys
$response = $this->tracer->trace($name, function () use ($criteria, $context) {
return $this->getDecorated()->load($criteria, $context);
});
// compress cache content to reduce cache size
$item = CacheCompressor::compress($item, $response);
$item->tag(array_merge(
// get traced tags and configs
$this->tracer->get(self::buildName()),
[self::buildName()]
));
$this->cache->save($item);
return $response;
}
public static function buildName(): string
{
return 'example-route';
}
private function generateKey(SalesChannelContext $context, Criteria $criteria): string
{
$parts = [
self::buildName(),
// generate a hash for the route criteria
$this->generator->getCriteriaHash($criteria),
// generate a hash for the current context
$this->generator->getSalesChannelContextHash($context),
];
return md5(JsonFieldSerializer::encodeJson($parts));
}
}
Inject the class in services.xml file
<service id="<pluginRoot>\Core\Content\Example\SalesChannel\CachedExampleRoute" decorates="Webkul\BasicExample\Core\Content\Example\SalesChannel\ExampleRoute"> <argument type="service" id=" <pluginRoot>\Core\Content\Example\SalesChannel\CachedExampleRoute.inner"/> <argument type="service" id="cache.object"/> <argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator"/> <argument type="service" id="Shopware\Core\Framework\Adapter\Cache\CacheTracer"/> <argument type="service" id="logger" /> </service>
As we can see below the response before the caching took more than 2 seconds,

And after caching the results are

Conclusion
The caching in our API always results in better performance if implemented correctly. The cache can also result in outdated data and security risks if not set up properly. For a detailed guide on how to add cache in store API check out shopware official documentation.
If you need custom Shopware Development Services then feel free to reach us and also explore our exclusive range of Shopware Plugins.
Have fun and keep coding!

Be the first to comment.