قالب پاسخ JSON

جوملا به صورت پیش‌فرض وقتی درخواست با هدر Accept: application/json یا هدر خاص JSON API ارسال شود، پاسخ‌های API را به صورت JSON بازمی‌گرداند. اگرچه هسته جوملا از انواع محتوای اضافی پشتیبانی نمی‌کند، اما قابلیت افزودن انواع محتوای اضافی که توسعه‌دهندگان بتوانند پاسخ دهند را فراهم کرده است.

اهداف

- دریافت پاسخ JSON از API جوملا 

- ایجاد پلاگین وب‌سرویس لازم و بخش API کامپوننت 

- استفاده از پارامترهای ماژول برای مدل‌سازی داده‌هایی که در پاسخ API ارسال می‌کنیم 

اهدافی که دنبال نمی‌شود

- چگونه یک افزونه بسازیم: این آموزش فرض می‌کند که شما نحوه ساخت افزونه در جوملا را می‌دانید. به یاد داشته باشید حداقل یک پلاگین و یک کامپوننت لازم است. اما نگران نباشید، این کامپوننت می‌تواند بسیار ساده باشد، در واقع حتی نیازی به داشتن مدل هم ندارد، فقط کافیست نمای داشبورد در پنل مدیریت به درستی کار کند. این برای عملکرد API شما کافی است.

Basic Component Dashboard View

نیاز به یک بخش پایه در پنل مدیریت از این جهت است که فایل‌های XML مانیفست، فایل پیکربندی (config.xml) و فایل مجوزها (access.xml) فقط در دایرکتوری پنل مدیریت کامپوننت موجود هستند. مانیفست XML برای نصب، به‌روزرسانی و حذف کامپوننت ضروری است. افزون بر این، جوملا همیشه یک آیتم منو در پنل مدیریت برای کامپوننت ایجاد می‌کند، به این معنی که کامپوننت باید بخش پنل مدیریت با یک نمای پیش‌فرض داشته باشد، حتی اگر فقط برای نمایش پیغام "چیزی برای انجام دادن در پنل مدیریت وجود ندارد" باشد.

- آموزش با رویکرد کلی تهیه شده است. ما یک منطق سفارشی API می‌سازیم با رویکرد خاص. هرچند پس از دنبال کردن آموزش، می‌توانید تغییراتی برای تطبیق با نیازهای خود اعمال کنید.

نکاتی که قبل از شروع باید بدانید

یکی از روش‌های احراز هویت در API جوملا، مبتنی بر توکن است. (یک روش دیگر بر پایه نام کاربری و رمز عبور نیز وجود دارد، اما بهتر است از آن اجتناب کنید).

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

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

پلاگین وب‌سرویس

خطوط اولیه کد ما در پلاگین شروع خواهند شد. این پلاگین مسئول ثبت مسیرهای (Endpoints) API ما و همچنین اشاره به کنترلر کامپوننت است که درخواست‌ها را مدیریت خواهد کرد. بیایید آن را بسازیم.

در فضای کاری خود، پوشه‌ای با نام plg_webservices_vapi ایجاد کنید. داخل پوشه، فایل php با نام vapi.php و محتوای زیر بسازید:

 
defined('_JEXEC') || die;

use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Router\ApiRouter;
use Joomla\Router\Route;

class PlgWebservicesVapi extends CMSPlugin
{
    /**
     * ثبت مسیرهای API کامپوننت com_vapi در اپلیکیشن
     *
     * @param   ApiRouter  &$router  شیء روتینگ API
     *
     * @return  void
     */
    public function onBeforeApiRoute(&$router)
    {
        // رندر کردن فهرستی از مقالات com_content با استفاده از ماژول خاص
        $this->createModuleSiteRoutes($router, 'v1/vapi/modules/:id', 'module.displayModule');
    }

