Sokan Academy

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

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

با عرضهٔ PHP 7، فیچر جدیدی تحت عنوان Return Type Declaration به این زبان افزوده شد که بر آن اساس توسعه‌دهنده می‌تواند دیتا تایپی که یک تابع ریترن می‌کند را مشخص سازد و در این آموزش قصد داریم تا با ذکر یک سری مثال،‌ این قابلیت زبان پی‌اچ‌پی را مورد بررسی قرار دهیم. برای شروع، داخل پوشهٔ oop پروژه‌ای تحت عنوان return-type-declaration ساخته و ساختار فولدربندی که در این دورهٔ آموزشی تاکنون مورد استفاده قرار داده‌ایم را در آن ایجاد می‌کنیم و در ادامه وارد پوشهٔ classes شده و فایلی تحت عنوان User.php داخل آن ساخته و متد زیر را داخلش می‌نویسیم:

<?php
namespace SokanAcademy;

class User
{
    public function returnUserAge($age)
    {
       return $age;
    }
}

پس از ساخت کلاسی به نام User، متدی ساخته‌ایم تحت عنوان ()returnUserAge که یک پارامتر ورودی می‌گیرد به نام age$ و در داخل بدنهٔ این متد نیز پارامتر ورودی‌اش را ریترن کرده‌ایم. جهت تست این متد، فایلی به نام index.php ساخته و آن را به صورت زیر تکمیل می‌کنیم:

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

$user = new SokanAcademy\User();
echo $user->returnUserAge(30);

به عنوان خروجی این فایل هم خواهیم داشت:

30

حال قصد داریم تا دستور دهیم که خروجی متد مذکور حتماً می‌باید عدد صحیح باشد؛ از همین روی،‌ کلاس User را به صورت زیر ریکفتور می‌کنیم:

public function returnUserAge($age): int
{
    return $age;
}

در واقع،‌ پس از علائم () از علامت : استفاده کرده و در ادامه کیورد int را آورده‌ایم. از این پس، مشخص کرده‌ایم که خروجی این متد فقط و فقط می‌باید عدد صحیح باشد اما اگر اکنون به فایل index.php بازگشته و مجدد آن را اجرا کنیم، در خروجی تغییر حاصل نخواهد شد چرا که آرگومان درستی برای این متد در نظر گرفته شده است. حال جهت تست این موضوع،‌ متد فوق را به صورت زیر تغییر می‌دهیم:

public function returnUserAge($age): int
{
    return $age + 1.1;
}

اگر به فایل index.php رفته و مجدد آن را اجرا کنیم، در خروجی خواهیم داشت:

31

در حقیقت،‌ آرگومان 30 برای این متد در نظر گرفته شده و داخل بدنهٔ این متد نیز عدد ۱/۱ نیز به آن افزوده شده که در نهایت می‌باید انتظار خروجی ۳۱/۱ را داشته باشیم اما می‌بینیم که این عدد رُند شده و به ۳۱ تغییر یافته است و علت این مسئله از آنجا ناشی می‌شود که در زبان پی‌اچ‌پی به طور پیش‌فرض قابلیت Strict Type غیرفعال است و همین مسئله باعث می‌گردد گاهی نتایج غیرمنتظره‌ای را شاهد باشیم که این موضوع را هیچ چیز بهتر از یک سری نمونه کد نشان نمی‌دهد به طوری که برای مثال داریم:

<?php
function add(int $one, int $two)
{
    echo $one + $two;
}
add(1, 2);

فانکشنی نوشته‌ایم به نام ()add که دو پارامتر ورودی از جنس int می‌گیرد و داخل بدنهٔ این فانکشن نیز این دو پارامتر را با یکدیگر جمع جبری کرده و خروجی را چاپ کرده‌ایم و زمانی هم که این فانکشن را با آرگومان‌های ۱ و ۲ مورد استفاده قرار می‌دهیممم، در خروجی شاهد عدد ۳ هستیم. در عین حال،‌ کد زیر نیز به درستی کار می‌کند:

<?php
function add(int $one, int $two)
{
    echo $one + $two;
}
add('1', '2');

می‌بینیم که در حین استفاده از فانکشن ()add از دو استرینگ به عنوان آرگومان استفاده کرده‌ایم اما در عین حال مفسر پی‌اچ‌پی به صورت خودکار آن‌ها را به عدد صحیح تبدیل کرده و با یکدیگر جمع می‌کند و مجدد خروجی ۳ را شاهد خواهیم بود. در کمال ناباوری، کد زیر نیز کار خواهد کرد البته دو هشدار هم در معرض دیدمان خواهد گذاشت:

<?php
function add(int $one, int $two)
{
    echo $one + $two;
}
add('1a', '2b');

برای آن که کاری کنیم تا مفسر پی‌اچ‌پی به صورت خودکار دیتا تایپ‌ها را به یکدیگر مبدل نسازد، می‌باید از دستور زیر در خط اول فایل استفاده کرد:

<?php
declare (strict_types = 1);
function add(int $one, int $two)
{
    echo $one + $two;
}
add('1', '2');

در صورت اجرای کد فوق،‌ در خروجی با ارور زیر مواجه خواهیم شد:

PHP Fatal error:  Uncaught TypeError: Argument 1 passed to add() must be of the type int, string given.

متن ارور حاکی از آن است که پارامتر اول فانکشن ()add می‌باید از جنس عدد صحیح باشد اما یک استرینگ پاس داده شده است. در واقع،‌ کاری که این دستور انجام می‌دهد آن است که قابلیت به اصطلاح Weak Type Checking پی‌اچ‌‌پی را به وضعیت Strict Type Checking قرار می‌دهد بدان معنا که دیتا تایپ‌ها می‌باید دقیقاً همانی باشند که در کد مشخص شده‌اند.

به خاطر داشته باشید محل قرارگیری دستور (declare (strict_types = 1 می‌باید پس از علائم php?> و قبل از هر دستور دیگری باشد. همچنین توجه داشته باشیم که عملکرد این دستور بر پایهٔ هر فایل است؛ به عبارتی،‌ این قابلیت فقط در همان فایلی فعال می‌گردد که دستور فوق داخل آن درج شده است و روی دیگر بخش‌های سورس‌کد اِعمال نخواهد شد.

اگر مجدد به کلاس User بازگردیم، می‌بینیم که در صورت عدم استفاده از دستور (declare (strict_types = 1 کیورد int که این دستور را می‌دهد تا مقدار بازگشتی حتماً می‌باید عدد صحیح باشد کار نخواهد کرد که برای رفع این مشکل،‌ این کلاس را به صورت زیر آپدیت می‌کنیم:

<?php
declare (strict_types = 1);
namespace SokanAcademy;

class User
{
    public function returnUserAge($age): int
    {
        return $age + 1.1;
    }
}

حال اگر مجدد به فایل index.php بازگشته و آن را اجرا کنیم، خواهیم داشت:

/var/www/oop/return-type-declarations$ php index.php 
PHP Fatal error:  Uncaught TypeError: Return value of SokanAcademy\User::returnUserAge() must be of the type int, float returned in /var/www/oop/return-type declarations/classes/User.php:9

همان‌طور که انتظار می‌رفت،‌ متن ارور حاکی از آن است که مقدار بازگشتی متد ()returnUserAge می‌باید عدد صحیح باشد، اما یک عدد اعشاری ریترن شده است و در صورتی که این متد را به صورت زیر تغییر دهیم، این ارور نیز مرتفع خواهد شد:

public function returnUserAge($age): int
{
    return $age; // or return $age + 1; 
}

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

یکی دیگر از ریترن تایپ‌هایی که در نسخهٔ PHP 7 ساپورت می‌شود، float است به طوری که برای مثال داریم:

public function returnUserHeight($height): float
{
    return $height; 
}

در ادامهٔ تکمیل کلاس User، متدی نوشته‌ایم تحت عنوان ()returnUserHeight که ریترن تایپ در نظر گرفته شده برای این متد float است؛ به عبارت بهتر،‌ مقدار بازگشتی توسط این متد حتماً می‌باید عدد اعشاری باشد. جهت تست این متد، وارد فایل index.php شده و آن را به صورت زیر تغییر می‌دهیم:

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

$user = new SokanAcademy\User();
// echo $user->returnUserAge(30);
echo $user->returnUserHeight('30.1');

می‌بینیم که به جای یک عدد اعشاری، آرگومانی از جنس استرینگ برای این متد در نظر گرفته‌ایم و در صورت اجرای این فایل، در خروجی خواهیم داشت:

PHP Fatal error:  Uncaught TypeError: Return value of SokanAcademy\User::returnUserHeight() must be of the type float, string returned in /var/www/oop/return-type-declarations/classes/U
ser.php:14

متن ارور حاکی از آن است که مقدار بازگشتی متد ()returnUserHeight می‌باید یک عدد اعشاری باشد اما استرینگ ریترن شده است. در ادامه، متدی تحت عنوان ()returnUserName به کلاس User می‌افزاییم که ریترن تایپ آن string است:

public function returnUserName($name): string
{
    return $name; 
}

حال فایل index.php را به صورت زیر آپدیت می‌کنیم:

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

$user = new SokanAcademy\User();
// echo $user->returnUserAge(30);
// echo $user->returnUserHeight('30.1');
echo $user->returnUserName(110);

و به عنوان خروجی هم خواهیم داشت:

PHP Fatal error:  Uncaught TypeError: Return value of SokanAcademy\User::returnUserName() must be of the type string, int returned in /var/www/oop/return-type-declarations/classes/User.php:19

این ارور بدان معنا است که مقدار بازگشتی متد ()returnUserName می‌باید استرینگ باشد اما عدد صحیح بازگردانده شده است که برای رفع این ارور، کد فوق را می‌توان به صورت زیر ریفکتور کرد:

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

$user = new SokanAcademy\User();
// echo $user->returnUserAge(30);
// echo $user->returnUserHeight('30.1');
echo $user->returnUserName('110');

در صورت اجرای این فایل،‌ خواهیم دید که ارور از بین خواهد رفت و دلیلش هم آن است که با درج علائم '' در اطراف عدد ۱۱۰ آن را به یک استرینگ مبدل ساخته‌ایم و این دقیقاً همان چیزی است که متد مذکور به آن نیاز دارد. در ادامه، متد دیگری به نام ()returnUserFullName به کلاس User می‌افزاییم که ریترن تایپ آن array است:

public function returnUserFullName($arr): array
{
    return $arr; 
}

این متد را به صورت زیر می‌توان مورد استفاده قرار داد:

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

$user = new SokanAcademy\User();
// echo $user->returnUserAge(30);
// echo $user->returnUserHeight('30.1');
// echo $user->returnUserName('110');
var_dump($user->returnUserFullName(['firstname' => 'Behzad', 'lastname' => 'moradi']));

با توجه به این که خروجی این تابع یک آرایه است، مسلماً قادر به استفاده از دستوراتی همچون echo و print نیستیم و در عوض از دستور ()var_dump استفاده کرده‌ایم. همچنین از آنجا که آرگومان ورودی این تابع یک آرایه است، مفسر پی‌اچ‌پی بدون هیچ گونه اروری خروجی را ریترن می‌کند اما این در حالی است که اگر هر دیتا تایپ دیگری همچون یک استرینگ، عدد اعشاری، عدد صحیح یا ... استفاده نماییم، با ارور مواجه خواهیم شد. به علاوه، می‌توان این دستور را به مفسر پی‌اچ‌پی داد که خروجی یک تابع بولین باشد. برای مثال،‌ متدی تحت عنوان ()isDeveloper به صورت زیر تعریف می‌کنیم:

public function isDeveloper($value): bool
{
    return $value; 
}

همان‌طور که ملاحظه می‌شود، با درج کیورد bool گفته‌ایم که خروجی این متد می‌باید حتماً‌ true یا false باشد و به صورت زیر هم آن را مورد استفاده قرار داده‌ایم:

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

$user = new SokanAcademy\User();
// echo $user->returnUserAge(30);
// echo $user->returnUserHeight('30.1');
// echo $user->returnUserName('110');
// var_dump($user->returnUserFullName(['firstname' => 'Behzad', 'lastname' => 'moradi']));
echo $user->isDeveloper(true);

با در نظر گرفتن آرگومان true برای این متد و اجرای فایل index.php، می‌بینیم که در خروجی عدد ۱ چاپ می‌شود و اگر این متد را به صورت زیر فراخوانی کنیم:

echo $user->isDeveloper('true');

در خروجی شاهد ارور زیر خواهیم بود:

PHP Fatal error:  Uncaught TypeError: Return value of SokanAcademy\User::isDeveloper() must be of the type bool, string returned in /var/www/oop/return-type-declarations/classes/User.php:29

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

public function sendMessage($obj): Email
{
    return $obj; 
}

متدی نوشته‌ایم به نام ()sendMessage که ریترن تایپ آن کلاسی است تحت عنوان Email و از آنجا که چنین کلاسی وجود خارجی ندارد، ابتدا وارد پوشهٔ classes شده و فایلی تحت عنوان Email.php ساخته و آن را به صورت زیر تکمیل می‌کنیم:

<?php
namespace SokanAcademy;

class Email
{}

با توجه به این که مثال فوق صرفاً‌ جهت تست است، داخل بدنهٔ این کلاس هیچ گونه پراپرتی یا متدی تعریف نکرده‌ایم. حال وارد فایل index.php شده و آن را به صورت زیر تکمیل می‌کنیم:

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

$user = new SokanAcademy\User();
// echo $user->returnUserAge(30);
// echo $user->returnUserHeight('30.1');
// echo $user->returnUserName('110');
// var_dump($user->returnUserFullName(['firstname' => 'Behzad', 'lastname' => 'moradi']));
// echo $user->isDeveloper('true');
var_dump($user->sendMessage(new SokanAcademy\Email));

می‌بینیم که به عنوان آرگومان ورودی متد ()sendEmail آبجکتی از روی کلاس Email ساخته‌ایم و اگر این فایل را اجرا کنیم، در خروجی خواهیم داشت:

object(SokanAcademy\Email)#2 (0) {
}

و اگر هم هر چیز به غیر از نمونه‌ای از کلاس Email در نظر بگیریم، با ارور مواجه خواهیم شد. علاوه بر ریترن تایپ کلاس‌‌ها،‌ می‌توان یک اینترفیس را نیز به عنوان خروجی یک تابع در نظر گرفت. برای این منظور، داخل پوشهٔ classes فایلی به نام MessageInterface.php ساخته و آن را به صورت زیر تکمیل می‌کنیم:

<?php
namespace SokanAcademy;

interface MessageInterface
{

}

حال متد ()sendMessage را به صورت زیر آپدیت می‌کنیم:

public function sendMessage($obj): MessageInterface
{
    return $obj; 
}

همان‌طور که ملاحظه می‌شود، به جای کلاس Email از اینترفیس MessageInterface به عنوان ریترن تایپ استفاده نموده‌ایم. در ادامه، نیاز است تا کلاس Email از این اینترفیس ایمپلیمنت کند که برای همین منظور داریم:

<?php
namespace SokanAcademy;

class Email implements MessageInterface
{
    
}

حال اگر فایل index.php را اجرا کنیم، در خروجی خواهیم داشت:

object(SokanAcademy\Email)#2 (0) {
}

می‌بینیم که این متد بدون هیچ گونه مشکلی کار می‌کند. همچنین به منظور تست بیشتر،‌ داخل پوشهٔ classes فایلی به نام SMS.php ساخته و آن را به صورت زیر تکمیل می‌کنیم:

<?php
namespace SokanAcademy;

class SMS implements MessageInterface
{

}

اکنون مجدد به فایل index.php رجوع کرده و آن را به صورت زیر آپدیت می‌کنیم:

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

$user = new SokanAcademy\User();
// echo $user->returnUserAge(30);
// echo $user->returnUserHeight('30.1');
// echo $user->returnUserName('110');
// var_dump($user->returnUserFullName(['firstname' => 'Behzad', 'lastname' => 'moradi']));
// echo $user->isDeveloper('true');
// var_dump($user->sendMessage(new SokanAcademy\Email));
var_dump($user->sendMessage(new SokanAcademy\SMS));

در واقع،‌ به جای کلاس Email از کلاس SMS استفاده کرده و در خروجی هم خواهیم داشت:

object(SokanAcademy\SMS)#2 (0) {
}

در حقیقت، با توجه به این که ریترن تایپ متد ()sendMessage اینترفیس MessageInterface است و هر دو کلاس Email و SMS از این اینترفیس ایمپلیمنت کرده‌اند، این متد به درستی اجرا می‌گردد و در صورتی هم که نوع فراخوانی آن را به صورت زیر تغییر دهیم:

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

$user = new SokanAcademy\User();
// echo $user->returnUserAge(30);
// echo $user->returnUserHeight('30.1');
// echo $user->returnUserName('110');
// var_dump($user->returnUserFullName(['firstname' => 'Behzad', 'lastname' => 'moradi']));
// echo $user->isDeveloper('true');
// var_dump($user->sendMessage(new SokanAcademy\Email));
// var_dump($user->sendMessage(new SokanAcademy\SMS));
var_dump($user->sendMessage(new stdClass));

به عنوان خروجی خواهیم داشت:

PHP Fatal error:  Uncaught TypeError: Return value of SokanAcademy\User::sendMessage() must be an instance of SokanAcademy\MessageInterface, instance of stdClass returned in /var/www/oop/return-type-declarations/classes/User.php:34

متن ارور حاکی از آن است که ریترن تایپ متد ()sendMessage می‌باید کلاسی ایمپلیمنت‌شده از روی اینترفیس MessageInterface باشد اما نمونه‌ای از کلاس stdClass ریترن شده است.

نوع دیگری از ریترن تایپ وجود دارد تحت عنوان Nullable که این امکان را در اختیار توسعه‌دهنده قرار می‌دهد تا علاوه بر یک دیتا تایپ خاص، مقدار null را نیز به عنوان نتیجهٔ متد ریترن کند که برای درک بهتر این موضوع، متدی تحت عنوان ()checkGender به صورت زیر به انتهای کلاس User می‌افزاییم:

public function checkGender($gender): ?string
{
    if (is_string($gender)) {
        if ($gender == 'male') {
            return $gender;
        } else if ($gender == 'female') {
            return $gender;
        } else {
            return null;
        }
    }
}

همان‌طور که ملاحظه می‌شود، پس از علائم () مرتبط با این متد یک : قرار داده سپس دستور string? را نوشته‌ایم و داخل این متد هم با دستور شرطی بیرونی ابتدا به ساکن چک کرده‌ایم که مقدار پارامتر ورودی حتماً استرینگ باشد سپس با دستورات شرطی درونی مقدار پارامتر ورودی gender$ را چک کرده‌ایم که اگر یکی از مقادیر male یا female نبود، مقدار null بازگردانده شود. جهت تست،‌ فایل index.php را به صورت زیر آپدیت می‌کنیم:

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

$user = new SokanAcademy\User();
// echo $user->returnUserAge(30);
// echo $user->returnUserHeight('30.1');
// echo $user->returnUserName('110');
// var_dump($user->returnUserFullName(['firstname' => 'Behzad', 'lastname' => 'moradi']));
// echo $user->isDeveloper('true');
// var_dump($user->sendMessage(new SokanAcademy\Email));
// var_dump($user->sendMessage(new SokanAcademy\SMS));
// var_dump($user->sendMessage(new stdClass));
echo $user->checkGender('male');

اگر این فایل را از طریق مرورگر و یا کامندلاین اجرا کنیم، به عنوان خروجی خواهیم داشت:

male

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

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

$user = new SokanAcademy\User();
// echo $user->returnUserAge(30);
// echo $user->returnUserHeight('30.1');
// echo $user->returnUserName('110');
// var_dump($user->returnUserFullName(['firstname' => 'Behzad', 'lastname' => 'moradi']));
// echo $user->isDeveloper('true');
// var_dump($user->sendMessage(new SokanAcademy\Email));
// var_dump($user->sendMessage(new SokanAcademy\SMS));
// var_dump($user->sendMessage(new stdClass));
echo $user->checkGender(15);

اکنون به عنوان نتیجهٔ اجرای اسکریپت فوق خواهیم داشت:

PHP Fatal error:  Uncaught TypeError: Return value of SokanAcademy\User::checkGender() must be of the type string or null, none returned in /var/www/oop/return-type-declarations/classes/User.php:47

در متن ارور آمده است که مقدار بازگشتی متد ()checkGender می‌باید استرینگ یا نال باشد در حالی که هیچ‌ چیزی ریترن نشده است.

توجه داشته باشیم که ریترن تایپ Nullable را می‌توان در ارتباط با دیگر دیتا تایپ‌ها نیز مورد استفاده قرار داد. به عنوان مثالی دیگر، اگر بخواهیم دستور دهیم که خروجی متد حتماً عدد صحیح یا نال باشد، از دستور int? استفاده می‌کنیم.

جمع‌بندی
پس از عرضهٔ PHP 7 قابلیتی به این زبان افزوده شد به نام Return Type Declaration که بر آن اساس می‌توانیم دقیقاً مشخص سازیم یک تابع چه دیتا تایپی را می‌تواند به عنوان خروجی ریترن کند که از آن جمله می‌توان به bool ،string ،float ،int و ... اشاره کرد که در این آموزش به طور مفصل این موارد به علاوهٔ ریترن تایپ‌های دیگر توضیح داده شد.


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