20200802のlaravelに関する記事は10件です。

LaravelでWebSocket

目次

Laravelの記事一覧は下記
PHPフレームワークLaravelの使い方

Laravelバージョン

動作確認はLaravel Framework 7.19.1で行っています

前提条件

eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています

LaravelでDIを使う
本記事は上記で作成したフォルダとファイルを使用します

LaravelでRedisを操作する
本記事は上記で作成したフォルダとファイルを使用します。LaravelでRedisを使う設定は完了済みで、sessionもRedisに格納されている前提で書かれています

Ratchetのインストール

今回はRatchetを使っていきます
Ratchet (http://socketo.me/)
コマンドラインで
cd sample
composer require cboden/ratchet
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
eclipseのプロジェクトを右クリック→リフレッシュ

サービスクラス修正

LaravelでRedisを操作するで作成したサービスクラスを修正します

(1) /sample/app/Services/Interfaces/RedisService.php修正

RedisService.php
<?php
namespace App\Services\Interfaces;

interface RedisService
{
    public function __construct();

    public function setKey1($value);
    public function getKey1();
    public function getSession($key);

}

getSessionメソッドを追加しました

(2) /sample/tests/Services/Impl/RedisServiceImpl.php修正

Tests\Services\Impl\RedisServiceImpl.php
‥‥
    public function getSession($key){
    }
‥‥

getSessionメソッドを追加しました

(3) /sample/app/Services/Impl/RedisServiceImpl.php修正

App\Services\Impl\RedisServiceImpl.php
‥‥
    private $sessionConn = null;

    public function __construct()
    {
        $this->redis = Redis::connection('db2');
        $this->sessionConn = Redis::connection(config('session.connection'));
    }
‥‥
    public function getSession($key)
    {
        $key = config('cache.prefix') . ':' . $key;
        $value = $this->sessionConn->get($key);
        return $value;
    }
‥‥

$sessionConnプロパティを追加しました
コンストラクタを修正しました
getSessionメソッドを追加しました

WebSocket処理作成

(1) /sample/app/Http/Controllers/ChatController.php作成

ChatController.php
<?php
namespace App\Http\Controllers;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class ChatController implements MessageComponentInterface  {

    protected $userList;

    public function __construct() {
        $this->userList = [];
    }

    public function addUser($resourceId, $user) {
        $this->userList[$resourceId] = ["user" => $user];
    }

    public function onOpen(ConnectionInterface $from) {
        if (!array_key_exists($from->resourceId, $this->userList)) {
            $this->userList[$from->resourceId] = ["user" => null];
        }
        $this->userList[$from->resourceId]["conn"] = $from;
        $msg = 'ゲストさんが入室しました';
        if (!is_null($this->userList[$from->resourceId]["user"])) {
            $msg = $this->userList[$from->resourceId]["user"]->name . 'さんが入室しました';
        }
        foreach ($this->userList as $userInfo) {
            $data = ['msg' => $msg, 'position' => 'center'];
            $userInfo["conn"]->send(json_encode($data));
        }
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        foreach ($this->userList as $userInfo) {
            $data = ['msg' => $msg];
            if (!is_null($this->userList[$from->resourceId]["user"])) {
                $data['name'] = $this->userList[$from->resourceId]["user"]->name;
            } else {
                $data['name'] = 'ゲスト';
            }
            if ($from === $userInfo["conn"]) {
                $data['position'] = 'right';
            } else {
                $data['position'] = 'left';
            }
            $userInfo["conn"]->send(json_encode($data));
        }
    }

    public function onClose(ConnectionInterface $conn) {
        unset($this->userList[$conn->resourceId]);
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
}

onOpenはクライアントが接続してきたときに自動的に実行されます
onMessageはクライアントがsendしてきたときに自動的に実行されます
onCloseはクライアントが切断したときに自動的に実行されます
$this->userListにEloquentのユーザーモデルとWebSocketのコネクションが格納されます
$this->userListをforeachで回してWebSocket接続している全ユーザーにJSONをsendしています

(2) /sample/app/Serversフォルダ作成
/sample/app/Servers/WsServer.php作成

WsServer.php
<?php
namespace App\Servers;

use Ratchet\ComponentInterface;
use Ratchet\ConnectionInterface;
use Psr\Http\Message\RequestInterface;
use App\Services\Interfaces\RedisService;
use App\Models\User;

class WsServer extends \Ratchet\WebSocket\WsServer
{

    private $component;

    public function __construct(ComponentInterface $component) {
        parent::__construct($component);
        $this->component = $component;
    }

    public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {

        $sessionName = config('session.cookie');
        $sessKey = null;
        $sessValue = null;

        $headerCookie = $request->getHeader('Cookie');
        if (isset($headerCookie[0])) {
            $cookieList = explode(';', $headerCookie[0]);
            foreach ($cookieList as $cookie) {
                $cookie = trim($cookie);
                $cookieKeyValue = explode('=', $cookie, 2);
                if ($sessionName === $cookieKeyValue[0]) {
                    $sessKey = decrypt(urldecode($cookieKeyValue[1]), false);
                    break;
                }
            }
        }

        $redisService = app()->makeWith(RedisService::class);
        if (!is_null($sessKey)) {
            $sessValue = $redisService->getSession($sessKey);
            $sessValue = unserialize(unserialize($sessValue));
            foreach ($sessValue as $k => $v) {
                if (strpos($k, 'login_web_') === 0) {
                    $user = User::find($v);
                    $this->component->addUser($conn->resourceId, $user);
                }
            }
        }

        return parent::onOpen($conn, $request);
    }

}

コンストラクタに渡されてくる$componentは先ほど作成したChatControllerです
onOpenはクライアントが接続してきたときに自動的に実行されます
Cookieを取得し、その中からsessionに使っているCookie値を取りだします(if ($sessionName === $cookieKeyValue[0]))。
取り出したCookie値を使いRedisからsessionを取得します($sessValue = $redisService->getSession($sessKey);)。
LaravelでRedisを操作するでsessionはRedisに格納されるようにしました。
そこからユーザーIDをとります。sessionの中のlogin_web_から始まるキーにユーザーIDがvalueとして格納されています(if (strpos($k, 'login_web_') === 0))。

(3) /sample直下にwsServer.php作成

wsServer.php
<?php

use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Illuminate\Contracts\Console\Kernel;
use App\Http\Controllers\ChatController;
use App\Servers\WsServer;

require dirname(__FILE__) . '\vendor\autoload.php';


$app = require __DIR__.'/bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();

$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new ChatController()
            )
        ),
    8282
    );

$server->run();

webSocketのエントリポイントになります
8282は今回WebSocketで使うポートです。firewallで許可しているポートにしてください

Controllerにメソッド追加

(1) /sample/app/Http/Controllers/SampleController.phpにwebSocketメソッドを追記

SampleController.php
    public function webSocket(Request $request)
    {
        return view('sample.webSocket');
    }

(2) /sample/routes/web.phpに下記を追記
Route::get('sample/web-socket', 'SampleController@webSocket');

viewの作成

(1) /sample/resources/views/sample/webSocket.blade.phpファイル作成

