- 投稿日:2020-06-29T23:47:52+09:00
WordPressでエリア検索を実装したい
WordPressで、関東地方の歴史建造物を検索できるサイトを作っており、エリア検索を実装したいと思っています。
キャンプ場検索サイト「なっぷ」のように検索窓をクリックすると、関東地方(1都6県)が縦に表示され、
その中で例えば東京都をクリックすると右側に市区町村が縦に表示されるようにしたいです。
市区町村はある程度グループ化して、表記するつもりですが、それでも縦に長くなるので、スクロールをつけると良いのではと考えています。参考はJAL公式サイトです。⬇︎
(都県) (市区町村)
東京 → 新宿・文京・豊島 ‖←スクロールバー
神奈川 渋谷・港・千代田・中央
埼玉 目黒・品川・大田
千葉 世田谷・杉並・中野
茨城 練馬・板橋・北
栃木 江東・台東・荒川
群馬 葛飾・足立・江戸川 (これ以上の市区町村はスクロールすることで表示可能)
ーーーーーーーーーーーーーーーーー上記のような実装はカテゴリ検索と呼ぶのでしょうか?
的確な表現がわからず、調べる段階でつまづいております、、
また、実装の手順をおおよそ教えていただけますでしょうか?
お力貸していただけると光栄です。
よろしくお願いいたします。
- 投稿日:2020-06-29T22:59:33+09:00
【PHP】Cloud9で$_SESSIONが反映されなかったときの対処法
はじめに
学習記録のアウトプット・自分自身の備忘録として書いた記事です。
今回はPHPのセッションがうまくいかなかったためその概要と解決方法についてメモしていきます。やりたかったこと
以下のように設定した
$_SESSION['$csrfToken']
をページ遷移後に反映したい。$csrfToken = bin2hex(random_bytes(32)); $_SESSION['$csrfToken'] = $csrfToken;トラブルの症状
ページ遷移後に
$_SESSION
が記録されていませんでした。セッションファイルの出力先を指定する。
(これは必要ではないかもしれませんが念のため。)
Cloud9はそもそもデフォルトではphp5.6のため
random_bytes
は使えません。
そのためphp7.2をyumでインストール。この状態でphpinfo() を実行すると、session.save_pathの右カラムはno valueになっています。
デフォルトのセッションファイルの格納先が特に指定されていなかったため/etc/php.ini
でphp.ini; RPM note : session directory must be owned by process owner ; for mod_php, see /etc/httpd/conf.d/php.conf ; for php-fpm, see /etc/php-fpm.d/*conf ; session.save_path = "/tmp"
↓";"を外す。
php.ini; RPM note : session directory must be owned by process owner ; for mod_php, see /etc/httpd/conf.d/php.conf ; for php-fpm, see /etc/php-fpm.d/*conf session.save_path = "/tmp"変更後デバックしてみて以下が作成されていることを確認。
/tmp/sess_*
session_start()の書く位置
<?php session_start();と
<?php
直下に書く必要があります。
尚且つ$_SESSION
の直前ではなく、一番最初のphpタグの直下です。。。参考
https://note.kiriukun.com/entry/20191125-session-not-working-in-php#file
- 投稿日:2020-06-29T22:59:33+09:00
【PHP】Cloud9でセッションが反映されなかったときの対処法
はじめに
学習記録のアウトプット・自分自身の備忘録として書いた記事です。
今回はPHPのセッションがうまくいかなかったためその概要と解決方法についてメモしていきます。やりたかったこと
以下のように設定した
$_SESSION['$csrfToken']
をページ遷移後に反映したい。$csrfToken = bin2hex(random_bytes(32)); $_SESSION['$csrfToken'] = $csrfToken;トラブルの症状
ページ遷移後に
$_SESSION
が記録されていませんでした。セッションファイルの出力先を指定する。
(これは必要ではないかもしれませんが念のため。)
Cloud9はそもそもデフォルトではphp5.6のため
random_bytes
は使えません。
そのためphp7.2をyumでインストール。この状態でphpinfo() を実行すると、session.save_pathの右カラムはno valueになっています。
デフォルトのセッションファイルの格納先が特に指定されていなかったため
/etc/php.ini
でphp.ini; RPM note : session directory must be owned by process owner ; for mod_php, see /etc/httpd/conf.d/php.conf ; for php-fpm, see /etc/php-fpm.d/*conf ; session.save_path = "/tmp"
↓";"を外す。
php.ini; RPM note : session directory must be owned by process owner ; for mod_php, see /etc/httpd/conf.d/php.conf ; for php-fpm, see /etc/php-fpm.d/*conf session.save_path = "/tmp"変更後デバックしてみて以下が作成されていることを確認。
/tmp/sess_*
session_start()の書く位置
<?php session_start();と
<?php
直下に書く必要があります。
尚且つ$_SESSION
の直前ではなく、一番最初のphpタグの直下です。。。参考
https://note.kiriukun.com/entry/20191125-session-not-working-in-php#file
- 投稿日:2020-06-29T17:29:09+09:00
【PHP】Symfony2, SwiftMailerでUTF-8のbase64encodeした日本語メールを送りたい
要件として
- クソ長い日本語文字列をメールの差出人名として表示したい
なぜUTF-8の送りたいのか
「メール送信する際にiso-2022-jpで送ればいいじゃない。」
って声が聞こえてきそうですが、iso-2022-jpだと
環境依存文字、絵文字、他の言語(ハングル文字、タイ語)が化ける日本語かどうか判定するのもめんどくさいと思ったので
ならいっそのことUTF-8統一で全メール送ろう と思った次第です。実装
環境は以下
Symfony 2
PHP 5
Swiftmailer 5.2.1(だと思う)ちょいと古いSymfonyを使用してる手前、書き方が最新ではないかもしれません
\Swift::init(function () { \Swift_DependencyContainer::getInstance() ->register('mime.qpheaderencoder') ->asAliasOf('mime.base64headerencoder'); \Swift_Preferences::getInstance()->setCharset(Constant::UTF8); }); $transport = new \Swift_SmtpTransport( 127.0.0.1, 25 ); $mailer = new \Swift_Mailer($transport); $message = (new \Swift_Message()) ->setFrom($fromAddress, $fromName) ->setTo($to) ->setSubject($subject) ->setBody($body, 'text/plain') ; $mailer->send($message);ざっくりとこんな感じですかね。
問題
とまぁ、例によって普通に実装したんですが、この状態だと日本語文字が化けることがあるんですね
一定数以上長い日本語文字を件名や、差出人名に入れると化けます。原因
単純に言うとSwiftmailerのバグかと思われます。
base64エンコード + UTF-8 だとエンコードされた日本語1文字の途中で改行がされることがあり、そのタイミングから文字が化ける
※ 最新のswiftmailerのコード見ても「この処理がバグってるのだろう」という部分はそのままだったのでおそらく最新でも発生する対応
「ライブラリのバグです。」
「じゃあ、仕方ないね。」
で片付けられないこの世の中。多少無理してでもやります。
改行しているコードを直すのは厳しそうだったので、改行されないようにします
ゴリ押し解決
まずBase64エンコードしてる部分を改行仕様を弄ります
swiftmailer/lib/classes/Swift/Encoder/Base64Encoder.phpif (0 >= $maxLineLength || 76 < $maxLineLength) { $maxLineLength = 76; }をこうしてやります
swiftmailer/lib/classes/Swift/Encoder/Base64Encoder.phpif (0 >= $maxLineLength || 998 < $maxLineLength) { $maxLineLength = 998; }次にメールヘッダの一行の最大文字数を弄ります
swiftmailer/lib/Swift/MIME/Headers/AbstractHeader.phpabstract class Swift_Mime_Headers_AbstractHeader implements Swift_Mime_Header { ... /** * The maximum length of a line in the header. * * @var int */ private $_lineLength = 78; ... protected function encodeWords(Swift_Mime_Header $header, $input, $usedLength = -1) { ... $header->setMaxLineLength(76); // Forcefully override ... } protected function getTokenAsEncodedWord($token, $firstLineOffset = 0) { ... if ($firstLineOffset >= 75) { //Does this logic need to be here? $firstLineOffset = 0; } ... $encodedTextLines = explode("\r\n", $this->_encoder->encodeString( $token, $firstLineOffset, 75 - $encodingWrapperLength, $this->_charset ) ); ... } ... }をこうしてやります
swiftmailer/lib/Swift/MIME/Headers/AbstractHeader.phpabstract class Swift_Mime_Headers_AbstractHeader implements Swift_Mime_Header { ... /** * The maximum length of a line in the header. * * @var int */ private $_lineLength = 998; ... protected function encodeWords(Swift_Mime_Header $header, $input, $usedLength = -1) { ... $header->setMaxLineLength(998); // Forcefully override ... } protected function getTokenAsEncodedWord($token, $firstLineOffset = 0) { ... if ($firstLineOffset >= 997) { //Does this logic need to be here? $firstLineOffset = 0; } ... $encodedTextLines = explode("\r\n", $this->_encoder->encodeString( $token, $firstLineOffset, 997 - $encodingWrapperLength, $this->_charset ) ); ... } ... }これすると何が起きる?
簡単に申しますと、メールヘッダが998文字まで入ります('ω')
つまり998文字までなら改行されないので改行バグには当たらないです・・・・・('ω')???根本解決にはなっていない???
最後に
脳筋ごり押し対応で無理やりバグを避けてみましたが
これは実際、根本解決に至っていない + ライブラリを改変しているので推奨できるやり方ではございません。また、もっとベストな方法で実装できる知見をお持ちの方がいらっしゃいましたら、ご教授いただけると幸いです。。。
(実際エンコード前段階で区切り文字を挿入しておくみたいなアプローチもあったかなぁ…
- 投稿日: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-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: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-29T11:55:46+09:00
WordPressにショートコードを使ってPHPを埋め込む方法
- 環境
- Windows 10 Pro 64bit バージョン1909
- Local by Flywheel 5.6.1
- PHP 7.4.1 / MySQL 8.0.16 / Apach / WordPress 5.4.2
- テーマ : Lightning
ショートコードをつくる基本の方法
- functions.phpを開く
- PHPで関数を定義して処理を書く
add_shortcode('{ショートコード用文字}', '{関数名}');
を書く- WordPressの記事などに
[{ショートコード用文字}]
を書く- プレビューなんかで見ると処理の結果が出力される
functions.phpの場所 :
{サイトのディレクトリ}/app/public/wp-content/themes/{テーマのディレクトリ}/functions.php
- 今回の環境では、
/c/Users/{ユーザ}/Local Sites/sample/app/public/wp-content/themes/{テーマ}/functions.php
参考
functions.phpに書くfunction short_cord_func() { return 'ショートコードをつくる基本の方法'; } add_shortcode('short_cord_string', 'short_cord_func');記事ショートコードを書く[short_cord_string]外部ファイルを読み込んでショートコードをつくる方法
- 外部ファイルにPHPで処理を書く
- functions.phpを開く
- PHPで関数を定義して外部ファイルを呼び出す処理を書く
- あとは「ショートコードをつくる基本の方法」と同じ
- 参考
テーマ用ディレクトリ配下にディレクトを作って外部ファイルをおいてみたテーマのディレクトリ ├ functions.php └ customphp └ short-cord.php << 外部ファイルshort-cord.phpという外部ファイルにPHPで処理を書く// この外部ファイルからの出力がそのまま埋め込まれるイメージ <p><?php echo '外部ファイルを読み込んでショートコードをつくる方法'; ?><p>functions.phpにPHPで関数を定義して外部ファイルを呼び出す処理を書くfunction short_cord_func() { // 出力バッファリングを有効化する ob_start(); // 外部ファイルを読み込む // 「.php(拡張子部分)」は不要 get_template_part('customphp/short-cord'); // 出力バッファを削除する return ob_get_clean(); } add_shortcode('short_cord_string', 'short_cord_func');外部ファイルを読み込むショートコードを複数作る場合
外部ファイルを読み込む専用の関数を作っておくと便利
こんな風に外部ファイルおいてみたテーマのディレクトリ ├ functions.php └ customphp ├ hoge.php << 外部ファイル └ fuga.php << 外部ファイルfunctions.php/** * 外部ファイルを読み込む専用関数. * @param array 読み込む外部ファイル名(拡張子なし)を値に持つ連想配列(keyはfile). **/ function custom_func($args) { // $file変数を配列からインポートする extract(shortcode_atts(array('file' => 'default'), $args)); // 出力バッファリングを有効化する ob_start(); // 外部ファイルを読み込む get_template_part("$file"); // 出力バッファを削除する return ob_get_clean(); } /** hoge.phpを使うショートコード用関数. */ function hoge() { return custom_func(['file' => 'customphp/hoge']); } add_shortcode('hoge', 'hoge'); /** fuga.phpを使うショートコード用関数. */ function fuga() { return custom_func(['file' => 'customphp/fuga']); } add_shortcode('fuga', 'fuga');テーマを選定中でちょいちょい変える場合
外部ファイルを複数作っている場合にはどっかのディレクトリにまとめておくとテーマが変わった時にディレクトリ毎持っていけるから便利
だけど、テーマを選定中で使うテーマのディレクトリ毎にコピーしていくのは面倒くさい
そんな時はシンボリックリンクを使うと便利外部ファイルを入れるディレクトリを作る{サイトのディレクトリ}/app/public ├ wp-admin ├ wp-includes ├ wp-content |└ themes | ├ lightning << テーマのディレクトリ | |├ functions.php | |└ customphp << シンボリックリンク | └ twentynineteen << テーマのディレクトリ | ├ functions.php | └ customphp << シンボリックリンク └ customphp << 外部ファイル用ディレクトリ ├ hoge.php └ fuga.phpWindowsのコマンドプロンプトでシンボリックリンクを作る方法# 1. 管理者権限でコマンドプロンプトを起動する # 2. シンボリックリンクを作りたいところに移動する >cd "c:\Users\{ユーザ}\Local Sites\sample\app\public\wp-content\themes\twentynineteen" # 3. ディレクトリ(/D)のシンボリックリンクをつくる(mklink) >mklink /D customphp "c:\Users\{ユーザ}\Local Sites\sample\app\public\customphp" customphp <<===>> c:\Users\{ユーザ}\Local Sites\sample\app\public\customphp のシンボリック リンクが作成されましたショートコードを作ってみた
WordPressでPHPを埋め込む方法がなんとなくわかったので早速Google検索した結果を表示するショートコードを作ってみた。
WordPressでGoogleのCustom Search APIを使ってみる - ponsuke_tarou’s blog
- 投稿日: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 にて公開しているので、宜しけば参考にしてください。
長くなりましたが、以上です。