Laravel Service Container

আজ আমরা লারাভেলের advanced টপিক Service Container বুঝার চেষ্টা করব।

প্রথম যে প্রশ্ন আমাদের মাথায় আসে তা হচ্ছে Service Container কি?
লারাভেলের অফিসিয়াল ডকুমেন্টেশনে Service Container নিয়ে কি বলা আছে, তা আগে দেখি:

The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are “injected” into the class via the constructor or, in some cases, “setter” methods.

তাহলে আমরা Simply বলতে পারি, Service Container হচ্ছে একটি কন্টেইনার যে কিনা ক্লাস গুলো কে তার কাছে hold করে যাতে করে পরবর্তিতে আমরা আমাদের প্রয়োজন মতো ক্লাসের অবজেক্ট গুলো কে ব্যবহার করতে পারি।
এখন কোডের মাধ্যমে বিষয় টি বুঝার চেষ্টা করব।

প্রথমে আমরা একটি ফোল্ডার তৈরী করলাম Billing নাম দিয়ে এই Billing ফোল্ডারের মধ্যে একটি PHP ফাইল তৈরী করব PayPaymentGateway.php নামে ।
এই ফাইলের মধ্যে member variable এবং member function তৈরী করলাম শুধু মাত্র।

# PayPaymentGateway.php

<?php
namespace App\Billing;

class PayPaymentGateway{
    private $discount=0;

    public function charge($amount){
        return [
            'charge'    => $amount - $this->discount,
            'discount'  => $this->discount
        ];
    }

    public function setDiscount($amount){
        $this->discount = $amount;
    }
}

এই ক্লাসকে আমরা যে কোনো কন্ট্রোলার দিয়েই এক্সেস করতে পারবো। তাহলে এখন একটি কন্ট্রোলার তৈরী করব যার নাম দিবো PayController.php, কন্ট্রোলার মধ্যে একটি মেথড তৈরী করব transaction নাম দিয়ে।

# PayController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Billing\PayPaymentGateway;

class PayController extends Controller
{
    public function transaction(){
        $paymentGateway = new PayPaymentGateway;
        dd($paymentGateway->charge(500));
    }
}


এখন Route ডিফাইন করব:

// web.php

Route::get('/payment', 'PayController@transaction');

এখন ব্রাউজারে দিয়ে URL হিট করলে দেখব যে আমাদের কোড সুন্দর আউটপুট দিচ্ছে:

array:2 [▼
  "charge" => 500
  "discount" => 0
]

PayController.php কন্ট্রোলারে আমরা চাইলে আমাদের লারাভেলের Dependency injection concept ও ব্যবহার করতে পারি:

# PayController.php

public function transaction(PayPaymentGateway $paymentGateway){
        dd($paymentGateway->charge(2200));
}

এইভাবে করলেও হবে, আগের মতোই আমাদের আউটপুট আসবে।
কিন্তু সমস্যা হবে তখনই যখন আমাদের PayPaymentGateway.php ফাইলে constructor ব্যবহার করব:

# PayPaymentGateway.php

<?php
namespace App\Billing;

class PayPaymentGateway{
    private $currency;
    private $discount=0;

    public function __construct($currency){
        $this->currency = $currency;
    }

    public function charge($amount){
        return [
            'currency'  => $this->currency,
            'charge'    => $amount - $this->discount,
            'discount'  => $this->discount
        ];
    }

    public function setDiscount($amount){
        $this->discount = $amount;
    }
}

এখন যদি ব্রাউজারে গিয়ে দেখি তাহলে দেখব যে এরর দেখাচ্ছে এইটাই স্বাভাবিক, আমাদের কে অবজেক্ট তৈরী করার সময় constructor এর ভ্যালু পাস করতে হবে।
এই ক্ষেত্রে এসে আমরা Service Container এর কনসেপ্ট টি ব্যবহার করব।
আমরা AppServiceProvider.php File এ গিয়ে register() মেথড এর মধ্যে আমাদের PayPaymentGateway ক্লাস টির একটি অবজেক্ট তৈরী করে রাখব, যাতে করে যখন আমাদের ইচ্ছা তখন যেনো এই PayPaymentGateway ক্লাসটির অবজেক্ট টি ব্যবহার করতে পারি:

# AppServiceProvider.php

public function register()
    {
        $this->app->bind(PayPaymentGateway::class, function($app){
            return new PayPaymentGateway('usd');
        });
}

এখন যদি ব্রাউজারে গিয়ে দেখি, তাহলে দেখব যে আমাদের কোড সুন্দর কাজ করছে।

array:3 [▼
  "currency" => "usd"
  "charge" => 2200
  "discount" => 0
]

এখন মনেকরি, আরেকটি ক্লাস বানাবো Discount.php নাম দিয়ে, এবং এই ফাইল টির মধ্যে একটি মেথড থাকবে যে মেথডটি আমাদের তৈরী করা PayPaymentGateway.php ফাইল টির setDiscount() মেথডটি কে কল করবে, এই add() method টির মধ্যে আমরা discount 500 সেট করে দিয়েছি:

# Discount.php

<?php
namespace App\Billing;

use App\Billing\PayPaymentGateway;

class Discount{
    
    private $paymentGateway;

    public function __construct(PayPaymentGateway $payPaymentGateway){
        $this->payPaymentGateway = $payPaymentGateway;
    }

    public function add(){
        $this->payPaymentGateway->setDiscount(500);
    }

}

এখন আমরা PayController.php ফাইলে গিয়ে Discount.php ফাইলের add() মেথড কে কল করব:

# PayController.php

class PayController extends Controller
{
    public function transaction(Discount $discount, PayPaymentGateway $paymentGateway){
        $discount = $discount->add();
        dd($paymentGateway->charge(2200));
    }
}

ব্রাউসার এ গিয়ে যদি দেখি তাহলে দেখব যে আমাদের কে আগের আউটপুট টি দেখাচ্ছে:

array:3 [▼
  "currency" => "usd"
  "charge" => 2200
  "discount" => 0
]

কিন্তু আমরা তো discount 500 সেট করে দিয়েছিলাম, তাহলে আমাদের আউটপুট টি সঠিক আসে নাই
এই রকম হবার কারণ কি ???

এইটার কারণ হচ্ছে, Discount.php ফাইলে constructor এর মধ্যে নতুন করে আরেক বার PayPaymentGateway.php ফাইল টির অবজেক্ট তৈরী হয়েছে।
তাহলে উপায় কি??
উপায় হচ্ছে আমাদের কে AppServiceProvider.php ফাইল এ গিয়ে bind() মেথড টি কে চেঞ্জ করে singleton() মেথড ব্যবহার করতে হবে:

# AppServiceProvider.php

public function register()
    {
        $this->app->singleton(PayPaymentGateway::class, function($app){
            return new PayPaymentGateway('usd');
        });
}

এখন যদি ব্রাউজারে গিয়ে দেখি, তাহলে দেখব যে আমাদের কোডটি সঠিক আউটপুট দিচ্ছে।

array:3 [▼
  "currency" => "usd"
  "charge" => 1700
  "discount" => 500
]

লারাভেল Service Container সম্পূর্ণ কোডটি দেখতে এই লিঙ্কে ক্লিক করুন।

Leave a comment