مفهوم مدیریت وابستگی در لاراول یکبار برای همیشه!

مفهوم Service Container یکی از مهم‌ترین مفاهیم در فریم‌ورک لاراول هست که اکثر توسعه‌دهندگان به دلیل پیچیدگی که در درک مفهوم تزریق وابستگی و سرویس‌ها دارند، توجهی به اون نمی‌کنند. از اینرو در این آموزش بصورت بسیار ساده و قدم به قدم سعی می‌کنیم این مفهوم اساسی در لاراول رو آموزش دهم.


تزریق وابستگی Dependency Injection

اگر بخوام خیلی ساده بگم، تزریق وابستگی فرآیند انتقال وابستگی از یک کلاس به کلاس دیگری است، بیایید با مثال این مفهوم رو مرور کنیم:

من در یک پروژه خام لاراول یک کلاس ساده به نام MeliPaymentGateway ایجاد کردم فرض کنید این کلاس برای انجام عملیات بانکی و مخصوص درگاه بانک ملی باشه (اسامی بانکی همینطوری انتخاب شده و جنبه آموزشی داره والا هیچ توصیه‌ای ندارم ?? ) و یک تابع به نام charge برای ارسال درخواست پرداخت داره مانند زیر:

و در کنترلر پرداخت در تابع store یک نمونه از کلاس MeliPaymentGateway ایجاد می‌کنیم و تابع charge رو فراخوانی می‌کنیم:

برای مشاهده خروجی هم کافیه در فایل routes/web.php مسیر مورد نظر رو تعریف کنید:

همانطور که در مثال ساده فوق مشاهده می‌کنید تابع store یک نمونه از کلاس MeliPaymentGateway ایجاد و از تابع charge آن استفاده کرده. در اینجا می‌توان چنین استنباط کرد که کلاس PaymentController برای اجرای عمل charge به کلاس MeliPaymentGateway وابستگی دارد. اما اجازه دهید تا بجای ایجاد یک نمونه از کلاس داخل تابع store آنرا داخل سازنده کنترلر بسازیم.

خواهید دید که خروجی یکسانه اما چه اتفاقی افتاد یا بهتره بپرسم چه فرقی کرد. تفاوت در اینجا اینه که تعریف وابستگی از درون تابع store به سازنده کلاس و در هنگام ساخته شدن منتقل شده یا به بیان دیگه این کلاس در زمان راه‌اندازی کنترلر در آن تزریق میشه.

این ساده‌ترین شکل تزریق وابستگی هست. همچنین چنین استدلال میشه که تزریق وابستگی به یک کلاس باعث وارونگی کنترل یا IoC (مخفف Inversion of Control) میشه! یعنی پیش از این کنترل تولید کلاس مورد نیاز (در اینجا MeliPaymentGateway) داخل یک متد از کلاس وابسته (در اینجا PaymentController) انجام میشد! اما بعد از انتقال عملیات درون سازنده کلاس، به نوعی کنترل تزریق وابستگی به دست فریم‌ورک میفته.

Container

یکی دیگر از مفاهیم که در اینجا وجود داره مفهوم Container هست. Containerها باعث میشن فرآیند تزریق وابستگی بسیار کارآمدتر انجام بشه. در زیر یک نمونه بسیار ساده از کلاس Container رو مشاهده می‌کنید که اساس کار Containerها را نشان می‌دهد.

همانطور که مشاهده می‌کنید در این کلاس یک متغیر برای ذخیره و فراخوانی داده به نام bindings وجود داره و همچنین تابعی به نام make برای گرفتن داده (که می‌تواند رشته یا یک آبجکت باشه)

اجازه بدید از این کلاس در کنترلر خودمون استفاده کنیم:

که خروجی بصورت یک رشته ‌خواهد بود.

همچنین شما می‌توانید یک کلاس به همراه یک یک تابع که یک نمونه از کلاس رو برمی‌گردونه به عنوان آرگومان دوم bind کنید:

بیاید فرض کنیم که کلاس MeliPaymentGateway ما نیاز به یک کلید API داره که هنگام ساخت نمونه به اون نیاز داشته باشه:

بار دیگه به معنی کانتینر و کاربردش در اینجا دقت کنید. این اساس کار کانتینرهاست شما زمانی که یک دیتا رو bind می‌کنید، می‌توانید هر وقت که لازم داشتید اون رو صدا بزنید. اما کانتینرها می‌توانند انعطاف‌پذیری بیشتری داشته باشند.

شرایطی رو در نظر بگیرید که شما می‌خواهید مدل درگاه پرداخت خود رو تغییر دهید. برای نمونه می‌خواهید مدل MeliPaymentGateway رو با MellatPaymentGateway جابجا کنید.

اگر با روشی که در حال حاضر پیاده سازی کردیم بخواهید این کار رو انجام بدید باید تمامی ارجاع‌ها رو تغییر بدید و این روشی نیست که ما دنبال آن باشیم. برای این منظور می‌توان از interface استفاده کرد. خب ابتدا یک interface بصورت زیر تعریف می‌کنیم.

و نهایتا کلاس MeliPaymentGateway رو بصورت زیر بازنویسی می‌کنیم:

کلاس MellatPaymentGateway هم دقیقا مشابه کلاس فوق درنظر می‌گیریم.

حالا به تابع store در کنترلر و مفهوم کانتینر برمی‌گردیم اما اینبار بجای استفاده از کلاس‌ها از interface در bind کردن استفاده می‌کنیم:

با این کار هر زمان که ما از طریق کانتینر دستور make رو بزنیم کانتینر به ما درگاهی که موقع bind کردن تعریف کردیم رو برمی‌گردونه. شرایطی رو تصور کنید که در سیستم شما مدل درگاه پرداخت با مشکل مواجه شود، با این ساختار تنها با مراجعه به محلی که binding انجام شده است می‌توانید مدل رو جایگزین کنید.

همانطور که مشاهده که کردید ما یک کلاس ساده به نام SimpleContainer برای آشنایی با عملکرد کانتینرها در لاراول ساختیم، اما به همراه لاراول یک کانتینر قدرتمند‌تر دیگر ارائه شده که با نام service container شناخته میشه! در هر اپلیکیشن لاراولی این کانتینر با استفاده از تابع کمکی app در دسترس هست و شما می‌توانید با تابع app به یک نمونه از این کانتینر دسترسی داشته باشید. همانند کانتینر سفارشی ما این کانتینر هم توابع bind و make رو برای ذخیره مدل و فراخوانی اون داره! اجازه بدید مثال فوق را با این تابع بازنویسی کنیم:

همانطور که مشاهده می‌کنید من در بالا دوبار از make استفاده کردم و خروجی در مرورگر بصورت زیر شد:

همانطور که انتظار میره اعداد 256 و 263 در انتهای هر خط نشان می‌دهد که دو نمونه MeliPaymentGateway متفاوت توسط تابع make ساخته شده است. در اینجا تابع دیگری به نام singleton وجود دارد که در صورت استفاده از اون نتیجه متفاوتی رو خواهید دید:

اما اینبار عدد ۲۵۶ در انتهای هر خط در مرورگر نشون میده تنها یک نمونه از مدل ساخته شده

اما حالا که با کانتینر‌ها و bind، make، و singleton آشنا شدید سوالیه که باید پرسید اینه که آیا این درسته که هر جایی تونستیم از کانتینرها استفاده کنیم؟ در جواب باید بگم خیر برای این منظور بهتره این موارد رو در service provider تعریف کنید. Providerها در دایرکتوری app/providers قرار دارند و در به عنوان هسته فریم‌ورک در راه‌اندازی سرویس‌ها از اون‌ها استفاده میشه!

به طور پیش فرض هر پروژه جدید لاراول دارای پنج کلاس service provider است. در میان آنها، کلاس AppServiceProvider به طور پیش فرض با دو متد خالی register و boot تعریف شده. که متد register برای ثبت سرویس‌های جدید در اپلیکیشن استفاده میشه. جایی که می‌تونید فراخوانی متدهای bind و singleton خود را قرار دهید.

....

امیدوارم تا اینجای آموزش لذت برده باشید برای مطالعه ادامه این محتوای آموزش و کلی موارد جذاب دیگه به آدرس زیر مراجعه کنید:

https://prct.ir/c9cRi