ল্যারাভেলে মিনিমাল ইকমার্স অ্যাপ

আমরা খুব মিনিমাল একটা অ্যাপ তৈরি করব। যেখানে শুধু মাত্র এডমিন ইউজার প্রোডাক্ট যুক্ত করতে পারবে। এছাড়া অন্যান্য ইউজাররা প্রোডাক্ট কার্টে যুক্ত করতে পারবে। অর্ডার প্লেস করতে পারবে। অর্ডার গুলো ইউজারের প্রোফাইলে সেভ থাকবে।

সিম্পল প্রোডাক্ট, যেখানে শুধু মাত্র নাম এবং দাম যোগ করা যাবে।

প্রোডাক্ট কার্টে এড করার পর কার্টঃ

অর্ডার গুলো প্লেস করার পর প্রোফাইলে দেখাবেঃ

আমি ইউআই এবং ফাংশনালিটিতে বেশি ফোকাস করিনি। একটা ইকমার্স অ্যাপ কিভাবে তৈরি করা যেতে পারে, তাই ছিল মূল লক্ষ্য। অনলাইনের বেশির ভাগ টিইউটোরিয়াল অনেক বিশাল! এই জন্যই এই মিনিমাল টিউটোরিয়াল। এই অ্যাপে আমি বেসিক জিনিস গুলো রাখার চেষ্টা করেছি। এখানে পেমেন্ট অপশন, এবং এডমিন অর্ডার স্ট্যাটাস ইত্যাদি যোগ করা যেতে পারে।

আমাদের দুই লেভেলের ইউজার লাগবে। একটা সাধারণ ইউজার, একটা এডমিন ইউজার। আমরা একই ইউজার টেবিলেই role নামে একটা কলাম করে ঐখানে এই লেভেল তৈরি করতে পারি। কিভাবে মাল্টিলেভেল ইউজার তৈরি করা যায়, তা এই লেখায় বিস্তারিত লিখেছিঃ ল্যারাভেলে মাল্টি লেভেল অথেনটিকেশন

প্রজেক্ট তৈরি এবং সেটআপ

শুরু করা যাক। প্রথমে একটা ল্যারাভেল প্রজেক্ট তৈরি করে নিব। ল্যারাভেল ব্রিজ যদি না এড করে থাকেন, তাহলে ব্রিজ যুক্ত করে নিতে হবেঃ

composer require laravel/breeze --dev
php artisan breeze:install

ডেটাবেজঃ

ল্যারাভেলে ডেটাবেজ হিসেবে SQLite সিলেক্ট করা থাকে। আমাদের এই প্রজেক্টের জন্য SQLite ই যথেষ্ঠ। তাই ডেটাবেজের কোন কিছু পরিবর্তন করতে হবে না। database ফোল্ডারের ভেতর databse.sqlite ফাইল ওপেন করে ডেটাবেজের ডেটা দেখা যাবে। ল্যারাভেল এবং ডেটাবেজ সম্পর্কে বিস্তারিত এই লেখায় জানা যাবে।

মাল্টি লেভেল ইউজার সেটআপ

