مجیک متدهای PHP این قابلیت رو بهمون میدن که رفتار آبجکتها رو سفارشی کنیم. مثلا get() و set() برای دسترسی به پراپرتیهای تعریفنشده، call() و callStatic() برای متدهای تعریفنشده، و invoke() برای کال کردن آبجکت مثل فانکشن. اینا کد رو انعطافپذیر میکنن، ولی اگه در استفاده ازشون زیادهروی کنیم، مشکلاتی مثل کندی، دیباگ سخت، و نگهداری پیچیده رو برامون به وجود میارن. خصوصا توی فریمورکها که پروژهها بزرگن.اینجا میخواییم این دردسرها رو با سناریوهای واقعی بررسی کنیم.

get($name) و set($name, $value): وقتی به پراپرتیای دسترسی پیدا میکنی که وجود نداره (مثل $obj->foo)، اینا وارد عمل میشن. کجاها میتونه مفید باشه؟ برای پروکسیها، lazy load، یا اعتبارسنجی بدون getter/setter مشخص.
call($name, $arguments) و callStatic($name, $arguments): برای متدهایی که تعریف نشدن (اینستنس یا استاتیک). تو facadeها میتونیم ببینیمش.
__invoke(...$args): آبجکتت رو مثل فانکشن کال میکنه (مثل $obj())، برای کلوژرها یا میدلویرها گزینه مناسبیه.
تو متاپروگرمینگ این موارد خودشون رو خیلی خوب نشون میدن، ولی هزینه دارن!
مجیک متدها یه لایه اضافی به کد اضافه میکنن؛ PHP باید چک کنه این متدها وجود دارن یا نه، که زمان میبره.
چه فایده ای داره: این داینامیک بودن باین قابلیت رو بهمون میده که بدون تعریف صریح پراپرتیها یا متدها، چیزها رو مدیریت کنیم.
کجا اذیتمون میکنه: دسترسیها کند میشن، گاهی ۵-۱۰ برابر. توی لوپهای بزرگ، CPU رو حسابی مشغول میکنه.
فرض کن یه سایت فروشگاهی با لاراول داریم. از __get() توی مدل Eloquent استفاده کردیم که $product->discount اتوماتیک تخفیف رو حساب کنه.
حالا این چه کمکی بهمون میکنه؟ کد کوتاه و تمیزه، بدون نیاز به getterهای اضافی.
اما چطور و کجا مارو به دردسر میندازه؟ توی صفحه اصلی که ۱۰۰تا (یا بیشتر) محصول رو لیست میکنیم، لود صفحه به جای ۱۰۰ میلیثانیه، ۳۰۰-۶۰۰ میلیثانیه طول میکشه، چون هر دسترسی یه چک داینامیک داره. تو این نقطه است که موضوع پرفورمنس مهم تر میشه.
جنبه دسترسی/کال مستقیم از طریق مجیک متد هزینه اضافی (بنچمارک ۲۰۲۵)
دسترسی به پراپرتی
$this->_foo (مستقیم)$obj->foo (__get)~۱۰-۲۰ میکروثانیه vs. ۵۰-۱۰۰ میکروثانیه کال متدmethod($args)$obj->method($args) (__call)~۵ میکروثانیه vs. ۱۵-۳۰ میکروثانیهلوپ (۱ میلیون تکرار)~۱۰۰ میلیثانیه~۳۰۰-۶۰۰ میلیثانیه۳-۶ برابر کندتر
مجیک متدها مسیر جریان کد رو مخفی میکنن؛ استک تریسها به خود متد اشاره میکنن، نه منطق اصلی که پشتش هست.
کجاها میتونه مفید باشه: میتونه منطق پیچیده رو پشت یه API ساده پنهان کنه.
چطور به دردسر میندازه: وقتی خطا میده، نمیفهمی مشکل از کجاست. ابزارهایی مثل Xdebug هم گیج میشن و فایده چندانی ندارن.
فرض کن داریم یه اپ بانکی با سیمفونی میسازیم. از call() توی یه facade استفاده کردیم که متدهای سرویسهای مختلف رو داینامیک کال کنه.
این facade همه چیز رو تمیز جمع میکنه و اون پیچیدیگی ها دیگه دیده نمیشه. اما وقتی یه ارور PDO توی یه سرویس نمایش داده میشه، تریس فقط call() رو نشون میده. دو ساعت میگردی تا بفهمی مشکل از یه کوئری دیتابیسه. توی پروداکشن، قطعا این منجر به داونتایم میشه و محصول رو دچار مشکل میکنه.
مجیک متدها خوانایی و وضوح کد رو کم میکنن، و کلاسها تبدیل به موجودات عجیبی میشن که پراپرتیها و متدها از هیچ جا خیلی یهویی پیداشون میشه.
فایده این ویژگی چیه؟ برای پروتوتایپ سریع، کد کمتر و تمیزتری نوشته میشه. اما موقع ریفکتورینگ کار سخت میشه، و توسعهدهندههای جدید نمیفهمن چی به چیه. ابزارهای استاتیک مثل Psalm هم نمیتونن درست تحلیل کنن.
فرض کن توی یه پروژه تیمی با Codeigniter، از __get() توی یه مدل استفاده شده که ۵۰ داینامیک پراپرتی تولید کنه. اینجا این قابلیت کمک میکنه که سریع پروتوتایپ زده بشه و کد کوتاه و مختصر بمونه. ولی وقتی یه توسعهدهنده جدید به تیم یا پروژه اضافه میشه، نمیفهمه این پراپرتیها از کجا میان. اگه یکی رو تغییر بده، بقیه هم خراب میشن. پروژه بدهی فنی جمع میکنه و نگهداریش دو برابر زمان میبره.
لوپ بینهایت: __call() که خودش رو کال کنه، باعث stack overflow میشه که توی روترهای دستساز زیاد دیده میشه.
مشکل سریالایزیشن: آبجکتها درست سریالایز نمیشن و باید __sleep() دستی درست بشه.
باگ امنیتی: زنجیرههای POP توی دیسیریالیزیشن از __wakeup() و مجیک متدها سوءاستفاده میکنن، مثل باگهای لاراول.
کمتر استفاده کنیم: از روشهای استاندارد مثل traitها به جای مجیک متدها استفاده کنیم. با Blackfire عملکرد رو بررسی کنیم.
شفاف باشیم: با PHPDoc داکیومنت کنیم؛ برای interceptها تست بنویسیم .از هوکهای پراپرتی PHP 8.4 استفاده کنیم البته اگر پروژه قابلیت ارتقای ورژن رو داره.
جایگزینها:
attributeها + getterها گزینه بهتری نسبت به Lazy Propertyها هستن.
از کلوژرها یا typeهای callable به جای __invoke() استفاده کنیم.
توی فریمورکها هم با کانفیگ مثل مود strict لاراول میتونیم این موضوع رو کنترل کنیم؛ با Rector هم میشه کد رو تمیز کرد.