تغییرات جوملا 4 و 5

تغییرات کلیدی

در جوملا 4، تغییرات مهمی در بخش پلاگین‌ها ایجاد شد:

- پلاگین‌ها اکنون از طریق Container تزریق وابستگی (Dependency Injection Container) و فایل `services/provider.php` ساخته می‌شوند. قبلاً پلاگین‌ها مستقیماً از طریق فایل اصلی PHP خود پلاگین اجرا می‌شدند.

- پلاگین‌ها باید به رویدادهایی که می‌خواهند اعلان دریافت کنند، مشترک شوند. قبلا روش برنامه‌نویسی به این صورت بود که متدی می‌نوشتید که نام آن با نام رویداد برابر بود و جوملا با استفاده از قابلیت «رفلکشن» PHP تعیین می‌کرد کی این متد فراخوانی شود. حالا با مکانیزم اشتراک جوملا چک می‌کند که آیا پلاگین شما اینترفیس `Joomla\Event\SubscriberInterface` را پیاده‌سازی کرده است یا نه و اگر بله، متد `getSubscribedEvents` را فراخوانی می‌کند که پلاگین باید در آن آرایه‌ای از رویدادهایی که می‌خواهد مدیریت کند و متدهای متناظر را برگرداند:

 
public static function getSubscribedEvents(): array
{
    return [
        'onContentPrepare' => 'myContentPrepareMethod',  
        'onContentAfterTitle' => 'myContentAfterTitleMethod',  
    ];
}

- رویدادها اکنون به صورت آبجکت‌های رویداد جوملا به پلاگین‌ها ارسال می‌شوند. قبلاً هر متد رویداد پارامترهای مشخص آن رویداد را به صورت جداگانه دریافت می‌کرد. مثلاً در جوملا 3 متد `onContentPrepare` چنین امضایی داشت:

 
public function onContentPrepare($context, &$row, $params, $page = 0)

در جوملا 3، مفهوم رویدادها اساساً شامل:

- یک رشته که نام رویداد بود مانند "onContentPrepare"

- پارامترهای مرتبط که به متد `onContentPrepare` ارسال می‌شد

