Magento 2: Creating Custom Web APIS

In this tutorial I will create a new Module called 'Jeff_Slider' with Web REST API

Posted on February 7, 2017 in Magento2

You could follow Alan Kent’s Blog to learn the fundamental knowledge about Magento 2 Api

Step 1: Create a new Module and Enable it

registration.php file


#create a file at app/code/Jeff/Slider/registration.php

<?php
    \Magento\Framework\Component\ComponentRegistrar::register(
        \Magento\Framework\Component\ComponentRegistrar::MODULE,
        'Jeff_Slider',
        __DIR__
    );

module.xml file


#create a file at app/code/Jeff/Slider/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Jeff_Slider" setup_version="0.0.1"/></config>

**Run php bin/magento module:enable Jeff_Slider to enable our new Module. But don’t run php bin/magento setup:upgrade command right now.

Step 2: Create CRUD Model for our new Module

Create InstallSchema.php file


#create a file at app/code/Jeff/Slider/Setup/InstallSchema.php

<?php
namespace Jeff\Slider\Setup;
class InstallSchema implements \Magento\Framework\Setup\InstallSchemaInterface
{
    public function install(\Magento\Framework\Setup\SchemaSetupInterface $setup, \Magento\Framework\Setup\ModuleContextInterface $context)
    {
        $installer = $setup;
        $installer->startSetup();
        //START: install stuff
        //END:   install stuff

//START table setup
$table = $installer->getConnection()->newTable(
            $installer->getTable('jeff_slider_slide')
    )->addColumn(
            'slide_id',
            \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
            null,
            [ 'identity' => true, 'nullable' => false, 'primary' => true, 'unsigned' => true, ],
            'Entity ID'
        )->addColumn(
            'title',
            \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
            255,
            [ 'nullable' => false, ],
            'Demo Title'
        )->addColumn(
            'creation_time',
            \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
            null,
            [ 'nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT, ],
            'Creation Time'
        )->addColumn(
            'update_time',
            \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
            null,
            [ 'nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE, ],
            'Modification Time'
        )->addColumn(
            'is_active',
            \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
            null,
            [ 'nullable' => false, 'default' => '1', ],
            'Is Active'
        );
$installer->getConnection()->createTable($table);
//END   table setup
$installer->endSetup();
    }
}

Create ACL xml file to authorize use to access our REST API


#create a file at app/code/Jeff/Slider/etc/acl.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="Magento_Backend::content">
                    <resource id="Magento_Backend::content_elements">
                        <resource id="Jeff_Slider::slider" title="Slider" sortOrder="10">
                            <resource id="Jeff_Slider::slide" title="Slider Slide" sortOrder="10">
                                <resource id="Jeff_Slider::slide_save" title="Save Slide" sortOrder="10" />
                                <resource id="Jeff_Slider::slide_delete" title="Delete Slide" sortOrder="20" />
                            </resource>
                        </resource>
                    </resource>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

Create webapi.xml file to setup access point


#create file at app/code/Jeff/Slider/etc/webapi.xml

<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
    <route url="/V1/jeffSliderSlide/:slideId" method="GET">
        <service class="Jeff\Slider\Api\SlideRepositoryInterface" method="getById"/>
        <resources>
            <!-- <resource ref="anonymous" /> -->
            <resource ref="Jeff_Slider::slide" />
        </resources>
    </route>

    <route url="/V1/jeffSliderSlide/search" method="GET">
        <service class="Jeff\Slider\Api\SlideRepositoryInterface" method="getList"/>
        <resources>
            <resource ref="anonymous" />
        </resources>
    </route>

    <route url="/V1/jeffSliderSlides" method="POST">
        <service class="Jeff\Slider\Api\SlideRepositoryInterface" method="save"/>
        <resources>
            <resource ref="Jeff_Slider::slide_save" />
        </resources>
    </route>

    <route url="/V1/jeffSliderSlide/:slideId" method="DELETE">
        <service class="Jeff\Slider\Api\SlideRepositoryInterface" method="deleteById"/>
        <resources>
            <resource ref="Jeff_Slider::slide_delete" />
        </resources>
    </route>
</routes>


**You can now run php bin/magento setup:upgrade command to install our database

Create di.xml file


#create dependency injection file at app/code/Jeff/Slider/etc/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Jeff\Slider\Api\Data\SlideInterface" type="Jeff\Slider\Model\Slide" />
    <preference for="Jeff\Slider\Api\SlideRepositoryInterface" type="Jeff\Slider\Model\SlideRepository" />
</config>

Step 3: Create our model resource and API functions

create SlideRepositoryInterface.php


#create a file at app/code/Jeff/Slider/Api/SlideRepositoryInterface.php

<?php
namespace Jeff\Slider\Api;

//use Jeff\Slider\Model\SlideInterface;
use Magento\Framework\Api\SearchCriteriaInterface;

/**
 * @api
 */
interface SlideRepositoryInterface
{
    /**
     * Save Slide.
     * @param \Jeff\Slider\Api\Data\SlideInterface $slide
     * @return \Jeff\Slider\Api\Data\SlideInterface
     *
     */
    public function save(\Jeff\Slider\Api\Data\SlideInterface  $slide);

    /**
     *  Slide.
     * @param int $slideId
     * @return \Jeff\Slider\Api\Data\SlideInterface
     *
     */
    public function getById($slideId);

    /**
     * Retrive slides matching the specified criteria
     * @param \Magento\Framework\Api\SearchCriteriaInterface $criteria
     * @return \Magento\Framework\Api\SearchResultsInterface
     */
    public function getList(SearchCriteriaInterface $criteria);

    /**
     * @param Jeff\Slider\Api\Data\SlideInterface $page
     * @return bool true on success
     */
    public function delete(\Jeff\Slider\Api\Data\SlideInterface $page);

    /**
     * @param int $slideId
     * @return bool true on success
     */
    public function deleteById($slideId);
}

Create SliderInterface.php file


#create a file at app/code/Jeff/Slider/Api/Data/SlideInterface.php

<?php
namespace Jeff\Slider\Api\Data;

interface SlideInterface
{
    const PROPERTY_ID  = 'slide_id';
    const PROPERTY_SLIDE_ID = 'slide_id';
    const PROPERTY_TITLE = 'title';

    /**
     * @api 
     * @return int|null
     */
    public function getId();

    /**
     * @api 
     *
     * @param int $slideId
     * @return \Jeff\Slider\Model\Slide 
     */
    public function setId($slideId);

    /**
     * get Slide Entity 'slide_id' property value
     * @return int|null
     */
    public function getSlideId();

    /**
     * set Slide entity 'slide_id' property value
     * @param int $slideId
     * @return \Jeff\Slider\Model\Slide 
     */
    public function setSlideId($slideId);
    
    /**
     * get Slide entity 'title' property value
     * @return string|null
     */
    public function getTitle();

    /**
     * set Slide entity 'title' property value
     * @param string $title
     * @return \Jeff\Slider\Model\Slide 
     */
    public function setTitle($title);

}

Create our model Slide.php


#create a file at app/code/Jeff/Slider/Model/Slide.php

<?php
namespace Jeff\Slider\Model;
/**
 *@api
 */
class Slide extends \Magento\Framework\Model\AbstractModel implements \Jeff\Slider\Api\Data\SlideInterface, \Magento\Framework\DataObject\IdentityInterface
{
    const CACHE_TAG = 'jeff_slider_slide';

    protected function _construct()
    {
        /* _init($resourceModel)  */
        $this->_init('Jeff\Slider\Model\ResourceModel\Slide');
    }

    public function getIdentities()
    {
        return [self::CACHE_TAG . '_' . $this->getId()];
    }

    /**
     * @api
     * @return int|null
     */
    public function getId() {
        return $this->getData(self::PROPERTY_ID);
    }

    /**
     * @api
     * @param int $id
     * @return \Jeff\Slider\Model\Slide 
     */
    public function setId($id) {
        $this->setData(self::PROPERTY_ID, $id);
        return $this;
    }

    /**
     * @api
     * @return int|null
     */
    public function getSlideId() {
        return $this->getData(self::PROPERTY_SLIDE_ID);
    }

    /**
     * @api
     * @param int $slideId
     * @return \Jeff\Slider\Model\Slide 
     */
    public function setSlideId($slideId) {
        $this->setData(self::PROPERTY_SLIDE_ID, $slideId);
        return $this;
    }

    /**
     * @api
     * @return string|null
     */
    public function getTitle() {
        return $this->getData(self::PROPERTY_TITLE);
    }

    /**
     * @api
     * @param string $title
     * @return \Jeff\Slider\Model\Slide 
     */
    public function setTitle($title) {
        $this->setData(self::PROPERTY_TITLE, $title);
    }
}

Create SlideInterface.php file


#create a file at app/code/Jeff/Slider/Model/SlideInterface.php

<?php
namespace Jeff\Slider\Model;
interface SlideInterface
{
// an empty interface
}

Create our model resource file Slide.php


#create a file at app/code/Jeff/Slider/Model/ResourceModel/Slide.php

<?php
namespace Jeff\Slider\Model\ResourceModel;
class Slide extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    protected function _construct()
    {
        // _init('$mainTable', '$idFieldName')
        $this->_init('jeff_slider_slide','slide_id');
    }
}


Create Our model collection Collection.php


#create a file at app/code/Jeff/Slider/Model/ResourceModel/Slide/Collection.php

<?php
namespace Jeff\Slider\Model\ResourceModel\Slide;
class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
    protected function _construct()
    {
        /* _init($model, $resourceModel) */
        $this->_init('Jeff\Slider\Model\Slide','Jeff\Slider\Model\ResourceModel\Slide');
    }
}

