【Drupal】QiitaのRSSをAPI化？キャッシュ機構も組み込みました

## はじめに
この記事は前回の記事の続編です。

https://qiita.com/umekikazuya/items/c20ac26b59670a72ca7b

今回はQiitaのRSSを題材に、Drupalの Controller・API配信・キャッシュ機構 について実装を行います。

Drupalには、Routing機構 / HTTP通信 / キャッシュシステム が標準で組み込まれてます。
なので比較的やりやすいかと思います。

<details><summary>※ RSSがわざわざ提供されているのになんでAPI化するの？</summary>
前回の記事でも書いていますが、実務だったらこんなことしません。

用途は人それぞれですが、「毎朝Slack通知したい」「フロントエンドフレームワークを使ったPFに使いたい」「独自のAPIを用意して、複数のアプリで使いまわしたい」とか、まぁAPI化すれば色んな場面で役立ちそうですよね。自分の好きなフォーマットで実装できるっていう強みもあります。
</details>


## なにやるの？
Qiitaが提供しているRSSフィードを解析して自前のAPIを配信します。

それだけだとおもしろくないので、以下の要件を加えてみます。
1. QiitaAccountを指定できるようにする
1. キャッシュの組み込みを行って都度HTTP通信が発生しないようにする

どうですか？ちょっとやり応えありそうな要件ですよね。

## 準備
### ■ Drupal環境の用意

環境構築はこちらを参考に。

https://qiita.com/umekikazuya/items/5b6069cd7a868bf74f3c


### ■ Drushの導入
```shell-session
composer require drush/drush
```

Drupal開発を普段から行っている方には説明不要ですね。
Drupalの開発で大活躍のCLIツールです。

### ■ 再現環境
多分Drupal10x系であれば動くような書き方をしてるので、問題ないかと思いますが一応。

#### Drupal 10.2.7
#### Drush 12.4

## 実装はじめます
### 001. モジュールのセットアップ
さっそく、モジュールを実装します。とは言っても`Drush`が勝手に作ってくれます。
```shell-session:Drush が Module作ってくれるよ😳
drush generate controller
```

https://www.drush.org/11.x/generators/controller/

コマンドを実行後、いろいろ聞かれるので答えてください。
```log:わかんなかったらとりあえず Enter でいいよ
$ drush generate controller

 Welcome to controller generator!
––––––––––––––––––––––––––––––––––
 Module machine name:
 ➤ qiita
 Module name [Qiita]:
 ➤ 
 Class [QiitaController]:
 ➤ 
 Would you like to inject dependencies? [No]:
 ➤ 
 Would you like to create a route for this controller? [Yes]:
 ➤ 
 Route name [qiita.example]:
 ➤ 
 Route path [/qiita/example]:
 ➤ /api/qiita/{id}
 Route title [Example]:
 ➤ 
 Route permission [access content]:
 ➤ 
```

いくつかの質問に答えると、Controller付きのModuleが用意されますので有効化してしまいましょう。

```shell-session:Moduleの有効化
drush en qiita
```

### 002. Routing設定
Routingの設定を行います。
とは言っても、ほとんどなにもすることありません。

URLや、権限周り特にこだわりがないのであれば以下の感じでいいかと思います。
```yaml:qiita.routing.yml
qiita.example:
  path: '/api/qiita/{id}'
  defaults:
    _controller: '\Drupal\qiita\Controller\QiitaController'
  requirements:
    access: TRUE
```

`{id}`がポイントですね。Controllerがアカウント名を受け取れるようにできます。

### 003. Controllerの実装
```php:src/Controller/QiitaController.php
final class QiitaController extends ControllerBase {

  private const ACCOUNT_NAME = 'umekikazuya';

  /**
   * QiitaのFeedを取得しJSONを返すResponse Method.
   *   
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   Jsonを返すよ. 処理が上手く以下なかったら 403 を返すよ.
   */
  public function __invoke(): JsonResponse {
    $res = new JsonResponse();

    // URLを設定.
    $url = 'https://qiita.com/' . self::ACCOUNT_NAME . '/feed';
    try {
      // HTTP通信実行.
      $http_request = $this->httpClient->get($url);
      $feed = $http_request->getBody()->getContents();
      if (!$feed) {
        throw new \Exception();
      }
      // 取得情報をXMLデータに変換. （Private関数は前回の記事を見てね）
      $data = $this->loadRawFeedData($feed);
      if (!$data) {
        throw new \Exception();
      }
      // XMLデータを好みのフォーマットに変換. （Private関数は前回の記事を見てね）
      $data = $this->parseXml($data, $rss_url);
      // JSONに変換してAPIを提供！！.
      return $res->setJson(json_encode($data));
    }
    catch (\Exception $e) {
      // 失敗してたら 403 返すよ.
      return $res->setStatusCode(403);
    }
  }

}
```