    /**
     * ایجاد نقشه مسیرها برای عملیات CRUD
     *
     * @param   ApiRouter   &$router        شیء روتینگ API
     * @param   string      $baseName       الگوی مسیر مورد استفاده برای تطبیق
     * @param   string      $controller     نام کنترلری که درخواست API را مدیریت می‌کند.
     * @param   array       $defaults       آرایه‌ای از مقادیر پیش‌فرض که هنگام تطبیق URL استفاده می‌شوند.
     * @param   bool        $publicGets     اجازه دسترسی عمومی به درخواست‌های GET.
     *
     * @return  void
     */
    private function createModuleSiteRoutes(&$router, $baseName, $controller, $defaults = [], $publicGets = true): void
    {
        $defaults = [
            'component'  => 'com_vapi',
            'public' => $publicGets,
            'format' => [
                'application/json'
            ]
        ];

        $routes = [
            new Route(['GET'], $baseName, $controller, ['id' => '(\d+)'], $defaults),
        ];

        $router->addRoutes($routes);
    }
}

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

- متد `onBeforeApiRoute`: این متد در هر پلاگین وب‌سرویس الزامی است. در اینجا شما مسیرهای API (Endpoints) خود را تعریف می‌کنید. می‌توانید آن‌ها را داخل همین متد تعریف کنید یا همان طور که اینجا انجام شده، در متد دیگری که به صورت سفارشی ساخته‌اید، قرار دهید (چون این متد سفارشی مربوط به شماست و بخشی از هسته جوملا نیست).

- متد `createModuleSiteRoutes`: اینجا می‌توانید سازنده کلاس `Joomla\Router\Route` را بررسی کنید تا همه پارامترهای استفاده شده را بهتر بشناسید. ولی توضیح مختصر آنها این‌گونه است:

  - `["GET"]`: متدهای HTTP که این مسیر پشتیبانی می‌کند. همه باید با حروف بزرگ باشند. متدهای معتبر شامل ["GET", "POST", "PUT", "DELETE", "HEAD", "TRACE", "PATCH"] هستند.

  - `$baseName`: الگوی مسیر یا همان endpoint. در این مثال `v1/vapi/modules/:id` است.

    - بخش `v1` برای نسخه‌بندی API است. ابتدا با نسخه ۱ شروع می‌کنیم.

    - بخش بعدی باید نام کامپوننت بدون پیش‌وند com_ باشد که فقط یک قرارداد برای تعریف مسیرهاست.

    - در نهایت `modules/:id` ویژگی‌ای است که کامپوننت ما پشتیبانی می‌کند و یک پارامتر `:id` که ممکن است در endpoint وجود داشته باشد یا نداشته باشد. این الگو می‌گوید این مسیر تنها زمانی مطابقت دارد که آدرس بعد از `v1/vapi/modules` یک مقدار عددی قرار گیرد که به عنوان پارامتر `id` درخواست در دسترس خواهد بود. اگر چنین مقداری موجود نباشد، مسیر مطابقت ندارد و جوملا از آن استفاده نمی‌کند.

  - `$controller`: کنترلر API کامپوننت و وظیفه ای (task) که اجرا خواهد شد. باید با نقطه از هم جدا شوند (مثل `module.displayModule`).

  - `['id' => '(\d+)']`: اگر الگوی مسیر پارامتر دارد، باید الگوی regex ارائه دهید که مقدار پارامتر باید با آن مطابقت کند. در اینجا، پارامتر `id` باید یک عدد صحیح باشد (شامل صفر).

  - `$defaults`: در این متغیر تعریف می‌شود:

- `'component' => 'com_vapi'`: کامپوننت مربوطه

- `'public' => $publicGets`: اگر مسیر باید برای کاربران غیرمجاز (بدون احراز هویت) قابل دسترسی باشد، مقدار این باید true باشد؛ در غیر این صورت false است.

- `'format' => ['application/json']`: مشخص می‌کند که پاسخ باید در قالب JSON باشد. در غیر اینصورت جوملا از JSON-API پیش‌فرض استفاده می‌کند.