Create our model repository file SlideRepository.php


#create a file at app/code/Jeff/Slider/Model/SlideRepository.php

<?php
namespace Jeff\Slider\Model;

//use Jeff\Slider\Api\SlideRepositoryInterface;
//use Jeff\Slider\Model\SlideInterface;
//use Jeff\Slider\Model\SlideFactory;
//use Jeff\Slider\Model\ResourceModel\Slide\CollectionFactory;

use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\DataObjectHelper;
use Magento\Framework\Reflection\DataObjectProcessor;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\Api\SearchResultsInterfaceFactory;

/**
 * @api
 */
class SlideRepository implements \Jeff\Slider\Api\SlideRepositoryInterface
{
    /**
     * @var \Jeff\Slider\Model\ResourceModel\Slide
     */
    protected $resource;

    /**
     * @var \Jeff\Slider\Model\SlideFactory
     */
    protected $slideFactory;

    /**
     * @var \Jeff\Slider\Model\ResourceModel\Slide\CollectionFactory
     */
    protected $slideCollectionFactory;

    /**
     * @var \Magento\Framework\Api\SearchResultsInterface
     */
    protected $searchResultsFactory;

    /**
     * @var \Magento\Framework\Api\DataObjectHelper
     */
    protected $dataObjectHelper;

