tailwindCSS環境構築とミニファイ化

tailwind CLI

前提 nodeJSnpmがインストールされていること。

1- Tailwind CSS をインストールする

npm install -D tailwindcss
npx tailwindcss init

npmの初期化

npm init -y

2- テンプレートのパスを構成する tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

3- Tailwind ディレクティブを CSS に追加します src/input.css

@tailwind base;
@tailwind components;
@tailwind utilities;

4- Tailwind CLI ビルド プロセスを開始する

npx tailwindcss -i ./src/input.css -o ./src/output.css --watch

5- HTML で Tailwind の使用を開始する src/index.html

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="./output.css" rel="stylesheet">
</head>
<body>
  <h1 class="text-3xl font-bold underline">
    Hello world!
  </h1>
</body>
</html>

スクリプト登録とエラーの削除

  1. ビルドプロセスをスクリプトに登録 package.json
    これでnpm run buildでビルドされるようになる。
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "npx tailwindcss -i ./src/input.css -o ./src/output.css --watch"
  },

ミニファイ化

build.cssがプロジェクトディレクトリ直下に作られる。
デプロイはミニファイファイルにて。

npx tailwindcss -o build.css --minify

Laravel ECサイト step03

認証

Laravel Breeze追加

  • breezeパッケージの追加 sail composer require'laravel/breeze:*' --dev
    :* の意味「利用可能な任意のバージョン」
  • Laravel Breezeパッケージのインストール
    sail artisan breeze:install
質問 回答
Which Breeze stack would you like to install?(どのBreezeスタックをインストールしますか?) 0
Would you like dark mode support?(ダークモードをサポートしますか?) no
Would you prefer Pest tests instead of PHPUnit?(PHPUnitの代わりにPestを使いますか?) no
  • ルーティングの復元
<?php

use App\Http\Controllers\ProfileController;
+use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');

Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});

require __DIR__.'/auth.php';

+Route::resource('products', ProductController::class);
  • 依存関係のインストール npm install

  • アセットのビルド
    npm run build

urlにアクセスするとログインと会員登録ページのリンクが追加されている。

Laravel Breezeエラーメッセージの日本語化

以下のコマンドを実行

php -r "copy('https://readouble.com/laravel/8.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');"

php -f install-ja-lang.php

php -r "unlink('install-ja-lang.php');"

  1. 日本語化ファイルをインストールするためのスクリプトを、install-ja-lang.phpという名前でダウンロードする
  2. 1でダウンロードしたinstall-ja-lang.phpファイルを実行する(resources/lang/jaフォルダの中に各種日本語化ファイルが生成される)
  3. 不要になったinstall-ja-lang.phpファイルを削除する

-> resources/lang/jaフォルダの中に生成されたvalidation.phpというファイルには、日本語化されたエラーメッセージが記述されています。

configフォルダ内のapp.phpファイルを開き、以下のように編集