قبل از رفتن به کد کامپوننت، به یاد داشته باشید پلاگین باید فایل مانیفست `vapi.xml` را داشته باشد که یک مانیفست استاندارد است و لازم نیست چیز جدیدی اضافه کنید. می‌توانید هر مانیفستی که در گروه پلاگین‌های وب‌سرویس هست را الگو قرار دهید.

بخش API کامپوننت

یادآوری می‌شود قبل از شروع این بخش، کامپوننت شما باید عملکرد پایه‌ای داشته باشد.

در جوملا، کامپوننت شما یک بخش اختیاری دیگر دارد: بخش API. درست مثل بخش‌های فرانت‌اند (سایت) و بک‌اند (مدیریت) کامپوننت، این بخش نیز کنترلرها، ویوها و مدل‌های مخصوص به خود را دارد که به جای صفحات HTML، مقادیر JSON بازمی‌گردانند.

فایل مانیفست XML کامپوننت شما باید بخش `<api>` را تحت ریشه `<extension>` داشته باشد که فایل‌ها و پوشه‌های بخش API کامپوننت را تعریف می‌کند. به طور معمول این بخش به صورت زیر است:

 
<api>
    <files folder="api/">
        <folder>src</folder>
    </files>
</api>

در پوشه ریشه نصب کامپوننت، حالا یک پوشه جدید با نام `api` بسازید. این پوشه یک زیرپوشه `src` خواهد داشت که تمامی پوشه‌ها و فایل‌های API را داخل آن قرار می‌دهیم. حالا کنترلر خود را ایجاد می‌کنیم. پوشه‌ای به نام `Controller` بسازید و فایل `ModuleController.php` را با کد زیر داخل آن قرار دهید:

 
<?php

namespace Carlitorweb\Component\Vapi\Api\Controller;

defined('_JEXEC') || die;

use Joomla\CMS\MVC\Factory\ApiMVCFactory;
use Joomla\CMS\Application\ApiApplication;
use Joomla\Input\Input;
use Joomla\CMS\Language\Text;
use Joomla\Component\Content\Administrator\Extension\ContentComponent;
use Joomla\CMS\Component\ComponentHelper;

class ModuleController extends \Joomla\CMS\MVC\Controller\BaseController
{
    /**
     * @var string $default_view نمای پیش‌فرض برای $viewName
     */
    protected $default_view = 'modules';

    /**
     * @var \Joomla\Registry\Registry $moduleParams پارامترهای ماژول برای اعمال فیلترها در مدل
     */
    protected $moduleParams;

    /**
     * سازنده
     *
     * @param   array           $config   آرایه اختیاری تنظیمات پیکربندی
     * @param   ApiMVCFactory   $factory  کارخانه (factory)  
     * @param   ApiApplication  $app      برنامه برای مدیریت Dispatcher
     * @param   Input           $input    ورودی
     *
     * @throws  \Exception
     */
    public function __construct($config = array(), ApiMVCFactory $factory = null, ?ApiApplication $app = null, ?Input $input = null)
    {
        if (\array_key_exists('moduleParams', $config)) {
            $this->moduleParams = new \Joomla\Registry\Registry($config['moduleParams']);
        }

        parent::__construct($config, $factory, $app, $input);
    }

    # کد متدهای شما از اینجا شروع می‌شود...
}

به نام فضا (namespace) کنترلر دقت کنید. شما می‌توانید آن را با namespace سفارشی خود که قبلاً در کامپوننت دارید، جایگزین کنید. Vapi نامی است که من در این کامپوننت استفاده کرده‌ام و باید این نام و پیشوند Carlitorweb را تغییر دهید؛ بقیه موارد را بهتر است به همان صورت نگه دارید (البته می‌توانید هر کدام را بنا به نیاز خود تغییر دهید).