[前回の記事](https://qiita.com/umekikazuya/items/c20ac26b59670a72ca7b#%E5%AE%9F%E8%A3%85%E4%BE%8B)で、取得データのXML変換・XMLパースについて触れていますのでそちらもぜひ。

### 004. ちょっと休憩（一旦動作確認）
ひとまずお疲れ様です。この時点でAPIの実装は完了です。

アクセスしてみてください。
```
｛HOST URL｝/api/qiita/umekikazuya
```
JSON返ってきましたか？
---

### 005. URLに応じてアカウントごとのデータ取得を取得
今はどんなURLを投げても同じJSONが返ってくるはずです。
```log:どのURLでも結果は同じはず
｛HOST URL｝/api/qiita/umekikazuya
｛HOST URL｝/api/qiita/hogehoge
｛HOST URL｝/api/qiita/fugafuga
```

Routing設定で`/api/qiita/{id}`と指定しているので、アカウント名を受け取るよう実装を修正します。

```diff:QiitaController.php
final class QiitaController extends ControllerBase {

-   private const ACCOUNT_NAME = 'umekikazuya';

  /**
   * QiitaのFeedを取得しJSONを返すResponse Method.
   * 
+    * @param string $id
+    *   Qiita accout id.
+    *   
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   Jsonを返すよ. 処理が上手く以下なかったら 403 を返すよ.
   */
-  public function __invoke(): JsonResponse {
+  public function __invoke(string $id): JsonResponse {
    $res = new JsonResponse();

-     // URLを設定.
+     // ここでアカウント別のHTTPリクエストを行うよ.
    $url = 'https://qiita.com/' . id . '/feed';

    /** 省略 */
    
  }

}
```

実装はこれだけです。

`{id}` を指定したエンドポイントでアクセスしてみてください。

### 006. キャッシュの組み込み
このままだと、正直RSS配信されてるのにAPIを提供するって正直意味ないですよね。
キャッシュを効かせます。**アカウントごとに。**

Drupalのキャッシュはすごいです。
#### キャッシュ操作
```php:キャッシュのセット（8時間キャッシュするよ）
$cache_key = 'api_qiita:' . $id;
$this->cache()->set($cache_key, $data, time() + 8 * 3600);
```

```php:キャッシュのゲット
$cache_key = 'api_qiita:' . $id;
$cache = $this->cache()->get($cache_key);
```


`$id` がUniqueなので、一つのメソッドだけでアカウントごとにキャッシュの保持を行うことができますね。

こちらを`__invoke`に組み込めば、キャッシュ機構もばっちしだと思います。

**Qiitaサーバーへの負荷も軽減できますね。**

## まとめ
お疲れ様でした。いかがでしたか？

今回は、Qiita/RSSを題材にDrupalのController・API配信・キャッシュ機構についての実装をやってみました。

RSSを題材にやってみましたが、今回のようなRoutingの設定方法は他にも活かせる場面が多いはず 😎

キャッシュ設定に関しても、DrupalのEntity機構と組み合わせればもっと動的にGUIで設定できたり...。

いろいろできそうですね 😏

実は、このシリーズまだ終わりません。（え〜！！？）
次回は、Next.js でこのAPIを使ってレンダリングをしてみましょう。（え〜！！？）

---
**シリーズ紹介**

https://qiita.com/umekikazuya/items/c20ac26b59670a72ca7b

https://qiita.com/umekikazuya/items/54b6ada9024f17499aa5

https://qiita.com/umekikazuya/items/8a8a81d7be27ba4153d1