- 投稿日:2020-06-29T22:55:01+09:00
How to adopt Sentry to Laravel 6 with user context
Check your Laravel version before get started.
$ php artisan --versionInstallation Sentry
$ composer require sentry/sentry-laravel:1.8.0Adding sentry DSN on your env
.envSENTRY_LARAVEL_DSN=https://[YOUR_URL]Make config file for Sentry
$ touch config/sentry.phpconfig/sentry.php<?php return array( 'dsn' => env('SENTRY_LARAVEL_DSN'), 'breadcrumbs.sql_bindings' => true, 'send_default_pii' => true );Modify your Hander file
app/Exceptions/Handler.phppublic function report(Exception $exception) { + if (app()->bound('sentry') && $this->shouldReport($exception)) { + app('sentry')->captureException($exception); + } + parent::report($exception); }Adding user context
$ php artisan make:middleware SentryContextapp/Http/Middleware/SentryContext.php<?php namespace App\Http\Middleware; use Closure; use Sentry\State\Scope; class SentryContext { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * * @return mixed */ public function handle($request, Closure $next) { if (auth()->check() && app()->bound('sentry')) { \Sentry\configureScope(function (Scope $scope): void { $scope->setUser([ 'id' => auth()->user()->id, // since email is sensitive information, be careful to handle it. //'email' => auth()->user()->email, ]); }); } return $next($request); } }app/Http/Kernel.phpprotected $routeMiddleware = [ ... + 'sentry.context' => \App\Http\Middleware\SentryContext::class ];Adding group on route
route/web.phpRoute::middleware(['sentry.context'])->group(function () { Route::get('/', 'ToppageController@index')->name('home'); // or page as you like });
- 投稿日:2020-06-29T20:34:26+09:00
Laradock + phpMyAdmin + Laravelの環境構築
Docker環境のMySQLは、コマンドで確認しないといけないため、初学者にとってとても大変。。
phpMyAdmin
を使用するとデータベース内がブラウザで確認、変更、削除ができるため重宝します。
phpMyAdmin
は、MAMP
やXAMPP
だけしか利用できないかと思っていましたがDocker環境でも利用できるようです。
laradock
を導入した基本フォルダ構成は、以下のようになります。laravel_projects #laradockとlaravelプロジェクトをまとめるフォルダ ├── laradock # laradockをインストール └── project # laravelをインストール
早速、環境構築していきましょう!
今回は、以下のディレクトリで作成します。
laravel-docker ├── laradock └── laravelDev
ワークディレクトリを作成します。#まとめるフォルダを作成 $ mkdir laravel-docker #作成したフォルダに移動 $ cd laravel-docker #laradockをインストール $ git clone https://github.com/LaraDock/laradock.git
laradockの設定に関わるファイルのひとつである、.env
ファイルを作成します。
laradockのenv-example
ファイルを.env
に変更して作成します。#laradockフォルダに移動 $ cd laradock #env-exampleをコピーして 名前を.envへ変更 $ cp env-example .env
laradockフォルダをエディタで開いて.env
ファイルを編集します。変更前.APP_CODE_PATH_HOST=../ DATA_PATH_HOST=~/.laradock/data COMPOSE_PROJECT_NAME=laradock変更後.APP_CODE_PATH_HOST=../laravelDev DATA_PATH_HOST=./laradock/data COMPOSE_PROJECT_NAME=laravel-docker
APP_CODE_PATH_HOSTの変更
アプリケーションのディレクトリを指定します。 laravelをインストールするディレクトリです。
DATA_PATH_HOSTの変更
デフォルトではデータベースなどがホームディレクトリへ保存されます。
それをプロジェクトディレクトリ下へ変更します。
COMPOSE_PROJECT_NAMEの変更
デフォルトで$ docker-compose upすると、laradock_***という名称のコンテナが起動します。
上記工程で、プロジェクトのディレクトリは分けていますが、そのまま$ docker-compose upすると、
既存プロジェクトのコンテナが(同じ名称のコンテナが)立ち上がってしまうことを避けるために
COMPOSE_PROJECT_NAME をlaradockからlaravel-dockerへ変更します。
phpMyAdmin
を使うために続けてlaradock
の.env
を編集します。変更前.### MYSQL ################################################# MYSQL_VERSION=latest MYSQL_DATABASE=default MYSQL_USER=default MYSQL_PASSWORD=secret MYSQL_PORT=3306 MYSQL_ROOT_PASSWORD=root MYSQL_ENTRYPOINT_INITDB=./mysql/docker-entrypoint-initdb.d ### REDIS #################################################変更後.### MYSQL ################################################# MYSQL_VERSION= 5.7 #編集 MYSQL_DATABASE=laravelDev #編集 MYSQL_USER=root #編集 MYSQL_PASSWORD=root #編集 MYSQL_PORT=3306 MYSQL_ROOT_PASSWORD=root #編集 MYSQL_ENTRYPOINT_INITDB=./mysql/docker-entrypoint-initdb.d ### REDIS #################################################
変更前.### PHP MY ADMIN ########################################## # Accepted values: mariadb - mysql PMA_DB_ENGINE=mysql # Credentials/Port: PMA_USER=default PMA_PASSWORD=secret PMA_ROOT_PASSWORD=secret PMA_PORT=8080 ### MAILDEV ###############################################変更後.### PHP MY ADMIN ########################################## # Accepted values: mariadb - mysql PMA_DB_ENGINE=mysql # Credentials/Port: PMA_USER=root #編集 PMA_PASSWORD=root #編集 PMA_ROOT_PASSWORD=root #編集 PMA_PORT=8080 #8080を使用していたら8081などに編集 ### MAILDEV ###############################################コンテナ作成、起動
docker-composeコマンドでnginx、mysql、workspace、phpmyadminの作成と起動をします。
$ docker-compose up -d nginx mysql workspace phpmyadmin※エラーが出てもコマンドを2、3回繰り返すと解決することがあります。
起動しているか確認します。#Stateが全部Upになっているか確認 $ docker-compose ps
Laravel プロジェクトの作成
作業は、
workspace
コンテナの中に入って行います。
デフォルトではコンテナの中に入るとroot
ユーザーになるが、composer をroot
ユーザーで実行しようとすると警告が表示されてしまうため、--user
オプションをlaradock
で指定してログインする。#workspace コンテナの中に入ります。 $ docker-compose exec --user=laradock workspace bash #Laravel プロジェクトを作成 # ※APP_CODE_PATH_HOST=../laravelDevをしているためアプリ名は,ドット . です /var/www$ composer create-project --prefer-dist laravel/laravel . "6.0"ブラウザで
http://localhost
にアクセスしてLaravelのトップ画面が表示されれば成功です!
http://localhost:8080
のphpMyAdmin
にアクセスできることを確認します。
※PMA_PORTを8081
に変更していればhttp://localhost:8081
になりますのでご注意ください。ログインは、
.env
で設定したユーザ名, パスワードを使います。
サーバ:mysql
ユーザ名:root
パスワード:root
ログインできれば成功です!Laravel MySQL接続設定
laravelDev
をエディタで開き.env
を編集します。変更前.DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD=変更後.DB_CONNECTION=mysql DB_HOST=mysql #編集 DB_PORT=3306 DB_DATABASE=laravelDev #編集 DB_USERNAME=root DB_PASSWORD=root #編集/var/www$ php artisan migrate Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table
phpMyAdmin
にアクセスして確認してみましょう。
ログイン、登録機能のUI追加
/var/www$ composer require laravel/ui Your requirements could not be resolved to an installable set of packages. ・ ・ ・ Installation failed, reverting ./composer.json to its original ,content.エラーが発生しましたので、バージョンをつけてみましょう。
/var/www$ composer require laravel/ui:2.* Your requirements could not be resolved to an installable set of packages. ・ ・ ・ Installation failed, reverting ./composer.json to its original ,content.バージョンを下げてみましょう。
/var/www$ composer require laravel/ui:1.* ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 1 install, 0 updates, 0 removals - Installing laravel/ui (v1.2.0): Downloading (100%) ・ ・ ・v1.2.0でインストールできました!
/var/www$ php artisan ui vue --auth /var/www$ npm install && npm run dev ERROR in ./resources/sass/app.scsssassのコンパイルがうまくいかないようです。
npmで管理しているsass-loader
のパッケージのバージョン表示してみましょう。/var/www$ npm info sass-loader versionsバージョンを下げてみましょう。
#先にアンインストールをします。 /var/www$ npm uninstall --save-dev sass-loader #先ほど確認したバージョンの中から前よりバージョンが低いsass-loaderをインストール /var/www$ npm install --save-dev sass-loader@7.1.0 #コンパイル /var/www$ npm run devうまくいきました!
http://localhost
にアクセスしてLaravelのトップ画面の右上にlogin
register
が表示されれば成功です!参考になったら幸いです。
参考にさせて頂いた記事
Laradockを使ってLaravelを動かすまで
https://qiita.com/ichimura/items/27d4a628ff22e2ba8b35
【初心者】Laradockで別のプロジェクト(コンテナ)を立ち上げる場合
https://qiita.com/tomokei5634/items/74c7734701fff8b99904
- 投稿日:2020-06-29T17:18:51+09:00
SQL Laravel 論理削除されたカラムを検索に含めない
# 目的
- Laravelにて論理削除するための設定を行ったDBのテーブルに対するSQLのselect文にてすでに論理削除されたレコードを出力しないwhere条件をまとめる
実施環境
- ハードウェア環境
項目 情報 OS macOS Catalina(10.15.5) ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports) プロセッサ 2 GHz クアッドコアIntel Core i5 メモリ 32 GB 3733 MHz LPDDR4 グラフィックス Intel Iris Plus Graphics 1536 MB
- ソフトウェア環境
項目 情報 備考 PHP バージョン 7.4.3 Homwbrewを用いて導入 Laravel バージョン 7.0.8 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする 前提条件
- 下記の作業を行い、現存するテーブルのレコード削除機能を物理削除から論理削除に変更する。
読後感
- SQL文でselect句を使用した際に論理削除されたレコードの情報を表示しないようにwhere句の条件が記載できる。
概要
- Laravel側からのデータ削除
- where句の条件の追加と実行
詳細
Laravel側からのデータ削除
アプリ名ディレクトリで下記コマンドを実行してtinkerを起動する。
$ php artisan tinker下記の公式ドキュメントに従い任意のレコードを論理削除する。
>>> 変数A = App\論理削除を行うレコードが存在するテーブルのデータ操作を行うモデル名::find(削除するレコードのid); >>> 変数A->delete();where句の条件の追加と実行
下記コマンドを実行してMySQLにターミナルからログインする。(MySQLのrootユーザのパスワードを忘れてしまった方はこちら→Mac ローカル環境の MySQL 8.0 のrootパスワードを忘れた時のリセット方法)
$ mysql -u root -p下記を実行してこれから実行するSQL文がどのテーブルに対するものなのかを設定する。
use Laravelアプリに紐づいたDB名;下記を一旦実行してselect句を使用してusersテーブルの内容を出力する。(論理削除のレコードも出力される。)
select * from users;下記を実行して論理削除のレコードを除いたusersテーブルの内容を出力する。(deleted_atカラムがnullとして条件付けしたのはdeleted_atは論理削除された日時を格納するカラムなのでnullなら論理削除していないことになるためである。)
select * from users where deleted_at is null;null判定の落とし穴
- nullは
=
を使って判定できない。
where deleted_at = null;
などの条件だとnullは判定することができない。間違えやすいので注意する。- nullの判定は
is null
もしくはis not null
を使用する。
=
での判定ができないためnullの判定に関しては条件の記載方法が変わることに注意する。
- 投稿日:2020-06-29T16:00:21+09:00
Laravel勉強 その6 リクエストのライフサイクル
前回記事
目的
- 勉強のため、Laravel 7.xの公式ドキュメントを読み解いていく。
- 今回は、公式ドキュメントの「リクエストのライフサイクル」の章を扱う。
参考
イントロダクション
このドキュメントの目的は、どのようにLaravelが動作しているのか、ハイレベルな概念を理解してもらうためです。
フレームワークの概念をより良く知ってもらうことで、すべてに対し「不可解さ」が減り、より自信を持ってアプリケーションを開発してもらえます。
これらの言葉を今すぐ理解できなくても、気落ちしないでください。ただ何が行われているかを理解するための基本だけを理解してください。
ドキュメントの他のセクションで明確にされる内容を理解すれば、知識は育っていくことでしょう。この辺は確かに気になっていたので楽しみ。
概論
始まり
アプリケーションに対するリクエストは、すべてpublic/index.phpファイルが入り口になります。
Webサーバ(Apache/Nginx)の設定により、すべてのリクエストをこのファイルへ渡しています。
index.phpファイルは、さほどコードを持っていません。フレームワークの残りをロードするための入り口にすぎません。index.phpファイルは、Composerが生成したオートローダーの定義をロードします。
それから、bootstrap/app.phpスクリプトから、Laravelアプリケーションのインスタンスを取得します。
Laravel自身の最初のアクションは、アプリケーション/サービスコンテナのインスタンスを生成することです。オートローダーとは、「ファイルを自動で読み込む仕組みのこと」らしい。
composerを利用してライブラリをインストールした際に作成される vendor/autoload.php がオートローダーの本体で、
このファイルだけを一度だけrequireしてあげれば、vendor以下のライブラリをすべてrequireしてくれるとのこと。
逆にこれがなければ、phpファイルの冒頭には大量のrequireが必要になってしまうと。HTTP/Consoleカーネル
次にアプリケーション起動のきっかけにより、送信されてきたリクエストをHTTPカーネルかコンソールカーネルのどちらかに送ります。
これらのカーネルは、全リクエストフローの中心に位置し動作します。
ここではapp/Http/Kernel.phpにあるHTTPカーネルへ焦点を合わせます。ちなみにコンソールカーネルは、Laravelで使用するコマンド(php artisan ***)のセットアップをしてくれるらしい。
HTTPカーネルはIlluminate\Foundation\Http\Kernelクラスを拡張しています。
このKernelクラスは、リクエストの実行前に処理されるbootstrappers(起動コード)の配列を定義してます。
これらの起動コードはエラー処理、ログ設定、アプリケーション動作環境の決定、そのほか実際にリクエストが処理される前に行う必要のあるタスクです。HTTPカーネルはさらに、アプリケーションによりリクエストが処理される前に通す必要のある、HTTPミドルウェアのリストも定義しています。
これらのミドルウェアはHTTPセッションの読み書き、アプリケーションがメンテナンスモードであるかの決定、CSRFトークンの確認などを行います。HTTPカーネルのhandleメソッドの使い方はとてもシンプルで、Requestを受け取り、Responseをリターンします。
カーネルをアプリケーション全体を表す大きなブラックボックスだと考えてください。HTTPリクエストを流し込み、HTTPレスポンスが返ってきます。要は、リクエストが処理される前に以下の処理を行うと。
- 起動コード(エラー処理、ログ設定、動作環境設定など)の定義
- ミドルウェアの定義(セッションの読み書き、メンテナンスモード、CSRFトークンの確認)の定義これらの前処理が終わったらリクエストを受け取り、色々な処理をしてレスポンスを返すと。
サービスプロバイダ
カーネル初期起動時の重要な処理の一つは、アプリケーションのサービスプロバイダをロードすることです。
アプリケーションの全サービスプロバイダは、config/app.phpファイルのproviders配列で設定されています。
最初に全プロバイダのregisterメソッドが呼び出され、その後に登録されている全プロバイダのbootメソッドが呼び出されます。サービスプロバイダーは、もの(オブジェクト)をサービスコンテナにバインド(配置)し、サービスコンテナからものを解決(取得)する役目で、
サービスコンテナは、アプリケーションのブートストラッププロセスで開始されたすべてのものが配置されるkey => valueの場所とのこと。サービスプロバイダはデータベース、キュー、バリデーション、ルーティングなど、フレームワークのさまざまなコンポーネントの初期起動処理に責任をもちます。
フレームワークにより提供されている全機能を初期化し、設定するものですから、サービスプロバイダはLaravelの初期処理の過程全体で一番重要な機能です。なるほど。
リクエストのディスパッチ
アプリケーションの初期処理が済み、全サービスプロバイダが登録されたら、ディスパッチ(実行制御の移行)するためにルーターへRequestが手渡されます。
ルーターはそのリクエストをルートかコントローラにディスパッチし、その時にルートへ指定されているミドルウェアも実行されます。ディスパッチが何なのかはあまりわからないが、とりあえず準備が終わったら実行に移る、ということだと思う。
調べてみたけれどもあまりわからず…サービスプロバイダの精査
サービスプロバイダはLaravelアプリケーションの初期起動段階における、まさに鍵となるものです。
アプリケーションインスタンスが作成され、サービスプロバイダが登録され、初期起動を終えたアプリケーションでリクエストが処理されます。とてもシンプルです!Laravelアプリケーションがどう構築されているか、そしてサービスプロバイダによる初期起動の仕組みをしっかりと把握することは、とても重要です。
皆さんのアプリケーションのためのデフォルトサービスプロバイダは、app/Providersディレクトリに設置されています。AppServiceProviderはデフォルトで完全に空っぽです。
このプロバイダは皆さんのアプリケーション自身の初期起動や、サービスコンテナの結合を追加するために用意されています。
大きなアプリケーションであれば、まとまった初期起動種別ごとに分け、好きなだけサービスプロバイダを作成できます。サービスプロバイダーの作成については、以下リンク先がわかりやすかった。
Laravelのサービスプロバイダーの仕組みやメリットとは
Laravel サービスプロバイダーついに理解
サービスプロバイダーについて
試しに作ってみようと思う。作成前のapp/Providersはこんな感じ。
# ll app/Providers/ 合計 20 -rw-rw-r-- 1 root root 578 5月 21 20:33 AuthServiceProvider.php -rw-rw-r-- 1 root root 403 5月 21 20:33 AppServiceProvider.php -rw-rw-r-- 1 root root 380 5月 21 20:33 BroadcastServiceProvider.php -rw-rw-r-- 1 root root 710 5月 21 20:33 EventServiceProvider.php -rw-rw-r-- 1 root root 1658 5月 21 20:33 RouteServiceProvider.phpリンク先の例に従って、サービスプロバイダーを作成する。
毎回、Dockerのコンテナ内に入って、というのを繰り返すのは面倒なので、コンテナを指定してコマンドを実行するようにする。# docker exec -it e1394b974a68 php artisan make:provider AnimalServiceProvider /コンテナIDを指定 Provider created successfully.成功したっぽい。
app/Providersを確認してみる。# ll app/Providers/ 合計 24 -rw-r--r-- 1 root root 374 6月 24 16:58 AnimalServiceProvider.php -rw-rw-r-- 1 root root 403 5月 21 20:33 AppServiceProvider.php -rw-rw-r-- 1 root root 578 5月 21 20:33 AuthServiceProvider.php -rw-rw-r-- 1 root root 380 5月 21 20:33 BroadcastServiceProvider.php -rw-rw-r-- 1 root root 710 5月 21 20:33 EventServiceProvider.php -rw-rw-r-- 1 root root 1658 5月 21 20:33 RouteServiceProvider.phpちゃんと作成されている。
中身はこんな感じ。app/Providers/AnimalServiceProvider.php<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class AnimalServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // } /** * Bootstrap services. * * @return void */ public function boot() { // } }このファイルを、以下の様に編集する。
app/Providers/AnimalServiceProvider.php<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Animals\Bird; use App\Animals\Food; class AnimalServiceProvider extends ServiceProvider { /** * Register the Bird class instance to the container. * Food class will be auto injected */ public function register() { $this->app->bind('bird', function($app){ return new Bird($app->make(Food::class)); }); } }ポイントとしては(たぶん)こんなところ?
- use
関連する(必要な)クラスを登録しておく。
この例のサービスプロバイダーは
- アプリケーションがBirdクラスを使用できる様に、サービスコンテナに登録する
- BirdクラスはFoodクラスを必要とするため、Birdクラス・Foodクラスを登録する。
register
bindメソッドを使用して、Birdクラスを'bird'というキーでサービスコンテナにバインドする。boot
デフォルトではbootメソッドがあるが、これはregisterメソッドによる初期処理が完了した後に実行されるもの。
使用しないため削除する。完成したサービスプロバイダーを、config/app.php に登録する。
config/app.php(略) /* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, App\Providers\AnimalServiceProvider::class, /ここを追加 (略)Birdクラス、Foodクラスを作成する。
# mkdir app/Animals # vi app/Animals/Bird.php # vi app/Animals/Food.php# cat app/Animals/Bird.php <?php namespace App\Animals; use App\Animals\Food; class Bird { private $food = null; public function __construct(Food $food){ $this->food = $food; } public function sound(){ return "tweet tweet"; } public function getFood(){ return $this->food; } } # cat app/Animals/Food.php <?php namespace App\Animals; class Food { public function foodType(){ return "warms"; } } ?>コントローラーを作成する。
# docker exec -it e1394b974a68 php artisan make:controller AnimalController Controller created successfully.成功したっぽい。
app/Providersを確認してみる。# ll app/Http/Controllers/ 合計 8 -rw-r--r-- 1 root root 123 6月 29 15:36 AnimalController.php -rw-rw-r-- 1 root root 361 5月 21 20:33 Controller.php作成されている。
中身はこんな感じ。app/Http/Controllers/AnimalController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class AnimalController extends Controller { // }このファイルを、以下の様に編集する。
app/Http/Controllers/AnimalController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class AnimalController extends Controller { public function index () { $bird = app()->make('bird'); $returnText = "Sound : ". $bird->sound(); $returnText .= "<br>Food : ". $bird->getFood()->foodType(); echo $returnText; } }routes/web.phpの末尾に以下を追加
./routes/web.phpRoute::get('/animal', 'AnimalController@index');これで http://<IP>/animal にリクエストすると、
Sound : tweet tweet
Food : warms
が表示された。わかったようなわからないような感じだが、とりあえずちゃんと動いたので今回はここまで。
- 投稿日:2020-06-29T14:44:26+09:00
laravel+Vueプロダクトのデプロイサーバセットアップ
書いてあること
EC2上にlaravel+vueで開発したプロダクトの実行環境を構築する方法。
OSはAmazonLinux2です。手順
1.EC2のセットアップ
初期ユーザやrootにパスワードかける。
「yum update -y」でパッケージアップデートする。
日本時間にして日本語に対応させる。$ timedatectl set-timezone Asia/Tokyo $ localectl set-locale LANG=ja_JP.UTF-8 $ localectl set-keymap jp106 $ date Wed Apr 22 13:53:00 JST 2020ホスト名設定
$ hostnamectl set-hostname host.example.com
ホスト名設定
$ vi /etc/sysconfig/network NETWORKING=yes NOZEROCONF=yes +HOSTNAME=host.example.com2.証明書取得
httpsで公開したいので。
rootユーザで$ wget https://dl.eff.org/certbot-auto $ chmod 700 certbot-auto EC2用に書き換え $ vi certbot-auto elif [ -f /etc/issue ] && grep -iq "Amazon Linux" /etc/issue ; then Bootstrap() { ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon } BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" ↓ elif grep -i "Amazon Linux" /etc/issue > /dev/null 2>&1 || \ grep 'cpe:.*:amazon_linux:2' /etc/os-release > /dev/null 2>&1; then Bootstrap() { ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon } BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION"コマンド移動
$ sudo mv ./certbot-auto /usr/local/bin証明書取得
$ certbot-auto certonly --standalone -d csfhost.example.com --debug /etc/letsencrypt/live/host.example.com/ 以下に証明書ができる。3.nginxのインストール
rootユーザで
$ amazon-linux-extras install nginx1.12 -ynginxの起動とインスタンス起動時自動起動の設定
$ sudo systemctl start nginx $ sudo systemctl enable nginx $ systemctl status nginx4.phpのインストール
rootユーザで
$ amazon-linux-extras info php7.4 $ sudo amazon-linux-extras install php7.4 -y $ php-fpm -v PHP 7.4.x (fpm-fcgi) (built: Aug 14 2018 16:48:43) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies $ yum install -y php-mbstring.x86_64 $ yum install -y php-xml.x86_64 $ yum install php-gd.x86_645.nginxとphp-fpmの連携設定
[/etc/nginx/nginx.conf]# For more information on configuration, see: # * Official English Documentation: http://nginx.org/en/docs/ # * Official Russian Documentation: http://nginx.org/ru/docs/ user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. #include /etc/nginx/conf.d/*.conf; index index.php index.html index.htm; server { listen 80 default_server; listen [::]:80 default_server; server_name localhost; return 301 https://$host$request_uri; } server { listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; server_name localhost; ssl_certificate "/etc/letsencrypt/live/host.example.com/fullchain.pem"; ssl_certificate_key "/etc/letsencrypt/live/host.example.com/privkey.pem"; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; root /usr/share/nginx/html/xxx/public; index index.php index.html index.htm; access_log /var/log/nginx/xxx-access.log main; error_log /var/log/nginx/xxx-error.log warn; location / { try_files $uri $uri/ /index.php?$query_string; } error_page 404 /404.html; location = /40x.html { root /usr/share/nginx/html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/run/php-fpm/xxx.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root/index.php; fastcgi_param HOSTNAME host.example.com; fastcgi_max_temp_file_size 0; fastcgi_buffer_size 4K; fastcgi_buffers 64 4k; include fastcgi_params; } } }/etc/php-fpm.d/www.conf
を
/etc/php-fpm.d/xxx.conf にリネームして以下のように編集[xxx.conf]-; Start a new pool named 'www'. +; Start a new pool named 'xxx'. ; the variable $pool can we used in any directive and will be replaced by the -; pool name ('www' here) -[www] +; pool name ('xxx' here) +[xxx] ; Per pool prefix (略) ; Unix user/group of processes ; Note: The user is mandatory. If the group is not set, the default user's group ; will be used. ; RPM: apache user chosen to provide access to the same directories as httpd -user = apache +user = nginx ; RPM: Keep a group allowed to write in log dir. -group = apache +group = nginx ; The address on which to accept FastCGI requests. ; Valid syntaxes are: ; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on ; a specific port; ; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on ; a specific port; ; 'port' - to listen on a TCP socket to all addresses ; (IPv6 and IPv4-mapped) on a specific port; ; '/path/to/unix/socket' - to listen on a unix socket. ; Note: This value is mandatory. -listen = /run/php-fpm/www.sock +listen = /run/php-fpm/xxx.sock ; Set listen(2) backlog. ; Default Value: 511 ;listen.backlog = 511 (略) ; these options, value is a comma separated list of user/group names. ; When set, listen.owner and listen.group are ignored -listen.acl_users = apache +listen.acl_users = apache,nginx ;listen.acl_groups = ; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect. (略) ; The log file for slow requests ; Default Value: not set ; Note: slowlog is mandatory if request_slowlog_timeout is set -slowlog = /var/log/php-fpm/www-slow.log +slowlog = /var/log/php-fpm/xxx-slow.log ; The timeout for serving a single request after which a PHP backtrace will be ; dumped to the 'slowlog' file. A value of '0s' means 'off'. (略) ; Default Value: nothing is defined by default except the values in php.ini and ; specified at startup with the -d argument ;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com ;php_flag[display_errors] = off -php_admin_value[error_log] = /var/log/php-fpm/www-error.log +php_admin_value[error_log] = /var/log/php-fpm/xxx-error.log php_admin_flag[log_errors] = on ;php_admin_value[memory_limit] = 128M (略)php-fpm再起動
$ sudo systemctl restart php-fpm.service nginx起動 $ sudo systemctl restart nginxプロジェクトのドキュメントルートにphpinfoのファイルを作成して表示できるか確認
$ echo '<?php phpinfo(); ?>' > /usr/share/nginx/html/xxx/public/phpinfo.phpブラウザでhttps://host.example.com/phpinfo.phpをたたいて確認
6.DBセットアップ
望むバージョンを入れるためリポジトリファイル新規作成
$ vi /etc/yum.repos.d/MariaDB.repo
以下のように定義
[mariadb] name = MariaDB baseurl = http://yum.mariadb.org/10.3/centos7-amd64 gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB gpgcheck=1インストール
$ yum install MariaDB-server MariaDB-client -y起動
$ systemctl start mariadb
バージョン確認
$ mysql -V mysql Ver 15.1 Distrib 10.3.22-MariaDB, for Linux (x86_64) using readline 5.1有効化
$ systemctl enable mariadb $ systemctl is-enabled mariadbセキュリティ設定 rootのパスワード等を設定します。
$ mysql_secure_installation Set root password? [Y/n] Y Remove anonymous users? [Y/n] Y Disallow root login remotely? [Y/n] n Remove test database and access to it? [Y/n] n Reload privilege tables now? [Y/n] n ... Thanks for using MariaDB!rootのパスワードを設定しておく。
リモート接続できるように。# mysql -u root -p Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 520 Server version: 10.1.31-MariaDB MariaDB Server Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> GRANT ALL PRIVILEGES ON *.* TO root@"%" IDENTIFIED BY '[リモート接続パスワード]' WITH GRANT OPTION; MariaDB [(none)]> FLUSH PRIVILEGES;セキュリティを高めるためにポートを変えます。一旦停止してから設定に追記します。
$ systemctl stop mariadb $ vi /etc/my.cnf.d/server.cnf/etc/my.cnf.d/server.cnfの末尾に追記
port=3406起動
$ systemctl start mariadb
7.DB構築実行
.envの接続設定のポート番号が合っているか確認する。
マイグレーション実行$ php artisan migrate
シーダー実行
$ php artisan db:seed
10.ファイルパーミッション変更
以下4つのフォルダのパーミッションを777にする。
/usr/share/nginx/html/csf/storage/logs /usr/share/nginx/html/csf/storage/framework/cache /usr/share/nginx/html/csf/storage/framework/sessions /usr/share/nginx/html/csf/storage/framework/views
- 投稿日:2020-06-29T14:07:58+09:00
【Laravel7でユーザー認証_9】マルチ認証機能を使って管理者を作成する
はじめに
Laravelのユーザー認証機能に、管理者権限を追加する(マルチ認証機能を使う)手順をまとめます。
環境
XAMPP環境でLaravelが使えるように設定してあります。
- Windows10 Pro 64bit
- PHP 7.3.18
- Laravel 7.12.0
- MariaDB 10.1.32
また、Laravelプロジェクトは以下の手順で作業を進めており、ユーザーは自身で情報を登録・変更・削除ができるようになっています。
- 【Laravel7でユーザー認証_1】基本のき
- 【Laravel7でユーザー認証_2】ユーザー認証を日本語化
- 【Laravel7でユーザー認証_3】ユーザー認証をメールアドレスからユーザー名に変更する
- 【Laravel7でユーザー認証_4】パスワード変更フォームを作成する
- 【Laravel7でユーザー認証_5】ユーザーを倫理削除(SoftDelete)する
- 【Laravel7でユーザー認証_6】ユーザーの情報を表示・変更する設定画面を作成する
- 【Laravel7でユーザー認証_7】会員登録時にメール認証を行う
- 【Laravel7でユーザー認証_8】メールアドレス変更時にメール認証を行う
実装手順
Adminモデルとadminsテーブルの作成
以下のコマンドで、Adminモデルとマイグレーションファイルを作成します。
php artisan make:model Admin -mAdminモデルは、Userモデルの内容をコピーして使います。
app/Admin.php<?php namespace App; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Admin extends Authenticatable implements MustVerifyEmail { use Notifiable; use SoftDeletes; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'username', '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', ]; }マイグレーションファイルも、現在のusersテーブルの仕様に合わせます。
database/migrations/日付_create_admins_table.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateAdminsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('admins', function (Blueprint $table) { $table->id(); $table->string('username', 32); $table->string('name'); $table->string('email'); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); $table->softDeletes(); $table->unique(['email', 'deleted_at'], 'admins_email_deleted_at_unique'); $table->unique(['username', 'deleted_at'], 'admins_username_deleted_at_unique'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('admins'); } }マイグレーションを実行すると、usersと同じ構造のadminsテーブルが出来上がります。
php artisan migrate管理者用のガードとプロバイダを追加
認証の設定ファイル
config/auth.php
に、adminが使うガードとプロバイダを設定します。config/auth.php'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], + + 'admin' => [ + 'driver' => 'session', + 'provider' => 'admins', + ], 'api' => [ 'driver' => 'token', 'provider' => 'users', 'hash' => false, ], ], /* |-------------------------------------------------------------------------- | User Providers |-------------------------------------------------------------------------- | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | If you have multiple user tables or models you may configure multiple | sources which represent each model / table. These sources may then | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" | */ 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, ], + + 'admins' => [ + 'driver' => 'eloquent', + 'model' => App\Admin::class, + ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ],configファイル変更後は、キャッシュをクリアしておきます。
php artisan config:cacheADMIN_HOME の定義
管理者用のhomeとして、ADMIN_HOMEを定義します。
app/Providers/RouteServiceProvider.php/** * The path to the "home" route for your application. * * @var string */ public const HOME = '/home'; + public const ADMIN_HOME = '/admin/home';
リダイレクト処理
未ログイン時のリダイレクト
管理者としてログインしていない閲覧者が管理者権限が必要なページにアクセスした場合、ログイン画面にリダイレクトさせる設定をします。
app/Http/Middleware/Authenticate.php+ use Illuminate\Support\Facades\Route; class Authenticate extends Middleware { /** * Get the path the user should be redirected to when they are not authenticated. * * @param \Illuminate\Http\Request $request * @return string|null */ protected function redirectTo($request) { if (! $request->expectsJson()) { - return route('login'); + if (Route::is('admin.*')) { + return route('admin.login'); + } else { + return route('login'); + } } }管理者ログイン直後のリダイレクト
管理者としてログインした際、ADMIN_HOMEにリダイレクトさせるよう設定します。
app/Http/Middleware/RedirectIfAuthenticated.php/** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check()) { - return redirect(RouteServiceProvider::HOME); + if ($guard === 'user') { + return redirect(RouteServiceProvider::HOME); + } elseif ( $guard === 'admin') { + return redirect(RouteServiceProvider::ADMIN_HOME); + } } return $next($request); }コントローラ作成
ユーザー用のコントローラをコピーして、管理者用の
HomeController
とLoginController
を作成します。コピー元:
app/Http/Controllers/HomeController.php
コピー先:app/Http/Controllers/Admin/HomeController.php
app/Http/Controllers/Admin/HomeController.php<?php - namespace App\Http\Controllers; + namespace App\Http\Controllers\Admin; use Illuminate\Http\Request; class HomeController.php extends Controller { /** * Create a new controller instance. * * @return void */ public function __construct() { - $this->middleware('auth'); + $this->middleware('auth:admin'); } /** * Show the application dashboard. * * @return \Illuminate\Contracts\Support\Renderable */ public function index() { - return view('home'); + return view('admin.home'); } }コピー元:
app/Http/Controllers/Auth/LoginController.php
コピー先:app/Http/Controllers/Admin/Auth/LoginController.php
app/Http/Controllers/Admin/Auth/LoginController.php<?php - namespace App\Http\Controllers\Auth; + namespace App\Http\Controllers\Admin\Auth; use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use Illuminate\Foundation\Auth\AuthenticatesUsers; class LoginController extends Controller { /* |-------------------------------------------------------------------------- | Login Controller |-------------------------------------------------------------------------- | | This controller handles authenticating users for the application and | redirecting them to your home screen. The controller uses a trait | to conveniently provide its functionality to your applications. | */ use AuthenticatesUsers; /** * Where to redirect users after login. * * @var string */ - protected $redirectTo = RouteServiceProvider::HOME; + protected $redirectTo = RouteServiceProvider::ADMIN_HOME; /** * Create a new controller instance. * * @return void */ public function __construct() { - $this->middleware('guest')->except('logout'); + $this->middleware('guest:admin')->except('logout'); } + + protected function guard() + { + return Auth::guard('admin'); + } + + public function showLoginForm() + { + return view('admin.auth.login'); + } public function username() { return 'username'; } }ルーティングの設定
App\Http\Controllers\Admin
名前空間下のコントローラを読み込ませたいので、namespace
メソッドでAdmin
を指定- 管理者用のURIは、
admin/
でまとめたいので、prefix
メソッドでadmin
を指定- ルート名もまとめてadmin.というプリフィックスをつけたいので、
name
メソッドでadmin.
を指定routes/web.php+ Route::namespace('Admin')->prefix('admin')->name('admin.')->group(function() { + Route::get('home', 'HomeController@index')->name('home'); + Route::get('login', 'Auth\LoginController@showLoginForm')->name('login'); + Route::post('login', 'Auth\LoginController@login'); + Route::post('logout', 'Auth\LoginController@logout')->name('logout'); + }
管理者用のviewを作成
管理者用とユーザー用の画面がわかりやすいように、管理者はダークモードにしたいと思います。
viewの構成は、以下のようにしました。
views/ ├ admin/ │ ├ auth/ │ │ └ login.blade.php #resources/views/auth/login.blade.phpをコピー │ └ home.blade.php #resources/views/home.blade.phpをコピー │ └ layouts/ └ admin/ └ app.blade.php #resources/views/layouts/app.blade.phpをコピー▼@guest で判定していた箇所は、 @if (Auth::guard('admin')->check()) で、管理者かどうかのチェックをするように変更しました。
resources/views/layouts/admin/app.blade.php<!doctype html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel') }}</title> <!-- Scripts --> <script src="{{ asset('js/app.js') }}" defer></script> <!-- Fonts --> <link rel="dns-prefetch" href="//fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet"> <!-- Styles --> <link href="{{ asset('css/app.css') }}" rel="stylesheet"> </head> <body> <div id="app"> <nav class="navbar navbar-expand-md navbar-dark bg-dark shadow-sm"> <div class="container"> <a class="navbar-brand" href="{{ url('/') }}"> {{ config('app.name', 'Laravel') }} </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <!-- Left Side Of Navbar --> <ul class="navbar-nav mr-auto"> </ul> <!-- Right Side Of Navbar --> <ul class="navbar-nav ml-auto"> <!-- Authentication Links --> @if (Auth::guard('admin')->check()) <li class="nav-item dropdown"> <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre> {{ Auth::user()->name }} <span class="caret"></span> </a> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> <a class="dropdown-item" href="{{ route('admin.logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> {{ __('Logout') }} </a> <form id="logout-form" action="{{ route('admin.logout') }}" method="POST" style="display: none;"> @csrf </form> </div> </li> @else <li class="nav-item"> <a class="nav-link" href="{{ route('admin.login') }}">{{ __('Login') }}</a> </li> @endif </ul> </div> </div> </nav> <main class="py-4"> @yield('content') </main> </div> </body> </html>▼ヘッダ部分について、作成した
layout/admin/app.blade.php
を読み込むようにします。resources/views/admin/home.blade.php@extends('layouts.admin.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">Dashboard</div> <div class="card-body"> @if (session('status')) <div class="alert alert-success" role="alert"> {{ session('status') }} </div> @endif You are logged in! </div> </div> </div> </div> </div> @endsection▼ヘッダ部分について、作成した
layout/admin/app.blade.php
を読み込むようにして、formのaction先もadminのloginとなるようにルートを指定します。resources/views/admin/login.blade.php@extends('layouts.admin.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Admin') }}{{ __('Login') }}</div> <div class="card-body"> <form method="POST" action="{{ route('admin.login') }}"> @csrf <div class="form-group row"> <label for="username" class="col-md-4 col-form-label text-md-right">{{ __('UserName') }}</label> <div class="col-md-6"> <input id="username" type="username" class="form-control @error('username') is-invalid @enderror" name="username" value="{{ old('username') }}" required autocomplete="username" autofocus> @error('username') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row"> <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label> <div class="col-md-6"> <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password"> @error('password') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row"> <div class="col-md-6 offset-md-4"> <div class="form-check"> <input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}> <label class="form-check-label" for="remember"> {{ __('Remember Me') }} </label> </div> </div> </div> <div class="form-group row mb-0"> <div class="col-md-8 offset-md-4"> <button type="submit" class="btn btn-primary"> {{ __('Admin') }}{{ __('Login') }} </button> </div> </div> </form> </div> </div> </div> </div> </div> @endsection翻訳ファイルに追記
view内で新たにAdminという翻訳文字列を使ったので、翻訳ファイルに追加します。
resources/lang/ja.json+ "Admin": "管理者" }
管理者情報の登録
http://127.0.0.1:8000/admin/login
にアクセスすると、管理者ログインフォームが表示されるまではできあがりました。
ですが、管理者の登録画面を作っていないので、このままだとログインも登録もできない状態です。
まずはログインができるかどうかを試したいので、データベースに管理者のデータを直接入れ込んでしまって確認します。SQLで直接データを入れてしまってもいいのですが、せっかくなのでLaravelのシーダー機能を使ってデータを入れてみます。
シーダーファイルの作成
AdminsTableSeeder
というシーダーファイルを作成します。php artisan make:seeder AdminsTableSeederadminsテーブルに入っていてほしいデータを指定します。
項目 カラム名 値 氏名 name 管理者 ユーザー名 username admin メールアドレス admin@example.com パスワード password secret database/seeds/AdminsTableSeeder.php<?php use Illuminate\Database\Seeder; class AdminsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('admins')->delete(); DB::table('admins')->insert([ [ 'name' => '管理者', 'username' => 'admin', 'email' => 'admin@example.com', 'email_verified_at' => date('Y-m-d H:i:s'), 'password' => Hash::make('secret'), 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ] ]); } }database/seeds/DatabaseSeeder.phppublic function run() { - // + $this->call(AdminsTableSeeder::class); }シーダーを実行して、データが入ったかどうか確認します。
php artisan db:seed動作確認
設定した情報でログインができるかどうか確認します。
▼私はこんなエラーと戦いました。
- フォーム表示→ Auth guard [admin] is not defined.
config/app.php
を変更した後、php artisan config:cache
をしていませんでした。
- ログイン→ Class 'App\Http\Controllers\Admin\Auth\Auth' not found
app/Http/Controllers/Admin/Auth/LoginController.php
に、use Illuminate\Support\Facades\Auth;
が抜けていました。
- ログイン→ Class 'App\Http\Controllers\Admin\Controller' not found
app/Http/Controllers/Admin/HomeController.php
に、use App\Http\Controllers\Controller;
が抜けていました。
- 未ログイン状態で /admin/home にアクセス→
Class 'App\Http\Middleware\Route' not found
app/Http/Controllers/Middleware/Authenticate.php
にuse Illuminate\Support\Facades\Route;
が抜けていました。
リダイレクト分岐判定のRouteの前にバックスラッシュを置いてグローバルクラスにすれば(if (\Route::is('admin.*')) {
)useを使わなくてもよいようです。
おわりに
管理者のログイン、ホームの表示ができるようになりました。
機能としてはまだ不十分なので、次回は、管理者のパスワード変更やリセットができるように修正したいと思います。参考サイト
- 投稿日:2020-06-29T13:39:26+09:00
Laravel,transaction
皆さん、こんにちは
今回はLaravelの勉強していた時に、transactionについてなにも分からないまま使っていたので、調べて見ました!開発環境
・Mac
・php 7.4.6
・Laravel 7.15.0
・MySQLtransaction
transactionは、複数の処理をまとめて実行したい時に使います。
掲示板アプリがあるとしましょう。掲示板にはユーザーが自由にコメントを書くことが出来ます。その上で、ある特定の投稿を削除するときのことを考えます。この時、投稿自体は削除出来たけど、コメントを削除する段階でエラーが発生したらどうなるでしょうか。もちろん、コメントテーブルと投稿のテーブルはリレーションを張っているわけなので、この場合投稿だけが残りコメントだけ残ってしまいます。このようなことを防ぐためにtransactionを使います。DB::transaction(function() { //処理を記述 });クロージャーの中で例外が発生した場合は、ロールバックされます。またクロージャー内で返した値が戻り値となります。
transactionの例
QuestionController.php$question = Question::findOrFail($question_id) \DB::transaction(function() use ($question){ $question->answers()->delete(); $question->delete(); });Q&Aがあり、questionsテーブルとanswersテーブルにリレーションがはられています。
transactionによって必ずまとめて削除されるか、エラーが出ても片方だけが実行されるということはありません。
指摘がありましたらコメントお願いします参考にした記事、サイト
Laravel Tips ? transactionメソッドは値を返す
Laravelでトランザクション処理の方法をサンプル付きで解説
- 投稿日:2020-06-29T13:33:44+09:00
laravel+vueプロダクトのローカル開発環境
書いてあること
Laravelでバックエンド、Vueでフロントエンドの構成のプロダクトをWindowsクライアントで開発する際のクライアントのローカル環境の構築方法。
手順
1.「Visual Studio Code(VS Code)」のインストール
公式サイトからダウンロードしてインストール
2.「VS Code」拡張機能のインストール
「VS Code」を起動する。
アクティビティバーの「Extensions(拡張)」アイコンをクリック。
以下の拡張機能名を入力し、拡張機能をインストールしていく。
拡張機能名 説明 .ejs EJSファイル(HTMLのテンプレートファイル)に対して構文をハイライトしてくれる拡張機能。 Bracket Pair Colorizer 対となるカッコ色付けして見やすくしてくれる拡張機能。 ESLint 構文チェックするために必要な拡張機能。 Japanese Language Pack for Visual Studio Code 日本語化するために必要な拡張機能。 php cs fixer PHPのコード整形するために必要な拡張機能。「PHP CS Fixer」や「PHP-CS-Fixer」など、似たような名前の拡張機能が存在するため、間違えないようにインストールしてください。 PHP Intelephense PHPのコード補完をしてくれる拡張機能。 Vetur 「.Vue」ファイルを扱うために必要な拡張機能。 GitLens Gitで管理しているファイルの変更点をショートカットキー等で開ける拡張機能。 Debugger for Chrome VSCodeでフロントサイドのデバッグするための拡張機能。 インストールを実行するには以下の「Install」ボタンを実行する。
3.「Node.js」のインストール
公式サイトからダウンロードしてインストール。
4.phpインストール
公式サイトから「php-7.3.17-Win32-VC15-x64.zip」をダウンロードし解凍して任意の場所に配置。
配置したフォルダを環境変数PATHに追加
配置したフォルダに入り”php.ini-development”を”php.ini"でコピー。
php.iniを開き以下編集[php.ini]-;extension_dir = "ext" -;extension=fileinfo -;extension=mbstring -;extension=gd2 +extension_dir = "ext" +extension=fileinfo +extension=mbstring +extension=gd2 ・・・ +extension=php_openssl +extension=php_pdo_mysql5.「Composer(コンポーザー)」のインストール
公式サイトからダウンロードしてインストール。
Developer modeはチェック入れる
phpの場所は手順4の配置フォルダ中のphp.exeを指定
update this php.iniはチェック外してスキップ
proxyは未設定のままスキップ6.「SourceTree」のインストール
公式サイトから「SourceTreeSetup-2.6.10.exe」をダウンロードしてインストール。
求められるアカウント認証は作るかグーグルアカウントがあればそれでできます。7.「SourceTree」を使ってソースの取得
以下のとおり設定し、「クローン」ボタンを実行する。
《設定内容》 《設定値》 元のパス/URL https://develop.fan-technology.com/gitlab/InHouseDev/xxx.git 保存先のパス {SourceTreeのワークパス}{リポジトリ名} 名前 リポジトリ名 Local Folder [ルート] 詳細オプション ※デフォルトのまま 8.ライブラリで利用するモジュールをインストール
「Node.js」で使用している拡張機能の物理ファイル群(「node_modules」の中身)をインストールする作業です。
「laravel」で使用している拡張機能の物理ファイル群(「vendor」の中身)をインストールする作業です。「コマンドプロンプト」を起動します。
以下のコマンドを入力して実行(Enterキー押下)し、「xxx」のソースがあるフォルダにカレントフォルダを設定します。cd C:\Work\SourceTree\xxx以下のコマンドを入力して実行(Enterキー押下)します。
npm install
初回はネットから落としてくるファイルが多いので10分ほどかかる場合があります。
つづいて以下のコマンドを入力して実行(Enterキー押下)します。composer install
初回はネットから落としてくるファイルが多いので10分ほどかかる場合があります。
9.DBセットアップ
resourceフォルダのmariadb-10.4.10-winx64.msiでデフォルト設定にて。
rootユーザのパスワードは"root"で。
※「HeidiSQL」というソフトが入ってしまうようですが、使わないので無視してください。10.DBマイグレーション
リポジトリ名のフォルダにコマンドプロンプトで入り以下実行
php artisan migrate11.初期データ投入
php artisan db:seed※「class not found」などのエラーが出る場合は「composer dump-autoload」のコマンドを実行して再度試してください。
12.実行
コマンドプロンプトを立ち上げてxxxのフォルダに移動
php artsan serveコマンドプロンプトを立ち上げてxxxのフォルダに移動
npm run watch※ビルドが終わると自動でブラウザでサイトを開いてくれます。
http://localhost:3000/また、browserSyncというホットリロードの仕組みを取り入れていますので、ソース系のファイル変更を検知してリビルドしてブラウザリロードしてくれます。
もしリロードしてほしくない場合は以下のURLでアクセスしてください。
http://localhost:8000/13.「Postman」のインストール
「Postman」はAPIの動作確認を行う際に有用なツール。
GUIで動くcurl。
公式サイトからダウンロードしてインストール。
- 投稿日:2020-06-29T13:20:32+09:00
## Laravel S3で複数枚画像をアップロードする
LaravelでAWS S3へ画像をアップロードする
前提条件
AWS S3でユーザ登録とバケットの作成ができていること。
まだ作成してない方はこちらが参考になりますS3パッケージインストール
パッケージはflysystem-aws-s3-v3を使用します。
以下を実行してインストールしてください。composer require league/flysystem-aws-s3-v3ファイルシステム設定
/config/filesystems.phpを以下のように編集します。
'default' => env('FILESYSTEM_DRIVER', 'local'), // 追記 'cloud' => env('FILESYSTEM_CLOUD', 's3'), // 〜 略 〜 'disks' => [ 'local' => [ // 〜 略 〜 ], 'public' => [ // 〜 略 〜 ], // 以下を追記 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), ],環境変数設定
プロジェクト直下にある.envファイルを以下のように編集します。
AWS_ACCESS_KEY_ID='AWSで作成したAccess key ID' AWS_SECRET_ACCESS_KEY='AWSで作成したSecret access key' AWS_DEFAULT_REGION=ap-northeast-1 AWS_BUCKET='作成したバケット名'Laravelの実装
実際にS3に保存していきます。
最低限のコードは次節以降になります。viewの実装
今回は複数枚アップロードできるようにしていきます。
<div class="name-filed width"> <div class="first-name-box"> <div class="text-label"> <p class="name">希望画像<span class="red">✳︎必須(5枚まで可)</span></p> </div> <div> {!! Form::file('item_url[]', ['multiple' => 'multiple']) !!} </div> @if ($errors->has('item_url') || $errors->has('item_url.*') ) <div class="alert alert-danger">{{ $errors->first('item_url') . $errors->first('item_url.*') }}</div> @endif </div> </div>point 1
formに'multiple' => 'multiple'をつけることで複数選択が可能になる。(Ctrl キーなどを押しながら写真を選択する)
point 2
Form::file()メソッドを利用します。
今回は複数枚になるので第一引数は配列で取得できるようにします。'item_url[]'余談
配列にフォームリクエストでバリデーションする場合は下記のように、 .* というプレースホルダーが使用する。
item_url.*controllerの実装
formから送られてきた値をS3に保存していきます。
laravelには簡単にS3を扱えるようにするファイルストレージ機能があります。
簡単なのでぜひ目を通してくださいね!public function store(ItemRequest $request) { $disk = Storage::disk('s3'); $images = $request->file('item_url'); foreach ( $images as $image) { $path = $disk->putFile('itemImages', $image, 'public'); $url[] = $disk->url($path); } return view('item.top'); }流れの解説
$disk変数にlaravelが用意してくれている Storage::disk('s3');を代入。
↓
$imageにはformから送られてきた写真をfile()を使用して取得する。
※$imageにはURLではなく、UploadedFile クラスが入ってる。
↓
foreachを使用して画像一枚ずつS3に保存していく。
putfile()メソッドの第一引数はバケット内の保存するフォルダを指定している。第二引数は保存する値。第三引数でpublicにすることで
どのユーザーでもweb上で画像を見ることが可能になる。※指定しないとweb上に画像が表示されない。$pathの中身は下記のように"バケットの保存先のフォルダ/画像"のpathになっています。
"itemImages/kmWqHDsESJtX1iTrvDbRRSsbIJOlOABHAuKfFY7X.jpeg"
ここまででS3への保存は完了になります!
最後の$urlにはurl()を使用してS3からのURLを作成しています。
以上でS3への複数枚画像アップロードは完了になります!
- 投稿日:2020-06-29T13:20:32+09:00
Laravel S3で複数枚画像をアップロードする
LaravelでAWS S3へ画像をアップロードする
前提条件
AWS S3でユーザ登録とバケットの作成ができていること。
まだ作成してない方はこちらが参考になりますS3パッケージインストール
パッケージはflysystem-aws-s3-v3を使用します。
以下を実行してインストールしてください。composer require league/flysystem-aws-s3-v3ファイルシステム設定
/config/filesystems.phpを以下のように編集します。
'default' => env('FILESYSTEM_DRIVER', 'local'), // 追記 'cloud' => env('FILESYSTEM_CLOUD', 's3'), // 〜 略 〜 'disks' => [ 'local' => [ // 〜 略 〜 ], 'public' => [ // 〜 略 〜 ], // 以下を追記 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), ],環境変数設定
プロジェクト直下にある.envファイルを以下のように編集します。
AWS_ACCESS_KEY_ID='AWSで作成したAccess key ID' AWS_SECRET_ACCESS_KEY='AWSで作成したSecret access key' AWS_DEFAULT_REGION=ap-northeast-1 AWS_BUCKET='作成したバケット名'Laravelの実装
実際にS3に保存していきます。
最低限のコードは次節以降になります。viewの実装
今回は複数枚アップロードできるようにしていきます。
<div class="name-filed width"> <div class="first-name-box"> <div class="text-label"> <p class="name">希望画像<span class="red">✳︎必須(5枚まで可)</span></p> </div> <div> {!! Form::file('item_url[]', ['multiple' => 'multiple']) !!} </div> @if ($errors->has('item_url') || $errors->has('item_url.*') ) <div class="alert alert-danger">{{ $errors->first('item_url') . $errors->first('item_url.*') }}</div> @endif </div> </div>point 1
formに'multiple' => 'multiple'をつけることで複数選択が可能になる。(Ctrl キーなどを押しながら写真を選択する)
point 2
Form::file()メソッドを利用します。
今回は複数枚になるので第一引数は配列で取得できるようにします。'item_url[]'余談
配列にフォームリクエストでバリデーションする場合は下記のように、 .* というプレースホルダーが使用する。
item_url.*controllerの実装
formから送られてきた値をS3に保存していきます。
laravelには簡単にS3を扱えるようにするファイルストレージ機能があります。
簡単なのでぜひ目を通してくださいね!public function store(ItemRequest $request) { $disk = Storage::disk('s3'); $images = $request->file('item_url'); foreach ( $images as $image) { $path = $disk->putFile('itemImages', $image, 'public'); $url[] = $disk->url($path); } return view('item.top'); }流れの解説
$disk変数にlaravelが用意してくれている Storage::disk('s3');を代入。$imageにはformから送られてきた写真をfile()を使用して取得する。
※$imageにはURLではなく、UploadedFile クラスが入ってる。
↓
foreachを使用して画像一枚ずつS3に保存していく。
putfile()メソッドの第一引数はバケット内の保存するフォルダを指定している。第二引数は保存する値。第三引数でpublicにすることで
どのユーザーでもweb上で画像を見ることが可能になる。※指定しないとweb上に画像が表示されない。$pathの中身は"バケットの保存先のフォルダ/画像"のpathになっています。
ここまででS3への保存は完了になります!
最後の$urlにはurl()を使用してS3からのURLを作成しています。
以上でS3への複数枚画像アップロードは完了になります!
- 投稿日:2020-06-29T13:20:32+09:00
Laravel S3で複数の画像をアップロードする
LaravelでAWS S3へ画像をアップロードする
前提条件
AWS S3でユーザ登録とバケットの作成ができていること。
まだ作成してない方はこちらが参考になりますS3パッケージインストール
パッケージはflysystem-aws-s3-v3を使用します。
以下を実行してインストールしてください。composer require league/flysystem-aws-s3-v3ファイルシステム設定
/config/filesystems.phpを以下のように編集します。
'default' => env('FILESYSTEM_DRIVER', 'local'), // 追記 'cloud' => env('FILESYSTEM_CLOUD', 's3'), // 〜 略 〜 'disks' => [ 'local' => [ // 〜 略 〜 ], 'public' => [ // 〜 略 〜 ], // 以下を追記 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), ],環境変数設定
プロジェクト直下にある.envファイルを以下のように編集します。
AWS_ACCESS_KEY_ID='AWSで作成したAccess key ID' AWS_SECRET_ACCESS_KEY='AWSで作成したSecret access key' AWS_DEFAULT_REGION=ap-northeast-1 AWS_BUCKET='作成したバケット名'Laravelの実装
実際にS3に保存していきます。
最低限のコードは次節以降になります。viewの実装
今回は複数枚アップロードできるようにしていきます。
<div class="name-filed width"> <div class="first-name-box"> <div class="text-label"> <p class="name">希望画像<span class="red">✳︎必須(5枚まで可)</span></p> </div> <div> {!! Form::file('item_url[]', ['multiple' => 'multiple']) !!} </div> @if ($errors->has('item_url') || $errors->has('item_url.*') ) <div class="alert alert-danger">{{ $errors->first('item_url') . $errors->first('item_url.*') }}</div> @endif </div> </div>point 1
formに'multiple' => 'multiple'をつけることで複数選択が可能になる。(Ctrl キーなどを押しながら写真を選択する)
point 2
Form::file()メソッドを利用します。
今回は複数枚になるので第一引数は配列で取得できるようにします。'item_url[]'余談
配列にフォームリクエストでバリデーションする場合は下記のように、 .* というプレースホルダーが使用する。
item_url.*controllerの実装
formから送られてきた値をS3に保存していきます。
laravelには簡単にS3を扱えるようにするファイルストレージ機能があります。
簡単なのでぜひ目を通してくださいね!public function store(ItemRequest $request) { $disk = Storage::disk('s3'); $images = $request->file('item_url'); foreach ( $images as $image) { $path = $disk->putFile('itemImages', $image, 'public'); $url[] = $disk->url($path); } return view('item.top'); }流れの解説
$disk変数にlaravelが用意してくれている Storage::disk('s3');を代入。$imageにはformから送られてきた写真をfile()を使用して取得する。
※$imageにはURLではなく、UploadedFile クラスが入ってる。
↓
foreachを使用して画像一枚ずつS3に保存していく。
putfile()メソッドの第一引数はバケット内の保存するフォルダを指定している。第二引数は保存する値。第三引数でpublicにすることでどのユーザーでもweb上で画像を見ることが可能になる。※指定しないとweb上に画像が表示されない。
$pathの中身は"バケットの保存先のフォルダ/画像"のpathになっています。
ここまででS3への保存は完了になります!最後の$urlにはurl()を使用してS3からのURLを作成しています。
以上でS3への複数枚画像アップロードは完了になります!
- 投稿日:2020-06-29T12:57:49+09:00
scaffold laravel php エラーがでる件
composer require laralib/l5scaffold --devを行うと、could not findなどいろんなエラーがでてくる。さまざまな解決サイトがあり試したが、変わらず。
下記サイトで解決。
https://awesome-linus.com/2019/05/17/laravel-l5scaffold-install-how-to-use/もともとapp.phpでproviderに入れろと言われていたが、
// 開発環境でのみ必要なプロバイダー 'dev-providers' => [ Laralib\L5scaffold\GeneratorsServiceProvider::class, ],という形でいれる。理由は上記サイトを参照。
- 投稿日:2020-06-29T06:40:20+09:00
【Laravel】Eloquent の has() や whereHas() が遅い?なら速くしてやるぜ
はじめに
Laravel は「リレーション先が存在するか」という制約条件を
has()
whereHas()
メソッドで表現できます。取得結果が複数になる
HasMany
と,取得結果が単一になるHasOne
BelongsTo
の, 2 通りのパターンを考えましょう。例
以下のようなモデル定義があるとします。
class Post extends Model { use SoftDeletes; public function comments() { return $this->hasMany(Post::class); } }class Comment extends Model { use SoftDeletes; public function post() { return $this->belongsTo(Post::class); } }
HasMany
の場合$posts = Post::query() ->has('comments') ->limit(5) ->get();select * from `posts` where exists ( select * from `comments` where `posts`.`id` = `comments`.`post_id` and `comments`.`deleted_at` is null ) and `posts`.`deleted_at` is null limit 5
posts.id
がサブクエリの中で参照されていて,これは 1 行ごとに変化する値であるため, MySQL は 1 行ごとにサブクエリを実行 します。これは 相関サブクエリ と呼ばれ,場合によっては著しいパフォーマンスの低下を引き起こします。但し,相関サブクエリがいつでも悪者になるというわけではありません。
メインクエリのレコード数 ≪ サブクエリのレコード数
のときは,相関サブクエリを素直に使ってもいいメインクエリのレコード数 ≫ サブクエリのレコード数
のときは,相関サブクエリは避けたほうが無難サブクエリを繰り返し実行したとしても,メインクエリのみの条件で絞り込みが十分に行われていれば,サブクエリの実行回数が削減されるため,問題ないと言えるケースもあるでしょう。以降では,メインクエリのみの条件で十分に絞り込めないケースを見ていきます。
相関サブクエリを JOIN に変換する
まずは誰もが思いつきそうな
JOIN
を試してみましょう。$posts = Post::query() ->join('comments', function (JoinClause $join) { $join->on('posts.id', '=', 'comments.post_id'); $join->whereNull('comments.deleted_at'); }) ->limit(5) ->select('posts.*') // 不要なカラムが含まれないように posts.* のみに絞り込む ->get();select `posts`.* from `posts` inner join `comments` on `posts`.`id` = `comments`.`post_id` and `comments`.`deleted_at` is null where `posts`.`deleted_at` is null limit 5シンプルな
JOIN
クエリになりました!但し,これは期待したように動作しません。なぜならば,
HasMany
の相手を JOIN すると 自分自身のレコードも増えてしまう からです。期待する結果+--------+-------------+ | Post 1 | Comment 1-1 | | | Comment 1-2 | | | Comment 1-3 | | | Comment 1-4 | | | Comment 1-5 | +--------+-------------+ | Post 2 | Comment 2-1 | +--------+-------------+実際の結果+--------+-------------+ | Post 1 | Comment 1-1 | +--------+-------------+ | Post 1 | Comment 1-2 | +--------+-------------+ | Post 1 | Comment 1-3 | +--------+-------------+ | Post 1 | Comment 1-4 | +--------+-------------+ | Post 1 | Comment 1-5 | +--------+-------------+ | Post 2 | Comment 2-1 | +--------+-------------+対策としては
DISTINCT
が使えます。$posts = Post::query() ->join('comments', function (JoinClause $join) { $join->on('posts.id', '=', 'comments.post_id'); $join->whereNull('comments.deleted_at'); }) ->limit(5) ->select('posts.*') // 不要なカラムが含まれないように posts.* のみに絞り込む ->distinct() ->get();select distinct `posts`.* from `posts` inner join `comments` on `posts`.`id` = `comments`.`post_id` and `comments`.`deleted_at` is null where `posts`.`deleted_at` is null limit 5一応これで解決はできます。但し,
DISTINCT
+LIMIT
の組み合わせはテンポラリテーブルの生成などによってLIMIT
の性能を悪化させてしまう 場合があり,銀の弾丸とは言えません。論理削除など モデルのグローバルスコープの機能も使えない ので,自分でそれらの制約条件を付与する必要がある点も懸念点です。相関サブクエリを通常のサブクエリに変換する
以下のようにすると,相関サブクエリを通常のサブクエリに変換することができます。
whereIn()
は第 2 引数にサブクエリを取ることができます。$posts = Post::query() ->whereIn('posts.id', Comment::query()->select('comments.id')) ->limit(5) ->get();select * from `posts` where `posts`.`id` in ( select `comments`.`id` from `comments` where `comments`.`deleted_at` is null ) and `posts`.`deleted_at` is null limit 5
comments
テーブルに対するクエリからposts
のカラム参照が消えて,相関サブクエリが普通のサブクエリになりました!またこちらの方法ではDISTINCT
を使わずに済んでいます。グローバルスコープが自動で付与されている点もポイントが高いです。実際の実行計画としては
JOIN
とほぼ同等になる可能性が高いですが,DISTINCT
を使用していないためテンポラリテーブル問題は解決できるでしょう。JOIN
よりはこちらを優先して使っていきたいところです。
- MySQL 5.5 の場合はサブクエリの最適化が効かない可能性があるので,
JOIN
を使ったほうが無難でしょう。- MySQL 5.6~5.7 でも
UPDATE
DELETE
に対しては最適化が効かないので,その際もJOIN
を使いましょう。- MySQL 8.x の場合はサブクエリ統一で問題なさそうです。
HasOne
BelongsTo
の場合相関サブクエリを JOIN に変換する
HasMany
は芳しくない結果になってしまいましたが, 取得結果が単一になれば何の問題もありません。躊躇なく JOIN しちゃって OK です。$comments = Comment::query() ->join('posts', function (JoinClause $join) { $join->on('comments.post_id', '=', 'posts.id'); $join->whereNull('posts.deleted_at'); }) ->limit(5) ->select('comments.*') // 不要なカラムが含まれないように posts.* のみに絞り込む ->get();select `comments`.* from `comments` inner join `posts` on `comments`.`post_id` = `posts`.`id` and `posts`.`deleted_at` is null where `comments`.`deleted_at` is null limit 5相関サブクエリを通常のサブクエリに変換する
こちらの方法でもいけます!サブクエリは万能でいいなぁ〜
$posts = Post::query() ->whereIn('posts.id', Comment::query()->select('comments.id')) ->limit(5) ->get();select * from `comments` where `comments`.`post_id` in ( select `posts`.`id` from `posts` where `posts`.`deleted_at` is null ) and `comments`.`deleted_at` is null limit 5ライブラリを作りました
こんなもん手作業で書いてたら保守性が終わるので, Laravel 標準の
has()
whereHas()
に謙遜無い感じで使えるライブラリを 2 つ実装しました!
- mpyw/eloquent-has-by-non-dependent-subquery: Convert has() and whereHas() constraints to non-dependent subqueries.
- mpyw/eloquent-has-by-join: Convert has() and whereHas() constraints to join() ones for single-result relations.
できることに差はありますが,基本的にはサブクエリ版のほうが上位互換だと思ってください。どっちもインストールしておくのもありです。
$posts = Post::query() ->hasByNonDependentSubquery('comments') ->limit(5) ->get();$posts = Comment::query() ->hasByJoin('posts') ->limit(5) ->get();これで最高の Eloquent ライフを送りましょう!!!
- 投稿日:2020-06-29T06:40:20+09:00
【Laravel】 Eloquent の has() や whereHas() が遅い?なら速くしてやるぜ
はじめに
Laravel は「リレーション先が存在するか」という制約条件を
has()
whereHas()
メソッドで表現できます。取得結果が複数になる
HasMany
と,取得結果が単一になるHasOne
BelongsTo
の, 2 通りのパターンを考えましょう。例
以下のようなモデル定義があるとします。
class Post extends Model { use SoftDeletes; public function comments() { return $this->hasMany(Comment::class); } }class Comment extends Model { use SoftDeletes; public function post() { return $this->belongsTo(Post::class); } }
HasMany
の場合$posts = Post::query() ->has('comments') ->limit(5) ->get();select * from `posts` where exists ( select * from `comments` where `posts`.`id` = `comments`.`post_id` and `comments`.`deleted_at` is null ) and `posts`.`deleted_at` is null limit 5
posts.id
がサブクエリの中で参照されていて,これは 1 行ごとに変化する値であるため, MySQL は 1 行ごとにサブクエリを実行 します。これは 相関サブクエリ と呼ばれ,場合によっては著しいパフォーマンスの低下を引き起こします。但し,相関サブクエリがいつでも悪者になるというわけではありません。
- メインクエリのみで十分に絞り込めている場合,相関サブクエリを素直に使ってもいい
- メインクエリのみで絞り込みが不足している場合,相関サブクエリは避けたほうが無難
サブクエリを繰り返し実行したとしても,メインクエリのみの条件で絞り込みが十分に行われていれば,サブクエリの実行回数が削減されるため,問題ないと言えるケースもあるでしょう。以降では,メインクエリのみの条件で十分に絞り込めないケースを見ていきます。
相関サブクエリを JOIN に変換する
まずは誰もが思いつきそうな
JOIN
を試してみましょう。$posts = Post::query() ->join('comments', function (JoinClause $join) { $join->on('posts.id', '=', 'comments.post_id'); $join->whereNull('comments.deleted_at'); }) ->limit(5) ->select('posts.*') // 不要なカラムが含まれないように posts.* のみに絞り込む ->get();select `posts`.* from `posts` inner join `comments` on `posts`.`id` = `comments`.`post_id` and `comments`.`deleted_at` is null where `posts`.`deleted_at` is null limit 5シンプルな
JOIN
クエリになりました!但し,これは期待したように動作しません。なぜならば,
HasMany
の相手を JOIN すると 自分自身のレコードも増えてしまう からです。期待する結果+--------+-------------+ | Post 1 | Comment 1-1 | | | Comment 1-2 | | | Comment 1-3 | | | Comment 1-4 | | | Comment 1-5 | +--------+-------------+ | Post 2 | Comment 2-1 | +--------+-------------+実際の結果+--------+-------------+ | Post 1 | Comment 1-1 | +--------+-------------+ | Post 1 | Comment 1-2 | +--------+-------------+ | Post 1 | Comment 1-3 | +--------+-------------+ | Post 1 | Comment 1-4 | +--------+-------------+ | Post 1 | Comment 1-5 | +--------+-------------+ | Post 2 | Comment 2-1 | +--------+-------------+対策としては
DISTINCT
が使えます。$posts = Post::query() ->join('comments', function (JoinClause $join) { $join->on('posts.id', '=', 'comments.post_id'); $join->whereNull('comments.deleted_at'); }) ->limit(5) ->select('posts.*') // 不要なカラムが含まれないように posts.* のみに絞り込む ->distinct() ->get();select distinct `posts`.* from `posts` inner join `comments` on `posts`.`id` = `comments`.`post_id` and `comments`.`deleted_at` is null where `posts`.`deleted_at` is null limit 5一応これで解決はできます。但し,
DISTINCT
+LIMIT
の組み合わせはテンポラリテーブルの生成などによってLIMIT
の性能を悪化させてしまう 場合があり,銀の弾丸とは言えません。論理削除など モデルのグローバルスコープの機能も使えない ので,自分でそれらの制約条件を付与する必要がある点も懸念点です。相関サブクエリを通常のサブクエリに変換する
以下のようにすると,相関サブクエリを通常のサブクエリに変換することができます。
whereIn()
は第 2 引数にサブクエリを取ることができます。$posts = Post::query() ->whereIn('posts.id', Comment::query()->select('comments.id')) ->limit(5) ->get();select * from `posts` where `posts`.`id` in ( select `comments`.`id` from `comments` where `comments`.`deleted_at` is null ) and `posts`.`deleted_at` is null limit 5
comments
テーブルに対するクエリからposts
のカラム参照が消えて,相関サブクエリが普通のサブクエリになりました!またこちらの方法ではDISTINCT
を使わずに済んでいます。グローバルスコープが自動で付与されている点もポイントが高いです。実際の実行計画としては
JOIN
とほぼ同等になる可能性が高いですが,DISTINCT
を使用していないためテンポラリテーブル問題は解決できるでしょう。JOIN
よりはこちらを優先して使っていきたいところです。
- MySQL 5.5 の場合はサブクエリの最適化が効かない可能性があるので,
JOIN
を使ったほうが無難でしょう。- MySQL 5.6~5.7 でも
UPDATE
DELETE
に対しては最適化が効かないので,その際もJOIN
を使いましょう。- MySQL 8.x の場合はサブクエリ統一で問題なさそうです。
HasOne
BelongsTo
の場合相関サブクエリを JOIN に変換する
HasMany
は芳しくない結果になってしまいましたが, 取得結果が単一になれば何の問題もありません。躊躇なく JOIN しちゃって OK です。$comments = Comment::query() ->join('posts', function (JoinClause $join) { $join->on('comments.post_id', '=', 'posts.id'); $join->whereNull('posts.deleted_at'); }) ->limit(5) ->select('comments.*') // 不要なカラムが含まれないように posts.* のみに絞り込む ->get();select `comments`.* from `comments` inner join `posts` on `comments`.`post_id` = `posts`.`id` and `posts`.`deleted_at` is null where `comments`.`deleted_at` is null limit 5相関サブクエリを通常のサブクエリに変換する
こちらの方法でもいけます!サブクエリは万能でいいなぁ〜
$posts = Post::query() ->whereIn('posts.id', Comment::query()->select('comments.id')) ->limit(5) ->get();select * from `comments` where `comments`.`post_id` in ( select `posts`.`id` from `posts` where `posts`.`deleted_at` is null ) and `comments`.`deleted_at` is null limit 5ライブラリを作りました
こんなもん手作業で書いてたら保守性が終わるので, Laravel 標準の
has()
whereHas()
に遜色無い感じで使えるライブラリを 2 つ実装しました!
- mpyw/eloquent-has-by-non-dependent-subquery: Convert has() and whereHas() constraints to non-dependent subqueries.
- mpyw/eloquent-has-by-join: Convert has() and whereHas() constraints to join() ones for single-result relations.
できることに差はありますが,基本的にはサブクエリ版のほうが上位互換だと思ってください。どっちもインストールしておくのもありです。
コメントを持つ投稿を 5件 取得$posts = Post::query() ->hasByNonDependentSubquery('comments') ->limit(5) ->get();コメント先の投稿が削除されていないコメントを 5 件取得$comments = Comment::query() ->hasByNonDependentSubquery('posts') ->limit(5) ->get();自分のコメントで,かつコメント対象の投稿を作成したユーザの名前が John であるものを 5 件取得$comments = Auth::user() ->comments() ->hasByNonDependentSubquery( 'posts.author', null, fn (BelongsTo $q) => $q->where('name', 'John') ) ->limit(5) ->get();自分のコメントで,かつコメント対象の投稿を作成したユーザの名前が John であるものを 5 件取得,さらに投稿が論理削除されているものも含める$comments = Auth::user() ->comments() ->hasByNonDependentSubquery( 'posts.author', fn (BelongsTo $q) => $q->withTrashed(), fn (BelongsTo $q) => $q->where('name', 'John') ) ->limit(5) ->get();これで最高の Eloquent ライフを送りましょう!!!
- 投稿日:2020-06-29T01:28:55+09:00
Laravel 7で簡易的なECサイトを作る+Twitterログインを実装する
以下のサイトを参考に、ECサイトを構築します。
エラーが起こった箇所のみ書いていますが、結構長いです。
・Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る
・TwitterAPI アカウント申請〜許可まで【2020年版】
・Laravel6.0+SocialiteでTwitterログインを実装する
・Laravel 6.5 と Socialite で Twitter OAuth ログインを実現する※ソースコードはgithubにて公開しているので、宜しけば参考にしてください。
作業環境
OS:Windows 10 HOME Edition(ver.2004)
Laravel:7.15.0
Xampp:7.4.6
Composer:1.10.7
Node.js:13.9.0環境構築
Laradockではなく、いつもどおりのXamppで行いました。
Laravelのインストール
下記のコマンドを実行すると、Laravelのインストールができます。今回は「ec_app」というアプリ名にしました。
$ cd c:\xampp\htdocs $ composer create-project laravel/laravel ec_app --prefer-distデバックバーのインストール
以下のコマンドを実行して、デバックバーのインストールします。
$ composer require barryvdh/laravel-debugbarLaravelのタイムゾーンと言語設定
「config/app.php」を編集します。
config/app.php70行目 'timezone' => 'Asia/Tokyo', 83行目 'locale' => 'ja',データベースの言語設定
「config/database.php」を編集します。
config/database.php55行目 'charset' => 'utf8', 56行目 'collation' => 'utf8_unicode_ci',データベースの設定
xamppのMySqlのAdminボタンを押してphpMyAdminを起動し、今回使用するデータベースを作成します。
今回はデータベース名は「ec_app_db」としていますが、任意の名前でOKです。だたし、言語は「utf8_general_ci」を選ぶ点に注意してください。
下記を参考に、データベースの設定を「.env」ファイルに追記します。.env9行目 DB_CONNECTION=mysql 10行目 DB_HOST=127.0.0.1 11行目 DB_PORT=3306 12行目 DB_DATABASE=データベース名 13行目 DB_USERNAME=ユーザー名 14行目 DB_PASSWORD=パスワードLaravelの日本語化
Laravel7を日本語化する方法
上記の記事を参考に、Laravelを日本語化します。1.以下からZipファイルをダウンロードします。
https://github.com/caouecs/Laravel-lang2.解凍したLaravel-lang-masterフォルダのjson/ja.jsonを、プロジェクトフォルダの resources/langに移動します。
3.Laravel-lang-master/src内のjaフォルダを、プロジェクト内のresources/langに移動します。最終的に下の画像になればOKです。
Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る①
ここから基本的にエラーが起こった場所のみとコードが違う場合のみ書いていきます。
1、まずは認証機能を作成します。
以下のコマンドを実行します。
$ cd ec_app $ composer require laravel/ui --dev $ php artisan ui vue --auth $ npm install $ npm run dev課題
$ php artisan make:migration create_carts_table上記コマンドを実行して、create_carts_tableを作成し、中身を以下のように変更します。
database/migrations/2020_06_24_185504_create_carts_table.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateCartsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('carts', function (Blueprint $table) { $table->id(); $table->integer('stock_id'); $table->integer('user_id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('carts'); } }下記のコマンドを実行して完了です。
$ php artisan migrateLaravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る②
エラーはありませんでした。
課題
$ php artisan make:migration create_mines_table上記コマンドを実行して、create_mines_tableを作成し、中身を以下のように変更します。
database/migrations/2020_06_24_185504_create_carts_table.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateMinesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('mines', function (Blueprint $table) { $table->id(); $table->string('name','50'); $table->integer('age'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('mines'); } }下記のコマンドを実行します。
$ php artisan migrate $ php artisan make:seed MineTableSeeder下記の2つのファイルを変更します。
database/seeds/MineTableSeeder.php<?php use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; class MineTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('mines')->truncate(); //2回目実行の際にシーダー情報をクリア DB::table('mines')->insert([ 'name' => 'フィルムカメラ', 'age' => 10, ]); DB::table('mines')->insert([ 'name' => 'イヤホン', 'age' => 20, ]); } }database/seeds/MineTableSeeder.php<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { $this->call(MineTableSeeder::class); } }最後に下記のコマンドを実行すればOKです。
$ composer dump-autoload $ php artisan db:seedLaravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る③
今回は何もエラーは起こりませんでしたので、記事通りにすすめてください。
課題
課題も長くなるので省略しますが、下記のコマンドを実行しました。create_carts_tableは既に作成済みのため、省略しています。
$ php artisan make:model Models/Cart下記のようにファイルを書き換えます。
routes/web.php//追記 Route::get('/mycart', 'ShopController@myCart');resources/views/mycart.blade.php@extends('layouts.app') @section('content') <div class="container-fluid"> <div class=""> <div class="mx-auto" style="max-width:1200px"> <h1 style="color:#555555; text-align:center; font-size:1.2em; padding:24px 0px; font-weight:bold;">商品一覧</h1> <div class=""> <div class="d-flex flex-row flex-wrap"> 商品一覧を出したい<br> {{-- 追加 --}} @foreach($carts as $cart) {{$cart->user_id}} <br> {{$cart->stock_id}}<br> @endforeach {{-- ここまで --}} </div> </div> </div> </div> </div> @endsectionapp/Models/Cart.php<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Cart extends Model { protected $guarded = [ 'stock_id', 'user_id' ]; }app/Http/Controllers/ShopController.php<?php namespace App\Http\Controllers; use App\Models\Stock; use App\Models\Cart; //追加 use Illuminate\Http\Request; class ShopController extends Controller { public function index() { $stocks = Stock::Paginate(6); return view('shop',compact('stocks')); } public function myCart() //追加 { $carts = Cart::all(); return view('mycart',compact('carts')); } }実践課題
下記のようにファイルを書き換えます。
routes/web.php// ログイン状態 Route::group(['middleware' => 'auth'], function() { Route::get('/mycart', 'ShopController@myCart'); });Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る④
前回の課題の答え合わせをして、相違点があったので修正しました。
4、カートの中身を表示する
ここまで来たときに下記のエラーが出ました。
エラーの内容を見ると、user_id
が取得できていないため、エラーになっています。
まだ、userテーブルにはデータを未挿入なので、とりあえず下記のように「ShopController.php」を変更しました。app/Http/Controllers/ShopController.phppublic function addMyCart(Request $request) { $user_id = 1; $stock_id=$request->stock_id; $cart_add_info=Cart::firstOrCreate(['stock_id' => $stock_id,'user_id' => $user_id]); if($cart_add_info->wasRecentlyCreated){ $message = 'カートに追加しました'; } else{ $message = 'カートに登録済みです'; } $my_carts = Cart::where('user_id',$user_id)->get(); return view('mycart',compact('my_carts' , 'message')); }すると、以下のようなエラーメッセージに変わりました。
まだログインしていないので、一旦「mycart.blade.php」ファイルを下記のように変更しました。resources/views/mycart.blade.php@extends('layouts.app') @section('content') <div class="container-fluid"> <div class="mx-auto" style="max-width:1200px"> <h1 class="text-center font-weight-bold" style="color:#555555; font-size:1.2em; padding:24px 0px;"> <div class=""> <p class="text-center">{{ $message }}</p><br> <div class="d-flex flex-row flex-wrap"> @foreach($my_carts as $my_cart) <div class="mycart_box"> <p>ユーザーID:{{$my_cart->user_id}}</p> <p>ストックID:{{$my_cart->stock_id}}</p> @endforeach </div> <a href="/">商品一覧へ</a> </div> </div> </div> </div> @endsectionこれで記事通りに表示されました。
Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る⑤
記事通りに進める前に、会員登録してUserを1つ作っておきます。
前回変更したファイルを、下記のように変更します。app/Http/Controllers/ShopController.php<?php namespace App\Http\Controllers; use App\Models\Stock; //追加 use App\Models\Cart; //追加 use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class ShopController extends Controller { public function index() //追加 { $stocks = Stock::Paginate(6); //Eloquantで検索 return view('shop',compact('stocks')); //追記変更 } public function myCart() //追加 { $my_carts = Cart::all(); //Eloquantで検索 return view('mycart',compact('my_carts')); //追記変更 } public function addMyCart(Request $request) { $user_id = Auth::id(); $stock_id=$request->stock_id; $cart_add_info=Cart::firstOrCreate(['stock_id' => $stock_id,'user_id' => $user_id]); if($cart_add_info->wasRecentlyCreated){ $message = 'カートに追加しました'; } else{ $message = 'カートに登録済みです'; } $my_carts = Cart::where('user_id',$user_id)->get(); return view('mycart',compact('my_carts' , 'message')); } }resources/views/mycart.blade.php@extends('layouts.app') @section('content') <div class="container-fluid"> <div class="mx-auto" style="max-width:1200px"> <h1 class="text-center font-weight-bold" style="color:#555555; font-size:1.2em; padding:24px 0px;"> {{ Auth::user()->name }}さんのカートの中身</h1> <div class=""> <p class="text-center">{{ $message ?? '' }}</p><br> <div class="d-flex flex-row flex-wrap"> @foreach($my_carts as $my_cart) <div class="mycart_box"> <p>ユーザーID:{{$my_cart->user_id}}</p> <p>ストックID:{{$my_cart->stock_id}}</p> @endforeach </div> <a href="/">商品一覧へ</a> </div> </div> </div> </div> @endsectionこれで記事のとおりに進めてもエラーは起こりませんでした。
課題
①「web.php」に以下を追記します。
routes/web.phpRoute::post('/cartdelete', 'ShopController@deleteCart');//追記②③以下のようにファイルに追記します。
app/Http/Controllers/ShopController.phppublic function deleteCart(Request $request, Cart $cart) { $stock_id=$request->stock_id; $user = auth()->user(); $message = $cart->deleteMyCart($user->id, $stock_id); $my_carts = $cart->showCart(); return view('mycart',compact('my_carts' , 'message')); }app/Models/Cart.phppublic function deleteMyCart(Int $stock_id, Int $user_id) { $user_id = Auth::id(); $this->where('user_id', $user_id)->where('stock_id', $stock_id)->delete(); return $message = '商品をカート内から削除しました'; }なんとなくは動きますが、上手く削除できなかったので、次に進んで答えを確認します。
Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る⑥
1、課題の答え合わせ
この通りにすると、上手く動きました。
6、購入完了のメールを送信する
ここで下記のエラーが出ました。
「ShopController.php」を下記のように書き換えました。public function checkout(Cart $cart) { $checkout_info = $cart->checkoutCart(); Mail::to('test@example.com')->send(new Thanks); //追記 return view('checkout'); }キャッシュをクリアするために、下記のコマンドを実行し、エラーが解消しました。
$ php artisan config:cacheあとは記事通りに進めていけば、エラーは起こりませんでした。
TwitterAPI アカウント申請〜許可まで【2020年版】
Twitterログインを実装するためには、Twitter Apiを使用する許可が必要となるため、この記事を参考に申請しました。
数日かかるようですが、私の場合はすぐに使えるようになりました。
Laravel6.0+SocialiteでTwitterログインを実装する
ここからは、個人的に始めてやることなので、記録として残していきます。
1、とりあえず認証機能を追加
構築済みなので飛ばします。
2、Socialiteをcomposerでダウンロード。
以下のコマンドを実行して、composerを利用してsocialite機能に必要なやつファイルを一気に取得します。
$ composer require laravel/socialite3、とりあえずルーティング
routes/web.phpにTwitterのためのルーティングを書きます。
routes/web.phpRoute::get('login/twitter', 'Auth\LoginController@redirectToTwitterProvider'); Route::get('login/twitter/callback', 'Auth\LoginController@handleTwitterProviderCallback');3、コントローラーに処理を記述する①
LoginController.phpに以下を追記します。
app/http/Controllers/Auth/LoginController.phppublic function redirectToTwitterProvider() { return Socialite::driver('twitter')->redirect(); }4、使う前の下準備
Socialite::を使うためにサービスプロバイダへの登録とエイリアスの作成を行います。
config/app.phpの'providers' => []と'aliases' => []に以下を追記します。config/app.php// providers Laravel\Socialite\SocialiteServiceProvider::class, //aliases 'Socialite' => Laravel\Socialite\Facades\Socialite::class,config/services.phpに以下を追記します。
config/services.php'twitter' => [ 'client_id' => env('TWITTER_CLIENT_ID'), 'client_secret' => env('TWITTER_CLIENT_SECRET'), 'redirect' => env('CALLBACK_URL') ],.envに以下を追記します。
.envTWITTER_CLIENT_ID=************************* //下を参照 TWITTER_CLIENT_SECRET=************************* //下を参照 CALLBACK_URL=http://localhost/login/twitter/callback //よく見たらルーティングしたやつTWITTER_CLIENT_IDとTWITTER_CLIENT_SECRETは、下記のように取得します。
https://developer.twitter.com/en/apps
にアクセスして、「create app」をクリックします。
英語を入力していき、「create」をクリックします。
下記のモーダルウィンドウが出てくるので、「create」をクリックします。
下記のような画面に遷移するのでにpermissionsタブを開き、Additional premissionsにチェックが入っていないとEmail情報が取得できないので許可を出しておきます。
最後に「Keys and tokens」にタブを切変えると、APIが取得できます。
「TWITTER_CLIENT_ID」に「API Key」を、「TWITTER_CLIENT_SECRET」に「API secret key」を入力します。5、コントローラーに処理を記述する②
Auth\LoginControllerにhandleTwitterProviderCallbackを実装します。以下を追記します。
app/http/Controllers/Auth/LoginController.phppublic function handleTwitterProviderCallback(){ try { $user = Socialite::with("twitter")->user(); } catch (\Exception $e) { return redirect('/login')->with('oauth_error', 'ログインに失敗しました'); // エラーならログイン画面へ転送 } $myinfo = User::firstOrCreate(['token' => $user->token ], ['name' => $user->nickname,'email' => $user->getEmail()]); Auth::login($myinfo); return redirect()->to('/'); // homeへ転送 }database/migration/2014_10_12_000000_create_users_tableのupメソッドを以下のように変更します。
database/migration/2014_10_12_000000_create_users_table.phppublic function up() { Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password')->nullable(); //->nullabale();追加 $table->string('token')->nullable(); //追加 $table->rememberToken(); $table->timestamps(); }); }app/User.phpを以下のように編集します。
protected $fillable = [ 'name', 'email', 'password', 'token', ];後は以下のコマンドを順に実行します。
$ php artisan migrate:fresh --seed $ composer dump-autoload $ npm run dev $ php artisan config:cache $ php artisan serveこのままだとパスワードに問題があるとのことで、さらにうまくできずにエラーが起きて修正できなかったので、下記に進みます。
Laravel 6.5 と Socialite で Twitter OAuth ログインを実現する
上記の記事を参考に以下の点を変更しました。
5.Twitter Developersから Twitterアプリを登録します
https://dev.twitter.com/index
上記にアクセスして下記を変更しました。Callback URL: http://あなたのサーバ名/auth/twitter/callback同様に.envファイルも修正します。
.envTWITTER_CALLBACK_URL=http://あなたのサーバ名/auth/twitter/callback6.laravelのルーティングを設定
routes/web.php に以下を追加します。
routes/web.php// Auth Twitter Route::get('auth/twitter', 'Auth\AuthController@TwitterRedirect'); Route::get('auth/twitter/callback', 'Auth\AuthController@TwitterCallback'); Route::get('auth/twitter/logout', 'Auth\AuthController@getLogout'); // Auth Google Route::get('auth/google', 'Auth\AuthController@GoogleRedirect'); Route::get('auth/google/callback', 'Auth\AuthController@GoogleCallback'); Route::get('auth/google/logout', 'Auth\AuthController@getLogout'); // Auth Facebook Route::get('auth/facebook', 'Auth\AuthController@FacebookRedirect'); Route::get('auth/facebook/callback', 'Auth\AuthController@FacebookCallback'); Route::get('auth/facebook/logout', 'Auth\AuthController@getLogout');7コントローラーの設定
app/Http/Controllers/Auth/AuthController.php を以下の内容で新規作成します。
app/Http/Controllers/Auth/AuthController.php<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Socialite; class AuthController extends Controller { public function TwitterRedirect() { return Socialite::driver('twitter')->redirect(); } public function TwitterCallback() { // OAuthユーザー情報を取得 $social_user = Socialite::driver('twitter')->user(); $user = $this->first_or_create_social_user('twitter', $social_user->id, $social_user->name, $social_user->avatar ); // Laravel 標準の Auth でログイン \Auth::login($user); return redirect('/home'); } /** * ログインしたソーシャルアカウントがDBにあるかどうか調べます * * @param string $service_name ( twitter , facebook ... ) * @param int $social_id ( 123456789 ) * @param string $social_avatar ( https://....... ) * * @return \App\User $user * */ protected function first_or_create_social_user( string $service_name, int $social_id, string $social_name, string $social_avatar ) { $user = null; $user = \App\User::where( "{$service_name}_id", '=', $social_id )->first(); if ( $user === null ){ $user = new \App\User(); $user->fill( [ "{$service_name}_id" => $social_id , 'name' => $social_name , 'avatar' => $social_avatar , 'password' => 'DUMMY_PASSWORD' , ] ); $user->save(); return $user; } else{ return $user; } } }8.URLを叩いてログインをテストする
login.blade.phpに以下を追記します。
resorces/views/auth/login.blade.php<div class="form-group row mt-3"> <div class="col-md-6 offset-md-4"> <a href="auth/twitter"> <button type="button" class="btn btn-primary"><i class="fab fa-twitter"></i> Twitterアカウントでログインする</button> </a> </div> </div>9.実際のログイン後の実装(ユーザー自動作成)
2014_10_12_000000_create_users_table.phpのupメソッドをデフォルトに戻します。
2014_10_12_000000_create_users_table.phppublic function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); }以下のコマンドを実行します。
$ composer require doctrine/dbal $ php artisan make:migration change_users_table_add_oauth_columns --table=userschange_users_table_add_oauth_columns.php を編集します。
2020_06_28_234825_change_users_table_add_oauth_columns.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class ChangeUsersTableAddOauthColumns extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function (Blueprint $table) { $table->datetime('avatar')->nullable()->after('remember_token')->comment('アバター画像'); $table->unsignedBigInteger('twitter_id')->nullable()->after('remember_token')->comment('Twitter ID'); $table->unsignedBigInteger('facebook_id')->nullable()->after('remember_token')->comment('Facebook ID'); $table->unsignedBigInteger('github_id')->nullable()->after('remember_token')->comment('GitHub ID'); $table->unsignedBigInteger('google_id')->nullable()->after('remember_token')->comment('Google ID'); $table->unsignedBigInteger('yahoo_id')->nullable()->after('remember_token')->comment('Yahoo ID'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('avatar'); $table->dropColumn('twitter_id'); $table->dropColumn('facebook_id'); $table->dropColumn('github_id'); $table->dropColumn('google_id'); $table->dropColumn('yahoo_id'); }); } }app\User.phpを編集します。
app\Users.php// protected $fillable = [ // 'name', 'email', 'password', 'token', // ]; // guarded protected $guarded = ['id', 'created_at', 'updated_at'];DatabaseSeeder.php を変更します。
DatabaseSeeder.phppublic function run() { $this->call(MineTableSeeder::class) ->call(StockTableSeeder::class); }後は以下のコマンドを順に実行します。
前項「Laravel6.0+SocialiteでTwitterログインを実装する」の項目を戻す
config/app.phpに追加した項目をコメントアウトします。
config/app.php'providers' //これ追加!! //Laravel\Socialite\SocialiteServiceProvider::class, 'aliases' // 以下を追記 //'Socialite' => Laravel\Socialite\Facades\Socialite::class,LoginControllerも戻します。
Auth\LoginController.php// public function redirectToTwitterProvider() // { // return Socialite::driver('twitter')->redirect(); // } // // public function handleTwitterProviderCallback(){ // // try { // $user = Socialite::with("twitter")->user(); // } // catch (\Exception $e) { // return redirect('/login')->with('oauth_error', 'ログインに失敗しました'); // // エラーならログイン画面へ転送 // } // // $myinfo = User::firstOrCreate(['token' => $user->token ], // ['name' => $user->nickname,'email' => $user->getEmail()]); // Auth::login($myinfo); // return redirect()->to('/'); // homeへ転送 // }まだエラーが起きたので、下記を修正
Twitter関連は絵文字を使うので、使えるように修正します。
config/database.php'mysql' => [ 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_general_ci', ],データベースはphpMyAdminでDBを選択し、「操作」→「照合順序」でか下記のように「utf8mb4_general_ci」に変更します。
画像のURLをtimestamp型に受け取るになっていたので修正します。2020_06_28_234825_change_users_table_add_oauth_columns.php// 変更前 $table->timestamp('avatar')->nullable()->after('remember_token')->comment('アバター画像'); // 変更後 $table->string('avatar')->nullable()->after('remember_token')->comment('アバター画像');※上記3つが終わったら、一度「$ php artisan migrate:fresh --seed」しないとエラーになるので注意。
Emailを入力していなかったので追加しました。ついでにリダイレクト先も変更しています。
AuthController.php<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Socialite; use App\Models\User; class AuthController extends Controller { public function TwitterRedirect() { return Socialite::driver('twitter')->redirect(); } public function TwitterCallback() { // OAuthユーザー情報を取得 $social_user = Socialite::driver('twitter')->user(); $user = $this->first_or_create_social_user('twitter', $social_user->id, $social_user->name, $social_user->email, $social_user->avatar ); // Laravel 標準の Auth でログイン \Auth::login($user); return redirect('/'); } /** * ログインしたソーシャルアカウントがDBにあるかどうか調べます * * @param string $service_name ( twitter , facebook ... ) * @param int $social_id ( 123456789 ) * @param string $social_avatar ( https://....... ) * * @return User $user * */ protected function first_or_create_social_user( string $service_name, int $social_id, string $social_name, string $social_email, string $social_avatar ) { $user = null; $user = User::where( "{$service_name}_id", '=', $social_id )->first(); if ( $user === null ){ $user = new User(); $user->fill( [ "{$service_name}_id" => $social_id , 'name' => $social_name , 'email' => $social_email , 'avatar' => $social_avatar , 'password' => 'DUMMY_PASSWORD' , ] ); $user->save(); return $user; } else{ return $user; } } }最後に下記のコマンドを実行します。
$ php artisan migrate:fresh --seed $ composer dump-autoload $ npm run dev $ php artisan config:cache $ php artisan serveこれでTwitterでログインできるようになりました。
ソースコードはhttps://github.com/neneta0921/ec_app にて公開しているので、宜しけば参考にしてください。
長くなりましたが、以上です。
- 投稿日:2020-06-29T01:28:55+09:00
Laravel 7で簡易的なECサイトを作る+Twitter OAuth ログインを実装する
以下のサイトを参考に、ECサイトを構築します。
エラーが起こった箇所のみ書いていますが、結構長いです。
・Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る
・TwitterAPI アカウント申請〜許可まで【2020年版】
・Laravel6.0+SocialiteでTwitterログインを実装する
・Laravel 6.5 と Socialite で Twitter OAuth ログインを実現する※ソースコードはgithubにて公開しているので、宜しけば参考にしてください。
作業環境
OS:Windows 10 HOME Edition(ver.2004)
Laravel:7.15.0
Xampp:7.4.6
Composer:1.10.7
Node.js:13.9.0環境構築
Laradockではなく、いつもどおりのXamppで行いました。
Laravelのインストール
下記のコマンドを実行すると、Laravelのインストールができます。今回は「ec_app」というアプリ名にしました。
$ cd c:\xampp\htdocs $ composer create-project laravel/laravel ec_app --prefer-distデバックバーのインストール
以下のコマンドを実行して、デバックバーのインストールします。
$ composer require barryvdh/laravel-debugbarLaravelのタイムゾーンと言語設定
「config/app.php」を編集します。
config/app.php70行目 'timezone' => 'Asia/Tokyo', 83行目 'locale' => 'ja',データベースの言語設定
「config/database.php」を編集します。
config/database.php55行目 'charset' => 'utf8', 56行目 'collation' => 'utf8_unicode_ci',データベースの設定
xamppのMySqlのAdminボタンを押してphpMyAdminを起動し、今回使用するデータベースを作成します。
今回はデータベース名は「ec_app_db」としていますが、任意の名前でOKです。だたし、言語は「utf8_general_ci」を選ぶ点に注意してください。
下記を参考に、データベースの設定を「.env」ファイルに追記します。.env9行目 DB_CONNECTION=mysql 10行目 DB_HOST=127.0.0.1 11行目 DB_PORT=3306 12行目 DB_DATABASE=データベース名 13行目 DB_USERNAME=ユーザー名 14行目 DB_PASSWORD=パスワードLaravelの日本語化
Laravel7を日本語化する方法
上記の記事を参考に、Laravelを日本語化します。1.以下からZipファイルをダウンロードします。
https://github.com/caouecs/Laravel-lang2.解凍したLaravel-lang-masterフォルダのjson/ja.jsonを、プロジェクトフォルダの resources/langに移動します。
3.Laravel-lang-master/src内のjaフォルダを、プロジェクト内のresources/langに移動します。最終的に下の画像になればOKです。
Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る①
ここから基本的にエラーが起こった場所のみとコードが違う場合のみ書いていきます。
1、まずは認証機能を作成します。
以下のコマンドを実行します。
$ cd ec_app $ composer require laravel/ui --dev $ php artisan ui vue --auth $ npm install $ npm run dev課題
$ php artisan make:migration create_carts_table上記コマンドを実行して、create_carts_tableを作成し、中身を以下のように変更します。
database/migrations/2020_06_24_185504_create_carts_table.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateCartsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('carts', function (Blueprint $table) { $table->id(); $table->integer('stock_id'); $table->integer('user_id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('carts'); } }下記のコマンドを実行して完了です。
$ php artisan migrateLaravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る②
エラーはありませんでした。
課題
$ php artisan make:migration create_mines_table上記コマンドを実行して、create_mines_tableを作成し、中身を以下のように変更します。
database/migrations/2020_06_24_185504_create_carts_table.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateMinesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('mines', function (Blueprint $table) { $table->id(); $table->string('name','50'); $table->integer('age'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('mines'); } }下記のコマンドを実行します。
$ php artisan migrate $ php artisan make:seed MineTableSeeder下記の2つのファイルを変更します。
database/seeds/MineTableSeeder.php<?php use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; class MineTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('mines')->truncate(); //2回目実行の際にシーダー情報をクリア DB::table('mines')->insert([ 'name' => 'フィルムカメラ', 'age' => 10, ]); DB::table('mines')->insert([ 'name' => 'イヤホン', 'age' => 20, ]); } }database/seeds/MineTableSeeder.php<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { $this->call(MineTableSeeder::class); } }最後に下記のコマンドを実行すればOKです。
$ composer dump-autoload $ php artisan db:seedLaravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る③
今回は何もエラーは起こりませんでしたので、記事通りにすすめてください。
課題
課題も長くなるので省略しますが、下記のコマンドを実行しました。create_carts_tableは既に作成済みのため、省略しています。
$ php artisan make:model Models/Cart下記のようにファイルを書き換えます。
routes/web.php//追記 Route::get('/mycart', 'ShopController@myCart');resources/views/mycart.blade.php@extends('layouts.app') @section('content') <div class="container-fluid"> <div class=""> <div class="mx-auto" style="max-width:1200px"> <h1 style="color:#555555; text-align:center; font-size:1.2em; padding:24px 0px; font-weight:bold;">商品一覧</h1> <div class=""> <div class="d-flex flex-row flex-wrap"> 商品一覧を出したい<br> {{-- 追加 --}} @foreach($carts as $cart) {{$cart->user_id}} <br> {{$cart->stock_id}}<br> @endforeach {{-- ここまで --}} </div> </div> </div> </div> </div> @endsectionapp/Models/Cart.php<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Cart extends Model { protected $guarded = [ 'stock_id', 'user_id' ]; }app/Http/Controllers/ShopController.php<?php namespace App\Http\Controllers; use App\Models\Stock; use App\Models\Cart; //追加 use Illuminate\Http\Request; class ShopController extends Controller { public function index() { $stocks = Stock::Paginate(6); return view('shop',compact('stocks')); } public function myCart() //追加 { $carts = Cart::all(); return view('mycart',compact('carts')); } }実践課題
下記のようにファイルを書き換えます。
routes/web.php// ログイン状態 Route::group(['middleware' => 'auth'], function() { Route::get('/mycart', 'ShopController@myCart'); });Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る④
前回の課題の答え合わせをして、相違点があったので修正しました。
4、カートの中身を表示する
ここまで来たときに下記のエラーが出ました。
エラーの内容を見ると、user_id
が取得できていないため、エラーになっています。
まだ、userテーブルにはデータを未挿入なので、とりあえず下記のように「ShopController.php」を変更しました。app/Http/Controllers/ShopController.phppublic function addMyCart(Request $request) { $user_id = 1; $stock_id=$request->stock_id; $cart_add_info=Cart::firstOrCreate(['stock_id' => $stock_id,'user_id' => $user_id]); if($cart_add_info->wasRecentlyCreated){ $message = 'カートに追加しました'; } else{ $message = 'カートに登録済みです'; } $my_carts = Cart::where('user_id',$user_id)->get(); return view('mycart',compact('my_carts' , 'message')); }すると、以下のようなエラーメッセージに変わりました。
まだログインしていないので、一旦「mycart.blade.php」ファイルを下記のように変更しました。resources/views/mycart.blade.php@extends('layouts.app') @section('content') <div class="container-fluid"> <div class="mx-auto" style="max-width:1200px"> <h1 class="text-center font-weight-bold" style="color:#555555; font-size:1.2em; padding:24px 0px;"> <div class=""> <p class="text-center">{{ $message }}</p><br> <div class="d-flex flex-row flex-wrap"> @foreach($my_carts as $my_cart) <div class="mycart_box"> <p>ユーザーID:{{$my_cart->user_id}}</p> <p>ストックID:{{$my_cart->stock_id}}</p> @endforeach </div> <a href="/">商品一覧へ</a> </div> </div> </div> </div> @endsectionこれで記事通りに表示されました。
Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る⑤
記事通りに進める前に、会員登録してUserを1つ作っておきます。
前回変更したファイルを、下記のように変更します。app/Http/Controllers/ShopController.php<?php namespace App\Http\Controllers; use App\Models\Stock; //追加 use App\Models\Cart; //追加 use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class ShopController extends Controller { public function index() //追加 { $stocks = Stock::Paginate(6); //Eloquantで検索 return view('shop',compact('stocks')); //追記変更 } public function myCart() //追加 { $my_carts = Cart::all(); //Eloquantで検索 return view('mycart',compact('my_carts')); //追記変更 } public function addMyCart(Request $request) { $user_id = Auth::id(); $stock_id=$request->stock_id; $cart_add_info=Cart::firstOrCreate(['stock_id' => $stock_id,'user_id' => $user_id]); if($cart_add_info->wasRecentlyCreated){ $message = 'カートに追加しました'; } else{ $message = 'カートに登録済みです'; } $my_carts = Cart::where('user_id',$user_id)->get(); return view('mycart',compact('my_carts' , 'message')); } }resources/views/mycart.blade.php@extends('layouts.app') @section('content') <div class="container-fluid"> <div class="mx-auto" style="max-width:1200px"> <h1 class="text-center font-weight-bold" style="color:#555555; font-size:1.2em; padding:24px 0px;"> {{ Auth::user()->name }}さんのカートの中身</h1> <div class=""> <p class="text-center">{{ $message ?? '' }}</p><br> <div class="d-flex flex-row flex-wrap"> @foreach($my_carts as $my_cart) <div class="mycart_box"> <p>ユーザーID:{{$my_cart->user_id}}</p> <p>ストックID:{{$my_cart->stock_id}}</p> @endforeach </div> <a href="/">商品一覧へ</a> </div> </div> </div> </div> @endsectionこれで記事のとおりに進めてもエラーは起こりませんでした。
課題
①「web.php」に以下を追記します。
routes/web.phpRoute::post('/cartdelete', 'ShopController@deleteCart');//追記②③以下のようにファイルに追記します。
app/Http/Controllers/ShopController.phppublic function deleteCart(Request $request, Cart $cart) { $stock_id=$request->stock_id; $user = auth()->user(); $message = $cart->deleteMyCart($user->id, $stock_id); $my_carts = $cart->showCart(); return view('mycart',compact('my_carts' , 'message')); }app/Models/Cart.phppublic function deleteMyCart(Int $stock_id, Int $user_id) { $user_id = Auth::id(); $this->where('user_id', $user_id)->where('stock_id', $stock_id)->delete(); return $message = '商品をカート内から削除しました'; }なんとなくは動きますが、上手く削除できなかったので、次に進んで答えを確認します。
Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る⑥
1、課題の答え合わせ
この通りにすると、上手く動きました。
6、購入完了のメールを送信する
ここで下記のエラーが出ました。
「ShopController.php」を下記のように書き換えました。public function checkout(Cart $cart) { $checkout_info = $cart->checkoutCart(); Mail::to('test@example.com')->send(new Thanks); //追記 return view('checkout'); }キャッシュをクリアするために、下記のコマンドを実行し、エラーが解消しました。
$ php artisan config:cacheあとは記事通りに進めていけば、エラーは起こりませんでした。
TwitterAPI アカウント申請〜許可まで【2020年版】
Twitterログインを実装するためには、Twitter Apiを使用する許可が必要となるため、この記事を参考に申請しました。
数日かかるようですが、私の場合はすぐに使えるようになりました。
Laravel6.0+SocialiteでTwitterログインを実装する
ここからは、個人的に始めてやることなので、記録として残していきます。
1、とりあえず認証機能を追加
構築済みなので飛ばします。
2、Socialiteをcomposerでダウンロード。
以下のコマンドを実行して、composerを利用してsocialite機能に必要なやつファイルを一気に取得します。
$ composer require laravel/socialite3、とりあえずルーティング
routes/web.phpにTwitterのためのルーティングを書きます。
routes/web.phpRoute::get('login/twitter', 'Auth\LoginController@redirectToTwitterProvider'); Route::get('login/twitter/callback', 'Auth\LoginController@handleTwitterProviderCallback');3、コントローラーに処理を記述する①
LoginController.phpに以下を追記します。
app/http/Controllers/Auth/LoginController.phppublic function redirectToTwitterProvider() { return Socialite::driver('twitter')->redirect(); }4、使う前の下準備
Socialite::を使うためにサービスプロバイダへの登録とエイリアスの作成を行います。
config/app.phpの'providers' => []と'aliases' => []に以下を追記します。config/app.php// providers Laravel\Socialite\SocialiteServiceProvider::class, //aliases 'Socialite' => Laravel\Socialite\Facades\Socialite::class,config/services.phpに以下を追記します。
config/services.php'twitter' => [ 'client_id' => env('TWITTER_CLIENT_ID'), 'client_secret' => env('TWITTER_CLIENT_SECRET'), 'redirect' => env('CALLBACK_URL') ],.envに以下を追記します。
.envTWITTER_CLIENT_ID=************************* //下を参照 TWITTER_CLIENT_SECRET=************************* //下を参照 CALLBACK_URL=http://localhost/login/twitter/callback //よく見たらルーティングしたやつTWITTER_CLIENT_IDとTWITTER_CLIENT_SECRETは、下記のように取得します。
https://developer.twitter.com/en/apps
にアクセスして、「create app」をクリックします。
英語を入力していき、「create」をクリックします。
下記のモーダルウィンドウが出てくるので、「create」をクリックします。
下記のような画面に遷移するのでにpermissionsタブを開き、Additional premissionsにチェックが入っていないとEmail情報が取得できないので許可を出しておきます。
最後に「Keys and tokens」にタブを切変えると、APIが取得できます。
「TWITTER_CLIENT_ID」に「API Key」を、「TWITTER_CLIENT_SECRET」に「API secret key」を入力します。5、コントローラーに処理を記述する②
Auth\LoginControllerにhandleTwitterProviderCallbackを実装します。以下を追記します。
app/http/Controllers/Auth/LoginController.phppublic function handleTwitterProviderCallback(){ try { $user = Socialite::with("twitter")->user(); } catch (\Exception $e) { return redirect('/login')->with('oauth_error', 'ログインに失敗しました'); // エラーならログイン画面へ転送 } $myinfo = User::firstOrCreate(['token' => $user->token ], ['name' => $user->nickname,'email' => $user->getEmail()]); Auth::login($myinfo); return redirect()->to('/'); // homeへ転送 }database/migration/2014_10_12_000000_create_users_tableのupメソッドを以下のように変更します。
database/migration/2014_10_12_000000_create_users_table.phppublic function up() { Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password')->nullable(); //->nullabale();追加 $table->string('token')->nullable(); //追加 $table->rememberToken(); $table->timestamps(); }); }app/User.phpを以下のように編集します。
protected $fillable = [ 'name', 'email', 'password', 'token', ];後は以下のコマンドを順に実行します。
$ php artisan migrate:fresh --seed $ composer dump-autoload $ npm run dev $ php artisan config:cache $ php artisan serveこのままだとパスワードに問題があるとのことで、さらにうまくできずにエラーが起きて修正できなかったので、下記に進みます。
Laravel 6.5 と Socialite で Twitter OAuth ログインを実現する
上記の記事を参考に以下の点を変更しました。
5.Twitter Developersから Twitterアプリを登録します
https://dev.twitter.com/index
上記にアクセスして下記を変更しました。Callback URL: http://あなたのサーバ名/auth/twitter/callback同様に.envファイルも修正します。
.envTWITTER_CALLBACK_URL=http://あなたのサーバ名/auth/twitter/callback6.laravelのルーティングを設定
routes/web.php に以下を追加します。
routes/web.php// Auth Twitter Route::get('auth/twitter', 'Auth\AuthController@TwitterRedirect'); Route::get('auth/twitter/callback', 'Auth\AuthController@TwitterCallback'); Route::get('auth/twitter/logout', 'Auth\AuthController@getLogout'); // Auth Google Route::get('auth/google', 'Auth\AuthController@GoogleRedirect'); Route::get('auth/google/callback', 'Auth\AuthController@GoogleCallback'); Route::get('auth/google/logout', 'Auth\AuthController@getLogout'); // Auth Facebook Route::get('auth/facebook', 'Auth\AuthController@FacebookRedirect'); Route::get('auth/facebook/callback', 'Auth\AuthController@FacebookCallback'); Route::get('auth/facebook/logout', 'Auth\AuthController@getLogout');7コントローラーの設定
app/Http/Controllers/Auth/AuthController.php を以下の内容で新規作成します。
app/Http/Controllers/Auth/AuthController.php<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Socialite; class AuthController extends Controller { public function TwitterRedirect() { return Socialite::driver('twitter')->redirect(); } public function TwitterCallback() { // OAuthユーザー情報を取得 $social_user = Socialite::driver('twitter')->user(); $user = $this->first_or_create_social_user('twitter', $social_user->id, $social_user->name, $social_user->avatar ); // Laravel 標準の Auth でログイン \Auth::login($user); return redirect('/home'); } /** * ログインしたソーシャルアカウントがDBにあるかどうか調べます * * @param string $service_name ( twitter , facebook ... ) * @param int $social_id ( 123456789 ) * @param string $social_avatar ( https://....... ) * * @return \App\User $user * */ protected function first_or_create_social_user( string $service_name, int $social_id, string $social_name, string $social_avatar ) { $user = null; $user = \App\User::where( "{$service_name}_id", '=', $social_id )->first(); if ( $user === null ){ $user = new \App\User(); $user->fill( [ "{$service_name}_id" => $social_id , 'name' => $social_name , 'avatar' => $social_avatar , 'password' => 'DUMMY_PASSWORD' , ] ); $user->save(); return $user; } else{ return $user; } } }8.URLを叩いてログインをテストする
login.blade.phpに以下を追記します。
resorces/views/auth/login.blade.php<div class="form-group row mt-3"> <div class="col-md-6 offset-md-4"> <a href="auth/twitter"> <button type="button" class="btn btn-primary"><i class="fab fa-twitter"></i> Twitterアカウントでログインする</button> </a> </div> </div>9.実際のログイン後の実装(ユーザー自動作成)
2014_10_12_000000_create_users_table.phpのupメソッドをデフォルトに戻します。
2014_10_12_000000_create_users_table.phppublic function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); }以下のコマンドを実行します。
$ composer require doctrine/dbal $ php artisan make:migration change_users_table_add_oauth_columns --table=userschange_users_table_add_oauth_columns.php を編集します。
2020_06_28_234825_change_users_table_add_oauth_columns.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class ChangeUsersTableAddOauthColumns extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function (Blueprint $table) { $table->datetime('avatar')->nullable()->after('remember_token')->comment('アバター画像'); $table->unsignedBigInteger('twitter_id')->nullable()->after('remember_token')->comment('Twitter ID'); $table->unsignedBigInteger('facebook_id')->nullable()->after('remember_token')->comment('Facebook ID'); $table->unsignedBigInteger('github_id')->nullable()->after('remember_token')->comment('GitHub ID'); $table->unsignedBigInteger('google_id')->nullable()->after('remember_token')->comment('Google ID'); $table->unsignedBigInteger('yahoo_id')->nullable()->after('remember_token')->comment('Yahoo ID'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('avatar'); $table->dropColumn('twitter_id'); $table->dropColumn('facebook_id'); $table->dropColumn('github_id'); $table->dropColumn('google_id'); $table->dropColumn('yahoo_id'); }); } }app\User.phpを編集します。
app\Users.php// protected $fillable = [ // 'name', 'email', 'password', 'token', // ]; // guarded protected $guarded = ['id', 'created_at', 'updated_at'];DatabaseSeeder.php を変更します。
DatabaseSeeder.phppublic function run() { $this->call(MineTableSeeder::class) ->call(StockTableSeeder::class); }後は以下のコマンドを順に実行します。
前項「Laravel6.0+SocialiteでTwitterログインを実装する」の項目を戻す
config/app.phpに追加した項目をコメントアウトします。
config/app.php'providers' //これ追加!! //Laravel\Socialite\SocialiteServiceProvider::class, 'aliases' // 以下を追記 //'Socialite' => Laravel\Socialite\Facades\Socialite::class,LoginControllerも戻します。
Auth\LoginController.php// public function redirectToTwitterProvider() // { // return Socialite::driver('twitter')->redirect(); // } // // public function handleTwitterProviderCallback(){ // // try { // $user = Socialite::with("twitter")->user(); // } // catch (\Exception $e) { // return redirect('/login')->with('oauth_error', 'ログインに失敗しました'); // // エラーならログイン画面へ転送 // } // // $myinfo = User::firstOrCreate(['token' => $user->token ], // ['name' => $user->nickname,'email' => $user->getEmail()]); // Auth::login($myinfo); // return redirect()->to('/'); // homeへ転送 // }まだエラーが起きたので、下記を修正
Twitter関連は絵文字を使うので、使えるように修正します。
config/database.php'mysql' => [ 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_general_ci', ],データベースはphpMyAdminでDBを選択し、「操作」→「照合順序」でか下記のように「utf8mb4_general_ci」に変更します。
画像のURLをtimestamp型に受け取るになっていたので修正します。2020_06_28_234825_change_users_table_add_oauth_columns.php// 変更前 $table->timestamp('avatar')->nullable()->after('remember_token')->comment('アバター画像'); // 変更後 $table->string('avatar')->nullable()->after('remember_token')->comment('アバター画像');※上記3つが終わったら、一度「$ php artisan migrate:fresh --seed」しないとエラーになるので注意。
Emailを入力していなかったので追加しました。ついでにリダイレクト先も変更しています。
AuthController.php<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Socialite; use App\Models\User; class AuthController extends Controller { public function TwitterRedirect() { return Socialite::driver('twitter')->redirect(); } public function TwitterCallback() { // OAuthユーザー情報を取得 $social_user = Socialite::driver('twitter')->user(); $user = $this->first_or_create_social_user('twitter', $social_user->id, $social_user->name, $social_user->email, $social_user->avatar ); // Laravel 標準の Auth でログイン \Auth::login($user); return redirect('/'); } /** * ログインしたソーシャルアカウントがDBにあるかどうか調べます * * @param string $service_name ( twitter , facebook ... ) * @param int $social_id ( 123456789 ) * @param string $social_avatar ( https://....... ) * * @return User $user * */ protected function first_or_create_social_user( string $service_name, int $social_id, string $social_name, string $social_email, string $social_avatar ) { $user = null; $user = User::where( "{$service_name}_id", '=', $social_id )->first(); if ( $user === null ){ $user = new User(); $user->fill( [ "{$service_name}_id" => $social_id , 'name' => $social_name , 'email' => $social_email , 'avatar' => $social_avatar , 'password' => 'DUMMY_PASSWORD' , ] ); $user->save(); return $user; } else{ return $user; } } }最後に下記のコマンドを実行します。
$ php artisan migrate:fresh --seed $ composer dump-autoload $ npm run dev $ php artisan config:cache $ php artisan serveこれでTwitterでログインできるようになりました。
ソースコードはhttps://github.com/neneta0921/ec_app にて公開しているので、宜しけば参考にしてください。
長くなりましたが、以上です。
- 投稿日:2020-06-29T01:26:42+09:00
proc_open(): fork failed errors#
タイトルの通りメモリーがないと怒られてしまった。
https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors
sudo /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024 sudo /sbin/mkswap /var/swap.1 sudo /sbin/swapon /var/swap.1で解決