Third & GroveThird & Grove
Oct 6, 2016 - Curtis Ogle

Creating a Custom REST API in Magento2

 

Magento2 comes out of the box with a slew of predefined REST APIs that allow clients to take control of virtually everything about the Magento2 site. Whether searching for customers and orders or setting up new tax classes, more than likely there's an API for doing so. Although core Magento2 comes with all of the REST APIs, sometimes they are not quite enough for your particular use case. So, in this post we look at how to create your own custom REST API in Magento2.

While integrating a Drupal site with Magento2 as the backend, we faced a few challenges with the existing REST APIs – specifically the cart/quote management endpoints. You can look at Swagger docs for the default Magento APIs. The cart management endpoints start about half way down the page.

For this example, we are most interested in quoteCartItemRepositoryV1. According to the documentation, it seems that adding or removing multiple items from the cart will require multiple API calls. That isn't too bad, but what if our use case is a single page checkout? Multiple API calls could cause some performance hits. You could try to update as things are changed on the page via ajax, but I think we can make a pretty simple endpoint that will let us update the cart all at once.

As mentioned in my last post, my code will live in magento/app/code/TAG. For this example, we will call the module CartManager, so I created a magento/app/code/TAG/CartManager directory. As always, when creating a new module, we have to create the registration.php files and module.xml files and so on. I'm going to assume you have created a module skeleton already, so I will only be covering the API portion. Just keep in mind that you may have user different paths for your module and will need to update accordingly.

The first step in creating a custom endpoint is to create a webapi.xml file. So in magento/app/code/TAG/CartManager/etc/webapi.xml place the following code:

<?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
	<route url="/V1/carts/:cartId/updateCart" method="POST">
		<service class="TAG\CartManager\Api\CartManagementInterface" method="updateCart"/>
			<resources>
				<resource ref="Magento_Cart::manage"/>
			</resources>
	</route>
</routes>

 

This code just tells Magento that you plan to expose an endpoint at www.your-hostname.com/V1/carts/:cartId/updateCart which will accept a POST request. The updateCart() method will be called from TAG\CartManager\Api\CartManagementInterface when the endpoint is used.

Next we have to create the interface and implementing class for our endpoint. So in magento/app/code/TAG/CartManager/Api/CartManagementInterface put the following code:

<?php
namespace TAG\CartManager\Api;
 
interface CartManagementInterface {
    /**
     * Updates the specified cart with the specified products.
     *
     * @api
     * @param int $cartId
     * @param \TAG\CartManager\Api\CartProductInformationInterface[] $products
     * @return boolean
     */
    public function updateCart($cartId, $products = null);
}

 

The implementing magento/app/code/TAG/CartManager/Model/CartManagement class should look like this:

<?php
namespace TAG\CartManager\Model;
 
use Magento\Quote\Api\CartRepositoryInterface;
use TAG\CartManager\Api\CartManagementInterface as ApiInterface;
 
class CartManagement implements ApiInterface {
 
    /**
     * Quote / Cart Repository
     * @var CartRepositoryInterface $quoteRepository
     */
    protected $quoteRepository;
 
    /**
     * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
     * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
     */
    public function __construct(
        CartRepositoryInterface $quoteRepository
    ) {
        $this->quoteRepository = $quoteRepository;
    }
 
    /**
     * Updates the specified cart with the specified products.
     *
     * @api
     * @param int $cartId
     * @param \TAG\CartManager\Api\CartProductInformationInterface[] $products
     * @return boolean
     */
    public function updateCart($cartId, $products = null) {
        $quote = $this->quoteRepository->getActive($cartId);
 
        if ($quote->hasItems()) {
            $quote->removeAllItems();
        }
 
        if (!empty($products)) {
            foreach ($products as $product) {
                $sku = $product->getSku();
                $qty = $product->getQty();
 
                $add_product = $this->productRepository->get($sku);
                $productType = $add_product->getTypeId();
 
                $quote->addProduct($add_product, $qty);
            }
        }
 
        $this->quoteRepository->save($quote->collectTotals());
 
        return true;
    }
}

 

The logic of the updateCart method is pretty simple. It assumes that the client knows the current state of the cart. So instead of having to keep track of what was added and what was removed, the Magento side just gets a single update from the client. As a result, we can just remove all the items from the existing cart and add items based on the cart state that was posted to the endpoint. We finish off by making sure the cart totals are up to date and returning.

You will notice that the updateCart() method takes a custom interface as a parameter. This is because Magento won't associate arrays because they also support SOAP – and it isn't possible to represent them in WSDL. To overcome this, we just have to create a simple interface and class to hold the data that we need. In magento/app/code/TAG/CartManager/Api/CartProductInformationInterface, add the following:

<?php
namespace TAG\CartManager\Api;
 
/**
 * Interface for tracking cart product updates.
 */
interface CartProductInformationInterface {
    /**
     * Gets the sku.
     *
     * @api
     * @return string
     */
    public function getSku();
 
    /**
     * Sets the sku.
     *
     * @api
     * @param int $sku
     */
    public function setSku($sku);
 
    /**
     * Gets the quantity.
     *
     * @api
     * @return string
     */
    public function getQty();
 
    /**
     * Sets the quantity.
     *
     * @api
     * @param int $qty
     * @return void
     */
    public function setQty($qty);
}

 

Then in the implementing class magento/app/code/TAG/CartManager/Model/CartProductInformation, add the following:

<?php
namespace TAG\CartManager\Model;
 
use \TAG\CartManager\Api\CartProductInformationInterface;
 
/**
 * Model that contains updated cart information.
 */
class CartProductInformation implements CartProductInformationInterface {
 
    /**
     * The sku for this cart entry.
     * @var string
     */
    protected $sku;
 
    /**
     * The quantity value for this cart entry.
     * @var int
     */
    protected $qty;
 
    /**
     * Gets the sku.
     *
     * @api
     * @return string
     */
    public function getSku() {
        return $this->sku;
    }
 
    /**
     * Sets the sku.
     *
     * @api
     * @param int $sku
     */
    public function setSku($sku) {
        $this->sku = $sku;
    }
 
    /**
     * Gets the quantity.
     *
     * @api
     * @return string
     */
    public function getQty() {
        return $this->qty;
    }
 
    /**
     * Sets the quantity.
     *
     * @api
     * @param int $qty
     * @return void
     */
    public function setQty($qty) {
        $this->qty = $qty;
    }
}

 

Lastly, you just have to update the dependency injection config file so that Magento knows which classes to pick up at run time. So in magento/app/code/TAG/CartManager/etc/di.xml add the following:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="TAG\CartManager\Api\CartManagementInterface" type="TAG\CartManager\Model\CartManagement"/>
    <preference for="TAG\CartManager\Api\CartProductInformationInterface" type="TAG\CartManager\Model\CartProductInformation"/>
</config>

 

Now all you have to do is run magento/bin/magento setup:upgrade and your new service should be active. Now, a client should be able to update a cart by posting data that looks like:

$post_data = array(
  'products[0][sku]' => 123456
  'products[0][qty]' => 4
  'products[1][sku]' => 321654
  'products[1][qty]' => 42
);

 

Note: This is just a PHP representation meant to be used with Guzzle, but this is just form url encoded data, so with very little modification it can work with your client.

Happy Magento-ing!