はじめに
この記事はLaravelフレームワークで旅行予約サイトを作成して、Auth0で認証機能を追加する手順で、こちらの原文を元に作成しています。完成版のソースコードはここで公開しています。
前提条件と検証環境
sshキーペアの作成、PHP, Virtualbox, Vagrant, Node, NPMのインストール、およびAuth0の無料アカウントの取得とテナントの作成が完了していることが前提となっています。Auth0の無料アカウント取得がまだの方はこちらの記事を参照の上ご準備をお願いします。
-
OS :
macOS Mojave 10.14.6
-
PHP :
7.2.24
-
Virtualbox :
6.0.14 r133895 (Qt5.6.3)
-
Vagrant :
2.2.6
-
node :
10.15.3
-
npm :
6.12.0
手順
各種ツールのインストール
PHP7.2をインストールします。この記事ではこちらを参考にインストールしています。
composer(PHPのパッケージ管理ツール)をインストールします。この記事ではこちらを参考にインストールしています。インストール後、~/.composer/vendor/binをPATHにセットします。その他のツールのインストール手順は割愛します。Qiitaに多数記事が掲載されているのでそれらをご参照お願いします。
composerを使ってLaravelをインストールします。
$ composer global require laravel/installer
Laravel Homesteadの設定
Laravelのプロジェクトを作成します。
$ laravel new travel-planet-crud
$ cd travel-planet-crud
Laravel Homestead Vagrant boxをダウンロードします。
$ vagrant box add laravel/homestead
Homesteadをインストールします。
$ pwd
~/travel-planet-crud
$ composer require laravel/homestead --dev
Homestead.yaml(構成情報ファイル)を作成します。
$ php vendor/bin/homestead make
/etc/hostsファイルにホスト名を追加します。IPはHomestead.yamlに記載されています。
192.168.10.10 homestead.test
仮想マシンを起動します。
$ pwd
~/travel-planet-crud
$ vagrant up
Chrhomeでhttp://homestead.test
にアクセスしてページが表示されることを確認します。
Routeの作成
routes/web.phpを編集してルートを指定します。手順の後半でApplicationとAuth0を連携します。ここでは連携後のAuth0モデルを利用することを前提としています。
<?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::view('/', 'home');
Route::get('/hotels', 'HotelController@index');
Route::get('/auth0/callback', '\Auth0\Login\Auth0Controller@callback' )->name('auth0-callback');
Route::get('/login', 'Auth\Auth0IndexController@login')->name('login');
Route::get('/logout', 'Auth\Auth0IndexController@logout')->name('logout')->middleware('auth');
Route::group(['prefix' => 'dashboard', 'middleware' => 'auth'], function() {
Route::view('/', 'dashboard/dashboard');
Route::get('reservations/create/{id}', 'ReservationController@create');
Route::resource('reservations', 'ReservationController')->except('create');
});
Databaseの作成
reservation, hotels, roomsの3つのテーブルを作成します。
設定
MySQLのパラメータを修正します。
---省略---
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
--省略--
Modelの作成
Modelファイルを作成します。この記事ではLaravel's Eloquent ORMを利用したModelを作成します。
$ pwd
~/travel-planet-crud
$ mkdir app/Models
$ php artisan make:model Models/Hotel -m
$ php artisan make:model Models/Room -m
$ php artisan make:model Models/Reservation -m
Hotel.php, Room.php, Reservation.phpを編集して属性とテーブル間の相関を定義します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Hotel extends Model
{
public $timestamps = false;
protected $fillable = [
'name',
'location',
'description',
'image'
];
public function rooms() {
return $this->hasMany('App\Models\Room');
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Room extends Model
{
public $timestamps = false;
protected $fillable = [
'hotel_id',
'type',
'description',
'price',
'image'
];
public function hotel() {
return $this->belongsTo('App\Models\Hotel');
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Reservation extends Model
{
protected $fillable = [
'user_id',
'room_id',
'num_of_guests',
'arrival',
'departure'
];
public function room() {
return $this->belongsTo('App\Models\Room');
}
}
Migrationの作成
LaravelのMigration(データベースのバージョンコントロール)を作成します。
プロジェクトルートディレクトリ配下のdatabase/migrations/xxxx_xx_xx_xxxxx_create_hotels_table.phpを編集します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateHotelsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('hotels', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('location');
$table->string('description');
$table->string('image');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('hotels');
}
}
database/migrations/xxxx_xx_xx_xxxxx_create_rooms_table.phpを編集します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateRoomsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('rooms', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('hotel_id');
$table->foreign('hotel_id')->references('id')->on('hotels');
$table->string('type');
$table->string('description');
$table->decimal('price', 10, 2);
$table->string('image');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('rooms');
}
}
database/migrations/xxxx_xx_xx_xxxxx_create_reservations_table.phpを編集します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateReservationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('reservations', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->string('user_id');
$table->unsignedBigInteger('room_id');
$table->foreign('room_id')->references('id')->on('rooms');
$table->integer('num_of_guests');
$table->date('arrival');
$table->date('departure');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('reservations');
}
}
データの投入
Seeder Fileを作成します。
$ pwd
~/travel-planet-crud
$ php artisan make:seeder HotelSeeder
$ php artisan make:seeder RoomSeeder
$ php artisan make:seeder ReservationSeeder
database/HotelSeeder.phpを編集します。
<?php
use Illuminate\Database\Seeder;
use App\Models\Hotel;
class HotelSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// array of specific hotels to populate database
$hotels = [
[
'name' => 'Marriott',
'location' => 'Seattle, WA',
'description' => 'International luxurious hotel.',
'image' => 'https://placeimg.com/640/480/arch'
],
[
'name' => 'Aria',
'location' => 'Las Vegas, NV',
'description' => 'International luxurious hotel.',
'image' => 'https://placeimg.com/640/480/arch'
],
[
'name' => 'MGM Grand',
'location' => 'Las Vegas, NV',
'description' => 'International luxurious hotel.',
'image' => 'https://placeimg.com/640/480/arch'
]
];
foreach ($hotels as $hotel) {
Hotel::create(array(
'name' => $hotel['name'],
'location' => $hotel['location'],
'description' => $hotel['description'],
'image' => $hotel['image']
));
}
}
}
database/seeds/RoomSeeder.phpを編集します。
<?php
use Illuminate\Database\Seeder;
use App\Models\Room;
class RoomSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// array of specific rooms to populate database
$rooms = [
[
'hotel_id' => 1,
'type' => 'Luxury Suite',
'description' => '2000 sqft, 3 king sized beds, full kitchen.',
'price' => 980.00,
'image' => 'https://placeimg.com/640/480/arch'
],
[
'hotel_id' => 1,
'type' => 'Double',
'description' => 'Two queen beds.',
'price' => 200.00,
'image' => 'https://placeimg.com/640/480/arch'
],
[
'hotel_id' => 2,
'type' => 'Suite',
'description' => 'International luxurious room.',
'price' => 350.00,
'image' => 'https://placeimg.com/640/480/arch'
],
[
'hotel_id' => 2,
'type' => 'Economy',
'description' => 'One queen bed, mini fridge.',
'price' => 87.99,
'image' => 'https://placeimg.com/640/480/arch'
],
[
'hotel_id' => 3,
'type' => 'Suite',
'description' => 'One ultra wide king bed, full kitchen.',
'price' => 399.00,
'image' => 'https://placeimg.com/640/480/arch'
]
];
foreach ($rooms as $room) {
Room::create(array(
'hotel_id' => $room['hotel_id'],
'type' => $room['type'],
'description' => $room['description'],
'price' => $room['price'],
'image' => $room['image']
));
}
}
}
database/seeds/ReservationSeeder.phpを編集します。
<?php
use Illuminate\Database\Seeder;
use App\Models\Reservation;
class ReservationSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// array of specific reservations to populate database
$reservations = [
[
'user_id' => '1',
'room_id' => 1,
'num_of_guests' => 4,
'arrival' => '2020-05-18',
'departure' => '2020-05-28'
],
[
'user_id' => '1',
'room_id' => 2,
'num_of_guests' => 1,
'arrival' => '2020-05-10',
'departure' => '2020-05-12'
],
[
'user_id' => '1',
'room_id' => 3,
'num_of_guests' => 3,
'arrival' => '2020-05-06',
'departure' => '2020-05-07'
],
[
'user_id' => '1',
'room_id' => 4,
'num_of_guests' => 2,
'arrival' => '2020-05-12',
'departure' => '2020-05-15'
],
[
'user_id' => '1',
'room_id' => 2,
'num_of_guests' => 2,
'arrival' => '2020-05-20',
'departure' => '2020-05-24'
]
];
foreach ($reservations as $reservation) {
Reservation::create(array(
'user_id' => $reservation['user_id'],
'room_id' => $reservation['room_id'],
'num_of_guests' => $reservation['num_of_guests'],
'arrival' => $reservation['arrival'],
'departure' => $reservation['departure']
));
}
}
}
database/seeds/DatabaseSeeder.phpを編集します。
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
$this->call(HotelSeeder::class);
$this->call(RoomSeeder::class);
$this->call(ReservationSeeder::class);
}
}
Homestead Boxにログインします。
$ vagrant ssh
MySQLにデータを投入します。
vagrant@travel-planet-crud:~$ cd code
vagrant@travel-planet-crud:~$ php artisan migrate
vagrant@travel-planet-crud:~$ php artisan db:seed
正常に投入できたか確認します。
vagrant@travel-planet-crud:~$ mysql
mysql> USE homestead;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
\Database changed
mysql> SHOW tables;
+---------------------+
| Tables_in_homestead |
+---------------------+
| failed_jobs |
| hotels |
| migrations |
| password_resets |
| reservations |
| rooms |
| users |
+---------------------+
7 rows in set (0.00 sec)
mysql> SELECT * FROM hotels;
+----+-----------+---------------+--------------------------------+-----------------------------------+
| id | name | location | description | image |
+----+-----------+---------------+--------------------------------+-----------------------------------+
| 1 | Marriott | Seattle, WA | International luxurious hotel. | https://placeimg.com/640/480/arch |
| 2 | Aria | Las Vegas, NV | International luxurious hotel. | https://placeimg.com/640/480/arch |
| 3 | MGM Grand | Las Vegas, NV | International luxurious hotel. | https://placeimg.com/640/480/arch |
+----+-----------+---------------+--------------------------------+-----------------------------------+
3 rows in set (0.00 sec)
Controllerの作成
HotelControllerとReservationControllerを作成します。
$ pwd
~/travel-planet-crud
$ php artisan make:controller HotelController
$ php artisan make:controller ReservationController --resource
app/Http/Controllers/ReservationController.phpを編集します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Models\Reservation;
use App\Models\Hotel;
use App\Models\Room;
class ReservationController extends Controller
{
/**
* Display a listing of the reservations.
*
* @return \Illuminate\Http\Response
*/
public function index() {
$reservations = Reservation::with('room', 'room.hotel')
->where('user_id', \Auth::user()->getUserInfo()['sub'])
->orderBy('arrival', 'asc')
->get();
return view('dashboard.reservations')->with('reservations', $reservations);
}
/**
* Show the form for creating a new reservation.
*
* @return \Illuminate\Http\Response
*/
public function create($hotel_id)
{
$hotelInfo = Hotel::with('rooms')->get()->find($hotel_id);
return view('dashboard.reservationCreate', compact('hotelInfo'));
}
/**
* Store a newly created reservation in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
// Set the user_id equal to the user's Auth0 sub id before
// Will be similar to "auth0|123123123123123"
$user_id = \Auth::user()->getUserInfo()['sub'];
$request->request->add(['user_id' => $user_id]);
// Create the request
Reservation::create($request->all());
return redirect('dashboard/reservations')->with('success', 'Reservation created!');
}
/**
* Display the specified reservation.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show(Reservation $reservation)
{
$reservation = Reservation::with('room', 'room.hotel')
->get()
->find($reservation->id);
if ($reservation->user_id === \Auth::user()->getUserInfo()['sub']) {
$hotel_id = $reservation->room->hotel_id;
$hotelInfo = Hotel::with('rooms')->get()->find($hotel_id);
return view('dashboard.reservationSingle', compact('reservation', 'hotelInfo'));
} else
return redirect('dashboard/reservations')->with('error', 'You are not authorized to see that.');
}
/**
* Show the form for editing the specified reservation.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit(Reservation $reservation)
{
$reservation = Reservation::with('room', 'room.hotel')
->get()
->find($reservation->id);
if ($reservation->user_id === \Auth::user()->getUserInfo()['sub']) {
$hotel_id = $reservation->room->hotel_id;
$hotelInfo = Hotel::with('rooms')->get()->find($hotel_id);
return view('dashboard.reservationEdit', compact('reservation', 'hotelInfo'));
} else
return redirect('dashboard/reservations')->with('error', 'You are not authorized to do that');
}
/**
* Update the specified reservation in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Reservation $reservation) {
if ($reservation->user_id != \Auth::user()->getUserInfo()['sub'])
return redirect('dashboard/reservations')->with('error', 'You are not authorized to update this reservation');
$user_id = \Auth::user()->getUserInfo()['sub'];
$reservation->user_id = $user_id;
$reservation->num_of_guests = $request->num_of_guests;
$reservation->arrival = $request->arrival;
$reservation->departure = $request->departure;
$reservation->room_id = $request->room_id;
$reservation->save();
return redirect('dashboard/reservations')->with('success', 'Successfully updated your reservation!');
}
/**
* Remove the specified reservation from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Reservation $reservation)
{
$reservation = Reservation::find($reservation->id);
if ($reservation->user_id === \Auth::user()->getUserInfo()['sub']) {
$reservation->delete();
return redirect('dashboard/reservations')->with('success', 'Successfully deleted your reservation!');
} else
return redirect('dashboard/reservations')->with('error', 'You are not authorized to delete this reservation');
}
}
app/Http/Controllers/HotelController.phpを編集します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Hotel;
class HotelController extends Controller
{
public function index() {
$hotels = Hotel::all();
return view('hotels')->with('hotels', $hotels);
}
}
Viewの作成
Viewディレクトリ/ファイルを作成します。
$ pwd
~/travel-planet-crud
$ cd resources/views
$ mkdir dashboard partials
$ cd dashboard
$ touch reservationCreate.blade.php reservationEdit.blade.php reservationSingle.blade.php reservations.blade.php dashboard.blade.php
$ cd ../partials
$ touch nav.blade.php
$ cd ..
$ touch home.blade.php hotels.blade.php index.blade.php
LaravelのデフォルトViewを削除します。
$ pwd
~/travel-planet-crud
$ rm resources/views/welcome.blade.php
Bootstrapをインストールします。
$ pwd
~/travel-planet-crud
$ composer require laravel/ui
$ php artisan ui bootstrap
$ npm install
$ npm run dev
resources/views/index.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">
<title>@yield('title') - Hotel Manager</title>
<link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">
<link rel="stylesheet" href="{{asset('css/app.css')}}">
</head>
<body>
@include('partials.nav')
<main>@yield('content')</main>
</body>
</html>
resources/views/home.blade.phpを編集します。
@extends('index') {{-- Specify that we want to extend the index file --}}
@section('title', 'Home') {{-- Set the title content to "Home" --}}
{{-- Set the "content" section, which will replace "@yield('content')" in the index file we're extending --}}
@section('content')
<div class="jumbotron text-light" style="background-image: url('https://cdn.auth0.com/blog/laravel-6-crud/laravel-beach-bg.png')">
<div class="container">
@if(Auth::user())
<h1 class="display-4">Welcome back, {{ Auth::user()->nickname}}!</h1>
<p class="lead">To your one stop shop for reservation management.</p>
<a href="/dashboard" class="btn btn-success btn-lg my-2">View your Dashboard</a>
@else
<h1 class="display-3">Reservation management made easy.</h1>
<p class="lead">Lorem, ipsum dolor sit amet consectetur adipisicing elit. Numquam in quia natus magnam ducimus quas molestias velit vero maiores. Eaque sunt laudantium voluptas. Fugiat molestiae ipsa delectus iusto vel quod.</p>
<a href="/login" class="btn btn-success btn-lg my-2">Sign Up for Access to Thousands of Hotels</a>
@endif
</div>
</div>
<div class="container">
<div class="row">
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Convenient</h5>
<p class="card-text">Manage all your hotel reservations in one place</p>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Best prices</h5>
<p class="card-text">We have special discounts at the best hotels</p>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Easy to use</h5>
<p class="card-text">Book and manage with the click of a button</p>
</div>
</div>
</div>
</div>
</div>
@endsection
resources/views/partials/nav.blade.phpを編集します。
<nav class="navbar navbar-expand navbar-dark bg-primary">
<div class="navbar-nav w-100">
<a class="navbar-brand text-color" href="/">TravelPlanet</a>
<a class="nav-item nav-link" href="/hotels">Browse Hotels</a>
</div>
</nav>
resources/views/hotels.blade.phpを編集します。
<!-- resources/views/hotels.blade.php -->
@extends('index')
@section('title', 'Hotels')
@section('content')
<div class="container my-5">
<div class="row">
<!-- Loop through hotels returned from controller -->
@foreach ($hotels as $hotel)
<div class="col-sm-4">
<div class="card mb-3">
<div style="background-image:url('{{ $hotel->image }}');height:300px;background-size:cover;" class="img-fluid" alt="Front of hotel"></div>
<div class="card-body">
<h5 class="card-title">{{ $hotel->name }}</h5>
<small class="text-muted">{{ $hotel->location }}</small>
<p class="card-text">{{ $hotel->description }}</p>
<a href="/dashboard/reservations/create/{{ $hotel->id }}" class="btn btn-primary">Book Now</a>
</div>
</div>
</div>
@endforeach
</div>
</div>
@endsection
resources/views/dashboard/dashboard.blade.phpを編集します。
<!-- resources/views/dashboard/dashboard.blade.php -->
@extends('index')
@section('title', 'Dashboard')
@section('content')
<div class="container text-center my-5">
<div class="row">
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h4 class="card-title">Manage your Reservations</h4>
<p class="card-text">Modify your current reservations.</p>
<a href="/dashboard/reservations" class="btn btn-primary">My Reservations</a>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h4 class="card-title">Find a Room</h4>
<p class="card-text">Browse our catalog of top-rated hotels.</p>
<a href="/hotels" class="btn btn-primary">Our Hotels</a>
</div>
</div>
</div>
</div>
</div>
@endsection
resources/views/dashboard/reservations.blade.phpを編集します。
@extends('index')
@section('title', 'Reservations')
@section('content')
<div class="container mt-5">
<h2>Your Reservations</h2>
<table class="table mt-3">
<thead>
<tr>
<th scope="col">Hotel</th>
<th scope="col">Arrival</th>
<th scope="col">Departure</th>
<th scope="col">Type</th>
<th scope="col">Guests</th>
<th scope="col">Price</th>
<th scope="col">Manage</th>
</tr>
</thead>
<tbody>
@foreach ($reservations as $reservation)
<tr>
<td>{{ $reservation->room->hotel['name'] }}</td>
<td>{{ $reservation->arrival }}</td>
<td>{{ $reservation->departure }}</td>
<td>{{ $reservation->room['type'] }}</td>
<td>{{ $reservation->num_of_guests }}</td>
<td>${{ $reservation->room['price'] }}</td>
<td><a href="/dashboard/reservations/{{ $reservation->id }}/edit" class="btn btn-sm btn-success">Edit</a></td>
</tr>
@endforeach
</tbody>
</table>
@if(!empty(Session::get('success')))
<div class="alert alert-success"> {{ Session::get('success') }}</div>
@endif
@if(!empty(Session::get('error')))
<div class="alert alert-danger"> {{ Session::get('error') }}</div>
@endif
</div>
@endsection
resources/views/dashboard/reservationSingle.blade.phpを編集します。
@extends('index')
@section('title', 'Edit Reservation')
@section('content')
<div class="container">
<div class="card my-5">
<div class="card-header">
<h2>You're all booked for the {{ $hotelInfo->name }} in {{ $hotelInfo->location }}!</h2>
</div>
<div class="card-body">
<div class="card-body">
<div class="row">
<div class="col-sm-6">
<img src="{{ $hotelInfo->image }}" class="img-fluid" alt="Front of hotel">
</div>
<div class="col-sm-6">
<h3 class="card-title">
{{ $hotelInfo->name }} - <small>{{ $hotelInfo->location }}</small>
</h3>
<p class="card-text">{{ $hotelInfo->description }}</p>
<p class="card-text"><strong>Arrival: </strong>{{ $reservation->arrival }}</p>
<p class="card-text"><strong>Departure: </strong>{{ $reservation->departure }}</p>
<p class="card-text"><strong>Room: </strong>{{ $reservation->room['type'] }}</p>
<p class="card-text"><strong>Guests: </strong>{{ $reservation->num_of_guests }}</p>
<p class="card-text"><strong>Price: </strong>${{ $reservation->room['price'] }}</p>
</div>
</div>
<div class="text-center mt-3">
<a href="/dashboard/reservations/{{ $reservation->id }}/edit" class="btn btn-lg btn-success">Edit this reservation</a>
<a href="/dashboard/reservations/{{ $reservation->id }}/delete" class="btn btn-lg btn-danger">Delete</a>
</div>
</div>
</div>
</div>
</div>
@endsection
resources/views/dashboard/reservationEdit.blade.phpを編集します。
<!-- resources/views/dashboard/reservationEdit.blade.php -->
@extends('index')
@section('title', 'Edit Reservation')
@section('content')
<div class="container">
<div class="card my-5">
<div class="card-header">
<h2>{{ $hotelInfo->name }} - <small class="text-muted">{{ $hotelInfo->location }}</small></h2>
</div>
<div class="card-body">
<h5 class="card-title"></h5>
<p class="card-text">Book your stay now at the most magnificent resort in the world!</p>
<form action="{{ route('reservations.update', $reservation->id) }}" method="POST">
@csrf
@method('PUT')
<div class="row">
<div class="col-sm-8">
<div class="form-group">
<label for="room">Room Type</label>
<select class="form-control" name="room_id" value="{{ old('room_id', $reservation->room_id) }}">
@foreach ($hotelInfo->rooms as $option)
<option value="{{$option->id}}">{{ $option->type }} - ${{ $option->price }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="guests">Number of guests</label>
<input class="form-control" name="num_of_guests" value="{{ old('num_of_guests', $reservation->num_of_guests) }}">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="arrival">Arrival</label>
<input type="date" class="form-control" name="arrival" placeholder="03/21/2020" value="{{ old('arrival', $reservation->arrival) }}">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="departure">Departure</label>
<input type="date" class="form-control" name="departure" placeholder="03/23/2020" value="{{ old('departure', $reservation->departure) }}">
</div>
</div>
</div>
<button type="submit" class="btn btn-lg btn-primary">Submit</button>
</form>
</div>
</div>
<form action="{{ route('reservations.destroy', $reservation->id) }}" method="POST">
@method('DELETE')
@csrf
<p class="text-right">
<button type="submit" class="btn btn-sm text-danger">Delete reservation</button>
</p>
</form>
</div>
@endsection
resources/views/dashboard/reservationCreate.blade.phpを編集します。
@extends('index')
@section('title', 'Create reservation')
@section('content')
<div class="container my-5">
<div class="card">
<div class="card-header">
<h2>{{ $hotelInfo->name }} - <small class="text-muted">{{ $hotelInfo->location }}</small></h2>
</div>
<div class="card-body">
<h5 class="card-title"></h5>
<p class="card-text">Book your stay now at the most magnificent resort in the world!</p>
<form action="{{ route('reservations.store') }}" method="POST">
@csrf
<div class="row">
<div class="col-sm-8">
<div class="form-group">
<label for="room">Room Type</label>
<select class="form-control" name="room_id">
@foreach ($hotelInfo->rooms as $option)
<option value="{{$option->id}}">{{ $option->type }} - ${{ $option->price }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="guests">Number of guests</label>
<input class="form-control" name="num_of_guests" placeholder="1">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="arrival">Arrival</label>
<input type="date" class="form-control" name="arrival" placeholder="03/21/2020">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="departure">Departure</label>
<input type="date" class="form-control" name="departure" placeholder="03/23/2020">
</div>
</div>
</div>
<button type="submit" class="btn btn-lg btn-primary">Book it</button>
</form>
</div>
</div>
</div>
@endsection
認証機能の追加
Auth0のダッシュボードにログイン、左ペインの"Applications"をクリックして右上の"CREATE APPLICATION"を押します。
"Name"に任意の名前を入力、"Choose an application type"で"Regular Web Applications"を選択して”CREATE”を押します。
"Settings"タブをクリック、"Allowed Callback URLs"に"http://homestead.test/auth0/callback
"を、"Allowed Logout URLs"に"http://homestead.test
"を入力して"SAVE CHANGES"を押します。Auth0でLogin/Logout後にリダイレクトを許可するURLを設定しています。実在するURLと完全一致している必要があります。
Auth0 PHP/Laravel Plug-inをインストールします。
$ pwd
~/travel-planet-crud
$ composer require auth0/login:"~5.0"
config/app.phpにAuth0 login service providerを追加します。以下、編集後のconfig/app.phpです。
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL', null),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => 'en',
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
*/
'faker_locale' => 'en_US',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
Auth0\Login\LoginServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Auth0' => Auth0\Login\Facade\Auth0::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,
],
];
app/Providers/AppServiceProvider.phpを編集して、Auth0UserRepositoryをバインドします。**ユーザがログインまたはJWTがデコードされるたびに生成されるUserモデルです。**以下、編集後のapp/Providers/AppServiceProvider.phpです。
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(
\Auth0\Login\Contract\Auth0UserRepository::class,
\Auth0\Login\Repository\Auth0UserRepository::class
);
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
}
Loginサービスプロバイダーを設定します。
$ pwd
~/travel-planet-crud
$ php artisan vendor:publish
php artisan vendor:publish 月 10/28 13:59:41 2019
Which provider or tag's files would you like to publish?:
[0 ] Publish files from all providers and tags listed below
[1 ] Provider: Auth0\Login\LoginServiceProvider --> これを選択します
[2 ] Provider: Facade\Ignition\IgnitionServiceProvider
[3 ] Provider: Fideloper\Proxy\TrustedProxyServiceProvider
[4 ] Provider: Illuminate\Foundation\Providers\FoundationServiceProvider
[5 ] Provider: Illuminate\Mail\MailServiceProvider
[6 ] Provider: Illuminate\Notifications\NotificationServiceProvider
[7 ] Provider: Illuminate\Pagination\PaginationServiceProvider
[8 ] Provider: Laravel\Tinker\TinkerServiceProvider
[9 ] Tag: flare-config
[10] Tag: ignition-config
[11] Tag: laravel-errors
[12] Tag: laravel-mail
[13] Tag: laravel-notifications
[14] Tag: laravel-pagination
>1
Publishing complete.
.envにAuth0の情報を追記します。
AUTH0_DOMAIN=kiriko.auth0.com
AUTH0_CLIENT_ID=xxxxxxxx
AUTH0_CLIENT_SECRET=xxxxxxxx
情報は"Applications"から作成したApplicationの"Settings"タブで確認できます。

