Dependency Injection in PHP

ডিপেন্ডেন্সি ইঞ্জেকশন প্যাটার্ণ কি? পিএইচপি আর দ্রুপালে DI ব্যবহার করে কিভাবে মডুলার কোড লেখা যায়?

কিছুদিন আগে একটা দ্রুপাল প্রজেক্টে কোডবেস মেইনটেইনেন্স এর কাজ করতে গিয়ে ওদের ডকে একটা সাজেশন পাই, যেখানে বলা হয়েছে দ্রুপাল ৮ থেকে দ্রুপালের ইন্টারনাল সার্ভিসগুলো এক্সেস করার বেটার উপায় হলো ডিপেন্ডেন্সি ইঞ্জেকশন। ব্যাপারটা একটু ইন্টারেস্টিং মনে হওয়ার ভাবলাম অফিসে আমার পরবর্তী নলেজ সেশনটা এটার উপরেই করবো এবং তারই ফলশ্রুতিতে এই পোস্ট।

ডিপেন্ডেন্সি ইঞ্জেকশন সফটওয়্যার ইঞ্জিনিয়ারিং এর খুবই গুরুত্বপূর্ণ একটি কনসেপ্টের উপরে তৈরি করা প্যাটার্ন, আর সেটা হলো মডুলারিটি। কোডকে যতটা সম্ভব (এবং যতটা উচিত) মডুলার রাখা, যাতে সেটাকে সহজেই বিভিন্ন প্রোগ্রামে ব্যবহার করা যায়, ওই প্রোগ্রামগুলোকে তেমন পরিবর্তন না করেই। ব্যাখ্যা শুরু করার আগে কিছু সংজ্ঞা দেয়া প্রয়োজন, পরে এগুলো বারবারই লেখার মধ্যে আসতে থাকবে।

  • সার্ভিস: যে ফাংশনালিটিটা আমরা ব্যবহার করবো। সেটা যেকোনো ধরনের কমন কাজই হতে পারে, যা কোডবেসের বিভিন্ন জায়গা থেকে প্রয়োজনমাফিক বারবার ব্যবহার করা যাবে। যেমন, অথেনটিকেশন। প্রজেক্টের অনেক জায়গাতেই হয়তো ইউজারের অথেন্টিসিটি চেক করতে হবে, যে সে আসলেই আমাদের ইউজার কিনা। আর সেই কাজটাই করবে অথেনটিকেশন সার্ভিস।

  • ক্লায়েন্ট: ক্লায়েন্ট হলো সেই ব্যক্তি যে রাত ২টার সময় মেইল দিয়ে ফন্টের সাইজ চেঞ্জ করতে বলে! জোকস এপার্ট, ক্লায়েন্ট হচ্ছে যেকোনো এনটিটি যা সার্ভিসকে ব্যবহার করে। যেমন, অথেনটিকেশন যদি আমাদের সার্ভিস হয় তাহলে লগইন সিস্টেমটা একটি ক্লায়েন্ট, যে অথেনটিকেশন সার্ভিসকে ব্যবহার করে ইউজারকে লগইন করায়।

  • ইন্টারফেস: ইন্টারফেস এর সাথে আমরা সবাই মোটামুটি পরিচিত। এটি ডিফাইন করে কিভাবে আমাদের সার্ভিসটা তৈরি করা হবে, অনেকটা বিল্ডিং বানানোর ব্লুপ্রিন্টের মত। অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং এ ইন্টারফেস ঠিক করে দেয় ওই ইন্টারফেসকে ইমপ্লিমেন্ট করা সব ক্লাসে কি কি মেথড থাকা লাগবে। ফলে ক্লায়েন্ট ইন্টারফেসকে দেখে সহজেই বুঝতে পারে তাতে ইমপ্লিমেন্ট করা সার্ভিসগুলো কেমন হবে।

  • ইঞ্জেকটর: ইঞ্জেকটর এর নাম আর কাজ একই, এটি সার্ভিসকে ক্লায়েন্টে ইঞ্জেক্ট করে। সিরিঞ্জ দিয়ে শরীরে ইনজেকশন নেবার উদাহরণটা যদি আমরা ভাবি, এখানে ক্লায়েন্ট হচ্ছে রোগী, মেডিসিন হচ্ছে সার্ভিস আর সিরিঞ্জ হচ্ছে ইঞ্জেকটর!

আচ্ছা এবার তাহলে শুরু করা যাক।

ডিপেন্ডেন্সি ইঞ্জেকশন কি?