config/app.php

    /*
    |--------------------------------------------------------------------------
    | 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' => 'Asia/Tokyo',

    /*
    |--------------------------------------------------------------------------
    | 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',
+    'locale' => 'ja',

    /*
    |--------------------------------------------------------------------------
    | 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',

各ページの表記の日本語化

resources/lang/ja.jsonファイルを作成
ja.json

{
      "Email": "メールアドレス",
      "Password": "パスワード",
      "Remember me": "ログイン状態を保存する",
      "Log in": "ログイン",
      "Forgot your password?": "パスワードをお忘れですか?",
      "Name": "名前",
      "Confirm Password": "パスワード(確認用)",
      "Register": "登録",
      "Already registered?": "すでに登録済みですか?",
      "Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.": "パスワードをお忘れですか?ご心配なく。メールアドレスをお知らせいただければ、パスワード再設定用のリンクをメールでお送りします。",
      "Email Password Reset Link": "パスワード再設定用リンクの送信",
      "Reset Password": "パスワード再設定",
      "Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn't receive the email, we will gladly send you another.": "ご登録ありがとうございます!ご利用を開始する前に、メールアドレスをご確認ください。メールが届いていない場合は、再度お送りいたします。",
      "A new verification link has been sent to the email address you provided during registration.": "ご登録いただいたメールアドレスに新しい認証リンクを送信しました。",
      "Resend Verification Email": "認証メールの再送信",
      "Profile": "アカウント",
      "Log Out": "ログアウト",
      "Dashboard": "ダッシュボード",
      "You're logged in!": "ログインしました!",
      "Profile Information": "アカウント情報",
      "Update your account's profile information and email address.": "アカウント情報とメールアドレスを更新できます。",
      "Your email address is unverified.": "メールアドレスが未確認です。",
      "Click here to re-send the verification email.": "こちらをクリックして認証メールを再送信してください。",
      "A new verification link has been sent to your email address.": "あなたのメールアドレスに新しい認証リンクを送信しました。",
      "Save": "保存",
      "Saved.": "保存しました。",
      "Update Password": "パスワード更新",
      "Ensure your account is using a long, random password to stay secure.": "アカウントの安全性を保つために、長くてランダムなパスワードを使用してください。",
      "Current Password": "現在のパスワード",
      "New Password": "新しいパスワード",
      "Delete Account": "アカウント削除",
      "Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.": "アカウントを削除すると、関連するすべてのデータが永久に削除されます。アカウントを削除する前に、必要に応じてデータをバックアップしてください。",
      "Are you sure you want to delete your account?": "本当にアカウントを削除してもよろしいですか?",
      "Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "アカウントを削除すると、関連するすべてのデータが永久に削除されます。アカウントを削除してよろしければ、パスワードを入力してください。",
      "Cancel": "キャンセル",
      "Verify Email Address": "メールアドレス確認",
      "Hello!": "こんにちは!",
      "Please click the button below to verify your email address.": "下のボタンをクリックしてメールアドレスを確認してください。",
      "If you did not create an account, no further action is required.": "心当たりがない場合は、本メールを破棄してください。",
      "Regards": "よろしくお願いいたします",
      "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "「:actionText」ボタンを押しても何も起きない場合、以下のURLをコピーしてWebブラウザに貼り付けてください。",
      "Reset Password Notification": "パスワード再設定のお知らせ",
      "You are receiving this email because we received a password reset request for your account.": "あなたのアカウントでパスワード再設定のリクエストがありました。",
      "This password reset link will expire in :count minutes.": "このパスワード再設定リンクの有効期限は :count 分です。",
      "If you did not request a password reset, no further action is required.": "心当たりがない場合は、本メールを破棄してください。"
    }

メール認証の有効化

  • メール認証機能を有効

app/Models/User.phpを編集

<?php

namespace App\Models;

-// use Illuminate\Contracts\Auth\MustVerifyEmail;
+use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

-class User extends Authenticatable
+class User extends Authenticatable implements MustVerifyEmail
{
    //======== 中略 ========
}
  • Laravelプロジェクトとメールサーバーを連携

    1. Gmailの2段階認証プロセスを有効に
      Googleアカウントのトップページ
      「アプリパスワード」 > 「App name」にアプリ名を入力してパスワードを生成
    2. GmailSMTPサーバーに設定
      .envを編集
#========  前略 ========

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
-MAIL_HOST=mailpit
+MAIL_HOST=smtp.gmail.com
-MAIL_PORT=1025
+MAIL_PORT=587
-MAIL_USERNAME=null
+MAIL_USERNAME=アプリパスワードを発行したアカウントのGmailアドレス
-MAIL_PASSWORD=null
+MAIL_PASSWORD=アプリパスワード
-MAIL_ENCRYPTION=null
+MAIL_ENCRYPTION=tls
-MAIL_FROM_ADDRESS="hello@example.com"
+MAIL_FROM_ADDRESS=アプリパスワードを発行したアカウントのGmailアドレス
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

#========  後略 ========

リダイレクト先を変更

  • 会員登録後 app/Http/Controllers/Auth/RegisteredUserController.phpを編集
//========  前略 ========

    /**
     * Handle an incoming registration request.
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function store(Request $request): RedirectResponse
    {
        $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class],
            'password' => ['required', 'confirmed', Rules\Password::defaults()],
        ]);

        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);

        event(new Registered($user));

        Auth::login($user);

-        return redirect(RouteServiceProvider::HOME);
+        return redirect('/verify-email');
    }
}
  • ログイン後
    app/Providers/RouteServiceProvider.phpを編集
<?php

namespace App\Providers;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;

class RouteServiceProvider extends ServiceProvider
{
    /**
     * The path to your application's "home" route.
     *
     * Typically, users are redirected here after authentication.
     *
     * @var string
     */