    /**
     * @var \Magento\Framework\Reflection\DataObjectProcessor
     */
    protected $dataObjectProcessor;

    /**
     * @var \Jeff\Slider\Api\Data\SlideInterfaceFactory
     */
    protected $dataSlideFactory;

    /**
     * @param ResourceModel\Slide $resource;
     * @param SlideFactory $slideFactory
     * @param ResourceModel\Slide\CollectionFactory $slideCollectionFactory
     * @param \Magento\Framework\Api\SearchResultsInterface $searchResultsFactory
     * @param DataObjectHelper $dataObjectHelper
     * @param DataObjectProcessor $dataObjectProcessor
     * @param \Jeff\Slider\Api\Data\SlideInterfaceFactory $dataSlideFactory
     */
    public function __construct(
        \Jeff\Slider\Model\ResourceModel\Slide $resource,
        \Jeff\Slider\Model\SlideFactory  $slideFactory,
        \Jeff\Slider\Model\ResourceModel\Slide\CollectionFactory $slideCollectionFactory,
        \Magento\Framework\Api\SearchResultsInterface $searchResultsFactory,
        \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
        \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor,
        \Jeff\Slider\Api\Data\SlideInterfaceFactory $dataSlideFactory
    )
    {
        $this->resource = $resource;
        $this->slideFactory        = $slideFactory;
        $this->slideCollectionFactory    = $slideCollectionFactory;
        $this->searchResultsFactory = $searchResultsFactory;
        $this->dataObjectHelper = $dataObjectHelper;
        $this->dataObjectProcessor = $dataObjectProcessor;
        $this->dataSlideFactory = $dataSlideFactory;
    }
    
