TIL2025年1月14日 – Laravel の TailwindCSS と ユーザー認証

laravelのTailwindCSS

Tailwind CSS のインストール

npm install tailwindcss postcss autoprefixer

 

Tailwind CSS の初期設定

npx tailwindcss init

 

SCSS のパッケージインストール

npm install sass

 

resources/scss/app.scss の作成

# resources/scss/app.scss
/* Tailwind のインポート */
@use "tailwindcss/base";
@use "tailwindcss/components";
@use "tailwindcss/utilities";

 

 

laravelのユーザー認証

共通レイアウトの作成

# resources/views/layouts/app.blade.php
<html lang="{{ app()->getLocale() }}">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="csrf-token" content="{{ csrf_token() }}">
  <title>@yield('title', 'Laravel App')</title>
  @vite(['resources/css/app.scss', 'resources/js/app.js'])
</head>

<body  id="app">
  <header class="relative flex justify-between bg-white shadow-lg p-6">
      <h2 class="text-center">ページタイトル</h2>
      @auth
        <p class="text-center">ログイン中: {{ Auth::user()->name }}</p>
      @endauth
  </header>
  <main class="flex flex-col items-center justify-center min-h-screen bg-slate-200 py-3">
    <div class="container shadow-md bg-white py-3 px-3 md:px-10">
      @yield('content')
    </div>
  </main>
</body>

</html>

 

 

コントローラーの作成

php artisan make:controller HomeController

 

# app/Http/Controllers/HomeController.php
<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;

class HomeController extends Controller
{
  public function index()
  {
    return view('index');
  }

  public function dashboard()
  {
    return view('dashboard');
  }
}

 

 

トップページの作成

# resources/views/index.blade.php
@extends('layouts.app')

@section('title', 'ページタイトル')

@section('content')
  <section id="top-page">
    <h2 class="text-center mb-4">Welcome</h2>

    <div class="mb-8">
      <ul>
        <li>アプリの説明</li>
      </ul>
    </div>

    @auth
      <p class="text-center mb-8">ログイン中: {{ Auth::user()->name }}</p>
    @endauth

    <div class="flex justify-center">
      @auth
        <a href="{{ route('dashboard') }}"
          class="block w-auto text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 font-medium rounded-full text-sm px-5 py-2.5 text-center me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">ダッシュボード</a>
      @else
        <a href="{{ route('register') }}"
          class="block w-auto text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 font-medium rounded-full text-sm px-5 py-2.5 text-center me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">ユーザー登録</a>
        <a href="{{ route('login') }}"
          class="block w-auto text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 font-medium rounded-full text-sm px-5 py-2.5 text-center me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">ログイン</a>
      @endauth
    </div>
  </section><!-- /#top-page -->
@endsection

 

 

ダッシュボードページの作成

# resources/views/dashboard.blade.php
@extends('layouts.app')

@section('title', 'ダッシュボード')

@section('content')
    <h2 class="text-center mb-4">ダッシュボード</h2>
    <p class="text-center">ログイン中: {{ Auth::user()->name }}</p>
    <div class="d-flex justify-content-center">
        <a href="{{ route('home') }}" class="btn btn-outline-secondary mx-2">トップページに戻る</a>
    </div>
    <form class="text-center mt-4" action="{{ route('logout') }}" method="POST" class="d-inline">
        @csrf
        <button type="submit" class="btn btn-danger mx-2">ログアウト</button>
    </form>
@endsection

 

 

 

Controller.phpの変更

# Controller.php
<?php

namespace AppHttpControllers;

abstract class Controller
{
  //
}

 

# Controller.php(変更後)
<?php

namespace AppHttpControllers;

use IlluminateFoundationAuthAccessAuthorizesRequests;
use IlluminateFoundationValidationValidatesRequests;
use IlluminateRoutingController as BaseController;

class Controller extends BaseController
{
    use AuthorizesRequests, ValidatesRequests;
}

 

 

ルーティングの設定

# routes/web.php
<?php

use IlluminateSupportFacadesRoute;
use AppHttpControllersTaskController;
use AppHttpControllersHomeController;
use AppHttpControllersAuthRegisterController;
use AppHttpControllersAuthLoginController;
use AppHttpControllersAuthLogoutController;

