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