Back to Top

Working of HTTP client at the Shopware

Updated 30 December 2021

In this blog, you are going to learn “about working with an HTTP client at the Shopware ”.
I hope you know the directory structure of Shopware 6 plugin, if you don’t know, see here- https://docs.shopware.com/en/shopware-platform-dev-en/internals/directory-structure.

Some things are not easy to solve without an API. For these cases Shopware 6 has a REST-API.
Shopware 6 comes with a powerful REST-API. You can use this API by using a HTTP-Client, like curl or similar. For reasons of simplicity we wrote a Shopware 6 plugin, but in most cases this is not a good use case. Please do not call the Shopware 6 API through a plugin unless you do have a really good reason to do so!

Let’s create a class that makes use of Guzzle, which is already included in the shopware/core, so that we can add multiple helpful functions to this class and simplify the use of the API.

RestService.php
<?php declare(strict_types=1);

namespace Webkul\RestApiHandling\Service;

use GuzzleHttp\Client;

class RestService
{
    /**
     * @var Client
     */
    private $restClient;

    public function __construct()
    {
        $this->restClient = new Client();
    }
}
service.xml
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="Webkul\RestApiHandling\Service\RestService" />
    </services>
</container>

The Admin API is secured via OAuth authentication, so we need some helpers to get the authentication token before we do any further requests. Therefore, we extend our RestService class to do this during the constructor.

<?php declare(strict_types=1);

namespace Webkul\RestApiHandling\Service;

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;
use Shopware\Core\System\SystemConfig\SystemConfigService;

class RestService
{
    /**
     * @var Client
     */
    private $restClient;

    /**
     * @var SystemConfigService
     */
    private $config;

    /**
     * @var string
     */
    private $accessToken;

    /**
     * @var string
     */
    private $refreshToken;

    /**
     * @var \DateTimeInterface
     */
    private $expiresAt;

    public function __construct(SystemConfigService $config)
    {
        $this->restClient = new Client();
        $this->config = $config;
    }

    private function getAdminAccess(): void
    {
        $body = \json_encode([
            'client_id' => 'administration',
            'grant_type' => 'password',
            'scopes' => $this->config->get('RestApiHandling.config.scope'),
            'username' => $this->config->get('RestApiHandling.config.username'),
            'password' => $this->config->get('RestApiHandling.config.password')
        ]);

        $request = new Request(
            'POST',
            getenv('APP_URL') . '/api/oauth/token',
            ['Content-Type' => 'application/json'],
            $body
        );

        $response = $this->restClient->send($request);

        $body = json_decode($response->getBody()->getContents(), true);

        $this->setAccessData($body);
    }

    private function setAccessData(array $body): void
    {
        $this->accessToken = $body['access_token'];
        $this->refreshToken = $body['refresh_token'];
        $this->expiresAt = $this->calculateExpiryTime((int) $body['expires_in']);
    }

    private function calculateExpiryTime(int $expiresIn): \DateTimeInterface
    {
        $expiryTimestamp = (new \DateTime())->getTimestamp() + $expiresIn;

        return (new \DateTimeImmutable())->setTimestamp($expiryTimestamp);
    }

    private function createShopwareApiRequest(string $method, string $uri, ?string $body = null): RequestInterface
    {
        return new Request(
            $method,
            getenv('APP_URL') . '/api/v3/' . $uri,
            [
                'Authorization' => 'Bearer ' . $this->accessToken,
                'Accept' => '*/*'
            ],
            $body
        );
    }
}

Note our extension of the constructor! First we get the SystemConfigService to ask for information we can maintain in the administration.

Start your headless eCommerce
now.
Find out More

Since we have changed our RestService constructor we need to change our services.xml.

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="Swag\RestApiHandling\Service\RestService">
            <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/>
        </service>
    </services>
</container>

Now we need to add a config.xml to our plugin so that we can maintain the required data information in the administration.

config.xml
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/System/SystemConfig/Schema/config.xsd">

    <card>
        <title>User Data</title>
        <title lang="de-DE">Benutzerdaten</title>
        <input-field>
            <name>username</name>
            <label>Api user name</label>
            <label lang="de-DE">API Benutzername</label>
        </input-field>
        <input-field type="password">
            <name>password</name>
            <label>Api user password</label>
            <label lang="de-DE">API Benutzerpasswort</label>
        </input-field>
        <input-field type="single-select">
            <name>scope</name>
            <label>API access level</label>
            <label lang="de-DE">API Zugriffslevel</label>
            <options>
                <option>
                    <id>write</id>
                    <name>Write-Access</name>
                    <name lang="de-DE">Schreibzugriff</name>
                </option>
                <option>
                    <id>read</id>
                    <name>Read-Access</name>
                    <name lang="de-DE">Lesezugriff</name>
                </option>
            </options>
        </input-field>
    </card>
</config>

One of the problems that can occur when working with the Admin API is that your access token has expired. To avoid having to deal with this problem we have already included the refresh token and expiration time in our properties, so let’s start automatically generating a new access token.

<?php

class RestService
{
    ...

    private function send(RequestInterface $request, string $uri)
    {
        if ($this->expiresAt <= (new \DateTime())) {
            $this->refreshAuthToken();

            $body = $request->getBody()->getContents();

            $request = $this->createShopwareApiRequest($request->getMethod(), $uri, $body);
        }

        return $this->restClient->send($request);
    }

    private function refreshAuthToken(): void
    {
        $body = \json_encode([
            'client_id' => 'administration',
            'grant_type' => 'refresh_token',
            'scopes' => $this->config->get('RestApiHandling.config.scope'),
            'refresh_token' => $this->refreshToken
        ]);

        $request = new Request(
            'POST',
            getenv('APP_URL') . '/api/oauth/token',
            ['Content-Type' => 'application/json'],
            $body
        );

        $response = $this->restClient->send($request);

        $body = json_decode($response->getBody()->getContents(), true);

        $this->setAccessData($body);
    }
}
<?php 

use Psr\Http\Message\ResponseInterface;

class RestService
{
    ...

    public function request(string $method, string $uri, ?array $body = null): ResponseInterface
    {
        if ($this->accessToken === null || $this->refreshToken === null || $this->expiresAt === null) {
            $this->getAdminAccess();
        }

        $bodyEncoded = json_encode($body);

        $request = $this->createShopwareApiRequest($method, $uri, $bodyEncoded);

        return $this->send($request, $uri);
    }
}

The request function makes it easy to send API requests. It requests the API credentials if they do not already exist, and converts your request into a Admin API request. 
$this->restService->request(‘GET’, ‘product’);

The first parameter is the HTTP method to be used.

The second parameter is the route / entity name you want to call. Some examples would be productrule or language.

The third parameter is the data you might want to send to the API. These data can be written as an array and is encoded by PHP according to the JSON scheme.

I hope it will help you. Thanks for reading. Happy Coding 🙂
Thank You.

. . .

Leave a Comment

Your email address will not be published. Required fields are marked*


Be the first to comment.

Back to Top

Message Sent!

If you have more details or questions, you can reply to the received confirmation email.

Back to Home