ハッシュタグ検索機能を作る

## この記事について
CMSなどで記事内に含まれるハッシュタグを検索できる機能を実装したので、その方法を記載します。
画面までは作らず、バックエンドのポイントとなる部分だけの説明とします。

## 機能概要
前提としてはCMSの中に以下の機能に追加するものとします。
・おすすめハッシュタグ（記事内に含まれたハッシュタグを抽出する機能）
・ハッシュタグ検索（指定されたハッシュタグが付いている記事を検索する機能）

## 前提知識
### ハッシュタグの考え方
実装する前に世の中で展開されているハッシュタグには以下のようなルールがあることを押さえておきます。

■ 名付けルール
ハッシュタグはどんな文字でも使えるわけではなく、InstagramやTwitterでもハッシュタグとして認められるためのルールがあります。
【例】
・Twitter　　→　記号や数字は禁止
・Instagram　→　数字は許可されているが、記号は_(アンダーバー)のみ許可

■ 境界文字
複数ハッシュタグをつける場合や文中にハッシュタグを入れたい場合、区切りとなる文字のルールがあります。
TwitterやInstagramも基本的に半角スペースか改行があることで、一つのハッシュタグと見なすようです。
文中にハッシュタグを入れる場合は前後に半角スペースをつけるなどのルールがあります。


## 実装
### ハッシュタグの付け方ルール
今回は簡単に以下のルールで行います。

■ ハッシュタグ基本構文
```
# 許可文字　単語境界文字
```
■ 詳細なルール
```
#　　　　　 ・・・先頭に記号のナンバー(ハッシュタグ)を使っていること
許可文字　　・・・半全角の英数字かなカナunicode対応漢字（特定の記号は対象外）
単語境界文字・・・改行、半角スペース（全角スペースは対象外）
```

### おすすめハッシュタグ機能
１． データベースからハッシュタグが含まれる記事を抽出します
以下のSQLでまずハッシュタグ含めれる記事が抽出されます。
```sql
SELECT * FROM contents where body regexp '#[^\s#]+';
-- →　ハッシュタグが含めれているレコードだけがヒットすると思います。
```

データベースは以下とします。
| id  | title  | body                                 | 
| --- | ------ | ------------------------------------ | 
| 1   | 一回目 | 新作出ました。 #夏コレ #ワンピ              | 
| 2   | 二回目 | 今日は暑いですね。                   | 
| 3   | 三回目 | サマーバーゲン開催中です。 #バーゲン | 
| 4   | 四回目 | バーゲン残り３日です。 #バーゲン |
| 5   | 五回目 | 有名Yotuberとコラボします。#ほげ@youtuber | 

２．記事からハッシュタグだけを抽出します
```php
// $searchResultはさっきのSQLで抽出したデータとします

foreach ( $searchResult as $body )
{
    // 記事内からハッシュタグがついているワードを抜き出して配列にする
    preg_match_all('/#[^\s]+/', $body, $matches);

    // 一つの変数に抽出した配列を加えていきます。
    $hashTags = array_merge($hashTags,$matches[0]);
}

// $hashTagsの中身は以下のようになっていると思います
// 　array(
//     0 => #夏コレ
//     1 => #ワンピ
//     2 => #バーゲン
//     3 => #バーゲン
//     4 => #ほげ@youtuber
//   )
```

３．重複しているタグを取り除きます。
```php
// $hashTagsは２で作成した配列

// リスト内で重複しているタグとユニークタグの数をカウントする
$countOfHashTagList = array_count_values($hashTags);

// カウントした内容から重複しているタグとユニークタグを分ける
$onlyDuplicateHashTags = [];
$onlyUniqueHashTags    = [];
foreach ( $countOfHashTagList as $tagName => $count )
{
    if ($count > 1)
    {
        $onlyDuplicateHashTags[$tagName] = $count;
    }
    if ($count === 1)
    {
        $onlyUniqueHashTags[$tagName] = $count;
    }
}

// 重複しているタグ数が多いものから順番にソート
arsort($onlyDuplicateHashTags);
$hashTagList = array_merge($onlyDuplicateHashTags,$onlyUniqueHashTags);

// $hashTagListの中身は以下になっていると思います。
// 　array(
//     0 => #バーゲン
//     1 => #夏コレ
//     2 => #ワンピ
//     3 => #ほげ@youtuber
//   )
```
４．許可されていない文字が含めれるハッシュタグは抜く
投稿時点で禁止にすべきですが、今回はバックエンドのみの実装なので、ルールとして禁止している文字があるハッシュタグはリストから削除したいと思います。
ハッシュタグ内に記号入っていてSQLでうまく検索できないなどバグを減らすために。

```php
// $hashTagListは３で作成した配列

// ハッシュタグ内に含めれない文字のパターン
$pattern = '/[　!、\-@¥_!$%&()"\'˚*+,.\/:;<=>?\[\]^{]/u';

$recommendHashTags = [];
foreach ( $hashTagList as $key => $value )
{
    // 禁止している文字がある場合はリストには含めないのでループをスキップする
    if ( preg_match($pattern,$key) ) continue;

    $recommendHashTags[] = $key;
    $index++;
}

// $recommendHashTagsの中身は以下になっていると思います。
// 　array(
//     0 => #バーゲン
//     1 => #夏コレ
//     2 => #ワンピ
//   )
```

これでおすすめハッシュタグの抽出は完了です。

### ハッシュタグ検索
指定されたハッシュタグが付いている記事を検索する機能を作っていきたいと思います。
といってもSQLでほぼ完結すると思います。

１． データベースから指定されたハッシュタグが含まれる記事を抽出します
以下のSQLでハッシュタグを含む記事が完全一致で抽出されます。
```sql
SELECT * FROM contents where body regexp '#ハッシュタグ名([[:space:]]|　|$)+';
```
例：上記のDBの例で`#バーゲン`を検索するSQLだと以下になります。
```sql
SELECT * FROM contents where body regexp '#バーゲン([[:space:]]|　|$)+';
-- →IDが3と4のものが検索されると思います。
```

これで検索部分は完了です。

## まとめ
改善の余地はあると思いますが、ある程度のところまでそれっぽく実装できていると思います。
実装していくと正規表現で結構苦労しました。。。