ল্যারাভেলে ইমেজ নিয়ে কাজ করা

আমরা যখন কোন অ্যাপ তৈরি করি, ডেটাবেজে নির্দিষ্ট মডেলের বিভিন্ন ডেটার পাশা পাশি ইমেজও স্টোর করার দরকার পড়ে। ইমেজ ডেটাবেজে সরাসরি রাখা হয় না। রাখা হয় শুধু ইমেজের নাম। আমরা যখন ইমেজ আপলোড করি, তা একটা পাবলিক ফোল্ডারে আপলোড করি। এরপর ঐ ফোল্ডারের URLটা ডেটাবেজে স্টোর করি।

তাহলে বুঝা গেলো নির্দিষ্ট মডেলের ডেটাবেজ স্কিমা তৈরির সময় image নামে অন্য একটা স্ট্রিং কলাম যোগ করে ইমেজ নিয়ে কাজ করতে পারব।

এর আগে আমরা ল্যারাভেলে পূর্ণাঙ্গ CRUD – নোট অ্যাপ নিয়ে কাজ করেছি। কিভাবে মিনিমাল ইকমার্স অ্যাপ নিয়ে কাজ করা যায়, তা নিয়ে কাজ করেছি। ঐ দুইটা অ্যাপে ইমেজ যুক্ত করিনি। চাইলে ঐ দুইটা অ্যাপের যে কোন একটা নিয়ে কাজ করতে পারেন। অথবা নতুন একটা ল্যারাভেল অ্যাপ তৈরি করে নিতে পারেন।

নতুন অ্যাপ তৈরি করলে নির্দিষ্ট মডেলের ডেটাবেজ স্কিমায় ইমেজ ফিল্ড যুক্ত করে নিতে পারেন।

  $table->string('image')->nullable();

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

এর জন্য আমরা products টেবিলে নতুন image ফিল্ড যোগ করার জন্য একটা মাইগ্রেশন ফাইল তৈরি করে নিবঃ

php artisan make:migration add_image_to_products_table --table=products

add_image_to_products_table.php ফাইলের up ebong down মেথড এভাবে লিখবঃ

    public function up(): void
    {
        Schema::table('products', function (Blueprint $table) {
            $table->string('image')->nullable()->after('price');
        });
    }
    public function down(): void
    {
        Schema::table('products', function (Blueprint $table) {
            $table->dropColumn('image');
        });
    }

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

php artisan migrate

এভাবে মাইগ্রেশন ফাইল তৈরি করলে আগের ডেটা ডেটাবেজে থেকে যাবে। ডেটা ডিলেট হবে না। যদি আমাদের ডেটাবেজের ডেটা গুরুত্বপূর্ণ না হয়, তাহলে নির্দিষ্ট মডেলের মাইগ্রেশন ফাইল যেমন _create_products_table.php এ এভাবে up মেথডে নতুন image কলাম যোগ করলেই হবেঃ

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

এরপর মাইগ্রেশনের জন্য এভাবে কমান্ড লিখতে হবেঃ

php artisan migrate:refresh --seed

এর ফলে ডেটাবেজের সব কিছু ডিলেট হয়ে যাবে এবং পুনরায় ডেটাবেজের কলাম গুলো তৈরি করে দিবে। এছাড়া ইউজার গুলো তৈরি করে দিবে।

এরপর নির্দিষ্ট মডেলে $fillable প্রোপার্টিটে ‘image’ যোগ করতে হবে। যেমন app/Models/Product.php ফাইলে লিখব এভাবেঃ

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

ইমেজ আপলোড ফরম

ইমেজ আপলোড ফরম ট্যাগের মধ্যে enctype="multipart/form-data" যুক্ত করতে হবে। না করলে The image field must be an image. এরর দেখাবে। এছাড়া ইমেজ আপলোড করার জন্য ফাইল সিলেক্টর ইনপুট যোগ করত হবে। এরপর শুধু মাত্র ফাইল সিলেক্ট করার ইনপুট যোগ করলেই হবেঃ

<label for="image">Product Image:</label>
<input type="file" name="image" class="form-control" accept="image/*" value="{{ old('image') }}">

যেমন প্রোডাক্ট যোগ করার ফরম আমরা এভাবে লিখবঃ

        <form class="space-y-5" action="{{ route('product.store') }}" method="POST" enctype="multipart/form-data">
            @csrf
            <h3>Add new product</h3>

            <input type="text" id="title" name="title" value="{{ old('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">

                <input   type="text" inputmode="numeric" id="price" name="price" value="{{ old('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">

            
                    <label for="image">Product Image:</label>
                    <input type="file" name="image" class="form-control" accept="image/*" value="{{ old('image') }}">
               
            <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>

ইমেজ প্রসেস করা এবং ডেটাবেজে ইমেজের নাম সেভ করা

