LaravelからSalesforceへのデータ連携入門

# これはなに

LaravelからSalesforceにデータを同期する方法について紹介します。

別システムで管理してるデータをSalesforceに同期させることで、
営業やカスタマーサクセスなど他部署でのデータ活用が可能になり、業務効率化が期待できます。

そういうわけでやってくれと依頼されたのでやってみました。
Salesforce何もわからない状況から始まってようやくデータ同期の道筋ができた状況です。

## Salesforceにデータを同期する方法

別システムからSalesforceにデータを同期する方法にはいくつかありますが、
今回はおそらく一番一般的であろうREST APIを使います。

## Lightning Platform REST API

ドキュメントはこちら https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/intro_what_is_rest_api.htm

Lightning Platformってなに？ って感じですが、
最近のSalesforceプラットフォームのことで、昔のはSalesforce Classicというようです。
あまり気にせず行きましょう。

APIいろいろあるんですが、正直あまり見てません。
次に紹介するラッパーが便利だったからです。

## LaravelからSalesfoceにデータ同期するのに便利なパッケージ

LaravelからSalesfoce REST APIを便利に使うパッケージを探したところ
`omniphx/forrest`というものを見つかりました。

`Salesforce REST API Client for Laravel`
https://github.com/omniphx/forrest
![Forrest.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268801/892cb5cc-8aab-7e26-6fea-79960dbac179.png)

## Forrestの使い方

### Installation
Laravel6前提です

`composer.json`に

```
"omniphx/forrest": "2.*"
```
を追加して

```bash
composer update
```

インストールが完了すると`config/app.php`に以下のコードが追加されるようです。
私は追加されてなかったので自分で追加しました。
それぞれ`providers`と`aliases`に追加します。

```config/app.php
Omniphx\Forrest\Providers\Laravel\ForrestServiceProvider::class
'Forrest' => Omniphx\Forrest\Providers\Laravel\Facades\Forrest::class
```

これでLaravelの他のFacade(LogやCache)と同じように、コードのどこでも`Forrest`と書くとFacadeが実行できるようになりました。

### 設定ファイルの外出しと環境変数

このままではforrestの設定ファイルがvender配下にあって変更できないので、
以下のコマンドで`config/`配下に設定ファイルを移動させます。

```
php artisan vendor:publish
```
他にも設定ファイルの外出しができる場合はどれを外出しするか聞かれますが、configってやつを選べばよかったと記憶してます。

次に`.env`に環境変数を追加します。

```ini:.env
CONSUMER_KEY=123455
CONSUMER_SECRET=ABCDEF
CALLBACK_URI=https://test.app/callback
LOGIN_URL=https://login.salesforce.com
USERNAME=mattjmitchener@gmail.com
PASSWORD=password123
```
どんな値を設定するかはあとで説明します。

私の場合は、`PASSWORD`みたいな変数名だと他の変数と被りそうだったので
config/forrest.phpをいじって`SALESFORCE_PASSWORD`のように名前を変えました。

## 認証方法の設定


Salesforceに接続する方法はOAuthとパスワード認証の２種類ありますが、
今回はパスワード認証を使用します。

```diff:config/forrest.php

    /*
     * Options include WebServer, UserPassword, and UserPasswordSoap
     */
-    'authentication' => 'WebServer',
+    'authentication' => 'UserPassword',
```

アクセストークンの保存先をキャッシュにしておきます。　

```diff:config/forrest.php

    /*
     * Where do you want to store access tokens fetched from Salesforce
     */
    'storage'        => [
-        'type'          => 'session', // 'session' or 'cache' are the two options
+        'type'          => 'cache', // 'session' or 'cache' are the two options
```

