Implement a query

Product Query

An example of a simple query would be asking for some product general information. In this section we will implement the query for getting the product title like so.

Query

query {
    product (
        id: "dc5ffdf380e15674b56dd562a7cb6aec"
    ){
        title
    }
}

Response

{
    "data": {
        "product": {
            "title": "Kuyichi Ledergürtel JEVER",
        }
    }
}

Important

Products in OXID eShop are multilanguage models. The language id is not given in the query itself but is set as parameter of the GraphQL endpoint url same as the shop id. So we usually don’t need to think much about handling the shop id or language parameters - shop and GraphQl base module are handling this for us.

File structure

As stated in the Introduction, we need Controller, DataType, Service, Infrastructure and maybe some Exception. Please also have a look at the documentation section about the Architecture of our modules.

├── ..
└── Product
    ├── Controller
       └── Product.php
    ├── DataType
       └── Product.php
    ├── Exception
       └── ProductNotFound.php
    ├── Infrastructure
       └── ProductRepository.php
    └── Service
        └── Product.php

Data type

In the data type we describe all the fields of the product object available for retrieval. In our case we are interested in product id and title:

src/Product/DataType/Product.php
<?php

declare(strict_types=1);

namespace MyVendor\GraphQL\MyGraph\Product\DataType;

use OxidEsales\Eshop\Application\Model\Article as EshopProductModel;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Annotations\Type;
use TheCodingMachine\GraphQLite\Types\ID;

/**
 * @Type()
 */
final class Product
{
    /** @var EshopProductModel */
    private $product;

    public function __construct(
        EshopProductModel $product
    ) {
        $this->product = $product;
    }

    /**
     * @Field()
     */
    public function id(): ID
    {
        return new ID(
            $this->product->getId()
        );
    }

    /**
     * @Field()
     */
    public function title(): string
    {
        return (string) $this->product->getFieldData('oxtitle');
    }
}

Repository

The Repository class contains relations to the OXID eShop Core. Ideally this layer should be the only one you need to change if something changes in the shop.

src/Product/Infrastructure/ProductRepository.php
<?php

declare(strict_types=1);

namespace MyVendor\GraphQL\MyGraph\Product\Infrastructure;

use MyVendor\GraphQL\MyGraph\Product\DataType\Product as ProductDataType;
use OxidEsales\Eshop\Application\Model\Article as EshopProductModel;
use OxidEsales\GraphQL\Base\Exception\NotFound;

final class ProductRepository
{
    /**
     * @throws NotFound
     */
    public function product(string $id): ProductDataType
    {
        /** @var EshopProductModel */
        $product = oxNew(EshopProductModel::class);

        if (!$product->load($id)) {
            throw new NotFound();
        }

        return new ProductDataType(
            $product
        );
    }
}

Service

The service layer contains the GraphQL module’s business logic. In our case its simple found/not found cases handling:

src/Product/Service/Product.php
<?php

declare(strict_types=1);

namespace MyVendor\GraphQL\MyGraph\Product\Service;

use MyVendor\GraphQL\MyGraph\Product\DataType\Product as ProductDataType;
use MyVendor\GraphQL\MyGraph\Product\Exception\ProductNotFound;
use MyVendor\GraphQL\MyGraph\Product\Infrastructure\ProductRepository;
use OxidEsales\GraphQL\Base\Exception\NotFound;

final class Product
{
    /** @var ProductRepository */
    private $productRepository;

    public function __construct(
        ProductRepository $productRepository
    ) {
        $this->productRepository = $productRepository;
    }

    /**
     * @throws ProductNotFound
     */
    public function product(string $id): ProductDataType
    {
        try {
            $product = $this->productRepository->product($id);
        } catch (NotFound $e) {
            throw ProductNotFound::byId($id);
        }

        return $product;
    }
}

We’ll throw the ProductNotFound Exception (with a 404 error code) in case the requested product cannot be found in the shop. Example of Exception class:

src/Product/Exception/ProductNotFound.php
<?php

declare(strict_types=1);

namespace MyVendor\GraphQL\MyGraph\Product\Exception;

use OxidEsales\GraphQL\Base\Exception\NotFound;

use function sprintf;

final class ProductNotFound extends NotFound
{
    public static function byId(string $id): self
    {
        return new self(sprintf('Product was not found by id: %s', $id));
    }
}

Controller

The Controller takes the request parameters and links it to the business logic located in the service:

src/Product/Controller/Product.php
<?php

declare(strict_types=1);

namespace MyVendor\GraphQL\MyGraph\Product\Controller;

use MyVendor\GraphQL\MyGraph\Product\DataType\Product as ProductDataType;
use MyVendor\GraphQL\MyGraph\Product\Service\Product as ProductService;
use TheCodingMachine\GraphQLite\Annotations\Query;

final class Product
{
    /** @var ProductService */
    private $productService;

    public function __construct(
        ProductService $productService
    ) {
        $this->productService = $productService;
    }

    /**
     * @Query()
     */
    public function product(string $id): ProductDataType
    {
        return $this->productService->product($id);
    }
}

Now we are ready to send a request (you might find more details in Requests) against the API.