…create_user_table.php মাইগ্রেশন ফাইলে role নামে নতুন স্কিমা যোগ করব। $table->enum('role',['admin','user'])->default('user'); :

        Schema::create('users', function (Blueprint $table) {
            $table->enum('role',['admin','user'])->default('user');
...

আমাদের জন্য দুইটা ভিন্ন ইউজার তৈরি জন্য এভাবে DatabseSeeder.php ফাইলের run মেথড আপডেট করবঃ

    public function run(): void
    {
        // User::factory(10)->create();

        User::factory()
        ->count(2)
        ->state(new Sequence(
            [
                'name' =>  'Admin',
                'email' => '[email protected]',
                'password' => Hash::make('password'),
                'role' => 'admin',
            ],
            [
                'name' =>  'User',
                'email' => '[email protected]',
                'password' => Hash::make('password'),
                'role' => 'user',
            ]
        ))
        ->create();
    }

use Illuminate\Database\Eloquent\Factories\Sequence; এবং use Illuminate\Support\Facades\Hash; ইম্পোর্ট করে নিতে হবে। এবার ডেটাবেজ মাইগ্রেশন এবং সিড করে নিবঃ

php artisan migrate:fresh --seed

আমাদের একটা মিডেলওয়ার তৈরি করতে হবে। যেন নির্দিষ্ট ইউজার লেভেল চেক করা যায়। তার জন্য Role নামে একটা মিডেলওয়ার তৈরি করে নিবঃ

php artisan make:middleware Role

এই ফাইলটি তৈরি হবে Middleware ফোল্ডারের ভেতর। Role.php handle নামে একথা মেথড বিল্টইন থাকবে। যা এভাবে রিরাইট করবঃ

public function handle(Request $request, Closure $next, $role): Response
{
    if (auth()->check() && auth()->user()->role === $role) {
        return $next($request);
    }
 
    abort(403, 'Unauthorized action.');
}

এই Role মিডেলওয়ার রেজিট্রার করতে হবে। আর তা করতে হবে bootstrap>app.php ফাইলে। withMiddleware এর ভেতর নিচের মিডেলওয়ার এলিয়াস যোগ করবঃ

$middleware->alias([
         'role' => \App\Http\Middleware\Role::class
         ]);

ইজার সেটআপ করা শেষ।

প্রোডাক্ট যুক্ত করা

প্রোডাক্ট যুক্ত করার জন্য আমরা প্রোডাক্ট মডেল, কন্ট্রোলার এবং ডেটাবেজ মাইগ্রেশন ফাইল তৈরি করে নিব। সব গুলো এক সাথে তিতি করতেঃ

php artisan make:model Product -cmr

প্রোডাক্ট ডেটাবেজ স্কিমা

database/migrations/…._create_products_table.php ফাইল ওপেন করব। ডিফল্ট ভাবে ডেটাবেজ স্কিমায় ID এবং টাইমস্ট্যাপ যোগ থাকে। আমরা আরো দুইটা কলাম যোগ করব। title এবং price:

    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->decimal('price', 8, 2);
            $table->timestamps();
        });
    }

এরপর ডেটাবেজ মাইগ্রেট করে নিবঃ

php artisan migrate

প্রোডাক্ট তৈরির জন্য routes> web.php ফাইলে রিসোর্স রুট যোগ করব। এবং use App\Http\Controllers\ProductController; ইম্পোর্ট করে নিব।

Route::resource('product', ProductController::class);

ProductController ক্লাসে আমরা ইনিশিয়ালি index, create, store, show মেথড গুলো নিয়ে কাজ করব। আশা করি কিভাবে edit, update, destroy মেথড নিয়ে কাজ করা যায়, তা ইতিমধ্যে জানেন। যদি না জেনে থাকেন, তাহলে সাজেস্ট করব ল্যারাভেলে পূর্ণাঙ্গ CRUD – নোট অ্যাপ লেখটা পড়তে।

সবার আগে create মেথড নিয়ে কাজ করি। ক্রিয়েট মেথডে আমরা একটা ফরম দেখাবো। তাই শুধু এভাবে create ভিউ রিটার্ণ করব।

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

create ভিউ এখনো তৈরি করিনি। views ফোল্ডারের create.blade.php ফাইল তৈরি করিঃ

<x-layout>
    <div class="w-full bg-slate-50 px-5 py-5">
        <form class="space-y-5" action="{{ route('product.store') }}" method="POST">
            @csrf
            <h3>Add new product</h3>

            <input type="text" id="title" name="title"
                class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
                placeholder="Product Title">
                @error('title')
                <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative">{{ $message }}</div>
                @enderror

                <input   type="text" inputmode="numeric" id="price" name="price"
                class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
                placeholder="Product Price">
                @error('price')
                <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative">{{ $message }}</div>
                @enderror


            <button type="submit"
                class="inline-flex items-center px-5 py-2.5 text-sm font-medium text-center text-white bg-blue-700 rounded-lg focus:ring-4 focus:ring-blue-200 hover:bg-blue-800">
                Save product
            </button>
        </form>
    </div>
