Laravelでは、csrf フォーム画面で放置した場合、セッションが異なる Token エラーで Page Expired ページが表示されるようになっています。
Laravel ではエラーが発生すると、App\Exceptions\Handler が実行され最終的に render() でエラー画面を表示します。ExceptionHandler の詳細に関しては Laravel 8.x エラー処理 を参考にしてください。
ただ、フォーム有効期限切れで Page Expired ページを表示するのは、UI/UX 的に好ましくないのでリダイレクトしてみます。
App\Exceptions\Handler の render() で処理を振り分けてダイレクトしますが、今回は Tokenエラー TokenMismatchException クラス
を判別します。
public function render($request, Throwable $exception)
{
$class = get_class($exception);
if ($class == 'Illuminate\Session\TokenMismatchException') {
return back()->withInput();
}
return parent::render($request, $exception);
}
フォームリクエストの場合は、 back()->withInput() で前の画面にリダイレクトするとよいでしょう。
またログインセッションの場合は Exception で振り分けず、Authenticate ミドルウェアなどで処理した方がわかりやすいでしょう。
Laravel のログファイルをクリアします。
デフォルトでは storage/logs/laravel.log に記載されています。
ターミナルで Laravel プロジェクトのホームに移動し、echo で空にします。
% echo "" > storage/logs/laravel.log
Mac の場合 truncate が入っていないので、Homebrew でインストールします。
% brew install truncate
truncate でログをクリアします。
% truncate -s 0 storage/logs/laravel.log
ログの設定は、.env ファイルで簡単に設定できます。 デフォルトでは stack (複数のログチャンネルを一つのログチャンネルへ集結)になっています。
LOG_CHANNEL=daily
config/logging.php でも設定が可能で、デフォルトの設定は stack になっています。
'default' => env('LOG_CHANNEL', 'stack'),
Laravel のログ設定に関しては公式を参考にしてください
ログファイルが増えた時にローテートしますが、config/logging.php でログの保存日数を指定できます。
cron などでログローテートする場合、パーミッションも設定しておきます。
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 7,
'permission' => 0666,
],
Laravel 6.x から Laravel 8.x にバージョンアップしますが、普通に composer update すると以下のエラーがでます。
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
PHP Fatal error: Declaration of App\Exceptions\Handler::report(Exception $exception) must be compatible with Illuminate\Foundation\Exceptions\Handler::report(Throwable $e) in /Users/yoo/projects/ict-kids/app/Exceptions/Handler.php on line 35
app/Exceptions/
Handler.php の Exception の処理が 6.x から 7.x で変更されているため、手動で修正します。
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
...
public function report(Exception $exception)
{
parent::report($exception);
}
...
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
namespace App\Exceptions;
use Throwable; //追加
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
...
public function report(Throwable $exception) //修正
{
parent::report($exception);
}
...
public function render($request, Throwable $exception) //修正
{
return parent::render($request, $exception);
}
「Laravel 8.x アップグレードガイド 」を参考に composer.json を修正します。
"require": {
...
"laravel/framework": "^8.0",
"guzzlehttp/guzzle": "^7.2",
...
},
"require-dev": {
"facade/ignition": "^2.3.6",
...
"laravel/ui": "^3.0",
...
"nunomaduro/collision": "^5.0",
"phpunit/phpunit": "^9.0
},
Composer で Laravel8 へアップデートします
$ composer update
バージョンを確認します。
$ php artisan -V
Laravel Framework 8.11.2
Blade ではデフォルトでカスタムクラスを呼び出すことができないため、別途設定する必要があります。
Laravel ではデフォルトでクラスをオートロードしていますが、その設定は app.php に記述されています。
'School' => App\School::class,
'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,
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
],
Laravel で Facade でよく利用される名称に気づくでしょう。
カスタムクラスをアプリ全体で利用する場合は、 app.php の 「aliases」
に追加します。
例えば、app/Item.php クラスファイルが以下のように税込計算の static 関数が定義してあるとします。
class Item {
public static function taxPrice($price, $tax) {
return $price * (1 + $tax);
}
}
この Item.php を app.php にネームスペース記述で追加します。
'aliases' => [
....
'Item' => App\Item::class,
],
これで、アプリ全体で利用可能になり Blade ファイルからも利用することができます。
{{ Item::taxPrice(100, 0.1) }}
アプリ全体で利用できるのは便利ですが毎回ローディングされるため、たくさんのカスタムファイルや大きなファイルはメモリ消費や負荷を念頭においた方がよいでしょう。
さほど利用しない機能のクラスは、コントローラーで都度呼び出したりミドルウェアなどで処理することが基本となります。 また、Blade でクラスを多用しすぎると MVC の分離ができなるなるのも注意が必要です。
Laravel のプロジェクトはコアライブラリの vender/ や環境設定ファイル .env など必要のないものは Git で管理しません。
当然 Laravel ライブラリがなければ Laravel の機能が利用できず、artisan コマンドでもエラーになります。
PHP Warning: require(/Users/yoo/projects/techboost/sample_laravel/vendor/autoload.php): failed to open stream: No such file or directory in /Users/yoo/projects/techboost/sample_laravel/artisan on line 18
Laravel のライブラリ(vender/) をインストールします。
$ composer install
※Laravel 用の composer.json が存在しないと、実行できません。
インストールが完了したら、artisan の動作を確認します。
$ cd プロジェクトフォルダ
$ php artisan --version
.env を作成します。
$ cp .env.example .env
DB の設定などがあれば修正しておきます。
Laravel のアプリケーションキーを再生成します。
$ php artisan key:generate
Web サーバが書き込みエラーにならないようにアクセス権限を変更します。
$ chmod 777 storage
$ chmod 777 bootstrap/cache/
DB を利用している場合は、マイグレーションもしておきましょう。
$ php artisan migrate
Laravel の動作確認をしましょう。 Web サーバ経由の場合はそのままブラウザで、Laravel のサーバを利用する場合は Laravel サーバを起動して確認します。
$ php artisan serve
Mac で Laravel の環境を構築するのに以下の例が挙げられます。
最近の主流は「Docker」や「Vagrant」を利用した「Homestead」といった仮想環境で環境構築することが多いかもしれません。
ただ、Laravel の基本環境に関しては Mac ネイティブでも構築可能でき、仮想環境と比べて比較的高速で動作するのがメリットです。
Mac のネイティブ環境構築の方法として「Xampp」のような統合インストールツールがあり、Windows でプログラミング初心者の人が利用するのに便利です。
ただMac の場合、将来的なことを考えると Xampp は個人的にはあまりおすすめしません。
macOS は BSD系の Unix ベースで開発されており、Linux のような環境(Linux とは別物)で利用することができます。
Mac で環境構築する場合、まずパッケージ管理ツール Homebrew のインストールが必要です。
Homebrew のインストールは以下を参考にインストールしてください。
Laravel は PHP 7系が必要になります。
Mac には既に PHP はインストールされていますが、バージョンが古いため最新にアップデートします。
$ brew update
$ brew upgrade php
$ php --version
PHP 7.4.9 (cli) (built: Aug 7 2020 19:21:48) ( NTS )
パッケージを指定してインストールすることもできます。
まず、PHP7 のパッケージを検索してみます。
$ brew search php@7
==> Formulae
php@7.2 php@7.3 php@7.4
PHP 7.4 をインストールします。
$ brew install php@7.4
Laravel を利用するには composer という PHP パッケージ管理ツールが必要です。 今回は、brew を利用して composer をインストールしてみます。
$ brew install composer
composer コマンドを利用できるようにのリンクを設定します。
$ brew link composer
Composer のバージョンを確認してみましょう。
$ composer --version
既に composer をインストールしている場合は強制的にリンクを上書きします。
$ brew link --overwrite composer
composer コマンドを利用するにあたり、以下のコマンドで Mac に環境変数を設定しておきます。
$ export PATH=$HOME/.composer/vendor/bin:$PATH
$ source ~/.bash_profile
これは必須ではありませんが、Laravel 独自の laravel コマンドを利用することもできます。
composer コマンドを利用して Laravel インストーラーをインストールしてみます。
$ composer global require "laravel/installer
インストールできたら、バージョンを確認します。
$ laravel --version
Laravel Installer 3.2.0
Laravel プロジェクトは、以下の2種類のコマンドで作成することができます。
Laravel のプロジェクトを作成してみましょう。
$ composer create-project laravel/laravel laravel_app
$ laravel new laravel-mac
プロジェクを作成したらプロジェクトディレクトリに移動し、各フォルダのパーミッションを変更します。
$ cd laravel-mac/
$ chmod 777 storage
$ chmod 777 bootstrap/cache/
Laravel サーバを起動します。
$ php artisan serve
Homebrew と Composer で Laravel をインストール・プロジェクトの作成・サーバ起動をしました。
データベースをはじめとした機能は、別途 Homebrew などでインストールしなければいけませんが、この記事ではここまでとします。
composer でプロジェクト名「laravel_app」を作成します。
$ composer create-project laravel/laravel laravel_app
Laravel のバージョンを確認しておきます。
Laravel Framework 7.12.0
laravel_app に移動して、ComposerでLaravelのUIパッケージ laravel/ui をインストールします。
$ cd laravel_app
$ composer require laravel/ui
Reactを利用できるようにスカフォールドをインストールします。
$ php artisan ui react
//Authを利用する場合
$ php artisan ui react --auth
package.jsonに記述された内容で依存パッケージをインストールとLaravel Mixでビルドします。
$ npm install
$ npm run dev
関連ファイルの変更を監視しておきます。
$ npm run watch
これでReactがインストールされたLaravelプロジェクト作成が完了しました。
ここで Laravelサーバを起動して確認してみましょう。
おなじみの Laravel の画面は表示されましたが、この画面ではReact の動作実装されていません。
Laravel で React をインストールするとあらかじめサンプルファイルが作成されています。
resources/js/app.js には Bootstrap と Reactコンポーネント「Example」が読み込まれています。
require('./bootstrap');
require('./components/Example');
resources/js/components/Example.js には、Reactコンポーネント「Example」の中身です。
Reactでは、HTMLレンダリングにJSXを利用しますが、JavaScriptに直接HTMLタグを記述できる点で便利です。
import React from 'react';
import ReactDOM from 'react-dom';
function Example() {
return (
...JSX...
);
}
export default Example;
...
現状だと Reactコンポーネントは表示できないため、Laravelのレイアウトファイル「resources/views/welcom.blade.php」を修正します。
Laravelのmix()関数で app.jsを読み込んでいます。
<script src="{{ mix('js/app.js') }}"></script>
また、Exampleコンポーネントを表示する場合、HTMLタグにidを指定して呼び出します。
<div id="example"></div>
もういちど、サーバを確認してみると今度は、Exampleコンポーネントを読み込んだ状態で表示されました。
artisan コマンドで、ペジネーションビューをプロジェクト内に書き出します。
$ php artisan vendor:publish --tag=laravel-pagination
resources/views/vendor/pagination/ にページネーションのBladeファイルが作成されます。
あとで説明しますが、デフォルトは「bootstrap-4」で書き出されますが、ページネーションメソッドで他のBladeファイルを指定することができます。
よって、利用したいBladeファイルをカスタマイズしてください。
Eloquent モデルには、paginate()、simplePaginate() メソッドを利用できます。
$limit = 10;
$offset = 0;
$user = User::paginate($limit, '[*]', 'page', $offset);
AppServiceProvider::boot() でページネーションのBlade ファイル名を指定します。
public function boot()
{
Paginator::defaultView('pagination::bootstrap-4');
Paginator::defaultSimpleView('pagination::simple-bootstrap-4');
}
Blade ファイルでは、Eloquent モデルデータの links() メソッドでページネーション表示できます。
{!! $user->links() !!}
また、Blade 側で明示的に指定することもできます。
{{ $paginator->links('pagination::bootstrap-4', ['key' => 'value']) }}
Eloquent のページネーションメソッドは以下を参照してください。
SQL が発行されるたびに Log ファサードを利用して SQLログを書き出してみます。
Laravel のLogファサードの仕様は以下を参照してください。
https://readouble.com/laravel/7.x/ja/logging.html
プロバイダは、AppServiceProvider に登録しても良いですが、今回はカスタムで LogSqlServiceProvider を作成します。
$ php artisan make:provider LogSqlServiceProvider
app/Providers/LogSqlServiceProvider.php が作成されているのを確認します。
config/app.php の providers に LogSqlServiceProvider を登録します。
'providers' => [
...
App\Providers\LogSqlServiceProvider::class,
...
],
app/Providers/LogSqlServiceProvider.php の register() に記述します。
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class AppServiceProvider extends ServiceProvider
{
const SQL_LOG_ENVIROMENT = [
'local',
'development',
];
....
public function register()
{
if (in_array(App::environment(), self::SQL_LOG_ENVIROMENT)) {
DB::listen(function ($query) {
$sql = $query->sql;
for ($i = 0; $i < count($query->bindings); $i++) {
$sql = preg_replace("/\?/", $query->bindings[$i], $sql, 1);
}
Log::channel('sql')->debug("SQL", ["time" => sprintf("%.2f ms", $query->time), "sql" => $sql]);
});
}
}
App::environment() で現在の環境を取得し、任意に設定した SQL_LOG_ENVIROMENT の場合に、ログを書き出します。
環境による設定は、.env の boolean で設定しても良いかも知れません。
config/logging.php で設定でき、デフォルト「storage/logs/larave.log」となります。
Log::debug() の場合、デフォルトで single チャンネルで書き出されるため、Log::channel('チャンネル名')で、SQL用のログを設定します。
(内部的に、Illuminate\Log\LogManagerクラスで、driver() に設定される模様)
'channels' => [
...
'sql' => [
'driver' => 'sql',
'path' => storage_path('logs/sql.log'),
'level' => 'debug',
],
...
],
またロガーは RFC 5424 の以下の8つのログレベルを提供しています。
Logファサードは、最終的にPsr\Log\LoggerInterface を実装した Monolog\Logger を利用しています。
Laravel 7 で認証ページを作成してみます。
※Laravel7 の認証ページ
https://readouble.com/laravel/7.x/ja/authentication.html
$ composer create-project laravel/laravel laravel-auth --prefer-dist
$ cd laravel-auth
$ chmod -R 777 storage
$ chmod -R 777 bootstrap/cache
この時点で、 config/auth.php などが作成されています。
デフォルトで app/User.php が作成されています。
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
}
今回は、App/Models/User.php にパスを変更しておきます。
(namespace など修正が必要)
マルチ認証用に、管理者(Admin)モデルを作成してみます。
Models/Admin.php を作成します。
$ php artisan make:model Models/Admin
databases/ に Admin用のマイグレーションファイルを作成します。
$ php artisan make:migration create_admins_table
上記のモデルとマイグレーションファイルを同時に作成することもできます。
Models/Admin.php を作成します。
$ php artisan make:model Models/Admin -m
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateAdminsTable extends Migration
{
public function up()
{
if (Schema::hasTable('admins')) return;
Schema::create('admins', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->string('email');
$table->string('name');
$table->string('password');
$table->string('remember_token')->nullable();
});
}
public function down()
{
Schema::dropIfExists('admins');
}
}
Guard は Auth認証用のドライバークラスで、config/auth.php に設定をします。
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],
Guard を設定することにより、Controllerなどに記載するミドルウェアでの認証が可能となります。
$this->middleware('auth:admin');
config/auth.php ファイルに Eloquent の App\Models\User クラスを設定します。
※User.php の namespace は App\Models\ に変更
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
],
Laravel6 から 以下のコマンドは廃止されました。
$ php artisan make:auth
代わりに、「laravel/ui」でログイン画面を作成するようになりました。
更に、リリースノートには、2.0 を指定するように記載されています。
$ composer require laravel/ui "^2.0"
vue の部分は bootstrap などの設定もできます。
が作成されているので、管理者用にコピーしておくと良いでしょう。
$ createdb sample_auth
# mysql -u root
mysql > create database sample_auth;
各種DBの設定を合わせる
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=sample
DB_USERNAME=postgres
DB_PASSWORD=secret
DB設定が一通り完了したら、マイグレートしてみます。
$ php artisan migrate
テーブルが作成したことを確認したら、サンプルデータを作成してみます。
サンプルデータを作成する場合、artisan に seeder コマンドにも用意されています。
ユーザ、管理者の Seeder を作成すると「database/seeds/」にファイルが書き出されます。
$ php artisan make:seeder UsersTableSeeder
$ php artisan make:seeder AdminsTableSeeder
public function run()
{
DB::table('users')->insert([
'name' => Str::random(10),
'email' => Str::random(10).'@gmail.com',
'password' => Hash::make('password'),
]);
}
public function run()
{
DB::table('admins')->insert([
'email' => 'xxxx@xxxx.com',
'name' => 'Admin',
'password' => Hash::make('password')
]
);
}
Seeder ファイルが作成されたら、「database/seeds/DatabaseSeeder.php」に seeder を実行できるよう設定します。
public function run()
{
$this->call(UsersTableSeeder::class);
$this->call(AdminsTableSeeder::class);
}
設定したら、artisan コマンドで Seeder を実行します。
$ php artisan db:seed
実際に、テーブルにデータが作成されているか確認してみましょう。
ここでは割愛しますが、Factory の faker を利用する方法で seeder を実行できます。
Factory ファイルは以下のコマンドで作成できます。
$ php artisan make:factory UserFactory
各種コントローラーを作成します。
App/Http/Controllers/Admin/ にコントローラーを作成します。
$ php artisan make:controller Admin/HomeController
ファイルを作成したら、index の実装と、コンストラクタに認証用のミドルウェアを記述(Guard で設定したパラメータ)をします。
以下の例は、管理者の認証制御となります。
public function __construct()
{
$this->middleware('auth:admin');
}
public function index(Request $request)
{
return view('admin.index');
}
ただコントローラが増えてくると、コンストラクタに毎度記述するのも面倒なので、Route のグループ設定で一括管理すると良いでしょう。
(認証用の親コントローラを継承する方法もありますが)
認証が必要・不要のページを考えて、グループ化した Route を設定します。
ログイン、ログアウト、登録ページなどの認証不要のページのルートを作成します。
管理者URIは、admin で統一したいので「admin」プリフィックスを利用しています。
Route::group(['prefix' => 'admin'], function () {
Route::get('login', 'Admin\Auth\LoginController@index')->name('admin.login');
Route::post('login', 'Admin\Auth\LoginController@login')->name('admin.login');
Route::get('logout', 'Admin\Auth\LoginController@logout')->name('admin.logout');
Route::post('logout', 'Admin\Auth\LoginController@logout')->name('admin.logout');
});
認証が必要なページのルートを設定します。
ルートグループのパラメータに、ミドルウェアの Guard 設定します。
Route::group(['prefix' => 'admin/topic', 'middleware' => 'auth:admin'], function() {
Route::get('/', 'Admin\HomeController@index')->name('admin.index');
});