ماکرو در لاراول

نمی‌دانم تا به حال دیده‌اید که یکی از Classهای لاراول، از یک Trait به نام Macroable استفاده کرده باشد؟ اگر ندیده‌اید، بدانید که کلاس‌هایی چون Filesystem، Arr، Collection، Request و غیره از این Trait استفاده کرده‌اند...تریت؟!تریت (Trait) یعنی خاصیت. در PHP امکان ارث‌بری چندگانه وجود ندارد و کد زیر با خطا مواجه خواهد شد:class President extends Human, Devil {}اما می‌توانیم با استفاده از Traitها هر چقدر که خواستیم، خصوصیاتی را بین کلاس‌های مختلفی به اشتراک بگذاریم. مثلاً بعضی از کلاس‌های لاراول از تریت Macroable استفاده کرده و متدهایش را به ارث برده‌اند:class Schedule { use Macroable; . . .چه‌کار می‌کند؟فرض کنید که می‌خواهیم قابلیتی را به یکی از کلاس‌های لاراول اضافه کنیم. اگر آن کلاس از Macroable استفاده کرده باشد، به آسانی قابل توسعه است و می‌توانیم بدون تغییر یک خط از کدهای آن کلاس، متدهای جدیدی ایجاد کنیم!مثال بگو!قلی خود را شناساننده‌ی مجموعه‌ی خاصی می‌داند و نامش را هم بر آن گذاشته است. مجموعه‌ی قلی، مجموعه‌ای از اعداد است که تعداد و مجموعشان فرد باشد. از آنجا که در پروژه‌اش بسیار با مجموعه‌ی قلی سر و کار دارد، آرزو می‌کند که ای کاش! امکانی در کالکشن‌های لاراول بود تا با آن قلی‌بودن کالکشن‌هایش را تشخیص می‌داد!برای رساندن قلی به آرزوی دیرینه‌اش، کدهای زیر را در متد boot موجود در AppServiceProvider می‌نویسیم:public function boot() { Collection::macro('isGholi', function () { return $this->sum() % 2 !== 0 && $this->count() % 2 !== 0; }); }از متد macro روی کلاس Collection استفاده کرده‌ایم. آرگومان اول، نام متد جدید است و آرگومان دوم، callable است و در بدنه‌اش دو شرط قلی ر ا بررسی کرده‌ایم: باید مجموع اعداد و هم تعدادشان فرد باشد.اگر قلی در هر جای پروژه به صورت زیر عمل کند:$collection = collect([1, 2, 4]); echo $collection->isGholi() ? 'قلی است' : 'قلی نیست';جواب می‌گیرد! این دفعه «قلی است»، زیرا مجموع 1 و 2 و 4، 7 است و تعدادشان 3، که هر دو شرط را دارند. پس ما متدی به کلاس کالکشن اضافه کردیم که می‌تواند در سراسر پروژه قابل دسترسی باشد!چی شد؟!طرز کار تریت Macroable بسیار ساده است و هیچ چیز ترسناکی ندارد. بیایید اتفاقی که در این تریت افتاده را با خواندن کدهای مرتبط، مرور کنیم:protected static $macros = [];در این تریت، یک مشخصه به نام macros(ماکروها) و از جنس آرایه تعریف شده است.public static function macro($name, $macro) { static::$macros[$name] = $macro; }این همان متدی است که برای حل دغدغه‌ی قلی از آن استفاده کردیم. پارامتر اول نام ماکرو است، پس نام ماکرو را به عنوان کلید آرایه‌ی macros در نظر می‌گیرد و callback ما را value آن قرار می‌دهد و به ازای هر نام (مثل isGholi) کدهایی برای اجراشدن داریم! ساده ولی جالب و خلاقانه است!public function __call($method, $parameters) { if (! static::hasMacro($method)) { throw new BadMethodCallException(sprintf( 'Method %s::%s does not exist.', static::class, $method )); } $macro = static::$macros[$method]; if ($macro instanceof Closure) { $macro = $macro->bindTo($this, static::class); } return $macro(...$parameters); }این یک متد جادویی (Magic Method) است. وقتی isGholi را روی کالکشن صدا بزنیم، پی‌اچ‌پی متوجه می‌شود که چنین متدی درون کلاس وجود ندارد، برای همین به متد جادویی call__ مراجعه می‌کند. با hasMacro تشخیص داده می‌شود که قبلاً ماکرویی با کلید متد درخواست‌شده در آرایه‌ی macros وجود دارد یا نه. اگر وجود داشت، مقدار آن خوانده می‌شود و در متغیر macro$ ریخته می‌شود. سپس بررسی می‌شود که از نوع Clousure هست یا نه؟ اگر باشد، از bindTo استفاده کرده‌اند تا this$ به جای AppServiceProvider به خود Collection اشاره کند (اگر بایند نشده بود، منطقاً کار نمی‌کرد) و در نهایت ماکروی ما با پارامترهایی که کاربر وارد کرده، اجرا می‌شود.آیا قابلیت ماکرو در لاراول و نحوه‌ی پیاده‌سازی‌اش، خلاقیت ظریفی نیست؟