</x-layout>

Tailwind CSS ব্যবহার করে উপরের ফরম তৈরি করেছি। উপড়ে আমরা <x-layout> </x-layout> ট্যাগের ভেতর কোড গুলো লেখার মানে হচ্ছে Views>Componens ফোল্ডারের layout.blade.php ফাইলকে এক্সটেন্ড করা। এর জন্য Views ফোল্ডারে যদি Componens ফোল্ডার না থাকে, থালে Componens ফোল্ডার তৈরি করে নিব। এরপর এর ভেতরে layout.blade.php নামক ফাইল তৈরি করবঃ

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.tailwindcss.com/"></script>
</head>
<body>
    <nav class="bg-gray-800">
        <div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
            <div class="relative flex h-16 items-center justify-between">

                <div class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
                    <div class="flex flex-shrink-0 items-center">
                        <img class="h-8 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500"
                            alt="Your Company">
                    </div>
                    <div class="hidden sm:ml-6 sm:block">
                        <div class="flex space-x-4">
                            <a href="/"
                                class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Home</a>
                            <a href="/product"
                                class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">All
                                Products</a>
                            <a href="/product/create"
                                class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">New
                                Product</a>
                            <a href="/cart"
                                class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Cart</a>

                            <a href="/order"
                                class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Order</a>

                            <a href="/dashboard"
                                class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Dashboard</a>
                        </div>
                    </div>
                </div>
            </div>
    </nav>

    <main class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
        {{ $slot }}
    </main>
</body>
</html>

এই ফাইল হচ্ছে আমাদের অ্যাপের মূল কন্টেইনার। বিভিন্ন ফ্রন্টএন্ড ভিউতে এই ফাইল এক্সটেন্ড করে ব্যবহার করব।

এতটুকু পর্যন্ত কাজ করলে আমরা http://127.0.0.1:8000/product/create ভিজিট করতে পারব এবং উপরের ফরম দেখব। পাশাপাশি welcome.blade.php এর কোড একটু পরিবর্তন করি। তাহলে হোমপেইজেও আমাদের লেআউট ফাইল ও ন্যাভিগেশন যুক্ত হবে।

<x-layout>
    <div class="text-center">
        <h1 class="mt-4 text-3xl font-bold tracking-tight text-gray-900 sm:text-5xl">Welcome page</h1>
        <p class="mt-6 text-base leading-7 text-gray-600">Something awesome is coming!</p>
    </div>
</x-layout>

যদিও ফরম প্রসেস করে প্রোডাক্ট ডেটা ডেটাবেজে স্টোর করার জন্য এখনো কোড লিখিনি। এবার ProductController.php te store মেথড এভাবে লিখিঃ

    public function store(Request $request){
        $data = $request->validate([
            'title' => ['required', 'string'],
            'price' => ['required', 'numeric']
        ]);

        $product = Product::create($data);

        return to_route('product.show', $product);
    }

Model>Product.php মডেল ওপেন করে use HasFactory; লাইনের নিচে নিচের কোড যুক্ত করিঃ

    protected $fillable = ['title', 'price'];

এবার আমরা চাইলে প্রোডাক্ট যুক্ত করতে পারব। ডেটাবেজে যা দেখাবে। প্রোডাক্ট তৈরি পর product.show রুটে রিটার্ণ করবে। ProductController.php ফাইলে show মেথড এভাবে লিখিঃ

    public function show(Product $product) {
        return view("single-product", ['product' => $product]);
    }

এবং Views ফোল্ডারে single-product.blade.php ফাইল তৈরি করিঃ

<x-layout>
    <div  class="block m-6 p-6 bg-white border border-gray-200 rounded-lg shadow ">
        <h2 class="mb-2 text-2xl font-bold"> {{ $product->title }}</h2>
        <p class="mt-6"> Product Price: ${{ $product->price }}</p>
        <p class="mt-6"> Product Id: {{ $product->id }}</p>

        <div class="flex justify-end mt-10  ">

            <form action="" method="POST">
                @csrf
                <input type="hidden" name="product_id" value="{{ $product->id }}">
                <input type="number" name="quantity" value="1" min="1" class="form-control" style="width: 60px;">
                <button type="submit" class="inline-flex items-center px-5 py-2.5 text-sm font-medium text-center text-white bg-green-700 rounded-lg hover:bg-green-800">
                    Add to Cart</button>
            </form>
        </div>
    </div>