// トップページのルート
Route::get('/', [HomeController::class, 'index'])->name('home');

// ダッシュボードのルート(ログインが必要)
Route::get('/dashboard', [HomeController::class, 'dashboard'])->middleware('auth')->name('dashboard');

// ユーザー登録のルート
Route::get('register', [RegisterController::class, 'showRegistrationForm'])->name('register')->middleware('guest');
Route::post('register', [RegisterController::class, 'register']);

// ログインのルート
Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
Route::post('login', [LoginController::class, 'login']);

// ログアウトのルート
Route::post('logout', [LogoutController::class, 'logout'])->name('logout');

Route::resource('tasks', TaskController::class);

 

 

ユーザー登録コントローラーの作成

php artisan make:controller Auth/RegisterController

 

# app/Http/Controllers/Auth/RegisterController.php
<?php

namespace AppHttpControllersAuth;

use AppHttpControllersController;
use AppModelsUser;
use IlluminateAuthEventsRegistered;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;
use IlluminateSupportFacadesHash;
use IlluminateValidationRules;

class RegisterController extends Controller
{
  /**
   * 登録フォームを表示する
   *
   * @return IlluminateViewView
   */
  public function showRegistrationForm()
  {
    return view('auth.register');
  }

  /**
   * 新しいユーザーインスタンスを作成し、保存する
   *
   * @param  IlluminateHttpRequest  $request
   * @return IlluminateHttpRedirectResponse
   */
  public function register(Request $request)
  {
    // 1. リクエストのデータを検証する
    $request->validate([
      'name' => ['required', 'string', 'max:255'],
      'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
      'password' => ['required', 'confirmed', RulesPassword::defaults()],
    ]);

    // 2. 新しいユーザーを作成する
    $user = User::create([
      'name' => $request->name,
      'email' => $request->email,
      'password' => Hash::make($request->password),
    ]);

    // 3. ユーザー登録イベントを発行する
    event(new Registered($user));

    // 4. ユーザーをログインさせる
    Auth::login($user);

    // 5. ダッシュボードページにリダイレクトする
    return redirect()->route('dashboard');
  }
}

 

 

ユーザー登録フォームのテンプレート作成

 

# resources/views/auth/register.blade.php
@extends('layouts.app')

@section('title', 'ユーザー登録')

@section('content')
  <h2 class="text-center mb-4">ユーザー登録</h2>
  <form method="POST" action="{{ route('register') }}">
    @csrf
    <div class="mb-3">
      <label for="name" class="form-label">ユーザー名</label>
      @error('name')
        <div class="text-danger">{{ $message }}</div>
      @enderror
      <input id="name" type="text" name="name" class="border border-slate-400 p-2 w-full rounded-md"
        value="{{ old('name') }}" autofocus>
    </div>

    <div class="mb-3">
      <label for="email" class="form-label">メールアドレス</label>
      @error('email')
        <div class="text-danger">{{ $message }}</div>
      @enderror
      <input id="email" type="email" name="email" class="border border-slate-400 p-2 w-full rounded-md"
        value="{{ old('email') }}">
    </div>

    <div class="mb-3">
      <label for="password" class="form-label">パスワード</label>
      @error('password')
        <div class="text-danger">{{ $message }}</div>
      @enderror
      <input id="password" type="password" name="password" class="border border-slate-400 p-2 w-full rounded-md">
    </div>

    <div class="mb-3">
      <label for="password-confirm" class="form-label">パスワード確認</label>
      @error('password_confirmation')
        <div class="text-danger">{{ $message }}</div>
      @enderror
      <input id="password-confirm" type="password" name="password_confirmation"
        class="border border-slate-400 p-2 w-full rounded-md">
    </div>
    <div class="flex justify-center flex-row-reverse">
      <button type="submit"
        class="block w-auto text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 font-medium rounded-full text-sm px-5 py-2.5 text-center me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">登録</button>
      <a href="{{ route('home') }}"
        class="w-auto text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 font-medium rounded-full text-sm px-5 py-2.5 text-center me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">戻る</a>
    </div>
  </form>

@endsection

 

ログイン用コントローラークラスの作成

php artisan make:controller Auth/LoginController

 

# app/Http/Controllers/Auth/LoginController.php
<?php

namespace AppHttpControllersAuth;

