Magento 2 Guide To Create Custom Api

As we have already seen how to create a basic module in Magento 2, now lets learn how to create you own custom api. So start creation of your new module by creating module.xml and registration.php. I have created my module ‘CustomApi’ under ‘W3solver’ namespace so below is the code of my module.xml and registration.php

Module.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * w3solver.com
 *
 * @category       W3solver
 * @package        W3solver_CustomApi
 * @Description    module
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
   <module name="W3solver_CustomApi" setup_version="2.0.1"></module>
</config>

Registration.php

<?php

/**
 * I95Dev.com
 *
 *
 * @category    W3solver
 * @package     W3solver_CustomApi
 * @Description Module registration file
 * @author      W3solver
 */
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'W3solver_CustomApi',
    __DIR__
);

Now to create any custom API we need to add ‘webapi.xml’ file under ‘etc’ folder. In below code you can see we have a rest api with method as GET and url as /V1/customapi/shipping/:name, the class to which it refers is W3solver\CustomApi\Api\ShippingInterface

<?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/customapi/shipping/:name" method="GET">
        <service class="W3solver\CustomApi\Api\ShippingInterface" method="exist"/>
        <resources>
            <resource ref="anonymous"/>
        </resources>
    </route>
</routes>

Now we will create the interface(ShippingInterface) file under ‘Api’ folder

<?php
namespace W3solver\CustomApi\Api;
 
interface ShippingInterface
{
    /**
     * check if shipping method exists
     *
     * @api
     * @param string $name shipping method name
     * @return bool
     */
    public function exist($name);
}

The interface is implemented in its respective model, in our case we are implementing it in Shipping.php file placed directly under ‘Model’ Folder.

<?php

namespace W3solver\CustomApi\Model;

use W3solver\CustomApi\Api\ShippingInterface;

class Shipping implements ShippingInterface
{

    public $shipconfig;
    public $scopeConfig;

    public function __construct(
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Shipping\Model\Config $shipconfig
    ) {
        $this->shipconfig = $shipconfig;
        $this->scopeConfig = $scopeConfig;
    }

    /**
     * checks if shipping method is active
     *
     * @api
     * @param string $name shipping method name
     * @return string Greeting message with users name.
     */
    public function exist($name)
    {

        $activeCarriers = $this->shipconfig->getActiveCarriers();
        $storeScope = \Magento\Store\Model\ScopeInterface::SCOPE_STORE;
        foreach ($activeCarriers as $carrierCode => $carrierModel) {
            if ($carrierMethods = $carrierModel->getAllowedMethods()) {
                foreach ($carrierMethods as $methodCode => $method) {
                    $code = $carrierCode . '_' . $methodCode;
                    if ($code == $name) {
                        return true;
                    }
                }
            }
        }

        return false;
    }
}

We are all set for your API to test, above API checks the availability of method.

Integrate Amazon Rekongition service With PHP.

Ahh! If you have never heard AWS Rekognition service that no need to worry. Rekognition is a service provided by Amazon in the year 2016. Your program can recognize faces, you can compare face, find a specific face, find content from the image and videos.You can moderate the uploaded images/video according to your need. It doesn’t sound excited now but it is a very important and useful in many use case.
Suppose you have an e-commerce site, where seller uploads their product. The seller is free to upload any product seller can upload an image of Weapon, Drug and some content that you really don’t want to allow on your application. To remove them you have to moderate the products manually. Here AWS Rekognition comes in the picture, by using this service you can write your logic in code and moderate images and video. It helps you to understand the image and you can write your algorithm accordingly.

Listed things, that you will be required to integrate AWS Recognition.

1. AWS account. If you don’t have an account you can create.
2. It will be better if you will create an IAM user and provide the sufficient access permission to him.(Obtain key and secret )
3. You can get the key, login to aws console -> search IAM -> Users(from left panel)-> security Credential-> Create access key
4. AWS PHP SDK.

There are 33 methods exposed by AWS underrecognition service. You can use according to your need. Visit this link to get all methods.

How to implement this: This service support almost all language. For PHP, AWS has SDK. You need to download this SDK. Follow Command to get AWS PHP SDK.

Go to your project folder and run command.

curl -sS https://getcomposer.org/installer | php

Run composer command and install PHP SDK

php composer.phar require aws/aws-sdk-php

That’s it. Now you have PHP SDK with you. You can Use this SDK to integrate all service provided By Amazon.

You can use aws recognition service with S3 as well as local storage. so let’s start your IDE and Write first code:

require 'vendor/autoload.php';

use Aws\Rekognition\RekognitionClient;
$options = [
    'region' => 'us-west-2',
    'version' => '2016-06-27',
    'credentials' => array(
        'key' => 'Your key',
        'secret'  => 'Your Secret',
      )
    
    ];
$rekognition = new RekognitionClient($options);
$fp_image = fopen('test.jpg', 'r');
$image = fread($fp_image, filesize('test.jpg'));
fclose($fp_image);

Custom Api Module to check if shipment and payment methods are active

There are lots of api provided by magento but here is the module that provide you a custom api to check if shipment and payment method are active or not. Below you can find out the module zip attached. I have tested my code via postman so below are some screenshot of the same:-

1. Check for payment method

Api End Point:- V1/customapi/payment/:paymentmethodname

 

2. Check for shipment method

Api End Point:- V1/customapi/payment/:paymentmethodname

Versions: I’ve tested the module with the version 2.2.2

click link to download the extension

Let me know if you found any issue or have any query.
Updated on :- 01-02-2018

 

Magento 2 Solid Principle

As soon as you start working with OOPs we came to know about sevral design pattern and SOLID principle is the first thing that we can across. As per magento technical guidelines (under class design)

Object decomposition MUST follow the SOLID principles

If you check the Magento code than you can clearly see that Magento code is following SOLID principles. We will look into it these principle one by one:-

SOLID stands for:

  • Single Responsibility
  • Open-Closed
  • Liskov Substitution
  • Interface Segregation
  • Dependency Inversion

Single Responsibility Principle

A class should have only and only one reason to change.

As its name suggest, it states that every object should have single responsibility i.e, single role to perform and that responsibility should be entirely encapsulated by the class. It means that when we are adding some unrelated feature they should not affect same class.
For example, In Magento 2 every controller has just one responsibility.

namespace Magento\Cms\Controller\Page;

class View extends \Magento\Framework\App\Action\Action {

    public function execute() {

        $pageId = $this->getRequest()->getParam('page_id', $this->getRequest()->getParam('id', false));
        $resultPage = $this->_objectManager->get(\Magento\Cms\Helper\Page::class)->prepareResultPage($this, $pageId);
        if (!$resultPage) {
            $resultForward = $this->resultForwardFactory->create();
            return $resultForward->forward('noroute');
        }
    return $resultPage;
    }
}

Open-Closed Principle

Objects or entities should be open for extension, but closed for modification.

The above statement itself explains everything that it should be always easy to make behaviour change of particular implementation but we should make changes directly into the original source code. We should make changes by extending the class.

For example, In Magento 2 if you check module-catalog/Controller/Adminhtml/Category.php it is just initiating category object and rest of the actions (such as:- move, refresh page) are using it by extending the \Magento\Catalog\Controller\Adminhtml\Category class and making behaviour changes as per the requirement.

namespace Magento\Catalog\Controller\Adminhtml\Category;

class Move extends \Magento\Catalog\Controller\Adminhtml\Category {

    public function execute() {
        
        $parentNodeId = $this->getRequest()->getPost('pid', false);
        $prevNodeId = $this->getRequest()->getPost('aid', false);
        ............
    }
}

Liskov substitution principle

This principle states that any substitute/child class (implemented by either extension or dependency injection) should adhere the same rules as its predecessor class i.e, it should not break the functionality that is already being used by keeping method signature consistent.

For example, in below example you can see that toOptionArray() in both class return an array


namespace Magento\Ups\Model\Config\Source;

use Magento\Shipping\Model\Carrier\Source\GenericInterface;

class Generic implements GenericInterface {

    public function toOptionArray()
    {
        $configData = $this->carrierConfig->getCode($this->_code);
        $arr = [];
        foreach ($configData as $code => $title) {
            $arr[] = ['value' => $code, 'label' => __($title)];
        }
        return $arr;
    }
}
namespace Magento\Ups\Model\Config\Source;

class OriginShipment extends \Magento\Ups\Model\Config\Source\Generic

{
    protected $_code = 'originShipment';
    public function toOptionArray()
    {
        $orShipArr = $this->carrierConfig->getCode($this->_code);
        $returnArr = [];
        foreach ($orShipArr as $key => $val) {
            $returnArr[] = ['value' => $key, 'label' => $key];
        }
        return $returnArr;
    }
    return $returnArr;
    }
}

Interface Segregation