-     public const HOME = '/dashboard';
+     public const HOME = '/';

//======== 後略 ========

Laravel ECサイト step02

CRUD

Create

  • 商品モデルを作成
カラム名 説明 Laravel関数 MySQLのデータ型
name 商品名 string varchar(255)
description 商品の説明文 text text
price 価格 integer int(11)

sail artisan make:model Product -m

database\migrations\XXXX_XX_XX_XXXXXX_create_products_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
+            $table->string('name');
+            $table->text('description');
+            $table->integer('price')->unsigned();
   /**
     *  マイナスの値が保存できないようにしている。
     */
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('products');
    }
};
  • 商品のコントローラーを作成
    CRUDをまとめて作成するので「--resource」オプション
    sail artisan make:controller ProductController --resource --model=Product

  • カテゴリと商品とを紐づけ

app\Models\Product.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasFactory;
+
+    public function category()
+    {
+        return $this->belongsTo(Category::class);
+    }
}

app\Models\Category.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    use HasFactory;
+
+    public function products()
+    {
+        return $this->hasMany(Product::class);
+    }
}

マイグレーションファイルdatabase\migrations\XXXX_XX_XX_XXXXXX_create_products_table.phpに1行追加
新しいカラムとして、カテゴリのIDを追加

カラム名 説明 Laravel関数 MySQLのデータ型
category_id カテゴリID integer int(11)
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description');
            $table->integer('price')->unsigned();
+            $table->integer('category_id')->unsigned();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('products');
    }
};

Eloquentは外部キーカラムを自動的に決定します。規約により、親モデルの小文字クラス名に「_id」という接尾辞を付けます。
この場合では、Categoryモデルに対する外部キーカラムはcategory_idとなり、商品とカテゴリが紐づきます。

カテゴリーIDにはマイナスの値はあり得ないので、unsignedを指定しています。

<?php

use Illuminate\Support\Facades\Route;
+use App\Http\Controllers\ProductController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});
+
+Route::resource('products', ProductController::class);
  • コントローラー
    app\Http\Controllers\ProductController.php
    • indexアクション
   public function index()
    {
-        //
+        $products = Product::all();
+
+        return view('products.index', compact('products'));
    }

indexで呼び出すviewファイル resources\views\products\index.blade.php

+<a href="{{ route('products.create') }}"> Create New Product</a>
+
+<table>
+    <tr>
+        <th>Name</th>
+        <th>Description</th>
+        <th>Price</th>
+        <th>Category ID</th>
+        <th >Action</th>
+    </tr>
+    @foreach ($products as $product)
+    <tr>
+        <td>{{ $product->name }}</td>
+        <td>{{ $product->description }}</td>
+        <td>{{ $product->price }}</td>
+        <td>{{ $product->category_id }}</td>
+        <td>
+            <a href="{{ route('products.show',$product->id) }}">Show</a>
+            <a href="{{ route('products.edit',$product->id) }}">Edit</a>
+        </td>
+    </tr>
+    @endforeach
+</table>
  • createアクション
   public function create()
    {
-        //
+        return view('products.create');
    }

データ入力フォーム
resources\views\products\create.blade.php

+<div>
+    <h2>Add New Product</h2>
+</div>
+<div>
+    <a href="{{ route('products.index') }}"> Back</a>
+</div>
+
+<form action="{{ route('products.store') }}" method="POST">
+    @csrf
+
+    <div>
+        <strong>Name:</strong>
+        <input type="text" name="name" placeholder="Name">
+    </div>
+    <div>
+        <strong>Description:</strong>
+        <textarea style="height:150px" name="description" placeholder="Description"></textarea>
+    </div>
+    <div>
+        <strong>Price:</strong>
+        <input type="number" name="price" placeholder="Price">
+    </div>
+    <div>
+        <button type="submit">Submit</button>
+    </div>
+
+</form>
  • storeアクション
    app\Http\Controllers\ProductController.php
   public function store(Request $request)
    {
-        //
+        $product = new Product();
+        $product->name = $request->input('name');
+        $product->description = $request->input('description');
+        $product->price = $request->input('price');
+        $product->save();
+
+        return to_route('products.index');
    }
  • showアクション app\Http\Controllers\ProductController.php
   public function show(Product $product)
    {
-        //
+        return view('products.show', compact('product'));
    }