use AppHttpControllersController;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;

class LoginController extends Controller
{
  public function __construct()
  {
    $this->middleware('guest');  // ログインしていないユーザーのみがアクセス可能
  }

  /**
   * ログインフォームを表示する
   *
   * @return IlluminateViewView
   */
  public function showLoginForm()
  {
    return view('auth.login');
  }

  /**
   * ユーザーのログイン処理を実行する
   *
   * @param  IlluminateHttpRequest  $request
   * @return IlluminateHttpRedirectResponse
   */
  public function login(Request $request)
  {
    // 1. リクエストのデータを検証する
    $credentials = $request->validate([
      'email' => ['required', 'email'],
      'password' => ['required'],
    ]);

    // 2. 認証を試みる
    if (Auth::attempt($credentials, $request->filled('remember'))) {
      // 認証に成功したら、セッションを再生成する
      $request->session()->regenerate();

      // ダッシュボードにリダイレクトする
      return redirect()->intended('dashboard');
    }

    // 認証に失敗した場合は、ログインページにリダイレクトする
    return back()->withErrors([
      'email' => 'ログイン情報が正しくありません。',
    ])->onlyInput('email');
  }
}

 

 

ログアウト用コントローラーの作成

php artisan make:controller Auth/LogoutController

 

# app/Http/Controllers/Auth/LogoutController.php
<?php

namespace AppHttpControllersAuth;

use AppHttpControllersController;
use IlluminateSupportFacadesAuth; // Authファサードのインポート
use IlluminateHttpRequest;

class LogoutController extends Controller
{
  /**
   * ログアウト処理を実行する
   *
   * @param IlluminateHttpRequest $request
   * @return IlluminateHttpRedirectResponse
   */
  public function logout(Request $request)
  {
    // 1. ユーザーをログアウトさせる
    Auth::logout();

    // 2. セッションを無効にする
    $request->session()->invalidate();

    // 3. 新しいCSRFトークンを再生成する
    $request->session()->regenerateToken();

    // 4. トップページにリダイレクトする
    return redirect('/');
  }
}

 

 

ログインフォームのテンプレート作成

# resources/views/auth/login.blade.php
@extends('layouts.app')

@section('title', 'ログイン')

@section('content')
  <section id="login">
    <h2 class="text-center mb-4">ログイン</h2>

    <form action="{{ route('login') }}" method="POST">
      @csrf
      <div class="mb-3">
        <label class="block" for="email">メールアドレス</label>
        <input id="email" class="border border-slate-400 p-2 w-full rounded-md" name="email" type="email"
          value="{{ old('email') }}" autofocus autocomplete="email" required />
        @error('email')
          <div class="text-danger">{{ $message }}</div>
        @enderror
      </div>

      <div class="mb-3">
        <label class="block" for="password">パスワード</label>
        <input id="password" class="border border-slate-400 p-2 w-full rounded-md" name="password" type="password"
          autocomplete="current-password" required />
        @error('password')
          <div class="text-danger">{{ $message }}</div>
        @enderror
      </div>

      <div class="flex justify-center items-center mb-3 form-check">
        <input id="remember"
          class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
          name="remember" type="checkbox" />
        <label class="ml-3" for="remember">ログイン情報を記憶する</label>
      </div>

      <div class="flex justify-center flex-row-reverse">
        <button
          class="block w-auto text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 font-medium rounded-full text-sm px-5 py-2.5 text-center me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
          type="submit">ログイン</button>
        <a href="{{ route('home') }}"
          class="w-auto text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 font-medium rounded-full text-sm px-5 py-2.5 text-center me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">戻る</a>
      </div>
    </form>
  </section><!-- /#login -->
@endsection

 

 

Laravel の validation 日本語化

  • locale の変更
# .env
APP_LOCALE=ja

 

日本語化
# resources/lang/ja/validation.php
<?php