### Salesforceに接続アプリケーションを追加する
今回はパスワード認証を使う前提で進めます。
1. Salsesforceプラットフォームにログインする
2. 設定 > アプリケーションマネージャ(左のメニュー） からアプリケーションマネージャの画面を表示する
3.  `新規接続アプリケーション`をクリック
4.  必須項目を埋める

接続アプリケーションができると、CONSUMER_KEYなどが発行されるのでこれをコピーして.envに貼り付けます。

```ini:.env
CONSUMER_KEY=123455
CONSUMER_SECRET=ABCDEF
# CALLBACK_URI=https://test.app/callback パスワード認証の場合これはいらない
LOGIN_URL=https://test.salesforce.com # 本番環境の場合はhttps://login.salesforce.com
USERNAME=mattjmitchener@gmail.com # Salesforceのログインアカウントと同じ
PASSWORD=password123
```

## 使い方


### 認証
認証はこれだけです。

```php
Forrest::authenticate();
```


### データ取得
データの取得にはSQLライクなSOQLというのを使います。

```php
Forrest::query('SELECT Id FROM Account');
```
```php
{
    "totalSize": 2,
    "done": true,
    "records": [
        {
            "attributes": {
                "type": "Account",
                "url": "\/services\/data\/v30.0\/sobjects\/Account\/001i000000xxx"
            },
            "Id": "001i000000xxx"
        },
        {
            "attributes": {
                "type": "Account",
                "url": "\/services\/data\/v30.0\/sobjects\/Account\/001i000000xxx"
            },
            "Id": "001i000000xxx"
        }
    ]
}
```
１度に2000件しか取れないという制限がありますが、`次のデータ`を取得する方法も簡単です。詳しくは[README](https://github.com/omniphx/forrest#query-a-record)を見てください。

ドキュメント： https://developer.salesforce.com/docs/atlas.ja-jp.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select.htm

### データ同期(Upsert)
データの同期は以下の`patch`メソッドを使います。

```php
$externalId = 'XYZ1234';

Forrest::sobjects('Account/External_Id__c/' + $externalId, [
    'method' => 'patch',
    'body'   => [
        'Name'  => 'Dunder Mifflin',
        'Phone' => '555-555-5555'
    ]
]);
```

Upsert(Salesfoce上にすでにデータがあれば上書き、なければ新規作成) をこれだけで実現できます。

そのためにはSalesforce上のオブジェクトに、`外部ID`属性のカラムを追加する必要があります。

# 外部IDとpatchを使った同期の例

Salesforceのオブジェクトには、外部IDという属性のカラムを追加することができます。
外部システムからは外部キーを使って簡単にオブジェクトを特定し、書き込むことができます。
外部IDには、外部システム上のオブジェクトのプライマリーキーを使うと、
外部システム上のレコードとSalesforce上のレコードが一対一の関係になって都合がいいと思います。

### 前提

Salesforceに同期したいLarave上のオブジェクトをUserというEloquentモデルだとします。
Userモデルはidというプライマリーキーを持つものとします。

### Salesforce側の準備

1. `設定 > オブジェクトマネージャ > 新規` からUserオブジェクトを作ります
2. `設定 > オブジェクトマネージャ >User > 項目とリレーション > 新規`から数値型の `user_id`という項目を作ります。
3. 作成時のオプションで`ユニーク``外部ID`というフラグをONにします。
4. これでオブジェクトの型ができました。

### Laravel側からUpsertする


```php
$user_id = $user->id;

Forrest::sobjects('User__c/user_id__c/' . $user_id, [
    'method' => 'patch',
    'body'   => [
        'Name'  => 'This is new user!',
    ]
]);
```

`User__c`は、UserオブジェクトのAPI参照名です。
`user_id__c`は、user_idカラムの項目名です。
`$user_id`はあなたが書き込みたいUserオブジェクトのIDです。
Salesforce上に`$user->id`と一致するuser_idを持つUserオブジェクトが存在しなければ新規追加し、存在すれば上書きをしてくれます。

patchメソッドを使うことで、データの同期を簡単に行うことができました。

#### 参照の同期について

Salesforceには、オブジェクトの参照関係があり、「どのオブジェクトを参照するか」という情報の同期が必要なケースがあります。
どのオブジェクトを参照するかの指定も外部IDを使えば簡単にできます。
詳しくは述べませんが、以下の資料が参考になるはずです。
https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/dome_upsert.htm


# まとめ

- LaravelからSalesforceへのデータ同期は`Forrest`が便利です
- Salesforceの`外部ID`という仕組みを使えばデータ同期(Upsert)が簡単です

以上、参考になりましたら幸いです。