- 投稿日:2020-09-16T23:21:54+09:00
PHP、Laravel学習 2
ポートフォリオ作成
ここからポートフォリオ作成に入ります。
献立を考えてくれるWebアプリケーションを開発したいです
私は料理が好きで、よく作るのですがレパートリーがたくさんあるにもかかわらず
いざ何かを作ろうとすると、かなり悩んでしまいます
私は料理を買い物からするので、スーパーに行ったは良いものの、そこで買い物をしながら
悩んでしまったり、いつも作っている物の繰り返しになってしまいます。解決したい問題
1、たくさんレパートリーがあるはずなのに忙しい中で料理をしようと思うとそれを思うように
引き出すことができない問題2、料理の種類が和食、洋食、中華...等、いざ作ろうと思うと混ざってしまったりする問題
3、献立を考えている時間の削減、スーパーで買い物をする時の効率化
4、似通った料理の連続になってしまう問題(例えば、オムライス→炊き込みご飯→カレーの繰り返し等)
欲しい機能
1、献立をランダムで表示してくれる
2、献立に必要な材料の入力、保存ができる
3、献立のレパートリーを入力、保存できる
4、献立をカテゴライズできる
5、献立のカテゴリーを入力、保存できる
6、入力した献立の確認ができる
7、入力したカテゴリーの確認ができる次回から作っていきます。
- 投稿日:2020-09-16T23:17:10+09:00
エラー内容を調べても解決できない時に確認したいこと
エンジニアになって最初の1ヶ月、エラーが発生したらとにかく調べる習慣を付けました。
調べてどうしても解決できそうにない時には聞くようにしています。
以前、あるエラーで3時間くらい悩み、色々試した結果、
”バージョンが違っていた” ことがありました。
バージョンが違っていて、使用しているパターンが使えない可能性があるので、検索して見つけた記事が、バージョンいくつのものなのかを確認すると良いかもです。
- 投稿日:2020-09-16T22:12:08+09:00
PHPでログイン機能を実装したら、ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)というエラーでハマった話
https://awesome-linus.com/2019/06/05/laravel-tutorial-todo/
こちらのサイト様の記事を参考にログイン機能を実装していたら、はまりました。
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)パスワードは使用していません。
ルートユーザーでアクセスしたら拒否されたというもの。
ログインと新規登録機能を実装し、アプリをWEBで開いた段階でこのメッセージが表示されました。$ mysql -u root ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)mysql -u rootでログインするとエラーが出るが、
$mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g.このように打ち込むとエラーが出ない。つまり、パスワードをYESにして、設定したパスワードをPHPのファイルのどこかに打ち込む必要があるのではないかと判断。
それっぽいファイルを探してみる。
database.php'connections' => [ 'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ], # 以下省略'password' => env('DB_PASSWORD', ''),
この部分に注目した。
’’の間にパスワードを手動で入れればもしかしたら行けるのでは?
と思ったが、’’の中に手動でパスワードを打ち込んでもうまくいかない。DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=todo DB_USERNAME=root DB_PASSWORD=secretしやここのsecretで設定されているパスワードの部分を書き換えたらいけるのではと思い、
secretの部分を書き換えたりしたがうまくいかず。パスワードをYESで接続するにはどうしたらいいのだ。
仕方ないので方針転換して、rootユーザのパスワードをなしにすることにしました。
$mysql -u root -pパスワードを打ち込んで接続
mysql> SET PASSWORD FOR ユーザ名@ホスト名=password('新しいパスワード');新しいパスワードの部分を空欄にして設定。
パスワードを打ち込んでログインしてみます。無事に成功!
rootユーザのパスワードの設定を使用をYESにして、パスワードを設定する方法はわかりませんでしたが、
パスワードの設定をログインして書き換えることでうまく行きませんでした。またなにかエラーを解決しましたら、引き続きアウトプットしていきます。
- 投稿日:2020-09-16T16:58:21+09:00
PHPで短絡評価をうまく使って条件分岐をすっきり書く
論理演算をしっかり理解している人にとっては当たり前の内容だと思います。
PHPで複数の条件がある条件分岐を書くとき、、、
example.php$con_a;//条件A boolean $con_b;//条件B boolean if($con_a){ if($con_b){ //処理1 }else{ //処理2 } }else{ //処理2 }このように書くと処理2を二度記述することになります。
処理2が短い処理であればこれで良いですが、長くなるとさすがに見づらくなるのでどうにかしたいです。ここで、PHPのリファレンスを読んでみます。
→https://www.php.net/manual/ja/language.operators.logical.phpdocument.php<?php // -------------------- // foo() は決してコールされることはありません。これらの演算子は短絡評価を行うからです。 $a = (false && foo()); $b = (true || foo()); $c = (false and foo()); $d = (true or foo());短絡評価とは、
「≪左辺≫ ≪論理演算子≫ ≪右辺≫」というような、論理演算子による式(論理演算子式)があるとする。左辺(第一引数)を評価した段階で式全体の値が定まらない場合のみ右辺(第二引数)を評価する評価法。
らしいです。(wikipediaより)つまり、条件が2つ以上ある場合、1つ目の条件で式全体の結果が確定する場合は2つ目以降は考慮しない(する必要がない)ということです。
これを踏まえてexample.php
を書き換えると、example.php$con_a;//条件A boolean $con_b;//条件B boolean if($con_a && $con_b){ //処理1 }else{ //処理2 }と書くことができます。
論理演算についてしっかり分かっていれば当然っちゃ当然ですが、条件が複雑になったりnot(!)がついたりするとややこしくなりますね。
- 投稿日:2020-09-16T16:25:07+09:00
自作したウイルススキャンのライブラリがあまりにも残念だったので、interfaceを使って少しでもましにしたい
みなさんこんにちは。
昔clamav使ったファイルのウイルススキャンをやったことがあって、それを思い出そうと思って適当なライブラリを作ったのですが、本当に適当すぎる作りになっちゃって、いや、これこのままだとさすがに恥ずかしすぎるやろ、って思いまして、ちょいと手直しすることにしたのです。
手直ししているときに、どんなふうに直すか、だけでなく、どんな風に使ってもらえるかを考えていたら、やっぱりinterface用意しておくのが都合がいいね、って感じになりました。というわけで、interface を使ってよりユーザフレンドりーなライブラリを目指してみましょう。
interface
はじめにinterfaceの解説をするので、不要な人は飛ばしてください。
interfaceとは
PHPにおいてinterfaceとは、あるクラスを定義するときに必要な公開メソッドをまとめたもの、です。
こんな風に書きます。Sequence.php<?php interface Sequence { public function factorial(int $num): int; public function permutation(int $num, int $factors): int; }ここには、単純な階乗と順列の計算をするメソッドが定義されていますが、動作の中身はありません。中身は別に実装する必要があります。
RecursiveSequence.php<?php require_once './Sequence.php'; class RecursiveSequence implements Sequence { public function factorial(int $num): int { if ($num === 0) { return 1; } return $num * $this->factorial($num - 1); } public function permutation(int $num, int $factors): int { return $this->factorial($num) / $this->factorial($num - $factors); } }interfaceの中身を実装したクラスに対しては、
implements Sequence
のように書かきます。
このようなclass <class_name> implements <interface_name>
と書いたとき、<class_name> は <interface_name> の実装であるといいます。
あとは、これを使うだけ。test.php<?php require './Sequence.php'; require './RecursiveSequence.php'; $obj = new RecursiveSequence; echo $obj->factorial(1), "\n"; echo $obj->factorial(3), "\n"; echo $obj->factorial(10), "\n"; echo $obj->permutation(3, 2), "\n"; echo $obj->permutation(5, 3), "\n"; echo $obj->permutation(10, 5), "\n";動作させると
# php test.php 1 6 3628800 6 60 30240となります。
interfaceの特徴
大雑把には以下のような特徴があります。
- 型宣言に使える
- interfaceをimplementsしたクラスは、interfaceで宣言したメソッドをすべて実装していないとエラーが出る
- interfaceも継承できる
- implementsに複数のinterfaceを指定できる
interfaceを型宣言に使える
普通のクラス名と同じく、interfaceも型宣言に使えます。
メソッドの返り値
Factory.php<?php require_once './Sequence.php'; require_once './RecursiveSequence.php'; class Factory { public function create(): Sequence { return new RecursiveSequence; } }一方で、引数でも使えて
test.php<?php require './Sequence.php'; require './Factory.php'; $obj = (new Factory)->create(); test($obj); function test(Sequence $obj) { echo $obj->factorial(1), "\n"; echo $obj->factorial(3), "\n"; echo $obj->factorial(10), "\n"; echo $obj->permutation(3, 2), "\n"; echo $obj->permutation(5, 3), "\n"; echo $obj->permutation(10, 5), "\n"; }こんな風になります。
PHP7.4以降で追加されるプロパティの型宣言でも、interfaceを使うことができます。
<?php require_once './Sequence.php'; require_once './RecursiveSequence.php'; class Some { private Sequence $obj; public function __construct() { $this->obj = new RecursiveSequence; } public function getObj() { return $this->obj; } } $some = new Some; echo get_class($some->getObj()), "\n"; // RecursiveSequence宣言されたメソッドがそろっていないとエラー
interfaceをimplementsしたクラスは、すべてのメソッドを不足なく実装する必要があります。
以下のような場合はコンソールで実行するとエラーが出ます。dame1.php<?php require_once './Sequence.php'; /** * Sequence の permutationが実装されていないパターンん */ class Dame1 implements Sequence { public function factorial(int $num): int { return 1; } } $obj = new Dame1; // Fatal error: Class Dame1 contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Sequence::permutation) in /var/www/dame1.php on line 5dame2.php<?php require_once './Sequence.php'; /** * Sequence の permutationが実装されていないパターンん */ class Dame2 implements Sequence { public function factorial(int $num): int { return 1; } public function permutation(int $num, int $factors): string { return "1"; } } $obj = new Dame2; // Fatal error: Declaration of Dame2::permutation(int $num, int $factors): string must be compatible with Sequence::permutation(int $num, int $factors): int in /var/www/dame2.php on line 15継承できる
interfaceを継承することもできます。
別のクラスを継承しつつ、implementsを指定することもできます。test2.php<?php require_once './Sequence.php'; require_once './RecursiveSequence.php'; interface OtherSequence extends Sequence { } class Test2 extends RecursiveSequence implements OtherSequence { } $obj = new Test2; echo $obj->permutation(10, 3), "\n"; // 720implementsに複数のinterfaceを指定できる
クラス定義のimplementsには、複数のinterfaceを指定することができます。
test4.php<?php interface Factorial { public function factorial(int $num): int; } interface Permutation { public function permutation(int $num, int $factors): int; } // 複数のinterfaceを実装できる class Some implements Factorial, Permutation { public function factorial(int $num): int { return 1; } public function permutation(int $num, int $factors): int { return 1; } } $some = new Some; test($some);// Factorial test2($some);// Permutation function test(Factorial $obj) { echo 'Factorial', "\n"; } function test2(Permutation $obj) { echo 'Permutation', "\n"; }ここで
Some
は、Factorial
であってPermutation
でもあるということになります。自作ライブラリにinterfaceを!
clamAVを使ったウイルススキャン
これですね。
https://github.com/niisan-tokyo/web-clamav-php/
前回の記事用に作ったリモートのclamdサーバにウイルススキャンしてもらうライブラリです。
https://qiita.com/niisan-tokyo/items/798c945ab4da26c31d16旧実装の問題点
何が問題だったかって、ここですよね
$manager = new Niisan\ClamAV\Manager(['url' => 'clamav']);この
Manager
の中身はリモートのclamdサーバにファイルを分割して投げるという、実装になっています。しかし、ウイルススキャンの方法は前回の記事でみたように、少なくとも2つはあります。
つまるところ、このようなManager
の実装をそのまま使うコードだと、別実装を使おうとしても、すべてのコードの中で、このManagerを使っている部分を探し出し、置き換え、さらに動作も初めから見直す必要があります。
さらに自作せにゃならんとなれば、その実装まではいるので、ことさら面倒です。interfaceを使って実装の陰を消す
とりあえず、実装を直に使っている状況はよろしくないということで、interfaceを通して使うようにしましょう。interfaceを通しておけば、そのinterfaceでやりたいこと、つまり、存在するメソッドとその使い方がわかります。
今回はウイルススキャンなので、こんな感じに作ってみます。src/Scanner.php<?php namespace Niisan\ClamAV; interface Scanner { /** * Ping to scanner server or socket * * When the scan server is not connected, * throw RuntimeException. * * @return boolean * @throws \RuntimeException */ public function ping() :bool; /** * Scan a file. * * When the file have some virus, this method return false, * otherwise return true. * * @param string $file_path * @return boolean */ public function scan(string $file_path): bool; }接続確認の
ping
と、ファイルスキャンするscan
があればいいといった感じですね。
次にこれを生成するクラスとしてFactory
を実装します。src/ScannerFactory.php<?php namespace Niisan\ClamAV; use Niisan\ClamAV\Scanners\RemoteScanner; class ScannerFactory { public static function create(array $config): Scanner { return new RemoteScanner($config); } }createの返り値をScannerにしておきます。
さらに、前回作成した
Manager
とやらを、Scanner
を実装したRemoteScanner
として定義しなおします。src/Scanners/RemoteScanner.php<?php namespace Niisan\ClamAV\Scanners; use Generator; use Niisan\ClamAV\Scanner; class RemoteScanner implements Scanner { private $port = 3310; private $url; public function __construct(array $option) { if (empty($option['url'])) { throw new \RuntimeException('ClamAV server host is not input.'); } $this->url = $option['url']; $this->port = $option['port'] ?? $this->port; } /** * @inheritDoc */ public function ping(): bool { // 何かの処理 return true; } /** * @inheritDoc */ public function scan(string $file_path): bool { // 何かの処理 return $this->checkMessage($message, 'OK'); } }そして、先に
Manager
をnewしていた部分をFactoryで置き換えます。$scanner = \Niisan\ClamAV\ScannerFactory::create([ 'host' => 'example.com' ]);一見して、newしていたところを
ScannerFactory::create()
に変えただけですが、この関数の返り値の型がScanner
となっていますので、このコード中の$scanner
は、とりあえずping
とscan
が実装された何か、という状態になります。実装を追加して好きなほうを選べるようにする
さて、このライブラリはリモートのclamdサーバを使うことを前提にしていましたが、サーバ一台しかないとかで同じサーバにclamdを動作させている場合もあると思い、それ用の実装を用意してみましょう。
src/Scanners/LocalScanner.php<?php namespace Niisan\ClamAV\Scanners; use Niisan\ClamAV\Scanner; class LocalScanner implements Scanner { private $path; public function __construct(array $options = []) { if (! isset($options['path'])) { throw new \RuntimeException("Socket path not given, i.e. ['path' => /var/run/clamav/clamd.ctl"); } $this->path = $options['path']; } /** * @inheritDoc */ public function ping(): bool { // いろいろな実装 return true; } /** * @inheritDoc */ public function scan(string $file_path): bool { // いろいろな実装 return $this->checkMessage($message, 'OK'); }この実装も
Scanner
の実装になっているので、もともとあったRemoteScanner
と同じように使えるはずです。
新しくできた実装を使うために、Factoryを改造します。src/ScannerFactory.php<?php namespace Niisan\ClamAV; use Niisan\ClamAV\Scanners\RemoteScanner; use Niisan\ClamAV\Scanners\LocalScanner; class ScannerFactory { public static function create(array $config): Scanner { return ($config['driver'] === 'local') ? new LocalScanner($config): new RemoteScanner($config); } }あとは、Factoryを使っているところで
$scanner = \Niisan\ClamAV\ScannerFactory::create([ 'driver' => 'remote', 'host' => 'example.com' ]);こんな感じで使えます。
このFactoryからはLocalScanner
とRemoteScanner
の二つをとりうるわけですが、同じinterfaceScanner
の実装になっているので、少なくともping
とscan
のみを使っている限りは、使い方は保証されています。最終的な使用法
この前とあまり変わらないですが、こんな感じです。
composer require niisan-tokyo/web-clamav-php:v0.2.1でライブラリをインストールするなり更新するなりしてから
index.html<!DOCTYPE html> <head> <meta charset="utf-8" /> </head> <body> <h1>ファイルを送信</h1> <form action="file.php" enctype="multipart/form-data" method="POST"> <input type="file" name="upfile" /><br> <button type="submit" name="go">送信する</button> </form> </body>file.php<?php require 'vendor/autoload.php'; use Niisan\ClamAV\ScannerFactory; $file = $_FILES['upfile']; $scanner = ScannerFactory::create([ 'driver' => 'remote', 'url' => 'clamav' ]); $result = true; if ($file) { $start = microtime(true); $result = $scanner->scan($file['tmp_name']); $resTime = microtime(true) - $start; } ?> <!DOCTYPE html> <head> <meta charset="utf-8" /> </head> <body> <h1>問題<?php if ($result) {?> なし <?php } else {?> あり <?php } ?>!</h1> 時間: <?php echo $resTime ?><br> <a href="index.html">戻る</a> </body>でいけます。
もしも、ローカルのclamdサーバで使うときは$scanner = ScannerFactory::create([ 'driver' => 'local', 'path' => '/var/run/clamav/clamd.ctl' ]);としてやればよいです。
まとめ
こんな感じで、interfaceを使ってライブラリの使い勝手を少しだけ上げてみました。
ここまでくればDIとかもできるので、そっちへの応用も記事化しようかなって思います。今回はこんなところです。
- 投稿日:2020-09-16T14:08:22+09:00
[Laravel] LINEログインv2.1を実装する
今回の題
LINEログインをLaravelにて実装しました。
アウトプットとして残します。使用したバージョン
- Laravel 6.8 ※Laravelのインストール手順は省略します。
- LINEログインv2.1
- guzzle7.0
チャネルIDとチャネルシークレットの取得
4ステップに分けて説明していきます。
1. 開発者用のアカウントを作成
以下にアクセスし、LINEのアカウントを使って開発者用のページにログインしてください。
LINE Developers2. プロバイダーの作成
以下のページの左サイドバーのプロバイダーを選択し、画面中央辺りの「作成」を押してください。
LINE Developers コンソール作成するプロバイダー名を求められるので適当に入力し、「作成」を押したらプロバイダーの作成は完了です。
3. チャネルの作成
プロバイダーの作成後、以下のようなページに飛ばされます。
画像内の赤枠の「LINE ログイン」を選択してください。
チャネル作成画面に飛びます。
チャネルの名前や説明などは適宜全て入力し、アプリタイプは「ウェブアプリ」を選択しておいてください。
「作成」を押したらチャネルの作成は完了です。
4. チャネルIDとチャネルシークレットの取得
チャネル作成後、作成したチャネルの設定ページに飛ばされます。
(画像は黒く塗りつぶしています)以上でチャネルIDとチャネルシークレットがGETできました。
コールバックURLの設定
ユーザーが認証を許可した後に、リダイレクトされるURLを指定します。
LINEログイン設定のタブに移動してコールバックURLを入力し更新を押してください。
尚、この記事では後のルーティングでauth/line/callback
というパスを指定するので、記事通りに進めるのであれば、
https://各自のドメイン/auth/line/callback
で設定しておいて下さい。
メールアドレスを取得する設定
ログインしたユーザーのメールアドレスを取得するための設定です。必要な場合のみ設定して下さい。
チャネル基本設定のタブの下の方に「OpenID Connect」という項目があります。
申請ボタンを押すと、申請条件への同意と、メールアドレスの取得と利用についてユーザーに提示する文面のスクリーンショットのアップロードを求められるので済ませます。
必要なライブラリの準備
・Guzzle
リクエストを送信する際に使用します。
公式$ composer require guzzlehttp/guzzleLaravel
ここからコードを書いていきます。
設定
・チャネルID
・チャネルシークレット
・コールバックURL
は.env
に書いておき、config経由で呼び出すことにします。.envLINE_CHANNEL_ID=あなたのチャネルid LINE_CHANNEL_SECRET=あなたのチャネルシークレット LINE_CALLBACK_URL=設定したコールバックURLconfigディレクトリにline.phpというファイルを作り、envから値を受け取るように編集します。
config/line.php<?php return [ 'client_id' => env('LINE_CHANNEL_ID'), 'client_secret' => env('LINE_CHANNEL_SECRET'), 'callback_url' => env('LINE_CALLBACK_URL'), ];ルーティング
routes/web.php
を以下のように設定します。routes/web.php// LINEの認証画面に遷移 Route::get('auth/line', 'Auth\LineOAuthController@redirectToProvider')->name('line.login'); // 認証後にリダイレクトされるURL(コールバックURL) Route::get('auth/line/callback', 'Auth\LineOAuthController@handleProviderCallback');view
この記事ではURLに遷移させるだけの単純なものを書いておきます。
本来は、公式の指定するデザインのログインボタンを設定する必要があります。
LINEログインボタン デザインガイドライン<a href="{{ route('line.login') }}">LINEでログイン</a>コントローラー
以下で作成
$ php artisan make:controller Auth/LineOAuthController編集します。
app/Http/Controllers/Auth/LineOAuthController.php<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Str; use GuzzleHttp\Client; class LineOAuthController extends Controller { private const LINE_OAUTH_URL = 'https://access.line.me/oauth2/v2.1/authorize?'; private const LINE_TOKEN_API_URL = 'https://api.line.me/oauth2/v2.1/'; private const LINE_PROFILE_API_URL = 'https://api.line.me/v2/'; private $client_id; private $client_secret; private $callback_url; public function __construct() { $this->client_id = Config('line.client_id'); $this->client_secret = Config('line.client_secret'); $this->callback_url = Config('line.callback_url'); } public function redirectToProvider() { $csrf_token = Str::random(32); $query_data = [ 'response_type' => 'code', 'client_id' => $this->client_id, 'redirect_uri' => $this->callback_url, 'state' => $csrf_token, 'scope' => 'profile openid', ]; $query_str = http_build_query($query_data, '', '&'); return redirect(self::LINE_OAUTH_URL . $query_str); } public function handleProviderCallback(Request $request) { $code = $request->query('code'); $token_info = $this->fetchTokenInfo($code); $user_info = $this->fetchUserInfo($token_info->access_token); // ログイン処理 } private function fetchUserInfo($access_token) { $base_uri = ['base_uri' => self::LINE_PROFILE_API_URL]; $method = 'GET'; $path = 'profile'; $headers = ['headers' => [ 'Authorization' => 'Bearer ' . $access_token ] ]; $user_info = $this->sendRequest($base_uri, $method, $path, $headers); return $user_info; } private function fetchTokenInfo($code) { $base_uri = ['base_uri' => self::LINE_TOKEN_API_URL]; $method = 'POST'; $path = 'token'; $headers = ['headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ] ]; $form_params = ['form_params' => [ 'code' => $code, 'client_id' => $this->client_id, 'client_secret' => $this->client_secret, 'redirect_uri' => $this->callback_url, 'grant_type' => 'authorization_code' ] ]; $token_info = $this->sendRequest($base_uri, $method, $path, $headers, $form_params); return $token_info; } private function sendRequest($base_uri, $method, $path, $headers, $form_params = null) { try { $client = new Client($base_uri); if ($form_params) { $response = $client->request($method, $path, $form_params, $headers); } else { $response = $client->request($method, $path, $headers); } } catch(\Exception $ex) { // 例外処理 } $result_json = $response->getbody()->getcontents(); $result = json_decode($result_json); return $result; } }アクション名が思いつかなかったのでSociliteから流用して使ってます。
handleProviderCallback()の変数、
$token_info
、$user_info
には、
$token_info
→ トークン情報(アクセストークン、リフレッシュトークンなど)$user_info
→ ユーザー情報(ユーザーID、ユーザー名、プロフィール画像、プロフィールメッセージ)が入っていますので、これらを各自のテーブル構成に合わせて保存しログインさせて下さい。
尚、アクセストークンの有効期間は30日ですので、適宜リフレッシュトークンを使って新しいアクセストークンを取得して下さい。一言
ソーシャルログインはSocialiteを使ったTwitterログインしか実装経験はありませんでしたが、LINEでも意外と簡単にできて驚きでした。特に鬼門はないかと思います。
何か間違えなどがあればコメントにお願いいたします。参考
- 投稿日:2020-09-16T14:08:22+09:00
[Laravel] LINEログインを実装する
今回の題
LINEログインをLaravelにて実装しました。
アウトプットとして残します。使用したバージョン
- Laravel 6.8 ※Laravelのインストール手順は省略します。
- LINEログインv2.1
- guzzle7.0
チャネルIDとチャネルシークレットの取得
4ステップに分けて説明していきます。
1. 開発者用のアカウントを作成
以下にアクセスし、LINEのアカウントを使って開発者用のページにログインしてください。
LINE Developers2. プロバイダーの作成
以下のページの左サイドバーのプロバイダーを選択し、画面中央辺りの「作成」を押してください。
LINE Developers コンソール作成するプロバイダー名を求められるので適当に入力し、「作成」を押したらプロバイダーの作成は完了です。
3. チャネルの作成
プロバイダーの作成後、以下のようなページに飛ばされます。
画像内の赤枠の「LINE ログイン」を選択してください。
チャネル作成画面に飛びます。
チャネルの名前や説明などは適宜全て入力し、アプリタイプは「ウェブアプリ」を選択しておいてください。
「作成」を押したらチャネルの作成は完了です。
4. チャネルIDとチャネルシークレットの取得
チャネル作成後、作成したチャネルの設定ページに飛ばされます。
(画像は黒く塗りつぶしています)以上でチャネルIDとチャネルシークレットがGETできました。
コールバックURLの設定
ユーザーが認証を許可した後に、リダイレクトされるURLを指定します。
LINEログイン設定のタブに移動してコールバックURLを入力し更新を押してください。
尚、この記事では後のルーティングでlogin/line/callback
というパスを指定するので、記事通りに進めるのであれば、
https://各自のドメイン/login/line/callback
で設定しておいて下さい。
メールアドレスを取得する設定
ログインしたユーザーのメールアドレスを取得するための設定です。必要な場合のみ設定して下さい。
チャネル基本設定のタブの下の方に「OpenID Connect」という項目があります。
申請ボタンを押すと、申請条件への同意と、メールアドレスの取得と利用についてユーザーに提示する文面のスクリーンショットのアップロードを求められるので済ませます。
必要なライブラリの準備
・Guzzle
リクエストを送信する際に使用します。
公式$ composer require guzzlehttp/guzzleLaravel
ここからコードを書いていきます。
設定
・チャネルID
・チャネルシークレット
・コールバックURL
は.env
に書いておき、config経由で呼び出すことにします。.envLINE_CHANNEL_ID=あなたのチャネルid LINE_CHANNEL_SECRET=あなたのチャネルシークレット LINE_CALLBACK_URL=設定したコールバックURLconfigディレクトリにline.phpというファイルを作り、envから値を受け取るように編集します。
config/line.php<?php return [ 'client_id' => env('LINE_CHANNEL_ID'), 'client_secret' => env('LINE_CHANNEL_SECRET'), 'callback_url' => env('LINE_CALLBACK_URL'), ];ルーティング
routes/web.php
を以下のように設定します。routes/web.php// LINEの認証画面に遷移 Route::get('auth/line', 'Auth\LineOAuthController@redirectToProvider')->name('line.login'); // 認証後にリダイレクトされるURL(コールバックURL) Route::get('auth/line/callback', 'Auth\LineOAuthController@handleProviderCallback');view
この記事ではURLに遷移させるだけの単純なものを書いておきます。
本来は、公式の指定するデザインのログインボタンを設定する必要があります。
LINEログインボタン デザインガイドライン<a href="{{ route('line.login') }}">LINEでログイン</a>コントローラー
以下で作成
$ php artisan make:controller Auth/LineOAuthController編集します。
app/Http/Controllers/Auth/LineOAuthController.php<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Str; use GuzzleHttp\Client; class LineOAuthController extends Controller { private const LINE_OAUTH_URL = 'https://access.line.me/oauth2/v2.1/authorize?'; private const LINE_TOKEN_API_URL = 'https://api.line.me/oauth2/v2.1/'; private const LINE_PROFILE_API_URL = 'https://api.line.me/v2/'; private $client_id; private $client_secret; private $callback_url; public function __construct() { $this->client_id = Config('line.client_id'); $this->client_secret = Config('line.client_secret'); $this->callback_url = Config('line.callback_url'); } public function redirectToProvider() { $csrf_token = Str::random(32); $query_data = [ 'response_type' => 'code', 'client_id' => $this->client_id, 'redirect_uri' => $this->callback_url, 'state' => $csrf_token, 'scope' => 'profile openid', ]; $query_str = http_build_query($query_data, '', '&'); return redirect(self::LINE_OAUTH_URL . $query_str); } public function handleProviderCallback(Request $request) { $code = $request->query('code'); $token_info = $this->fetchAccessToken($code); $user_info = $this->fetchUserInfo($token_info->access_token); // ログイン処理 } private function fetchUserInfo($access_token) { $base_uri = ['base_uri' => self::LINE_PROFILE_API_URL]; $method = 'GET'; $path = 'profile'; $headers = ['headers' => [ 'Authorization' => 'Bearer ' . $access_token ] ]; $user_info = $this->sendRequest($base_uri, $method, $path, $headers); return $user_info; } private function fetchAccessToken($code) { $base_uri = ['base_uri' => self::LINE_TOKEN_API_URL]; $method = 'POST'; $path = 'token'; $headers = ['headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ] ]; $form_params = ['form_params' => [ 'code' => $code, 'client_id' => $this->client_id, 'client_secret' => $this->client_secret, 'redirect_uri' => $this->callback_url, 'grant_type' => 'authorization_code' ] ]; $token_info = $this->sendRequest($base_uri, $method, $path, $headers, $form_params); return $token_info; } private function sendRequest($base_uri, $method, $path, $headers, $form_params = null) { try { $client = new Client($base_uri); if ($form_params) { $response = $client->request($method, $path, $form_params, $headers); } else { $response = $client->request($method, $path, $headers); } } catch(\Exception $ex) { // 例外処理 } $result_json = $response->getbody()->getcontents(); $result = json_decode($result_json); return $result; } }handleProviderCallback()の変数、
$token_info
、$user_info
には、
$token_info
→ トークン情報(アクセストークン、リフレッシュトークンなど)$user_info
→ ユーザー情報(ユーザーID、ユーザー名、プロフィール画像、プロフィールメッセージ)が入っていますので、これらを各自のテーブル構成に合わせて保存しログインさせて下さい。
尚、アクセストークンの有効期間は30日ですので、適宜リフレッシュトークンを使って新しいアクセストークンを取得して下さい。一言
ソーシャルログインはSocialiteを使ったTwitterログインしか実装経験はありませんでしたが、LINEでも意外と簡単にできて驚きでした。特に鬼門はないかと思います。
何か間違えなどがあればコメントにお願いいたします。参考
- 投稿日:2020-09-16T12:00:38+09:00
Laravel getFormattedDueDateAttributeでTrailing data のエラーが出た場合
初めに
仕事でLaravelの知識が必要となり、下記リンクの良さげなチュートリアルをやっていまた。
https://www.hypertextcandy.com/laravel-tutorial-todo-app-list-tasksチュートリアル通りにやっていてもエラーってやっぱり起きるのですよね〜。。。
環境
チュートリアルが2018年のものだったので、少し古めのv6でやってみた(この時、もっと詳しく年代別のバージョンを調べていれば・・・)
$ php artisan --version Laravel Framework 6.18.40問題の箇所
上記リンク先のチュートリアルである「日付の表示形式を変更する」の部分
Task.php// この行を追加 use Carbon\Carbon; class Task extends Model { /* 中略 */ /** * 整形した期限日 * @return string */ public function getFormattedDueDateAttribute() { return Carbon::createFromFormat('Y-m-d', $this->attributes['due_date']) ->format('Y/m/d'); } }Carbon ライブラリを使って期限日の値の形式を変更して返す部分で、以下のようなエラーが!
Carbon\Exceptions\InvalidFormatException Trailing data (View: /Users/***/resources/views/tasks/index.blade.php)全てコピペし直したりしたので、どう考えてもスペルミスではない。とすると、、、
バージョンによる違いでした。。
https://stackoverflow.com/questions/50208932/laravel-model-trailing-data-when-save-the-model
上記stackoverflowで見た回答を試すとエラーは解消された。。。
おそらくv5とv6の違いなのでしょう。Task.php/** * 整形した期限日 * @return string */ public function getFormattedDueDateAttribute() { // createFromFormatの中を 'Y-m-d H:i:s'に変更する return Carbon::createFromFormat('Y-m-d H:i:s', $this->attributes['due_date']) ->format('Y/m/d'); }と言うか、チュートリアルのページをよく見ると、以下のようなコメントもありました
お世話になります。
Laravel6.14.0です。
後半の「日付の表示形式を変更する」の項で、getFormattedDueDateAttributeを追加し、テンプレートの修正をしてブラウザを再読み込みしたところ、「trailing error」が出ました。
createFromFormat内を('Y-m-d H:i:s' , ......)と修正したところ、解決しました。
念のため、お知らせしておきます。
- 投稿日:2020-09-16T10:10:39+09:00
Laravel8 ユーザ認証時にメールアドレス確認の処理を付与する
目的
- LaravelのAuth認証ではメールアドレスはただ入力するだけで、入力されたメールアドレスが正しい物であるかどうかの確認がない
- 入力メールアドレス宛にメールを送信し、初回のログインはメール内のURLからのみ行える様にする
実施環境
- ハードウェア環境
項目 情報 OS macOS Catalina(10.15.5) ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports) プロセッサ 2GHzクアッドコアIntel Core i5 メモリ 32 GB 3733 MHz LPDDR4 グラフィックス Intel Iris Plus Graphics 1536 MB
- ソフトウェア環境
項目 情報 備考 PHPバージョン 7.4.3 Homwbrewを用いて導入 Laravelバージョン 8.1.0 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする 前提条件
- 前述した実施環境に準ずる環境が整っていること。
- Laravelアプリが作成され、アプリの起動、ブラウザからの確認ができる状態になっていること。
- Authを用いたユーザ認証機能がすでに付与されていること。
前提情報
- 筆者は新規でLaravel8のアプリを作成して本検証を実施する。
読後感
- LaravelのAuth認証にメールアドレスの確認処理を付与することができる。
- ユーザ認証情報入力→メール受信→メール内のURLから初回ログインをしてもらう。
概要
- .envの修正
- モデルファイルの修正
- ルーティング情報の修正
- コントローラファイルの修正
- 確認
詳細
.envの修正
アプリ名ディレクトリで下記コマンドを実行して.envファイルを開く。
$ vi .envMAIL_MAILERの設定を下記の様に修正する。メール送信は行われず、情報がLaravelのログに出力される。
アプリ名ディレクトリ/.envMAIL_MAILER=log下記に修正後の.envファイルの全体の内容を記載する。
アプリ名ディレクトリ/.envAPP_NAME=Laravel APP_ENV=local APP_KEY=アプリキーの記載は各個人で異なります。 APP_DEBUG=true APP_URL=http://localhost LOG_CHANNEL=stack DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=DB名 DB_USERNAME=root DB_PASSWORD=皆さんの環境のMySQLのrootユーザのパスワード BROADCAST_DRIVER=log CACHE_DRIVER=file QUEUE_CONNECTION=sync SESSION_DRIVER=file SESSION_LIFETIME=120 REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 MAIL_MAILER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"モデルファイルの修正
アプリ名ディレクトリで下記コマンドを実行してusersテーブルにリンクするモデルファイルを開く。
$ vi app/Models/User.php下記の様に修正する。
アプリ名ディレクトリ/app/Models/User.php<?php namespace App; // 下記を修正する //use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Contracts\Auth\MustVerifyEmail as MustVerifyEmailContract; use Illuminate\Auth\MustVerifyEmail; // 上記までを修正する use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; // 下記を修正する //class User extends Authenticatable class User extends Authenticatable implements MustVerifyEmailContract { // 下記を修正する //use Notifiable; use MustVerifyEmail, Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'email_verified_at' => 'datetime', ]; }ルーティング情報の修正
アプリ名ディレクトリで下記コマンドを実行してルーティングファイルを開く。
$ vi routes/web.php下記の様に修正を行う。
アプリ名ディレクトリ/routes/web.php<?php use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); // 下記を修正する //Auth::routes(); Auth::routes(['verify' => true]);コントローラファイルの修正
アプリ名ディレクトリで下記コマンドを実行してコントローラファイルを開く。
$ vi app/Http/Controllers/HomeController.php下記の様に修正を行う。
アプリ名ディレクトリ/app/Http/Controllers/HomeController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class HomeController extends Controller { /** * Create a new controller instance. * * @return void */ public function __construct() { // 下記を修正する //$this->middleware('auth'); $this->middleware('verified'); } /** * Show the application dashboard. * * @return \Illuminate\Contracts\Support\Renderable */ public function index() { return view('home'); } }確認
アプリ名ディレクトリで下記コマンドを実行してローカルサーバを起動する。
$ php artisan serve下記リンクにアクセスする。
下記のページの右上の「REGISTER」をクリックする。
任意の情報を入力し「Register」をクリックする。
下記画面が表示されることを確認する。
アプリ名ディレクトリで下記コマンドを実行してLaravelのログファイルを開く。
$ vi storage/logs/laravel.log開いたログファイルの最終行付近に下記の様な記載があることを確認する。
アプリ名ディレクトリ/storage/logs/laravel.log[Laravel](http://localhost) # Hello! Please click the button below to verify your email address. Verify Email Address: http://127.0.0.1:8000/email/verify/6/1107c7572edff3d7050bd4c68404ff6d0919d508?expires=1598068938&signature=a52ea1e97b368ac4b68dd2183cd3ff84d121b608556673e01c6a1aa2a5715161 If you did not create an account, no further action is required. Regards, Laravel If you’re having trouble clicking the "Verify Email Address" button, copy and paste the URL below into your web browser: [http://127.0.0.1:8000/email/verify/6/1107c7572edff3d7050bd4c68404ff6d0919d508?expires=1598068938&signature=a52ea1e97b368ac4b68dd2183cd3ff84d121b608556673e01c6a1aa2a5715161](http://127.0.0.1:8000/email/verify/6/1107c7572edff3d7050bd4c68404ff6d0919d508?expires=1598068938&signature=a52ea1e97b368ac4b68dd2183cd3ff84d121b608556673e01c6a1aa2a5715161) © 2020 Laravel. All rights reserved.みなさんのlogファイルの「Verify Email Address」の後に書かれたURLにアクセスする。(本記事のリンクは筆者の環境の物なのでアクセスしても正常な処理にならない。)
下記の様なページが表示されたらメールアドレスの確認が完了し初回ログインも完了である。
参考文献
- 投稿日:2020-09-16T00:49:39+09:00
Gate権限制御 @canでクエリ発行数が増えちゃった話+対処法
なんだかものすごい権限参照のクエリ重複してる...
①対処
blade内の
@can @endcanの記述を
@if @endifへと変更し、判定に用いる権限の値をviewへと渡す。
ループ処理内でcanが使われていた場合、これが原因の可能性が高いですが以下に続きを...
②原因
@canによる判定をおこなう時にFacadeクラスのインスタンス化、権限参照のDBアクセスが発生します。
今回の自分の場合レコード情報と合わせてアイコン表示をする必要がありました。
この時、ループ処理内で表示/非表示の制御をしていたことからレコード数分のDBアクセスが起こっていました。③対処例
自分の作業でおこなった対処の一例です。
1:①の通りbladeの記述を@can→@ifへ変更
2:controller側でユーザーインスタンスから権限、またはそれに相当する値を取得し、compactもしくはwithでviewへ渡す
3:bladeで受け取り判定に使用大したことはしていませんが、手間をかけずにできたのでこの方法で解決できるパターンもあると思います。
参考記事
@Yorinton様
https://qiita.com/Yorinton/items/604c5b1f3445cb23d565公式
https://readouble.com/laravel/5.7/ja/authorization.html
以上となります。
仕様や現状の改善点など調べておりますので、備忘録ではありますが随時修正していきます。
- 投稿日:2020-09-16T00:49:39+09:00
【備忘録】Gate権限制御 @canでクエリ発行数が増えちゃった話+対処法
なんだかものすごい権限参照のクエリ重複してる...
①対処
blade内の
@can @endcanの記述を
@if @endifへと変更し、判定に用いる権限の値をviewへと渡す。
ループ処理内でcanが使われていた場合、これが原因の可能性が高いですが以下に続きを...
②原因
@canによる判定をおこなう時にFacadeクラスのインスタンス化、権限参照のDBアクセスが発生します。
今回の自分の場合レコード情報と合わせてアイコン表示をする必要がありました。
この時、ループ処理内で表示/非表示の制御をしていたことからレコード数分のDBアクセスが起こっていました。③対処例
自分の作業でおこなった対処の一例です。
1:①の通りbladeの記述を@can→@ifへ変更
2:controller側でユーザーインスタンスから権限、またはそれに相当する値を取得し、compactもしくはwithでviewへ渡す
3:bladeで受け取り判定に使用大したことはしていませんが、手間をかけずにできたのでこの方法で解決できるパターンもあると思います。
参考記事
@Yorinton様
https://qiita.com/Yorinton/items/604c5b1f3445cb23d565公式
https://readouble.com/laravel/5.7/ja/authorization.html
以上となります。
仕様や現状の改善点など調べておりますので、備忘録ではありますが随時修正していきます。
- 投稿日:2020-09-16T00:25:42+09:00
【備忘録】Laravelのエラーメッセージを日本語にしたい場合
エラーメッセージ定義があるのはよいけど日本語にならないものか...
1つずつ設定することも可能ですが、公式ドキュメントや偉大なる先人が日本語化したものがあるので簡単に変更できます。
手順
1:resources/lang 配下に日本語メッセージ用ファイルを作成
@ama_keshi様の記事を参考に作成していただくとスムーズです。お世話になりました。
https://qiita.com/ama_keshi/items/27292949d41fdd8bd930Laravel5.7の時点では、以下のコマンド実行で日本語化用ファイルがまとめて生成できます。
https://readouble.com/laravel/5.7/ja/validation-php.htmlこのページの内容を含め、auth.php、passwords.php、pagination.php,validation.phpの4日本語ファイルをまとめて生成するには、以下のコマンドをプロジェクトのルートディレクトリで実行してください。(Windows環境ではまだ試していません。)
php -r "copy('https://readouble.com/laravel/5.7/ja/install-ja-lang-files.php', 'install-ja-lang.php');" php -f install-ja-lang.php php -r "unlink('install-ja-lang.php');"2:メッセージ編集
好きなメッセージへと個別に変更することも可能ですが、日本語にしたいだけであれば一括で書き換えましょう。公式ドキュメント
https://readouble.com/laravel/5.7/ja/validation-php.html
git minoryorg様
https://github.com/minoryorg/laravel-resources-lang-ja
- 投稿日:2020-09-16T00:01:00+09:00
PHP コードレビュー嵐に苦しむ社内 SE が,最近感じること。ちょっとしたお作法で,コードをグーンと読みやすくするエチケット
はじめに
社内 SE という職業柄,社外へシステムを発注することは多く,ビジネスパートナーのエンジニアが書いたコードをレビューする機会が日常的にあります。
その中で,「こう書いてくれた方が,もっと,楽に読めるんだけどなぁ」と感じる,口惜しいポイントがいくつかあることを日頃から感じており,記事としてアイデアをシェアしてみようと思いました。
コードレビューでは NG 判定にするまでもない小さなことですが,ちょっとしたお作法で,読み手の負担を下げられる事例の紹介になると思います。
余分な変数は切らない
コードをどれだけ速く読めるかは,エンジニアの器量にも大きく依存するところですが,コードの質,とりわけ,変数の個数は,思考作業の大きな負荷となります。
なぜなら,変数が一度見つかると,スコープが終わるまでは,その挙動を頭の片隅に置く必要があるため,考えることが増えてしまうからです。
出番が一回しかないような変数は作らないというルールを守るだけでも,コードの印象が,ものすごくスッキリしたものになります。
【追記】読者の皆さんのアドバイスを盛り込んで,改善案を追加しました(ありがとうございました)
// 2行に分けて書く方法(出番が一回しかない変数 $emp_id がある) $emp_id = $this->input->post('emp_id'); $emp_name = $this->getEmpName($emp_id); // 1行でまとめて書く方法(一行に収まるが,横太になって,ちょっと読みづらくなる) $emp_name = $this->getEmpName($this->input->post('emp_id')); // 1行でまとめて書く方法(改善版) $emp_name = $this->getEmpName( $this->input->post('emp_id') );無意味な初期化はしない
変数を初期化するコードをよく見かけますが,意味のないケースが散見されます。
数値型だったらゼロ,文字列型だったら null で初期化しておけば,なんとなく安心できる心理効果はあるものの,消しても挙動に影響を与えない初期化は,使わないようにします。
// 意味のない初期化 $age = 0; $age = $this->getAge(); // こうすればいいだけ $age = $this->getAge();連想配列は「母数」の分かる書き方を
連想配列って,多くの定義方法がありますね。
これも,書き方ひとつで,コードを読むときの思考負荷が大きく変わります。
読み手が分かりやすいよう,「母数はこれだけ」というメッセージを込めた定義の仕方が,スッキリします。
// 思考負荷が高まってしまう書き方(要素がまだまだ増えるかもしれないと,不安になる) $employee['name'] = 'Yamada'; $employee['age'] = 24; $employee['address'] = 'Tokyo'; // 思考負荷を低減できる書き方(要素が3つだというメッセージが込められている) $employee = [ 'name' => 'Yamada', 'age' => 24, 'address' => 'Tokyo', ];まとめ
本記事でご紹介した例は,ちょっとした工夫ですが,積み重なると,コードの読みやすさを大きく改善できる好例だと思いますが,いかがでしたでしょうか?
コードの読み手に与える負荷を少しでも低減するという配慮は,将来の自分自身のためにもなります(一か月後の自分は,他人のようなものですから)。
日々の小さな積み重ねで,幸せなコーディング体験が得られるようにしたいですね。