viewファイル作成
resources\views\products\show.blade.php

+<div>
+    <h2> Show Product</h2>
+</div>
+<div>
+    <a href="{{ route('products.index') }}"> Back</a>
+</div>
+
+<div>
+    <strong>Name:</strong>
+    {{$product->name}}
+</div>
+
+<div>
+    <strong>Description:</strong>
+    {{$product->description}}
+</div>
+
+<div>
+    <strong>Price:</strong>
+    {{$product->price}} 
+</div>
  • editアクション
   public function edit(Product $product)
    {
-        //
+        return view('products.edit', compact('product'));
    }

resources\views\products\edit.blade.php

+<div>
+    <h2>Edit Product</h2>
+</div>
+<div>
+    <a href="{{ route('products.index') }}"> Back</a>
+</div>
+
+<form action="{{ route('products.update',$product->id) }}" method="POST">
+    @csrf
+    @method('PUT')
+
+    <div>
+        <strong>Name:</strong>
+        <input type="text" name="name" value="{{ $product->name }}" placeholder="Name">
+    </div>
+    <div>
+        <strong>Description:</strong>
+        <textarea style="height:150px" name="description" placeholder="description">{{ $product->description }}</textarea>
+    </div>
+    <div>
+        <strong>Price:</strong>
+        <input type="number" name="price"  value="{{ $product->price }}">
+    </div>
+    <div>
+        <button type="submit">Submit</button>
+    </div>
+
+</form>
  • updateアクション
   public function update(Request $request, Product $product)
    {
-        //
+        $product->name = $request->input('name');
+        $product->description = $request->input('description');
+        $product->price = $request->input('price');
+        $product->update();
+
+        return to_route('products.index');
    }
  • destroyアクション
   public function destroy(Product $product)
    {
-        //
+        $product->delete();
+ 
+        return to_route('products.index');
    }

resources\views\products\index.blade.phpを変更

<a href="{{ route('products.create') }}"> Create New Product</a>

<table>
    <tr>
        <th>Name</th>
        <th>Description</th>
        <th>Price</th>
        <th>Category ID</th>
        <th >Action</th>
    </tr>
    @foreach ($products as $product)
    <tr>
        <td>{{ $product->name }}</td>
        <td>{{ $product->description }}</td>
        <td>{{ $product->price }}</td>
        <td>{{ $product->category_id }}</td>
        <td>
+            <form action="{{ route('products.destroy',$product->id) }}" method="POST">
                <a href="{{ route('products.show',$product->id) }}">Show</a>
                <a href="{{ route('products.edit',$product->id) }}">Edit</a>
+                @csrf
+                @method('DELETE')
+                <button type="submit">Delete</button>
+            </form>
        </td>
    </tr>
    @endforeach
</table>
  • 商品登録時にカテゴリを選択できるようにしよう

    1. app\Http\Controllers\ProductController.phpにモデルを追加
<?php

namespace App\Http\Controllers;
use App\Models\Product;
+use App\Models\Category;
use Illuminate\Http\Request;

class ProductController extends Controller
/* === 後略 === */
  1. app\Http\Controllers\ProductController.phpのcreateアクションを修正
    public function create()
    {
-        return view('products.create');
+        $categories = Category::all();
+ 
+        return view('products.create', compact('categories'));
    }
  1. resources\views\products\create.blade.phpを修正
<div>
    <h2>Add New Product</h2>
</div>
<div>
    <a href="{{ route('products.index') }}"> Back</a>
</div>

<form action="{{ route('products.store') }}" method="POST">
    @csrf
  
    <div>
        <strong>Name:</strong>
        <input type="text" name="name" placeholder="Name">
    </div>
    <div>
        <strong>Description:</strong>
        <textarea style="height:150px" name="description" placeholder="Description"></textarea>
    </div>
    <div>
        <strong>Price:</strong>
        <input type="number" name="price" placeholder="Price">
    </div>
