اتریبیوتهای PHP: متادیتا کنار خود کد
خلاصهٔ کاملتر
نویسنده با یه مثال آشنا شروع میکنه: یه دیسپچر وبهوک باید بدونه کدوم هندلر invoice.paid رو قبول میکنه، و راه معمول اینه که این نگاشت توی یه آرایهٔ مرکزی بمونه. کار میکنه، ولی اطلاعات از خود هندلر جدا میافته و هر کسی که هندلر جدید میسازه باید یادش باشه یه جای دوم رو هم عوض کنه. اتریبیوتهای PHP راه دیگهای میدن: متادیتای ساختاریافته و ماشینخوان رو مستقیم بچسبون به خود کد.
مدل ذهنی اصلی اینه که اتریبیوت کد رو توصیف میکنه، نه اینکه اجراش کنه. یه #[Audit('orders')] روی یه کلاس هیچ رکورد ممیزی نمیسازه و هیچ لاگری صدا نمیزنه؛ فقط میگه این کلاس متادیتای ممیزی داره و استریمش orders ـه. یه چیز دیگه — اپلیکیشن، فریمورک، پکیج، تسترانر یا ابزار تحلیل ایستا — باید اون اعلان رو بخونه و تصمیم بگیره یعنی چی. به همین خاطر اتریبیوتها بخشی از متابرنامهنویسیان: کد دربارهٔ کد استدلال میکنه.
نویسنده مرز سه تا ابزار رو هم روشن میکنه: PHPDoc برای اطلاعات توسعهدهنده و تحلیل ایستاست و PHP ازش آبجکت زمان اجرا نمیسازه؛ اتریبیوت برای متادیتای پایداریه که یه مصرفکننده در زمان اجرا بازرسیش میکنه؛ و کانفیگ برای مقدارهاییه که بین محیطها و دیپلویها فرق میکنن. آرگومانهای اتریبیوت هم فقط میتونن چیزهایی باشن که PHP توی خود اعلان ارزیابی میکنه — اسکالر، آرایه، ثابت، کیس enum و نام کلاس — پس جای خوندن تنظیمات مستأجر یا سکرتهای محیطی نیستن.
از نظر ساختار، اتریبیوت یه کلاس معمولیه که با اتریبیوت داخلی Attribute علامت خورده. Attribute::TARGET_CLASS و همخانوادههاش محدود میکنن که کجا میشه ازش استفاده کرد و با بیتماسک ترکیب میشن، و کانستراکتور در عمل همون API عمومی اتریبیوته:
#[Attribute(Attribute::TARGET_CLASS)]
final readonly class HandlesWebhook
{
public function __construct(
public string $event,
public bool $verifySignature = true,
) {}
}چرخهٔ عمرش هم سادهست: اتریبیوت رو اعلان میکنی، به کد میچسبونیش، با Reflection کلاس رو بازتاب میدی و متادیتا رو میخونی. نکتهٔ ظریف اینه که getAttributes() هنوز آبجکت HandlesWebhook نمیده، فقط ReflectionAttribute میده؛ ساخت واقعی و صدا زدن کانستراکتور وقتی اتفاق میافته که newInstance() رو صدا بزنی، برای همین کانستراکتور باید کوچیک، دادهمحور و بدون side effect بمونه:
$class = new ReflectionClass(RecordInvoicePayment::class);
$attributes = $class->getAttributes(HandlesWebhook::class);
$metadata = $attributes[0]->newInstance();سود اصلی به گفتهٔ نویسنده locality ـه: کسی که RecordInvoicePayment رو باز میکنه بلافاصله میبینه هندلر وبهوکه و چه ایونتی رو قبول میکنه، و کسی که هندلر جدید میسازه یه جای بدیهی برای اعلانش داره. کنارش قرارداد تایپدار بهجای کلیدهای آرایه، یکدستی واژگان، ناوبری بهتر تو IDE و اعتبارسنجی زودهنگام هم میآد. ولی نویسنده تأکید میکنه هیچکدوم از اینها خودکار نیستن؛ اتریبیوت فقط وقتی طراحی رو بهتر میکنه که متادیتا واقعاً به همون کد تعلق داشته باشه.
نکات کلیدی:
- اتریبیوت متادیتاست، نه رفتار؛ تا یه مصرفکننده نخونتش هیچ اتفاقی نمیافته
- از PHP 8 یه قابلیت زبانی درجهیکه و روی کلاس، متد، تابع، پراپرتی، ثابت کلاس و پارامتر میشینه
- PHPDoc برای تحلیل ایستا، اتریبیوت برای متادیتای پایدارِ زمان اجرا، کانفیگ برای مقدارهای وابسته به محیط
- #[Attribute(...)] و ثابتهای TARGET_* مشخص میکنن کجا مجازه ازش استفاده بشه؛ بیتماسک چند هدف رو ترکیب میکنه
- کانستراکتور همون API عمومی اتریبیوته: تایپ، مقدار پیشفرض و آرگومان نامدار
- getAttributes() فقط ReflectionAttribute میده؛ نمونهسازی با newInstance() انجام میشه
- کانستراکتور اتریبیوت باید سبک و بدون side effect باشه، وگرنه کشف متادیتا گرون و غیرقابل پیشبینی میشه




