backend Dec 20

Using custom Channel Context with Sylius Shop API based on URL

2 min read –

At the moment we are working on a very complex eCommerce headless platform. On the back-end, we are using Sylius with SyliusShopApi plugin and considering how interesting the project is, I can already see that we will be writing much more about this one in the coming months.

Sylius channel request query-based context, only for ‘shop-api’ routes with fallback to default channel context.

This project is using all the possibilities that channels usually allow in Sylius, especially for generating front-end pages based on the channel. In SyliusShopApi documentation, you can access the cookbook on how to implement your custom channel context based on a parameter in the route.

If you just follow this example, this will be actually your only Channel Context and all pages in the Sylius will be using this Channel Context. If you want to enable fallback to default Channel Context, in this example, just replace ‘Sylius\ShopApiPlugin\Exception\ChannelNotFoundException’ with ‘Sylius\Component\Channel\Context\ChannelNotFoundException’.

What we wanted to achieve is to have a Sylius Channel Context that will be active only for some routes – in our case for all routes that are containing ‘shop-api’ in the path – for our API routes. Our flow is:

  • check if the route is part of shop-api
  • if the route is part of shop-api and if channelCode is missing fallback to default ChannelContext
  • if the route is part of shop-api and channelCode exist, check if there is Channel with channelCode; if yes return channel, if not throw an exception (no fallback)
  • ift he the route is not part of shop-api, fallback to default ChannelContext

Here is the code that does the magic and configuration. If you need to change the path pattern, just edit isPathValid method.

<?php


namespace App\Context;

use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Channel\Context\ChannelNotFoundException;
use Sylius\Component\Channel\Model\ChannelInterface;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Webmozart\Assert\Assert;

class RequestQueryChannelContext implements ChannelContextInterface
{
    private $channelRepository;
    private $requestStack;

    public function __construct(ChannelRepositoryInterface $channelRepository, RequestStack $requestStack)
    {
        $this->channelRepository = $channelRepository;
        $this->requestStack = $requestStack;
    }

    public function getChannel(): ChannelInterface
    {
        $request = $this->requestStack->getMasterRequest();

        Assert::notNull($request);

        if (!$this->isPathValid($request)) {
            throw new ChannelNotFoundException('Channel not found!');
        }

        try {
            $channelCode = $request->query->get('channelCode');
            Assert::notNull($channelCode);

            $channel = $this->channelRepository->findOneByCode($channelCode);

            if (!$channel instanceof ChannelInterface) {
                throw new \Sylius\ShopApiPlugin\Exception\ChannelNotFoundException('Channel Not Found!');
            }

            return $channel;
        } catch (\Sylius\ShopApiPlugin\Exception\ChannelNotFoundException $e) {
            throw new \Sylius\ShopApiPlugin\Exception\ChannelNotFoundException($e->getMessage());
        } catch (\Exception $e) {
            throw new ChannelNotFoundException('Channel not found!');
        }
    }

    private function isPathValid(Request $request): bool
    {
        $path = $request->getPathInfo();

        if (strpos($path, 'shop-api') !== false) {
            return true;
        }

        return false;
    }
}

Don’t forget to add configuration for new ChannelContext service, and tag it as ‘sylius.context.channel’:

services:
    # channel request query based context 
    app.context.channel.request_query_based:
        class: App\Context\RequestQueryChannelContext
        arguments:
            - '@sylius.repository.channel'
            - '@request_stack'
        tags:
            - { name: sylius.context.channel, priority: 100 }

Did you try the method already, or you have a different approach to solve the same issue on the project – let me know about it!