همچنین، JSON-API هسته جوملا کنترلرها را از کلاس `Joomla\CMS\MVC\Controller\ApiController` ارث‌بری می‌کند. اما در مورد ما چون فقط نیاز به بازگرداندن پاسخ JSON داریم، از کلاس پایه `\Joomla\CMS\MVC\Controller\BaseController` ارث‌بری شده است.

دو متغیر (property) تعریف کردیم؛ یکی برای اطلاع دادن به جوملا که کدام فایل View را انتظار داریم استفاده کند و دیگری برای تنظیم پارامترهای ماژول که داده‌های ارسالی در پاسخ API را مدل‌سازی می‌کند.

حالا متدهای کلاس که درگیر هستند را ببینیم:

 
/**
 * تعیین مدل‌ها و اجرای View
 *
 * @throws \Exception
 */
public function displayModule(): void
{
    # کد خودتان اینجا ...
}

/**
 * راه‌اندازی مدل و تنظیم وضعیت‌ها
 *
 * @param \Joomla\Registry\Registry $params پارامترهای ماژول
 *
 */
protected function getMainModelForView($params): \Joomla\Component\Content\Site\Model\ArticlesModel
{
    # کد خودتان اینجا ...
}

/**
 * تنظیم پارامترهای ماژول
 *
 * @param \Carlitorweb\Component\Vapi\Api\Model\ModuleModel $moduleModel
 *
 */
protected function setModuleParams($moduleModel): \Joomla\Registry\Registry
{
    # کد خودتان اینجا ...
}

متد `displayModule()` همان متدی است که در پلاگین وب‌سرویس ما به عنوان تسک (وظیفه) تعریف شده و اولین متدی است که کنترلر اجرا می‌کند. اگر می‌خواهید تست کنید همه چیز مانند انتظار پیش می‌رود، می‌توانید ابتدای این متد این کد را قرار دهید:

 
var_dump(__METHOD__);
die;

سپس با استفاده از کلاینت API مورد علاقه‌تان مثل Postman، Thunder Client در وی‌اس‌کد یا curl، درخواست به مسیر زیر بزنید:

 
[آدرس_سایت_محلی_شما]/api/index.php/v1/vapi/modules/[شناسه_ماژول_شما]

پاسخ زیر را خواهید دید:

string(73)"Carlitorweb\Component\Vapi\Api\Controller\ModuleController::displayModule"

متد `getMainModelForView()` وظیفه راه‌اندازی و آماده‌سازی مدل اصلی که View استفاده خواهد کرد را دارد. از کلمه اصلی (main) استفاده کردم چون جوملا اجازه می‌دهد View بتواند با چند مدل تعامل داشته باشد.

متد `setModuleParams()` جایی است که پارامترهای ماژول را دریافت می‌کنیم تا در متد `getMainModelForView()` استفاده کنیم. اگر دقت کنید، متد پارامتری با نوع `\Carlitorweb\Component\Vapi\Api\Model\ModuleModel` می‌پذیرد. این یک مدل سفارشی است که API خواهد داشت و ما باید آن را بسازیم. در اینجا ماژول مربوطه بر اساس شناسه‌ای که در URL درخواست آمده، بارگذاری می‌شود. توجه داشته باشید این شناسه باید با یک ماژول فرانت‌اند (قالب نمای سایت) که قبلاً در مدیریت جوملا ساخته‌اید، تطابق داشته باشد.

از آنجا که پارامترها بسیار مهم هستند، بهتر است حالا مدل خود را بسازیم.

مدل – تعریف کلاس

در همان ریشه‌ای که پوشه Controller را ساخته‌ایم، اکنون پوشه‌ای به نام Model بسازید و فایل `ModuleModel.php` را با کد زیر داخل آن قرار دهید:

 
<?php
defined('_JEXEC') || die;

use Joomla\CMS\Factory;
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
use Joomla\Database\ParameterType;
use Joomla\CMS\Language\Text;

