- 投稿日:2020-07-07T20:50:37+09:00
web上にある表からSQL文を自動生成する
webサイトなどにある表から一行ごとにINSERT文を自動で生成するプログラムをphpで作成してみました。
実行環境
Windows10
PHP 7.1.33データのコピー
まず、INSERT文にするためのデータをコピーし、適当なファイルに貼り付けます。
例えば、以下のような表がweb上にあるとします。
ID 名前 価格 001 りんご 100円 002 ぶどう 200円 003 いちご 300円 004 バナナ 100円 005 ナシ 250円 006 スイカ 3000円 007 メロン 4000円 008 さくらんぼ 350円 009 モモ 600円 010 レモン 300円 この表のIDと名前だけでSQL文を作成したい場合は、Chromeの拡張機能にある「Copytables」を使うことで、以下の画像のように特定の列や行のみを選択することが可能となります。
コピーができたら、適当な名前でファイルを作成し、貼り付けます。(今回はdata.txt)
SQL文を生成するプログラム
次にPHPファイル(ここではinsert.php)を作成し、下のコードを記述して実行します。(解説は後ほど)
insert.php<?php header("Content-type: text/html; charset=utf-8"); $filename = "data.txt"; $file = file($filename); $fp = fopen("insert_sql.txt","w"); for($i=0; $i<count($file); $i++){ $data = preg_split("/\s/", $file[$i], -1, PREG_SPLIT_NO_EMPTY); $data_values = "'".$data[0]."'"; if(count($data) >= 2){ for($j=1; $j<count($data); $j++){ $data_values .= ", '".$data[$j]."'"; } } $contents = "INSERT INTO test VALUES(".$data_values.");"; fwrite($fp,$contents."\r\n"); } fclose($fp); ?>実行すると以下のように「insert_sql.txt」というファイルが作成され、そのなかにSQL文が記述されています。
ソースコードの解説
PHPファイルに記述したコードの内容について詳しく解説します。
まず、3~5行目ではデータを記述した「data.txt」というファイルをfile()関数を使って 、1行ずつ配列に格納します。その後、fopen()関数で書き込み可能なファイルを新規で作成し、開いています。
insert.php$filename = "data.txt"; $file = file($filename); $fp = fopen("insert_sql.txt","w");次にfor文でデータの行数分だけSQL文を生成する準備をします。
count()関数で配列変数$fileにある要素の数を数えることで、その分だけ処理を繰り返すことができます。insert.phpfor($i=0; $i<count($file); $i++){ }7行目ではpreg_split()関数を使って文字列をある文字列で区切って配列に格納します。
もし、データの区切りがカンマだった場合は第一引数を “/,/” にし、半角スペースだった場合は “/\s/” にしたりします。insert.php$data = preg_split("/\s/", $file[$i], -1, PREG_SPLIT_NO_EMPTY);8~11行目でinsert文の VALUES の中身を作成します。データの1行の中身を文字列で区切って配列に格納した配列変数$dataの要素分だけカンマで繋げていきます。
insert.php$data_value = "'".$data[0]."'"; if(count($data) >= 2){ for($j=1; $j<count($data); $j++){ $data_value .= ", '".$data[$j]."'";その後、SQL文を作成し、変数に格納します。
insert.php$contents = "INSERT INTO test VALUES(".$data_values.");";fwite()関数で最初に作成して開いたファイルにSQL文を記述します。
insert.phpfwrite($fp,$contents."\r\n");最後にfor文の外で開いていたファイルを閉じ、完成です。
insert.phpfclose($fp);ソースコードを少し変えることで、いろんなパターンのSQL文に対応できると思います。
- 投稿日:2020-07-07T20:50:37+09:00
web上にある表やExcelからSQL文を自動生成する
webサイトなどにある表やExcelなどから一行ごとにINSERT文を自動で生成するプログラムをphpで作成してみました。
実行環境
Windows10
PHP 7.1.33データのコピー
まず、INSERT文にするためのデータをコピーし、適当なファイルに貼り付けます。
例えば、以下のような表がweb上にあるとします。
ID 名前 価格 001 りんご 100円 002 ぶどう 200円 003 いちご 300円 004 バナナ 100円 005 ナシ 250円 006 スイカ 3000円 007 メロン 4000円 008 さくらんぼ 350円 009 モモ 600円 010 レモン 300円 この表のIDと名前だけでSQL文を作成したい場合は、Chromeの拡張機能にある「Copytables」を使うことで、以下の画像のように特定の列や行のみを選択することが可能となります。(Excelの場合はctrlキーなどを使ってうまくコピーして下さい)
コピーができたら、適当な名前でファイルを作成し、貼り付けます。(今回はdata.txt)
SQL文を生成するプログラム
次にPHPファイル(ここではinsert.php)を作成し、下のコードを記述して実行します。(解説は後ほど)
insert.php<?php header("Content-type: text/html; charset=utf-8"); $filename = "data.txt"; $file = file($filename); $fp = fopen("insert_sql.txt","w"); for($i=0; $i<count($file); $i++){ $data = preg_split("/\s/", $file[$i], -1, PREG_SPLIT_NO_EMPTY); $data_values = "'".$data[0]."'"; if(count($data) >= 2){ for($j=1; $j<count($data); $j++){ $data_values .= ", '".$data[$j]."'"; } } $contents = "INSERT INTO test VALUES(".$data_values.");"; fwrite($fp,$contents."\r\n"); } fclose($fp); ?>実行すると以下のように「insert_sql.txt」というファイルが作成され、そのなかにSQL文が記述されています。
ソースコードの解説
PHPファイルに記述したコードの内容について詳しく解説します。
まず、3~5行目ではデータを記述した「data.txt」というファイルをfile()関数を使って 、1行ずつ配列に格納します。その後、fopen()関数で書き込み可能なファイルを新規で作成し、開いています。
insert.php$filename = "data.txt"; $file = file($filename); $fp = fopen("insert_sql.txt","w");次にfor文でデータの行数分だけSQL文を生成する準備をします。
count()関数で配列変数$fileにある要素の数を数えることで、その分だけ処理を繰り返すことができます。insert.phpfor($i=0; $i<count($file); $i++){ }7行目ではpreg_split()関数を使って文字列をある文字列で区切って配列に格納します。
もし、データの区切りがカンマだった場合は第一引数を “/,/” にし、半角スペースだった場合は “/\s/” にしたりします。insert.php$data = preg_split("/\s/", $file[$i], -1, PREG_SPLIT_NO_EMPTY);8~11行目でinsert文の VALUES の中身を作成します。データの1行の中身を文字列で区切って配列に格納した配列変数$dataの要素分だけカンマで繋げていきます。
insert.php$data_value = "'".$data[0]."'"; if(count($data) >= 2){ for($j=1; $j<count($data); $j++){ $data_value .= ", '".$data[$j]."'";その後、SQL文を作成し、変数に格納します。
insert.php$contents = "INSERT INTO test VALUES(".$data_values.");";fwite()関数で最初に作成して開いたファイルにSQL文を記述します。
insert.phpfwrite($fp,$contents."\r\n");最後にfor文の外で開いていたファイルを閉じ、完成です。
insert.phpfclose($fp);ソースコードを少し変えることで、いろんなパターンのSQL文に対応できると思います。
- 投稿日: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-07T17:51:44+09:00
【PHP】 Composer コマンドで tty を Docker につなげて開発したい(TTY as Interactive Shell/対話式シェルで tty)
composer
のscripts
設定で、composer dev
と打ったら PHP の Docker コンテナを起動して触りたい。つまりコンテナにソースコードをマウントしてコンテナ内で開発・デバッグできる状態。しかし、
Interactive shell
と表示され終了してしまう。Docker に限らず、他の対話式のシェル・スクリプトでも同じ。「composer ユーザーコマンド 対話式 実行 tty interactive shell」でググってもドンピシャの Qiita 記事が出てこなかったので、自分のググラビリティとして。
TL; DR
[コマンド] < /dev/tty
でtty
をコマンドとつなげる。その際、
Composer\\Config::disableProcessTimeout
も設定しないと、composer
の制限で 300 秒で接続が切れてしまう。TS; DR
以下は3つのユーザー・コマンドを
composer
に設定している例です。composer.json{ ... "scripts" :{ "test": [ "./vendor/bin/phpunit --configuration=./config/phpunit.xml" ], "compile": [ "./vendor/bin/box compile --config=./config/box.json" ], "dev": [ "Composer\\Config::disableProcessTimeout", "docker run --rm --entrypoint='/bin/bash' --workdir='/app' -v $(pwd):/app php:7-cli-alpine < /dev/tty" ] } }
composer test
コマンドで PHPUnit を使ってユニット・テストをローカルで実行。composer compile
コマンドで Box3 を使って./bin
ディレクトリ設置用の Phar アーカイブの作成。そしてcomposer dev
コマンドで Docker の PHP7 コンテナを起動して対話式(bash
シェル)で触れるようにしている例です。この時、
composer dev
のメリットは、任意の PHP バージョンで Docker 上で実行できるので、Travis CI で回す前の簡易テストに便利です。
composer
のユーザー・コマンド経由でターミナルのtty
(入出力)を Docker に繋げるのがうまく行かなかったのですが、< /dev/tty
によりターミナルの tty がコンテナに繋がるようになり、SSH した時のような操作感があるので便利です。? 注意点
ターミナルの種類(macOS のターミナルなど)によってコンテナからexit
するとtty
接続が壊れてターミナルを立ち上げ直さないといけない時があります。これはcomposer
が使っている依存パッケージのsymfony
のconsole
の仕様です。しかし、VSCode のターミナルからだとexit
した以降でもローカルに戻ってきます(VSCode を立ち上げ直さなくても大丈夫です)。これは VSCode 側が、戻ってきたらターミナルを初期化をするからです。参考文献
- Writing Composer Scripts by Greg Anderson @ pantheon.io
- Interactive PHP script with composer @ StackOverflow
- 拡張/プラグイン/ユーザーコマンド/カスタム | 逆引き!Composer コマンド・ライン一覧 @ Qiita
- 投稿日:2020-07-07T17:51:44+09:00
【PHP】 Composer コマンドで tty を Docker につなげて開発したい(Scripts, TTY and Interactive Shell)
composer
のscripts
設定で、composer dev
と打ったら PHP の Docker コンテナを起動して触りたい。つまりコンテナにソースコードをマウントしてコンテナ内で開発・デバッグできる状態。しかし、
Interactive shell
と表示され終了してしまう。Docker に限らず、他の対話式のシェル・スクリプトでも同じ。「composer ユーザーコマンド 対話式 実行 tty interactive shell」でググってもドンピシャの Qiita 記事が出てこなかったので、自分のググラビリティとして。
TL; DR
[コマンド] < /dev/tty
でtty
をコマンドとつなげる。その際、
Composer\\Config::disableProcessTimeout
も設定しないと、composer
の制限で 300 秒で接続が切れてしまう。TS; DR
以下は3つのユーザー・コマンドを
composer
に設定している例です。composer.json{ ... "scripts" :{ "test": [ "./vendor/bin/phpunit --configuration=./config/phpunit.xml" ], "compile": [ "./vendor/bin/box compile --config=./config/box.json" ], "dev": [ "Composer\\Config::disableProcessTimeout", "docker run --rm --entrypoint='/bin/bash' --workdir='/app' -v $(pwd):/app php:7-cli-alpine < /dev/tty" ] } }
composer test
コマンドで PHPUnit を使ってユニット・テストをローカルで実行。composer compile
コマンドで Box3 を使って./bin
ディレクトリ設置用の Phar アーカイブの作成。そしてcomposer dev
コマンドで Docker の PHP7 コンテナを起動して対話式(bash
シェル)で触れるようにしている例です。この時、
composer dev
のメリットは、任意の PHP バージョンで Docker 上で実行できるので、Travis CI で回す前の簡易テストに便利です。
composer
のユーザー・コマンド経由でターミナルのtty
(入出力)を Docker に繋げるのがうまく行かなかったのですが、< /dev/tty
によりターミナルの tty がコンテナに繋がるようになり、SSH した時のような操作感があるので便利です。? 注意点
ターミナルの種類(macOS のターミナルなど)によってコンテナからexit
するとtty
接続が壊れてターミナルを立ち上げ直さないといけない時があります。これはcomposer
が使っている依存パッケージのsymfony
のconsole
の仕様です。しかし、VSCode のターミナルからだとexit
した以降でもローカルに戻ってきます(VSCode を立ち上げ直さなくても大丈夫です)。これは VSCode 側が、戻ってきたらターミナルを初期化をするからです。参考文献
- Writing Composer Scripts by Greg Anderson @ pantheon.io
- Interactive PHP script with composer @ StackOverflow
- 拡張/プラグイン/ユーザーコマンド/カスタム | 逆引き!Composer コマンド・ライン一覧 @ Qiita
- 投稿日:2020-07-07T12:24:33+09:00
PHPのWhile文にif文を書く!
PHPの学習中のアウトプットとして書きます!
例えば変数$xを定義し、変数$xを用いて2〜100までの偶数をechoする場合!
index.php$i = 2; //変数定義 while ($i <= 100){ //$iが100になるまでループ if ($i % 2 == 0){ //if分で$iを2で割った時余り0になる数字をechoする echo $i; echo '<br>'; } $i++; //$iが100になるまで1を足す }結果
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
52
54
56
58
60
62
64
66
68
70
72
74
76
78
80
82
84
86
88
90
92
94
96
98
100このようになります!!
以上です!!!
- 投稿日:2020-07-07T10:35:16+09:00
PHPで配列を比較して差分を抽出するときに使えるメソッド
はじめに
PHPで配列の要素の比較をしたいと思い、調べていたら、「array_diff」というメソッドがあり、使ってみたら便利だったので、備忘録を残します。
使い方(例)
hoge.php<?PHP $array1 = ['a', 'b', 'c']; $array2 = ['a', 'b', 'c']; $array3 = ['b', 'c', 'a']; $array4 = ['a', 'b']; $array5 = ['a', 'b', 'c', 'd']; // 順序、値が一致する場合 $diff1and2 = array_diff($array1, $array2); print_r($diff1and2); // Array() // 順序が不一致、値が一致する場合 $diff1and3 = array_diff($array1, $array3); print_r($diff1and3); // Array() // $array1と$array4の値が不一致($array1に「c」が存在) $diff1and4 = array_diff($array1, $array4); print_r($diff1and4); // Array([2] => c) // $array1と$array5の値が不一致($array5に「d」が存在) $diff1and5 = array_diff($array1, $array5); print_r($diff1and5); // Array()参考
- 投稿日: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ファイルが反映されない時
- 投稿日:2020-07-07T02:07:22+09:00
PHPサーバーで、悪質脆弱性スキャナ(Vulnerability Scanner)や悪いボット(Bad Bots)を制裁します。
悪質脆弱性スキャナ(Vulnerability Scanner)や悪いボット(Bad Bots)とは
悪質脆弱性スキャナ(Vulnerability Scanner)や悪いボット(Bad Bots)とは、許可なくサーバーを強制的にスキャンするプログラム。通常、このようなスキャナは、大規模かつ規則的な規模でインターネット全体をスキャンします。
確かに、安全なサーバー中ではありますが、危害はありません。 しかし、このアクセスログを見るたびに、うっとうしいと思う。
制裁準備
今回は、このような悪質な脆弱性スキャナーと戦うために、非常に古いテクニックを使ってみました。
それは、「GZIP爆弾」です。
この方法は、少なくとも20年以上前からありましたが、今もなお良い解決策を持っていません。
でも、その原因で、このような悪質脆弱性スキャナを制裁するために、この方法を使うのもいいかもしれない。
そのため、私は具体的に「この方法を使う自分のサーバーを守るが犯罪行為ではないか」と弁護士に尋ねました。
(関連記事: 《みんなで逮捕されようプロジェクト》 《消せない画面…不正URL貼り付けた疑いで中1女子ら家宅捜索》)
それに対する弁護士の答えは、「この場合、このウェブアドレスへのアクセスの人はすでに不正アクセスで、つまりこのアクセスの人は自分が本当の犯罪者です。」というものでした。
法律面の準備が整いましたので、次は技術的な準備に取り掛かりましょう。
設定開始
まず、このトラップをデフォルトのホストの中で設定することだけをお勧めします。
何故なら、一般できに、デフォルトのホストの中は何もありません。
そして、普通の悪質脆弱性スキャナはただのデフォルトのホストの中をスキャナする。
もし、本番環境の設定と私の説明するの環境が違ってあれば、ぜひとも諦めていただきたいと思います。そして、サーバー環境のセキュリティ設定が正しいかどうかを確認してください。
そして、もしあなたのサーバーが無制限の転送データ通信量を持っていない場合は、私もそれをお勧めしません。
以下のすべてが Linux オペレーティングシステム上で動作します。
1. GZIP爆弾ファイルを生成する
dd if=/dev/zero bs=1M count=10240 | gzip -9 > /root/bomb.gzip上記のコマンドを使用して、Linuxで10GBサイズのGZIPファイルを生成します。この処理には多少の待ち時間が必要です。
もちろん、私が書いたシェルスクリプトを使って生成することもできます。
ファストモード:
wget https://raw.githubusercontent.com/DeepSkyFire/BadBotsTraps/master/src/Generate.sh && chmod +x Generate.sh && bash Generate.sh -s 10 -m fastノーマルモード:
wget https://raw.githubusercontent.com/DeepSkyFire/BadBotsTraps/master/src/Generate.sh && chmod +x Generate.sh && bash Generate.sh -s 10 -m normalそして、生成されたbomb.gzipファイルの名前を変更します。
2. トラップ充填
先に生成したのGZIP爆弾ファイルの名前は「1ba286020e414afb.gzip」に変更しました。
続きから、PHPファイルを作成してみましょう。
PHPコード:
<?php $GzipFile = '1ba286020e414afb.gzip'; //先に生成したのGZIP爆弾ファイルの名前。 header("Content-Encoding: gzip"); header("Content-Type: text/html; charset=utf-8"); header("Cache-control: no-store"); //CDN環境にキャッシュしないのため、この設定が必要だ。 header("Content-Length: ".filesize($GzipFile)); Header("HTTP/1.1 200 OK"); //'HTTP/1.1 404 Not Found'もできる。好きなコードに設定しよう。 if (ob_get_level()){ ob_end_clean(); //自分のサーバーを傷つけないように。 } readfile($GzipFile); exit; ?>以上、PHPのコントロールファイルが完成しました。名前は「1ba286020e414afb.php」にしました。
3. トラップをインストール
「1ba286020e414afb.php」と「1ba286020e414afb.gzip」この2つのファイルをデフォルトのホストのフォルダに置きます。
そして、nginxの設定ファイル「nginx.conf」を設定する:
server { listen 80 default_server reuseport; ...... error_page 404 403 /1ba286020e414afb.php; //このような設定する ...... }もちろん、Apacheでも設定可能です。デフォルトのホストのフォルダに「.htaccess」ファイルを設定する:
...... errorDocument 404 /1ba286020e414afb.php errorDocument 403 /1ba286020e414afb.php ......以上、すべての設定全部完成しました。
効果は?
今までのテストによると、70%の悪質脆弱性スキャナを1回の訪問したで訪問を停止しています。
しかし、残りの30%はどうでしょうか? それは後だ。
今日は初めてqiitaに日本語で投稿しました。 何か間違いがあったら教えてください、よろしくお願いします。
上記のコードはすべてGithubにあります:《Github DeepSkyFire/BadBotsTraps》
以上です。