CakePHP3.3でcURLを使用してkintone APIからデータを取得する

# CakePHP3.3でcURLを使用してkintone APIからデータを取得する

## はじめに
私は駆け出しエンジニアです。
今回業務の中で、cURLを使用してkintone APIからデータを取得＆アプリ側で保存する実装を行いました。
どなたかの参考になればと思い、その内容をアウトプットさせていただきます。

改善点等ありましたら、優しくご指摘いただければ幸いです。
では本題に移ります。

## 1. 概要と目的

この記事では、CakePHPフレームワークを用いたバッチ処理の一環として、cURLを使用してkintone APIからデータを定期的に取得し、そのデータをデータベースに保存する方法を解説します。
この手法は、外部サービスからのデータを一定間隔（バッチ処理）で取り込み、ローカルデータベースに統合するという一般的な問題に対する解決策となります。


## 結論

以下は私が実装した全体のコードです。
内容はご自身の状況に合わせて適宜変更して使用してください。
```php
<?php
namespace App\Shell;

use Cake\Console\Shell;
use Cake\Datasource\ConnectionManager;
use Cake\Database\Exception;

class MyCustomShell extends Shell
{
    /* デバッグモード */
    private $debug = false;

    /**
     * 初期処理
     */
    public function initialize()
    {
        set_time_limit(3600);
        ini_set('memory_limit', '-1');
        parent::initialize();
        //使用するテーブル名を入れてください
        $this->loadModel('MyModel1');
    }

    public function main()
    {
        $this->__echo('MyCustomShell Start', 'info');

        try {
            $this->__task();
        } catch (\Throwable $e) {
            $this->__echo($e, 'error');
            $this->__echo('MyCustomShell Error End', 'info');
            return $this::CODE_ERROR;
        }

        $this->__echo('MyCustomShell End', 'info');
        return $this::CODE_SUCCESS;
    }

    /**
     * curlでkintone API へgetリクエストを送信
     */
    private function __task()
    {
        $ch = curl_init();

        $headers = [
            'X-Cybozu-API-Token: [Your_API_Token]'
        ];

        curl_setopt($ch, CURLOPT_URL, 'https://(サブドメイン名).cybozu.com/k/v1/records.json?app=（アプリID）');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $response = curl_exec($ch);

        if ($response === false) {
            throw new Exception('Curl error: ' . curl_error($ch));
        }
        
        curl_close($ch);

        $kintoneData = json_decode($response, true);
        $this->__echo('Kintone data fetched', 'info');

        $this->connection = ConnectionManager::get('default');
        $this->connection->begin();
        try {
            foreach($kintoneData['records'] as $record){
                $myModel1Entity = $this->MyModel1->newEntity();
                
                // データ処理部分
                // ここでは、kintoneから取得した各レコードをCakePHPのエンティティにマッピングします。
                // 詳細なマッピングはアプリケーションのデータモデルに依存するため、この部分は各自の状況に合わせて調整してください。

                $this->MyModel1->save($myModel1Entity);
            }
            $this->connection->commit();
        } catch(\Throwable $e) {
            $this->__echo($e, 'error');
            $this->log("【レコード更新時のエラー】データの更新処理に失敗しました");
            $this->__echo('MyCustomShell Error End', 'info');
            $this->connection->rollback();
            return $this::CODE_ERROR;
        }
    }

    private function __echo($arg, $loglevel) {
        if ($this->debug) {
            $this->out($this->name . ' ' . $arg);
        } else {
            $this->log($this->name . ' ' . $arg, $loglevel);
        }
    }
}

```

## 2. 使用技術

- **CakePHP**: PHPで書かれたフレームワークで、MVCパターンに基づいています。
- **cURL**: HTTPリクエストを扱えるPHPのライブラリです。
- **kintone REST API**: データ連携のための API です。レコードの取得／登録／更新／削除や、ファイルのアップロード／ダウンロードのAPIがあります。また、アプリの情報取得やフォームの情報取得の API もあります。（https://cybozu.dev/ja/kintone/docs/rest-api/）

## 3. コード解説