+    <div>
+        <strong>Category:</strong>
+        <select name="category_id">
+        @foreach ($categories as $category)
+        <option value="{{ $category->id }}">{{ $category->name }}</option>
+        @endforeach
+        </select>
+    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
   
</form>
  1. app\Http\Controllers\ProductController.phpのstoreアクションでcategory_idをデータベースに保存できるように修正
   public function store(Request $request)
    {
        $product = new Product();
        $product->name = $request->input('name');
        $product->description = $request->input('description');
        $product->price = $request->input('price');
+        $product->category_id = $request->input('category_id');
        $product->save();

        return to_route('products.index');
    }

app\Http\Controllers\ProductController.phpのedhitアクションを変更

    public function edit(Product $product)
    {
-        return view('products.edit', compact('product'));
+        $categories = Category::all();
+ 
+        return view('products.edit', compact('product', 'categories'));
    }
  1. resources\views\products\edit.blade.phpを修正
<div>
    <h2>Edit Product</h2>
</div>
<div>
    <a href="{{ route('products.index') }}"> Back</a>
</div>

<form action="{{ route('products.update',$product->id) }}" method="POST">
    @csrf
    @method('PUT')

    <div>
        <strong>Name:</strong>
        <input type="text" name="name" value="{{ $product->name }}" placeholder="Name">
    </div>
    <div>
        <strong>Description:</strong>
        <textarea style="height:150px" name="description" placeholder="description">{{ $product->description }}</textarea>
    </div>
    <div>
        <strong>Price:</strong>
        <input type="number" name="price"  value="{{ $product->price }}">
    </div>
+    <div>
+        <strong>Category:</strong>
+        <select name="category_id">
+        @foreach ($categories as $category)
+            @if ($category->id == $product->category_id)
+                <option value="{{ $category->id }}" selected>{{ $category->name }}</option>
+            @else
+                <option value="{{ $category->id }}">{{ $category->name }}</option>
+            @endif
+        @endforeach
+        </select>
+    </div>
    <div>
        <button type="submit">Submit</button>
    </div>

</form>
  1. app\Http\Controllers\ProductController.phpのupdadeアクションを修正
   public function update(Request $request, Product $product)
    {
        $product->name = $request->input('name');
        $product->description = $request->input('description');
        $product->price = $request->input('price');
+        $product->category_id = $request->input('category_id');
        $product->update();

        return to_route('products.index');
    }

ダミーデータの追加

Fakerのランダム値の日本語化。設定ファイルを編集
config/app.php

'faker_locale' => 'ja_JP',
  • ファクトリ
    コマンド sail artisan make:factory ProductFactory

database/factories/ProductFactory.php

<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends
\Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Product>
*/
class ProductFactory extends Factory

{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'name' => fake()->name(),
'description' => fake()->realText(50, 5),
'price' => fake()->numberBetween(100, 200000),
'category_id' => 1,
];
}
}
  • シーダ
    コマンド sail artisan make:seeder ProductSeeder database/seeders/ProductSeecer.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Product;
class ProductSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// ProductFactoryクラスで定義した内容にもとづいてダミーデータを20件生成
し、productsテーブルに追加する
Product::factory()->count(20)->create();
}
}
  • ファクトリとシーダークラスの作成後にコマンド実行でダミーデータを追加
    コマンド sail artisan db:seed --class=ProductSeeder

Laravel ECサイト step01

ひな型作り

  • プロジェクトを作成
curl -s https://laravel.build/example-app | bash
//======== 前略 ========

|--------------------------------------------------------------------------
    | 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',
+    'timezone' => 'Asia/Tokyo',

//======== 後略 ========

データベースを作成

docker-compose.yml

phpmyadmin:
        image: phpmyadmin/phpmyadmin
        links:
            - mysql:mysql
        ports:
            - 8080:80
        environment:
            #PMA_USER: "${DB_USERNAME}"
            #PMA_PASSWORD: "${DB_PASSWORD}"
            PMA_HOST: mysql
        networks:
            - sail
  • phpMyAdmin上で新規データベースを作成

  • 設定ファイル
    .env