class ModuleModel extends \Joomla\CMS\MVC\Model\BaseDatabaseModel
{
    /**
     * گرفتن ماژول
     *
     * @return  \stdClass|null شیء ماژول
     *
     * @throws \InvalidArgumentException اگر شناسه ماژول تنظیم نشده باشد
     * @throws \RuntimeException اگر ماژول پیدا نشود
     *
     */
    public function getModule(): ?object
    {
        /** @var \Joomla\CMS\Application\CMSApplicationInterface $app */
        $app = Factory::getApplication();

        $mid = $this->state->get('moduleID', 0);

        if ($mid === 0) {
            throw new \InvalidArgumentException(
                sprintf(
                    'شناسه ماژول در %s ضروری است',
                    __METHOD__
                )
            );
        }

        /** @var \Joomla\Database\DatabaseInterface $db */
        $db    = $this->getDatabase();
        $query = $this->getModuleQuery($db, $mid);

        // تنظیم کوئری
        $db->setQuery($query);

        // ساخت شناسه کش برای شیء داده بازگشتی
        $cacheId = 'com_vapi.moduleId' . $mid;

        try {
            /** @var \Joomla\CMS\Cache\Controller\CallbackController $cache */
            $cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)
                ->createCacheController('callback', ['defaultgroup' => 'com_modules']);

            $module = $cache->get(array($db, 'loadObject'), array(), md5($cacheId), false);
        } catch (\RuntimeException $e) {
            $app->getLogger()->warning(
                Text::sprintf('JLIB_APPLICATION_ERROR_MODULE_LOAD', $e->getMessage()),
                array('category' => 'jerror')
            );

            return new \stdClass();
        }

        return $module;
    }

    /**
     * دریافت کوئری ماژول
     *
     * @param  int                                 $mid شناسه ماژول
     * @param  \Joomla\Database\DatabaseInterface  $db
     *
     */
    protected function getModuleQuery($db, $mid): \Joomla\Database\QueryInterface
    {
        $query  = $db->getQuery(true);

        $query->select('*')
            ->from($db->quoteName('#__modules'))
            ->where(
                $db->quoteName('id') . ' = :moduleId'
            )
            ->bind(':moduleId', $mid, ParameterType::INTEGER);

        return $query;
    }
}

مدل نسبتاً ساده است. شناسه ماژول برای جستجو در خط زیر گرفته می‌شود:

 
$this->state->get('moduleID', 0)

این شناسه باید توسط کنترلر ما فراهم شود (که به زودی انجام می‌دهیم). سپس با استفاده از متد `getModuleQuery()` کوئری پایگاه داده ایجاد می‌شود که بعداً اجرا می‌کنیم. در نهایت با استفاده از بلوک try/catch شیء ماژول گرفته شده و کش می‌شود.

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

Controller - تعریف متدها

اکنون که می‌توانیم یک ماژول مشخص را دریافت کنیم، متد setModuleParams() را کامل کنیم:

 
/**
 * تنظیم پارامترهای ماژول
 *
 * @param  \Carlitorweb\Component\Vapi\Api\Model\ModuleModel  $moduleModel
 *
 */
protected function setModuleParams($moduleModel): \Joomla\Registry\Registry
{
    // دریافت پارامترهای ماژول
    $module = $moduleModel->getModule();

    if (is_null($module)) {
        throw new \UnexpectedValueException(
            sprintf(
                '$module باید از نوع شیء باشد، نوع %s در متد %s() بازگردانده شده است',
                gettype($module), __FUNCTION__
            )
        );
    }

    return $this->moduleParams = new \Joomla\Registry\Registry($module->params);
}

بیایید ببینیم خاصیت $moduleParams در کنترلر، نتیجه‌ی مورد انتظار را می‌گیرد یا نه. برای این منظور متد اصلی displayModule() را ویرایش می‌کنیم: 

 
/**
 * تنظیم مدل‌ها و اجرای ویو
 *
 * @throws  \Exception
 */
