استفاده از Service Container در Dependency Injection


توجه بیشتر موضوع بر روی ابزاری به نام Service Container میباشد ولی جهت بررسی این ابزار باید در ابتدا دلیل استفاده از Service Container و همچنین نحوه استفاده از Dependency Injection را بیان کنیم .

Dependency Injection چیست ؟

بزارید با یک مثال این علامت سوال رو برطرف کنم، شما روی یک پروزه در حال کار هستید و از یک پلاگین در چندین جای پروژه استفاده کرده ایید، بر فرض مثال ما میخوهیم از یک کلاس به نام Logger در بیشتر مکانهای پروژه استفاده کنیم .

class HomeController{

    private $log;

    function __construct(){

        $this->log = new Logger();
    }

    function getIndex(){

    }

}

خوب خیلی راحت ما هر جای کلاس HomeController میتونیم از آبجکت Logger استفاده کنیم، همه چیز خوب پیش میره تا موقعی که ما انعطاف بیشتری نیاز داشته باشیم، بر فرض مثال به دلیل تغیرات توی کلاس Logger ما باید یک پارامتر ورودی به این کلاس ارسال کنیم که این پارامتر ورودی نام Table میباشد که این کلاس برای ذخیره اطلاعات در آن استفاده میکند. و یا  فرض کنید که ما به دلیل مشکلی که توی کلاس Logger ایجاد شده نتونیم دیگه از این کلاس استفاده کنیم و بخوایم از کلاس Logger خودمون استفاده کنیم خوب خیلی جاها باید بیایم و تغییراتی انجام بدیم ، این تغییرات باعث میشن که

  • قاعده دوم شی گرایی رو از بین میبره Open Close Principle ، یعنی اینکه کلاس رو میتونید گسترش بدید ولی محتوای یک کلاس رو نمیتونید تغییری بدید
  • کلاس شما به صورت خیلی سخت با آبجکت Logger در تعامل هست
  • جهت تست کلاس مورد نظر باید کلاس Logger رو هم تست کنیم

این Pattern یا راه حل میگه، یک نمونه جدید از کلاس مورد نظر رو توی خود Constructor ایجاد نکنید ، بلکه باید کلاس Logger را به صورت پارامتر ورودی به هر کلاس دیگه ایی Inject کنیم .

class HomeController{

    private $log;

    function __construct(Logger $logger){

        $this->log = $logger;
    }

    function getIndex(){

    }

}

در مثال بالا شما در صورت تغییر در هر یک از پارامترهای ورودی کلاس Logger نیازی به تغییر کدهای خودتون ندارید، چرا که شما در Constructor آرگومان ورودی را از نوع logger میخواهید، ولی در صورت استفاده از کلاس Log دیگه ایی به جای Logger با مشکل مواجه میشیم ، که واسه حل این مشکل ما به جای استفاده از نام کلاس به صورت Typehint میتونیم از یک Interface یا Abstraction Class استفاده کنیم . اینکار سبب میشود بدونیم که هر کلاس Log که در پروژه خودمون استفاده میکنم از این قرارداد (Interface) تبعیت میکند ، لذا در استفاده از آن اطمینان لازم را داریم .

interface Ilog
{

    public function log();
}

و میتونیم کد بالا را به این شکل تغییر بدهیم .

class HomeController{

    private $log;

    function __construct(Ilog $logger){

        $this->log = $logger;
    }

    function getIndex(){

    }

}

class Logger implement Ilog{
    
     public function log(){
        
         // do Log
     }
}

در php7 ما میتونیم نوع بازگشت یک تابع رو مشخص کنیم لذا برای اطمینان بیشتر از حاصل شدن نتیجه میتوانیم نوع بازگشتی متد log را هم مشخص کنیم .

Dependency Injection فقط مربوط به Constructor Injection نمیشه و Interface Injection , Method Injection و Properties Injection رو هم میتونیم استفاده کنیم در کد بالا ما از Interface Injection در Constructor خود استفاده کردیم .

مزایای استفاده از Dependency Injection چیست ؟

  • از بین بردن وابستگی در کلاس مورد نظر 
  • اطمینان داشتن از اینکه در صورت تغییر کلاس Logger نرم افزار یا قطعه کد با مشکل مواجه نخواهد شد .
  • بالا بردن کیفیت کد نویسی 
  • تست کردن آسانتر کلاس مورد نظر
  • از مزایای دیگه استفاده از این تکنیک اینه که شما میتونید خیلی راحتر وابستگیها رو کنترل کنید .