    /**
     * @api
     * @param \Jeff\Slider\Api\Data\SlideInterface $slide
     * @return \Jeff\Slider\Api\Data\SlideInterface 
     */
    public function save(\Jeff\Slider\Api\Data\SlideInterface $slide)
    {
        try
        {
            $this->resource->save($slide);
        }
        catch(\Exception $e)
        {
            throw new CouldNotSaveException(__($e->getMessage()));
        }
        return $slide;
    }

    /**
     * @api
     * @param int $slideId
     * @return \Jeff\Slider\Api\Data\SlideInterface
     */
    public function getById($slideId)
    {
        $slide = $this->slideFactory->create();

        $this->resource->load($slide, $slideId);

        if (!$slide->getId()) {
            throw new NoSuchEntityException(__('Object with id "%1" does not exist.', $slideId));
        }
        return $slide;        
    }       

    /**
     * @api
     * @param \Jeff\Slider\Api\Data\SlideInterface $slide
     * @return bool ture if success 
     */
    public function delete(\Jeff\Slider\Api\Data\SlideInterface $slide)
    {
        try {
            $this->resource->delete($slide);
        } catch (\Exception $exception) {
            throw new CouldNotDeleteException(__($exception->getMessage()));
        }
        return true;    
    }    

    /**
     * @api
     * @param int $id
     * return bool true on success
     */
    public function deleteById($id)
    {
        return $this->delete($this->getById($id));
    }    

    /**
     * @api
     * @param \Magento\Framework\Api\SearchCriteriaInterface $criteria
     * @return \Magento\Framework\Api\SearchResultsInterface
     */
    public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
    {
        //echo get_class($this->searchResultsFactory). "\n\n";
        $searchResults = $this->searchResultsFactory; //->create();
        $searchResults->setSearchCriteria($searchCriteria);
        $collection = $this->slideCollectionFactory->create();

        foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
            $fields = [];
            $conditions = [];
            foreach ($filterGroup->getFilters() as $filter) {
                $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq';
                $fields[] = $filter->getField();
                $conditions[] = [$condition => $filter->getValue()];
            }
            if ($fields) {
                $collection->addFieldToFilter($fields, $conditions);
            }
        }

        $searchResults->setTotalCount($collection->getSize());
        $sortOrders = $searchCriteria->getSortOrders();
        if ($sortOrders) {
            /** @var SortOrder $sortOrder */
            foreach ($sortOrders as $sortOrder) {
                $collection->addOrder(
                    $sortOrder->getField(),
                    ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC'
                );
            }
        }
        $collection->setCurPage($searchCriteria->getCurrentPage());
        $collection->setPageSize($searchCriteria->getPageSize());
        $slides = [];

        /** @var \Foggyline\Slider\Model\Slide $slideModel */
        foreach($collection as $slideModel) {
            $slideData = $this->dataSlideFactory->create();
            $this->dataObjectHelper->populateWithArray($slideData, $slideModel->getData(), '\Jeff\Slider\Api\Data\SlideInterface');
            $slides[] = $this->dataObjectProcessor->buildOutputDataArray($slideData, '\Jeff\Slider\Api\Data\SlideInterface');
        }

