20210611のPHPに関する記事は4件です。

PHP8で追加されたJITと、PHP OPcacheについて徹底解説!「悪りぃが、こっから先は一方通行だ」

はじめに PHPerの皆さんこんにちは。一方通行と申します。 皆さんは去年(2020年)の11月にPHP 8がリリースされたのは既知のことかとは思いますが、 PHP 8の最も目玉である機能に「JIT (ジャストインタイム) コンパイル」というものがあります。 JITをPHPアプリケーションに導入しようか検討する際にJITが内部的に何を行っているのかきちんと理解していなければ導入出来ないと思い、 別の高速化アプローチであるOPcacheと合わせて、解説します。 雰囲気は知っているよという方や、OPcache is 何?という方々はぜひご参考にしてください。 前提知識 まずですが、インタプリタ言語がマシン上でどのように実行されているかおさらいしましょう。 大まかな流れは下図のとおりとなります。 このようにソースコードを元に機械語まで変換するプログラムをインタプリタと呼びます。 PHP及びRubyやPythonのインタプリタはC言語で書かれています。 これらを踏まえた上で、説明していきます。 PHPの実行フローをおさらい PHPの実行フローは下図のとおりです。 ソースコードからOpcodeへ変換し、変換されたOpcodeをZend Engineが解釈し機械語へ変換していきます。 Opcode is 何? ここで、新たにOpcodeという単語が出てきましたが、これは最初の図にあるバイトコードと同義です。 Opcodeとは仮想マシン(Zend VM)で実行できる疑似命令で、Opcodeは最終的に配列になり順次VMを介して実行されていきます。 以下のtest.phpをDebugした結果が、更に下のterminalのキャプチャで、 0000~0004までがOpcodeとなります。 test.php <?php $accelerator = '悪りぃが、'; echo $accelerator . 'こっから先は一方通行だ'; echo PHP_EOL; Opcodeの処理内容を確認するにはここから確認できます。 (※ LinkはZend Engine 2のものです) Zend Engine is 何? Zend Engine(VM)はソースコードを中間表現(Opcode)に変換し、中間表現を実行します。 実行の度にソースコードを解析し機械語にコンパイルしていくより、 一旦ファイル単位でOpcodeへ変換しそれを実行する方がより高速でありリソース管理の面から優れているためZend VMが導入されました。 php -vを実行した際にZend Engineのバージョンとコピーライトが出てくるかと思いますがそれです。 OPcache is 何? Opcodeを共有メモリに保存しておくことを指します。 つまり、OPcacheはPHPのアクセラレータと言えます。 図に表すと以下のとおりです。 JIT is 何? 厳密な意味合いを言うと、VMの「バイトコード」を「ネイティブコード(機械語)」にコンパイルすることを指します。 そしてJITはコンパイルの際、ネイティブコード(機械語)のキャッシュを行います。 キャッシュが存在する場合はZend VMを挟む必要がなくなり、プロセッサレベルの命令として直接機械語の実行が可能となります。 PHP JITの種類 Function JIT Function JITは関数単位でJITコンパイルが行われます。 問題は関数単位で全てJITコンパイルするため、場合によってはパフォーマンスが低下する恐れがあるということです。 Tracing JIT Tracing JITはランタイム情報を含んだスタックトレースを作成し、トレースの回数や内容を元にJITコンパイルするかを判別してくれます。(ホットコードを識別して最適化) カスタマイズ 4桁の整数値 CRTO を入力することで、JITのトリガーや最適化レベルを変更できます。 詳しくはこちらを参照ください。 あとがき PHP 8で導入されたJITは、簡素に説明してしまえば「OPcacheより高速化が見込めるキャッシュ機能」と言えるわけですが、 実際はOPcacheとは切り離して考えることは出来ず、インタプリタの実行フローを把握していないと全貌を捉えることが出来ませんでした。 また、記事執筆にあたり新たな発見があり自分的にも書いてよかったと思っています。 PHPは言語仕様がちょっとアレなところはありますが、なんでも取り込む貪欲な姿勢からは今後も目が話せませんね。 ここまで読んで頂きありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel7.系でマルチログイン機能を実装する。

