در فرآیند توسعهٔ نرمافزار به روش شيئگرا مفهومی داریم تحت عنوان 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 که به نوعی این تضمین را ایجاد میکند وابستگیهایی که به کلاسهای خود پاس میدهیم از همان نوعی باشند که مد نظر داریم که این موضوع را در آموزش بعد مورد بررسی قرار خواهیم داد.