This states that we should not use those method or properties in the interface that the client may not use. Rather than having all method which are related to responsibility it’s good to break it into multiple interfaces and let a single class extends multiple interface. For example see the below image:-

Dependency Inversion principle

High level module should not depend on low level moduleFirst of all keep in mind that dependency inversion is not same as dependency injection, although they go hand in hand.

Magento2.2 add custom product tab with custom template on product edit page

In this blogpost we will learn how to add custom tab in product edit page in admin panel.Create a file with mentioned path and add the following code:-

view\adminhtml\ui_component\product_form.xml
 <form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <htmlContent name="qrcode" sortOrder="22">
        <argument name="data" xsi:type="array">
            <item name="wrapper" xsi:type="array">
                <item name="label" xsi:type="string" translate="true">Qrcode</item>
                <item name="collapsible" xsi:type="boolean">true</item>
                <item name="opened" xsi:type="boolean">false</item>
            </item>
        </argument>
        <settings>
            <wrapper>
                <canShow>true</canShow>
                <componentType>fieldset</componentType>
            </wrapper>
        </settings>
        <block class="W3solver\Qrcode\Block\Adminhtml\Catalog\Product\Edit\Tab\Qrcode" name="qrcode">
            <arguments>
                <argument name="config" xsi:type="array">
                    <item name="label" xsi:type="string" translate="true">Qrcode</item>
                    <item name="collapsible" xsi:type="boolean">true</item>
                    <item name="opened" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="string">11</item>
                    <item name="canShow" xsi:type="boolean">true</item>
                    <item name="componentType" xsi:type="string">fieldset</item>
                </argument>
            </arguments>
        </block>
    </htmlContent>
</form>

Lets understand above code. product_form is used for customization of product edit page in admin panel as per magento documentation(for more info refer the link). So above code will add some htmlcontent which will thus render a block to show your custom tab and its template.

Now lets create our block at below path

 W3solver\Qrcode\Block\Adminhtml\Catalog\Product\Edit\Tab\Qrcode.php 
<?php
/**
 * W3solver.com
 * @category    W3solver
 * @package     W3solver_Qrcode
 * @Description Block to add qrcode tab to product edit page
 * @author      W3solver
 * @copyright   Copyright (c) 2017 w3solver
 */
namespace W3solver\Qrcode\Block\Adminhtml\Catalog\Product\Edit\Tab;

use Magento\Backend\Block\Template\Context;
use Magento\Framework\Registry;
use W3solver\Qrcode\Model\QrcodeListFactory;

class Qrcode extends \Magento\Framework\View\Element\Template {

    protected $_template = 'product/edit/qrcode.phtml';
    protected $_qrcodeListFactory;
    protected $fileName;

    /**
     * Core registry
     *
     * @var Registry
     */
    protected $_coreRegistry = null;

    /**
     * @param Magento\Backend\Block\Template\Context
     * @param Magento\Framework\Registry
     * @param W3solver\Qrcode\Model\QrcodeListFactory
     * @param Magento\Store\Model\StoreManagerInterface
     */
    public function __construct(
    Context $context, Registry $registry, QrcodeListFactory $qrcodeListFactory, array $data = []
    ) {
        $this->_coreRegistry = $registry;
        $this->_qrcodeListFactory = $qrcodeListFactory;
        
        parent::__construct($context, $data);
    }

    /**
     * Retrieve product
     *
     * @return \Magento\Catalog\Model\Product
     */
    public function getProduct() {
        return $this->_coreRegistry->registry('current_product');
    }
}

We also need to create our template file to show whatever content we want at following path:-

w3solver\Qrcode\view\adminhtml\templates\product\edit\qrcode.phtml

So above code will craete a Qrcode tab on product edit page in admin. Drop in your comments if you face any issue.

Magento 2 some important coding practice for developers

In my previous blogpost for code sniffer, we have come to know how we can find if our extension is following MEQP2 standards. Today let us see some most common coding practice that will help us while developing Magento extension. This will make our code more magento friendly.

Use dependency injection not object manager

We all love to use object manager though we know it is not a recommended practice by Magento. We should not use it for several reason:-

a. To maintain code consistency
b. Testing become easier(as we can pass mock object as an argument)
c. Dependency injection make dependencies more clear – as everything can be easily found in constructor rather than hidden in middle of the code.
d. It also prevent us from using repeated dependency as we can make use already existing dependency of parent class

There are several exception to this scenario:-

• in static magic methods like __wakeup, serialize, etc
• in case you should make backward compatibility of constructor
• in global scope, like in fixtures of integration test.
• in class that needs only for creation of object like factory, proxy , etc