ইউজার যদি ইমেজ সিলেক্ট করে তাহলে ঐ ইমেজ সার্ভারের নির্দিষ্ট লোকেশনে স্টোর করব। যেমন public ফোল্ডারের ভেতর images/products লোকেশনে ইমেজ গুলো আপলোড করার জন্য এভাবে কোড লিখব। এরপর ঐ ইমেজের শুধু লোকেশনটা আমরা ডেটাবেজে স্টোর করব।

            $imagePath = null;
             if ($request->hasFile('image')) {
                 $imagePath = $request->file('image')->store('images/products', 'public'); // Store the image in the public disk
             }

ProductController এর সম্পূর্ণ create মেথডঃ

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

    public function store(Request $request)
    {
        $data = $request->validate([
            'title' => ['required', 'string'],
            'price' => ['required', 'numeric'],
            'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048', // Validation for image file
        ]);

             // Handle the image upload if there is an image
             $imagePath = null;
             if ($request->hasFile('image')) {
                 $imagePath = $request->file('image')->store('images/products', 'public'); // Store the image in the public disk
             }

        $product =  Product::create([
            'title' => $request->input('title'),
            'price' => $request->input('price'),
            'image' => $imagePath, // Save the path of the image
        ]);

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

ইমেজ দেখানোঃ

ইমেজ তো আপলোড হয়েছে। এবার দেখানোর জন্য প্রস্তুত। তার জন্য সাধারণ একটা ইমেজ ট্যাগে ইমেজ ভ্যারিয়েবল পাস করলেই হবেঃ

  <img src="{{ asset('storage/' . $product->image) }}" class="card-img-top" alt="{{ $product->title }}">

যেহেতু image কলামটা তৈরি করার সময় nullable বলে দিয়েছি। মানে ইউজার চাইলে প্রোডাক্টে ইমেজ নাও আপলোড করতে পারে। তাই আমরা চেক করে নিতে পারি ইমেজ রয়েহছে কিনা। এরপর ইমেজ দেখাতে পারিঃ

 @if ($product->image)
<img src="{{ asset('storage/' . $product->image) }}" class="card-img-top" alt="{{ $product->title }}">
@endif

সহজ না?

ইমেজ রিলেটেড কিছু টিপসঃ

404 এরর

ইমেজ যদি ঠিক মত সার্ভারে আপলোড হয়, কিন্তু ব্রাউজারে 404 এরর দেখায় বা লোড না হয়, তাহলে হয়তো storage ফোল্ডার নাও লিংক করা থাকতে পারে। তাই আমরা storage ফোল্ডার এভাবে লিঙ্ক করে নিবঃ

php artisan storage:link

উপরের কোড গুলো Laravel-mini-ecommerce এর image ব্রাঞ্চে আছে। এই রিপোজিটোরি ডাউনলোড করার পর image ব্রাঞ্চে গেলে ইমেজ নিয়ে কাজ করার কোড গুলো পাওয়া যাবে। নিচের কমান্ড লিখে image ব্রাঞ্চে যাওয়া যাবে।

git checkout image

গিট নিয়ে বিস্তারিত গিট সম্পর্কে ধারণা, গিট ইন্সটল, ব্যবহার লেখায়।

ল্যারাভেল নিয়ে অন্যান্য লেখা গুলো পাওয়া যাবে বাংলায় ল্যারাভেল টিউটোরিয়াল পেইজে।

ইমেজের পাথ

উপরে যখন ইমেজ আপলোড করি, তখন ইমেজের নাম হিসেবে সাধারণত একটা হ্যাস ভ্যালু স্টোর হয়। ল্যারাভেলের store() মেথড এই ভ্যালু অটো জেনারেট করে দেয়। ইমেজের url ও বিশাল হয়। যেমন F7hbi6RcMecChcb3KzU5xPnODBD6TO0TtxtgFdCN.jpg। এর মূল কারণ হচ্ছে একই নামের একাধিক ইমেজ আপলোড করলেও যেন সমস্যা না হয়।

আমরা চাইলে আরো সিম্পল একটা নাম ব্যবহার করতে পারি। যেমন টাইমস্ট্যাম্প। যেটা ইউনিক হয়ে থাকে। 1725906897.jpg এমন হবে তখন ইমেজের URL। উপরে ইমেজ স্টোর করার সময় ইমেজের পাথ নিয়েছি এভাবেঃ

 $imagePath = $request->file('image')->store('images/products', 'public');

এর পরিবর্তে এভাবে ইমেজের পাথ তৈরি করতে পারিঃ

    // Get the original file extension
    $extension = $request->file('image')->getClientOriginalExtension();
    // Create a custom file name using the current time
    $fileName = time() . '.' . $extension;
    // Store the file with the custom name in the 'images' directory inside 'public'
    $imagePath = $request->file('image')->storeAs('images', $fileName, 'public');

Leave a Reply