قابلیت ارتقای مشخصه سازنده در PHP — به زبان ساده

به زودی شاهد انتشار رسمی جدیدترین نسخه از زبان برنامهنویسی پیاچپی به نام PHP 8 خواهیم بود که قابلیتها و امکانات تازه زیادی را معرفی میکند. یکی از این امکانات مهم «ارتقای مشخصه سازنده» (Constructor Property Promotion) نام دارد. این قابلیت موجب میشود که بخش زیادی از کدهای تکراری که در زمان ساخت اشیای ساده مانند «اشیای مقداری» (VO) و «اشیای انتقال داده» (DTO) باید مینوشتیم، کاهش یابند. در این مقاله به توضیح قابلیت ارتقای مشخصه سازنده در PHP میپردازیم.
به طور خلاصه ارتقای مشخصه موجب میشود که بتوانیم فیلدهای کلاس، تعریف سازنده و انتسابهای متغیر را در یک ساختار منفرد در لیست پارامتر سازنده ترکیب کنیم. به این ترتیب به جای این که به روش قدیم کدی مانند زیر بنویسیم:
class CustomerDTO { public string $name; public string $email; public DateTimeImmutable $birth_date; public function __construct( string $name, string $email, DateTimeImmutable $birth_date ) { $this->name = $name; $this->email = $email; $this->birth_date = $birth_date; } }
میتوانیم از کدی مانند زیر استفاده کنیم:
class CustomerDTO { public function __construct( public string $name, public string $email, public DateTimeImmutable $birth_date, ) {} }
طرز کار قابلیت ارتقای مشخصه سازنده در PHP چگونه است؟
ایده اصلی این قابلیت کاملاً ساده است. ما میخواهیم همه مشخصههای کلاس، انتسابهای مقادیر و پیشوندهای پارامتر سازنده مانند public ،protected یا private را حذف کنیم.
PHP ساختار جدیدی دریافت میکند و آن را در پسزمینه پیش از اجرای عملی کد، به ساختار قدیمی تبدیل میکند.
بنابراین در ساختار جدید از کدی مانند زیر استفاده کنیم:
class MyDTO { public function __construct( public string $name = 'Brent', ) {} }
که جایگزین کد قدیمی زیر شده است:
class MyDTO { public string $name; public function __construct( string $name = 'Brent' ) { $this->name = $name; } }
توجه کنید که مقدار پیشفرض روی مشخصه کلاس تعیین نشده است، بلکه روی آرگومان متد در سازنده تعیین گشته است.
مشخصات مشخصه ارتقا یافته
در این بخش به بررسی مشخصههای این قابلیت جدید و کارهایی که میتوانیم یا نمیتوانیم با آن انجام دهیم میپردازیم. در این خصوص موارد ظریف زیادی وجود دارند که باید به آنها توجه داشته باشیم.
کاربرد صرف در سازندهها
توجه کنید که مشخصههای ارتقا یافته را تنها میتوان در سازندهها (Constructors) استفاده کرد. این موضوع شاید بدیهی به نظر برسد، اما برای روشنتر شدن موضوع لازم بود که دوباره یادآوری کنیم.
امکان استفاده از هر دو روش وجود ندارد
شما نمیتوانید یک مشخصه کلاس و یک مشخصه ارتقا یافته را با نام یکسان اعلان کنید. این موضوع منطقی هم نیست، زیرا مشخصه ارتقا یافته در عمل در زمان اجرا به مشخصه کلاس ترجمه میشود:
class MyClass { public string $a; public function __construct( public string $a, ) {} }
مشخصههای بدون نوع مجاز هستند
امکان ارتقای مشخصههای فاقد نوع وجود دارد، با این حال با توجه به رویکردهای PHP مدرن، بهتر است که هر چیزی را نوعبندی کنید.
class MyDTO { public function __construct( public $untyped, ) {} }
مقادیر ساده پیشفرض
مشخصههای ارتقا یافته میتوانند مقادیر پیشفرض داشته باشند، اما عبارتهایی مانند …new مجاز نیستند.
public function __construct( public string $name = 'Brent', public DateTimeImmutable $date = new DateTimeImmutable(), ) {}
ترکیب مشخصههای ارتقا یافته و معمولی
همه مشخصههای سازنده نباید ارتقا یابند و شما میتوانید آنها را ترکیب و تطبیق دهید.
class MyClass { public string $b; public function __construct( public string $a, string $b, ) { $this->b = $b; } }
البته در زمان ترکیب ساختارها باید هوشیار باشید، اگر این کار موجب میشود که کد روشن نباشد، بهتر است به جای آن از سازنده معمولی استفاده کنید.
دسترسی به مشخصههای ارتقا یافته از بدنه سازنده
شما مجاز به خواندن مشخصههای ارتقا یافته از بدنه سازنده هستید. این موضوع میتواند در صورت نیاز به بررسیهای اعتبارسنجی بیشتر، مفید باشد. شما میتوانید از هر دو نوع متغیرهای لوکال و متغیر وهلهای استفاده کنید و هر دو نوع به درستی کار میکنند.
public function __construct( public int $a, public int $b, ) { assert($this->a >= 100); if ($b >= 0) { throw new InvalidArgumentException('…'); } }
کامنتهای مستندسازی روی مشخصههای ارتقا یافته
امکان افزودن توضیحهای مستندسازی به مشخصههای ارتقا یافته وجود دارد و همچنان در زمان رفلکشن در دسترس شما قرار دارند.
class MyClass { public function __construct( /** @var string */ public $a, ) {} }
$property = new ReflectionProperty(MyClass::class, 'a'); $property->getDocComment(); // "/** @var string */"
خصوصیتها
همانند کامنتهای مستندسازی که در بخش قبل اشاره کردیم، امکان استفاده از «خصوصیتها» (Attributes) روی مشخصههای ارتقا یافته وجود دارد. زمانی که این موارد transpile میشوند، روی هر دو نوع پارامتر سازنده و همچنین مشخصه کلاس در دسترس قرار میگیرند.
class MyClass { public function __construct( #[MyAttribute] public $a, ) {} }
که به حالت زیر transpile میشود:
class MyClass { #[MyAttribute] public $a; public function __construct( #[MyAttribute] $a, ) { $this->a = $a; } }
در سازندههای مجرد مجاز نیستند
شاید حتی ندانید که نوعی از سازندهها به نام «سازندههای مجرد» (Abstract Constructors) وجود دارند، اما به هر حال باید بدانید که مشخصههای ارتقا یافته روی این نوع از سازندهها مجاز نیستند.
abstract class A { abstract public function __construct( public string $a, ) {} }
روی خصیصهها مجاز هستند
اما از سوی دیگر امکان استفاده از مشخصههای ارتقا یافته روی «خصیصهها» (Traits) وجود دارد. این وضعیت منطقی است چون ساختار تبدیل یافته (Transpiled) نیز در خصیصهها مجاز است.
trait MyTrait { public function __construct( public string $a, ) {} }
Var پشتیبانی نمیشود
توسعهدهندگان پیشکسوت و باتجربه PHP ممکن است در گذشته دور از کلیدواژه var برای اعلان متغیرهای کلاس استفاده کرده باشند. باید اشاره کنیم که امکان استفاده از آن در مشخصههای ارتقا یافته وجود ندارد. تنها کلیدواژههای مجاز شامل موارد public ،protected و private هستند.
public function __construct( var string $a, ) {}
پارامترهای نامعین (Variadic) نمیتوانند ارتقا یابند
از آنجا که نمیتوانید دادهای را به نوعی از «آرایه نوعها» (array of type) تبدیل کنید، امکان ارتقای پارامترهای نامعین نیز وجود ندارد.
public function __construct( public string ...$a, ) {}
در این مورد باید از ژنریکها استفاده کنیم.
Reflection برای isPromoted
هر دو گزینه ReflectionProperty و ReflectionParameter اینک دارای یک متد به نام isPromoted هستند که بررسی میکند آیا مشخصه کلاس یا پارامتر متد ارتقا یافته است.
وراثت
از آنجا که سازندههای PHP لزومی به تبعیت از اعلان سازنده والد خود ندارند، بنابراین به طور مختصر میتوان اشاره کرد که وراثت در مشخصههای ارتقا یافته مجاز است. با این حال اگر نیاز به ارسال مشخصهها از سازنده فرزند به سازنده والد باشد، باید آنها را به صورت دستی ارسال کنید:
class A { public function __construct( public $a, ) {} } class B extends A { public function __construct( $a, public $b, ) { parent::__construct($a); } }
سخن پایانی
به این ترتیب به انتهای این مقاله با موضوع مشخصههای ارتقا یافته در PHP میرسیم. استفاده از این قابلیت جدید PHP 8 موجب سهولت زیادی در فرایند کارها میشود و از این رو پیشنهاد میکنیم، آن را جایگزین روش قدیمی اعلان مشخصههای کلاس بکنید.