- 投稿日:2019-09-15T23:08:09+09:00
LaravelにFont Awesome5を導入する
決まったコマンドと貼り付けるファイルがあるので、メモをしておきます。
ターミナル$ npm install @fortawesome/fontawesome-freeと打ちます。
上記のコマンドの結果、package.jsonに
package.json"@fortawesome/fontawesome-free": "^5.10.2", (バージョンはそれぞれ)と追加されているのを確認したら、
下記のコマンドでパスを指定し
_variables.scss$ fa-font-path: "../webfonts";scssファイルをインポートします。
app.scss@import "~@fortawesome/fontawesome-free/scss/fontawesome.scss"; @import "~@fortawesome/fontawesome-free/scss/solid.scss"; @import "~@fortawesome/fontawesome-free/scss/brands.scss";以上で完成です!
ターミナル$ npm run watchあとは監視!
- 投稿日:2019-09-15T20:56:34+09:00
Laravel6.0でログイン機能の実装メモ
- 投稿日:2019-09-15T18:56:37+09:00
EC2とAmazon Elasticsearch Serviceを利用するときにド嵌った
概要
AWSでEC2とElasticsearch Service
をつなげようと思ったら意外と嵌ってしまった話です。結構ec2上にelasticsearchを導入して利用している方は多いけど、Elasticsearch Serviceを立ててつなげる記事はあまりない、、、。
AWS EC2上にElasticSearchをインストール
AWSで怯えず作るEC2 + Amazon Elasticsearch Serviceでハマったところ多少ネットに転がっているのですが、今をときめくLaravelを利用した記事がなかったので書いている次第。
利用環境
インフラ編
- EC2(centOS7)
- elasticsearch Service
動作環境
- laravel5.8(php7.2)
- elasticsearch Service 6.7
- mysql8.0
- nginx1.17
ローカル開発環境
ローカル開発環境はdockerを利用してelasticsearchのコンテナを立てて開発をしています。
laravelを使っているので、せっかくなのでscoutを利用。ローカル上の.envの設定はこちら
.envSCOUT_DRIVER=elasticsearch ELASTICSEARCH_HOST=http://elasticsearch:9200ELASTICSEARCH_HOSTのエンドポイントだけ変更してあげればいいんじゃん!
とElasticsearch ServiceとEC2をつないでみる。
※Elasticsearch Serviceマネジメントコンソール画面
ec2からcurlで叩いたら正常に動いている反応がある!
ec2[centos@ip-***-**-**-* ****]$ curl https://エンドポイント.es.amazonaws.com { "name" : "WNwU_Bn", "cluster_name" : "*********:********", "cluster_uuid" : "*****************", "version" : { "number" : "6.7.0", "build_flavor" : "oss", "build_type" : "zip", "build_hash" : "b8dfb4c", "build_date" : "2019-05-22T06:01:36.852084Z", "build_snapshot" : false, "lucene_version" : "7.7.0", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search" }つながっているということは
.env(ローカル)SCOUT_DRIVER=elasticsearch ELASTICSEARCH_HOST=http://elasticsearch:9200を
.env(EC2)SCOUT_DRIVER=elasticsearch ELASTICSEARCH_HOST=エンドポイントのURLに.envを書き換えれば行けるはず!
php artisan migrate:refresh --seedが通らない!
.envファイルを書き換えても一向に
php artisan migrate:refresh --seed
が通らない!
RDSともつないでいてそちらは正常に動いているのですが、
テストデータを入れているシーディングがどうも通らない。UserTableSeeder.phpArtisan::call('scout:import', ["model" => User::class]);エラーがでる箇所。
エラーメッセージElasticsearch\Common\Exceptions\NoNodesAvailableException : No alive nodes found in your cluster調べて見ると、タイムアウトでこのエラーメッセージがでるからタイムアウトの時間を長くしてみてと書いてある
しかし、テストレコードは1レコードのみ。
タイムアウトになるはずもない。
相変わらずcurlは通る。
ローカル環境も問題なく動いている。
謎が深まり時間だけが過ぎていく、、、。立ちはだかっていたのはport番号
思い当たる節もなく、PCとにらめっこ。
vendor以下のソースコードをひたすら追っていると変な記述が。ClientBuilder.php/** * @throws InvalidArgumentException */ private function extractURIParts(string $host): array { $parts = parse_url($host); if ($parts === false) { throw new InvalidArgumentException("Could not parse URI"); } if (isset($parts['port']) !== true) { $parts['port'] = 9200; } return $parts; }あれ?
port番号書いてないと勝手に9200番につなぐようにしている?
いやいや、、、、流石に
https
ってついてたら443に繋ぐでしょ、、、、。と追ってみるもそんな処理なし。
つまり、
.env(EC2)SCOUT_DRIVER=elasticsearch ELASTICSEARCH_HOST=エンドポイントのURL:443とポート番号を記載する必要があるみたいです!
些細な設定ファイルで完全に嵌ってしまいました。
やっぱりちゃんとソースコードを追わないと行けないですね!
- 投稿日:2019-09-15T18:56:37+09:00
EC2上のlaravel5.8とAmazon Elasticsearch Serviceを利用するときにド嵌った
概要
AWSでEC2とElasticsearch Service
をつなげようと思ったら意外と嵌ってしまった話です。結構ec2上にelasticsearchを導入して利用している方は多いけど、Elasticsearch Serviceを立ててつなげる記事はあまりない、、、。
AWS EC2上にElasticSearchをインストール
AWSで怯えず作るEC2 + Amazon Elasticsearch Serviceでハマったところ多少ネットに転がっているのですが、今をときめくLaravelを利用した記事がなかったので書いている次第。
利用環境
インフラ編
- EC2(centOS7)
- elasticsearch Service
動作環境
- laravel5.8(php7.2)
- elasticsearch Service 6.7
- mysql8.0
- nginx1.17
ローカル開発環境
ローカル開発環境はdockerを利用してelasticsearchのコンテナを立てて開発をしています。
laravelを使っているので、せっかくなのでscoutを利用。ローカル上の.envの設定はこちら
.envSCOUT_DRIVER=elasticsearch ELASTICSEARCH_HOST=http://elasticsearch:9200ELASTICSEARCH_HOSTのエンドポイントだけ変更してあげればいいんじゃん!
とElasticsearch ServiceとEC2をつないでみる。
※Elasticsearch Serviceマネジメントコンソール画面
ec2からcurlで叩いたら正常に動いている反応がある!
ec2[centos@ip-***-**-**-* ****]$ curl https://エンドポイント.es.amazonaws.com { "name" : "WNwU_Bn", "cluster_name" : "*********:********", "cluster_uuid" : "*****************", "version" : { "number" : "6.7.0", "build_flavor" : "oss", "build_type" : "zip", "build_hash" : "b8dfb4c", "build_date" : "2019-05-22T06:01:36.852084Z", "build_snapshot" : false, "lucene_version" : "7.7.0", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search" }つながっているということは
.env(ローカル)SCOUT_DRIVER=elasticsearch ELASTICSEARCH_HOST=http://elasticsearch:9200を
.env(EC2)SCOUT_DRIVER=elasticsearch ELASTICSEARCH_HOST=エンドポイントのURLに.envを書き換えれば行けるはず!
laravelとElasticsearch Serviceが連動しない!
.envファイルを書き換えても一向に
php artisan migrate:refresh --seed
が通らない!
RDSともつないでいてそちらは正常に動いているのですが、
テストデータを入れているシーディングがどうも通らない。UserTableSeeder.phpArtisan::call('scout:import', ["model" => User::class]);エラーがでる箇所。
エラーメッセージElasticsearch\Common\Exceptions\NoNodesAvailableException : No alive nodes found in your cluster調べて見ると、タイムアウトでこのエラーメッセージがでるからタイムアウトの時間を長くしてみてと書いてある
しかし、テストレコードは1レコードのみ。
タイムアウトになるはずもない。
相変わらずcurlは通る。
ローカル環境も問題なく動いている。
謎が深まり時間だけが過ぎていく、、、。立ちはだかっていたのはport番号
思い当たる節もなく、PCとにらめっこ。
vendor以下のソースコードをひたすら追っていると変な記述が。ClientBuilder.php/** * @throws InvalidArgumentException */ private function extractURIParts(string $host): array { $parts = parse_url($host); if ($parts === false) { throw new InvalidArgumentException("Could not parse URI"); } if (isset($parts['port']) !== true) { $parts['port'] = 9200; } return $parts; }あれ?
port番号書いてないと勝手に9200番につなぐようにしている?
いやいや、、、、流石に
https
ってついてたら443に繋ぐでしょ、、、、。と追ってみるもそんな処理なし。
つまり、
.env(EC2)SCOUT_DRIVER=elasticsearch ELASTICSEARCH_HOST=エンドポイントのURL:443とポート番号を記載する必要があるみたいです!
些細な設定ファイルで完全に嵌ってしまいました。
やっぱりちゃんとソースコードを追わないと行けないですね!
- 投稿日:2019-09-15T18:33:31+09:00
未経験で新規案件でインターンして学んだこと(学生です)
どんな目的で記事を書いているか
・とにかくアドバイスが欲しいです(切実)
・技術がどう身についていったかの過程を示してエンジニアで人事をやっている方にいい情報が届くといいな
・今エンジニア目指してる人の就活のお助けにもなったらいいな自分のステータス
・理系大学院生
・開発経験なし、研究のためにプログラミングは少しやっていた
・触れたことある言語はjava,Ruby,ほんの少しC
・今開発で使っているのはphp(laravel),JavaScript(VueとJQuery)
・インターン初めて今ちょうど4ヶ月くらい何をどう学んだか
インターンで初めて本格的な環境開発した
インターンするまではDocker使ったことなかったです。もうなにも知らなかった。もちろんPHPも触ったことなし。
でも会社入ったら一番最初に環境開発して〜と言われて何もわからずめっちゃDocker勉強しました。開発の環境は先輩に教えてもらいながらなんとかできたけど、理解できていなかったからDockerの参考書使って自分で0から開発環境作ってみました。これでかなり理解深まった。
そして記念すべきQiita初投稿がこれ
Dockerでnginx+MySQL+Laravelの環境構築からプロジェクト作成・DBへのマイグレーションまでフレームワークってすごい
今までデータベースを扱ったことがなく、sql文とかも勉強しながら開発してたのですが、フレームワークというものがあったおかげでものすごく理解が早くなった。言語の中身・特徴理解する前にフレームワーク使うのはよくないと言われていますが、自分の場合はフレームワークから入って成長できたと思っています。とにかく実装できないことが恥ずかしいと思って必死に勉強した
これは環境にもよると思いますが、全く未経験なのにガンガンタスク振られて焦りました。できないです、とか間に合わなかったですを言いたくなくて死に物狂いで勉強しました。人のコード見て学べる、自分のコードのレビューがもらえる
サービス作ってない時は計算できていればいいやと思っていたものが、現場になると可読性や処理速度を気にするようになった。ただ動けば良いだけではないことを実感できた。コミュニケーションすごく大切
一人でやっていれば仕様に迷いはないですが、チームの開発だと要件にマッチした実装になっているかがすごく大事。「多分こうだろう」と思って実装したことが無駄になったことがあり、仕様の認識違いがないか都度確認することがとても重要だと学びました。使えるものは使いまくる
案件によるかもしれませんが、便利なライブラリはどんどん使うべきだと思います。最初は知らないものを使うことに抵抗がありましたが今では新しいものどんどん使いたくてたまらない!とうう感じになってます。その例がこの記事。
Lalavel ScoutでAlgoliaを使って全文検索を実装してみた!
ClosureTableを使って〇〇年のアニメを季節ごとに1テーブルで管理する公式ドキュメントは神!特に英語版!!!!!!
自分はとても英語が苦手だったのですが、物によっては英語のドキュメントしかない場合があります(Algoliaも英語しかない)。どうしても英語を読まなければいけない状況になってから、「英語の方がわかりやすい!」となりました。日本語にこだわらずになんでも英語で調べたり読んだりすると知識増えるなと思います。新しい情報に敏感になることが大事
ITの世界は多分他の業界より発展速度が速い。いつまでも昔のやり方にとらわれずにどんどん新しいもの使ってあわよくば自分が新しいツール開発するくらいの意気込みを持っていた方が良い。実年齢は年取っても技術の話では若くいたい!0から作ることは自信になる
まだリリースはしていないのですが、新規だからこそ今自分が作ったものがリリースされるのが楽しみで仕方ないです。ワクワクが止まらん!書籍は自分で持っていた方がいい
わからないことは調べれば出てくるという人もいますが、自分は本を買うことを勧めます。わからないことを調べているうちに別のことも学べるので絶対投資すべき!最後に
とにかくいろんな技術者の人と話していろんなことを吸収していきたいです。YYPHPいきたかったのに終わってしまたの悲しい。。。。。何かいいコミュニティあったら教えてください。
- 投稿日:2019-09-15T17:14:40+09:00
Laravel5.8でパスワードリセット処理(メール送信まで)を追ってみた
前提
- Laravel 5.8.31
- make::auth済み
- migrate済み
リセットフォームの表示
make:auth
するとweb.php
にAuth::routes()
が追加されます。web.php<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); Auth::routes(); Route::get('/home', 'HomeController@index')->name('home');
Auth::routes();
はFacadeで、実際には下記のようにaliaseとして定義されています。
laravel/framework/src/Illuminate/Support/Facades/Auth.php
内でRouterクラスのauthメソッドを呼ぶようにしています。
詳しくはこちらconfig/app.phpreturn [ ~中略~ 'aliases' => [ 'App' => Illuminate\Support\Facades\App::class, 'Arr' => Illuminate\Support\Arr::class, 'Artisan' => Illuminate\Support\Facades\Artisan::class, 'Auth' => Illuminate\Support\Facades\Auth::class,←こいつ 'Blade' => Illuminate\Support\Facades\Blade::class, ~中略~vendor/laravel/framework/src/Illuminate/Support/Facades/Auth.phpclass Auth extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'auth'; } /** * Register the typical authentication routes for an application. * * @param array $options * @return void */ public static function routes(array $options = []) { static::$app->make('router')->auth($options); } }vendor/laravel/framework/src/Illuminate/Routing/Router.phpclass Router implements RegistrarContract, BindingRegistrar { ~中略~ /** * Register the typical authentication routes for an application. * * @param array $options * @return void */ public function auth(array $options = []) { // Authentication Routes... $this->get('login', 'Auth\LoginController@showLoginForm')->name('login'); $this->post('login', 'Auth\LoginController@login'); $this->post('logout', 'Auth\LoginController@logout')->name('logout'); // Registration Routes... if ($options['register'] ?? true) { $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register'); $this->post('register', 'Auth\RegisterController@register'); } // Password Reset Routes... if ($options['reset'] ?? true) { $this->resetPassword(); } // Email Verification Routes... if ($options['verify'] ?? false) { $this->emailVerification(); } } /** * Register the typical reset password routes for an application. * * @return void */ public function resetPassword() { $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request'); $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email'); $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset'); $this->post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update'); } /** * Register the typical email verification routes for an application. * * @return void */ public function emailVerification() { $this->get('email/verify', 'Auth\VerificationController@show')->name('verification.notice'); $this->get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify'); $this->get('email/resend', 'Auth\VerificationController@resend')->name('verification.resend'); }null合体演算子でoptionの有無でルーティングを決めたりしてますが、パスワードリセットはoptionなしではtrueなのでルーティングに含まれています。
ここから、フォームの表示は
ForgotPasswordController
のshowLinkRequestForm
で行い
フォームの送信はForgotPasswordController
のsendResetLinkEmail
で行っていることがわかります。ForgotPasswordController.php<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\SendsPasswordResetEmails; class ForgotPasswordController extends Controller { /* |-------------------------------------------------------------------------- | Password Reset Controller |-------------------------------------------------------------------------- | | This controller is responsible for handling password reset emails and | includes a trait which assists in sending these notifications from | your application to your users. Feel free to explore this trait. | */ use SendsPasswordResetEmails; /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('guest'); } }これしか記載がありません。
実際はSendsPasswordResetEmails
に色々書かれており、カスタマイズする場合showLinkRequestFormをオーバーライドして色々してきますが、デフォルトだとSendsPasswordResetEmails
に書かれているので見ていきます。vendor/laravel/framework/src/Illuminate/Foundation/Auth/SendsPasswordResetEmails.php<?php namespace Illuminate\Foundation\Auth; use Illuminate\Http\Request; use Illuminate\Support\Facades\Password; trait SendsPasswordResetEmails { /** * Display the form to request a password reset link. * * @return \Illuminate\Http\Response */ public function showLinkRequestForm() { return view('auth.passwords.email'); } /** * Send a reset link to the given user. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse */ public function sendResetLinkEmail(Request $request) { $this->validateEmail($request); // We will send the password reset link to this user. Once we have attempted // to send the link, we will examine the response then see the message we // need to show to the user. Finally, we'll send out a proper response. $response = $this->broker()->sendResetLink( $this->credentials($request) ); return $response == Password::RESET_LINK_SENT ? $this->sendResetLinkResponse($request, $response) : $this->sendResetLinkFailedResponse($request, $response); } /** * Validate the email for the given request. * * @param \Illuminate\Http\Request $request * @return void */ protected function validateEmail(Request $request) { $request->validate(['email' => 'required|email']); } /** * Get the needed authentication credentials from the request. * * @param \Illuminate\Http\Request $request * @return array */ protected function credentials(Request $request) { return $request->only('email'); } /** * Get the response for a successful password reset link. * * @param \Illuminate\Http\Request $request * @param string $response * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse */ protected function sendResetLinkResponse(Request $request, $response) { return back()->with('status', trans($response)); } /** * Get the response for a failed password reset link. * * @param \Illuminate\Http\Request $request * @param string $response * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse */ protected function sendResetLinkFailedResponse(Request $request, $response) { return back() ->withInput($request->only('email')) ->withErrors(['email' => trans($response)]); } /** * Get the broker to be used during password reset. * * @return \Illuminate\Contracts\Auth\PasswordBroker */ public function broker() { return Password::broker(); } }
showLinkRequestForm
が定義されており、viewとしてauth/passwords/email.balade.php
返却されている事がわかります。メールアドレスを入力し、メール送信
次にフォームにメールアドレスを入力し、postすると
ForgotPasswordController
のsendResetLinkEmail
が呼ばれるのですが、こちらもデフォルトだとないので、結局SendsPasswordResetEmails
のsendResetLinkEmail
が呼ばれます。vendor/laravel/framework/src/Illuminate/Foundation/Auth/SendsPasswordResetEmails.php/** * Send a reset link to the given user. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse */ public function sendResetLinkEmail(Request $request) { $this->validateEmail($request); // We will send the password reset link to this user. Once we have attempted // to send the link, we will examine the response then see the message we // need to show to the user. Finally, we'll send out a proper response. $response = $this->broker()->sendResetLink( $this->credentials($request) ); return $response == Password::RESET_LINK_SENT ? $this->sendResetLinkResponse($request, $response) : $this->sendResetLinkFailedResponse($request, $response); }
$this->validateEmail($request);
で必須チェック、メールアドレス形式かバリデーションをしています。$response = $this->broker()->sendResetLink( $this->credentials($request) );
$this->credentials($request);
でリクエストから、
$this->broker()
ではbrokerメソッドでPasswordのFacadeを使ってパスワードリセットに使用するブローカーを返却しています。
vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBroker.php
のsendResetLink
が呼ばれますvendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBroker.php<?php namespace Illuminate\Auth\Passwords; use Closure; use Illuminate\Support\Arr; use UnexpectedValueException; use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Auth\PasswordBroker as PasswordBrokerContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; class PasswordBroker implements PasswordBrokerContract { /** * The password token repository. * * @var \Illuminate\Auth\Passwords\TokenRepositoryInterface */ protected $tokens; /** * The user provider implementation. * * @var \Illuminate\Contracts\Auth\UserProvider */ protected $users; /** * The custom password validator callback. * * @var \Closure */ protected $passwordValidator; /** * Create a new password broker instance. * * @param \Illuminate\Auth\Passwords\TokenRepositoryInterface $tokens * @param \Illuminate\Contracts\Auth\UserProvider $users * @return void */ public function __construct(TokenRepositoryInterface $tokens, UserProvider $users) { $this->users = $users; $this->tokens = $tokens; } /** * Send a password reset link to a user. * * @param array $credentials * @return string */ public function sendResetLink(array $credentials) { // First we will check to see if we found a user at the given credentials and // if we did not we will redirect back to this current URI with a piece of // "flash" data in the session to indicate to the developers the errors. $user = $this->getUser($credentials); if (is_null($user)) { return static::INVALID_USER; } // Once we have the reset token, we are ready to send the message out to this // user with a link to reset their password. We will then redirect back to // the current URI having nothing set in the session to indicate errors. $user->sendPasswordResetNotification( $this->tokens->create($user) ); return static::RESET_LINK_SENT; } /** * Reset the password for the given token. * * @param array $credentials * @param \Closure $callback * @return mixed */ public function reset(array $credentials, Closure $callback) { // If the responses from the validate method is not a user instance, we will // assume that it is a redirect and simply return it from this method and // the user is properly redirected having an error message on the post. $user = $this->validateReset($credentials); if (! $user instanceof CanResetPasswordContract) { return $user; } $password = $credentials['password']; // Once the reset has been validated, we'll call the given callback with the // new password. This gives the user an opportunity to store the password // in their persistent storage. Then we'll delete the token and return. $callback($user, $password); $this->tokens->delete($user); return static::PASSWORD_RESET; } /** * Validate a password reset for the given credentials. * * @param array $credentials * @return \Illuminate\Contracts\Auth\CanResetPassword|string */ protected function validateReset(array $credentials) { if (is_null($user = $this->getUser($credentials))) { return static::INVALID_USER; } if (! $this->validateNewPassword($credentials)) { return static::INVALID_PASSWORD; } if (! $this->tokens->exists($user, $credentials['token'])) { return static::INVALID_TOKEN; } return $user; } /** * Set a custom password validator. * * @param \Closure $callback * @return void */ public function validator(Closure $callback) { $this->passwordValidator = $callback; } /** * Determine if the passwords match for the request. * * @param array $credentials * @return bool */ public function validateNewPassword(array $credentials) { if (isset($this->passwordValidator)) { [$password, $confirm] = [ $credentials['password'], $credentials['password_confirmation'], ]; return call_user_func( $this->passwordValidator, $credentials ) && $password === $confirm; } return $this->validatePasswordWithDefaults($credentials); } /** * Determine if the passwords are valid for the request. * * @param array $credentials * @return bool */ protected function validatePasswordWithDefaults(array $credentials) { [$password, $confirm] = [ $credentials['password'], $credentials['password_confirmation'], ]; return $password === $confirm && mb_strlen($password) >= 8; } /** * Get the user for the given credentials. * * @param array $credentials * @return \Illuminate\Contracts\Auth\CanResetPassword|null * * @throws \UnexpectedValueException */ public function getUser(array $credentials) { $credentials = Arr::except($credentials, ['token']); $user = $this->users->retrieveByCredentials($credentials); if ($user && ! $user instanceof CanResetPasswordContract) { throw new UnexpectedValueException('User must implement CanResetPassword interface.'); } return $user; } /** * Create a new password reset token for the given user. * * @param \Illuminate\Contracts\Auth\CanResetPassword $user * @return string */ public function createToken(CanResetPasswordContract $user) { return $this->tokens->create($user); } /** * Delete password reset tokens of the given user. * * @param \Illuminate\Contracts\Auth\CanResetPassword $user * @return void */ public function deleteToken(CanResetPasswordContract $user) { $this->tokens->delete($user); } /** * Validate the given password reset token. * * @param \Illuminate\Contracts\Auth\CanResetPassword $user * @param string $token * @return bool */ public function tokenExists(CanResetPasswordContract $user, $token) { return $this->tokens->exists($user, $token); } /** * Get the password reset token repository implementation. * * @return \Illuminate\Auth\Passwords\TokenRepositoryInterface */ public function getRepository() { return $this->tokens; } }上記のこの部分ですね。
/** * Send a password reset link to a user. * * @param array $credentials * @return string */ public function sendResetLink(array $credentials) { // First we will check to see if we found a user at the given credentials and // if we did not we will redirect back to this current URI with a piece of // "flash" data in the session to indicate to the developers the errors. $user = $this->getUser($credentials); if (is_null($user)) { return static::INVALID_USER; } // Once we have the reset token, we are ready to send the message out to this // user with a link to reset their password. We will then redirect back to // the current URI having nothing set in the session to indicate errors. $user->sendPasswordResetNotification( $this->tokens->create($user) ); return static::RESET_LINK_SENT; }
$user = $this->getUser($credentials);
で$credentials
に与えられたもので、userを取りに行きます。デフォルトだとemailなので、emailでユーザーを取得しています。ユーザーがいた場合、App\Userインスタンスが返されます。
UserはAuthenticatable(Illuminate\Foundation\Auth\User)
を継承していますApp\Use.php<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Notifiable; ~中略~Authenticatableの中では
Illuminate\Contracts\Auth\CanResetPassword
というinterfaceを/vendor/laravel/framework/src/Illuminate/Auth/Passwords/CanResetPassword.php
というtraitをuseすることで実装していますvendor/laravel/framework/src/Illuminate/Foundation/Auth/User.php<?php namespace Illuminate\Foundation\Auth; use Illuminate\Auth\Authenticatable; use Illuminate\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Model; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail; }vendor/laravel/framework/src/Illuminate/Auth/Passwords/CanResetPassword.php<?php namespace Illuminate\Auth\Passwords; use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification; trait CanResetPassword { /** * Get the e-mail address where password reset links are sent. * * @return string */ public function getEmailForPasswordReset() { return $this->email; } /** * Send the password reset notification. * * @param string $token * @return void */ public function sendPasswordResetNotification($token) { $this->notify(new ResetPasswordNotification($token)); } }つまり、戻ると、この処理では
$user->sendPasswordResetNotification( $this->tokens->create($user) );
vendor/laravel/framework/src/Illuminate/Auth/Passwords/CanResetPassword.php
のsendPasswordResetNotification
が呼ばれています。引数として、
TokenRepositoryInterface.php
の実装のvendor/laravel/framework/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php
のcreateが呼ばれます。vendor/laravel/framework/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php/** * Create a new token record. * * @param \Illuminate\Contracts\Auth\CanResetPassword $user * @return string */ public function create(CanResetPasswordContract $user) { $email = $user->getEmailForPasswordReset(); $this->deleteExisting($user); // We will create a new, random token for the user so that we can e-mail them // a safe link to the password reset form. Then we will insert a record in // the database so that we can verify the token within the actual reset. $token = $this->createNewToken(); $this->getTable()->insert($this->getPayload($email, $token)); return $token; }
getEmailForPasswordReset
でメールアドレスを取得し、$this->deleteExisting($user);
でpassword_reset
テーブルに既に登録されてたら削除し、$token = $this->createNewToken();
でhash済みのtoken生成後に、再度登録しています。テーブルに保存したら、tokenを返却しています。
こいつが下記の引数になっています。
$user->sendPasswordResetNotification( $this->tokens->create($user) );vendor/laravel/framework/src/Illuminate/Auth/Passwords/CanResetPassword.php/** * Send the password reset notification. * * @param string $token * @return void */ public function sendPasswordResetNotification($token) { $this->notify(new ResetPasswordNotification($token)); }tokenを渡し、実際のメール送信は
Notification
で行っています。
action
はボタンを作ってくれるみたいなので、そこにtokenを連結したものでパスワードリセット画面へのurlを作ってる感じですかねvendor/laravel/framework/src/Illuminate/Auth/Notifications/ResetPassword.php/** * Build the mail representation of the notification. * * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) { if (static::$toMailCallback) { return call_user_func(static::$toMailCallback, $notifiable, $this->token); } return (new MailMessage) ->subject(Lang::getFromJson('Reset Password Notification')) ->line(Lang::getFromJson('You are receiving this email because we received a password reset request for your account.')) ->action(Lang::getFromJson('Reset Password'), url(config('app.url').route('password.reset', ['token' => $this->token, 'email' => $notifiable->getEmailForPasswordReset()], false))) ->line(Lang::getFromJson('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.users.expire')])) ->line(Lang::getFromJson('If you did not request a password reset, no further action is required.')); }あとは
ResetPasswordController@showResetForm
に続くって感じでしょうFWってすごい(小並感)
お疲れさまでした
今回基本ググらず、追っていたので
ミスや訂正がありましたら、ご指摘いただけると幸いです。書き終わってから調べたら、わかりやすい記事がありました...
いい勉強になりました...
- 投稿日:2019-09-15T12:19:43+09:00
laravel6.0でのsubQueryの書き方(+subQuery vs EagerLoadingについて)
はじめに
laravel6.0が出て数日たちましたね。
今までsubQueryどころかEagerLoadingすらも面倒だからいいやとサボっていた人間なので、
この機会に少しまとめておきたいと思います。laravel6.0でのsubQuery
今usersとbooksがあるとして
function ($query) { $query->select("name")->from("books")->whereColumn("user_id", "users.id") ->limit(1); }これをaddSelectや、orderByに入れるだけです。
$users = User::addSelect(["book_name" => function ($query) { $query->select("name")->from("books")->whereColumn("user_id", "users.id") ->limit(1); }])->get(); foreach ($users as $user) { $name = $user->book_name; }こんな感じです。
何もしない vs EagerLoading vs subQuery
controllerを作ってそこにアクセスして比較していきます。
SampleController.phpclass SampleController extends Controller { public function index() { $start = microtime(true); $memory = memory_get_usage(); $this->getBooks(); $result = [ "time" => microtime(true) - $start, "memory" => (memory_get_peak_usage() - $memory) / (1024 * 1024) ]; dump($result); } public function getBooks() { //optionalはuserに対応するbookが必ず存在するとは限らないためつけてます。 //通常 $users = User::all(); foreach ($users as $user) { $name = optional($user->books->first())->name; } //EagerLoading $users = User::with("books")->get(); foreach ($users as $user) { //リレーションメソッドにしてしまうとeagerloadingの効果が失われてしまうので気を付けて $name = optional($user->books->first())->name; } //subQuery //addSelectによりusersのすべてと、book_nameが選択されている状態 $users = User::addSelect(["book_name" => function ($query) { $query->select("name")->from("books")->whereColumn("user_id", "users.id") ->limit(1); }])->get(); foreach ($users as $user) { $name = $user->book_name; } } }user 1000件 book 1000件の場合
method time memory 通常 2.1359429359436 4.7769012451172 EagerLoading 0.242516040802 4.222671508789 subQuery 0.46214890480042 3.937858581543 user 3000件 book 3000件の場合
method time memory 通常 8.952919960022 13.542144775391 EagerLoading 0.79955101013184 11.902877807617 subQuery 3.0595591068268 10.085777282715 user 5000件 book 5000件の場合
method time memory 通常 19.71000289917 28.348297119141 EagerLoading 1.9218430519104 25.727615356445 subQuery 6.256618976593 16.407073974609 user 10000件 book 10000件の場合
通常は60秒を超えてtimeoutでした。
method time memory 通常 EagerLoading 3.9436728954315 50.53173828125 subQuery 24.066353082657 31.92147064209 まとめ
上の結果だけ見るとEagerLoading一択みたいに見えるんですけど、
書き方や計測方法が悪いだけなんですかね・・・?
memory的にはやっぱりsubQueryがいい感じなんだけども・・・
おそらくどこか間違っていると思うので詳しい方いたら指摘していただければ助かります!
- 投稿日:2019-09-15T09:06:35+09:00
laravelのdump-serverが使えなくなってしまった時
composerから
composer require --dev beyondcode/laravel-dump-server
で解決します。