- 投稿日:2020-07-07T18:05:21+09:00
Laravel、プロジェクトを作成して最初にやること
今回は私がプロジェクト作成して最初に行うことをメモ程度にまとめました。
かなり説明は省いています。すいません開発環境
・Mac
・php 7.4.6
・Laravel 7.15.0
・MySQLプロジェクトの作成と.envファイルの編集
1.
laravel new プロジェクト名
2. config/app.phpのtimezoneとlocaleの変更config/app.php'timezone' => 'Asia/Tokyo', 'locale' => 'ja',データベースに関する設定
(1) .envファイルのデータベースの設定
.envDB_CONNECTION=mysql DB_HOST= DB_DATABASE=データベース名 DB_USERNAME= DB_PASSWORD= DB_SOCKET=(2) マイグレーションファイルの設定
まず、マイグレーションを行う意味を知りましょう!
マイグレーションは、環境が変わった時などにデータベースを一から作るのは大変だからチャチャっと作って!という時の為にあります。
ローカル環境から本番環境に移行することを考えましょう。マイグレーションを使えば、本番環境でローカル環境と同じデータベースをコマンド一発で作成してくれます。まずはマイグレーションファイルを作っていきましょう!以下のコマンドでファイルが作成されます。
$php artisan make:migration create_posts_table --create=posts
作成日時_create_posts_table.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreatePostsTable extends Migration { public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->timestamps(); }); } public function down() { Schema::dropIfExists('posts'); } }このファイルの中にpostsテーブルの記述をしていきましょう。マイグレーションファイルが書き終わったら、以下のコマンドでmigrateしましょう。
$php artisan migrate
※ テーブルを加えたり、データベースに変更を加えた場合は以下のコマンドで更新出来ます。
$php artisan migrate:refresh
このコマンドによってデータベースにテーブルが作成されます。
(3)モデルの設定
モデルを作成する意味は、モデルクラスを使うことでデータベースにデータを挿入したり、削除することができるようにする為です。
以下のコマンドでファイルを作成出来ます。モデルの名前は、テーブルの名前がpostsだったらPost.phpになる。どういうことかというと、テーブルの名前の単数形の名前をモデルにつけると、自動的にそのモデルはそのテーブルと紐づけられる。
以下のコマンドでモデルのファイルを作成しましょう!
$php artisan make:model Post
ファイルに記述する内容は、今回は割愛します。
(4)シーディング
シーディングは、サンプルデータをコマンド一発で作成するためのものです。
以下のコマンドで、ファイルを作成しましょう。
$php artisan make:seeder PostsTableSeeder
ファイルの中身を記入したら、DatabaseSeeder.phpにPostsTableSeeder.phpを登録します。
DatabaseSeeder.php<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { public function run() { $this->call(PostsTableSeeder::class); } }これで、一通り最初にやることは完了です!
seederの書き方については以下の記事にまとめているので参照してください。
まとめ
今回は、私がプロジェクトを作成してから最初に行うことをまとめて見ました。
間違いありましたら、指摘をお願いします!
- 投稿日:2020-07-07T17:54:26+09:00
位置情報を送ると近くの餃子か焼肉のお店を教えてくれるLINE BOTを作った
経緯
なんか面白い物が作りたいと思い、位置情報を送ると近くの餃子屋さんと焼肉屋さんを教えてくれるLINE BOTを作りました。
なぜ餃子と焼肉かと言うと、私が好きだからです。参考
【公式リファレンス】
https://developers.line.biz/ja/docs/messaging-api【LINE SDK for PHP】
https://github.com/line/line-bot-sdk-php【[LINE Bot] 位置情報から食べログ3.5以上の優良店を検索するbot作った】
(これをお手本につくりました。)
https://qiita.com/NARI_Creator/items/f29112e6f604c86b3c0d【ぐるなびAPIの取得方法 そのまま使えるソースコード付き】
https://enjoy-surfing.com/gurunavi/【LineBotを作るときの雛形 for Laravel】
https://qiita.com/sh-ogawa/items/2238e579d7ee538025a0【LaravelでLINEにチャットボットをつくる(QRコード作成)】
https://blog.capilano-fw.com/?p=4285#i-4大変参考になりました?ありがとうございます。
仕様
1.餃子が食べたいか焼肉が食べたいか入力する
2.位置情報を送信する
3.近くのお店を教えてくれる簡単な仕様はこんな感じです。?
作成前にやっておくこと
herokuのアプリケーションを作る
ここら辺みておけばできるんじゃないかなーと思います。
Heroku アカウント登録してデプロイするまでの簡単な使い方
Laravelをherokuにデプロイする(データベースはMySQL)herokuはクレカを登録すると無料で1ヶ月あたり1000時間使えます。
無料プランだと30分ごとにスリープしてしまうのでスリープ後の初アクセスはレスポンスが遅いですが、バッチ処理をしておけば問題ないみたいです。LINE Message APIの登録
LINE BOTのアカウントを作ります。
公式ドキュメント
公式が割と丁寧に説明してくれてるので、見ながらやったらできると思います。実装
LINE Messaging API SDKをインストールする
$ composer require linecorp/line-bot-sdk環境変数を追加する
#line LINE_SECRET_TOKEN = LINE_ACCESS_TOKEN =トークンはアカウントのBasic Settingから参照できます。
ルートの作成
ユーザーがLINE BOTにメッセージを送ると、LINEプラットフォームから指定されたアプリケーションのURLにHTTPリクエストを送るので、そのURLを決めます。
web.phpRoute::post('line','LineController@post')->name('line.post');ちなみにリクエストはPOSTで送られます?
あとついでにControllerも作っちゃいます。クライアントを作成する
今回はサービスクラスにまとめました。
LineService.php<?php namespace App\Services; use LINE\LINEBot; use LINE\LINEBot\HTTPClient\CurlHTTPClient; class LineService { /** * linesdkの使用開始 * * @return LINEBot */ public static function lineSdk() { $token = config('services.line.token'); $secret = config('services.line.secret'); $httpClient = new CurlHTTPClient($token); $bot = new LINEBot($httpClient,['channelSecret' => $secret]); return $bot; } }
$token
と$service
は.envに登録したLINE_SECRET_TOKEN
とLINE_ACCESS_TOKEN
ですが、configのserviceに登録してからもってきてます。基本公式のREADMEに書いてあるまんまですね?
署名を検証する
ラインプラットフォーム以外のわるものから危険なリクエストが送られてくる可能性があるので、検証する必要があります。
ラインのプラットフォームからリクエストが送られてきた時は、リクエストヘッダーにX-Line-Signature
というものが含まれます。これを検証するのですが、チャネルシークレットを秘密鍵として、HMAC-SHA256アルゴリズムを使用してリクエストボディのダイジェスト値を取得します。
ダイジェスト値をBase64エンコードした値と、リクエストヘッダーのX-Line-Signatureに含まれる署名が一致することを確認します。と丁寧に書かれているのでその通りに検証します。
LineController.php<?php namespace App\Http\Controllers; use App\Services\LineService; use LINE\LINEBot\SignatureValidator; class LineController extends Controller { public function post() { $signarure = request()->header('X-Line-Signature'); $validateSignature = SignatureValidator::validateSignature($httpRequestBody, $channelSecret, $signarure); if ($validateSignature) { return response()->json(200); } else { abort(400); } } }とりあえずsignatureの検証ができれば
200
、できなければ400
を返すようにします。この
SignatureValidator::validateSignature
が何をしているのかと言うと、
リクエストボディのダイジェスト値をbase64にエンコードして、リクエストヘッダーのX-Line-Signatureと比較しています。SignatureValidator.phppublic static function validateSignature($body, $channelSecret, $signature) { if (!isset($signature)) { throw new InvalidSignatureException('Signature must not be empty'); } return hash_equals(base64_encode(hash_hmac('sha256', $body, $channelSecret, true)), $signature); }こういうことです?
$channelSecret = config('services.line.secret'); $httpRequestBody = request()->getContent(); $hash = hash_hmac('sha256', $httpRequestBody, $channelSecret, true); $signature = base64_encode($hash);Webhook URLを登録する
次LINEのコントロールパネルからWebhook URLを登録します。
herokuのurl(×××××.herokuapp.com/line)登録して、verifyボタンを押します。
success
というメッセージがでてきたらOKです?
コントローラで200を返さないようにしないとエラーになります。テキストメッセージが送られてきたら返事を返してみる
とりあえず、テキストメッセージが送られてきたら
こんにちは!
と返してみます。LineController.php$bot = LineService::lineSdk(); try{ $events = $bot->parseEventRequest($httpRequestBody, $signature); foreach ($events as $event) { if ($event instanceof LINE\LINEBot\Event\MessageEvent\TextMessage) { $bot->replyText($event->getReplyToken(), 'こんにちは!'); } } }catch(\Exception $e){ Log::debug($e); }
parseEventRequest
で署名が正当であるか検証して、正当であればリクエストをパースします。webhookで送られてくるイベントはいろいろ種類がある↓?
Webhookイベントオブジェクト
SDK送られてきたイベントがテキストメッセージであるかは
$event instanceof LINE\LINEBot\Event\MessageEvent\TextMessage
のインスタンスであるかで確認します。応答できるリクエストには応答トークンというものが発行されるので、
$event->getReplyToken()
で取得して、応答メッセージと一緒に$bot->replyText
で返しています?イベントの種類でレスポンスを変えてみる
とりあえず、以下のイベントに対するレスポンスを実装します。
・フォローイベント
・フォロー解除イベント
・スタンプメッセージイベント
・テキストメッセージイベント
・位置情報メッセージイベントLineController.phptry{ $events = $bot->parseEventRequest($httpRequestBody, $signature); foreach ($events as $event) { //フォローイベント if($event instanceof FollowEvent){ $followMessage = '友達登録ありがとう!何が食べたいか入力してね。近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)'; $bot->replyText($event->getReplyToken(), $followMessage); continue; } //フォロー解除イベント else if ($event instanceof UnfollowEvent) { continue; } //スタンプメッセージイベント else if($event instanceof StickerMessage){ $stampMessage = '可愛いスタンプだね〜!何が食べたいか入力してくれたら近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)' $bot->replyText($event->getReplyToken(), $stampMessage); continue; } //テキストメッセージイベント else if($event instanceof TextMessage){ //TODO テキストメッセージ実装 } //位置情報メッセージイベント else if($event instanceof LocationMessage){ //TODO 位置情報実装 } } }catch(\Exception $e){ Log::debug($e); }エイリアスはこちらを参考に??♀️
テキストメッセージに対するリスポンス
このBOTでは最初に何を食べたいか入力してもらってから位置情報を要求します?
餃子か焼肉かを判定したいのですが、
餃子、ぎょうざ、ぎょーざ、ギョーザ
などいろいろなパターンの餃子と焼肉に対応したいと思います。
単純にif文に全部ぶち込みます。多分よくない?LineService.php/** * 入力された文字が餃子か判定 * * @param $text * @return bool */ public function isGyoza($text) { return ($text === '餃子' || $text === 'ぎょうざ' || $text === 'ぎょーざ' || $text === 'ギョーザ' || $text === 'ギョーザ'); } /** * 入力された文字が餃子か判定 * * @param $text * @return bool */ public function isYakiniku($text) { return ($text === '焼肉' || $text === '焼き肉' || $text === 'やきにく' || $text === 'ヤキニク' || $text === 'ヤキニク'); }焼肉か餃子かの結果はここで受け取ります。↓
焼肉か餃子が入力された場合は後述するrequireLocation
で位置情報を求めます?
また、putCategory
でDBにuser_id
とcategory
を保存するようにしました。
category
は餃子であれば1、焼肉は2が保存されます。
もし他の単語が入力された場合は、テキストメッセージを返します?LineService.php/** * 送られてきたメッセージに対するレスポンス * * @param $bot * @param $event * @return void */ public function getMessage($bot, $event) { try{ $text = $event->getText(); if ($this->isGyoza($text)) { //DBに保存 $user_id = $event->getUserId(); $category = Line::GYOZA; $this->putCategory($user_id,$category); //位置情報を求める $word = '餃子'; $this->requireLocation($bot, $event, $word); } else if ($this->isYakiniku($text)) { //DBに保存 $user_id = $event->getUserId(); $category = Line::YAKINIKU; $this->putCategory($user_id,$category); //位置情報を求める $word = '焼肉'; $this->requireLocation($bot, $event, $word); }else{ $bot->replyText($event->getReplyToken(), '焼肉か餃子しか調べられないよ、、ごめんね。'); } }catch(\Exception $e){ Log::debug($e); } } /** * カテゴリーとユーザーIDをDBに保存 * * @param $user_id * @param $category * @return void */ public function putCategory($user_id, $category) { Line::create([ 'user_id' => $user_id, 'category' => $category ]); } /** * 位置情報を求めるメッセージを送る * * @param $bot * @param $event * @param $word //餃子か焼肉か * @return void */ public function requireLocation($bot, $event, $word) { $uri = new UriTemplateActionBuilder('現在地を送る!', 'line://nv/location'); $message = new ButtonTemplateBuilder(null, $word.'が食べたいんだね!今どこにいるか教えてほしいな!', null, [$uri]); $bot->replyMessage($event->getReplyToken(), new TemplateMessageBuilder('位置情報を送ってね', $message)); }・UriTemplateActionBuilder
このアクションが関連づけられたコントロールがタップされると、第二引数に登録されているURLが開きます。
line://nv/location
は、lineで位置情報が開かれます。・ButtonTemplateBuilder
ボタンテンプレートが作られます。引数は、タイトル、本文、画像URL、アクションの順番です。
アクションは配列にしなきゃダメなので注意!・TemplateMessageBuilder
テンプレートメッセージを作ります。引数は代替テキスト、ButtonTemplateBuilderです。送られた住所から経度と緯度を取得する
getLatitude()
とgetLongitude()
という関数で緯度経度が取れるので、これをつかいます?♀️
https://line.github.io/line-bot-sdk-php/source-class-LINE.LINEBot.Event.MessageEvent.LocationMessage.html#65-68LineController.phptry{ $events = $bot->parseEventRequest($httpRequestBody, $signature); foreach ($events as $event) { //フォローイベント if($event instanceof FollowEvent){ $followMessage = '友達登録ありがとう!何が食べたいか入力してね。近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)'; $bot->replyText($event->getReplyToken(), $followMessage); continue; } //フォロー解除イベント else if ($event instanceof UnfollowEvent) { continue; } //スタンプメッセージイベント else if($event instanceof StickerMessage){ $stampMessage = '可愛いスタンプだね〜!何が食べたいか入力してくれたら近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)'; $bot->replyText($event->getReplyToken(), $stampMessage); continue; } //テキストメッセージイベント else if($event instanceof TextMessage){ (new LineService())->getMessage($bot,$event); continue; } //位置情報メッセージイベント else if($event instanceof LocationMessage){ (new GurunaviService())->returnGurunaviList($bot, $event,$event->getLatitude(), $event->getLongitude()); //これを追加 } else{ break; } }ぐるなびから情報を取得する
では
returnGurunaviList
を実装します。
レストラン検索APIを使用します。必要なパラメータはこのくらい??↓
・keyid(ぐるなびから与えられるアクセスキー)
・category_s(カテゴリーコード)
https://api.gnavi.co.jp/api/manual/categorysmaster/
このAPIを叩けばカテゴリーコードの一覧が取得できます。・latitude(緯度)
・longitude(経度)
・range(緯度/経度からの検索範囲(半径))
1:300m、2:500m、3:1000m、4:2000m、5:3000mって感じです。最大3000m?GurunabiService.php/** * * カテゴリーが存在すればぐるなびで検索をかける * * @param $bot * @param $event * @param $lat * @param $lng */ public function returnGurunaviList($bot, $event, $lat, $lng) { //DBにカテゴリーがあるか検証 $category = Line::where(['user_id' => $event->getUserId()])->latest()->first(); if ($category) { $getGurunaviResult = $this->getGurunavi($lat, $lng, $category); if (property_exists($getGurunaviResult, 'error')) { $bot->replyText($event->getReplyToken(), 'お店が見つからなかったよ〜、ごめんね。'); } else { $category->delete(); (new FlexMessage())->returnFlexMessage($event, $getGurunaviResult); } } else { $bot->replyText($event->getReplyToken(), '先に何が食べたいか教えてね!'); } } /** * * ぐるなびで検索 * * @param $lat * @param $lng * @param $category * @return mixed */ public static function getGurunavi($lat, $lng, $category) { $endpoint = 'https://api.gnavi.co.jp/RestSearchAPI/v3/'; //自分のアクセスキー $keyId = config('services.gurunavi.key'); //検索範囲 $range = 5; //カテゴリーコードを取得 if ($category->category === 1) { $categoryCode = 'RSFST14008'; } else { if ($category->category === 2) { $categoryCode = 'RSFST05001'; } else { $categoryCode = 'RSFST05003'; } } $url = $endpoint.'?keyid='.$keyId.'&category_s='.$categoryCode.'&latitude='.$lat.'&longitude='.$lng.'&range='.$range; $json = file_get_contents($url); return json_decode($json); }
returnGurunaviList
でカテゴリーがあることを検証してから、getGurunavi
でぐるなび検索をかけています。DBに保存してあったカテゴリーをぐるなびのカテゴリーコードと照らし合わせています。(最初からぐるなびのカテゴリーコードを格納したほうがいいですね?)
ちなみにここまできてカテゴリーが餃子と焼肉以外だったらたこ焼きのカテゴリーコードで検索がかかるようにしました?
そして検索結果はjsonで返ってくるので、デコードして返します。検索結果は
returnFlexMessage
で色々といじって返します?♀️食べログの検索結果をFlex Messageに変換して送る
Flex Messageはjsonで書くっぽいのですが、絶対できないと思ったので、
LINE Bot Designerを入れました。LINE Bot DesignerでFlex Messageのテンプレートデザインを確認しながら作成し、jsonを配列に変換します。
先ほどのデコードした検索結果を
returnFlexMessage
で受け取って、generateFlexTemplateContent
でいじくりまわして最終的にcurlでPOSTします。
ここにヘッダーとかボディとかかいてあります?flexMessage.php/** * ぐるなびの情報を送信 * * @param $event * @param $gurunavi * @return void */ public function returnFlexMessage($event,$gurunavi) { $token = config('services.line.token'); $postJson = $this->generateFlexTemplateContent($gurunavi); $result = json_encode(['replyToken' => $event->getReplyToken(), 'messages' => [$postJson]]); $curl = curl_init(); //curl_exec() の返り値を文字列で返す curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //POSTリクエスト curl_setopt($curl, CURLOPT_POST, true); //ヘッダを指定 curl_setopt($curl, CURLOPT_HTTPHEADER, array('Authorization: Bearer '.$token, 'Content-type: application/json')); //リクエストURL curl_setopt($curl, CURLOPT_URL, 'https://api.line.me/v2/bot/message/reply'); //送信するデータ curl_setopt($curl, CURLOPT_POSTFIELDS, $result); $curlResult = curl_exec($curl); curl_close($curl); return $curlResult; }
generateFlexTemplateContent
では取得したぐるなびの情報をeachでまわして、Flex Messageのテンプレートに当てはめています。
テンプレートはjsonで書くのですが、LINE Bot Designerを使うと、デザインを確認しながら簡単にjsonを作れます。あとはここを参考にjsonの形を整えます。?♀️
flexMessage.php/** * ぐるなびの情報をflexMessageテンプレートにあてめる * * @param $gurunavis * @return array */ private function generateFlexTemplateContent($gurunavis) { $lists = []; foreach ($gurunavis->rest as $rest) { $lists[] = $this->getFlexTemplate($rest); } $contents = ["type" => "carousel", "contents" => $lists]; return ['type' => 'flex', 'altText' => 'searchResult', 'contents' => $contents]; } /** * flexMessageテンプレート * * @param $gurunavi * @return array */ private function getFlexTemplate($gurunavi) { return [ "type" => "bubble", "hero" => [ "type" => "image", "url" => $gurunavi->image_url->shop_image1 ? $gurunavi->image_url->shop_image1: "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_1_cafe.png", "size" => "full", "aspectRatio" => "20:13", "aspectMode" => "cover", "action" => [ "type" => "uri", "label" => "Line", "uri" => $gurunavi->url_mobile ? $gurunavi->url_mobile : '不明', ] ], "body" => [ "type" => "box", "layout" => "vertical", "contents" => [ [ "type" => "text", "text" => $gurunavi->name ? $gurunavi->name : '不明', "size" => "xl", "weight" => "bold", "wrap" => true ], [ "type" => "box", "layout" => "baseline", "margin" => "md", "contents" => [ [ "type" => "text", "text" => $gurunavi->opentime ? $gurunavi->opentime : '不明', "flex" => 0, "margin" => "md", "size" => "sm", "color" => "#999999", "wrap" => true ] ] ], [ "type" => "box", "layout" => "vertical", "spacing" => "sm", "margin" => "lg", "contents" => [ [ "type" => "box", "layout" => "baseline", "spacing" => "sm", "contents" => [ [ "type" => "text", "text" => "種類", "flex" => 1, "size" => "sm", "color" => "#AAAAAA" ], [ "type" => "text", "text" => $gurunavi->code->category_name_l[0] ? $gurunavi->code->category_name_l[0] : '不明', "flex" => 5, "size" => "sm", "color" => "#666666", "wrap" => true ] ] ], [ "type" => "box", "layout" => "baseline", "spacing" => "sm", "contents" => [ [ "type" => "text", "text" => "場所", "flex" => 1, "size" => "sm", "color" => "#AAAAAA" ], [ "type" => "text", "text" => $gurunavi->access->station.'徒歩'.$gurunavi->access->walk.'分' ? $gurunavi->access->station.'徒歩'.$gurunavi->access->walk.'分' : '不明', "flex" => 5, "size" => "sm", "color" => "#666666", "wrap" => true ] ] ], [ "type" => "box", "layout" => "baseline", "spacing" => "sm", "contents" => [ [ "type" => "text", "text" => "駐車場", "flex" => 2, "size" => "sm", "color" => "#AAAAAA" ], [ "type" => "text", "text" => $gurunavi->parking_lots ? $gurunavi->parking_lots : '不明', "flex" => 5, "size" => "sm", "color" => "#666666", "wrap" => true ] ] ] ] ] ] ], "footer" => [ "type" => "box", "layout" => "vertical", "flex" => 0, "spacing" => "sm", "contents" => [ [ "type" => "button", "action" => [ "type" => "uri", "label" => "ぐるなびで見る", "uri" => $gurunavi->url_mobile ? $gurunavi->url_mobile : '不明' ], "height" => "sm", "style" => "link" ], [ "type" => "spacer", "size" => "sm" ] ] ] ]; } }完成?
エンジニアになって1ヶ月目に作ろうとして挫折して、4ヶ月経ったのでもう一回やってみたら割とできてびっくりしました。
まだまだきれいなソースコード書けないし、サービスクラスの使い分けやメソッドの分け方もきっとぐちゃぐちゃなのでこれからも精進します?LINEのリファレンスすごくわかりやすかった!
何か違うよ!ってところがあれば教えてください?
- 投稿日:2020-07-07T17:54:26+09:00
[Laravel]位置情報を送ると近くの餃子か焼肉のお店を教えてくれるLINE BOTを作った
経緯
なんか面白い物が作りたいと思い、位置情報を送ると近くの餃子屋さんと焼肉屋さんを教えてくれるLINE BOTを作りました。
なぜ餃子と焼肉かと言うと、私が好きだからです。参考
【公式リファレンス】
https://developers.line.biz/ja/docs/messaging-api【LINE SDK for PHP】
https://github.com/line/line-bot-sdk-php【[LINE Bot] 位置情報から食べログ3.5以上の優良店を検索するbot作った】
(これをお手本につくりました。)
https://qiita.com/NARI_Creator/items/f29112e6f604c86b3c0d【ぐるなびAPIの取得方法 そのまま使えるソースコード付き】
https://enjoy-surfing.com/gurunavi/【LineBotを作るときの雛形 for Laravel】
https://qiita.com/sh-ogawa/items/2238e579d7ee538025a0【LaravelでLINEにチャットボットをつくる(QRコード作成)】
https://blog.capilano-fw.com/?p=4285#i-4大変参考になりました?ありがとうございます。
仕様
1.餃子が食べたいか焼肉が食べたいか入力する
2.位置情報を送信する
3.近くのお店を教えてくれる簡単な仕様はこんな感じです。?
作成前にやっておくこと
herokuのアプリケーションを作る
ここら辺みておけばできるんじゃないかなーと思います。
Heroku アカウント登録してデプロイするまでの簡単な使い方
Laravelをherokuにデプロイする(データベースはMySQL)herokuはクレカを登録すると無料で1ヶ月あたり1000時間使えます。
無料プランだと30分ごとにスリープしてしまうのでスリープ後の初アクセスはレスポンスが遅いですが、バッチ処理をしておけば問題ないみたいです。LINE Message APIの登録
LINE BOTのアカウントを作ります。
公式ドキュメント
公式が割と丁寧に説明してくれてるので、見ながらやったらできると思います。実装
LINE Messaging API SDKをインストールする
$ composer require linecorp/line-bot-sdk環境変数を追加する
#line LINE_SECRET_TOKEN = LINE_ACCESS_TOKEN =トークンはアカウントのBasic Settingから参照できます。
ルートの作成
ユーザーがLINE BOTにメッセージを送ると、LINEプラットフォームから指定されたアプリケーションのURLにHTTPリクエストを送るので、そのURLを決めます。
web.phpRoute::post('line','LineController@post')->name('line.post');ちなみにリクエストはPOSTで送られます?
あとついでにControllerも作っちゃいます。クライアントを作成する
今回はサービスクラスにまとめました。
LineService.php<?php namespace App\Services; use LINE\LINEBot; use LINE\LINEBot\HTTPClient\CurlHTTPClient; class LineService { /** * linesdkの使用開始 * * @return LINEBot */ public static function lineSdk() { $token = config('services.line.token'); $secret = config('services.line.secret'); $httpClient = new CurlHTTPClient($token); $bot = new LINEBot($httpClient,['channelSecret' => $secret]); return $bot; } }
$token
と$service
は.envに登録したLINE_SECRET_TOKEN
とLINE_ACCESS_TOKEN
ですが、configのserviceに登録してからもってきてます。基本公式のREADMEに書いてあるまんまですね?
署名を検証する
ラインプラットフォーム以外のわるものから危険なリクエストが送られてくる可能性があるので、検証する必要があります。
ラインのプラットフォームからリクエストが送られてきた時は、リクエストヘッダーにX-Line-Signature
というものが含まれます。これを検証するのですが、チャネルシークレットを秘密鍵として、HMAC-SHA256アルゴリズムを使用してリクエストボディのダイジェスト値を取得します。
ダイジェスト値をBase64エンコードした値と、リクエストヘッダーのX-Line-Signatureに含まれる署名が一致することを確認します。と丁寧に書かれているのでその通りに検証します。
LineController.php<?php namespace App\Http\Controllers; use App\Services\LineService; use LINE\LINEBot\SignatureValidator; class LineController extends Controller { public function post() { $signarure = request()->header('X-Line-Signature'); $validateSignature = SignatureValidator::validateSignature($httpRequestBody, $channelSecret, $signarure); if ($validateSignature) { return response()->json(200); } else { abort(400); } } }とりあえずsignatureの検証ができれば
200
、できなければ400
を返すようにします。この
SignatureValidator::validateSignature
が何をしているのかと言うと、
リクエストボディのダイジェスト値をbase64にエンコードして、リクエストヘッダーのX-Line-Signatureと比較しています。SignatureValidator.phppublic static function validateSignature($body, $channelSecret, $signature) { if (!isset($signature)) { throw new InvalidSignatureException('Signature must not be empty'); } return hash_equals(base64_encode(hash_hmac('sha256', $body, $channelSecret, true)), $signature); }こういうことです?
$channelSecret = config('services.line.secret'); $httpRequestBody = request()->getContent(); $hash = hash_hmac('sha256', $httpRequestBody, $channelSecret, true); $signature = base64_encode($hash);Webhook URLを登録する
次LINEのコントロールパネルからWebhook URLを登録します。
herokuのurl(×××××.herokuapp.com/line)登録して、verifyボタンを押します。
success
というメッセージがでてきたらOKです?
コントローラで200を返さないようにしないとエラーになります。テキストメッセージが送られてきたら返事を返してみる
とりあえず、テキストメッセージが送られてきたら
こんにちは!
と返してみます。LineController.php$bot = LineService::lineSdk(); try{ $events = $bot->parseEventRequest($httpRequestBody, $signature); foreach ($events as $event) { if ($event instanceof LINE\LINEBot\Event\MessageEvent\TextMessage) { $bot->replyText($event->getReplyToken(), 'こんにちは!'); } } }catch(\Exception $e){ Log::debug($e); }
parseEventRequest
で署名が正当であるか検証して、正当であればリクエストをパースします。webhookで送られてくるイベントはいろいろ種類がある↓?
Webhookイベントオブジェクト
SDK送られてきたイベントがテキストメッセージであるかは
$event instanceof LINE\LINEBot\Event\MessageEvent\TextMessage
のインスタンスであるかで確認します。応答できるリクエストには応答トークンというものが発行されるので、
$event->getReplyToken()
で取得して、応答メッセージと一緒に$bot->replyText
で返しています?イベントの種類でレスポンスを変えてみる
とりあえず、以下のイベントに対するレスポンスを実装します。
・フォローイベント
・フォロー解除イベント
・スタンプメッセージイベント
・テキストメッセージイベント
・位置情報メッセージイベントLineController.phptry{ $events = $bot->parseEventRequest($httpRequestBody, $signature); foreach ($events as $event) { //フォローイベント if($event instanceof FollowEvent){ $followMessage = '友達登録ありがとう!何が食べたいか入力してね。近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)'; $bot->replyText($event->getReplyToken(), $followMessage); continue; } //フォロー解除イベント else if ($event instanceof UnfollowEvent) { continue; } //スタンプメッセージイベント else if($event instanceof StickerMessage){ $stampMessage = '可愛いスタンプだね〜!何が食べたいか入力してくれたら近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)' $bot->replyText($event->getReplyToken(), $stampMessage); continue; } //テキストメッセージイベント else if($event instanceof TextMessage){ //TODO テキストメッセージ実装 } //位置情報メッセージイベント else if($event instanceof LocationMessage){ //TODO 位置情報実装 } } }catch(\Exception $e){ Log::debug($e); }エイリアスはこちらを参考に??♀️
テキストメッセージに対するリスポンス
このBOTでは最初に何を食べたいか入力してもらってから位置情報を要求します?
餃子か焼肉かを判定したいのですが、
餃子、ぎょうざ、ぎょーざ、ギョーザ
などいろいろなパターンの餃子と焼肉に対応したいと思います。
単純にif文に全部ぶち込みます。多分よくない?LineService.php/** * 入力された文字が餃子か判定 * * @param $text * @return bool */ public function isGyoza($text) { return ($text === '餃子' || $text === 'ぎょうざ' || $text === 'ぎょーざ' || $text === 'ギョーザ' || $text === 'ギョーザ'); } /** * 入力された文字が餃子か判定 * * @param $text * @return bool */ public function isYakiniku($text) { return ($text === '焼肉' || $text === '焼き肉' || $text === 'やきにく' || $text === 'ヤキニク' || $text === 'ヤキニク'); }焼肉か餃子かの結果はここで受け取ります。↓
焼肉か餃子が入力された場合は後述するrequireLocation
で位置情報を求めます?
また、putCategory
でDBにuser_id
とcategory
を保存するようにしました。
category
は餃子であれば1、焼肉は2が保存されます。
もし他の単語が入力された場合は、テキストメッセージを返します?LineService.php/** * 送られてきたメッセージに対するレスポンス * * @param $bot * @param $event * @return void */ public function getMessage($bot, $event) { try{ $text = $event->getText(); if ($this->isGyoza($text)) { //DBに保存 $user_id = $event->getUserId(); $category = Line::GYOZA; $this->putCategory($user_id,$category); //位置情報を求める $word = '餃子'; $this->requireLocation($bot, $event, $word); } else if ($this->isYakiniku($text)) { //DBに保存 $user_id = $event->getUserId(); $category = Line::YAKINIKU; $this->putCategory($user_id,$category); //位置情報を求める $word = '焼肉'; $this->requireLocation($bot, $event, $word); }else{ $bot->replyText($event->getReplyToken(), '焼肉か餃子しか調べられないよ、、ごめんね。'); } }catch(\Exception $e){ Log::debug($e); } } /** * カテゴリーとユーザーIDをDBに保存 * * @param $user_id * @param $category * @return void */ public function putCategory($user_id, $category) { Line::create([ 'user_id' => $user_id, 'category' => $category ]); } /** * 位置情報を求めるメッセージを送る * * @param $bot * @param $event * @param $word //餃子か焼肉か * @return void */ public function requireLocation($bot, $event, $word) { $uri = new UriTemplateActionBuilder('現在地を送る!', 'line://nv/location'); $message = new ButtonTemplateBuilder(null, $word.'が食べたいんだね!今どこにいるか教えてほしいな!', null, [$uri]); $bot->replyMessage($event->getReplyToken(), new TemplateMessageBuilder('位置情報を送ってね', $message)); }・UriTemplateActionBuilder
このアクションが関連づけられたコントロールがタップされると、第二引数に登録されているURLが開きます。
line://nv/location
は、lineで位置情報が開かれます。・ButtonTemplateBuilder
ボタンテンプレートが作られます。引数は、タイトル、本文、画像URL、アクションの順番です。
アクションは配列にしなきゃダメなので注意!・TemplateMessageBuilder
テンプレートメッセージを作ります。引数は代替テキスト、ButtonTemplateBuilderです。送られた住所から経度と緯度を取得する
getLatitude()
とgetLongitude()
という関数で緯度経度が取れるので、これをつかいます?♀️
https://line.github.io/line-bot-sdk-php/source-class-LINE.LINEBot.Event.MessageEvent.LocationMessage.html#65-68LineController.phptry{ $events = $bot->parseEventRequest($httpRequestBody, $signature); foreach ($events as $event) { //フォローイベント if($event instanceof FollowEvent){ $followMessage = '友達登録ありがとう!何が食べたいか入力してね。近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)'; $bot->replyText($event->getReplyToken(), $followMessage); continue; } //フォロー解除イベント else if ($event instanceof UnfollowEvent) { continue; } //スタンプメッセージイベント else if($event instanceof StickerMessage){ $stampMessage = '可愛いスタンプだね〜!何が食べたいか入力してくれたら近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)'; $bot->replyText($event->getReplyToken(), $stampMessage); continue; } //テキストメッセージイベント else if($event instanceof TextMessage){ (new LineService())->getMessage($bot,$event); continue; } //位置情報メッセージイベント else if($event instanceof LocationMessage){ (new GurunaviService())->returnGurunaviList($bot, $event,$event->getLatitude(), $event->getLongitude()); //これを追加 } else{ break; } }ぐるなびから情報を取得する
では
returnGurunaviList
を実装します。
レストラン検索APIを使用します。必要なパラメータはこのくらい??↓
・keyid(ぐるなびから与えられるアクセスキー)
・category_s(カテゴリーコード)
https://api.gnavi.co.jp/api/manual/categorysmaster/
このAPIを叩けばカテゴリーコードの一覧が取得できます。・latitude(緯度)
・longitude(経度)
・range(緯度/経度からの検索範囲(半径))
1:300m、2:500m、3:1000m、4:2000m、5:3000mって感じです。最大3000m?GurunabiService.php/** * * カテゴリーが存在すればぐるなびで検索をかける * * @param $bot * @param $event * @param $lat * @param $lng */ public function returnGurunaviList($bot, $event, $lat, $lng) { //DBにカテゴリーがあるか検証 $category = Line::where(['user_id' => $event->getUserId()])->latest()->first(); if ($category) { $getGurunaviResult = $this->getGurunavi($lat, $lng, $category); if (property_exists($getGurunaviResult, 'error')) { $bot->replyText($event->getReplyToken(), 'お店が見つからなかったよ〜、ごめんね。'); } else { $category->delete(); (new FlexMessage())->returnFlexMessage($event, $getGurunaviResult); } } else { $bot->replyText($event->getReplyToken(), '先に何が食べたいか教えてね!'); } } /** * * ぐるなびで検索 * * @param $lat * @param $lng * @param $category * @return mixed */ public static function getGurunavi($lat, $lng, $category) { $endpoint = 'https://api.gnavi.co.jp/RestSearchAPI/v3/'; //自分のアクセスキー $keyId = config('services.gurunavi.key'); //検索範囲 $range = 5; //カテゴリーコードを取得 if ($category->category === 1) { $categoryCode = 'RSFST14008'; } else { if ($category->category === 2) { $categoryCode = 'RSFST05001'; } else { $categoryCode = 'RSFST05003'; } } $url = $endpoint.'?keyid='.$keyId.'&category_s='.$categoryCode.'&latitude='.$lat.'&longitude='.$lng.'&range='.$range; $json = file_get_contents($url); return json_decode($json); }
returnGurunaviList
でカテゴリーがあることを検証してから、getGurunavi
でぐるなび検索をかけています。DBに保存してあったカテゴリーをぐるなびのカテゴリーコードと照らし合わせています。(最初からぐるなびのカテゴリーコードを格納したほうがいいですね?)
ちなみにここまできてカテゴリーが餃子と焼肉以外だったらたこ焼きのカテゴリーコードで検索がかかるようにしました?
そして検索結果はjsonで返ってくるので、デコードして返します。検索結果は
returnFlexMessage
で色々といじって返します?♀️食べログの検索結果をFlex Messageに変換して送る
Flex Messageはjsonで書くっぽいのですが、絶対できないと思ったので、
LINE Bot Designerを入れました。LINE Bot DesignerでFlex Messageのテンプレートデザインを確認しながら作成し、jsonを配列に変換します。
先ほどのデコードした検索結果を
returnFlexMessage
で受け取って、generateFlexTemplateContent
でいじくりまわして最終的にcurlでPOSTします。
ここにヘッダーとかボディとかかいてあります?flexMessage.php/** * ぐるなびの情報を送信 * * @param $event * @param $gurunavi * @return void */ public function returnFlexMessage($event,$gurunavi) { $token = config('services.line.token'); $postJson = $this->generateFlexTemplateContent($gurunavi); $result = json_encode(['replyToken' => $event->getReplyToken(), 'messages' => [$postJson]]); $curl = curl_init(); //curl_exec() の返り値を文字列で返す curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //POSTリクエスト curl_setopt($curl, CURLOPT_POST, true); //ヘッダを指定 curl_setopt($curl, CURLOPT_HTTPHEADER, array('Authorization: Bearer '.$token, 'Content-type: application/json')); //リクエストURL curl_setopt($curl, CURLOPT_URL, 'https://api.line.me/v2/bot/message/reply'); //送信するデータ curl_setopt($curl, CURLOPT_POSTFIELDS, $result); $curlResult = curl_exec($curl); curl_close($curl); return $curlResult; }
generateFlexTemplateContent
では取得したぐるなびの情報をeachでまわして、Flex Messageのテンプレートに当てはめています。
テンプレートはjsonで書くのですが、LINE Bot Designerを使うと、デザインを確認しながら簡単にjsonを作れます。あとはここを参考にjsonの形を整えます。?♀️
flexMessage.php/** * ぐるなびの情報をflexMessageテンプレートにあてめる * * @param $gurunavis * @return array */ private function generateFlexTemplateContent($gurunavis) { $lists = []; foreach ($gurunavis->rest as $rest) { $lists[] = $this->getFlexTemplate($rest); } $contents = ["type" => "carousel", "contents" => $lists]; return ['type' => 'flex', 'altText' => 'searchResult', 'contents' => $contents]; } /** * flexMessageテンプレート * * @param $gurunavi * @return array */ private function getFlexTemplate($gurunavi) { return [ "type" => "bubble", "hero" => [ "type" => "image", "url" => $gurunavi->image_url->shop_image1 ? $gurunavi->image_url->shop_image1: "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_1_cafe.png", "size" => "full", "aspectRatio" => "20:13", "aspectMode" => "cover", "action" => [ "type" => "uri", "label" => "Line", "uri" => $gurunavi->url_mobile ? $gurunavi->url_mobile : '不明', ] ], "body" => [ "type" => "box", "layout" => "vertical", "contents" => [ [ "type" => "text", "text" => $gurunavi->name ? $gurunavi->name : '不明', "size" => "xl", "weight" => "bold", "wrap" => true ], [ "type" => "box", "layout" => "baseline", "margin" => "md", "contents" => [ [ "type" => "text", "text" => $gurunavi->opentime ? $gurunavi->opentime : '不明', "flex" => 0, "margin" => "md", "size" => "sm", "color" => "#999999", "wrap" => true ] ] ], [ "type" => "box", "layout" => "vertical", "spacing" => "sm", "margin" => "lg", "contents" => [ [ "type" => "box", "layout" => "baseline", "spacing" => "sm", "contents" => [ [ "type" => "text", "text" => "種類", "flex" => 1, "size" => "sm", "color" => "#AAAAAA" ], [ "type" => "text", "text" => $gurunavi->code->category_name_l[0] ? $gurunavi->code->category_name_l[0] : '不明', "flex" => 5, "size" => "sm", "color" => "#666666", "wrap" => true ] ] ], [ "type" => "box", "layout" => "baseline", "spacing" => "sm", "contents" => [ [ "type" => "text", "text" => "場所", "flex" => 1, "size" => "sm", "color" => "#AAAAAA" ], [ "type" => "text", "text" => $gurunavi->access->station.'徒歩'.$gurunavi->access->walk.'分' ? $gurunavi->access->station.'徒歩'.$gurunavi->access->walk.'分' : '不明', "flex" => 5, "size" => "sm", "color" => "#666666", "wrap" => true ] ] ], [ "type" => "box", "layout" => "baseline", "spacing" => "sm", "contents" => [ [ "type" => "text", "text" => "駐車場", "flex" => 2, "size" => "sm", "color" => "#AAAAAA" ], [ "type" => "text", "text" => $gurunavi->parking_lots ? $gurunavi->parking_lots : '不明', "flex" => 5, "size" => "sm", "color" => "#666666", "wrap" => true ] ] ] ] ] ] ], "footer" => [ "type" => "box", "layout" => "vertical", "flex" => 0, "spacing" => "sm", "contents" => [ [ "type" => "button", "action" => [ "type" => "uri", "label" => "ぐるなびで見る", "uri" => $gurunavi->url_mobile ? $gurunavi->url_mobile : '不明' ], "height" => "sm", "style" => "link" ], [ "type" => "spacer", "size" => "sm" ] ] ] ]; } }完成?
エンジニアになって1ヶ月目に作ろうとして挫折して、4ヶ月経ったのでもう一回やってみたら割とできてびっくりしました。
まだまだきれいなソースコード書けないし、サービスクラスの使い分けやメソッドの分け方もきっとぐちゃぐちゃなのでこれからも精進します?LINEのリファレンスすごくわかりやすかった!
何か違うよ!ってところがあれば教えてください?
- 投稿日:2020-07-07T12:57:06+09:00
laravel 多対多 タグ付け
参考
https://qiita.com/Ioan/items/bac58de02b826ae8e9e9Profile テーブル
Tag テーブルをそれぞれ 多対多 で結びつけたい。
profile の綴りを間違えないように気をつけて。Profile.phppublic function tag(){ return $this->belongsToMany('App\Tag', 'profile_tag', 'profile_id', 'tag_id'); }Tag.phppublic function profile(){ return $this->belongsToMany('App\Profile', 'profile_tag', 'tag_id', 'profile_id'); }データ読み出し
//タグをすべて読みだし、タグがいくつのプロフィールを持っているか取得 $hoge = Tag::withCount('profile') ->orderBy('profile_count', 'desc') ->get(); // タグとともにすべてのデータを取得 $res = Profile::query()//単数形 User ->with('tag') ->get();これでOK。
中間テーブルを複数作れば
User と Profile にTagテーブルのタグをつけれるようになる。User.phppublic function tag(){ return $this->belongsToMany('App\Tag', 'user_tag', 'user_id', 'tag_id'); }Tag.phppublic function user(){ return $this->belongsToMany('App\User', 'user_tag', 'tag_id', 'user_id'); }
- 投稿日:2020-07-07T12:17:21+09:00
Laravel-mixのnpm watch-pollのcpu負荷が重いのでwatch-pollの間隔を変える
windowsのdockerの環境をhyper-vからwsl2に乗り換えた時にnpm run watchが聞かなくなったので、npm run watch-pollに切り替えました。
が、npm run watch-poll をするとcpuの負荷が重すぎてファンがうるさかったので、なんとかcpu負荷を軽減できないかと対策したときのメモ。結論
laravelのプロジェクト直下のpackage.jsonに対して
package.json{ "scripts": { "watch-poll": "npm run watch -- --watch-poll=5000", }, }と時間(watch-poll=5000で5000ms=5sの意味)を指定しましょう。(参考[1])
解説
watchとwatch-pollの違い
そもそもwatchはwebpackの仕組みです。
ファイルを監視し、ファイルの変更があった時に差分だけをトランスパイル先にアップロードしてくれます。
watchのおかげで、何回も全体のトランスパイルを繰り返すことなく、最小限の時間で更新を確認することができます。このwatchは仕組みとしては、ファイル変更のイベントを検知して、それに処理をするようです。
なので、ファイルシステムやパスの等に問題が発生して適切にファイル変更のイベントを受信できなければwatchは機能しません。[注釈1]watchが動作しない環境のために、webpackにはpollというオプションがあります。
webpackのwatchが変更の通知を待つのに対して、pollはwebpackから更新処理を定期的に確認しにいきます。
webpackをラップしているlaravel-mixでは、pollオプション付きのwatchが--watch-pollで使用することができます。watch-pollの処理間隔の仕様
pollingの間隔はデフォルトでは1秒になっています。
webpackにおいて、処理の間隔は変更が可能です。
Laravel-mixでも--watch-poll=(時間) とすればpollingの間隔を変更できます。注釈
- 自分の場合はwsl2のdockerコンテナで稼働するlaravelを使っています。 この場合はおそらくは、vscode等によるwindows上でのファイルの変更がdockerの使っているwsl2上のlinuxに通知されなかったことが原因です(参考[2])。
参考文献
[1]https://github.com/JeffreyWay/laravel-mix/issues/1418
[2]https://github.com/microsoft/WSL/issues/4739
- 投稿日:2020-07-07T05:06:34+09:00
Laravel データベース接続の設定
Laravelで作成したデータベースに接続するための設定を解説します。
database.php
config/database.php
にデータベースの接続設定を記述します。初期設定は
mysql
になっています。
ここを切り替えることで、connections
キー内にあるその他のデータベースも使用することができます。'default' => env('DB_CONNECTION', 'mysql'),使用するデータベースの内容を確認します。
config/database.php'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', ''), 'username' => env('DB_USERNAME', ''), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ],
env()
は、Laravelのヘルパ関数です。
デフォルトでは、.env
に記載した値をdatabase.php
が読み込むようになっています。config/database.php'database' => env('DB_DATABASE', 'quick_laravel'), 'username' => env('DB_USERNAME', 'quickusr'), 'password' => env('DB_PASSWORD', 'qucikpass'),第二引数に値を直接記述することも可能です。
.env
通常は、
.env
にDB環境設定を記述します。.envDB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=quick_laravel DB_USERNAME=quickusr DB_PASSWORD=quickpass
.env
はGitリポジトリのソースコントール外にあるので、環境設定が公開されることはありません。
SQLSTATE[HY000] [2002] Connection refused
等のエラーが表示される場合、上記の設定のどこかが間違っていると考えられます。
.env
を変更しても接続できない場合は、下記の記事を参照してください。
Laravelで.envファイルが反映されない時