</x-layout>

এবার প্রোডাক্ট তৈরি পর সব প্রোডাক্ট আমরা দেখতে পাবো।

সব গুলো প্রোডাক্ট দেখার জন্য ProductController.php ফাইলে index মেথড এভাবে লিখিঃ

    public function index(){
        $products = Product::query()
            ->orderBy('created_at', 'desc')
            ->paginate(10);
        return view("product-index", ['products' => $products]);
    }

এবং প্রোডাক্ট গুলো দেখানোর জন্য product-index.blade.php ফাইল তৈরি করে নেইঃ

<x-layout>
    <div class="text-left mr-auto w-full  ">
        @foreach ($products as $product)
            <div class="block max-w-sm m-5 p-6 float-left  bg-white border border-gray-200 rounded-lg shadow ">
                <a href="{{ route('product.show', $product) }}">
                    <h5 class="mb-2 text-2xl">Product name:
                        {{ $product->title }}</h5>
                </a>
                <p class="">Price: ${{ $product->price }}</p>
                <form action="" method="POST" class="mt-10">
                    @csrf
                    <input type="hidden" name="product_id" value="{{ $product->id }}">
                    <input type="number" name="quantity" value="1" min="1" class="font-large"
                        style="width: 60px;">
                    <button type="submit"
                        class="inline-flex items-center  px-5 py-2.5 text-sm font-medium text-center text-white bg-green-700 rounded-lg   hover:bg-green-800">
                        Add to Cart</button>
                </form>
            </div>
        @endforeach
        {{ $products->links() }}
    </div>
</x-layout>

আমরা বলেছি শুধু মাত্র এডমিন ইউজার প্রোডাক্ট তৈরি করতে পারবে। আমরা তাই index এবং show মেথড ছাড়া বাকি মেথড গুলো পাবলিক ইউজার এবং রেগুলার ইউজারের জন্য রেস্ট্রিক্ট করে দিব। তার করতে পারি ProductController এ কনস্ট্রাকশন যুক্ত করেঃ

    public function __construct()
    {
        $this->middleware(['auth', 'role:admin'], ['except' => ['index', 'show']]);
    }

ProductController.php ফাইলে Controller এর পরিবর্তে BaseController এক্সটেন্ড করতে হবে। আর এর জন্য রাউটিং কন্ট্রোলার ইম্পোর্ট করে নিতে হবে use Illuminate\Routing\Controller as BaseController; এর মাধ্যমে।

প্রোডাক্ট যোগ করা, এবং প্রোডাক্ট দেখানোর কাজ শেষ। আমরা সম্পূর্ণ ProductController.php:

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Product;
use Illuminate\Routing\Controller as BaseController;
class ProductController extends BaseController{
    public function __construct()
    {
        $this->middleware(['auth', 'role:admin'], ['except' => ['index', 'show']]);
    }
    public function index(){
        $products = Product::query()
            ->orderBy('created_at', 'desc')
            ->paginate(10);
        return view("product-index", ['products' => $products]);
    }
    public function create(){
        return view('create');
    }
    public function store(Request $request){
        $data = $request->validate([
            'title' => ['required', 'string'],
            'price' => ['required', 'numeric']
        ]);
        $product = Product::create($data);
        return to_route('product.show', $product);
    }
    public function show(Product $product){
        return view("single-product", ['product' => $product]);
    }
    public function edit(string $id){
        //
    }
    public function update(Request $request, string $id){
    }
    public function destroy(Product $product){
        $product->delete();
        return to_route('product.index', $product);
    }
}

এতটুকু করতে পারলে আমরা প্রোডাক্ট এড করা এবং প্রোডাক্ট গুলো দেখতে পাবো।

কার্ট তৈরি

