【初心者でもできる】近隣のラーメン屋検索機能つきLINE BOT

#はじめに
PHP学習から約3ヶ月。新たにLINE BOT+ぐるなびapi導入に挑戦。
「駅名」や「位置情報」を送ると、ラーメン屋の情報を送ってくれるBotを作ってみました！
ラーメンに関して食べログ並みの情報量、信用性それ以上の知人がおり、しょっちゅう教えてもらってます。
*「この知識を自分だけのものにしておくのは勿体ない。」
「ラーメン屋を教えてもらうために、しょっちゅう問い合わせるのも悪いなあ。でも知りたい」
これがこのサービスを創るきっかけ。*
まだ実装途中の機能もありますが、ある程度整ったのでシェアします！

**サービス利用イメージ**
![ramen_bot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392997/4f146330-123c-2d8c-bacd-5878b3658683.gif)


**:ramen:こんな方々に向けて書きます**
＊PHP学習初級者で、LINE BOTや、ぐるなびapiに興味のある方
＊初級者ではないが、レビューしてくれる優しい方
＊ラーメン好きなすべての方へ

**:writing_hand_tone1:ちょこっと自己紹介**
何か自分の手で、創りたいという想いと、WEB系企業への転職を目指し、2019年4月より、PHPの勉強を始めました。
([オンラインプログラミングサークル　プロサー](https://problog.site/)で学習中）
学習時間は現在トータル約370h
言語　PHP 7.1.29
DB　MySQL 
開発環境　CentOS 7.6.1810 
Apache 2.4.6
本番環境　ConoHa　VPSサーバー 
よかったら[twitterフォロー](https://twitter.com/kashimaweb)お願いします！

:ramen:それではいきましょう:ramen:


#サービス概要
ラーメン好きなユーザーから送信されたメッセージに応答するBOTです。
メッセージのタイプ毎（テキスト、スタンプ、位置情報）に返信する内容を変えてます。
###テキストの場合###
**データベースにある*「渋谷」*,*「新宿」*などの*「駅名」*にヒット**
->おすすめのラーメン屋情報（名前、URL（食べログ該当ページ）、コメント）を返信。（最大3件）
*データベースを使用せず、ぐるなびapiを利用する方が簡単です。
自分の場合は、自分で情報を入れたかった（知人の情報ですが。。）のでデータベースを用意することにしました。*
**データベースにヒットしない場合**
->定型メッセージをランダムで返信。
###スタンプの場合###
特定のスタンプを返信。
###位置情報の場合###
ぐるなびapiを活用し、送られた緯度、軽度を元に近隣のラーメン屋３件返信。
表示方法はLINEテンプレート「カルーセル」使用。

#必要なコト
1.LINE@のDeveloper Trialアカウント 
　->[LINE Developers](https://developers.line.biz/ja/)
　登録から用意するファイルなどこちら参考にさせていただきました:bow_tone1:
　https://qiita.com/at_1016/items/9f97dc1c561182c18182
2.ぐるなびAPIを使うのでAPIキー
　->[ぐるなび　web サービス](https://api.gnavi.co.jp/api/)
3.PHPとSSLを利用できるサーバー

#ソースコード
###全体###
```php:webhook.php
<?php
/** 
 * Copyright 2016 LINE Corporation 
 * LINE Corporation licenses this file to you under the Apache License,
Copyright 2016 LINE Corporation 
・・・省略・・・
* under the License.
*/ 
require_once('./LINEBotTiny.php'); 
//LINE developerアクセス 
$channelAccessToken = 'LINE Developersで取得'; 
$channelSecret = 'LINE Developersで取得'; 
$client = new LINEBotTiny($channelAccessToken, $channelSecret); 
//ラーメン屋情報提供のためデータベース接続 
$dsn = 'mysql:dbname=xxxx;host=xxxx'; 
$user = 'xxxx'; 
$pass = 'xxxxxxxxxx'; 
//共通部分の関数化
function replyMessage($client, $reply_token, $messages) {
	return $client->replyMessage([
		'replyToken' => $reply_token,
		'messages' => $messages
	]);
}

//ユーザーのメッセージタイプ毎のレスポンス
foreach ($client->parseEvents() as $event) {
	if ($event['type'] == 'message') {
		$message = $event['message'];
		switch ($message['type']) {
			//メッセージタイプがテキストの場合
			case 'text':
				$res_name = $message['text'];
				$res_name = '%' . $res_name . '%';
				try {
					$dbh = new PDO ($dsn, $user, $pass);
					$sql = "SELECT * FROM ramen_info WHERE station1 LIKE ?";
					$stmt = $dbh->prepare($sql);
					$stmt->bindValue(1, $res_name);//送信された内容で検索
					$stmt->execute();
					$results = $stmt->fetchAll();
				} catch (EXCEption $e) {
					echo $e->getMessage();
				}
				$i = 1;
				if ($results) {
					foreach ($results as $result) {
						${'res' . $i} = "\ $result[point] / \r\n $result[name] \r\n $result[URL]\r\n";
						$i++;
					}
				} else {
					$comment1 = array('未開の地', '知らんなー', '初耳ガクッ', 'それは臭うな');
					$comment2 = array('調査して共有するぞよ', '教えてくれてくれてありがとな', 'ちょっと行ってくる', 'ハヤシ先生に聞いてみよ');
					$key1 = array_rand($comment1);
					$key2 = array_rand($comment2);
					$res1 = $comment1[$key1];
					$res2 = $comment2[$key2];
					$res3 = null;
				}
				$messages = [
					[
						'type' => 'text',
						'text' => $res1 . "\r\n" . $res2 . "\r\n" . $res3
					]
				];
				replyMessage($client, $event['replyToken'], $messages);
				break;
			//メッセージタイプがスタンプの場合
			case 'sticker':
				$messages = [
					[
						'type' => 'sticker',
						'packageId' => 11537,
						'stickerId' => 52002739,
					]
				];
				replyMessage($client, $event['replyToken'], $messages);
				break;
			//メッセージタイプが位置情報の場合
			case 'location':
				// 受信した位置情報からの情報
				$lat = $message['latitude'];
				$lon = $message['longitude'];
				// ぐるなびapiURL
				$uri = 'https://api.gnavi.co.jp/RestSearchAPI/v3/';
				// ぐるなびアクセスキー
				$gnaviaccesskey = 'ぐるなびアクセスキー';
				// ラーメン屋さんを意味するぐるなびのコード(小業態マスタ取得APIをコールして調査)
				$category_s1 = 'RSFST08008';
				// つけ麺屋さんを意味するぐるなびのコード(小業態マスタ取得APIをコールして調査)
				$category_s2 = 'RSFST08008';
				// 3件抽出
				$hit_per_page = 3;
				//範囲
				$range = 3;
				//URL組み立て
				$url  = $uri . '?keyid=' . $gnaviaccesskey . '&latitude=' . $lat . '&longitude=' . $lon . '&range=' . $range . '&category_s=' . $category_s1 .'&category_s=' . $category_s2 . '&hit_per_page=' . $hit_per_page ;
				//ぐるなびapiの情報取得
				$conn = curl_init();
				curl_setopt($conn, CURLOPT_URL, $url);
				curl_setopt($conn, CURLOPT_RETURNTRANSFER, true);
				$res = curl_exec($conn);
				$obj = json_decode($res);
				curl_close($conn);
				// 店舗情報を取得
				$columns = array();
				foreach ($obj->rest as $restaurant) {
					$columns[] = array(
						'thumbnailImageUrl' => $restaurant->image_url->shop_image1,
						'title' => $restaurant->name,
						'text' => $restaurant->address,
						'actions' => array(
							array(
								'type' => 'uri',
								'label' => '詳細を見る',
								'uri' => $restaurant->url
							)
						)
					);
				}
				if ($columns !== null) {
					$messages = [
						[
							'type' => 'template',
							'altText' => '周辺のラーメン屋情報',
							'template' => [
								'type' => 'carousel',
								'columns' => [
									[
										//'thumbnailImageUrl' => $columns[0][thumbnailImageUrl] , //画面表示方法検討中
										'imageBackgroundColor' => '#FFFFFF',
										'title' => $columns[0][title],
										'text' => $columns[0][text],//位置情報から店舗までの経路案内にリンク予定
										//'defaultAction' => [
										//'type' => 'uri',
										//'label' =>' View detail',
										//'uri' => 'http://example.com/page/123', //ぐるなびuri
									//],
										'actions' => [
											[
												'type' => 'uri',
												'label' => 'ぐるなびサイトへ',
												'uri'=>$columns[0]['actions'][0]['uri'],
											]
										]
									],
									[
										//'thumbnailImageUrl' => $columns[0][thumbnailImageUrl] , //画面表示方法検討中
										'imageBackgroundColor' => '#FFFFFF',
										'title' => $columns[1][title],
										'text' => $columns[1][text],//位置情報から店舗までの経路案内にリンク予定
										//'defaultAction' => [
										//'type' => 'uri',
										//'label' =>' View detail',
										//'uri' => 'http://example.com/page/123', //ぐるなびuri
									//],
										'actions' => [
											[
												'type' => 'uri',
												'label' => 'ぐるなびサイトへ',
												'uri' => $columns[1]['actions'][0]['uri'],
											]
										]
									],
									[
										//'thumbnailImageUrl' =>$columns[0][thumbnailImageUrl] , //画面表示方法検討中
										'imageBackgroundColor' => '#FFFFFF',
										'title' => $columns[2][title],
										'text' => $columns[2][text],//リンクにしたい
										//'defaultAction' => [
										//'type' => 'uri',
										//'label' =>' View detail',
										//'uri' => 'http://example.com/page/123', //ぐるなびuri
									//],
										'actions' => [
											[
												'type' => 'uri',
												'label' => 'ぐるなびサイトへ',
												'uri' => $columns[2]['actions'][0]['uri'],
											]
										]
									]
								]
							]
						]
					];
					replyMessage($client, $event['replyToken'], $messages);
					break;
				} else {
						$messages = [
							[
								'type' => 'text',
								'text' => '残念ですが近くにラーメン屋が見つかりませんでした。'
							]
					];
					replyMessage($client, $event['replyToken'], $messages);
					break;
				}
			}
	} else {
		error_log('Unsupported event type:' . $event['type']);
		break;
	}
};
?>
```
#やってみて
###ハマった点
１.ぐるなびApiを活用し、ユーザーから送信された位置情報から緯度と軽度を元に情報を取り出す方法
2.位置情報に対する返信カルーセルテンプレート表示部分

###改善点
1.位置情報に対する返信カルーセルテンプレート表示部分
　・動作はしているが、記述がスマートではないので、要修正。
　・位置情報に対する返信カルーセルテンプレートサムネイル画像表示
　　→店舗側でのイメージ画像が不揃いのため、サムネイル画像がnullとなりerrorになってしまう。
　　　nullの場合に備え、デフォルトの画像を用意するまたは、現状のまま画像を用意しない。

２.近隣にラーメン屋がヒットしない場合について既読スルーの状態になっている

３.動作はするが、非効率な部分もあるので、上記と合わせリファクタリングする

#ただの感想
LINE　BOT作成はアイデアも出しやすく、形にしやすいので自分のような初心者が取りくむものとしておすすめです。
機能も広がるのでAPI活用するのもまたおすすめ。
適宜アップデートしてどんどん完成形に近づけたいと思います！