Sokan Academy

آشنایی با مفهوم Dependency Injection در زبان PHP

آشنایی با مفهوم Dependency Injection در زبان PHP

در فرآیند توسعهٔ نرم‌افزار به روش شيئ‌گرا مفهومی داریم تحت عنوان Dependency Injection با این توضیح که گاهی برخی کلاس‌ها برای عملکرد کامل خود نیاز به دیگر کلاس‌ها دارند و از همین روی نیاز است تا آبجکتی از کلاس‌های وابسته را به کلاس مذکور پاس دهیم. اگر برای اصطلاح فوق معادلی تحت‌الفظی همچون «تزریق وابستگی» را در نظر بگیریم، می‌بینیم که انتخاب نامی بامسمی صورت گرفته است چرا که ما در این پروسه وابستگی‌های یک کلاس را به آن تزریق کرده یا بهتر بگوییم پاس می‌دهیم.

در همین راستا، در این آموزش داخل پوشهٔ oop پروژه‌ای تحت عنوان dependency-injection ساخته و ساختار پروژه‌هایی که پیش از این مورد استفاده قرار دادیم را داخل آن ایجاد می‌کنیم اما در عین حال ابتدا قصد داریم تا بدون استفاده از دیپندنسی اینجکشن پروژه را نوشته سپس آن را اِعمال نماییم. برای شروع، ابتدا دیتابیسی تحت عنوان php-oop در محیط PhpMyAdmin ساخته سپس با کدهای اس‌کیو‌ال زیر، جدولی تحت عنوان users داخل آن می‌سازیم:

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  `first_name` varchar(255) NOT NULL,
  `last_name` varchar(255) NOT NULL
);

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

+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| first_name | varchar(255) | NO   |     | NULL    |                |
| last_name  | varchar(255) | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+

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

INSERT INTO `users` (`first_name`, `last_name`) VALUES ('Behzad', 'Moradi');

اگر به محتوای این جدول نگاهی بیندازیم، خواهیم داشت:

+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
|  1 | Behzad     | Moradi    |
+----+------------+-----------+

حال داخل پوشهٔ classes فایلی تحت عنوان Database.php ساخته و آن را به صورت زیر تکمیل می‌کنیم:

<?php
namespace SokanAcademy;

final class Database
{
    public $connection;

    public function __construct()
    {
        $this->connection = new \PDO("mysql:host=localhost;dbname=php-oop", 'root', '');
        $this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
    }
}

همان‌طور که ملاحظه می‌شود، کلاسی تحت عنوان Database ساخته‌ایم که از این جهت از جنس final است که قصد نداریم سایر کلاس‌های پروژه از آن ارث‌بری کنند. داخل بدنهٔ کلاس یک پراپرتی تحت عنوان connection$ ساخته و داخل کانستراکتور آن را مقداردهی کرده‌ایم بدین صورت که یک نمونه از روی کلاس PDO به آن اختصاص داده‌ایم و از آنجا که داخل نِیم‌اِسپیس SokanAcademy قرار داریم، برای آن که آبجکت‌سازی از روی این کلاس به مشکل برنخورد، قبل از آن از علامت \ استفاده کرده‌ایم. در کلاس PDO متدی تعبیه شده به نام ()setAttribute که با استفاده از آن می‌توان یک سری تنظیمات را فعال ساخت. به طور مثال،‌ اتریبوت PDO::ATTR_ERRMODE\ به منظور فعال‌سازی ارورها و اتریبوت PDO::ERRMODE_EXCEPTION\ به منظور فعال‌سازی اِکسپشن‌ها مورد استفاده قرار می‌گیرد.

از این پس، پراپرتی connection$ حاوی ارتباطی صحیح با دیتابیس خواهد بود و در ادامه داخل پوشهٔ classes فایلی به نام User.php ساخته و کدهای زیر را داخل آن درج می‌کنیم:

<?php
namespace SokanAcademy;