কার্টে তৈরি আইটেম গুলো লোকাল স্টোরেজে স্টোর করব আমরা। ডেটাবেজের সাথে কোন সম্পর্ক নেই। তো এর জন্য আমরা প্রথমে CartController তৈরি করে নিবঃ

php artisan make:controller CartController

কার্টের মূল অপারেশন হচ্ছে কার্টে প্রোডাক্ট যোগ করা, কার্টের আইটেম লিস্ট এবং কার্ট আইটেম গুলো অর্ডার প্লেস করা। index, add এবং placeOrder।

এই মেথড গুলো লেখার পূর্বে রুট গুলো যোগ করে নেই। যেন টেস্ট করতে সুবিধা হয়। routes>web.php ফাইলে এই রুট গুলো যোগ করি।

// Routes for Cart operations
Route::middleware(['auth'])->group(function () {
    Route::get('/cart', [CartController::class, 'index'])->name('cart.index');
    Route::post('/cart/add', [CartController::class, 'add'])->name('cart.add');
    Route::post('/cart/place-order', [CartController::class, 'placeOrder'])->name('cart.placeOrder');
});

প্রথমে যোগ করি add মেথড। যেহেতু প্রোডাক্ট মডেল নিয়ে কাজ করব, তাই use App\Models\Product; CartController.php ফাইলে ইনক্লুড করে নেই। এরপর নিচের মেথড যোগ করিঃ

    // Add a product to the cart
    public function add(Request $request)
    {
        // Validate product ID and quantity
        $request->validate([
            'product_id' => 'required|exists:products,id',
            'quantity' => 'required|integer|min:1'
        ]);

        $product = Product::find($request->product_id);
        $cart = session()->get('cart', []);

        // Check if the product is already in the cart
        if (isset($cart[$product->id])) {
            // Increment the quantity if it already exists
            $cart[$product->id]['quantity'] += $request->quantity;
        } else {
            // Add the product to the cart
            $cart[$product->id] = [
                'title' => $product->title,
                'price' => $product->price,
                'quantity' => $request->quantity,
            ];
        }

        // Save the cart back to the session
        session()->put('cart', $cart);

        return redirect()->route('cart.index')->with('success', 'Product added to cart successfully!');
    }

উপরে প্রতিটা কোডের কাজ কমেন্ট আকারে যুক্ত করা আছে। এই কার্ট আইটেম মূলত সেশন ব্যবহার করে লোকাল স্টোরেজে স্টোর করবে।

এর আগে product-index এবং single-product ভিউতে Add to Cart বাটন যোগ করেছি। ঐ দুইটা বাটন একটা ফরমের ভেতর রয়েছে। action ভ্যালুতে {{ route('cart.add') }} যোগ করি।

              <form action="{{ route('cart.add') }}" method="POST" class="mt-10">
                    @csrf
                    <input type="hidden" name="product_id" value="{{ $product->id }}">
                    <input type="number" name="quantity" value="1" min="1" class="font-large"
                        style="width: 60px;">
                    <button type="submit"
                        class="inline-flex items-center  px-5 py-2.5 text-sm font-medium text-center text-white bg-green-700 rounded-lg   hover:bg-green-800">
                        Add to Cart</button>
                </form>

এখন যদি Add to Cart বাটনে ক্লিক করি, তাহলে কার্টে প্রোডাক্ট যুক্ত হবে। যদিও এখনো আমরা cart-index ফাইল তৈরি করিনি, তাই এরর দেখাবে।

তবে তার আগে CartController.php ফাইলে index মেথড যোগ করিঃ

        // Retrieve cart items from session
        $cart = session()->get('cart', []);

        // Calculate the total price
        $total = array_reduce($cart, function ($sum, $item) {
            return $sum + $item['price'] * $item['quantity'];
        }, 0);

        return view('cart-index', compact('cart', 'total'));
    }

এই মেথডে আমরা সেশন থেকে কার্ট আইটেম গুলো কোয়েরি করে cart-index ভিউতে পাস করেছি। Views ফোল্ডারের ভেতর cart-index.blade.php ফাইল তৈরি করে নিচের কোড যোগ করিঃ

