【Laravel】Backlogから課題を取得してcsvで出力する方法

## はじめに

さて、今年のアドベントカレンダーは何を書こうかと、、、。
ちょうどLaravelでBacklogとNotionを連携する処理を書いていたので、それ関連で書こうと考えた。
本当はNotionに連携するところまで書こうと思っていたのだが、
まだ実装途中で時間がかかりそうなので、
**Backlogから値を取得してcsvで出力するまで**の記事を書くことにした。
プロジェクトの情報が漏洩しないように、外部の情報は、サニタイズして記述するので、
動かなかったらごめん🙇‍♀️

## 対象読者
- Web系エンジニアの人
- プログラミングやってる人
- Laravelやってる人
- Backlog使ってる人

## backlogとは？
チームのタスク管理ツールのこと。
URL：https://backlog.com/ja/

## 大まかな手順
1. Laravel環境構築
2. League\Csvのインストール
2. BacklogAPIキーの取得
2. 実装

## Laravel環境構築

### 使用技術
※ MySqlは現在は使われていないが、今後使用するかもしれないので、一応入れておく。
|  Name  |  version  |
| ---- | ---- |
|  PHP  |  8.1.12  |
|  Laravel  |  9.41.0  |
|  Laravel-sail  |  8.1  |
|  MySql  |  8.0  |
|  League/csv  |  9.8  |

とりあえずsailで環境構築。csvはLeague/csvで出力する。


### 事前準備
- Docker Desktop (Docker for Mac) をインストールしておく
公式：https://www.docker.com/products/docker-desktop/

- 手元のマシンに php@8.1 と composer 2.x を入れておく
brewでインストールするのが早い(brewのインストールとかは抜粋します。)
```
$ brew install php@8.1
$ brew install composer
```


### 1. 任意のディレクトリでLaravelアプリケーションを作成する
example-appの箇所は任意のプロジェクト名を入れる
```terminal
$ curl -s "https://laravel.build/example-app" | bash
```
### 2. ディレクトリを移動して、sailを立ち上げる
```
$ cd example-app

$ ./vendor/bin/sail up
```

#### エイリアスを作成すると下記だけでいける。
```
$ sail up -d
```

#### エイリアスの設定の仕方
bashの場合 →　.bashrc
zshの場合　→ .zshrc
.bashrcか、.zshrcに下記を記述する
```.zshrc
alias sail='[ -f sail ] && bash sail || bash vendor/bin/sail'
```

### 4. docker-compose.ymlを修正する
使わないサーバーを削除(mysqlは使用しないがなんとなく残している)
```docker-compose.yml
# For more information: https://laravel.com/docs/sail
version: '3'
services:
    app:
        build:
            context: ./vendor/laravel/sail/runtimes/8.1
            dockerfile: Dockerfile
            args:
                WWWGROUP: '${WWWGROUP}'
        image: sail-8.1/app
        extra_hosts:
            - 'host.docker.internal:host-gateway'
        ports:
            - '${APP_PORT:-80}:80'
            - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
        environment:
            WWWUSER: '${WWWUSER}'
            LARAVEL_SAIL: 1
            XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
            XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
        volumes:
            - '.:/var/www/html'
        networks:
            - sail
        depends_on:
            - mysql
    mysql:
        image: 'mysql/mysql-server:8.0'
        ports:
            - '${FORWARD_DB_PORT:-3306}:3306'
        environment:
            MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ROOT_HOST: "%"
            MYSQL_DATABASE: '${DB_DATABASE}'
            MYSQL_USER: '${DB_USERNAME}'
            MYSQL_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ALLOW_EMPTY_PASSWORD: 1
        volumes:
            - 'sail-mysql:/var/lib/mysql'
            - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
        networks:
            - sail
        healthcheck:
            test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"]
            retries: 3
            timeout: 5s
networks:
    sail:
        driver: bridge
volumes:
    sail-mysql:
        driver: local
```

servicesの名前を変更したので、.envにAPP_SERVICE="app"を記述。

```.env
APP_SERVICE="app"
```

ymlファイルを変更したので、sailを立ち上げ直す
```
$ sail down
$ sail up -d
```

## League\Csvのインストール
インストール
```
$ composer　require　league/csv
```

## BacklogAPIキーの取得

バックログにログインして、
Backlog　> 個人設定　> APIにて、わかりやすいメモを記入し発行ボタンを押して、
APIキーを発行します。
（本当はスクショして見せようかと思ったのですが、社内情報が漏れるのが怖いので、文字だけで勘弁。）

## 実装

### 1..envにAPIKEYを記述する
```.env
BACKLOG_API_KEY=${取得したAPIKEY}
```
### 2.config配下にbacklog.phpを作成する

```config/backlog.php
<?php

return [
    'base_uri' => ${プロジェクトのURL},
    'api_key' => env('BACKLOG_API_KEY'),
    'project_id' => ${プロジェクトのID},
];
```

### 3.今回はバッチ処理のなので、config/filesystem.phpにbatchを追加する
```config/filesystem.php
'batch' => [
            'driver' => 'local',
            'root'   => storage_path('app/batch'),
        ],
```
### 4. app/Console/Commands配下にバッチ処理を作成する
```ImportAndExportCsvBacklog.php
<?php

namespace App\Console\Commands;

use App\Actions\ExportBacklogCsv;
use App\Actions\GetBacklogDatas;
use Illuminate\Console\Command;

class ImportAndExportCsvForEvmsBacklog extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'import-and-export-csv-for-backlog';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'バックログ取り込み・CSV出力バッチ';

    /**
     * Execute the console command.
     */
    public function handle(GetBacklogDatas $getBacklogDatas, ExportBacklogCsv $exportCsv)
    {
        $this->info('開始');

        $baseUri = config('backlog.base_uri');

        try {
            // バックログにAPIを投げて値を取得
            $backlogDatas = $getBacklogDatas->exec($baseUri);

            // csvに出力
            $exportCsv->exec($backlogDatas, $baseUri);
        } catch (\Exception $e) {
            $this->info($e);
        }

        $this->info('終了');
    }
}

```