- DB_DATABASE=laravel
+DB_DATABASE=samuraimart

カテゴリのモデルとマイグレーションファイルを生成

sail artisan make:model Category -m
-mオプションはマイグレーションファイルも同時に作成するオプション

カラム名 説明 Laravel関数 MySQLのデータ型
name カテゴリ名 string varchar(255)
description カテゴリの説明文 text text
major_category_name カテゴリの大分類 string varchar(255)

database\migrations\XXXX_XX_XX_XXXXXX_create_categories_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
+            $table->string('name');
+            $table->text('description');
+            $table->string('major_category_name');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('categories');
    }
};

Tinkerでカテゴリーを追加

sail artisan tinkerTinkerを起動

Psy Shell v0.11.8 (PHP 8.1.6 — cli) by Justin Hileman
>>>

例えば以下のような、Categoryモデルのデータを取得するコードを、コントローラーに実装する前に確認できます。
App\Models\Category::all()

[!] Aliasing 'Category' to 'App\Models\Category' for this Tinker session.
=> Illuminate\Database\Eloquent\Collection {#4284
     all: [],
   }

Tinkerを使ってカテゴリを作成

$category = new App\Models\Category() >> => App\Models\Category {#3566}

$category->major_category_name = "Food" >> => "Food"

$category->name = "meat" >> => "meat"

$category->description = "description for meat." >> => "description for meat."

$category->save() >> => true

データが作成されているかCategoryモデルのデータを取得するコードを実行
App\Models\Category::all()

=> Illuminate\Database\Eloquent\Collection {#4500
     all: [
       App\Models\Category {#4498
         id: 1,
         name: "meat",
         description: "description for meat.",
         major_category_name: "Food",
         created_at: "2022-08-06 21:52:58",
         updated_at: "2022-08-06 21:52:58",
       },
     ],
   }
  • 終了

exit

シーダーでカテゴリを追加

php artisan make:seeder CategoriesTableSeeder >> INFO Seeder created successfully.

database\seeders\CategoriesTableSeeder.php

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
+use App\Models\Category;

class CategoriesTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
-        //
+        $major_category_names = [
+            '本', 'コンピュータ', 'ディスプレイ'
+        ];
+
+        $book_categories = [
+            'ビジネス', '文学・評論', '人文・思想', 'スポーツ',
+            'コンピュータ・IT', '資格・検定・就職', '絵本・児童書', '写真集',
+            'ゲーム攻略本', '雑誌', 'アート・デザイン', 'ノンフィクション'
+        ];
+
+        $computer_categories = [
+            'ノートPC', 'デスクトップPC', 'タブレット' 
+        ];
+
+        $display_categories = [
+            '19~20インチ', 'デスクトップPC', 'タブレット' 
+        ];
+
+        foreach ($major_category_names as $major_category_name) {
+            if ($major_category_name == '本') {
+                foreach ($book_categories as $book_category) {
+                    Category::create([
+                        'name' => $book_category,
+                        'description' => $book_category,
+                        'major_category_name' => $major_category_name
+                    ]);
+                }
+            }
+
+            if ($major_category_name == 'コンピュータ') {
+                foreach ($computer_categories as $computer_category) {
+                    Category::create([
+                        'name' => $computer_category,
+                        'description' => $computer_category,
+                        'major_category_name' => $major_category_name
+                    ]);
+                }
+            }
+
+            if ($major_category_name == 'ディスプレイ') {
+                foreach ($display_categories as $display_category) {
+                    Category::create([
+                        'name' => $display_category,
+                        'description' => $display_category,
+                        'major_category_name' => $major_category_name
+                    ]);
+                }
+            }
+        }
    }
}

作成したシーダーファイルを読み込むためのコマンドを実行
sail composer dump-autoload

全テーブルを削除
php artisan migrate:fresh

シーダーでカテゴリーデータを作成
sail artisan db:seed --class=CategoriesTableSeeder

phpMyAdminでテーブルにデータが追加されているか確認する。

Laravel ミニブログ作成4 投稿のCRUD機能