<x-layout>
    <div class="text-left mr-auto w-full">

        <h2 class="text-2xl font-bold">Your Cart</h2>

        @foreach ($cart as $id => $item)
            <div class="block mt-6 p-6 bg-white border border-gray-200 rounded-lg shadow">

                <h5 class="mb-2 text-2xl font-bold">{{ $item['title'] }}</h5>
                <p>Count: {{ $item['quantity'] }}</p>
                <p>Total: {{ $total }}</p>
            </div>
        @endforeach

        @if (count($cart) > 0)
            <form action="" method="POST">
                @csrf
                <button type="submit"
                    class="inline-flex  mt-6  items-center px-5 py-2.5 text-sm font-medium text-center text-white bg-green-700 rounded-lg  hover:bg-green-800">
                    Place Order</button>
            </form>
        @endif

    </div>
</x-layout>

এবার যদি কার্টে কোন আইটেম যোগ করি, তা /cart রুটে দেখা যাবে।

কত কিছু করে ফেলছি না? এবার হচ্ছে কার্ট থেকে অর্ডার প্লেস করার ফাংশনালিটি যোগ করা। যেহেতু ডেটাবেজে অর্ডার গুলো স্টোর করে রাখতে হবে, তাই আমাদের মোটামুটি বেশি কিছু স্টেপ নিতে হবে। শুরু করা যাক!

অর্ডার সিস্টেম তৈরি

অর্ডার সিস্টেম তৈরির জন্য আমাদের একটা Order মডেল তৈরি করতে হবে। পাশাপাশি কন্ট্রোলার এবং মাইগ্রেশন ফাইল তৈরি করে নিব।

php artisan make:model Order -cmr

মাইগ্রেশন ফাইল তৈরির আগে ডেটাবেজ স্কিমা দেখা দরকার। নিচের ডেটাবেজ ডায়াগ্রাম ভালো করে লক্ষ্য করি। এর আগে user টেবিল নিয়ে কাজ করেছি। products টেবিল নিয়ে কাজ করেছি। নতুন যোগ করতে হবে order এবং order_items নিয়ে।

ডেটাবেজ রিলেশনশিপ বুঝি এবারঃ

  • একটা ইউজার একাধিক অর্ডার তৈরি করতে পারবে।
  • আবার একটা অর্ডারের ভেতর একাধিক আইটেম থাকতে পারবে। পাশাপাশি নির্দিষ্ট অর্ডারে একটা আইটেম কতগুলো অর্ডার করেছে, তাও থাকতে হবে।

শুধু মাত্র অর্ডার টেবিল যদি আমরা তৈরি করতাম, উপরের বিষয় গুলো সুন্দর ভাবে ম্যানেজ করা যেত না। তাই order টেবিলের পাশা পাশি order_items নামে আরেকটা টেবিল ব্যবহার করব আমরা।

আমাদের OrderItem মডেলও লাগবে। পাশা পাশি লাগবে ডেটাবেজ মাইগ্রেশন ফাইল। এই ক্ষেত্রে আলাদা কোন কন্ট্রোলার লাগবে না। তাই নিচের মত করে কমান্ড লিখে OrderItem মডেল এবং মাইগ্রেশন ফাইল তৈরি করে নিবঃ

php artisan make:model OrderItem -m

Order টেবিল ডেটাবেজ স্কিমা এবং রিলেশনশিপঃ

orders টেবিলে শুধু মাত্র অর্ডার আইডি এবং ইউজার আইডি লাগবে। id কলাম ডিফল্ট ভাবে এড করা থাকে। তাই আমরা শুধু user_id কলাম যোগ করব। _create_orders_table.php টেবিল ওপেন করে নিচের মত করে up মেথডের ভেতর স্কিমা লিখি। শুধু $table->foreignId('user_id')->constrained()->onDelete('cascade'); লাইন যোগ করতে হবে।

        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->timestamps();
        });

এখানে শুধু মাত্র user_id রাখব। তাই protected $fillable = [‘user_id’, ]; যোগ করব।

রিলেশনশিপঃ