処理は下記のクラスに分けました。
```
GetBacklogDatas　→ バックログから値を引っ張ってくるクラス
ExportBacklogCsv　→ データをCSVに吐き出すクラス
```

### 5. app/Actions/GetBacklogDatasを作成する

今回は課題の一覧を取得したかったので、下記を使用します。

https://developer.nulab.com/ja/docs/backlog/api/2/get-issue-list/#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E5%B1%9E%E6%80%A7%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%9F%E6%A4%9C%E7%B4%A2-%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E5%B1%9E%E6%80%A7

**条件**
```
ステータスは、未対応、処理中、処理済で絞る
課題は100件以上ないことを前提に処理を組んでいる（backlogのAPIは一度に100件しかデータが取れないため。）
```

```GetBacklogDatas.php
<?php

namespace App\Actions;

use Illuminate\Support\Facades\Http;

class GetBacklogDatas
{

    private const NO_COMPATIBLE = 1; // 未対応
    private const PROCESSING = 2; //　処理中
    private const PROCESSED = 3; // 処理済

    /**
     * exec
     * @param string $baseUri
     * @return array
     */
    public function exec($baseUri): array
    {
        $apiKey = config('backlog.api_key');

        $params = $this->getParams($apiKey);

        $response = Http::get("$baseUri/api/v2/issues", $params);

        return $response->json();
    }

    /**
     * getParams
     * @param string $apiKey
     * @return array
     */
    protected function getParams(string $apiKey): array
    {
        /**
         * backlogのAPIが1回に100件しか値を取れないので、
         * それ以上取得する場合は、offsetをうまく利用した実装に変更する必要があるが、
         * 一旦は100件を超えることは考えにくいため、そのままとする
         */
        return [
            'apiKey' => $apiKey,
            'projectId[]' => config('backlog.project_id'),
            'statusId[]' =>  self::NO_COMPATIBLE,
            'statusId[1]' => self::PROCESSING,
            'statusId[2]' => self::PROCESSED,
            'count' => 100,
        ];
    }
}
```

### 6. ExportBacklogCsvを作成する

League/csvを使用して出力する

https://csv.thephpleague.com/

**条件**
```
課題名、担当者、期日、のデータを出力する
```

```ExportBacklogCsv.php
<?php

namespace App\Actions;

use Carbon\Carbon;
use Illuminate\Support\Facades\Storage;
use League\Csv\CharsetConverter;
use League\Csv\Writer;

class ExportBacklogCsv
{
    protected string $inputEncode = 'UTF-8';
    protected string $outPutEncode = 'SJIS-win';
    protected string $disk = 'batch';
    protected string $uri;
    protected array $backlogDatas;

    /**
     * exec
     * @param array $backlogDatas
     * @param string $uri
     */
    public function exec(array $backlogDatas, string $uri)
    {
        $this->uri = $uri;
        $this->backlogDatas = $backlogDatas;
        $this->exportCsv($backlogDatas);
    }

    /**
     * exportCsv
     */
    protected function exportCsv()
    {
        [$header, $records] = $this->getHeaderAndRecords();

        // エンコーディングの設定
        $converter = (new CharsetConverter())
                ->inputEncoding($this->inputEncode)
                ->outputEncoding($this->outPutEncode);

        $csv = Writer::createFromString()
            ->addFormatter($converter);

        // Headerの作成
        $csv->insertOne($header);

        // Recodeの作成
        $csv->insertAll($records);

        if (Storage::exists($this->disk)) {
            Storage::makeDirectory($this->disk);
        }

        $nowFormat = Carbon::now()->format('YmdHis');

        $fileName = "backlog_issue/backlog_issue_$nowFormat.csv";

        // ファイルの生成
        Storage::disk($this->disk)->put($fileName, '');

        // ファイルパスの取得
        $path = Storage::disk($this->disk)->path($fileName, true);

        // ファイルに記述
        file_put_contents($path, $csv);
    }

    /**
     * getHeaderAndRecords
     * @return array
     */
    protected function getHeaderAndRecords(): array
    {
        $header = [
            '課題名',
            '担当者',
            '期日',
        ];

        foreach ($this->backlogDatas as $data) {
            $records[] = [
                $data['summary'] ?? '', // 課題名
                $data['assignee']['name'] ?? '', // 担当者
                $data['dueDate'] // 期日
                    ? Carbon::parse($data['dueDate'])->format('Y/m/d')
                    : '',
            ];
        }

        return [$header, $records];
    }
}
```

### 7. バッチを実行してみる
```
$ sail artisan import-and-export-csv-for-backlog
```

storage/app/batch/backlog_issue配下にファイルが出力されるはず！

中身はこんな感じ！
![スクリーンショット 2022-12-13 19.42.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/965248/405f4029-e55e-a1f9-1745-071f3120e367.png)

## まとめ
とりあえずこれで動いた。
作ってから、記事にしたので、この通りやっても動かなかったらごめん。。。
良いお年を。