今回の実装内容は、cURLを使ってkintone APIにリクエストを送信し、レスポンスをデコードしてデータベースに保存することです。

それでは、主要な部分について解説していきます。

まず、`__task()`メソッドでkintone APIへリクエストを送信しています。HTTPヘッダには`X-Cybozu-API-Token`を含めることでAPIを認証します。
その後、レスポンスはJSON形式なので、PHPの`json_decode`関数を使用して配列に変換します。

```php
$headers = [
    'X-Cybozu-API-Token: YOUR_TOKEN'
];

curl_setopt($ch, CURLOPT_URL, 'https://(サブドメイン名).cybozu.com/k/v1/records.json?app=（アプリID）');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$response = curl_exec($ch);

if ($response === false) {
    throw new Exception('Curl error: ' . curl_error($ch));
}

curl_close($ch);

$kintoneData = json_decode($response, true);
```
##

・ここでは、json_decode関数を使用してkintoneから取得したJSON形式のレスポンスをPHPの配列に変換しています。第2引数にtrueを指定することで、連想配列としてデコードされます。

・次に、$this->__echo('Kintone data fetched', 'info');の行で、情報ログとして"kintone data fetched"を出力しています。これはデータ取得が正常に行われたことを示すメッセージです。

・ConnectionManager::get('default')でデフォルトのデータベース接続を取得し、begin()メソッドを呼び出してトランザクションを開始しています。
トランザクションを使用することで、一連のデータベース操作が全て正常に行われた場合のみデータベースの変更を確定することができます。
何か問題が発生した場合、ロールバックを行い、データベースの状態を一貫したものに保つことが可能です。

```php
$kintoneData = json_decode($response, true);
$this->__echo('Kintone data fetched', 'info');

$this->connection = ConnectionManager::get('default');
$this->connection->begin();
```

##

### try-catchブロックについて
foreachループを使用して、kintoneData['records']の各要素（レコード）に対して処理を行っています。こちらの処理については、仕様に応じて変更してください。

```php
 try {
            foreach($kintoneData['records'] as $record){
                $myModel1Entity = $this->MyModel1->newEntity();
                
                // データ処理部分
                // ここでは、kintoneから取得した各レコードをCakePHPのエンティティにマッピングします。
                // 詳細なマッピングはアプリケーションのデータモデルに依存するため、この部分は各自の状況に合わせて調整してください。

                $this->MyModel1->save($myModel1Entity);
                
            }
            $this->connection->commit();
        } catch(\Throwable $e) {
            $this->__echo($e, 'error');
            $this->log("【レコード更新時のエラー】データの更新処理に失敗しました");
            $this->__echo('MyCustomShell Error End', 'info');
            $this->connection->rollback();
            return $this::CODE_ERROR;
```

### 4. エラーハンドリング

このスクリプトではエラーハンドリングも行っています。try/catchブロックを使用してエラーをキャプチャし、エラーが発生した場合にはロールバックを行います。
これにより、何か問題が発生した場合でもデータベースの状態を一貫したものに保つことができます。

### 5. デバッグとログ

このスクリプトでは、デバッグモードとログ出力を使用しています。デバッグモードは開発時に便利で、ログ出力は問題の特定と解決に役立ちます。


### 6. 新人エンジニアとしての振り返りと改善の見通し

私が実装したこのプロジェクトでは、「kintone APIからの定期的なデータ取得とそのローカルデータベースへの保存する」という課題をうまく解決することができました。

改めて私が学んだことは、完璧なシステムは存在しないということです。
システムは常に改良を重ねるべきもので、それが我々エンジニアの任務であるという認識を新たにしました。
たとえば、エラーハンドリングをさらに強化することで、一時的な通信エラーなどに対応できるようにするためのリトライロジックの追加などがあります。

また、大量のデータを取り扱う場合には、システムのパフォーマンスを最適化するためにデータ取得と保存を非同期に行うなどの工夫が必要になることも学びました。
これらの改良により、より効率的で信頼性の高いシステムを作り上げることができます。

このプロジェクトは私の成長の一部であり、今後も改善と学習を続けることで、より良いエンジニアになることを目指していきます。