ডিপেন্ডেন্সি ইঞ্জেকশন হচ্ছে এমন একটা টেকনিক যার মাধ্যমে আমরা কোনো একটা অবজেক্টকে তার উপরে ডিপেন্ডেন্ট বা নির্ভরশীল কোনো অবজেক্টের কাছে পৌঁছে দেই। ধরা যাক , অবজেক্ট A, অবজেক্ট B এর ডিপেন্ডেন্সি । মানে অবজেক্ট B কে নিজের কাজ ভালোমত শেষ করতে অবজেক্ট A এর কোনো ফাংশনালিটি ব্যবহার করতে হবে। B , A এর উপরে ডিপেনডেন্ট। ডিপেন্ডেন্সি ইঞ্জেকশনের মাধ্যমে আমরা এই সম্পর্কটা এমনভাবে তৈরি করতে পারি যাতে A এর ইমপ্লিমেন্টেশনে কোনো পরিবর্তন আসলে B তে পরিবর্তন করা লাগে না আর লাগলেও তা হয় নূন্যতম। সোজা কথায়, কোনো অবজেক্টের যত ডিপেন্ডেন্সি আছে তাদেরকে ওই অবজেক্টের সাথে এমনভাবে যুক্ত করা, যাতে অবজেক্ট বা ডিপেন্ডেন্টের সবচেয়ে কম চেঞ্জ হয়। এই টেকনিকটি ইনভার্শন অফ কন্ট্রোল প্রিন্সিপালের উপরে তৈরি করা। উইকিপিডিয়ার ভাষ্যমতে,

In software engineering, dependency injection is a technique in which an object receives other objects that it depends on, called dependencies.

একটা ঊদাহরণ যদি দেখতে চাই, শুরু করা যাক ডিপেন্ডেন্সি ইঞ্জেকশন ছাড়া কিভাবে আমরা ইঞ্জিন গাড়িতে বসাতে পারি!

interface BaseEngine {
  public function showSpeed(): void;
}

class SportsCarEngine implements BaseEngine {

  public function showSpeed(): void {
    echo "0-60 in 5 seconds!";
  }
}

class EconomyCarEngine implements BaseEngine {

  public function showSpeed(): void {
    echo "0-60 in 1 minute!";
  }
}

এখানে BaseEngine নামে আমাদের একটি ইন্টারফেস আছে, যেটা ঠিক করে দিয়েছে showSpeed() ফাংশনটা সব ইঞ্জিনের ইমপ্লইমেন্টেশনেই থাকবে। দুই ধরনের ইঞ্জিন আছে, SportsCarEngine এবং EconomyCarEngine। এখন আমরা যদি একটা Car ক্লাস বানাতে যাই, আমরা কন্সট্রাকটরের মধ্যেই ঠিক করে দিতে পারি কোন ইঞ্জিন আমরা ব্যবহার করবো এবং সেই অনুযায়ী carDetails() ফাংশনটা আউটপুট দেখাবে।

class Car {

  public $engine;

  public function __construct() {
    $this->engine = new SportsCarEngine();
  }

  public function carDetails() {
    echo $this->engine->showSpeed();
  }
}
$myCar = new Car();
$myCar->carDetails(); // 0-60 in 5 seconds!

কিন্তু এখানে সমস্যাটা যেটা দেখা যাচ্ছে তা হলো SportsCarEngine এর ইন্সট্যানসিয়েশন এ। কন্সট্রাকটরের মধ্যে আমরা টাইট কাপলিং করে দিয়েছি, ফলে প্রতিবারই Car ক্লাস ইন্সট্যানসিয়েট করলে SportsCarEngineওয়ালা Carই তৈরি হবে। বাইরে থেকে এটা কন্ট্রোল করার উপায় নেই। আচ্ছা আমরা যদি কন্সট্রাকটরে কি ইঞ্জিন সেটা পাস করি, তাহলে?

public function __construct(EconomyCarEngine $engine) {
  $this->engine = $engine;
}
.
.
$engine = new EconomyCarEngine();
$my_car = new Car($engine);
$my_car->carDetails(); // 0-60 in 1 minute!

এটাও কাজ করবে। এবং আমরা বাইরে থেকে কন্ট্রোল করতে পারছি কি ইঞ্জিন লাগবে। তাহলে কি ডিপেন্ডেন্সি ইঞ্জেকশন ছাড়াই আমরা কোডের কোয়ালিটি ইম্প্রুভ করে ফেলেছি? আসলে না! এটাও একধরনের ডিপেন্ডেন্সি ইনজেকশন। কিন্তু এখানে আপাতদৃষ্টিতে কোড কিছুটা মডুলার লাগলেও কন্সট্রাকটরে কিন্তু এখনো আমাদেরকে কোন টাইপের ইঞ্জিন আমরা এক্সপেক্ট করছি সেটা বলে দিতে হচ্ছে। ফলে আমরা EconomyCarEngine এর বদলে SportsCarEngine পাস করলে এটা ফেইল করবে। এটার সমাধান হিসাবে আমরা BaseEngine ইন্টারফেসকে দিতে পারি প্যারামিটারে, তাহলে ওই ইন্টারফেসের যেকোনো ইমপ্লিমেন্টেশনই আমরা আর্গুমেন্টে পাস করতে পারবো।

public function __construct(BaseEngine $engine) {
  $this->engine = $engine;
}
.
.
$engine = new EconomyCarEngine();
$my_car = new Car($engine);
$my_car->carDetails(); // 0-60 in 1 minute!

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

ডিপেন্ডেন্সি ইঞ্জেক্ট করা হয় কয়ভাবে?