একজন ইউজারের আন্ডারে একাধিক অর্ডার থাকতে হবে। তাই belongsTo(User::class) রিলেশন যোগ করতে হবে। আবার একটা অর্ডারে একাধিক আইটেম থাকতে পারে, তাই hasMany(OrderItem::class); রিলেশন যোগ করতে হবে। সম্পূর্ণ Order.php মডেলঃ

namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
    use HasFactory;
    protected $fillable = ['user_id', 'product_ids'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function items()
    {
        return $this->hasMany(OrderItem::class);
    }
}

OrderItem মডেলের ডেটাবেজ স্কিমা এবং রিলেশনশিপঃ

order_items টেবিলে order_id, product_id এবং ঐ প্রোডাক্টের quantity যোগ করব। তাই _create_order_items_table.php এভাবে স্কিমা লিখিঃ

        Schema::create('order_items', function (Blueprint $table) {
            $table->id();
            $table->foreignId('order_id')->constrained()->onDelete('cascade');
            $table->foreignId('product_id')->constrained()->onDelete('cascade');
            $table->integer('quantity'); // Column to store the quantity of each product
            $table->timestamps();
        });

রিলেশনশিপঃ প্রতিটা অর্ডারের আইটেম গুলো একটা অর্ডার এবং একটা প্রোডাক্টের আন্ডারে হবে। তাই এভাবে OrderItem.php মডেলে রিলেশনশিপ লিখবঃ

namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class OrderItem extends Model
{
    use HasFactory;
    protected $fillable = ['order_id', 'product_id', 'quantity'];

        // Define the relationship with the Order model
        public function order()
        {
            return $this->belongsTo(Order::class);
        }
        // Define the relationship with the Product model
        public function product()
        {
            return $this->belongsTo(Product::class);
        }
}

order_id, product_id, quantity এগুলো order_items টেবিলে জগ করতে পারবে। তাই fillable প্রোপার্টিও যোগ করেছি।

এগুলো শেষে ডেটাবেজ মাইগ্রেট করে নিবঃ

php artisan migrate

কার্ট থেকে অর্ডার প্লেস করা

উপরের স্টেপ গুলো ফলো করলে কার্ট থেকে অর্ডার প্লেস করার জন্য আমরা প্রস্তুত। তার আগে রুট যোগ করতে হবে।

তার জন্য OrderController কে রিসোর্স রুট হিসেবে যোগ করব। যেহেতু অর্ডার গুলো ডেটাবেজে স্টোর হওয়ার জন্য ইউজার লাগবে, তার জন্য auth মিডেলওয়ার যোগ করবঃ

Route::resource('order', OrderController::class)->middleware('auth');

আর এর পাশাপাশি use App\Http\Controllers\OrderController; ইম্পোর্ট করে নিতে হবে।

CartController এ placeOder মেথড যোগ করবঃ

    public function placeOrder()
    {
        $cart = session()->get('cart', []);
        if (empty($cart)) {
            return redirect()->route('cart.index')->with('error', 'Your cart is empty!');
        }

        // Create the order for the logged-in user
        $order = Order::create([
            'user_id' => Auth::id(),
        ]);

        // Iterate through the cart and create order items
        foreach ($cart as $productId => $item) {
            OrderItem::create([
                'order_id' => $order->id,
                'product_id' => $productId,
                'quantity' => $item['quantity'], // Store the quantity of each product
            ]);
        }

        // Clear the cart after placing the order
        session()->forget('cart');

        return redirect()->route('order.index')->with('success', 'Order placed successfully!');
    }

এছাড়া নিচের ফাইল গুলো ইম্পোর্ট করতে হবেঃ

use App\Models\OrderItem;
use App\Models\Order;
use Illuminate\Support\Facades\Auth;

এখানে প্রথমে সেশন থেকে কার্টে থাকা আইটেম গুলো নিচ্ছি। এরপর একটা orders টেবিলে একটা অর্ডার যোগ করছি। কার্টে থাকা প্রতিটা আইটেম এবং ঐ অর্ডার আইডি order_items টেবিলে যোগ করছি।

