【PHP】Qiita/RSSフィード解析

QiitaのRSSフィード活用されていますか？
個人サイトや、推しメンの記事を確認するのに結構便利です。

↓↓ Feedの存在知らない方はこちら ↓↓

https://qiita.com/umekikazuya/feed

---
今回は個人用に提供されているRSSフィードをPHPで解析してみました。

公式からRSSが提供されているのに、わざわざ解析してJSON化する意味がわからない...。って思う方もいると思います。
用途は人それぞれですが、「毎朝Slack通知したい」「記録として残したい」「独自のAPIを用意して、複数のアプリで使いまわしたい」とか、まぁ役立ちそうですよね。

**「HTTP通信」をして「XMLの解析」をして「JSON化」する。**
わりと、初学者の学習にぴったりな題材な気がします。

## 使用ライブラリ
### Guzzle Http Client

HTTP通信のためにGuzzleライブラリを利用します。

https://docs.guzzlephp.org/en/stable/

## 実装例
さっそくですが実装例を紹介。
（※ 記事用に細かい例外処理など省いています。甘い部分あるかと思いますが、ご了承ください。）
```php
/**
 * データのFetch.
 *
 * @param string $id
 *   Qiitaのアカウント名.
 * @param bool $format_json
 *   Jsonで返すか. 配列で返すか.
 *
 * @return string|array|false
 *   例外はFALSE.
 */
public function fetch(string $id, $format_json = TRUE): string|array|false {
  $url = 'https://qiita.com/' . $id . '/feed';
  try {
    $http_request = $this->httpClient->get($url);
    $feed = $http_request->getBody()->getContents();
    if (!$feed) {
      throw new \Exception();
    }
    $data = $this->loadRawFeedData($feed);
    if (!$data) {
      throw new \Exception();
    }
    $data = $this->parseXml($data, $url);
  }
  catch (\Exception $e) {
    return FALSE;
  }
  return $format_json ? json_encode($data) : $data;
}

/**
 * Fetchした生データを \SimpleXMLElement に変換.
 *
 * @param string $feed
 *   生データ.
 *
 * @return \SimpleXMLElement|false
 *   オブジェクトを返す. 失敗したら FALSE を返す.
 */
private function loadRawFeedData(string $feed): \SimpleXMLElement|false {
  $data = simplexml_load_string($feed);
  if (assert($data instanceof \SimpleXMLElement)) {
    return $data;
  }
  return FALSE;
}

/**
 * XMLデータの解析.
 *
 * @param \SimpleXMLElement $xml
 *   XMLデータ.
 * @param string $url
 *   RSS取得URL.
 *
 * @return array
 *   解析データを配列で返す.
 */
private function parseXml(\SimpleXMLElement $xml, string $url): array {
  $articles = [];
  foreach ($xml->entry as $o) {
    $title = (string) $o->title;
    $link = (string) $o->url;
    $published = (string) $o->published;
    if (!isset($title) || !isset($link) || !isset($published)) {
      continue;
    }
    $published = new \Datetime($published);

    $articles[] = [
      'title' => $title,
      'link' => $link,
      'published' => $published->format('c'),
    ];
  }
  return [
    'title' => (string) $xml->title,
    'link' => $url,
    'data' => $articles,
  ];
}
```

## 実行
```php
fetch('umekikazuya');
```
## 結果
```json
{
  "title": "umekikazuyaの記事 - Qiita",
  "link": "https://qiita.com/umekikazuya/feed",
  "data": [
    {
      "title": "PRに対して意識が変わりました 〜エンジニア2年目の夏〜",
      "link": "https://qiita.com/umekikazuya/items/a3260117bbc64e6673c3",
      "published": "2024-06-25T20:42:15+09:00"
    },
    {
      "title": "【PHP】DateTime::createFromFormat における ISO 8601 形式の罠",
      "link": "https://qiita.com/umekikazuya/items/fb1b407ddd0fc4d6021c",
      "published": "2024-06-24T21:39:14+09:00"
    },
    {
      "title": "アジャイルでキャリア形成しよ 〜スプリントを管理するDB設計法〜",
      "link": "https://qiita.com/umekikazuya/items/032c1e5d144830548d79",
      "published": "2024-06-24T09:29:09+09:00"
    },
    {
      "title": "【Drupal】過去の hook_update_N をスキップしたい",
      "link": "https://qiita.com/umekikazuya/items/421cf61a64b363cf9080",
      "published": "2024-06-24T01:36:38+09:00"
    }
  ]
}
```

いい感じ！

## 解説
ちょっとだけ解説。
### ``fetch(string $id, $format_json = TRUE)`` について
Fetchして、データを返す関数です。
1. アカウント名を引数にしてどのユーザーのFeedでも取得できるようにしています
2. 第2引数で、JSON形式でデータを返す or 配列でデータを返す を指定できます
3. データの取得にコケた場合や、解析時に不正なデータが合った場合はFALSEを返します

**コードの責務が曖昧なので HTTP通信の実装部分は関数化してもいいかもですね。**

### ``loadRawFeedData(string $feed)`` について
HTTP通信で取得した、生データを \SimpleXMLElement に変換します。

### ``parseXml(\SimpleXMLElement $xml, string $url)`` について
XMLデータを解析して、データのフォーマットをします。
キャストがちょっと雑かもですが、大目に見てください...

## まとめ
いかがでしょうか？

記事内では、関数として実装を行いましたが...。
私はAPIを提供する Controller を実装してみました！！

もちろん、アカウントごとにResponseをCacheする機構も組み込んでいます。

次回もお楽しみに。

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

https://qiita.com/umekikazuya/items/6379e3785b385bceabb3

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

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