.env内のAPP_URLの設定が"homestead.test"になっていることを確認します。
APP_URL=http://homestead.test
config/auth.phpを修正してuser driverをAuth0にスイッチします。**これでApplictionとAuth0の連携は完了です。**以下、修正後の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',
],
'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' => 'auth0',
'model' => App\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| 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,
],
],
];
Auth0IndexControllerを作成します。/Login, /Logoutルートが使うControllerです。
$ php artisan make:controller Auth/Auth0IndexController
app/Http/Controllers/Auth/Auth0IndexController.phpを編集します。
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class Auth0IndexController extends Controller
{
/**
* Redirect to the Auth0 hosted login page
*
* @return mixed
*/
public function login()
{
$authorize_params = [
'scope' => 'openid profile email',
// Use the key below to get an access token for your API.
// 'audience' => config('laravel-auth0.api_identifier'),
];
return \App::make('auth0')->login(null, null, $authorize_params);
}
/**
* Log out of Auth0
*
* @return mixed
*/
public function logout()
{
\Auth::logout();
$logoutUrl = sprintf(
'https://%s/v2/logout?client_id=%s&returnTo=%s',
env('AUTH0_DOMAIN'),
env('AUTH0_CLIENT_ID'),
env('APP_URL'));
return \Redirect::intended($logoutUrl);
}
}
resources/views/partials/nav.blade.phpを修正します。以下、修正後です。
<nav class="navbar navbar-expand navbar-dark bg-primary">
<div class="navbar-nav w-100">
<a class="navbar-brand text-color" href="/">TravelPlanet</a>
<a class="nav-item nav-link" href="/hotels">Browse Hotels</a>
@if (Route::has('login'))
<div class="ml-auto">
@auth
<a class="nav-item nav-link" href="{{ route('logout') }}">Logout</a>
@else
<a class="nav-item nav-link" href="{{ route('login') }}">Login/Signup</a>
@endauth
</div>
@endif
</div>
</nav>
resources/views/home.blade.phpを修正します。以下、修正後です。
@extends('index') {{-- Specify that we want to extend the index file --}}
@section('title', 'Home') {{-- Set the title content to "Home" --}}
{{-- Set the "content" section, which will replace "@yield('content')" in the index file we're extending --}}
@section('content')
<div class="jumbotron text-light" style="background-image: url('https://cdn.auth0.com/blog/laravel-6-crud/laravel-beach-bg.png')">
<div class="container">
@if(Auth::user())
<h1 class="display-4">Welcome back, {{ Auth::user()->nickname}}!</h1>
<p class="lead">To your one stop shop for reservation management.</p>
<a href="/dashboard" class="btn btn-success btn-lg my-2">View your Dashboard</a>
@else
<h1 class="display-3">Reservation management made easy.</h1>
<p class="lead">Lorem, ipsum dolor sit amet consectetur adipisicing elit. Numquam in quia natus magnam ducimus quas molestias velit vero maiores. Eaque sunt laudantium voluptas. Fugiat molestiae ipsa delectus iusto vel quod.</p>
<a href="/login" class="btn btn-success btn-lg my-2">Sign Up for Access to Thousands of Hotels</a>
@endif
</div>
</div>
<div class="container">
<div class="row">
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Convenient</h5>
<p class="card-text">Manage all your hotel reservations in one place</p>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Best prices</h5>
<p class="card-text">We have special discounts at the best hotels</p>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Easy to use</h5>
<p class="card-text">Book and manage with the click of a button</p>
</div>
</div>
</div>
</div>
</div>
@endsection
動作確認
Chromeでhttp://http://homestead.test/
にアクセスして、右上の"Login/Signup"をクリックします。

Email/Passwordを入力してログインします。


おわりに
Laravelのような便利なフレームワークのおかげで、今はスタイリッシュなWeb Applicationを誰でも簡単に実装できるようになりました。であれば、Web Applicationに必須の認証機能も簡単に実装できるべきかと思います。ソフトウェア開発者の皆様には、ボタン設定で簡単に認証機能を実装できるAuth0を利用して、ワクワクするようなWeb Applicationを早く世に送り出して頂きたいですね。