public function displayModule(): void
{
    $moduleID    = $this->input->get('id', 0, 'int');
    $moduleState = new \Joomla\Registry\Registry(['moduleID' => $moduleID]);

    /** @var \Carlitorweb\Component\Vapi\Api\Model\ModuleModel $moduleModel */
    $moduleModel = $this->factory->createModel('Module', 'Api', ['ignore_request' => true, 'state' => $moduleState]);

    // تنظیم پارامترهایی که مدل استفاده خواهد کرد
    if(empty($this->moduleParams)) {
        $this->setModuleParams($moduleModel);
    }

    var_dump($this->moduleParams);die;
}

توضیح پارامترها:

- `$this->input->get('id', 0, 'int')`: مقدار پارامتر :id در URL (که در پلاگین وب‌سرویس‌ها تعریف کردیم)

- `'state' => $moduleState`: توجه کنید که هنگام بارگذاری مدل، شناسه ماژول را ارسال کرده‌ایم. پیش‌تر دیدیم که این شناسه در مدل چگونه استفاده شده است.

دوباره، با استفاده از کلاینت API مورد علاقه‌تان، درخواست را به آدرس زیر ارسال کنید: 

`[آدرس_سایت_محلی_شما]/api/index.php/v1/vapi/modules/[شناسه_ماژول_شما]`

 خروجی چیزی شبیه این خواهد بود:

 