class User
{
    private $db;
    private $tableName = 'users';

    public function __construct()
    {
        $this->db = (new Database())->connection;
    }

    public function fetch()
    {
        $statement = $this->db->query("SELECT * FROM $this->tableName");
        return $statement->fetchAll(\PDO::FETCH_ASSOC);  
    }
}

دو پراپرتی تحت عناوین db$ و tableName$ از جنس private ساخته‌ایم به طوری که پراپرتی اول داخل کانستراکتور مقداردهی می‌شود بدین شکل که یک آبجکت از روی کلاس Database به آن منتسب می‌گردد و پراپرتی دوم نیز حاوی مقدار پیش‌فرض users است که به نوعی مرتبط با نام تنها جدول موجود در دیتابیس php-oop است. 

داخل کانستراکتور این کلاس، مقدار پراپرتی db$ را برابر فراخوانی پراپرتی connection$ از کلاس Database قرار داده‌ایم؛ در واقع، هر زمانی که نخواهیم از روی یک کلاس آبجکت ساخته سپس با استفاده از آن آبجکت به پراپرتی‌ها و متدهای کلاس مذکور دست یابیم، از یک جفت علامت () استفاده می‌کنیم به طوری که کلیدواژهٔ new به علاوهٔ‌ نام کلاس را داخل () قرار می‌دهیم سپس با استفاده از علامت <- پس از علامت ( به پراپرتی‌ها و متدهای کلاس مذکور دسترسی خواهیم داشت.

در ادامه، متدی تحت عنوان ()fetch نوشته‌ایم که وظیفهٔ فراخوانی داده‌های جدول users را بر عهده دارد بدین صورت که داخل بدنهٔ این متد متغیری ساخته‌ایم تحت عنوان statement$ و مقدار آن را برابر با فراخوانی پراپرتی db$ که حاوی آبجکتی از کلاس Database است سپس فراخوانی متد ()query که مرتبط با کلاس PDO می‌باشد قرار داده و یک کد سادهٔ اس‌کیوال که به منظور دریافت کلیهٔ‌ رکوردهای جدول users نوشته شده را هم به آن پاس داده‌ایم و در نهایت متد ()fetchAll از لایبرری PDO را روی متغیر statement$ فراخوانی کرده و به عنوان پارامتر ورودی این متد از PDO::FETCH_ASSOC\ استفاده کرده‌ایم که این امکان را در اختیارمان می‌گذارد تا یک به اصطلاح Associative Array به عنوان خروجی داشته باشیم. حال نوبت به تکمیل فایل index.php است به طوری که داریم:

<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';

$user = new SokanAcademy\User();
$allUsers = $user->fetch();
if ($allUsers) {
    foreach ($allUsers as $user) {
        echo $user['first_name'] . ' ' . $user['last_name'] . "\n";
    }
}

پس از ساخت یک آبجکت از روی کلاس User، متغیری ساخته‌ایم تحت عنوان allUsers$ که مقدارش برابر با فراخوانی متد ()fetch روی آبجکت مذکور است سپس با استفاده از یک دستور شرطی چک کرده‌ایم ببینیم که آیا مقداری داخل متغیر allUsers$ وجود دارد یا خیر که اگر این گونه بود، با استفاده از یک حلقه تک‌تک رکورد‌ها را چاپ کرده‌ایم به طوری که در خروجی خواهیم داشت:

/var/www/oop/dependency-injection$ php index.php 
Behzad Moradi

آنچه در ارتباط با کلاس User وجود دارد این است که چنین کلاسی به اصطلاح Loosely-coupled نیست؛ به عبارت دیگر،‌ با توجه به وابستگی‌اش به کلاس Database، هرگز نمی‌توان این کلاس را مستقل قلمداد کرده و به سادگی آن را در سایر پروژه‌های خود استفاده نمود بلکه همواره می‌باید کلاس Database را نیز همراه آن داشت و اینجا است که پای مفاهیم Decoupling و Dependency Injection به میان می‌آید با این توضیح که با رعایت چنین اصلی خواهیم توانست کلاس‌های پروژه‌ را تا حد ممکن مستقل از یکدیگر توسعه داده تا در صورت نیاز بتوان آن‌ها را با سایر دولوپرها بدون هیچ مشکلی به اشتراک گذاشت که در ادامهٔ این آموزش خواهیم دید که چگونه می‌توان این اصل را در زبان پی‌اچ‌پی پیاده‌سازی کرد.

آشنایی با نحوهٔ پیاده‌سازی Dependency Injection

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

<?php
namespace SokanAcademy;

class User
{
    private $db;
    private $tableName = 'users';

    public function __construct($database)
    {
        $this->db = $database->connection;
    }

    public function fetch()
    {
        $statement = $this->db->query("SELECT * FROM $this->tableName");
        return $statement->fetchAll(\PDO::FETCH_ASSOC);  
    }
}

همان‌طور که می‌بینیم، کانستراکتور این کلاس یک پارامتر ورودی تحت عنوان database$ می‌گیرد که در حین ساخت آبجکت از روی کلاس User می‌باید آبجکتی از روی کلاس Database به آن پاس داده شود؛ سپس داخل این کانستراکتور، پراپرتی connection$ کلاس Database که از این پس آبجکتی از آن داخل پارامتر ورودی database$ وجود دارد را فراخوانی کرده و نتیجه را به پراپرتی db$ منتسب کرده‌ایم. حال وارد فایل index.php شده و آن را به صورت زیر آپدیت می‌کنیم:

<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';

$user = new SokanAcademy\User(new SokanAcademy\Database);
$allUsers = $user->fetch();
if ($allUsers) {
    foreach ($allUsers as $user) {
        echo $user['first_name'] . ' ' . $user['last_name'] . "\n";
    }
}

به عنوان پارامتر ورودی کلاس User آبجکتی از روی کلاس Database را پاس داده‌ایم. برای این که کد فوق قابل‌فهم‌تر گردد،‌ می‌توان آن را به صورت زیر ریکفتور کرد:

<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';

$dbObj = new SokanAcademy\Database();
$user = new SokanAcademy\User($dbObj);
$allUsers = $user->fetch();
if ($allUsers) {
    foreach ($allUsers as $user) {
        echo $user['first_name'] . ' ' . $user['last_name'] . "\n";
    }
}

در صورت اجرای این فایل، خواهیم دید که همچون گذشته به درستی کار خواهد کرد و تنها تفاوتی که صورت گرفته این است که از این پس کلاس User هرگز وابسته به کلاس Database نبوده بلکه برای کارکرد صحیح نیاز به آبجکتی از دیتابیس دارد که این آبجکت به هر شکلی می‌تواند ساخته شده و به عنوان پارامتر ورودی کانستراکتور این کلاس در نظر گرفته شود.

جمع‌بندی
در ابتدای این دوره گفتیم که یکی از مزایای متودولوژی OOP آن است که کامپوننت‌های مختلف سورس‌کد مستقل از یکدیگر گشته به طوری که می‌توانیم آن‌ها را مستقل از سایر اجزای پروژه مورد استفاده قرار دهیم که این مهم به یُمن مفهومی تحت عنوان Dependency Injection امکان‌پذیر است اما آنچه در این ارتباط حائز اهمیت می‌باشد مفهومی است تحت عنوان Type Hinting که به نوعی این تضمین را ایجاد می‌کند وابستگی‌هایی که به کلاس‌های خود پاس می‌دهیم از همان نوعی باشند که مد نظر داریم که این موضوع را در آموزش بعد مورد بررسی قرار خواهیم داد.


sokan-academy-footer-logo
کلیه حقوق مادی و معنوی این وب‌سایت متعلق به سکان آکادمی می باشد.