Implement a mutation

Having the query for the products it is time to create a mutation to change products. Let’s keep it simple and just change the product title.

Anonymous users being able to change data is probably not a good idea. In fact we should make sure only a logged in user is able to update a product’s title.

Important

This is only an example. In production, only a logged in and authorized admin user should be able to mutate product data. Please see section Authorization for a PermissionProvider example.

We start with the controller this time and (this is entirely done because of step by step tutorial reasons) add the mutation to the Product controller we build earlier.

src/Product/Controller/Product.php
use TheCodingMachine\GraphQLite\Annotations\Mutation;
use TheCodingMachine\GraphQLite\Annotations\Logged;

final class Product
{
    // ...

    /**
     * @Mutation()
     * @Logged()
     */
    public function productTitleUpdate(ProductDataType $product): ProductDataType
    {
        $this->productService->store($product);

        return $product;
    }
}

In the product service, we need to add the method that takes care of storing the product

src/Product/Service/Product.php
final class Product
{
    // ...

    /**
     * @return true
     */
    public function store(ProductDataType $product): bool
    {
        return $this->productRepository->saveProduct(
            $product
        );
    }
}

As we can reuse the OXID eShop core functionality to save the application model, let’s add some functionality to the infrastructure layers ProductRepository from the query tutorial to deal with this.

src/Product/Infrastructure/ProductRepository.php
use RuntimeException;

final class ProductRepository
{
    // ..

    /**
     * @throws RuntimeException
     * @return true
     */
    public function saveProduct(ProductDataType $product): bool
    {
        if (!$product->getEshopModel()->save()) {
            throw new RuntimeException('Object save failed');
        }

        return true;
    }
}

We are not done yet. The request will probably contain the product id (we want to change exactly this product) and the new title. If we look at the Controller\Product::productTitleUpdate() method, it expects a ProductDataType as argument. Also we will need a check if the product in question even exists before it can be updated. So we need some factory to create a ProductDataType from the input:

src/Product/Service/ProductTitleInput.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\Infrastructure\ProductRepository as ProductRepository;
use MyVendor\GraphQL\MyGraph\Product\Infrastructure\ProductMutation as ProductMutationService;
use MyVendor\GraphQL\MyGraph\Product\Exception\ProductNotFound;
use OxidEsales\GraphQL\Base\Exception\NotFound;
use TheCodingMachine\GraphQLite\Annotations\Factory;

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

    /** @var ProductMutationService */
    private $productMutationService;

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

    /**
     * @Factory(name="ProductInput")
     */
    public function fromUserInput(string $productId, string $title): ProductDataType
    {
        try {
            $product = $this->productRepository->product($productId);
        } catch (NotFound $e) {
            throw ProductNotFound::byId($productId);
        }

        return $this->productMutationService->assignTitle($product, $title);
    }
}

This in turn needs some infrastructure layer part, because we need the OXID eShop model’s BaseModel::assign() method.

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

declare(strict_types=1);

namespace MyVendor\GraphQL\MyGraph\Product\Infrastructure;

use MyVendor\GraphQL\MyGraph\Product\DataType\Product as ProductDataType;

final class ProductMutation
{
    public function assignTitle(
        ProductDataType $product,
        string $title
    ): ProductDataType {
        $product->getEshopModel()->assign([
            'oxtitle' => $title
        ]);

        return $product;
    }
}

To get a clearer view on what we need for the mutation, here’s the relevant file structure after the changes from this tutorial applied.

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

Important

As stated in section Authorization, when using the @Logged annotation as done here, we need to send the token in the HTTP Authorization header in order to see the mutation in the schema.

Mutation

mutation {
    productTitleUpdate(
        product: {
            productId: "dc5ffdf380e15674b56dd562a7cb6aec"
            title: "my new product title"
        }
    ){
        id
        title
    }
}

Response

{
    "data": {
        "productTitleUpdate": {
            "id": "dc5ffdf380e15674b56dd562a7cb6aec",
            "title": "my new product title"
        }
    }
}