آشنایی با مفهوم Type Hinting در زبان PHP


در آموزش قبل دیدیم که کانستراکتور کلاس User یک پارامتر الزامی داشت تحت عنوان database$ با این توضیح که حین ساخت یک آبجکت جدید از روی این کلاس، می‌بایست آبجکتی از روی کلاس Database به آن پاس می‌‌دادیم. با مد نظر قرار دادن این نکته، در این آموزش قصد داریم Type Hinting را در پروژه‌ای که در آموزش گذشته ساختیم اِعمال نماییم. برای شروع، یک کپی از روی فایل Database.php گرفته و آن را داخل همان پوشهٔ classes تحت عنوانی دلخواه همچون Database2.php ذخیره می‌سازیم و محتوای داخل آن را به صورت زیر ریفکتور می‌کنیم:

<?php
namespace SokanAcademy;

final class Database2
{
    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 را به Database2 تغییر داده‌ایم. حال وارد فایل index.php شده و آن را به صورت زیر آپدیت می‌کنیم:

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

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

آبجکت قبلی تحت عنوان dbObj$ که از روی کلاس Database ساخته بودیم را کامنت کرده در عوض آبجکتی جدید به نام tmpDbObj$ از روی کلاس Database2 ساخته‌ایم و آن را به عنوان آرگومان کلاس User در نظر گرفته‌ایم و این در حالی است که اگر این فایل را اجرا کنیم، به درستی خروجی زیر در معرض دیدمان قرار خواهد گرفت:

Behzad Moradi

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

<?php
namespace SokanAcademy;

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

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

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

تنها کاری که داخل این کلاس انجام داده‌ایم آن است که قبل از پارامتری تحت عنوان database$ که برای کانستراکتور این کلاس در نظر گرفته بودیم نام کلاس Database را نوشته‌ایم که به این کار اصطلاحاً Type Hinting گفته می‌شود. به عبارت دیگر، به کانستراکتور این کلاس دستور داده‌ایم که آرگومان ورودی به این کلاس در حین ساخت یک آبجکت جدید فقط و فقط می‌باید از جنس کلاس Database باشد. مجدد نگاهی به محتویات فایل index.php می‌اندازیم:

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

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

می‌بینیم که به جای آبجکت ساخته‌شده از روی کلاس Database، آبجکتی را به عنوان آرگومان پاس داده‌ایم که از روی کلاس Database2 ساخته شده است و با این توضیحات در خروجی خواهیم داشت:

/var/www/oop/type-hinting$ php index.php 
PHP Fatal error:  Uncaught TypeError: Argument 1 passed to SokanAcademy\User::__construct() must be an instance of SokanAcademy\Database, instance of SokanAcademy\Database2 given, called in /var/www/oop/type-hinting/index.php on line 7 and defined in /var/www/oop/type-hinting/classes/User.php:9

متن ارور حاکی از آن است که آرگومان ورودی به کلاس User می‌باید از جنس Database باشد اما این در حالی است که آبجکتی از روی کلاس Database2 پاس داده شده است. در حقیقت،‌ Type Hinting باعث شده تا دولوپر اجازه نداشته باشد تا هر نوع آبجکتی را به عنوان آرگومان به کلاس User دهد. مجدد کدهای فوق را به صورت زیر تغییر داده و این فایل را اجرا خواهیم کرد:

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

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

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

آشنایی با مفهوم Interface Type Hinting

در آموزش آشنایی با مفهوم Interface در متودولوژی OOP با مفهوم و نحوهٔ کارکرد اینترفیس‌ها در زبان برنامه‌نویسی پی‌اچ‌پی آشنا شدیم. حال در این قسمت از آموزش قصد داریم ببینیم که به چه شکل می‌توان از اینترفیس‌ها در پروسهٔ تایپ هینتینگ استفاده نماییم که برای همین منظور داخل پوشهٔ classes فایلی تحت عنوان DatabaseInterface.php ایجاد کرده و کدهای زیر را داخل آن می‌نویسیم:

<?php
namespace SokanAcademy;

interface DatabaseInterface
{
    
}

همان‌طور که می‌بینیم، اینترفیسی ایجاد کرده‌ایم تحت عنوان DatabaseInterface که هیچ متدی داخل آن تعریف نشده است. در ادامه، هر دو کلاس Database و Database2 را از این اینترفیس ایمپلیمنت می‌کنیم به طوری که مثلاً برای کلاس Database داریم:

<?php
namespace SokanAcademy;

final class Database implements DatabaseInterface
{
    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);
    }
}

به همین شکل کلاس Database2 را نیز ریفکتور می‌کنیم و در ادامه به کلاس User رفته و کانستراکتور آن را به شکل زیر آپدیت می‌کنیم:

<?php
namespace SokanAcademy;

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

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

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

می‌بینیم که بر خلاف گذشته که کلاس Database را تایپ هینتینگ کرده بودیم، این بار اینترفیس DatabaseInterface را مورد استفاده قرار داده‌ایم. حال به فایل index.php رفته و آن را به صورت زیر تغییر می‌دهیم:

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

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

به عنوان پارامتر ورودی کلاس User خواه آبجکت dbObj$ و خواه tmpDbObj$ را مورد استفاده قرار دهیم، کلاس User عملکرد صحیحی خواهد داشت چرا که داخل کانستراکتور آن دستور داده‌ایم که آبجکت ورودی می‌باید از جنس اینترفیس DatabaseInterface باشد و از آنجا که هر دو کلاس Database و Database2 از روی چنین اینترفیسی ایمپلیمنت شده‌اند، این کلاس به درستی کار خواهد کرد.

کاربردهای Type Hinting فقط به مثال فوق محدود نمی‌شود بلکه در تعریف متدها نیز می‌توان از این قابلیت استفاده نمود تا اطمینان پیدا کنیم آرگومان‌های ورودی دقیقاً از همان جنسی هستند که مد نظر ما است که در ادامه در قالب مثال‌هایی کاربردی این موضوع را مورد بررسی قرار خواهیم داد.

این بخش از محتوا مخصوص کاربرانی است که ثبت‌نام کرده‌اند.
جهت مشاهدهٔ این بخش از محتوا لاگین نمایید.

جمع‌بندی
در این آموزش پروژه‌ای که در آموزش آشنایی با مفهوم Dependency Injection پیاده‌سازی کردیم را تا حدودی اصولی‌تر نموده و این امکان را فراهم آوردیم تا آبجکت دیتابیسی پاس داده شده به کلاس User دقیقاً از همان تایپی باشد که مد نظرمان است به طوری که اِعمال این نوع تغییرات باعث می‌گردد تا در پروژه‌هایی که به صورت گروهی توسط چندین و چند دولوپر مختلف توسعه می‌یابند احتمال وقوع باگ‌های سهوی کاهش یابد. 

دانلود فایل‌های تمرین

لیست نظرات
کاربر میهمان
دیدگاه شما چیست؟
کاربر میهمان