webSocket.blade.php
<html>
    <head>
        <title>sample</title>
        <style type="text/css">
        .container {
            width: 100%;
            height: 100%;
            box-sizing: border-box;
        }
        .msg-log {
            width: 100%;
            height: 92%;
            vertical-align:top;
            box-sizing: border-box;
            padding: 0px;
            border: black solid 1px;
            overflow-y: scroll;
        }
        .input-area {
            width: 100%;
            box-sizing: border-box;
        }
        .msg {
            width: 90%;
            height: 8%;
            vertical-align:top;
            box-sizing: border-box;
            padding: 0px;
            float: left;
        }
        .btn {
            width: 10%;
            height: 8%;
            vertical-align:top;
            box-sizing: border-box;
            padding: 0px;
        }
        .receive-msg-left {
            border-radius: 10px;
            border: black solid 1px;
            padding: 10px;
            margin: 0px 10px 10px 10px;
            display: inline-block;
            float: left;
            background-color: #FFFFFF;
        }
        .receive-msg-right {
            border-radius: 10px;
            border: black solid 1px;
            padding: 10px;
            margin: 0px 10px 10px 10px;
            display: inline-block;
            float: right;
            background-color: #00FF00;
        }
        .receive-msg-center {
            border: none;
            padding: 0px;
            margin: 10px;
            display: block;
            text-align: center;
            background-color: transparent;
        }
        .name-left {
            border: none;
            padding: 0px;
            margin: 0px 0px 0px 10px;
            display: inline-block;
            float: left;
            background-color: transparent;
        }
        .name-right {
            border: none;
            padding: 0px;
            margin: 0px 10px 0px 0px;
            display: inline-block;
            float: right;
            background-color: transparent;
        }
        .br {
            line-height: 0px;
            clear: both;
        }
        </style>
        <script type="text/javascript">

          var conn = "";

          function open(){

              conn = new WebSocket('ws://localhost:8282');

              conn.onopen = function(e) {
              };

              conn.onerror = function(e) {
                alert("エラーが発生しました");
              };

              conn.onmessage = function(e) {
                  var data = JSON.parse(e.data);
                  var msgLog = document.getElementById("msg_log");
                  var divObj = document.createElement("DIV");
                  var msg = null;
                  var msgSplit = null;
                  var nameObj = null;
                  var rowObj = null;
                  var br = null;
                  if (data["name"]) {
                      msg = document.createTextNode(data["name"]);
                      nameObj = document.createElement("DIV");
                      if (data["position"] == "left") {
                          nameObj.className = 'name-left';
                      } else {
                          nameObj.className = 'name-right';
                      }
                      nameObj.appendChild(msg);
                      msgLog.appendChild(nameObj);

                      br = document.createElement("BR");
                      br.className = 'br';
                      msgLog.appendChild(br);
                  }
                  if (data["position"] == "left") {
                      divObj.className = 'receive-msg-left';
                  } else if (data["position"] == "center") {
                      divObj.className = 'receive-msg-center';
                  } else {
                      divObj.className = 'receive-msg-right';
                  }
                  msgSplit = data["msg"].split('\n');
                  for (var i in msgSplit) {
                      msg = document.createTextNode(msgSplit[i]);
                      rowObj = document.createElement("DIV");
                      rowObj.appendChild(msg);
                      divObj.appendChild(rowObj);
                  }

                  msgLog.appendChild(divObj);

                  br = document.createElement("BR");
                  br.className = 'br';
                  msgLog.appendChild(br);

                  msgLog.scrollTop = msgLog.scrollHeight;

              };

              conn.onclose = function() {
                  alert("切断しました");
                  setTimeout(open, 5000);
              };

          }

          function send(){
              conn.send(document.getElementById("msg").value);
          }

          function close(){
              conn.close();
          }

          open();

        </script>
    </head>
    <body>
        <div class="container">
            <div id="msg_log" class="msg-log"></div>
            <div class="input-area">
                <textarea id="msg" class="msg"></textarea>
                <button class="btn" onclick="send();" >送信</button>
            </div>
        </div>
    </body>
</html>

conn = new WebSocket('ws://localhost:8282');で接続してます。8282は先ほどwsServer.phpに書いたポートです
conn.send(document.getElementById("msg").value);でWebSocketサーバーにデータ送信しています
conn.onmessageはwebSocketサーバーからsendがあった場合に自動的に実行されます

動作確認

コマンドラインで
cd sample
php wsServer.php
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
これでWebSocketサーバーが起動します

Chromeから
http://localhost/laravelSample/
左上のLOGINリンクからログインする
Firefoxから
http://localhost/laravelSample/
左上のLOGINリンクからログインする
Chromeから
http://localhost/laravelSample/sample/web-socket
にアクセスする
Firefoxから
http://localhost/laravelSample/sample/web-socket
にアクセスする

Chromeで
あいうえお
かきくけこ
と入力し、送信ボタンをクリックします

Firefoxで
さしすせそ
と入力し、送信ボタンをクリックします

a.png

動きました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ニートがPHPで求人サイト(Webアプリ)を作ってみた

技術記事ではなく、「作ったものを共有する」ライトな記事です。

経緯

小学5年生でゲームにハマる

ニートになって暇人

アプリ作ってみよう、みたいな

公開する理由

頑張って勉強されている方に少しでも参考になればいいなと思ったからです。
また、リリース後はしっかり運用・保守していく予定なので、エンジニアさんからフィードバックをもらえるかも?という下心もあります:rolling_eyes:

作ったもの

地元「釧路」限定の求人サイトです。
※釧路:北海道の避暑地であり、魚が美味しい海沿いの町

■サイトURL
https://job-cinema.com
■github
https://github.com/haruyaono/jobcinema-prod

ただ、サイトURLがリンク切れしていたらごめんなさい。
ニートは節約せずに生き残れないので、AWSのリソースを止めています。

代わりにドキュメントにまとめております。暇な方はどうぞ。
https://docs.google.com/document/d/1VUamIAlg_RNReFJJk1KSRj7qHN_bp-UEiwZcwO_1Lqk/edit?usp=sharing

使用技術

  • PHP(Laravel)
  • JavaScript(Vue.js)
  • AWS(ECS)

githubに詳細を記してあります

気をつけたこと

コーディング中は、「いきなり難しいことはしない」を意識していました。

概念やロジックの理解が中途半端なまま進めた結果、基本的なことで詰まった経験が1万回ほどあったので、例えば

①まずは比較的簡単な書き方で実装
②1が出来たら、次は可読性が上がる書き方(少し難しい)に置き換えてみる

という感じです。

コードについて

3つポイントがあると思っています。

①非同期処理

メール送信は全て非同期で処理しているので、ユーザーはメール送信処理の完了を待つ必要がありません。

ユーザーに会員登録完了画面を表示させて、裏ではメール送信処理をジョブとしてキューへ投入し、ジョブを実行させていく流れです。箱に入ったボールを取り出すイメージ。

本番では、プロセス管理ツール「supervisor」を使用して、Amazon ElasticCache(Redis)にメール送信のキューを保存するプロセスを管理しています。

プロセス管理ツールを使うことで、キューを保存するためのコマンドを自動で実行してくれるので、わざわざサーバーに入って手動でコマンド打つ必要がなく超絶便利でした!:sunglasses:

■参考にさせていただいた記事
https://reffect.co.jp/laravel/laravel-ubuntu-supervisor
https://docs.docker.jp/engine/admin/using_supervisord.html

②リポジトリパターンを採用

同じようなデータ操作のロジックを一箇所にまとめるために採用しました。

リポジトリパターンを用いることで、ビジネスロジックからデータ操作に関するロジックを切り離して、(データ操作を)抽象化したレイヤに任せることで拡張性・保守性の向上に努めました。

初見は概念が複雑すぎてぶっちゃけ理解に苦しみました。
しかし、ドキュメントや記事(Qiita、その他ブログ)を参考にしながら、手を動かして実装を続けていくと、少しずつ点と点が繋がっていきました:grin:

■参考にさせていただいた記事
https://www.ritolab.com/entry/165
https://qiita.com/bmf_san/items/c8d7b38b5f1f5747c2fd

③テスト駆動開発(TDD)

※あくまでリポジトリのみです

コントローラーからリポジトリを使用する時に「エラーが出る→修正→エラーが出る→修正」の繰り返しにうんざりしたため、リポジトリに記述したデータ操作ロジックが正しく動作するかどうかをテストしながら開発していきました。

しかし、TDDの知見はかなり浅く、本格的に導入するには勉強不足というのが本音。

体系的に網羅されている情報を吸収して実践していく必要があると感じています:sob:

■参考にさせていただいた記事
https://www.ritolab.com/entry/168
https://qiita.com/bmf_san/items/c8d7b38b5f1f5747c2fd

課題

以下、3つ書きました。
正直、たくさん課題が湧き出てくるので挙げたらキリがないです。

機能が物足りない
・検索項目の数が少ない
・レコメンド機能が微妙すぎる
・求職者と採用担当者間でチャットを導入したい

という欲求不満をどうにかしたいです。

また、他サービスを参考にしつつ、どうやればオリジナリティやユーザビリティが向上するのか?も分析していきたい。

結合テストが書かれていない
実際にアプリを動かしてテスト・デバッグしてますが、執筆時点で結合テストが全く書かれていない状況です。バグ温床の回避やテストへの理解を深めるためにも、早急に取り掛かるべき問題と思っています。

また、今後は「機能追加とテストは必ずセット」というルールで、開発に取り込んでいきます。..あたりまえ?

インフラをコードで管理したい

ニートでお金がないので、開発中はAWS環境を常に維持できません。
使い終わったリソースは削除→変更を確認するためにリソース作成→削除→...

AWSコンソールでテンプレの如く毎回ポチポチするのは億劫なので、巷で噂のterraformを検討しています。

まとめ

筆者は、Webアプリ実務経験なしのニート、つまり社会のゴ*であり、

・エンジニアを目指して勉強されている方
・すでにエンジニアとして就労されている方

にとって有益かは分かりません。
ただ、少しでも参考になれば嬉しいです。

間違っていることをご指摘いただけたり、フィードバック・アドバイスをいただけますと幸いです:mask:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINE Messaging APIを使って、ポケモンの弱点を教えてくれるBotを作る【Laravel・初心者向け】

1.はじめに

先日、久しぶりにポケモンの対人対戦をやっていたときに、

「相手(ポケモン)の弱点何???」

といった疑念を抱き、対戦時間と戦略を見失いモチベーションが下がってしまいました。

見た目だけだとタイプが分からないポケモンもいるし、タイプが2つあるポケモンもいるとなると覚えられない、、と悩んでました。

これって対戦初心者にありがちでは・・・

と思ったのがきっかけで、これを解決したいと思い