You can go through http://www.javacreed.com/why-should-we-use-dependency-injection/ for more details about dependency injection.

Do not use session in dependency injection

Whenever you will check your code with MEQP2 standard it will raise an issue if you have used session object in constructor as dependency injection. You will get below error. You can simply replace it from parent class session objects.

“Session object MUST NOT be requested in constructor. It can only be passed as a method argument. “

Use Magento exception library not core PHP – Do not throw exception using PHP keyword.

You can use message manager to show error, warning or ant exception message rather than using throw keyword.
Include \Magento\Framework\Message\ManagerInterface $messageManager in your constructor and call the respective function to show error, warning or exception.

Use magento library function instead of core PHP function

For example for curl request instead of using curl_init, curl_setopt, curl_exec use the function provide by curl class in magento (\Magento\Framework\HTTP\Client\Curl $curl)

Magento 2 place order without a Email address

This blogpost guide you a hack to place order in magento 2 via Email. As we all know email is an essential component for managing data in Magento. But today i came across to one of my client who does not want email to be necessary, i told him that this is not possible in Magento but he wanted to implement such functionality. So i came across a hack, i removed validation from client side and passed a random generated email. Here are the steps that i followed:-

To achieve this we need to override Magento_Checkout/js/action/place-order file and Magento_Checkout/template/form/element/email.html, to do that create a requirejs-config file in your module under view/frontend/requirejs-config.js

var config = {
    map: {
        '*': {
            'Magento_Checkout/template/form/element/email.html': 
            'W3solver_Trackorder/template/form/element/email.phtml',
	    'Magento_Checkout/js/action/place-order': 
            'W3solver_Trackorder/js/action/place-order'
        }
  }
};

Place your overriden file under view/frontend/web/js/action/place-order.js and add the below code. Here we have just added an optional email(emailOptional@w3solver.com), so all order will be placed by this email.

/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
define(
    [
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/model/url-builder',
        'Magento_Customer/js/model/customer',
        'Magento_Checkout/js/model/place-order'
    ],
    function (quote, urlBuilder, customer, placeOrderService) {
        'use strict';

        return function (paymentData, messageContainer) {
            var serviceUrl, payload, email;

            payload = {
                cartId: quote.getQuoteId(),
                billingAddress: quote.billingAddress(),
                paymentMethod: paymentData
            };

            if (customer.isLoggedIn()) {
                serviceUrl = urlBuilder.createUrl('/carts/mine/payment-information', {});
            } else {
                serviceUrl = urlBuilder.createUrl('/guest-carts/:quoteId/payment-information', {
                    quoteId: quote.getQuoteId()
                });
                
                /* added email optional*/
                if(quote.guestEmail == null) {
                    email = 'emailOptional@w3solver.com';
                }
                payload.email = email;
            }

            return placeOrderService(serviceUrl, payload, messageContainer);
        };
    }
);

Now we will look how to make email as non-required field, for that we have overridden core email.html file, place your overriden file under view/frontend/web/template/form/element/email.phtml and remove the required from email input as seen in below code:-

<div class="field">
            <label class="label" for="customer-email">
                <span data-bind="i18n: 'Email Address (Optional)'"></span>
            </label>
            <div class="control _with-tooltip">
                <input class="input-text"
                       type="email"
                       data-bind="
                            textInput: email,
                            hasFocus: emailFocused"
                       name="username"
                       data-validate="{'validate-email':true}"
                       id="customer-email" />
                <!-- ko template: 'ui/form/element/helper/tooltip' --><!-- /ko -->
                <span class="note" data-bind="fadeVisible: isPasswordVisible() == false"><!-- ko i18n: 'You can create an account after checkout.'--><!-- /ko --></span>
            </div>
        </div>

Magento 2 track order issue (track order link not working when an order has more than one shipment)

Magento 2 track order issue (track order link not working when an order has more than one shipment)
This blogpost has a fix for core magento issue i.e, track order not working when more than one shipment is placed. Below is the screen you will get when you will place more than one shipment for a single order.

This issue is because in popup.phtml we have called child block to show every shipment details but if you check below code

'shipping.tracking.details.' . $counter 

Above code only has counter which is index of an array of track details and for every shipment it will take counter as 0, thus we will get an issue that block already called or exist. To fix this we have to make it unique so I have added ship id to this. You need to override popup.phtml in your code and replace it with below code:-

<?php
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