【Laravel7.系でマルチログイン機能を実装する。】 プロジェクトにて、複数でのログイン機能が必要になり、色々悪戦苦闘しながらやっとこさで作成できました。もし皆様も必要になった場合はよければご参考にしてみてください。 ■追記しました。2021/06/11 新規登録のコントローラーファイル・パスワードリセットのコントローラーなど一部記述が足りていなかったので、追記しました。 ■マルチ認証システム(複数のユーザーレベルでのログイン機能) 〇参考サイト様 大変参考になりました。ありがとうございます。 Laravel 7.xでマルチ認証機能を作成してみた(ほぼ以下のサイトを基本に実装した) 〇手順 △0.本手順の前提 Lataravelでコマンドを使い、ログイン機能を作成済み。 本手順にて、複数のログイン機能を実装するが、例として、「Admin」という名称にて実装していく。(Masterとか別の名前で作成したい場合は適宜、ファイル名やテーブル、namespaceなど変更すること) △1.モデルファイルとマイグレーションファイルを作成 以下コマンドで、Modelとmigrationを作成する。 $ php artisan make:model Models/Admin -m ◇モデルファイル側 以下をコピー&ペーストする。 app/Models/Admin.php <?php namespace App\Models; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use App\Notifications\Admin\PasswordResetNotification; class Admin extends Authenticatable { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'email_verified_at' => 'datetime', ]; /** * パスワードリセット通知の送信 * * @param string $token * @return void */ public function sendPasswordResetNotification($token) { $this->notify(new PasswordResetNotification($token)); } } ◇マイグレーションファイル側 以下をコピー&ペーストする。 database/migrations/「生成されたマイグレーションファイル」.php <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateAdminsTable extends Migration { public function up() { Schema::create('admins', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email',191)->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } public function down() { Schema::dropIfExists('admins'); } } △2.マイグレーションファイルを実行する 作成したマイグレーションファイルを実行して、テーブルを作成する。 $ php artisan migrate △3.auth.phpにAdminの定義を追加 auth.phpにAdminの情報を追加する。 config/auth.php <?php return [ /* |-------------------------------------------------------------------------- | Authentication Defaults |-------------------------------------------------------------------------- | | This option controls the default authentication "guard" and password | reset options for your application. You may change these defaults | as required, but they're a perfect start for most applications. | */ 'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ], /* |-------------------------------------------------------------------------- | Authentication Guards |-------------------------------------------------------------------------- | | Next, you may define every authentication guard for your application. | Of course, a great default configuration has been defined for you | here which uses session storage and the Eloquent user provider. | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | Supported: "session", "token" | */ 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'admin' => [ // adminの定義を追加 'driver' => 'session', 'provider' => 'admins', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', 'hash' => false, ], ], /* |-------------------------------------------------------------------------- | User Providers |-------------------------------------------------------------------------- | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | If you have multiple user tables or models you may configure multiple | sources which represent each model / table. These sources may then | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" | */ 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, ], 'admins' => [ // adminの定義を追加 'driver' => 'eloquent', 'model' => App\Models\Admin::class, ], ], /* |-------------------------------------------------------------------------- | Resetting Passwords |-------------------------------------------------------------------------- | | You may specify multiple password reset configurations if you have more | than one user table or model in the application and you want to have | separate password reset settings based on the specific user types. | | The expire time is the number of minutes that the reset token should be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | */ 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, ], 'admins' => [ // 追加 'provider' => 'admins', 'table' => 'admin_password_resets', // さきほど追加作成したテーブルを指定 'expire' => 60, 'throttle' => 60, ] ], /* |-------------------------------------------------------------------------- | Password Confirmation Timeout |-------------------------------------------------------------------------- | | Here you may define the amount of seconds before a password confirmation | times out and the user is prompted to re-enter their password via the | confirmation screen. By default, the timeout lasts for three hours. | */ 'password_timeout' => 10800, ]; △4.RouteServiceProviderにAdmin用のパスを追加 RouteServiceProvider.phpにAdminのパスとして、ADMIN_HOMEを追加を追加する。 app/Providers/RouteServiceProvider.php すでにコマンドにログイン機能が実装されているならば、以下の、 「public const HOME = '/home';」を目印に、以下のように一行追加をする。 public const HOME = '/home'; public const ADMIN_HOME = '/admin/home'; // 管理者用のパスを追加 △5.Authenticateに未ログイン時の挙動を書く Authenticate.phpに以下の内容をコピー&ペーストする。 app/Http/Middleware/Authenticate.php <?php namespace App\Http\Middleware; use Illuminate\Auth\Middleware\Authenticate as Middleware; use Illuminate\Support\Facades\Route; class Authenticate extends Middleware { /** * Get the path the user should be redirected to when they are not authenticated. * * @param \Illuminate\Http\Request $request * @return string|null */ protected function redirectTo($request) { if (! $request->expectsJson()) { if (Route::is('admin.*')) { // admin からのRouteの分岐を追加 return route('admin.login'); } else { return route('login'); } } } } △6.RedirectIfAuthenticatedにログイン時の挙動を書く RedirectIfAuthenticated.phpに以下の内容をコピー&ペーストする。 app/Http/Middleware/RedirectIfAuthenticated.php use App\Providers\RouteServiceProvider; use Closure; use Illuminate\Support\Facades\Auth; class RedirectIfAuthenticated { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) { // if (Auth::guard($guard)->check()) { // return redirect(RouteServiceProvider::HOME); // } if (Auth::guard($guard)->check() && $guard === 'admin') { // guardがadminかそれ以外かで分岐させる return redirect(RouteServiceProvider::ADMIN_HOME); } elseif (Auth::guard($guard)->check()) { return redirect(RouteServiceProvider::HOME); } return $next($request); } } △7.Controllerを追加 まずは、コマンドにて、コントローラーファイルを作成する。 そして、HomeController.phpに以下の内容をコピー&ペーストする。 ◇HomeController app/Http/Controllers/Admin/HomeController.php <?php namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class HomeController extends Controller { public function index() { return view('admin.home'); } } ◇LoginController app/Http/Controllers/Admin/LoginController.php <?php namespace App\Http\Controllers\Admin\Auth; use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use App\Models\Admin; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class LoginController extends Controller { use AuthenticatesUsers; protected $redirectTo = RouteServiceProvider::ADMIN_HOME; protected function guard() { return Auth::guard('admin'); } public function showLoginForm() { //dd(Auth::check()); if (!Auth::guard('admin')->check()) { return view('admin.auth.login'); //return redirect( route('admin.login') ); } else { //return view('admin.home'); return redirect( route('admin.home') ); } } public function logout(Request $request) { Auth::guard('admin')->logout(); return redirect(route('admin.login')); } } ◇RegisterController アカウント新規登録の際に使用されるクラスファイル。 Validatorクラスの 'email' => ['required', 'string', 'email', 'max:255', 'unique:admins'], の部分の 'unique:admins' をテーブル名に変更することを忘れてはいけない。 忘れてデフォルトのまま(users)にしてしまうと、常にusersテーブルの内容と重複しないかをチェックしてしまうことになるため注意。 app/Http/Controllers/Admin/RegisterController.php <?php namespace App\Http\Controllers\Admin\Auth; use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use App\Models\Admin; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Auth; class RegisterController extends Controller { use RegistersUsers; // 管理者用のホーム画面へのリダイレクト protected $redirectTo = RouteServiceProvider::ADMIN_HOME; // 管理者用のguardを指定 protected function guard() { return Auth::guard('admin'); } // 管理者ユーザ登録用のviewを指定 public function showRegistrationForm() { return view('admin.auth.register'); } // 登録時のバリデーション protected function validator(array $data) { return Validator::make($data, [ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:admins'], 'password' => ['required', 'string', 'min:8', 'confirmed'], ]); } // 登録 protected function create(array $data) { // 管理者ユーザ用のAdmin Modelを指定 return Admin::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), ]); } } ◇ResetPasswordController パスワードリセットの際に使用されるクラスファイル。 app/Http/Controllers/Admin/ResetPasswordController.php <?php namespace App\Http\Controllers\Admin\Auth; use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use App\Models\Admin; use Illuminate\Http\Request; use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\Auth; class ResetPasswordController extends Controller { use ResetsPasswords; protected $redirectTo = RouteServiceProvider::ADMIN_HOME; // 管理者用のguardを指定 protected function guard() { return Auth::guard('admin'); } public function showResetForm(Request $request, $token = null) { // 管理者ユーザ用のviewを指定 return view('admin.auth.passwords.reset')->with( ['token' => $token, 'email' => $request->email] ); } public function broker() { // 管理者ユーザ用のパスワードブローカーを指定 return Password::broker('admins'); } } △8.ルーティング設定 Admin用のルーティング設定をする。 routes/web.php Auth::routes(); //これはコマンドにて作成したログイン機能用なのでそのままにしておく。 // 管理者用のルーティング Route::namespace('Admin')->prefix('admin')->name('admin.')->group(function () { Auth::routes([ 'register' => true, 'reset' => true, 'verify' => false ]); // 管理者のhome(認証後は/admin/homeへ) Route::middleware('auth:admin')->group(function () { Route::get('home', 'HomeController@index')->name('home'); }); }); △9.Admin用のviewを追加する すでに作成済み(コマンドにてログイン機能を作成している場合は)の、home.blade.phpとauth/login.blade.phpをコピーしてadminにそれぞれ同じような構成で作る。 layoutsもAdmin用で作成。 ※基本的に丸ごとコピーで大丈夫だと思いますが、 CSSやJSファイルなど必要な場合は別途記述を追加してください。 また、HTMLタグの構造やifの判定なども皆様の実装したい機能に応じて変更してもらってOKです。 ◇resources/views/layouts/admin/app.blade.php resources/views/layouts/admin/app.blade.php <!doctype html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel') }}</title> <!-- Fonts --> <link rel="dns-prefetch" href="//fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet"> </head> <body> <div id="app"> <header class="gheader"> <div class="logo"> <h1>LOGO</h1> </div> </header> @if (in_array(\Request::route()->getName(), ['admin.home'])) <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm"> <div class="container"> <a class="navbar-brand" href="{{ url('/') }}"> {{ config('app.name', 'Laravel') }} </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <!-- Left Side Of Navbar --> <ul class="navbar-nav mr-auto"> </ul> <!-- Right Side Of Navbar --> <ul class="navbar-nav ml-auto"> <!-- Authentication Links --> @unless (Auth::guard('admin')->check()) <li class="nav-item"> <a class="nav-link" href="{{ route('admin.login') }}">{{ __('Login') }}</a> </li> @if (Route::has('register')) <li class="nav-item"> <a class="nav-link" href="{{ route('admin.register') }}">{{ __('Register') }}</a> </li> @endif @else <li class="nav-item dropdown"> <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre> <span class="caret"></span> </a> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> <a class="dropdown-item" href="{{ route('admin.logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> {{ __('Logout') }} </a> <form id="logout-form" action="{{ route('admin.logout') }}" method="POST" style="display: none;"> @csrf </form> </div> </li> @endunless </ul> </div> </div> </nav> @endif <main> @yield('content') </main> </div> </body> </html> ◇resources/views/admin/auth/login.blade.php resources/views/admin/auth/login.blade.php ※{{ __('E-Mail Address') }}などの部分も適宜文字列を変更してもらってOKです。 こういう設定は、resources/lang/ja.jsonというファイルで変更できるはずです。 @extends('layouts.admin.app') @section('content') <section class="c-section"> <div class="login-screen"> <form method="POST" action="{{ route('admin.login') }}"> @csrf <div class="form-group row"> <label for="email" class="form-label">{{ __('E-Mail Address') }}</label> <div class="col-md-6"> <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" placeholder="abc@〇〇〇〇.jp" autofocus> @error('email') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row"> <label for="password" class="form-label">{{ __('Password') }}</label> <div class="col-md-6"> <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password" placeholder="●●●●●●"> @error('password') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row" style="display: none;"> <div class="col-md-6 offset-md-4"> <div class="form-check"> <input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}> <label class="form-check-label" for="remember"> {{ __('Remember Me') }} </label> </div> </div> </div> <div class="form-group submit-area"> <div class="btn-link"> <button type="submit" class="c-btn"> <span class="c-btn__body"> {{ __('AdminLogin') }}</span> </button> </div> @if (Route::has('admin.password.request')) <div class="password-request"> <a href="{{ route('admin.password.request') }}"> {{ __('Forgot Your Password?') }} </a> </div> @endif <div class="direction-link"> <a href="{{ route('admin.register') }}">{{ __('CreateAccount') }}</a> </div> </div> </form> </div> </section> @endsection ◇resources/views/admin/auth/register.blade.php @extends('layouts.admin.app') @section('content') <section class="c-section"> <div class="login-screen"> <form class="form" method="POST" action="{{ route('admin.register') }}"> @csrf @if ($errors->any()) <span class="c-invalid-feedback" role="alert"> @foreach ($errors->all() as $error) <strong>{{ $error }}</strong> @endforeach </span> @endif <div class="form-group"> <label for="name" class="form-label">{{ __('AccountName') }}</label> <div> <input id="name" type="text" placeholder="〇〇〇〇" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus> @error('name') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group"> <label for="email" class="form-label">{{ __('E-Mail Address') }}</label> <div> <input id="email" type="email" placeholder="abc@〇〇〇〇.jp" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email"> @error('email') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group"> <label for="password" class="form-label">{{ __('Password') }}</label> <div> <input id="password" type="password" placeholder="●●●●●●" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password"> @error('password') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group"> <label for="password-confirm" class="form-label">{{ __('Confirm Password') }}</label> <div> <input id="password-confirm" type="password" placeholder="●●●●●●" class="form-control" name="password_confirmation" required autocomplete="new-password"> </div> </div> <div class="form-group submit-area"> <div class="btn-link"> <button type="submit" class="c-btn"> <span class="c-btn__body"> {{ __('Create') }}</span> </button> </div> </div> <div class="direction-link"> <a href="{{ route('login') }}">{{ __('AlreadyHaveAccount') }}</a> </div> </form> </div> </section> @endsection ◇resources/views/admin/auth/emails/password_reset.blade.php これはパスワードをリセット用のメールが送られてきた場合のメールの中身のコーディング内容です。 管理者がパスワードをリセットしなくても良いという場合は、作成しても変更はしなくてもいいかもしれません。 <!DOCTYPE html> <html lang="ja"> <style> body { background-color: #fffacd; } h1 { font-size: 16px; color: #ff6666; } #button { width: 200px; text-align: center; } #button a { padding: 10px 20px; display: block; border: 1px solid #2a88bd; background-color: #FFFFFF; color: #2a88bd; text-decoration: none; box-shadow: 2px 2px 3px #f5deb3; } #button a:hover { background-color: #2a88bd; color: #FFFFFF; } </style> <body> <h1> 管理者パスワードリセット </h1> <p> 以下のボタンを押下し、パスワードリセットの手続きを行ってください。 </p> <p id="button"> <a href="{{$reset_url}}">パスワードリセット</a> </p> </body> </html> ◇resources/views/admin/auth/passwords/email.blade.php これはパスワードをリセット用のメール送る画面のコーディング内容です。 管理者がパスワードをリセットしなくても良いという場合は、作成しても変更は作成しなくてもいいかもしれません。 @extends('layouts.admin.app') @section('content') <div class="c-section"> <div class="login-screen"> <h2 class="login-header">{{ __('Reset Password') }}</h2> @if (session('status')) <div class="alert alert-success" role="alert"> {{ session('status') }} </div> @endif <form method="POST" action="{{ route('admin.password.email') }}"> @csrf @if ($errors->any()) <span class="c-invalid-feedback" role="alert"> @foreach ($errors->all() as $error) <strong>{{ $error }}</strong> @endforeach </span> @endif <div class="form-group"> <label for="email" class="form-label">{{ __('E-Mail Address') }}</label> <div> <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" placeholder="abc@〇〇〇〇.jp" autofocus> </div> </div> <div class="form-group submit-area"> <div class="btn-link --wider"> <button type="submit" class="c-btn"> <span class="c-btn__body"> {{ __('Send Password Reset Link') }}</span> </button> </div> </div> </form> </div> </div> @endsection ◇resources/views/admin/auth/passwords/reset.blade.php これはパスワードを実際にリセットする画面のコーディング内容です。 管理者がパスワードをリセットしなくても良いという場合は、作成しても変更は作成しなくてもいいかもしれません。 @extends('layouts.admin.app') @section('content') <div class="c-section"> <div class="login-screen"> <h2 class="login-header">{{ __('Reset Password') }}</h2> <form method="POST" action="{{ route('admin.password.update') }}"> @csrf @if ($errors->any()) <span class="c-invalid-feedback" role="alert"> @foreach ($errors->all() as $error) <strong>{{ $error }}</strong> @endforeach </span> @endif <input type="hidden" name="token" value="{{ $token }}"> <div class="form-group"> <label for="email" class="form-label">{{ __('メールアドレス') }}</label> <div> <input id="email" type="email" placeholder="abc@〇〇〇〇.jp" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ $email ?? old('email') }}" required autocomplete="email" autofocus> </div> </div> <div class="form-group"> <label for="password" class="form-label">{{ __('Password') }}</label> <div> <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required placeholder="●●●●●●" autocomplete="new-password"> </div> </div> <div class="form-group"> <label for="password-confirm" class="form-label">{{ __('Confirm Password') }}</label> <div> <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required placeholder="●●●●●●" autocomplete="new-password"> </div> </div> <div class="form-group submit-area"> <div class="btn-link"> <button type="submit" class="c-btn"> <span class="c-btn__body"> {{ __('Reset Password') }}</span> </button> </div> </div> </form> </div> </div> @endsection ◇resources/views/admin/auth/passwords/comfirm.blade.php こちらも必要ない場合は作成しても変更はしなくてもいいかもしれません。 @extends('layouts.admin.app') @section('content') <div class="c-section"> <div class="login-screen"> <div class="card-header">{{ __('Confirm Password') }}</div> <div class="card-body"> {{ __('Please confirm your password before continuing.') }} <form method="POST" action="{{ route('admin.password.confirm') }}"> @csrf <div class="form-group row"> <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label> <div class="col-md-6"> <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password"> @error('password') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row mb-0"> <div class="col-md-8 offset-md-4"> <button type="submit" class="btn btn-primary"> {{ __('Confirm Password') }} </button> @if (Route::has('password.request')) <a class="btn btn-link" href="{{ route('admin.password.request') }}"> {{ __('Forgot Your Password?') }} </a> @endif </div> </div> </form> </div> </div> </div> @endsection △10.Admin用のNotifications追加する これは、パスワードをリセットする際のメール送信のクラスで、どのviewファイルをメールのテンプレートかを選択することができる。 このファイルを作成しておかないと、デフォルトの本文とテンプレートファイルが選択されてしまうことになるので注意。 Notifications/Admin/PasswordResetNotification.php <?php namespace App\Notifications\Admin; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Auth\Notifications\ResetPassword; class PasswordResetNotification extends ResetPassword { use Queueable; /** * Get 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('パスワードリセット通知') ->view('admin.emails.password_reset', [ 'reset_url' => url(config('app.url').route('admin.password.reset', ['token' => $this->token, 'email' => $notifiable->getEmailForPasswordReset()], false)) ]); } } △11.Admin用のパスワードリセット用のテーブルを作成する。 以下を実行するとマイグレーションファイルが生成される。 $ php artisan make:migration add_admin_password_resets database/日付_add_admin_password_resets_table.php <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class AddAdminPasswordResetsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('admin_password_resets', function (Blueprint $table) { $table->string('email')->index(); $table->string('token'); $table->timestamp('created_at')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('admin_password_resets'); } } その後、以下を実行してテーブルを実際に作成する。 $ php artisan migrate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Php】PDOでdatabaseに接続する(3)

初めに PDOについて学習した内容のoutput用記事です。 ※内容に間違いなどがある場合はご指摘をよろしくお願いします。 ※こちらの記事はあくまでも個人で学習した内容のoutputとしての記事になります。 前回の記事: https://qiita.com/redrabbit1104/items/2668e56d4c058221388e https://qiita.com/redrabbit1104/items/8c5bb2b364347915a0f1 ユーザーがフォームなどを通じて入力した場合を想定 前回と同様、sql文を使ってデータベースに接続しますが、今回はユーザーがフォームからデータを入力した場合を考えます。 sql文のplace holder ユーザーが何を入力するか分からないので、sql文のwhere句にはplace holderを利用します。place holderには2種類があり、①名前付きplace holderと②疑問符place holderがあります。 ①名前付きplace holderを使う sql文で「:」の後ろにplace holderとして使う名前を指定します。 $sql = 'select * from accounts where user_id = :id'; //名前付きplace holder それから、sql文を実行する準備をするprepare関数を使います。戻り値はステートメントになります。 $stmt = $pdo -> prepare($sql); //sql文を実行する準備を行う。戻り値はPDOStatement PDOStatementのbindValue関数を使い、place holderと値を紐付けます。 3番目の引数にはPDO::PARAM_INTのように値の型を指定します。こちらはPHPで予め用意されている定数を使います。 $stmt -> bindValue('id', 4, PDO::PARAM_INT); 紐付けの方法にはこの他にbindParam関数を使う方法もあります。 https://www.php.net/manual/ja/pdo.constants.php 最後にexecute()関数を使って実行します。 $stmt -> execute(); //実行 結果を表示させるためにfetchall()関数で該当するデータを全て取得します。 $result = $stmt -> fetchall(); 連想配列という形で格納されているので、var_dump()で中身を確認できます。 var_dump($result); ②疑問符place holderを使う 基本的には名前付きplace holderと同じですが、place holderとして「?」を使います。 $sql = 'select * from accounts where user_id = ?'; //疑問符place holder また、bindValue()関数を使う際に、第一引数として1から始まる番号を振ります。今回はwhere句でuser_idしかカラムがないので、そのまま1を代入します。 $stmt -> bindValue(1, 1, PDO::PARAM_INT); //値の紐付け その他は名前付きplace holderと同じなので割愛します。 参考サイト https://www.php.net/manual/ja/class.pdo.php https://www.php.net/manual/ja/pdo.prepare.php https://www.php.net/manual/ja/pdostatement.bindvalue.php https://www.php.net/manual/ja/pdostatement.bindparam.php https://www.php.net/manual/ja/pdostatement.fetchall.php https://www.php.net/manual/ja/pdostatement.execute.php https://qiita.com/mpyw/items/b00b72c5c95aac573b71
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPのパフォーマンスチューニング(メモ化)

はじめに Laravelはよくパフォーマンスが悪いと言われますが、コードの書き方やLaravelの使い方次第で大幅にパフォーマンスは改善することが可能です。 本日はパフォーマンスチューニングの手法の一つであるメモ化について記事にしたいと思います。 動作確認環境 PHP 8.0 Laravel 8.0 ※下位環境でも動作する場合がございます パフォーマンスについて 一番遅い:データベースから値を取得 遅い:外部キャッシュ(Redis、Memcacheなど)から値を取得 速い:データキャッシュとしてのapcuから値を取得 一番速い:メモ化して値を取得 保存箇所へのアクセスが遠いほど、パフォーマンス劣化が発生します。 メモ化とは メモ化(英: Memoization)とは、プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブルーチン(関数)の呼び出し毎の再計算を防ぐ手法 Wikipediaには上記のようにあります。 噛み砕くと、処理の返り値を変数に保存しておき、それを再利用することで同じ計算を省略して処理速度を向上させる手法です。 Laravelでメモ化が使われている箇所 Laravelでは、Illuminate\Support\Strなどで実際に使われています。 以下は、引数で渡された値を先頭大文字の英字(通称アッパーキャメルケース)に変換してくれるメソッドです。 一度目の呼び出しと同じ引数が渡された場合は、すでにメモ化しているので、変換処理をはさまずに値が返ります。 /** * The cache of studly-cased words. * * @var array */ protected static $studlyCache = []; /** * Convert a value to studly caps case. * * @param string $value * @return string */ public static function studly($value) { $key = $value; if (isset(static::$studlyCache[$key])) { return static::$studlyCache[$key]; } $value = ucwords(str_replace(['-', '_'], ' ', $value)); return static::$studlyCache[$key] = str_replace(' ', '', $value); } クロージャーを使ったメモ化 Laravelの\Illuminate\Cache\Repository::rememberをメモ化用に改変 /** * @var array */ protected $localCache = []; /** * Memoization sample * * @param string $cacheKey * @return mixed */ public function localCache(string $cacheKey) { return $this->rememberLocal($cacheKey, function () { // メモ化したい処理 // 引数のcacheKeyが同じであれば一度目しか重い処理は通りません }); } /** * Get an item from the cache, or execute the given Closure and store the result. * * @param string $key * @param \Closure $callback * @return mixed */ private function rememberLocal(string $key, Closure $callback) { $value = $this->get($key); if ($value !== null) { return $value; } $this->put($key, $value = $callback()); return $value; } /** * Retrieve an item from the local cache by key. * * @param string $key * @return mixed */ private function get(string $key) { return $this->localCache[$key] ?? null; } /** * Store an item in the local cache. * * @param string $key * @param mixed $value */ private function put(string $key, $value): void { $this->localCache[$key] = $value; } 編集後記 パフォーマンスチューニングについて勉強した時に一番最初に学んだメモ化について記事にしました。 どこまでパフォーマンスチューニングするかはケースバイケースだとは思いますが、私の場合DBへのアクセスするのは最終手段という中で育ってきたので、メモ化は積極的に使うようにしています。 1回の通信で同じ重い処理を2度以上する可能性がある場合にはまずはメモ化の検討をオススメします。 Laravelのパフォーマンスチューニングに関する記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む