Typed class propertie ها در php 7.4 اضافه شدهاند و بهبود عمدهای در type system پی اچ پی ایجاد کرده است.این تغییرات نسبت به نسخههای قبلی کاملاً انتخابی است.
در این مقاله از راکت این ویژگیها را به صورت عمیق بررسی خواهیم کرد، اما بیایید ابتدا با خلاصهای از مهمترین نکات شروع کنیم:
- آنها از php 7.4 در دسترس قرار گرفتند که در نوامبر سال ۲۰۱۹ منتشر شده است.
- آنها فقط در کلاسها موجود هستند و به یک access modifier نیاز دارند : public، protected یا private ؛ یا var .
- همهی typeها مجاز هستند، به جز void و callable.
این چیزی است که در عمل به نظر میرسند:
class Foo
{
public int $a;
public ?string $b = 'foo';
private Foo $prop;
protected static string $static = 'default';
}
اگر در مورد مزایایی که به type ها اضافه شده هنوز مردد هستید، توصیه میکنم ابتدا این مقاله را بخوانید.
Uninitialized (بدون مقدار اولیه)
قبل از دیدن چیزهای جالب، یک جنبه مهم در مورد typed propertie ها وجود دارد، که لازم است در ابتدا در مورد آن صحبت کنیم.
علیرغم آنچه در نگاه اول فکر میکنید، کد زیر معتبر است:
class Foo
{
public int $bar;
}
$foo = new Foo;
حتی اگر مقدار $bar بعد از ساخت یک آبجکت از Foo، یک عدد صحیح نباشد؛ php فقط هنگام دسترسی به $bar خطایی ایجاد میکند:
var_dump($foo->bar);
Fatal error: Uncaught Error: Typed property Foo::$bar
must not be accessed before initialization
همانطور که میتوانید از پیام خطا بخوانید، نوع جدیدی از " حالت متغییر " وجود دارد: بدون مقدار اولیه.
اگر $bar یک نوع یا type نداشته باشد، مقدار آن به راحتی میتواند null باشد. اگرچه type ها هم میتوانند nullable باشند، بنابراین نمیتوان تعیین کرد که آیا خاصیت نوع nullable تنظیم شده است یا اینکه به سادگی فراموش شده. به همین دلیل "uninitialized" یا همان بدون مقدار اولیه اضافه شد.
چهار چیز مهم در مورد uninitialized وجود دارد که باید به خاطر بسپارید:
- شما نمیتوانید از uninitialized propertie چیزی بخوانید، انجام این کار منجر به یک خطای مهلک (fatal error ) میشود.
- از آنجا که حالت uninitialized در هنگام دسترسی به یک property چک میشود، حتی اگر نوع آن non-nullable باشد، میتوانید یک آبجکت با uninitialized بسازید.
- میتوانید قبل از اینکه از uninitialized property چیزی بخوانید، در آن بنویسید.
- استفاده از unset در یک typed property آن را uninitialized (بدون مقدار اولیه) میکند، در حالی که تنظیم نکردن (unset) یکuntyped property آن را null میکند.
بهخصوص به این نکته توجه داشته باشید که کد زیر، جایی که یک uninitialised (متغییری که بدون مقدار اولیه است) و non-nullable property، بعد از ساخت آبجکت تنظیم میشوند، معتبر است.
class Foo
{
public int $a;
}
$foo = new Foo;
$foo->a = 1;
درحالی که حالت uninitialized فقط هنگام خواندن مقدار یک property بررسی میشود، type validation هنگام نوشتن در آن انجام میشود. این به این معنیست که شما میتوانید مطمئن باشید که هیچ invalid type (نوع نامعتبری) منتهی به مقدار یک property نمیشود.
پیشفرضها و constructor ها
بیایید نگاهی دقیقتر به نحوه مقدار دهی مقادیر type ها بیندازیم. در مورد scalar type ها، تهیه یک مقدار پیشفرض امکانپذیر است:
class Foo
{
public int $bar = 4;
public ?string $baz = null;
public array $list = [1, 2, 3];
}
توجه داشته باشید که فقط در صورتی که نوع آن nullable یا null پذیر باشد میتوانید از null به عنوان پیشفرض استفاده کنید. این ممکن است واضح به نظر برسد، اما چند رفتار قدیمی با پیشفرض پارامترها وجود دارد، که این موارد را مجاز میکند:
function passNull(int $i = null)
{ /* … */ }
passNull(null);
خوشبختانه این رفتار گیجکننده با typed properties ها مجاز نیست.
همچنین توجه داشته باشید که داشتن مقادیر پیشفرض با آبجکت یا class type ها غیرممکن است. برای تنظیم پیشفرضهای آنها باید از constructor استفاده کنید.
مکانی واضح برای مقداردهی typed value ها که البته باید constructor باشد.
class Foo
{
private int $a;
public function __construct(int $a)
{
$this->a = $a;
}
}
اما آنچه را که قبلاً ذکر کردم نیز به خاطر بسپارید: نوشتن یک uninitialized property، خارج از constructor مجاز است؛ تا زمانی که چیزی از property خوانده نشود، بررسی uninitialized انجام نمیشود.
انواع type ها
خب پس دقیقاً چه چیزی میتواند typed باشد؟ قبلاً اشاره کردم که typed propertie ها فقط در کلاسها کار خواهند کرد (البته فعلا) و آنها به یک access modifier یا کلمه کلیدی var در مقابل خود نیاز دارند.
همانطور که در typeهای مختلف موجود است، تقریباً از همه typeها میشود استفاده کرد به غیر از void و callable.
از آنجا که void به معنای عدم وجود یک مقدار است، منطقی است که نمیتوان از آن برای type یک مقدار استفاده کرد. Callable اما کمی متفاوت است.
ببینید، یک "callable" در php میتواند اینگونه نوشته شود:
$callable = [$this, 'method'];
و حالا این کد بهم ریخته را دارید:
class Foo
{
public callable $callable;
public function __construct(callable $callable)
{ /* … */ }
}
class Bar
{
public Foo $foo;
public function __construct()
{
$this->foo = new Foo([$this, 'method'])
}
private function method()
{ /* … */ }
}
$bar = new Bar;
($bar->foo->callable)();
در این مثال، $callable به private Bar::method برمیگردد، اما داخل Foo فراخوانی میشود. به خاطر این مشکل، تصمیم گرفته شد پشتیبانی callable اضافه نشود.
هر چند این مسأله مهمی نیست، زیرا Closure یک type معتبر است که $this این context را در جایی که ساخته شده یادآوری میکند.
با اتمام این موارد نسبتاً سخت، در اینجا لیستی از همهی type ها موجود است:
- bool
- int
- float
- string
- array
- iterable
- object
- ? (nullable)
- self & parent
- Classes & interfaces
type های اجباری یا strict types
php زبان پویایی است که ما آن را دوست داریم و در عین حال از آن متنفریم :))))، چرا که تا جایی که امکان دارد شما را مجبور خواهد کرد انواع مختلف را تبدیل کنید. مثلاً میخواهید یک استرینگ را در جایی که انتظار یک integer دارید، pass کنید. Php سعی میکند آن رشته را به طور خودکار تبدیل کند:
function coerce(int $i)
{ /* … */ }
coerce('1'); // 1
همین اصول در مورد typed propertie ها نیز اعمال میشوند. کد زیر '1' را به 1 تبدیل میکند و معتبر نیز هست.
class Bar
{
public int $i;
}
$bar = new Bar;
$bar->i = '1'; // 1
اگر این رفتار را دوست ندارید، میتوانید با اعلان strict type ها آن را غیرفعال کنید.
declare(strict_types=1);
$bar = new Bar;
$bar->i = '1'; // 1
Fatal error: Uncaught TypeError:
Typed property Bar::$i must be int, string used
Type های variance و inheritance
حتی اگر php۷.۴ یک type variance بهبود یافته ارائه دهد، typed propertie ها هنوز ثابت هستند.
این به این معنیست که موارد زیر معتبر نیستند:
class A {}
class B extends A {}
class Foo
{
public A $prop;
}
class Bar extends Foo
{
public B $prop;
}
Fatal error: Type of Bar::$prop must be A (as in class Foo)
مثال بالا چندان قابل توجه به نظر نمیرسد، باید نگاهی به موارد زیر بیندازید:
class Foo
{
public self $prop;
}
class Bar extends Foo
{
public self $prop;
}
php قبل از اجرای کد، self را در پشت صحنه با کلاس concrete که به آن اشاره دارد جایگزین میکند. این بدان معنیست که همان خطا در این مثال نیز آورده خواهد شد. تنها راه کنترل آن، انجام موارد زیر است:
class Foo
{
public Foo $prop;
}
class Bar extends Foo
{
public Foo $prop;
}
کمی درباره وراثت یا inheritance صحبت کنیم، ممکن است پیدا کردن موارد خوب برای بازنویسی انواع propertie های ارثی، برای شما دشوار باشد.
درحالی که من با این کار موافق هستم، ولی لازم به ذکر است که تغییر نوع یک inherited property ممکن است، اما فقط در صورتی کهaccess modifier نیز از private به protected یا public تغییر کند.
کد زیر صحیح است :
class Foo
{
private int $prop;
}
class Bar extends Foo
{
public string $prop;
}
با این حال، تغییر یک TYPE از nullable به non-nullable یا reverse، مجاز نیست.
class Foo
{
public int $a;
public ?int $b;
}
class Bar extends Foo
{
public ?int $a;
public int $b;
}
Fatal error: Type of Bar::$a must be int (as in class Foo)
و همین!
همانطور که در ابتدای مقاله گفته شد، typed propertie ها یکی از اصلیترین مواردی هستند که به php اضافه شدهاند. البته که چیزهای بسیار بیشتری برای گفتن وجود دارد؛ به همین علت به شما پیشنهاد میکنم که RFC را به دقت و با تمام جزئیات مطالعه کنید.
اگر در php تازه وارد هستید، احتمالاً میخواهید لیستی کامل از تغییرات ایجاد شده و ویژگیهای اضافه شده را بخوانید. صادقه بگوییم، این یکی از بهترین نسخههای طولانی مدت است و ارزش وقت گذاشتن را دارد!
در نهایت اگر نظری دارید در قسمت نظرات با ما به اشتراک بگذارید؛ امیدوارم این مقاله برای شما مفید بوده باشد.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید