SalesforceのREST APIのCRUDをphpで実現する

この記事は [マイナビ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/mynavi) 20日目の記事となります。

#概要
phpからSalesforceのデータオブジェクトに対し、CRUDの処理を試した際のコードについて記載します。

###Salesforceで用意されているAPI

Salesforceで用意されているAPIについては下記の記事が参考になります。
https://base.terrasky.co.jp/articles/3KYyA

今回はSalesforceの**REST API**を使用し標準オブジェクトの一つの取引先（Account）をphpでデータ操作する際の一例を説明します。
※接続するためのSalesforce上での事前準備等は省略します。


#実装
##用意するクラス


```php:salesforce.php
<?php
require_once 'curl.php';

/**
 * Salesforce APIを実行するクラス
 */
class Salesforce{
    public static $properties = [];
    const API_KEYS = [
        'client_id'=> '(※コンシューマキー)',
        'username'=> '(※SalesforceログインID)',
        'password'=> '(※SalesforceログインPW)',
    ];

    /**
     * oauthを取得し返す
     *
     * @return array
     */
    public static function oauth(){
        #oauth取得済みの場合、設定されているoauthを返す
        if(self::property('oauth')){
            return self::property('oauth');
        }

        #oauth取得処理
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, 'https://login.salesforce.com/services/oauth2/token?grant_type=password'
                                            .'&client_id='.self::API_KEYS['client_id']
                                            .'&username='. self::API_KEYS['username']
                                            .'&password='. self::API_KEYS['password']
                    );
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        $oauth_data = Curl::execute($curl);
        $oauth_data = json_decode($oauth_data, true);
        
        #oauthを設定
        self::property('oauth', $oauth_data);

        return $oauth_data;
    }

    /**
     * 読み込み処理（取引先の単一のデータの取得）
     *
     * @param [string] $id
     * @return array
     */
    public static function read($id){
        if(empty($id)) return;

        $oauth = self::oauth();

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, "https://ap8.salesforce.com/services/data/v50.0/query/?q=SELECT+Id,+Name+FROM+Account+WHERE+Id='$id'");
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HTTPHEADER, [
            'Authorization: '.$oauth['token_type'].' '.$oauth['access_token'],
        ]);

        $data = Curl::execute($curl);
        return json_decode($data, true);
    }

    /**
     * 更新処理（取引先の単一のデータの更新）
     *
     * @param [string] $id
     * @param [array] $updates
     * @return array
     */
    public static function update($id, $updates){
        if(empty($id) or empty($updates)) return;

        $oauth = self::oauth();

        $json = json_encode($updates);
        $json = preg_replace('/\\\\\//', '/', $json);

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, "https://ap8.salesforce.com/services/data/v50.0/sobjects/Account/$id");
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PATCH');
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $json);
        curl_setopt($curl, CURLOPT_HTTPHEADER, [
            'Authorization: '.$oauth['token_type'].' '.$oauth['access_token'],
            'Content-Type: application/json;charset=UTF-8',
        ]);

        $data = Curl::execute($curl);
        return json_decode($data, true);
    }

    /**
     * 登録処理（取引先のデータの登録）
     *
     * @param [array] $creates
     * @return array
     */
    public static function create($creates){
        if(empty($creates)) return;
        $oauth = self::oauth();

        $json = json_encode($creates);
        $json = preg_replace('/\\\\\//', '/', $json);

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, "https://ap8.salesforce.com/services/data/v50.0/sobjects/Account/Id");
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $json);
        curl_setopt($curl, CURLOPT_HTTPHEADER, [
            'Authorization: '.$oauth['token_type'].' '.$oauth['access_token'],
            'Content-Type: application/json;charset=UTF-8',
        ]);

        $data = Curl::execute($curl);
        return json_decode($data, true);
    }

    /**
     * 削除処理（取引先のデータの削除）
     *
     * @param [string] $id
     * @return array
     */
    public static function delete($id){
        if(empty($id)) return;
        $oauth = self::oauth();

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, "https://ap8.salesforce.com/services/data/v50.0/sobjects/Account/$id");
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_HTTPHEADER, [
            'Authorization: '.$oauth['token_type'].' '.$oauth['access_token'],
        ]);

        $data = Curl::execute($curl);
        return json_decode($data, true);
    }

    /**
     * getter,setter
     *
     * @param [string] $name
     * @param $value
     * @return
     */
    public static function property($name, $value = null) {
        if (func_num_args() > 1) {
            return self::$properties[$name] = $value;
        } else {
            return isset(self::$properties[$name]) ? self::$properties[$name] : null;
        }
    }
}

```
curl.phpはこちらの[ライブラリ](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/Curl/Util.php)を参考にしています。今回の記事では割愛してます。

##CREATE


```php:create.php
require_once 'salesforce.php';

#登録処理
print_r(Salesforce::create(['Name'=>'test取引先1']));
/* 成功した場合
Array
(
    [id] => 0010o00002nJtDUMMY
    [success] => 1
    [errors] => Array
        (
        )

)
*/
```
今回はNameだけの指定ですが、配列内で他の項目を追加すれば一度に他の項目を登録できます。

##READ
```php:read.php
require_once 'salesforce.php';

#読み込み処理
print_r(Salesforce::read('0010o00002nJtDUMMY'));
/* 成功した場合
Array
(
    [totalSize] => 1
    [done] => 1
    [records] => Array
        (
            [0] => Array
                (
                    [attributes] => Array
                        (
                            [type] => Account
                            [url] => /services/data/v50.0/sobjects/Account/0010o00002nJtDUMMY
                        )

                    [Id] => 0010o00002nJtDUMMY
                    [Name] => test取引先1
                )

        )

)
*/
```

read処理の内部に書いてありますがクエリストリングの値にSOQL（Salesforce内のDB言語）を使用しデータの取得を行っています。書き方次第で複数のデータ取得も可能です。

##UPDATE
```php:update.php
require_once 'salesforce.php';

#更新処理
print_r(Salesforce::update('0010o00002nJtDUMMY',['Name'=>'更新test取引先1']));
/* 成功した場合responseはなし */
```

こちらもcreateと同じ様に配列内で他の項目を追加すれば一度に他の項目を更新できます。
更新に失敗した場合はresponseがあります。

##DELETE
```php:delete.php
require_once 'salesforce.php';

#削除処理
print_r(Salesforce::delete('0010o00002nJtDUMMY'));
/* 成功した場合responseはなし */
```
削除に失敗した場合はresponseがあります。


#まとめ・感想
今回は入門程度でしか触れていませんが、responceが速いので外部のWebアプリなどのリアルタイム連携も結構行けるのではと感じました。大量のデータを登録する系は専用のBulk APIが向いていそうです。


最初、read-APIから入ってSOQLでさっと実現できたので他の処理も同様に行けるかと思ったのですが全く違う処理だったのがハマりポイントでした。（APIのSOQLではデータ操作が対応していない。）

今後Slackとの話も上がったので、API周りも色々な進化が見れそうですね。


#参考にさせていただいた記事・Salesforce開発者ガイド
- https://qiita.com/na0AaooQ/items/157c28a80948c4b97bed
- https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/dome_upsert.htm
- https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/dome_delete_record.htm