Magento2でのWebAPIの作り方

[Magento Advent Calender 2017](https://adventar.org/calendars/2349) の6日目の記事です。

今回の記事では、Magento2でのWebAPIの作成方法を紹介していきたいと思います。

聞くところによると、Magento2でのWebAPIの作成はMagento1に比べて非常に簡単になったようです。(私は1系の経験が乏しいので詳しくは分かりませんが。。。)

簡単になったと言っても、初めて実装してみると、いくつか難解な部分が散見されたので、今回はWebAPI実装の際に気をつけなければいけない点を抑えながら、Magento2上でのWebAPI実装方法を解説していきたいと思います。

まずはじめに、Magento2ではMagento独自のAPIフレームワークを用いることで、容易に外部サービスと連携させることが出来るようになっています。

例えば、MagentoのAPIフレームワークを用いることで以下のようなことができるようになります。

・ショッピングアプリ開発
・CRMやERPシステムとの連携
・AJAXを利用したMagentoのリソースを扱うことができるJavascriptウィジェットの作成

今回の記事では３つ目の使用方法を参考にして、投げられた値を計算して返すというシンプルなAPIを実装し、AJAXを利用してAPIをフロントエンドからコールしてみたいと思います。

##具体的な流れ

1. モジュールの基礎ファイル作成
2. APIの設定(etc/webapi.xml)
3. インターフェイスの作成 
4. インターフェイスの定義(etc/di.xml)
5. モデルの実装
6. フロントエンド側の実装


## 1. モジュールの基礎ファイル作成
まずは、皆さんご存知の通りモジュールの基礎となるファイルを作成してあげます。
・registration.php

```php
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Test_Ajaxapi',
    __DIR__
);
```
・etc/module.xml

```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="Test_Ajaxapi" setup_version="1.0.0"/>
</config>
```

## 2. APIの設定
Magento2でのWebAPIの設定はwebapi.xmlというファイルで行います。このファイルにコール先のURLとコールされた時に呼び出される関数を定義してあげます。
また、resource部分では、以下の三つのユーザータイプに合わせた権限設定ができます。

・Administrator or Integration
・Customer
・Guest User

今回はフロントエンドから誰でもアクセスできるAPIを作成したいので、Guest Userでも利用できるよう anonymousを指定します。
また、使用者の権限を制限したい場合はこちらのDevDocsを参考にすると良いと思います。
[Magent2 DevDocs Web API authentication overview](http://devdocs.magento.com/guides/v2.0/get-started/authentication/gs-authentication.html)

```xml
<?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/twice" method="POST">
        <service class="Test\Ajaxapi\Api\TwiceInterface" method="getTwice"/>
        <resources>
            <resource ref="anonymous" />
        </resources>
    </route>
</routes>
```
method部分にコールしたい関数を指定してあげるのですが、大文字小文字に注意して正しく入力しましょう。(開発中においては常に気をつけないといけないことではありますが。。。)

本来のClassに書かれているmethod名と、webapi.xmlで指定するmethodの大文字小文字をちぐはぐにしてしまった場合、なんと正しくメソッドは実行されてしまうのですが、指定のメソッドの実行後の段階でエラーが発生してしまうために、原因究明に大変手間取ってしまいます。。。

##3. インターフェイスの作成
POSTされたデータを計算するための、インターフェイスを定義します。

**PHPDocをしっかり書かないと動きません！！**

Magento2では、WebAPI上でのデータの送信時に、PHPDocをもとにPHPオブジェクトからJSONへの変換・クラスの生成などを動的に行ってくれるため、PHPDocに正しい情報が記載されていないとエラーが出てしまいます。

PHPDocに注意して、入力データに計算を施すインターフェイスTwiceInterface.phpを作成します。

```php
<?php

namespace Test\Ajaxapi\Api;

/**
 * Interface TwiceInterface
 * @package Test\Ajaxapi\Api
 */
interface TwiceInterface
{
    /**
     * @param \Test\Ajaxapi\Model\Webapi\Data\Number $number
     * @return int
     */
    public function sumNumber(
        \Test\Ajaxapi\Model\Webapi\Data\Number $number
    );
}
```

また、Magento2ではAPIを用いてPOSTするデータはオブジェクトとして扱う必要があります。

したがって計算用のインターフェイスとは別にもうひとつ、POSTされたデータを扱うためのインターフェイスNumberInterface.phpを定義します。


```php
<?php

namespace Test\Ajaxapi\Api\Data;

/**
 * Interface NumberInterface
 * @package Test\Ajaxapi\Api\Data
 */
interface NumberInterface
{
    /**
     * @return int
     */
    public function getNum();

    /**
     * @param int $number
     * @return self
     */
    public function setNum($number);
}
```

##4. インターフェイスの定義(etc/di.xml)
使用するモデルとインターフェイスの依存関係をdi.xmlを用いて定義します。webapi.xmlで指定したClassと、そのClassを実装するモデルをpreferenceで指定します。

```xml
<?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="Test\Ajaxapi\Api\Data\NumberInterface" type="Test\Ajaxapi\Model\Webapi\Data\Number" />
    <preference for="Test\Ajaxapi\Api\TwiceInterface" type="Test\Ajaxapi\Model\Webapi\Twice" />
</config>
```
Test\Ajaxapi\Model\Webapi\TwiceはPOSTされた数字を計算するためのクラス。
Test\Ajaxapi\Model\Webapi\Data\NumberはPOSTデータを扱うためのクラスです。

##5. モデルの実装
さきほどの4.で定義した各インターフェイスを実装しているモデルをModel/Webapi配下に作成していきます。
Twiceクラスでは、POSTされた数値を二倍するメソッドが定義されています。

```php
<?php

namespace Test\Ajaxapi\Model\Webapi;

/**
 * Class Twice
 * @package Test\Ajaxapi\Model\Webapi
 */
class Twice implements \Test\Ajaxapi\Api\TwiceInterface
{

    /**
     * @param \Test\Ajaxapi\Model\Webapi\Data\Number $number
     * @return int
     */
    public function sumNumber(
        \Test\Ajaxapi\Model\Webapi\Data\Number $number
    ){
        $num = $number->getNum();
        $twice = 2 * $num;
        return $twice;
    }
}
```
Numberクラスでは、POSTされるデータを扱うためのセッター・ゲッターを定義しています。

```php
<?php

namespace Test\Ajaxapi\Model\Webapi\Data;

/**
 * Class Number
 * @package Test\Ajaxapi\Model\Webapi\Data
 */
class Number implements \Test\Ajaxapi\Api\Data\NumberInterface
{
    /**
     * @var
     */
    protected $number;

    /**
     * @return int
     */
    public function getNum(){
        return $this->number;
    }

    /**
     * @param int $number
     * @return int
     */
    public function setNum($number){
        $this->number = $number;
        return $this->number;
    }
}
```

##6. フロントエンド側の実装
APIをコールするためのtestsum.jsを作成します。

POSTデータは以下のようなフォーマットでpostDataに格納します。
こうすることで、さきほど実装したNumberオブジェクトのgetNum()でPOSTしたいintnumという値をMagento側で取得することができるようになります。

```js
define(
    [
        'jquery',
        'mage/url'
    ],
    function ($j, url) {

        return {
            twiceNumber: function (intnum) {

                alert(intnum);
                var postFile = url.build('rest/V1/twice/number'); 
                var postData = {
                    'number': {'num': intnum}
                };
                $j.ajax({
                    type: "POST",
                    accept: 'application/json',
                    url: postFile,  // URL
                    data: JSON.stringify(postData),
                    dataType: 'json',
                    contentType: "application/json; charset=utf-8",
                    success: function (data) {
                        alert(data);
                    }
                });
            }
        }
    }
);
```

requirejs-config.jsを利用して、マッピングを行います。こうすることで、テンプレート上では、testSumと指定するだけで、Test_Api/js/testsumを呼び出すことができます。

```js
var config = {
    map: {
        '*': {
            testSum: 'Test_Ajaxapi/js/testsum'
        }
    }
};
```

数値の入力が出来るようにテンプレートajax.phtmlを作成します。
数値の入力後、ボタンを押すことでAPIがコールされ、入力値の二倍の値が返ってきます。

```html
<h1>TEST</h1>
<input id="intnum" type="number" />
<h1><a id="button">Button</a></h1>

<script type="text/javascript">
    document.getElementById("button").onclick = function(){
        require([
            'jquery',
            'testSum'
        ], function ($j,test) {
            var intnum = $j("#intnum").val();
            test.twiceNumber(intnum);
        });
    };
</script>
```

トップページにテンプレートブロックを表示させるために、layout.xmlで指定します。

```xml
<?xml version="1.0"?>

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="main.content">
            <block class="Magento\Framework\View\Element\Template" name="test.ajax" template="Test_Api::ajax.phtml" before="-"/>
        </referenceContainer>
    </body>
</page>
```

##まとめ

以上で解説は終了です。

今回作成したエクステンションはGithubで公開しておきます。
https://github.com/Taku-Yamashita/magento2-webapi

Magento2でのWebAPIの実装は、Magentoが裏で色々頑張ってくれている分非常にシンプルに作成することができるようになっていますが、一方で開発者がMagentoの意図しない実装を行ってしまうと大きなトラップにハマってしまいます。(PHPDocの記述やPOSTデータの扱い)

次回の7日目の記事はMariaさんで[Understanding Consumer Psychology: An impact to e-Commerce optimization](https://principle-works.jp/blog/understanding-consumer-psychology)です。