        $this->searchResultsFactory->setItems($slides);
        return $this->searchResultsFactory;
/* Modified by Jeff on Jun 23r, 2017
        $objects = [];                                     
        foreach ($collection as $objectModel) {
            $objects[] = $objectModel;
        }
        $searchResults->setItems($objects);
        return $searchResults; 
*/
    }
}

** At this point, our WEB REST API is created.

Step 4: Set up oAuth authoriztion

create callback-url.php


#create a fold /webroot/web-app and generate a file callback-url.php

<?php

file_put_contents(
    'external-book-app.log',
    'callback-url.php' . print_r($_POST, true) . print_r($_GET, true),
    FILE_APPEND
);

session_id('BookAppOAuth');
session_start();

$_SESSION['oauth_consumer_key'] = $_POST['oauth_consumer_key'];
$_SESSION['oauth_consumer_secret'] = $_POST['oauth_consumer_secret'];
$_SESSION['store_base_url'] = $_POST['store_base_url'];
$_SESSION['oauth_verifier'] = $_POST['oauth_verifier'];

session_write_close();

header('HTTP/1.0 200 OK');

echo 'Response';

Create identity-link-url.php


#create a file at /webroot/web-app/identity-link-url.php

<?php

file_put_contents(
    'my-app.log',
    'identity-link-url.php' . print_r($_POST, true) . print_r($_GET, true),
    FILE_APPEND
);

$consumerId = $_REQUEST['consumer_id'];
$callbackUrl = urlencode(urldecode($_REQUEST['success_call_back']));

echo <<<HTML
<form method="post" action="check-login.php?consumer_id={$consumerId}&callback_url={$callbackUrl}">
    <p>External Book App Login</p>
    <input type="text" name="username" id="username" placeholder="Username">
    <input type="password" name="password" id="password" placeholder="Password">
    <input type="submit" name="submit" value="Login">
</form>
HTML;

Create New Integration at backend at System=>Extension=>Integration as shown in following image

screen shot for api creating


screen shot for api creating second

After you save the new Integration, you could get Consumer Key, Consumer Secret, Access Token, and Access Token Secret

Call our new WEB REST API with following code


<?php

function sign($method, $url, $data, $consumerSecret, $tokenSecret)
{
    $url = urlEncodeAsZend($url);
    $data = urlEncodeAsZend(http_build_query($data, '', '&'));
    $data = implode('&', [$method, $url, $data]);
    $secret = implode('&', [$consumerSecret, $tokenSecret]);
                 
    return base64_encode(hash_hmac('sha1', $data, $secret, true));
}

function urlEncodeAsZend($value)
{
    $encoded = rawurlencode($value);
    $encoded = str_replace('%7E', '~', $encoded);
    return $encoded;
}

$consumerKey = 'rgh0gwf91yc3ev49up9x2ykmoaf71xud';
$consumerSecret = 'jrymmimkjo6gpu2we80xi0be49kphtx6';
$accessToken = 'olp9wq1978ysgrr1atur9xis4q4o88rv';
$accessTokenSecret = '39k8eaomlgk7vu73o0hjqrbmed5eaak1';
 
$method = 'GET';

$url = "http://magento2.local/index.php/rest/V1/jeffSliderSlide/3";

$data = [
    'oauth_consumer_key' => $consumerKey,
    'oauth_nonce' => md5(uniqid(rand(), true)),
    'oauth_signature_method' => 'HMAC-SHA1',
    'oauth_timestamp' => time(),
    'oauth_token' => $accessToken,
    'oauth_version' => '1.0',
];

$data['oauth_signature'] = sign($method, $url, $data, $consumerSecret, $accessTokenSecret);
//echo $data['oauth_signature'], "\n";

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_URL => $url,
    CURLOPT_HTTPHEADER => [
        'Authorization: OAuth ' . http_build_query($data, '', ',')
    ]
]);


$result = curl_exec($curl);

var_dump($result);


curl_close($curl);

You will get the final result like:

string(43) "{"id":3,"slide_id":3,"title":"Third Slide"}"

The complete code can be downloaded at GitHub

I hope my tutorial will help generate your own WEB REST APIs

comments powered by Disqus