return [

  /*
    |--------------------------------------------------------------------------
    | Validation Language Lines
    |--------------------------------------------------------------------------
    |
    | The following language lines contain the default error messages used by
    | the validator class. Some of these rules have multiple versions such
    | as the size rules. Feel free to tweak each of these messages here.
    |
    */
  'attributes' => [
    'name' => '名前',
  ],

  'accepted'        => ':attributeを承認してください。',
  'active_url'      => ':attributeは、有効なURLではありません。',
  'after'           => ':attributeには、:dateより後の日付を指定してください。',
  'after_or_equal'  => ':attributeには、:date以降の日付を指定してください。',
  'alpha'           => ':attributeには、アルファベッドのみ使用できます。',
  'alpha_dash'      => ":attributeには、英数字('A-Z','a-z','0-9')とハイフンと下線('-','_')が使用できます。",
  'alpha_num'       => ":attributeには、英数字('A-Z','a-z','0-9')が使用できます。",
  'array'           => ':attributeには、配列を指定してください。',
  'before'          => ':attributeには、:dateより前の日付を指定してください。',
  'before_or_equal' => ':attributeには、:date以前の日付を指定してください。',
  'between'         => [
    'numeric' => ':attributeには、:minから、:maxまでの数字を指定してください。',
    'file'    => ':attributeには、:min KBから:max KBまでのサイズのファイルを指定してください。',
    'string'  => ':attributeは、:min文字から:max文字にしてください。',
    'array'   => ':attributeの項目は、:min個から:max個にしてください。',
  ],
  'boolean'              => ":attributeには、'true'か'false'を指定してください。",
  'confirmed'            => ':attributeと:attribute確認が一致しません。',
  'date'                 => ':attributeは、正しい日付ではありません。',
  'date_equals'          => ':attributeは:dateに等しい日付でなければなりません。',
  'date_format'          => ":attributeの形式は、':format'と合いません。",
  'different'            => ':attributeと:otherには、異なるものを指定してください。',
  'digits'               => ':attributeは、:digits桁にしてください。',
  'digits_between'       => ':attributeは、:min桁から:max桁にしてください。',
  'dimensions'           => ':attributeの画像サイズが無効です',
  'distinct'             => ':attributeの値が重複しています。',
  'email'                => ':attributeは、有効なメールアドレス形式で指定してください。',
  'ends_with'            => 'The :attribute must end with one of the following: :values',
  'exists'               => '選択された:attributeは、有効ではありません。',
  'file'                 => ':attributeはファイルでなければいけません。',
  'filled'               => ':attributeは必須です。',
  'gt'                   => [
    'numeric' => ':attributeは、:valueより大きくなければなりません。',
    'file'    => ':attributeは、:value KBより大きくなければなりません。',
    'string'  => ':attributeは、:value文字より大きくなければなりません。',
    'array'   => ':attributeの項目数は、:value個より大きくなければなりません。',
  ],
  'gte'                  => [
    'numeric' => ':attributeは、:value以上でなければなりません。',
    'file'    => ':attributeは、:value KB以上でなければなりません。',
    'string'  => ':attributeは、:value文字以上でなければなりません。',
    'array'   => ':attributeの項目数は、:value個以上でなければなりません。',
  ],
  'image'                => ':attributeには、画像を指定してください。',
  'in'                   => '選択された:attributeは、有効ではありません。',
  'in_array'             => ':attributeが:otherに存在しません。',
  'integer'              => ':attributeには、整数を指定してください。',
  'ip'                   => ':attributeには、有効なIPアドレスを指定してください。',
  'ipv4'                 => ':attributeはIPv4アドレスを指定してください。',
  'ipv6'                 => ':attributeはIPv6アドレスを指定してください。',
  'json'                 => ':attributeには、有効なJSON文字列を指定してください。',
  'lt'                   => [
    'numeric' => ':attributeは、:valueより小さくなければなりません。',
    'file'    => ':attributeは、:value KBより小さくなければなりません。',
    'string'  => ':attributeは、:value文字より小さくなければなりません。',
    'array'   => ':attributeの項目数は、:value個より小さくなければなりません。',
  ],
  'lte'                  => [
    'numeric' => ':attributeは、:value以下でなければなりません。',
    'file'    => ':attributeは、:value KB以下でなければなりません。',
    'string'  => ':attributeは、:value文字以下でなければなりません。',
    'array'   => ':attributeの項目数は、:value個以下でなければなりません。',
  ],
  'max'                  => [
    'numeric' => ':attributeには、:max以下の数字を指定してください。',
    'file'    => ':attributeには、:max KB以下のファイルを指定してください。',
    'string'  => ':attributeは、:max文字以下にしてください。',
    'array'   => ':attributeの項目は、:max個以下にしてください。',
  ],
  'mimes'                => ':attributeには、:valuesタイプのファイルを指定してください。',
  'mimetypes'            => ':attributeには、:valuesタイプのファイルを指定してください。',
  'min'                  => [
    'numeric' => ':attributeには、:min以上の数字を指定してください。',
    'file'    => ':attributeには、:min KB以上のファイルを指定してください。',
    'string'  => ':attributeは、:min文字以上にしてください。',
    'array'   => ':attributeの項目は、:min個以上にしてください。',
  ],
  'not_in'               => '選択された:attributeは、有効ではありません。',
  'not_regex'            => ':attributeの形式が無効です。',
  'numeric'              => ':attributeには、数字を指定してください。',
  'password'             => ':attributeが間違っています',
  'present'              => ':attributeが存在している必要があります。',
  'regex'                => ':attributeには、有効な正規表現を指定してください。',
  'required'             => ':attributeは、必ず指定してください。',
  'required_if'          => ':otherが:valueの場合、:attributeを指定してください。',
  'required_unless'      => ':otherが:values以外の場合、:attributeを指定してください。',
  'required_with'        => ':valuesが指定されている場合、:attributeも指定してください。',
  'required_with_all'    => ':valuesが全て指定されている場合、:attributeも指定してください。',
  'required_without'     => ':valuesが指定されていない場合、:attributeを指定してください。',
  'required_without_all' => ':valuesが全て指定されていない場合、:attributeを指定してください。',
  'same'                 => ':attributeと:otherが一致しません。',
  'size'                 => [
    'numeric' => ':attributeには、:sizeを指定してください。',
    'file'    => ':attributeには、:size KBのファイルを指定してください。',
    'string'  => ':attributeは、:size文字にしてください。',
    'array'   => ':attributeの項目は、:size個にしてください。',
  ],
  'starts_with'          => ':attributeは、次のいずれかで始まる必要があります。:values',
  'string'               => ':attributeには、文字を指定してください。',
  'timezone'             => ':attributeには、有効なタイムゾーンを指定してください。',
  'unique'               => '指定の:attributeは既に使用されています。',
  'uploaded'             => ':attributeのアップロードに失敗しました。',
  'url'                  => ':attributeは、有効なURL形式で指定してください。',
  'uuid'                 => ':attributeは、有効なUUIDでなければなりません。',

  /*
    |--------------------------------------------------------------------------
    | Custom Validation Language Lines
    |--------------------------------------------------------------------------
    |
    | Here you may specify custom validation messages for attributes using the
    | convention "attribute.rule" to name the lines. This makes it quick to
    | specify a specific custom language line for a given attribute rule.
    |
    */

  'custom' => [
    'attribute-name' => [
      'rule-name' => 'custom-message',
    ],
  ],

  /*
    |--------------------------------------------------------------------------
    | Custom Validation Attributes
    |--------------------------------------------------------------------------
    |
    | The following language lines are used to swap our attribute placeholder
    | with something more reader friendly such as "E-Mail Address" instead
    | of "email". This simply helps us make our message more expressive.
    |
    */
];

 

 

laravel のテンプレート的使用方法

  • 既存のプロジェクトをコピーし、コピーしたプロジェクトの.env を編集します
# .env
DB_DATABASE=kadaikanriserviceapp_db

 

  • キャッシュを削除します
php artisan config:clear                                      
php artisan cache:clear
php artisan route:clear
php artisan view:clear

 

  • composer.lock を削除します
rm -rf vendor composer.lock   

 

  • composer install を実行します
composer install

 

  • db を作成します
mysql -u root -p -e "CREATE DATABASE kadaikanriserviceapp_db;"

 

  • migrate を実行します
php artisan migrate

 

  • key を生成します
php artisan key:generate

 

  • git の設定を削除します
rm -rf .git

 

  • commit します
git init                                                      
git add .
git commit -m "Initial commit for new project"

 

  • リモートリポジトリを作成して、push します
git branch -M main                                            
git remote add origin https://github.com/****.git
git push -u origin main

 

 

laravel の composer は、ruby の gem

  • composer.jsonにGemfileと同様に、バージョンなどが記載されています。