object(Joomla\Registry\Registry)#938 (3) {
    ["data":protected]=>
        object(stdClass)#1090 (44) {
            ["mode"]=>
                string(6) "normal"
            ["show_on_article_page"]=>
                int(1)
            ["count"]=>
                int(0)
            ["show_front"]=>
                string(4) "only"
            ["category_filtering_type"]=>
                int(1)
            ["catid"]=>
                array(5) {
                    [0]=>
                        int(2)
                    [1]=>
                        int(8)
                    [2]=>
                        int(9)
                    [3]=>
                        int(10)
                    .....

با داشتن پارامترها در دست، بیایید از آن‌ها استفاده کنیم. متد getMainModelForView() را ویرایش کنید:

 
/**
 * راه‌اندازی مدل و تنظیم حالات
 *
 * @param  \Joomla\Registry\Registry  $params پارامترهای ماژول مقالات - دسته‌بندی
 *
 */
protected function getMainModelForView($params): \Joomla\Component\Content\Site\Model\ArticlesModel
{
    $mvcContentFactory  = $this->app->bootComponent('com_content')->getMVCFactory();

    // دریافت یک نمونه از مدل عمومی مقالات
    /** @var \Joomla\Component\Content\Site\Model\ArticlesModel $articlesModel */
    $articlesModel = $mvcContentFactory->createModel('Articles', 'Site', ['ignore_request' => true]);

    if (!$articlesModel) {
        throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
    }

    $appParams = ComponentHelper::getComponent('com_content')->getParams();
    $articlesModel->setState('params', $appParams);

    $articlesModel->setState('filter.published', ContentComponent::CONDITION_PUBLISHED);

    /*
     * تنظیم فیلترها بر اساس پارامترهای ماژول
    */
    $articlesModel->setState('list.start', 0);
    $articlesModel->setState('list.limit', (int) $params->get('count', 0));

    $catids = $params->get('catid');
    $articlesModel->setState('filter.category_id', $catids);

    // ترتیب بندی
    $ordering = $params->get('article_ordering', 'a.ordering');
    $articlesModel->setState('list.ordering', $ordering);
    $articlesModel->setState('list.direction', $params->get('article_ordering_direction', 'ASC'));

    $articlesModel->setState('filter.featured', $params->get('show_front', 'show'));

    $excluded_articles = $params->get('excluded_articles', '');

    if ($excluded_articles) {
        $excluded_articles = explode("\r\n", $excluded_articles);
        $articlesModel->setState('filter.article_id', $excluded_articles);

        // حذف موارد
        $articlesModel->setState('filter.article_id.include', false);
    }

    return $articlesModel;
}

پارامترهایی که برای مدل داده‌های‌مان استفاده می‌کنیم، از ماژول مقالات - دسته‌بندی آمده‌اند. همان شناسه‌ای است که با پارامتر :id درخواست داده‌ایم. (توجه کنید همه پارامترها استفاده نشده‌اند)

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

حالا دوباره متد اصلی displayModule() را جهت تست دریافت شیء ArticlesModel ویرایش کنیم:

 
/**
 * تنظیم مدل‌ها و اجرای ویو
 *
 * @throws  \Exception
 */
public function displayModule(): void
{
    $moduleID    = $this->input->get('id', 0, 'int');
    $moduleState = new \Joomla\Registry\Registry(['moduleID' => $moduleID]);

    /** @var \Carlitorweb\Component\Vapi\Api\Model\ModuleModel $moduleModel */
    $moduleModel = $this->factory->createModel('Module', 'Api', ['ignore_request' => true, 'state' => $moduleState]);

    // تنظیم پارامترهایی که مدل استفاده خواهد کرد
    if(empty($this->moduleParams)) {
        $this->setModuleParams($moduleModel);
    }

    $mainModel = $this->getMainModelForView($this->moduleParams);

    var_dump($mainModel::class);die;
}

دوباره با کلاینت API دلخواه خود، درخواست را به آدرس 

 
`[آدرس_سایت_محلی_شما]/api/index.php/v1/vapi/modules/[شناسه_ماژول_شما]`

ارسال کنید. خواهید دید:

 
string(49) "Joomla\Component\Content\Site\Model\ArticlesModel"

 

تعریف کلاس و متدهای ویو

 
<?php

namespace Carlitorweb\Component\Vapi\Api\View\Modules;

defined('_JEXEC') || die;

use \Joomla\CMS\MVC\View\JsonView as BaseJsonView;
use \Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\HTML\HTMLHelper;
use \Carlitorweb\Component\Vapi\Api\Model\ModuleModel;

class JsonView extends BaseJsonView
{
    /**
     * @var  array $fieldsToRenderList آرایه‌ای از فیلدهای مجاز برای رندر شدن
     */
    protected $fieldsToRenderList = [
        'id',
        'title',
        'alias',
        'displayDate',
        'metadesc',
        'metakey',
        'params',
        'displayHits',
        'displayCategoryTitle',
        'displayAuthorName',
    ];

    /**
     * @var  array  $display پارامترهای اضافی برای آماده‌سازی مقالات
     */
    protected $display = array();

    /**
     * سازنده
     *
     * @param   array  $config آرایه‌ای با نام پیکربندی برای ساخت شیء
     *
     */
    public function __construct($config = [])
    {
        if (\array_key_exists('moduleParams', $config)) {
            $params = $config['moduleParams'];

            // گزینه‌های نمایش
            $this->display['show_date']         = $params->get('show_date', 0);
            $this->display['show_date_field']   = $params->get('show_date_field', 'created');
            $this->display['show_date_format']  = $params->get('show_date_format', 'Y-m-d H:i:s');
            $this->display['show_category']     = $params->get('show_category', 0);
            $this->display['show_author']       = $params->get('show_author', 0);
            $this->display['show_hits']         = $params->get('show_hits', 0);
        }

        parent::__construct($config);
    }

    /**
     * تنظیم داده‌ای که باید بارگذاری شود
     */
    protected function setOutput(array $items = null): void
    {
        /** @var \Joomla\CMS\MVC\Model\ListModel $mainModel */
        $mainModel = $this->getModel();

        /** @var \Carlitorweb\Component\Vapi\Api\Model\ModuleModel $moduleModel */
        $moduleModel = $this->getModel('module');

        if ($items === null) {
            $items = [];

            foreach ($mainModel->getItems() as $item) {
                $_item = $this->prepareItem($item, $moduleModel);
                $items[] = $this->getAllowedPropertiesToRender($_item);
            }
        }

        // بررسی خطاها
        if (\count($errors = $this->get('Errors'))) {
            throw new GenericDataException(implode("\n", $errors), 500);
        }

        $this->_output = $items;
    }

    /**
     * @param  \stdClass $item مقاله برای آماده‌سازی
     */
    protected function getAllowedPropertiesToRender($item): \stdClass
    {
        $allowedFields = new \stdClass;

        foreach($item as $key => $value) {
            if (in_array($key, $this->fieldsToRenderList, true)) {
                $allowedFields->$key = $value;
            }
        }

        return $allowedFields;
    }

    /**
     * آماده‌سازی آیتم قبل از رندر کردن
     *
     * @param   object       $item  آیتم مدل
     * @param   ModuleModel  $moduleModel
     *
     * @return  object
     *
     */
    protected function prepareItem($item, $moduleModel)
    {
        $item->slug = $item->alias . ':' . $item->id;
        if ($this->display['show_date']) {
            $show_date_field = $this->display['show_date_field'];
            $item->displayDate = HTMLHelper::_('date', $item->$show_date_field, $this->display['show_date_format']);
        }

        $item->displayCategoryTitle = $this->display['show_category'] ? $item->category_title : '';

        $item->displayHits          = $this->display['show_hits'] ? $item->hits : '';
        $item->displayAuthorName    = $this->display['show_author'] ? $item->author : '';

        return $item;
    }

/**
* اجرا و نمایش یک اسکریپت قالب.

*
* @param string $tpl نام فایل قالب برای تجزیه؛ به طور خودکار در مسیرهای قالب جستجو می‌کند.

*
* @return void

*
*/
public function display($tpl = null)
{
// حذف هر رشته‌ای که می‌تواند یک JSON نامعتبر ایجاد کند
// مانند PHP Notice، Warning، logs...
ob_clean();

// این دستور هر هدری را که قبلاً اضافه شده است، برای شروع clean پاک می‌کند
header_remove();

$this->setOutput();

parent::display($tpl);

echo $this->document->render();
}
}

JsonView: این موضوع اهمیت دارد. نام باید همین باشد چون جوملا به دنبال همین نام کلاس می‌گردد. همچنین، JSON-API هسته جوملا از view در کلاس Joomla\CMS\MVC\View\JsonApiView ارث‌بری می‌کند، اما هدف این آموزش دریافت پاسخ JSON است و باید از \Joomla\CMS\MVC\View\JsonView ارث‌بری شود.

setOutput(): با استفاده از متد $this->getModel() به مدل‌هایی که در کنترلر تنظیم کرده‌ایم دسترسی پیدا می‌کنیم، یکی به طور پیش‌فرض و دیگری در مواقعی که نام به عنوان کلید در آرایه \Joomla\CMS\MVC\View\AbstractView::_models نیاز باشد.

getAllowedPropertiesToRender(): اگر مسیری عمومی دارید، باید به این فکر کنید که چه فیلدهایی را در شیء JsonView خود شامل می‌کنید. همه فیلدها برای نمایش عمومی مناسب نیستند؛ این موضوع می‌تواند به یک آسیب‌پذیری امنیتی به نام افشای اطلاعات منجر شود.

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

$this->document->render(): خروجی سند را انجام می‌دهد.

یک بار دیگر، با استفاده از کلاینت API مورد علاقه‌تان، یک درخواست به مسیر زیر بفرستید

 [yourLocalRootSiteURL]/api/index.php/v1/vapi/modules/[idOfYourModule]

نتیجه به صورت زیر خواهد بود:

 
[
  {
    "id": 11,
    "title": "Typography",
    "alias": "typography",
    "metakey": "",
    "metadesc": "",
    "params": {...},
    "displayDate": "2022-11-20 20:49:17",
    "displayCategoryTitle": "Typography",
    "displayHits": 0,
    "displayAuthorName": "Carlos Rodriguez"
  }
]

عالی! پاسخ JSON معمول ما اینجاست و ماندگار خواهد بود ;)