簡単・気軽・素早く弱点を知れるもの作ってみよう!!

と考えて実際に作ってみました。

本記事は、以下のようなコンセプトの記事になります。

・製作の経緯と意図

・簡単な製作の手順とコードのポイントの説明

・初心者向け(今後外部APIを使用したいと考えている方)

といった記事になります。

注意
実際には、とくせいや環境変化によってバトル戦略は変化すると思うので、タイプ相性だけではガチ対戦者さんにとっては不向きかと思います。あくまで、LINE botを作ってみよう!という趣旨です。

また、実際のゲームに基づいていますので、掲載において不適切な表現等が判明した場合は、真摯に受け止め対処致します。

2.作ったもの

circleAnimationMuvie.gif

LINEで、相手ポケモンの名前を入力して送信する

これだけ!!!

自動で弱点を返答して教えてくれます。

またおまけ程度ですが、ゲーム画面上では2倍弱点と4倍弱点の違いがないので
(どちらも【効果ばつぐん】の表記で2倍なのか4倍なのかは分からない。)

4倍弱点のタイプがあるときはメッセージで分かるようにしました。

シンプルですが、自分も含めた対戦初心者にとってはちょっとは使えると思えるものになりました。

言語については、php(Laravel)を使用しています。

3.なぜ作ったのか

シンプルな答えが欲しいと思ったから。

ポケモン対戦初心者勢の私がまず知りたいと思ったのはタイプ相性でした。

攻略サイトにはタイプ相性が記載されているのですが、とくせい、性格などタイプ相性以外など様々な情報が載っています。

初心者にとって対戦中には情報が多すぎて迷ってしまい判断を鈍らせるので、シンプルに弱点だけ教えてくれるものもあっていいかと思って製作しようと思いました。

LINE Messaging APIを使おうと思った理由

・ポケモンは数が多いので、チェックボックス等の選択肢から選ぶ方式だと時間がかかりすぎる(入力した方が早い。)
・対話式なので答えを知るにはうってつけである。
・単純にLINE Messaging APIを使ってみたかった。

Laravelで製作した理由

今回はDBを用意していないし、単純に入力された値を元に返すだけなのでわざわざLaravelに書く必要はなかったかもしれない。

理由としては、

・DB持った時に対応できそう(ポケモンの追加など)
・UIの管理画面上で色々できそう。(将来的に)
・アクセストークンなど退避して書く事に慣れている。API通信が安全に行える。
・配列を多く扱うと思ったので、使い慣れているPHPを使おうと思った。
・デバッグがしやすい。

辺りが理由でした。

LINE Messaging APIで他の方が作られているのを見ると、JSで書いている方が多い印象を受けましたが、今回はPHPで書くことにしました。

4.製作手順

4-1. LINE Developersに登録する。

まず、LINE Developersに登録します。

LINEに既に使っている場合は、登録した際のアカウントでDevelopersにも登録できます。

特に理由がなければ、使っているアカウントでそのまま登録していいかと思います。

LINEのBot開発 超入門(前編) ゼロから応答ができるまで
https://qiita.com/nkjm/items/38808bbc97d6927837cd

こちらの記事を参考しました。

頻繁にUIが変わっていますが、必要な情報は

・チャネルシークレット

・チャネルアクセストークン(長期)

の二つです。後でWebhookの利用を設定するのでログインしたままにしときます。

4-2. Laravel プロジェクトの立ち上げと初期設定

command
$laravel new laravel-project

名前は自由で。

.envファイルの設定

envは隠しファイルなので普通は見れないです。

macの場合はcommand + shift + . を押すと表示されます。

envファイルを開いて、4-1で記載されたLINEの情報を記述します。

LINE_CHANNEL_SECRET = "チャネルシークレット"
LINE_ACCESS_TOKEN = "チャネルアクセストークン"
config.servicesに登録

envファイルを直接参照してもいいのですが、本番用と環境用でenvに書いてある設定(DB接続情報など)が違う場合は不便なので、config.servicesからenvの内容を参照する様にします。

config.services
   return [
      //省略
      'line' => [
          'access_token' => env('LINE_ACCESS_TOKEN'),
          'channel_secret' => env('LINE_CHANNEL_SECRET'),
       ],
    ]

後でコントローラから上記を参照する様にします。

4-3.LINE Messaging API SDK for PHP をインストール

LINE公式から開発のためのSDK(Software Development Kit)が用意されているのでコンポーザを使ってインストールします。

command
composer require linecorp/line-bot-sdk

インストールすることで、LINEが用意してくれたクラス・メソッドなどを使う事ができる様になります。

4-4.ルーティングを設定する

LINEからAPIで通信を受けたときに、どのコントローラに処理をお願いするかを記述します。

今回はブラウザを介さずにコントローラを呼び出すのでroutes/api.phpに記述します。

routes/api.php
Route::post('/pokename', 'LineBotPokenameController@input_pokemon');

LINEからapi/pokenameに通信を受けたときに、LineBotPokenameControllerクラスのinput_pokemonメソッドを実行しなさい。という意味になります。

4-5で実際にコントローラを作ってきます。

4-5.コントローラを作成する

command
php artisan make:controller LineBotPokenameController

Larabelプロジェクトのルートフォルダで上記artsianコマンドを打ちます。

ここでは、4-4で設定したルートで同じコントローラ名(LineBotPokenameController)で作成します。

開きます。

/app/Http/Controllers/LineBotPokenameController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class LineBotPokenameController extends Controller
{
    public function input_pokemon(Request $request)
    {
       //ここに書いていく
    }
}

こんな感じになっているかと思います。

続けて、input_pokemonメソッドに愚直に処理を書いてきます。

4-6.クラスを利用してAPIを受け取るための設定をする

LINE Messaging API SDK for PHPをインストールしたことで使えるクラスを追加します。

/app/Http/Controllers/LineBotPokenameController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use LINE\LINEBot;  //追加
use LINE\LINEBot\HTTPClient\CurlHTTPClient;  //追加
use LINE\LINEBot\Event\MessageEvent\TextMessage;  //追加

class LineBotPokenameController extends Controller
//省略

上記ではuseを使い、コントローラを呼び出します。

次にinput_pokemonメソッド内で、インスタンス化します。(下記)

その他にも、LINEから情報を受け取った際に、署名の検証などの情報も一緒に記載します。

/app/Http/Controllers/LineBotPokenameController
//省略
    public function input_pokemon(Request $request)
    {
        //認証を行う
        $lineAccessToken = config('services.line.access_token');
        $lineChannelSecret = config('services.line.channel_secret');

        $httpClient = new CurlHTTPClient($lineAccessToken);
        $lineBot = new LINEBot($httpClient, ['channelSecret' => $lineChannelSecret]);

        $signature = $request->header('x-line-signature');

        if (!$lineBot->validateSignature($request->getContent(), $signature)) {
            //送信元に400エラーを伝える
            abort(400, 'Invalid signature');
        }

        //LINEで入力されたメッセージ情報を受け取る
        $events = $lineBot->parseEventRequest($request->getContent(), $signature);
    }
//省略

最後にある$eventsの所で、LINEからの送られてきたテキスト情報を受け取って入れ込んでます。

が、今の段階では通信がうまくいきません。

LINE DevelopersのWebhookの部分にURLを記載していないからです。

Webhookの設定をしないといけないのですが、こちらはローカルPC内では作動してくれないため、インターネット上でのURLを記載しないといけません。