Service Container  و Dependency Injection به هم دیگه نیازی ندارند ولی در صورت نیاز میتونن از همدیگه استفاده کنند . 

Service Container: ابزاری است جهت تزریق کردن وابستگی های یک کلاس .

Dependency Injection:  روشی جهت کد زدن بهتر میباشد .

فرض کنید که شما یک کلاس دارید به نام کلاس A که این کلاس A نیازمند یه کلاس دیگه میباشد به نام کلاس B و همچنین کلاس B نیازمند کلاسی است به نام کلاس C .

این اتفاق در دنیای واقعی به ندرت پیش میاد ولی در بیشتر فرم ورکها این نیازمندیها خیلی دیده میشوند . 

class A{

    private $b;
    function __construct(B $b){
        $this->b = $b;
    }

    public function getB(){
        return $this->b;
    }
}

Class B{

    private $c;
    function __construct(C $c){

        $this->c = $c;
    }

    public function getC(){
        return $this->c;
    }
}

Class C{

    public $class = "class_c";
}

خوب اگه بخوایم به صورت کلاسیک کار کنیم، واسه اینکه یک نمونه جدید از کلاس A ایجاد کنیم باید یک نمونه جدید از کلاس B و سپس یک نمونه جدید از کلاس C ایجاد کنیم .

$a = new A( new B( new C ) );
$a->getB()->getC()->class;

واسه اینکه بتونیم این کار رو راحتر انجام بدیم ، میتونیم از Service Container استفاده کنیم . همونطوری که از اسمش مشخصه هر کلاس به صورت یک Service در Container شناخته میشود . نحوه کار کردن با Service Container به این شکل میباشد .

class A{

    private $b;
    function __construct(B $b){
        $this->b = $b;
    }

    public function getB(){
        return $this->b;
    }
}

Class B{

    private $c;
    function __construct(C $c){

        $this->c = $c;
    }

    public function getC(){
        return $this->c;
    }
}

Class C{

    public $class = "class_c";
}

$container->register('A', 'A')
    ->addArgument(new Reference('B'));
$container->register('B', 'B')
    ->addArgument(new Reference('C'));
$container->register('C', 'C');
$a = $container->get("a");

Dependency Injection Container یک کمپوننت میباشد که در بیشتر فرم ورکها استفاده میشود ، من در اینجا از Service Container فرم ورک سیمفونی استفاده میکنم جهت دانلود میتوانید از این دستور استفاده کنید .   composer require symfony/dependency-injection

متد Register در کد بالا 2 آرگومان ورودی دارد ، آرگومان اول نام مستعاری است که به سرویس داده میشود و آرگومان دوم نام کلاسی است که میخواهیم آنرا در Container ثبت کنیم .

متد addArgument جهت برطرف کردن نیازمندیها یا وابستگیهای یک کلاس میباشد، که برای برطرف کردن این وابستگیها، ما یک نمونه جدیدی از کلاس Reference ایجاد میکنم . و سپس آرگومان ورودی کلاس Refrence را معادل کلاسی قرار میدهیم که میخواهیم آنرا به عنوان وابستگی استفاده کنیم . ولی همانطوری که میتونید مشاهده کنید ، Reference کردن و ثبت کردن کلاس در Container کمی زمان بر میباشد. برای حل این مشکل فرم ورکها راه حلهایی را خلق کردند که من یکی از این راه حلها رو در آموزش دیگه ایی میگم که بدون ثبت وابستگیها به صورت اتوماتیک وابستیگهای یک سرویس Inject شوند . ولی قبل از اینکه راه حلها رو بگم مزایای استفاده از Service Container رو میگم .

وقتی از Service Container استفاده میکنیم

  •  نیازی به ایجاد همه کللاسها نداریم و Container فقط اون کلاسها و وابستگیهای که در پروژه بهشون نیاز داشته باشه رو Resolve میکنه . اینطوری بار load کردن کلاسها میاد پایین و خوب سرعت اجرای یک request میره بالا
  • میتونید اسم یک service رو عوض کنید بدون اینکه دست ببرید توی کدهاتون ، یعنی اینکه سرویس میتونه با یک نام باشه و اسم کلاس با یک نام دیگه باشه .

 امیدوارم این آموزش به دردتون خورده باشه .

مشکل ، مورد ، نظر ، ایده هر سوالی که به ذهنتون در مورد این آموزش میرسه لطفاً بیان کنید



    • Z.hadianpoor@gmail.com

      عالی بود مطالبتون خیلی خاص و جالب و کاربردی هستش. مرسی

      • mohammad.kaab@gmail.com

        خواهش میکنم .