تیم جوملا قصد داشت این ساختار را به کلاس‌های رویداد تبدیل کند، یعنی هر نوع رویداد کلاس خودش را داشته باشد (مثلاً کلاس `ContentPrepareEvent` با خصوصیات (`$context`, `$row`, `$params`, `$page`) که به تدریج در نسخه‌های مختلف جوملا 4 و 5 پیاده‌سازی شده است.

برای اینکه پلاگین‌ها بتوانند قبل از کامل شدن کلاس‌های رویداد خاص، از کلاس عمومی `GenericEvent` استفاده کنند، راه‌حلی موقتی اتخاذ شد که این امکان را می‌دهد پلاگین‌ها کدشان را با متدهایی که پارامتر تک رویداد (آبجکت) می‌گیرند بنویسند.

باید توجه داشته باشید که نحوه دریافت پارامترهای متد و نحوه ارائه مقدار بازگشتی متفاوت است، بسته به اینکه از `GenericEvent` استفاده شود یا کلاس رویداد خاص.

مقایسه جوملا 3 / 4 / 5

بیایید مثال `onContentPrepare` را در این نسخه‌ها بررسی کنیم.

جوملا 3

رویداد در `com_content` این‌گونه فعال می‌شد:

 
$dispatcher->trigger('onContentPrepare', array ('com_content.article', &$item, &$item->params, $offset));

که منجر به فراخوانی متد پلاگین شما می‌شد:

 
public function onContentPrepare($context, &$row, $params, $page = 0)

جوملا از کلاس رفلکشن پلاگین برای پیدا کردن این متد استفاده می‌کرد.

`onContentPrepare` مقدار بازگشتی ندارد، ولی رویدادهایی مثل `onContentAfterTitle` دارند. وقتی بخواهید مقدار برگردانید، کافی بود:

 
return $value;

جوملا در نسخه‌های 4 و 5 برای سازگاری رو به عقب، این روش را همچنان پشتیبانی می‌کند اما احتمالاً در جوملا 6 حذف خواهد شد، پس بهتر است در پلاگین‌های جدید این روش را به کار نبرید.

جوملا 4

در جوملا 4 رویداد به این شکل فراخوانی می‌شود:

 
$this->dispatchEvent(new Event('onContentPrepare', ['com_content.article', &$item, &$item->params, $offset]));

کلاسی به نام `GenericEvent` ایجاد می‌شود که پارامترها را به صورت آرایه‌ای در خود دارد و برای دسترسی به پارامترها از متد `getArguments` استفاده می‌شود. به شکل تخریب آرایه (array destructuring) اینگونه می‌نویسیم:

 
[$context, $article, $params, $page] = array_values($event->getArguments());

تابع `array_values` در اینجا ضرورتی ندارد ولی در نسخه‌های بعدی به آن نیاز است.

برای برگرداندن مقدار از پلاگین وقتی از `GenericEvent` استفاده می‌کنید، باید مقدار بازگشتی را به آرایه‌ای در آبجکت رویداد اضافه کنید:

 
$result = $event->getArgument('result') ?: [];   // دریافت آرایه نتایج فعلی
$result[] = $value;                              // اضافه کردن مقدار برگردانده شده
$event->setArgument('result', $result);         // ذخیره آرایه بروز شده

جوملا 5

در جوملا 5 رویداد به شکل زیر فراخوانی می‌شود:

 
$dispatcher->dispatch('onContentPrepare', new Content\ContentPrepareEvent('onContentPrepare', $contentEventArguments));

 اینجا از کلاس رویداد اختصاصی `ContentPrepareEvent` استفاده می‌شود. این کلاس می‌تواند متدهای خواندن اختصاصی (getter) مانند `getContext` داشته باشد اما همچنان می‌توانید پارامترها را با روش تخریب آرایه بگیرید: 

[$context, $article, $params, $page] = array_values($event->getArguments());

اینجا تابع `array_values` ضروری است تا در هر دو حالت `GenericEvent` و کلاس اختصاصی کار کند.

برای برگرداندن مقدار از پلاگین باید از متد زیر استفاده کنید:

 
$event->addResult($value);

اما بهتر است ابتدا بررسی کنید که این متد وجود دارد یا نه:

 
use Joomla\CMS\Event\Result\ResultAwareInterface;
...
if ($event instanceof ResultAwareInterface) {
    $event->addResult($value);
    return;
} else {
    // روش GenericEvent
}

اگر پلاگین شما برای رویداد `GenericEvent` نوشته شده است، استفاده از این روش‌ها حیاتی است تا پلاگین شما هنگام تغییر به کلاس رویداد اختصاصی دچار مشکل نشود.

کلاس رویداد اختصاصی (Concrete Event Class)

اگر در مسیر `libraries/src/Event` یک کلاس رویداد جوملا (که به آن «کلاس رویداد اختصاصی» گفته می‌شود) وجود دارد که مخصوص گروه رویداد پلاگین شما است (برای جوملا 4 تعداد محدودی موجود است)، می‌توانید از روش زیر استفاده کنید:

 
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
use Joomla\CMS\Event\Content\ContentPrepareEvent;

class MyPlugin extends CMSPlugin implements SubscriberInterface
{
  public static function getSubscribedEvents(): array
  {
    return [
      'onContentPrepare' => 'myOnContentPrepare',  
    ];
  } 

  public function myOnContentPrepare(ContentPrepareEvent $event)
  {
    $context = $event->getContext();
    $item = $event->getItem(); 
    $params = $event->getParams();
    $page = $event->getPage();
    // ...
  }

}

در فراخوانی متدهای دریافت‌کننده (getter)، باید نام صحیح آرگومان را استفاده کنید. در مستندات جوملا نام درست همیشه مشخص شده است.

همچنین متدهای getter در مستندات API کلاس‌های رویداد، مانند Event/Content/ContentPrepareEvent قابل مشاهده است.

برای استفاده از کلاس رویداد، کلاس پلاگین شما باید اینترفیس `\Joomla\Event\SubscriberInterface` را پیاده‌سازی کرده و تابع `getSubscribedEvents` را ارائه کند. سپس متد شنونده (listener) شما باید فقط یک پارامتر `$event` داشته باشد.

کلاس رویداد عمومی (Generic Event Class)

اگر با نسخه جوملا 4 به بعد کار می‌کنید و می‌خواهید از کلاس‌های رویداد استفاده کنید، ولی کلاس رویداد اختصاصی برای نسخه مدنظر شما موجود نیست، از این روش استفاده کنید:

 
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
use Joomla\Event\Event;

class MyPlugin extends CMSPlugin implements SubscriberInterface
{
  public static function getSubscribedEvents(): array
  {
    return [
      'onContentPrepare' => 'myOnContentPrepare',  
    ];
  } 

  public function myOnContentPrepare(Event $event)
  {
    [$context, $item, $params, $page] = array_values($event->getArguments());
    // ...
  }

}

این روش همچنین برای کلاس‌های رویداد اختصاصی (concrete event classes) نیز کار می‌کند.

ترتیب پارامترها مهم است و همیشه ترتیب صحیح در مستندات جوملا بیان شده است.

تیم جوملا متعهد است که ترتیب آرگومان‌های رویداد را مطابق ترتیب پارامترهای متدهای قدیمی (legacy) حفظ کند، هرچند این پشتیبانی در جوملا 6 حذف خواهد شد. این ترتیب در کلاس‌های رویداد با متغیر `$legacyArgumentsOrder` مشخص شده است؛ مثلاً در کلاس `ContentPrepareEvent`:

 
class ContentPrepareEvent extends ContentEvent
{
    protected $legacyArgumentsOrder = ['context', 'subject', 'params', 'page'];
}

روش قدیمی سنتی (Traditional Legacy Method)

اگر پلاگین شما قبل از جوملا 4 توسعه یافته است، احتمالا از این روش استفاده می‌کنید. بهتر است تا زمانی که کلاس‌های اختصاصی رویداد برای همه رویدادهای پلاگین شما در دسترس قرار نگرفته‌اند، این روش را ادامه دهید.

با این حال، برای ساخت پلاگین‌های جدید نباید از این روش استفاده کنید چون بعدها مجبور به بازنویسی خواهید شد.

در این روش، شما تابع public ای با همان نام رویداد مشخص می‌کنید و جوملا با استفاده از رفلکشن PHP متد را پیدا می‌کند. امضای متد باید آرگومان‌های رویداد را داشته باشد، مثلاً:

 
use Joomla\CMS\Plugin\CMSPlugin;

class MyPlugin extends CMSPlugin 
{
  public function onContentPrepare($context, $item, $params, $page)
  {
    if ($context == "com_content.article") {
      // ...
    }
  }
}

نام آرگومان‌ها در امضای متد اهمیتی ندارد، مهم ترتیب آنها در دنباله پارامترهاست. مستندات جوملا همیشه ترتیب صحیح را تعیین می‌کنند.

خلاصه - مقادیر برگشتی

کلاس رویداد اختصاصی (Concrete Event Class)

برای بازگرداندن مقدار هنگام استفاده از کلاس رویداد اختصاصی از متد `$event->addResult()` استفاده کنید:

 
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Event\Content\AfterTitleEvent;

class MyPlugin extends CMSPlugin 
{
    public function onContentAfterTitle(AfterTitleEvent $event)
    {
        // ...
        $event->addResult($value);
    }
}

کلاس رویداد عمومی (Generic Event Class)

برای بازگرداندن مقدار هنگام استفاده از کلاس رویداد عمومی روش زیر را بکار برید:

 
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\Event;

class MyPlugin extends CMSPlugin 
{
    public function onContentAfterTitle(Event $event)
    {
        // ...
        $result = $event->getArgument('result') ?: [];    // دریافت آرایه نتایج فعلی
        $result[] = $value;                               // افزودن مقدار بازگشتی به آرایه
        $event->setArgument('result', $result);          // ذخیره آرایه بروز شده
    }
}

این روش با کلاس‌های رویداد اختصاصی نیز کار می‌کند.

روش قدیمی سنتی (Traditional Legacy Method)

برای بازگرداندن مقدار در روش قدیمی از عبارت `return` در PHP استفاده کنید:

 
public function onContentAfterTitle($context, $item, $params, $page)
{
    // ...
    return $value;
}

قابلیت‌های جدید دیگر

اولویت (Priority)

برای اختصاص اولویت غیر از پیشفرض به پلاگین‌ها، آن را در پاسخ به `getSubscribedEvents` مشخص کنید:

 
public static function getSubscribedEvents(): array
{
    return [
        'onContentPrepare' => ['myContentPrepareMethod', \Joomla\Event\Priority::HIGH], 
        'onContentAfterTitle' => ['myContentAfterTitleMethod', \Joomla\Event\Priority::MIN], 
    ];
}

مقادیر دیگر اولویت را می‌توانید در `libraries/vendor/joomla/event/src/Priority.php` ببینید. با احتیاط از این قابلیت استفاده کنید چون ممکن است ترتیب دلخواه مدیر سایت را نادیده بگیرد.

توقف انتشار رویداد (Stop Propagation)

برای جلوگیری از ارسال ادامه رویداد به پلاگین‌های دیگر متد زیر را فراخوانی کنید:

 
$event->stopPropagation();