やり方としては、herokuなどホスティングサービスを利用し、インターネット上でのURL(https://)を取得した後に、LINE DevelopersのWebhook URLの欄にURLを記載します。

(私は、ローカル上でのURLを一時的にインターネットでのURLに変換してくれるngrokを利用しました。)

記載するURLはhttps://herokuなどのURL/api/pokenameとなります。

うまくいくと、LINEからメッセージを送ったときに$eventsに入力したテキストの情報が入ります。

デバッグで変数$eventsを見てみます。

command
[2020-08-02 20:47:55] local.DEBUG: array (
  0 => 
  LINE\LINEBot\Event\MessageEvent\TextMessage::__set_state(array(
     'emojis' => NULL,
     'message' => 
    array (
      'type' => 'text',
      'id' => '12428220966824',
      'text' => 'ピカチュウ',
    ),
     'event' => 
    array (
      'type' => 'message',
      'replyToken' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
      'source' => 
      array (
        'userId' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
        'type' => 'user',
      ),
      'timestamp' => 1596368874886,
      'mode' => 'active',
      'message' => 
      array (
        'type' => 'text',
        'id' => '12428220966824',
        'text' => 'ピカチュウ',
      ),
    ),
  )),
)  

こんな感じで情報を受け取ってくれます。

ここまでで、設定完了です!!

後は、返信をするための処理を書いていきます。

4-7.返信するための処理をコントローラーに記述する

今回作成したものは、この様に書きました。

作りたいアプリによって自由に書いてみるといいかもです。

/app/Http/Controllers/LineBotPokenameController
   public function input_pokemon(Request $request)
    {
        //省略
        foreach ($events as $event) {
            if (!($event instanceof TextMessage)) {
                continue;
            }

            $replyToken = $event->getReplyToken();
            $replyText = $event->getText();
            //エンコードを行う
            $replyText = mb_convert_encoding($replyText,'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');

            //Jsonを取得する
            $url = public_path() . '/data/double_weakness.json';
            $json = file_get_contents($url);
            $json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
            $type_array = json_decode($json, true);

            //ポケモン情報を取得
            $url = 'https://raw.githubusercontent.com/kotofurumiya/pokemon_data/master/data/pokemon_data.json';
            $json = file_get_contents($url);
            $json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
            $pokemon_array = json_decode($json, true);

            //入力チェック
            if(in_array($replyText, array_column($pokemon_array, 'name'), true))
            {
              //該当するポケモンがいる場合
              $pokemon_data = $pokemon_array[array_search($replyText, array_column($pokemon_array, 'name'))];

              //タイプを入れる
              $type1 = $pokemon_data['types'][0];

              //単タイプかどうか
              if(isset($pokemon_data['types'][1])){
                $type2 = $pokemon_data['types'][1];
              }else{
                $type2 = 'mono_type';
                $sp_weak = "";
              }

              //タイプ判定
              foreach($type_array as $key1 => $value1){
                $array_list = $value1;

                //タイプ1
                $type_match1 = array_filter($array_list, function($element) use($type1)
                {
                  //必ずある前提なので返すだけ
                  return $element['type'] == $type1;
                });
                //該当するものを取得
                $type_a = array_column($type_match1, 'double_weakness_type');

                //タイプ2
                if ($type2 === 'mono_type'){
                  $type_match = $type_a[0];
                }else{
                  $type_match2 = array_filter($array_list, function($element) use($type2)
                  {
                    return $element['type'] == $type2;
                  });
                  //該当するものを取得
                  $type_b = array_column($type_match2, 'double_weakness_type');

                  //複合弱点をマージ
                  $type_match = array_merge($type_a[0],$type_b[0]);

                  //4倍弱点
                  $sp_weak = array_intersect($type_a[0],$type_b[0]);

                  //2倍弱点
                  $type_match = array_diff($type_match,$sp_weak);

                }

                //重複削除
                $type_match = array_unique($type_match);
                $type_match = array_values($type_match);
              };
            }else{
              //ポケモン以外の入力の場合
                $type_match = array("no_much");
            }

            //変数宣言
            $weak_text = "";
            $spweak_text = "";

            //タイプ以外の入力がされてきた場合
            if(in_array("no_much", $type_match)){
              $weak_text = "ポケモンのなまえを入力してね";
            }else{
              for($i = 0; $i < count($type_match); $i++)
              {
                $weak_text = $type_match[$i].",".$weak_text;
              }
              if($sp_weak){
                $weak_text = $weak_text. "\n \n4倍弱点だよ \n".$sp_weak[0];
              }
            }

            //LINEへ送信する
            $lineBot->replyText($replyToken, $weak_text);
        }
    }
 いくつかポイント

・ポケモンのデータはgithub上でJSONデータを配布されている方から拝借しました。
  https://github.com/kotofurumiya/pokemon_data

・通常の2倍弱点を一覧にしたJSONデータをLaravelのパブリックに置いて情報を取得しています。基本的には入力されたポケモンの弱点と、JSONで取得したデータを比較してるだけです。

・配列を多用しているので、加工したり制御する関数を多く使っています。
(もっといい書き方があると思う。)

・ポケモン以外の名前が入力されて来た時、タイプが一つだけのポケモン、複合タイプのポケモンの3パターンがあると想定して書いています。

5.実際に使ってみて

使いやすかった!! LINEめっちゃ便利!!

画面に相手の名前が書いてあるので入力に迷うことはないし、限られた時間の中で素早く弱点が分かるのは結構便利だった。

まあ、勝てるかどうかは別として。

・・・対戦初心者には十分活用できるかなと思いました!!!

6.反省点

・例外処理(仮にJSONを取得できなかったときに処理が止まる)を直さないといけない。

・一応自分としてはやりたい事ができたが、書き方の部分では甘い部分があるはずなので満足しない。

・これだけだったらあまりLaravelで書く意味がないので、DBを持ったり一覧表示や修正をUI上で行える様にすれば多少は意味が出てくるかもしれないです。

7.最後に

Line Messaging APIはリファレンスも丁寧な日本語で分かりやすくて、始める壁も低く感じました。

あとは、自分で使って便利なだと思って制作できたので楽しく作れました。

Qiitaとかも結構参考になる記事が多くて助かったので、外部APIを使ったアプリ作成のとっかかりとしてもLINEはいいと思います!!おすすめ!!

アイデア次第で他にも結構やれることもありそうなので、思いついたらまた何か作ってみようと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINE Messaging APIを使って、ポケモンの弱点を教えてくれるBotを作る【初心者向け】

1.はじめに

先日、久しぶりにポケモンの対人対戦をやっていたときに、

「相手(ポケモン)の弱点何???」

といった疑念を抱き、対戦時間と戦略を見失いモチベーションが下がってしまいました。

見た目だけだとタイプが分からないポケモンもいるし、タイプが2つあるポケモンもいるとなると覚えられない、、と悩んでました。

これって対戦初心者にありがちでは・・・

と思ったのがきっかけで、これを解決したいと思い

簡単・気軽・素早く弱点を知れるもの作ってみよう!!

と考えて実際に作ってみました。

本記事は、以下のようなコンセプトの記事になります。

・製作の経緯と意図

・簡単な製作の手順とコードのポイントの説明

・初心者向け(今後外部APIを使用したいと考えている方)

といった記事になります。

注意
実際には、とくせいや環境変化によってバトル戦略は変化すると思うので、タイプ相性だけではガチ対戦者さんにとっては不向きかと思います。あくまで、LINE botを作ってみよう!という趣旨です。

また、実際のゲームに基づいていますので、掲載において不適切な表現等が判明した場合は、真摯に受け止め対処致します。

2.作ったもの

circleAnimationMuvie.gif

LINEで、相手ポケモンの名前を入力して送信する

これだけ!!!

自動で弱点を返答して教えてくれます。

またおまけ程度ですが、ゲーム画面上では2倍弱点と4倍弱点の違いがないので
(どちらも【効果ばつぐん】の表記で2倍なのか4倍なのかは分からない。)

4倍弱点のタイプがあるときはメッセージで分かるようにしました。

シンプルですが、自分も含めた対戦初心者にとってはちょっとは使えると思えるものになりました。

言語については、php(Laravel)を使用しています。

3.なぜ作ったのか

シンプルな答えが欲しいと思ったから。

ポケモン対戦初心者勢の私がまず知りたいと思ったのはタイプ相性でした。

攻略サイトにはタイプ相性が記載されているのですが、とくせい、性格などタイプ相性以外など様々な情報が載っています。

初心者にとって対戦中には情報が多すぎて迷ってしまい判断を鈍らせるので、シンプルに弱点だけ教えてくれるものもあっていいかと思って製作しようと思いました。

LINE Messaging APIを使おうと思った理由

・ポケモンは数が多いので、チェックボックス等の選択肢から選ぶ方式だと時間がかかりすぎる(入力した方が早い。)
・対話式なので答えを知るにはうってつけである。
・単純にLINE Messaging APIを使ってみたかった。

Laravelで製作した理由

今回はDBを用意していないし、単純に入力された値を元に返すだけなのでわざわざLaravelに書く必要はなかったかもしれない。

理由としては、

・DB持った時に対応できそう(ポケモンの追加など)
・UIの管理画面上で色々できそう。(将来的に)
・アクセストークンなど退避して書く事に慣れている。API通信が安全に行える。
・配列を多く扱うと思ったので、使い慣れているPHPを使おうと思った。
・デバッグがしやすい。

辺りが理由でした。

LINE Messaging APIで他の方が作られているのを見ると、JSで書いている方が多い印象を受けましたが、今回はPHPで書くことにしました。

4.製作手順

4-1. LINE Developersに登録する。

まず、LINE Developersに登録します。

LINEに既に使っている場合は、登録した際のアカウントでDevelopersにも登録できます。

特に理由がなければ、使っているアカウントでそのまま登録していいかと思います。

LINEのBot開発 超入門(前編) ゼロから応答ができるまで
https://qiita.com/nkjm/items/38808bbc97d6927837cd

こちらの記事を参考しました。

頻繁にUIが変わっていますが、必要な情報は

・チャネルシークレット

・チャネルアクセストークン(長期)

の二つです。後でWebhookの利用を設定するのでログインしたままにしときます。

4-2. Laravel プロジェクトの立ち上げと初期設定

command
$laravel new laravel-project

名前は自由で。

.envファイルの設定

envは隠しファイルなので普通は見れないです。

macの場合はcommand + shift + . を押すと表示されます。

envファイルを開いて、4-1で記載されたLINEの情報を記述します。

LINE_CHANNEL_SECRET = "チャネルシークレット"
LINE_ACCESS_TOKEN = "チャネルアクセストークン"
config.servicesに登録

envファイルを直接参照してもいいのですが、本番用と環境用でenvに書いてある設定(DB接続情報など)が違う場合は不便なので、config.servicesからenvの内容を参照する様にします。

config.services
   return [
      //省略
      'line' => [
          'access_token' => env('LINE_ACCESS_TOKEN'),
          'channel_secret' => env('LINE_CHANNEL_SECRET'),
       ],
    ]

後でコントローラから上記を参照する様にします。

4-3.LINE Messaging API SDK for PHP をインストール

LINE公式から開発のためのSDK(Software Development Kit)が用意されているのでコンポーザを使ってインストールします。

command
composer require linecorp/line-bot-sdk

インストールすることで、LINEが用意してくれたクラス・メソッドなどを使う事ができる様になります。

4-4.ルーティングを設定する

LINEからAPIで通信を受けたときに、どのコントローラに処理をお願いするかを記述します。

今回はブラウザを介さずにコントローラを呼び出すのでroutes/api.phpに記述します。

routes/api.php
Route::post('/pokename', 'LineBotPokenameController@input_pokemon');

LINEからapi/pokenameに通信を受けたときに、LineBotPokenameControllerクラスのinput_pokemonメソッドを実行しなさい。という意味になります。

4-5で実際にコントローラを作ってきます。

4-5.コントローラを作成する

command
php artisan make:controller LineBotPokenameController

Larabelプロジェクトのルートフォルダで上記artsianコマンドを打ちます。

ここでは、4-4で設定したルートで同じコントローラ名(LineBotPokenameController)で作成します。

開きます。

/app/Http/Controllers/LineBotPokenameController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class LineBotPokenameController extends Controller
{
    public function input_pokemon(Request $request)
    {
       //ここに書いていく
    }
}

こんな感じになっているかと思います。

続けて、input_pokemonメソッドに愚直に処理を書いてきます。

4-6.クラスを利用してAPIを受け取るための設定をする

LINE Messaging API SDK for PHPをインストールしたことで使えるクラスを追加します。

/app/Http/Controllers/LineBotPokenameController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use LINE\LINEBot;  //追加
use LINE\LINEBot\HTTPClient\CurlHTTPClient;  //追加
use LINE\LINEBot\Event\MessageEvent\TextMessage;  //追加

class LineBotPokenameController extends Controller
//省略

上記ではuseを使い、コントローラを呼び出します。

次にinput_pokemonメソッド内で、インスタンス化します。(下記)

その他にも、LINEから情報を受け取った際に、署名の検証などの情報も一緒に記載します。

/app/Http/Controllers/LineBotPokenameController
//省略
    public function input_pokemon(Request $request)
    {
        //認証を行う
        $lineAccessToken = config('services.line.access_token');
        $lineChannelSecret = config('services.line.channel_secret');

        $httpClient = new CurlHTTPClient($lineAccessToken);
        $lineBot = new LINEBot($httpClient, ['channelSecret' => $lineChannelSecret]);

        $signature = $request->header('x-line-signature');

        if (!$lineBot->validateSignature($request->getContent(), $signature)) {
            //送信元に400エラーを伝える
            abort(400, 'Invalid signature');
        }

        //LINEで入力されたメッセージ情報を受け取る
        $events = $lineBot->parseEventRequest($request->getContent(), $signature);
    }
//省略

最後にある$eventsの所で、LINEからの送られてきたテキスト情報を受け取って入れ込んでます。

が、今の段階では通信がうまくいきません。

LINE DevelopersのWebhookの部分にURLを記載していないからです。

Webhookの設定をしないといけないのですが、こちらはローカルPC内では作動してくれないため、インターネット上でのURLを記載しないといけません。

やり方としては、herokuなどホスティングサービスを利用し、インターネット上でのURL(https://)を取得した後に、LINE DevelopersのWebhook URLの欄にURLを記載します。

(私は、ローカル上でのURLを一時的にインターネットでのURLに変換してくれるngrokを利用しました。)

記載するURLはhttps://herokuなどのURL/api/pokenameとなります。

うまくいくと、LINEからメッセージを送ったときに$eventsに入力したテキストの情報が入ります。

デバッグで変数$eventsを見てみます。

command
[2020-08-02 20:47:55] local.DEBUG: array (
  0 => 
  LINE\LINEBot\Event\MessageEvent\TextMessage::__set_state(array(
     'emojis' => NULL,
     'message' => 
    array (
      'type' => 'text',
      'id' => '12428220966824',
      'text' => 'ピカチュウ',
    ),
     'event' => 
    array (
      'type' => 'message',
      'replyToken' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
      'source' => 
      array (
        'userId' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
        'type' => 'user',
      ),
      'timestamp' => 1596368874886,
      'mode' => 'active',
      'message' => 
      array (
        'type' => 'text',
        'id' => '12428220966824',
        'text' => 'ピカチュウ',
      ),
    ),
  )),
)  

こんな感じで情報を受け取ってくれます。

ここまでで、設定完了です!!

後は、返信をするための処理を書いていきます。

4-7.返信するための処理をコントローラーに記述する

今回作成したものは、この様に書きました。

作りたいアプリによって自由に書いてみるといいかもです。

/app/Http/Controllers/LineBotPokenameController
   public function input_pokemon(Request $request)
    {
        //省略
        foreach ($events as $event) {
            if (!($event instanceof TextMessage)) {
                continue;
            }

            $replyToken = $event->getReplyToken();
            $replyText = $event->getText();
            //エンコードを行う
            $replyText = mb_convert_encoding($replyText,'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');

            //Jsonを取得する
            $url = public_path() . '/data/double_weakness.json';
            $json = file_get_contents($url);
            $json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
            $type_array = json_decode($json, true);

            //ポケモン情報を取得
            $url = 'https://raw.githubusercontent.com/kotofurumiya/pokemon_data/master/data/pokemon_data.json';
            $json = file_get_contents($url);
            $json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
            $pokemon_array = json_decode($json, true);

            //入力チェック
            if(in_array($replyText, array_column($pokemon_array, 'name'), true))
            {
              //該当するポケモンがいる場合
              $pokemon_data = $pokemon_array[array_search($replyText, array_column($pokemon_array, 'name'))];

              //タイプを入れる
              $type1 = $pokemon_data['types'][0];

              //単タイプかどうか
              if(isset($pokemon_data['types'][1])){
                $type2 = $pokemon_data['types'][1];
              }else{
                $type2 = 'mono_type';
                $sp_weak = "";
              }

              //タイプ判定
              foreach($type_array as $key1 => $value1){
                $array_list = $value1;

                //タイプ1
                $type_match1 = array_filter($array_list, function($element) use($type1)
                {
                  //必ずある前提なので返すだけ
                  return $element['type'] == $type1;
                });
                //該当するものを取得
                $type_a = array_column($type_match1, 'double_weakness_type');

                //タイプ2
                if ($type2 === 'mono_type'){
                  $type_match = $type_a[0];
                }else{
                  $type_match2 = array_filter($array_list, function($element) use($type2)
                  {
                    return $element['type'] == $type2;
                  });
                  //該当するものを取得
                  $type_b = array_column($type_match2, 'double_weakness_type');

                  //複合弱点をマージ
                  $type_match = array_merge($type_a[0],$type_b[0]);

                  //4倍弱点
                  $sp_weak = array_intersect($type_a[0],$type_b[0]);

                  //2倍弱点
                  $type_match = array_diff($type_match,$sp_weak);

                }

                //重複削除
                $type_match = array_unique($type_match);
                $type_match = array_values($type_match);
              };
            }else{
              //ポケモン以外の入力の場合
                $type_match = array("no_much");
            }

            //変数宣言
            $weak_text = "";
            $spweak_text = "";

            //タイプ以外の入力がされてきた場合
            if(in_array("no_much", $type_match)){
              $weak_text = "ポケモンのなまえを入力してね";
            }else{
              for($i = 0; $i < count($type_match); $i++)
              {
                $weak_text = $type_match[$i].",".$weak_text;
              }
              if($sp_weak){
                $weak_text = $weak_text. "\n \n4倍弱点だよ \n".$sp_weak[0];
              }
            }

            //LINEへ送信する
            $lineBot->replyText($replyToken, $weak_text);
        }
    }
 いくつかポイント

・ポケモンのデータはgithub上でJSONデータを配布されている方から拝借しました。
  https://github.com/kotofurumiya/pokemon_data

・通常の2倍弱点を一覧にしたJSONデータをLaravelのパブリックに置いて情報を取得しています。基本的には入力されたポケモンの弱点と、JSONで取得したデータを比較してるだけです。

・配列を多用しているので、加工したり制御する関数を多く使っています。
(もっといい書き方があると思う。)

・ポケモン以外の名前が入力されて来た時、タイプが一つだけのポケモン、複合タイプのポケモンの3パターンがあると想定して書いています。

5.実際に使ってみて

使いやすかった!! LINEめっちゃ便利!!

画面に相手の名前が書いてあるので入力に迷うことはないし、限られた時間の中で素早く弱点が分かるのは結構便利だった。

まあ、勝てるかどうかは別として。

・・・対戦初心者には十分活用できるかなと思いました!!!

6.反省点

・例外処理(仮にJSONを取得できなかったときに処理が止まる)を直さないといけない。

・一応自分としてはやりたい事ができたが、書き方の部分では甘い部分があるはずなので満足しない。

・これだけだったらあまりLaravelで書く意味がないので、DBを持ったり一覧表示や修正をUI上で行える様にすれば多少は意味が出てくるかもしれないです。

7.最後に

Line Messaging APIはリファレンスも丁寧な日本語で分かりやすくて、始める壁も低く感じました。

あとは、自分で使って便利なだと思って制作できたので楽しく作れました。

Qiitaとかも結構参考になる記事が多くて助かったので、外部APIを使ったアプリ作成のとっかかりとしてもLINEはいいと思います!!おすすめ!!

アイデア次第で他にも結構やれることもありそうなので、思いついたらまた何か作ってみようと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelにSassを入れて好きなCSSフレームワークを使う

準備

  • 導入したいCSSフレームワークのSassのソースフォルダをダウンロードしておきます。
  • Laravel標準のBootstrapではなく、どうしてもMaterializeを使ってみたかったのでこれを導入します。

Materialize
Materialize01.png

環境

  • Laravel Framework 7.22.4
  • Materialize 1.0.0

Sassを使えるようにする

Sassの導入は他の方が解説されていたので、こちらを参考にしました。
LaravelにSCSSの導入方法

フレームワークを導入する

npmをインストールしたら、プロジェクトフォルダ直下に作られるwebpack.mix.jsを開きます。

webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css');

この.sass~の部分でwebpackがresources/sass/app.scssファイルをpublic/css/app.cssにコンパイルしてくれる

ということなのでコンパイル元のresources/sass/app.scssを見てみます。

resources/sass/app.scss
// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

// Variables
@import 'variables';

// Bootstrap
@import '~bootstrap/scss/bootstrap';

一番下でBootstrapを読み込んでいるようです。
インポート元のパスはプロジェクトフォルダの
node_modules/bootstrap/scss/bootstrapとなっていました。

同じようにしてnode_modulesフォルダにダウンロードしたフォルダ(今回はmaterialize-src)を入れます。

そしてパスを変更します。

esources/sass/app.scss
// Materialize
@import '~materialize-src/sass/materialize.scss';

あとはnpm run devでコンパイルしてあげれば変更が適用されます。

念の為、コンパイル先を確認します。

public/css/app.css
@import url(https://fonts.googleapis.com/css?family=Nunito);@charset "UTF-8";

.materialize-red {
  background-color: #e51c23 !important;
}

.materialize-red-text {
  color: #e51c23 !important;
}

.materialize-red.lighten-5 {
  background-color: #fdeaeb !important;
                 ~省略~

コンパイルされていました。

最後に、CSSを適用させたいビューテンプレートの中に
<link href="{{ asset('/css/app.css') }}" rel="stylesheet">
を書いてあげれば完成です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelで好きなCSSフレームワークを使う

準備

  • 導入したいCSSフレームワークのSassのソースフォルダをダウンロードしておきます。
  • ソースフォルダのダウンロードではなく、npmなどのパッケージマネージャでのインストールが間違いが起きないのでおすすめです。
  • Laravel標準のBootstrapではなく、どうしてもMaterializeを使ってみたかったのでこれを導入します。

Materialize
Materialize01.png

環境

  • Laravel Framework 7.22.4
  • Materialize 1.0.0

フレームワークを導入する

npmをインストールしたら、プロジェクトフォルダ直下に作られるwebpack.mix.jsを開きます。

webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css');

この.sass~の部分でwebpackがresources/sass/app.scssファイルをpublic/css/app.cssにコンパイルしてくれる

ということなのでコンパイル元のresources/sass/app.scssを見てみます。

resources/sass/app.scss
// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

// Variables
@import 'variables';

// Bootstrap
@import '~bootstrap/scss/bootstrap';

一番下でBootstrapを読み込んでいるようです。
インポート元のパスはプロジェクトフォルダの
node_modules/bootstrap/scss/bootstrapとなっていました。

同じようにしてnode_modulesフォルダにダウンロードしたフォルダ(今回はmaterialize-src)を入れます。
【訂正】
パッケージマネージャでのインストールをおすすめします。
materializeの場合は以下です。

npm install materialize-css

そしてパスを変更します。

esources/sass/app.scss
// Materialize
@import '~materialize-src/sass/materialize.scss';

あとはnpm run devでコンパイルしてあげれば変更が適用されます。

念の為、コンパイル先を確認します。

public/css/app.css
@import url(https://fonts.googleapis.com/css?family=Nunito);@charset "UTF-8";

.materialize-red {
  background-color: #e51c23 !important;
}

.materialize-red-text {
  color: #e51c23 !important;
}

.materialize-red.lighten-5 {
  background-color: #fdeaeb !important;
                 ~省略~

コンパイルされていました。

最後に、CSSを適用させたいビューテンプレートの中に
<link href="{{ asset('/css/app.css') }}" rel="stylesheet">
を書いてあげれば完成です。

番外編:Sassを使う

導入時に一緒にSassも入れたのでわかりやすかったページのリンクを貼っておきます。
LaravelにSCSSの導入方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP/Laravel】マイグレート時に発生したエラー解決

はじめに

Laravelでアプリ開発をしております。
環境構築が完了し、いざマイグレートをする段階で今回のエラーが発生しました。
備忘のために記録しております。
同じような方の助けになれば幸いです。

開発環境

・MacOS:10.14.6 (Mojave)
・PHP7.3
・Laravel6

発生したエラー

$ php artisan migrate
**************************************
*     Application In Production!     *
**************************************

 Do you really wish to run this command? (yes/no) [no]:
 > yes


   Illuminate\Database\QueryException  : SQLSTATE[HY000] [1044] Access denied for user ''@'localhost' to database 'forge' (SQL: select * from information_schema.tables where table_schema = forge and table_name = migrations and table_type = 'BASE TABLE')

  at /Users/ユーザーネーム/projects/stg/8001-laravel/phpsample/vendor/laravel/framework/src/Illuminate/Database/Connection.php:669
    665|         // If an exception occurs when attempting to run a query, we'll format the error
    666|         // message to include the bindings with SQL, which will make this exception a
    667|         // lot more helpful to the developer instead of just the database's errors.
    668|         catch (Exception $e) {
  > 669|             throw new QueryException(
    670|                 $query, $this->prepareBindings($bindings), $e
    671|             );
    672|         }
    673| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000] [1044] Access denied for user ''@'localhost' to database 'forge'")
      /Users/ユーザーネーム/projects/stg/8001-laravel/phpsample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=127.0.0.1;port=3306;dbname=forge", "forge", "", [])
      /Users/ユーザーネーム/projects/stg/8001-laravel/phpsample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.

解決方法

発生したエラーより、指定してるデータベースにアクセスができないとのこと。
これは「/config/database.php」の修正してやることで、指定できます。
私の場合は以下の「修正」あたりをMAMPの設定に合わせました。

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', 'php_sample_db'), /*修正*/
            'username' => env('DB_USERNAME', 'root'), /*修正*/
            'password' => env('DB_PASSWORD', 'root'), /*修正*/
            'unix_socket' => env('DB_SOCKET', '/Applications/MAMP/tmp/mysql/mysql.sock'), /*修正*/
            'charset' => 'utf8', /*修正*/
            'collation' => 'utf8_general_ci', /*修正*/
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => false, /*修正*/
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

 
これで再度マイグレートを実行すると、無事に通りました。

$ php artisan migrate
**************************************
*     Application In Production!     *
**************************************

 Do you really wish to run this command? (yes/no) [no]:
 > yes

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.04 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.02 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0.01 seconds)
Migrating: 2020_08_02_063050_create_categories_table
Migrated:  2020_08_02_063050_create_categories_table (0.01 seconds)
Migrating: 2020_08_02_063128_create_shops_table
Migrated:  2020_08_02_063128_create_shops_table (0.01 seconds)

参考文献

以下の記事を参考にさせていただき、解決できました。
ありがとうございました。

本件に限らず、見やすいですし理解もしやすいのでオススメです。

PHPフレームワークのLaravelララベルをつかおう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel PassportのOAuth2認証をやる

Laravel Passportを使ったPKCEを使った認可コード引き換えをするOAuth2認証をやってみよう!
ノリで先行してやってしまって、あとから備忘録的に書いているので間違っている箇所があるかもしれません。

使ったもの

Laravel 7.22.2
Docker Desktop for Windows (on WSL2)

またフロントエンドにはNuxtをつかいました。
ユースケースとしてはLaravelをWeb APIサーバに、NuxtをWebクライアントに、という形ですが応用は効くかと思います。

Laravelの準備

インストールや環境構築は飛ばします。
認証機能でLaravel Mixを使うのでnpmコマンドを使えるように仕込む必要があります。

認証機能を準備する

認証 7.x Laravelに従ってコマンドをポチポチしたりします。

ルート定義

composer require laravel\ui
php artisan ui vue --auth

2行目のコマンドをタイプすると「npm installnpm run devよろしく~」みたいなメッセージがでるのでやっておきましょう。

npm install
npm run dev

Passportの準備

Laravel PassportはLaravel公式パッケージのひとつでOAuth2.0認証をLaravelで手早く行えるようになるものです。

インストール

composer require laravel/passport

インストールするとマイグレーションされるテーブルが増えるのでphp artisan migrateします。
その後、認証に使われたりする暗号キーをつくるためにphp artisan passport:installをします。

php artisan migrate
php artisan passport:install


プロダクション環境にデプロイする時には...

開発環境でPassportを使いつつ開発をして、本番環境でデプロイした際、暗号キーの生成だけ必要なことがあると思います。

暗号キーは.gitignoreでバージョニングされないよう指定されているし、外部へ公開されるべきでなく、プロダクション環境ではプロダクション環境用の暗号キーが必要になるでしょう。

その場合は以下のコマンドをタイプすることで暗号キーの生成のみを行うことができます。

php artisan passport:keys


UserAuthServiceProviderconfig/auth.phpの書き換え

UserモデルでHasApiTokensトレイトを使うように追記してやります。

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

そしてAuthServiceProviderbootメソッドにPassportの機能がルーティングされるように処理を追加します。

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * アプリケーションのポリシーのマップ
     *
     * @var array
     */
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * 全認証/認可サービスの登録
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Passport::routes();
    }

最後にconfig/auth.phpを書き換えて、API認証にPassportが使われるように仕向けます。

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'passport', // <- これ
        'provider' => 'users',
    ],
],

Passportクライアントをつくる

artisanでつくります。
この時--publicオプションを設定します。
Laravelはそもそも普通のWebアプリケーションを構築するフレームワークであるため、Passport自体もWebアプリケーション間で認証をし合うように設計されているものです。

認証のやりとりにclient_secretを利用することになるのですが、Web APIを利用するクライアントにclient_secretを配信してしまってはマズいため、--publicオプションを付け、クライアント-サーバ間でclient_secretを送受信することなくトークンを受け取れるようにします。(PKCEを使った認可コードグラントというらしい)

php artisan passport:client --public

このコマンドを実行すると対話形式でセットアップがすすみます。
どのユーザに関連づけるかクライアントの名前は何か、認証後のリダイレクト先はどこかと聞かれるので、適切な値を入れましょう。
認証後のリダイレクト先は後程作ります。クライアントの然るべきページのURIを設定してください。

クライアントの名前は後で晒されるので、ストレスが溜まっていたとしてもほどほどの名前をつけてあげましょうね?

image.png
※晒し例

クライアントが作成されるとClient IDClient Secretが出ますが、Publicクライアントの場合Client Secretが空欄になっています。
Client IDは後々使うので控えておきましょう!
忘れちゃった場合はもう一度Passportクライアントを作り直すか、データベースに格納されている値を見ちゃいましょう。

CORSの設定

Laravelサーバとクライアント開発機内だったとしても、ポートが異なる場合はオリジンをまたぐことになるのでCORSを設定する必要があります。

LaravelのCORSについてはあまり深く理解できていないので、説明はこのリンク先にお任せします...
CORS を許可する - Larapet

Laravelのconfig/cors.phppathsの値を変えます。
デフォルトではapi/*のみが設定されており、APIルート全体でオリジンをまたぐ通信を許すようになっています。
これにoauth/tokenを追加してください。

クライアントでは認可サーバでのユーザ認証後、クライアントにリダイレクトされ、トークンとの引き換え用コードが渡されます。
この引き換えコードとトークンを引き換えるときにオリジンをまたぐリクエストを送信することになるので、このレスポンスを読むためにこの設定が必要です。


ここまで来たらLaravelの設定は終わりです。

クライアントの準備

Nuxtを使ったのでNuxtで説明していきます。

パスワード・メールアドレスの入力フォームはLaravel側のものを使います。
入力フォームのあるページへクライアントからリダイレクト、Laravelで認証後にリダイレクトでクライエントへ返される、という流れです。

ページをつくる

認証をするのに最低限2つのページが必要です。
1つ目はページを認可サーバのフォームへ置き換えるもの、2つ目は認証後に認可コードとトークンの引き換えを行うものです。
例として前者をlogin.vue、後者をauthenticated.vueとしてpages/ディレクトリ配下に追加します。

login.vue

<template>

</template>

<script lang="ts">
import {Component, Vue} from "nuxt-property-decorator";

@Component
export default class login extends Vue {
  async mounted() {
    const state = createRandomString(40);
    const verifier = createRandomString(128);
    const challenge = await AuthCode.makeCodeChallenge(verifier);

    sessionStorage.setItem('state', state);
    sessionStorage.setItem('verifier', verifier);

    location.replace(`http://localhost:8080/oauth/authorize?${this.transformQueryString({
      'client_id': '2',
      'redirect_uri': 'http://localhost:3000/authenticated',
      'response_type': 'code',
      'scope': '',
      'state': state,
      'code_challenge': challenge,
      'code_challenge_method': 'S256'
    })}`);
  }

  transformQueryString(obj: any) {
    let str = '';
    for (const it in obj) {
      str += `${encodeURIComponent(it)}=${encodeURIComponent(obj[it])}&`;
    }

    return str.slice(0, -1);
  }

  hash(message: string){
    const encoder = new TextEncoder();
    const data = encoder.encode(message);
    return crypto.subtle.digest('SHA-256', data);
  }

  async makeCodeChallenge(verifier: string) {
    const encoded = btoa(String.fromCharCode(...new Uint8Array(await this.hash(verifier))));
    return encoded
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '')
  }

  createRandomString(num: number){
    return [...Array(num)].map(() => Math.random().toString(36)[2]).join('');
  }
}
</script>

<style scoped>

</style>

statecode_verifierを作り、後程ページ内で読める場所に保管します。
これを使う機会は認可後のリダイレクトを受けた時だけなのでsessionStorageが最適かと思います。

stateはランダムな文字列を指定します。Passportの例が40文字だったのでそれに倣って40文字の適当な文字列を入れました。
code_verifierRFC 7636仕様で決められているのだそう。43文字から128文字の文字、数字、それと4種の記号を含んだランダムな文字列である必要があるそうです。
が、Passportの例ではStr::random(128)を使っていたので、少々いい加減ですがjsで似たような感じにしました。

code_challengecode_verifierのハッシュ値を作り、これをBase64エンコードしたものになります。

Passportの例はこんな感じです。

$encoded = base64_encode(hash('sha256', $code_verifier, true));
$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_');

認可後のコード引き換え時にcode_verifierをサーバ側でこの処理をしたものとクライアントが生成したcode_challengeが比較されるのでこれと同じ動きをするものをクライアント側で書かなくてはなりません。

jsだとこんな感じです。
SHA256ハッシュ値生成を文字列にせずArrayBufferを保つのがポイントです。

// ハッシュ値をつくる
const encoder = new TextEncoder();
const data = encoder.encode(message);
const hashed = await crypto.subtle.digest('SHA-256', data); // <- DOMが持つ関数ですが、Promiseが返ります

// base64エンコード
const encoded = btoa(String.fromCharCode(...new Uint8Array(await this.hash(verifier))));
const codeChallenge = encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');

これで下ごしらえは完了です。

location.replace()でページを置き換えます。
location.replace()を使うことで置き換え後のページはブラウザの履歴に残らず、このページで生成するいろいろな値が用意されない状態で認可サーバへ飛んでしまうことを防ぎます。

クエリパラメータとして以下のものが必要です。

{
  'client_id': '1',  // <- Passportクライアントを作ったときに表示されたもの
  'redirect_uri': 'http://localhost:3000/authenticated', // <- Passportクライアントを作ったときに指定したもの
  'response_type': 'code',
  'scope': '', // <- 空だよ Laravelの設定で認証ユーザの権限が表現できるみたい
  'state': '...', // <- 先ほど作ったランダムな文字列
  'code_challenge': '...', // <- 先ほど作ったハッシュ化とかエンコードとかしたやつ
  'code_challenge_method': 'S256'
}

client_idはPassportクライアント作成後に表示された数字を指定してあげます。
redirect_uriはPassportクライアントをコマンドで作ったときに入力したURIを入力します。
これが一致しないと認可されません。

リダイレクトして処理を続けることで認証に必要だけど漏れてしまったらマズい情報を漏らさずに認証ができる、というわけです。

authenticated.vue

<template>
  <div>
    引き換えコードをもらった
    <p v-if="!!token">
      {{ token }}
    </p>
  </div>
</template>

<script lang="ts">

import {Component, Vue} from "nuxt-property-decorator";
import axios from 'axios';

@Component
export default class authenticated extends Vue {
  token: string | null = null;

  mounted() {
    const state = sessionStorage.getItem('state');
    const verifier = sessionStorage.getItem('verifier');
    const code = this.$route.query.code;

    if (state != this.$route.query.state) {
      console.error('ステートが違う');
      return;
    }

    axios.post('http://localhost:8080/oauth/token', {
      grant_type: 'authorization_code',
      client_id: 2,
      redirect_uri: 'http://localhost:3000/authenticated',
      code_verifier: verifier,
      code: code
    }).then(({data}) => console.log(data))
  }
}
</script>

<style scoped>

</style>

login.vueでsessionStorageへ保存しておいたstatecode_verifierを取り出します。
そして、リダイレクトURIのクエリパラメータに認可コードが含まれてますのでcodeパラメータを参照し、取り出します。

次に、認可コード引き換えの規約としてクライアント側でstateを検査する必要があるとのことなので、クエリパラメータのstateとsessionStorageから取り出したstateが一致するか検証します。

そして、認証処理の大詰め、認可コードとトークンの引き換えを行います。
Laravelサーバのoauth/tokenへPOSTリクエストを送信します。

必要なパラメータは以下の通り。

{
  grant_type: 'authorization_code,
  client_id: 1, // <- Passportクライアントを作ったときに表示されたもの
  redirect_uri: 'http://localhost:3000/authenticated', // <- Passportクライアントを作ったときに指定したもの
  code_verifier: verifier, // <- sessionStorageから取り出したverifier
  code: code // <- クエリパラメータから取り出したcode
}

リクエストをサーバに送信するとCORSになるので、まずプリフライトがされ、航路が安全か確認されます。
そのあと、トークンのリクエスト本体が飛んで、トークンがレスポンスされます。

ここでCORSに引っかかったり、code_verifierがなんのといわれることがありますが、エラーレスポンスがしっかりしていて、原因の断定がしやすいかと思うので、頑張って解決しましょう!

うごかす

クライアントのlogin.vueがマッピングされてるところにアクセスしてみます。
そしたらLaravelのログインページへリダイレクトされたような感じになって、ログインするとOAuthらしさのある「アクセスしちゃう?する?」みたいな画面になって、「Authorize」をクリックするとクライアントに戻ってきて、トークンが獲得できると思います。


以上Laravel Passportを使ったPKCEを使った認可コード引き換えをするOAuth2認証でした!!
Web APIの認証をひとつステップアップさせましょう!

見たもの

CORS を許可する - Larapet
PHP: hash - Manual
SubtleCrypto.digest() - Web API | MDN
JavaScriptでバイナリを扱いたい - Qiita
sessionStorageをつかってみる - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バリデーションの検証ツールについて簡単にまとめてみた!(テキストのみ)

accept

  • 主にチェックボックスに使う。
  • true、on、yes、1の値かどうか判断できる。
  • チェックされているかどうかの処理を実行できる。

active_url、url

  • active_urlはアドレスで指定されたドメインが実際に有効かどうかをチェックできる。(dns_get_record関数でDNS情報を取得し、有効なIPアドレスかどうかをチェック)

  • urlはurlの形式で書かれているかどうかをチェック。

after: 日付、 after_or_equal: 日付

  • afterは指定した日付よりも後かどうかをチェックする。
  • after_or_equalは指定した日付と同じかそれよりもあとであるかチェックできる。

(これらは「:」の後に日付を表す文字列をつけて利用します。あるいは、日付を入力する他のフィールド名を指定することもできる。)

before: 日付、 before_or_equal: 日付

  • beforeは指定の日付より前かどうかをチェックできる。
  • before_or_equalは指定の日付と同じかそれより前であるかをチェックできる。

これらはafterの反対です。
日付についてはafterと同じです。

alpha、alpha-dash、alpha-num

  • alphaは入力したテキストが全て「A~Z(a~z)」であるかチェックできる。
  • alpha-dashは「A~Z(a~z)」と「-」と「_」であるかチェックできる。
  • alpha-numは「A~Z(a~z)」と数字であるかをチェックできる。

array

  • フィールドが配列となっているかどうかをチェックできる。

between: 最小値, 最大値

  • 数値のフィールドで用いる。
  • 値が指定の範囲内かをチェックできる。
  • betweenの後には、最小値と最大値を「,」で区切って記述しておく。

boolean

  • 値が真偽値かチェックできる。
  • true、false、0、1といった値であれば判別できる。(それ以外は不可)

date、date_format: フォーマット

  • dateは入力されたテキストが日時の値として扱えるものかをチェックできる。これはstrtotime関数でタイムスタンプに変換できればOKです。(https://www.php.net/manual/ja/function.strtotime.php strtotime関数については公式リファレンスを参照)
  • date_formatは、入力された値が指定フォーマットの定義に一致しているかどうかをチェックします。フォーマットに沿った形式ならばOKです。

different: フィールド、 same: フィールド

  • 指定されたフィールドと同じ値かどうかをチェックできる。
  • differentは異なる値であればOKです。
  • sameは反対に同じ値ならばOKです。

digits: 桁数、 digits_between: 最小桁数, 最大桁数

  • 数値で使う。
  • 入力された値が指定された桁数かチェックできる。
  • digitsは指定の桁数ならばOKです。
  • digits_betweenは最小桁数〜最大桁数の範囲内であればOKです。

distinct

  • 配列として用意されている項目で使う。
  • 配列内に同じ値がないかチェックできる。(同じ値が複数あった場合不可)

email

  • 電子メールアドレスの形式かチェックできる。
  • これは形式をチェックするだけで実際にそのアドレスが使えるかはチェックしない

exists: テーブル, カラム

  • データベースを利用する場合に使われる。
  • 入力された値が指定のデータベースの指定のカラムにあるかどうかをチェックできる。(あればOK、なければ不可)

filled、 required

  • filledはその項目が空でないかチェックできる。(入力されていれば不可)
  • requiredはそれが必須項目であることを示す。(入力されていればOKです)

image

  • ファイルのフィールドで指定します。
  • 指定されたファイルがイメージファイルかどうかをチェックできる。

in: 値1, 値2, ・・・・・、not_in: 値1, 値2, ・・・・・

  • inは入力された値がin: ・・・・・に用意した値に含まれているかチェックできる。
  • not_inは逆です。

integer、 numeric

  • integerは値が整数であることをチェックできる。
  • numericは値が数値であることをチェックできる。

ip、 ipv4、 ipv6

  • 値がIPアドレスかどうかチェックできる。
  • ipv4はIPアドレスの中でipv4かどうかチェックできる。
  • ipv6はIPアドレスの中でipv6かどうかチェックできる。

json

  • 値がjson形式の文字列かどうかをチェックできる。

min: 値、 max: 値

  • 値が指定よりも大きいか小さいかをチェックできる。
  • minは指定の値よりも小さい、maxは大きいかをチェックできる。
  • 最大か最小かを調べる場合は前述したbetweenを使う。

regix: パターン

  • 指定した正規表現パターンにマッチするかチェックできる。

size: 値

  • 値の大きさをチェックできる。
  • 文字列ならば文字数、数値の場合は整数値、配列の場合は要素の数をチェックできる。

string

  • 文字列の値かどうかをチェックできる。

unique: テーブル, カラム

  • データベース利用の際に用います。
  • 指定のテーブルの指定のカラムに同じ値が存在しないかチェックできる。

学習のまとめとして書いてみました。
もし間違いや記述が古いなど指摘をいただけますと幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】定義した「名前付きルート」からのURL生成方法

名前付きルートへのURLを生成するには

ルート定義ファイルroutes/web.phpに名前付きルートの設定をしていれば、その名前をグローバルなroute関数を使用することで、URLを生成したり、設定した名前のルートへリダイレクトできたりします。

// URLの生成
$url = route('profile');

// リダイレクトの生成
return redirect()->route('profile');

上記のように、コントローラ内とかで利用可能です。
ルート定義で名前付きルートをするだけで、リダイレクトの文が、とても見やすくなります。ぜひ活用してみましょう。

パラメータも付け加えて、URLを生成することもできる

ちなみに、名前付きルートを設定したルートで、なおかつ、パラメータも定義してある場合は、route関数の第2引数にパラメータを渡すことができます。

routes/web.php
Route::get('user/{id}/profile', function ($id) {
    //
})->name('profile');

上記のように、ルート定義でprofileという名前を指定のルートにつけて

HogeController.php
$url = route('profile', ['id' => 1]);
// $urlには「 /user/1/profile 」

コントローラでパラメータを渡してあげて、URLを生成することができます。

参考文献:https://readouble.com/laravel/7.x/ja/routing.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む