cart-index.blade.php ফাইল ওপেন করব। Place Order বাটনের ফরম একশনে {{ route(‘cart.placeOrder’) }} রুট যোগ করবঃ

            <form action="{{ route('cart.placeOrder') }}" method="POST">
                @csrf
                <button type="submit"
                    class="inline-flex  mt-6  items-center px-5 py-2.5 text-sm font-medium text-center text-white bg-green-700 rounded-lg  hover:bg-green-800">
                    Place Order</button>
            </form>

এবার যদি আমরা কার্ট পেইজ থেকে Place Order এ ক্লিক করি, তাহলে অর্ডার প্লেস হবে। যা আমাদের orders.index রুটে নিয়ে যাবে। ঐ রুটে এখনো কোন ভিউ রিটার্ণ করেনি, তাই শূন্য একটা পেইজ দেখব। সমাধানের জন্য order-list.blade.php নামে একটা ভিউ তৈরি করিঃ

<x-layout>
    <div class="text-left mr-auto w-full">

        <h2 class="text-2xl font-bold">Your Orders</h2>

        @forelse ($orders as $order)
            <div class="block mt-6 p-6 bg-white border border-gray-200 rounded-lg shadow">
                <h2 class="text-2xl">
                    Order #{{ $order->id }} - Placed on {{ $order->created_at->format('d M Y, H:i') }}
                </h2>

                <ul class="">
                    @foreach ($order->items as $item)
                        <li class=" mt-6 p-6 bg-white border border-gray-200">
                            {{ $item->product->title }}
                            <span>Quantity: {{ $item->quantity }}</span>
                            <span>Price: ${{ number_format($item->product->price * $item->quantity, 2) }}</span>
                        </li>
                    @endforeach
                </ul>

            </div>
        @empty
            <p>You have no orders yet.</p>
        @endforelse

    </div>
</x-layout>

এবং OrderController.php ফাইল ওপেন করে এর index মেথডে নিচের মত করে লিখবঃ

    public function index()
    {
        // Retrieve all orders for the logged-in user, along with their items and products
        $orders = Order::with('items.product')
            ->where('user_id', Auth::id()) // Filter orders by the authenticated user's ID
            ->orderBy('created_at', 'desc') // Optionally order by creation date
            ->get();
        // Return the view with the user's orders
        return view('order-list', compact('orders'));
    }

এখানে লগডইন ইউজারের অর্ডার গুলো দেখাবে।

কনগ্র্যাচুলেশন! আমরা একটা মিনি ইকমার্স সাইট তৈরি করেছি। যেখানে একটা ইকমার্স সাইটের বেসিক বিষয় গুলো কভার করা হয়েছে। এবার চাইলে এখানে পেমেন্ট মেথড যোগ করে পূর্ণাঙ্গ রূপ দেওয়া যেতে পারে। এছাড়া প্রোডাক্টে ইমেজ যোগ করা যেতে পারে। এগুলো খুব একটা কঠিন কাজ না। চেষ্টা করে দেখতে পারেন।

কোড পাওয়া যাবে গিটহাবে। এছাড়া ল্যারাভেল নিয়ে অন্যান্য লেখা গুলো পাওয়া যাবে বাংলায় ল্যারাভেল পেইজে

কোন কোড যদি কাজ না করে, তাহলে গিটহাব প্রজেক্টটি ডাউনলোড করে নির্দিষ্ট ফাইল দেখে নিতে পারেন। এছাড়া কোন সমস্যা ফেইস করলে কমেন্টে জানানোর অনুরোধ থাকল।

আমি ফ্রন্টেন্ডে খুব একটা ফোকাস দেইনি। টেলউইন্ড সিএসএস এর দারুণ সব রেডি কম্পোনেন্ট পাওয়া যায় অনলাইনে। চাইলে সেগুলো ব্যবহার করে ইউআই ইম্প্রুভ করতে পারেন।

প্রোডাক্টে ইমেজ নিয়ে কিভাবে কাজ করবেন, তা জানতে পরের লেখা পড়তে পারেনঃ

Leave a Reply