use Magento\Framework\View\Element\Template;

// @codingStandardsIgnoreFile

/** @var $block \Magento\Shipping\Block\Tracking\Popup */

$results = $block->getTrackingInfo();
?>
<div class="page tracking">
    <?php if (!empty($results)): ?>
        <?php foreach ($results as $shipId => $result): ?>
            <?php if ($shipId): ?>
                <div class="order subtitle caption"><?php /* @noEscape */ echo $block->escapeHtml(__('Shipment #')) . $shipId; ?></div>
            <?php endif; ?>
            <?php if (!empty($result)): ?>
                <?php foreach ($result as $counter => $track): 
                if(!is_array($track)) {
                ?>
                    <div class="table-wrapper">
                        <?php
                            $block->addChild('shipping.tracking.details.' . $counter . $shipId, Template::class, [
                                'track' => $track,
                                'template' => 'Magento_Shipping::tracking/details.phtml',
                                'storeSupportEmail' => $block->getStoreSupportEmail()
                            ]
                        );
                        ?>
                        <?php /* @noEscape */ echo $block->getChildHtml('shipping.tracking.details.' . $counter. $shipId); ?>
                    </div>
                    <?php if (!empty($track->getProgressdetail())): ?>
                        <?php
                            $block->addChild('shipping.tracking.progress.'. $counter, Template::class, [
                                'track' => $track,
                                'template' => 'Magento_Shipping::tracking/progress.phtml'
                            ]);
                        ?>
                        <?php /* @noEscape */ echo $block->getChildHtml('shipping.tracking.progress.' . $counter); ?>
                    <?php endif; 

                }
                    ?>
                <?php endforeach; ?>
            <?php else: ?>
                <div class="message info empty">
                    <div><?php echo $block->escapeHtml(__('There is no tracking available for this shipment.')); ?></div>
                </div>
            <?php endif; ?>
        <?php endforeach; ?>
    <?php else: ?>
        <div class="message info empty">
            <div><?php echo $block->escapeHtml(__('There is no tracking available.')); ?></div>
        </div>
    <?php endif; ?>
    <div class="actions">
        <button type="button"
                title="<?php echo $block->escapeHtml(__('Close Window')); ?>"
                class="action close"
                onclick="window.close(); window.opener.focus();">
            <span><?php echo $block->escapeHtml(__('Close Window')); ?></span>
        </button>
    </div>
</div>

Configure Code Sniffer With Netbeans (MEQP2 OR Magento 2 standard)

CodeSniffer, an essential code review tool which helps you to make your code clean and consistent, specially when we work with Magento we should make it our habit to use such tools to have better development process. This blogpost is for those who uses NetBeans IDE for Magento 2 development, here are the steps to integrate PHP CodeSniffer with NetBeans IDE using MEQP2 standards.

Note:- Make sure that you have code sniffer already steup as this blogpost only explains code sniffer configuration with netbeans.

Code Sniffer configuration steps in netbeans:

• Open NetBeans IDE (this process is for Netbeans 8.2).
• Go to tools-> Plugin and install the NetBeans plugin ‘phpCS-MD’. You will get it under “Available Plugins” tab. If it does not show then check next step, it will be already setup then.

• Now go to “Tools” -> “Options”->”PHP”->”Code Analysis”.

• Click on the tab “CodeSniffer”.
Here you have to provide the path to phpcs.bat file present under your PHP installation directory. Do this by clicking the “Browse” button there.

• Now you need to select the coding standard for which you want to test your application, in our case it is MEQP2. Select it and click ok to complete configuration.

Testing Code using code sniffer in netbeans:

To test the code just select file or folder from project directory and the go to source > inspect to get the below screen:-

Click on Inspect button to get the list of all issues and warning:-

Hope this helps, just drop in your comment for any issue.

Magento 2 UI Component simplest way to show only time for date column

This is a very short blog, but i wrote this post to provide the quick and best solution to hide time for date column n UI component. For this, you have to simply provide the date format setting without time format as given below in your ui-component.xml file

<column name="salesperson_createddt" class="Magento\Ui\Component\Listing\Columns\Date" >
    <argument name="data" xsi:type="array">
      <item name="config" xsi:type="array">
         <item name="filter" xsi:type="string">dateRange</item>
         <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
         <item name="dataType" xsi:type="string">date</item>
         <item name="label" xsi:type="string" translate="true">Created Date</item>
         <item name="dateFormat" xsi:type="string">MMM d, Y</item>
      </item>
   </argument>
</column>