তিনটা উপায়ে সাধারণত ডিপেন্ডেন্সি ইঞ্জেক্ট করা হয়ে থাকে।

/**
 * Constructor Injection
 */
public function __construct(EconomyCarEngine $engine) {
  $this->engine = $engine;
}
.
.
$engine = new EconomyCarEngine();
$my_car = new Car($engine);
/**
 * Setter Injection
 */
public function setEngine(EconomyCarEngine $engine) {
  $this->engine = $engine;
}
.
.
$engine = new EconomyCarEngine();
$my_car = new Car();
$my_car->setEngine($engine);
/**
 * Interface Injection
 */
public function __construct(BaseEngine $engine) {
  $this->engine = $engine;
}
.
.
$engine = new EconomyCarEngine();
$my_car = new Car($engine);

ডিপেন্ডেন্সি ইঞ্জেকশন করা হয়ে কেনো?

ডিপেন্ডেন্সি ইনভার্সান প্রিন্সিপালে বলা হয় কোনো এনটিটিকে (ক্লাস/অবজেক্ট) কোনো সার্ভিসের লো-লেভেল ইমপ্লিমেন্টেশনের উপরে ডিপেন্ডেন্ট হওয়া উচিত না, বরং সেই সার্ভিসের অ্যাবস্ট্রাকশনের উপরে ডিপেন্ড করা উচিত। ডিপেন্ডেন্সি ইঞ্জেকশন টেকনিকটিও এটা মেনে চলে, যা ক্লায়েন্টকে অনেক ফ্লেক্সিবিলিটি দেয়। ক্লায়েন্ট যেহেতু সরাসরি সার্ভিস না, বরং সার্ভিসের অ্যাবস্ট্রাকশনের/ইন্টারফেসের উপরে ডিপেন্ডেন্ট, তাই সার্ভিসের ইমপ্লিমেন্টেশন পরিবর্তন হলেও ক্লায়েন্ট পার্সপেক্টিভ থেকে তেমন কিছুই চেঞ্জ হয়না। ফলে একবার ক্লায়েন্টে কি লাগবে তা ঠিক করা হয়ে গেলে, সেটা সার্ভিস কিভাবে দিচ্ছে সেটা নিয়ে আর মাথা ঘামানোর প্রয়োজন পরে না। এতে করে যেমন সার্ভিস এবং ক্লায়েন্টকে আলাদা ভাবে টেস্ট করে দেখা যায়, তেমনি পরবর্তীতে লজিক্যাল চেঞ্জগুলোও সহজে হ্যান্ডেল করা যায়, যেটা কোডের এক্সটেন্সিবিলিটি বাড়িয়ে দেয় আর টাইট কাপলিং কমায়। এছাড়া ক্লায়েন্ট রিকোয়ারমেন্ট ফিক্সড বলে একই ক্লায়েন্ট এর জন্য সমসাময়িকভাবে অনেকগুলো সার্ভিসের কাজ করা যায়, যা টিমের প্রোডাক্টিভিটি ও বাড়ায়।

তবে ডিপেন্ডেন্সি ইঞ্জেকশন যে একটা ফুলপ্রুফ টেকনিক তাও না। অনেকসময় দেখা যায় এই প্যাটার্নের কারণে কোডবেসের ডিজাইন কিছুটা কমপ্লেক্স হয়ে যায় এবং সার্ভিসের কোন ইমপ্লিমেন্টেশন কখন ইঞ্জেক্ট করা লাগবে তা বোঝা যায়না। আবার আমরা যেহেতু ইন্টারফেস ইঞ্জেক্ট করছি, ইমপ্লিমেন্টেশন এর বদলে, অনেক এরর রানটাইমে গিয়ে ধরা পরে, যেখানে সেটা কম্পাইল টাইমে ধরা পড়ার কথা ছিল।

দ্রুপালে ডিপেন্ডেন্সি ইঞ্জেকশন

সহজে রিপিটেটিভ কাজগুলো করার জন্য দ্রুপাল এপিআই বিভিন্ন সার্ভিস দিয়ে থাকে। ফাইল ম্যানেজ করা থেকে ডাটাবেস হ্যান্ডেল করা, সব কাজের জন্যই এখানে সার্ভিস আছে। ফলে আমরা ক্লাস বা ফাংশনের মধ্যে \Drupal::service(service_name) এর মাধ্যমে যেকোনো সার্ভিস এক্সেস করতে পারি। যেমন, আমাদের যদি কোনো টাইপের সব এনটিটির দরকার হয় আমরা সেটা পেতে পারি এভাবে,

class DemoEntityUser {

  public $entities;

  public function __construct() {}

  public function generateEntities() {
    $this->entities = \Drupal::service('entity_type.manager')->getStorage($entity_type)->loadMultiple($ids);
  }
}

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

class DemoEntityUser {

  public $entity_type_manager;
  public $entities;

  public function __construct(EntityTypeManagerInterface $entity_type_manager_interface) {
    $this->entity_type_manager = $entity_type_manager_interface;
  }

  public function generateEntities() {
    $this->entities = $this->entity_type_manager->getStorage($entity_type)->loadMultiple($ids);
  }
}