投稿のコントローラーPostControllerを作成

sail artisan make:controller PostController

Read

投稿一覧(indexアクション) と 投稿詳細(showアクション)の登録

投稿一覧

  1. 現在ログイン中のユーザーに属するすべての投稿を取得し、作成日時が新しい順に並べ替える
    「現在ログイン中のユーザー」を取得するには、Authファサードを利用
    Auth::user()
  2. 1で取得した投稿を変数$postsに代入し、ビューに渡す
    Userモデルのインスタンス(ここではAuth::user())に対してposts()メソッドが使えるようになった理由は、ミニブログ作成3 投稿用テーブルとモデルでUserモデルとPostモデルのリレーションシップを設定したから。

投稿詳細

  • 補足:アクション内でモデルのインスタンスを受け取る
    アクションの引数でモデルの型宣言を行うと、アクション内でモデルのインスタンスを直接受け取ることができます。
    /routes/web.php
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');

/app/Http/Controllers/PostController

// Postモデルのインスタンスを受け取る
public function show(Post $post)
{
    return view('posts.show', compact('post'));
}

このようにモデルのインスタンスを直接受け取ることでコードが減り、可読性が上がります。

ただしこの場合、ビュー内においてroute()ヘルパ関数の第2引数にモデルのインスタンスを渡す必要があるので注意しましょう(ビューは次節で作成します)。

<a href="{{ route('posts.show', $post) }}">詳細</a>

 

app/Http/Controllers/PostController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use App\Models\Post;

class PostController extends Controller
{
+    // 一覧ページ
+    public function index()
+    {
+        $posts = Auth::user()->posts()->orderBy('created_at', 'desc')->get();
+
+        return view('posts.index', compact('posts'));
+    }

+     // 詳細ページ
+    public function show(Post $post)
+   {
+         return view('posts.show', compact('post'));
+    }
}

投稿詳細

Laravel シーディングとファクトリ

シーディング

データベースにダミーデータを追加

シーダークラスを作成

sail artisan make:seeder シーダークラス名
database/seedersフォルダの中にシーダクラスが生成される

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class ProductSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        //
    }
}

クエリビルダで追加

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class ProductSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        DB::table('products')->insert([
            'product_name' => 'ノート5冊セット',
            'price' => 600,
            'created_at' => '2023-06-01 00:00:00',
            'updated_at' => '2023-06-01 00:00:00',
            'vendor_code' => 1111
        ]);
    }
}

Eloquent ORMで追加

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Product;

class ProductSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $product = new Product([
            'product_name' => 'ノート5冊セット',
            'price' => 600,
            'vendor_code' => 1111        
        ]);
        $product->save();
    }
}

シーダークラスを実行

  • --classオプションに指定したシーダークラスのみが実行
sail artisan db:seed --class=シーダークラス名
  • --classオプションをつけない場合、database/seedersフォルダの中にあらかじめ用意されているDatabaseSeederクラスが実行
sail artisan db:seed

複数のシーダークラスを一度に実行するにはDatabaseクラス内で他のシーダークラスを呼び出す
database/seeders/DatabaseSeeder

<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call([
+            シーダー1Seeder::class,
+            シーダー2Seeder::class            
        ]);        
    }
}
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class ProductSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        //
    }
}

ファクトリ

ダミーデータを生成

  • ファクトリの命名規則
    「対象となるテーブル名の単数形(モデル名)+Factory」という名前
    ファクトリではこの命名規則に従うことで、Laravel側がモデルとファクトリを自動的に紐づけてくれます。

例えばproductsテーブル(Productモデル)用のファクトリであればProductFactory、vendorsテーブル(Vendorモデル)用のファクトリであればVendorFactoryです。このように命名することで、Laravel側はProductモデルとProductFactory、VendorモデルとVendorFactoryを自動的に紐づけてくれます。

  • Fakerのランダム値の日本語化
    config/app.php
    'faker_locale'の値に'ja_JP'を指定。
//======== 前略 ========

    /*
    |--------------------------------------------------------------------------
    | 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' => 'ja_JP',

//======== 後略 ========

ファクトリを作成

sail artisan make:factory ファクトリ名

database/factoriesフォルダの中にファクトリが生成される。
definition()メソッドの中で以下のように連想配列を返すことで、ダミーデータを生成

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Vendor>
 */
class VendorFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'vendor_code' => fake()->randomNumber(5, true),
            'vendor_name' => fake()->company()                       
        ];
    }
}

fake()ヘルパ関数を使うことで、ファクトリからFakerというPHPライブラリにアクセスでき、ダミーデータ用のさまざまなランダム値を生成
Fakerの代表的なメソッド

メソッド 生成されるランダム値 引数
name() 姓名 木村 舞 'male'を指定した場合は男性の姓名、'female'を指定した場合は女性の姓名が生成される(オプション)。
lastName() 工藤 なし
firstName() 裕太 'male'を指定した場合は男性名、'female'を指定した場合は女性名が生成される(オプション)。
kanaName() カタカナの姓名 ササダ アケミ 'male'を指定した場合は男性の姓名、'female'を指定した場合は女性の姓名が生成される(オプション)。
lastKanaName() カタカナの姓 イシダ なし
firstKanaName() カタカナの名 シュウヘイ 'male'を指定した場合は男性名、'female'を指定した場合は女性名が生成される(オプション)。
email() メールアドレス(ドメイン部分もランダムなので、実在する可能性がある) hideki.fujimoto@aoyama.org なし
safeEmail() 安全なメールアドレス(ドメイン部分が数種類の中から固定であり、実在しない) akira61@example.com なし
address() 住所 8983502 長崎県伊藤市北区鈴木町宮沢1-10-8 ハイツ中村109号 なし
postcode() 郵便番号 2746333 なし
city() 田辺市 なし
streetName() 山岸町 なし
streetAddress() 町以降の住所 石田町西之園4-2-9 なし
secondaryAddress() 建物以降の住所 ハイツ吉本101号 なし
phoneNumber() 電話番号 06-1841-5463 なし
company() 会社名 株式会社 山口 なし
realText() 文章 ぼくには一昨日おとといたいへん元気な便たよりがあったんだ。レールを七つ組み合わせました。そのとき舟。 第1引数は最大文字数(最小値は10、初期値は200)。第2引数はインデックスサイズ(1〜5を指定、初期値は2)。インデックスサイズが大きいほど自然な文章になる。
randomNumber() 整数 8424749 第1引数は桁数(1〜9を指定、初期値はnull)。第2引数は桁数を固定するかどうか(trueまたはfalseを指定、初期値はfalse)。桁数を固定しない場合(第2引数がfalseの場合)、1桁から第1引数で指定した桁数までの整数が生成される。
numberBetween() 指定した範囲内の整数 1615557015 第1引数(初期値は0)から第2引数(初期値は2147483647)までの整数が生成される。
dateTime() 日時 1976-07-20 02:35:16 第1引数は日時の最大値(初期値は'now')。第2引数はタイムゾーン(初期値はシステムのデフォルトのタイムゾーン)。
date() 日付 1988-01-13 第1引数はフォーマット(初期値は'Y-m-d')、第2引数は日付の最大値(初期値は'now')。
time() 時間 15:28:38 第1引数はフォーマット(初期値は'H:i:s')、第2引数は時間の最大値(初期値は'now')。
dateTimeBetween() 指定した期間内の日時 2003-05-26 09:45:02 第1引数(初期値は'-30 years')から第2引数(初期値は'now')までの日時が生成される。第3引数はタイムゾーン(初期値はシステムのデフォルトのタイムゾーン)。

ファクトリでダミーデータの各カラムの値を定義したら、あとはシーダークラスで以下のようにモデル名::factory()->create();と記述すれば、ダミーデータをテーブルに追加できます。

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Vendor;

class VendorSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void {
        // VendorFactoryクラスで定義した内容にもとづいてダミーデータを生成し、vendorsテーブルに追加する
        Vendor::factory()->create();
    }
}

以下のようにcount()メソッドを使うことで、生成するダミーデータの数を指定することもできます。

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Vendor;

class VendorSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void {
        // VendorFactoryクラスで定義した内容にもとづいてダミーデータを5つ生成し、vendorsテーブルに追加する
        Vendor::factory()->count(5)->create();
    }
}