سرفصل‌های آموزشی
آموزش معماری MVC
ساخت کلاس Routing

ساخت کلاس Routing

در آموزش بررسی ساختار فریمورکی که در این دوره توسعه خواهیم داد، دیدیم که فولدر mvc حاوی فایل و فولدرهای زیر است:

mvc
├── app
├── composer.json
├── public
└── vendor

در فصل دوم به بررسی فولدر public پرداختیم و در این این فصل قصد داریم تا به بررسی فولدر app بپردازیم. برای همین منظور، در مسیر روت پروژه با استفاده از کامند زیر فولدری تحت عنوان app می‌سازیم:

/var/www/mvc$ mkdir app

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

Core
├── App.php
├── BaseController.php
├── Interfaces
│   ├── ControllerInterface.php
│   └── UserInterface.php
└── Routing.php

در همین راستا، در این آموزش قصد داریم تا فایل Routing.php را مورد بررسی قرار دهیم. بر اساس استاندارد PSR-4: Autoloader که در آموزش گذشته با مفهوم‌اش آشنا شدیم، ابتدا پوشه‌ای تحت عنوان Core ساخته سپس با استفاده از کامند زیر این فایل را می‌سازیم:

/var/www/mvc/app/Core$ touch Routing.php

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

<?php
namespace Core;
class Routing
{
    public $routes = [
        [
            'route' => '',
            'module' => 'Base',
            'controller' => 'DefaultController',
            'action' => 'homepage',
        ],
        [
            'route' => 'default/homepage',
            'module' => 'Base',
            'controller' => 'DefaultController',
            'action' => 'homepage',
        ],
        [
            'route' => 'default/about',
            'module' => 'Base',
            'controller' => 'DefaultController',
            'action' => 'about',
        ],
        [
            'route' => 'default/users',
            'module' => 'Base',
            'controller' => 'DefaultController',
            'action' => 'users',
        ]
    ];

    public function __construct()
    {
        return $this->routes;
    }
}

پس از درج تگ آغازین php?> ابتدا کیورد namesapce را نوشته سپس نام پوشه‌ای که داخل آن قرار داریم را می‌نویسیم که در این مثال Core است. در واقع، کاری که نِیم‌اِسپیس انجام می‌دهد این است که مشخص می‌سازد فایل Routing.php دقیقاً در چه مسیری قرار داد و به کدام نِیم‌اِسپیس تعلق دارد؛ سپس در ادامه آموزش فایل composer.json را تکمیل می‌کنیم تا نِیم‌اِسپیسی تحت عنوان Core در این پروژه به رسمیت شناخته شود.

همان‌طور که می‌بینیم، کلاسی تعریف کرده‌ایم به نام Routing که حاوی یک پراپرتی تحت عنوان routes$ از جنس public است که این امکان را برای‌مان فراهم می‌سازد تا از هر جای پروژه به آن دسترسی داشته باشیم. نیاز به توضیح نیست که routes$ آرایه‌ای چندبُعدی است که در آن یکسری یوآرال پیش‌فرض تعریف کرده‌ایم به طوری که اگر کاربر هر لینکی به غیر از آنچه در کلیدهای route در این آرایه تعریف شده را در آدرس‌بار درج نماید، با ارور 404 مواجه خواهد شد. برای مثال، آرایهٔ مربوط به هوم‌پیج سایت را مد نظر قرار می‌دهیم:

[
    'route' => 'default/homepage',
    'module' => 'Base',
    'controller' => 'DefaultController',
    'action' => 'homepage',
]

در آرایهٔ فوق کلیدی داریم تحت عنوان route که مشخصاً لینکی که به هوم‌پیج سایت منتج می‌شود را تعیین می‌کند. سپس کلیدی داریم به نام module که مقدار در نظر گرفته شده برای آن Base است که به منزلهٔ ماژول اصلی این فریمورک می‌باشد که در فصول آتی آن را خواهیم ساخت. کلیدهای controller و action نیز به ترتیب مشخص‌کنندهٔ کنترلر و متدی در ماژول Base هستند که پس از وارد کردن لینک default/homepage فراخوانی خواهند شد.

لازم به یادآوری است کانستراکتور متدی می‌باشد که به محض ساخت یک آبجکت از روی کلاس‌مان فراخوانی خواهد شد. از همین روی، در کانستراکتور کلاس Routing.php دستور داده‌ایم تا به محض ساخت یک آبجکت از روی این کلاس، پراپرتی routes$ ریترن گردد. اکنون به منظور تست، مجدد به فایل index.php بازگشته و آن را به صورت زیر تغییر می‌‌دهیم:

<?php
ini_set('display_errors', '1');
require_once __DIR__ . '/../vendor/autoload.php';

$routingObj = new Core\Routing();
dd($routingObj);

function dd($input)
{
    echo "<pre>";
    var_dump($input);
    echo "</pre>";
    die;
}

با رفتن به مسیر http://mvc.local در مرورگر، در خروجی با ارور زیر مواجه خواهیم شد:

Fatal error: Uncaught Error: Class 'Core\Routing' not found in /var/www/mvc/public/index.php:5 Stack trace: #0 {main} thrown in /var/www/mvc/public/index.php on line 5

این ارور حاکی از آن است که کلاسی با نِیم‌اِسپیس Core\Routing که در خط پنجم فایل index.php استفاده شده است یافت نشد! دلیل این مسئله آن است که در فایل composer.json چنین نِیم‌اِسپیسی تعریف نشده است.

تعریف نِیم‌اِسپیس Core در فایل composer.json

پیش از این گفتیم که در راستای به رسمیت شناخته شدن نِیم‌اِسپیس Core، می‌باید یکسری قوانین در فایل composer.json تعریف نماییم. برای همین منظور، این فایل را به صورت تکمیل می‌کنیم:

{
    "autoload": {
        "classmap": [
            "app/Core"
        ]
    }
}

با رفتن به مسیر http://mvc.local مجدد با ارور زیر مواجه خواهیم شد:

Fatal error: Uncaught Error: Class 'Core\Routing' not found in /var/www/mvc/public/index.php:5 Stack trace: #0 {main} thrown in /var/www/mvc/public/index.php on line 5

دلیل این مسئله آن است که دستور composer dump-autoload -o را اجرا نکرده‌ایم تا دیتای موجود در پوشهٔ vendor آپدیت گردد. در همین راستا، ابتدا دستور زیر را اجرا کرده سپس مجدد صفحه را رِفرش می‌کنیم:

/var/www/mvc$ composer dump-autoload -o
Generated optimized autoload files containing 1 classes

می‌بینیم که پس از اجرای موفقیت‌آمیز کامند فوق، پیامی در معرض دیدمان قرار می‌گیرد با این مضمون که «یک کلاس» داریم که از این پس با استفاده از قابلیت Autoload کامپوزر قابل‌استفاده است. حال اگر در مسیر vendor/composer/ فایلی تحت عنوان autoload_classmap.php را باز کنیم، خواهیم داشت:

<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Core\\Routing' => $baseDir . '/app/Core/Routing.php',
);

می‌بینیم که نِیم‌اِسپیس مذکور افزوده شده است. اکنون با رفتن به مسیر http://mvc.local و رِفرش کردن صفحه خواهیم داشت:

object(Core\Routing)#3 (1) {
  ["routes"]=>
  array(4) {
    [0]=>
    array(4) {
      ["route"]=>
      string(0) ""
      ["module"]=>
      string(4) "Base"
      ["controller"]=>
      string(17) "DefaultController"
      ["action"]=>
      string(8) "homepage"
    }
    [1]=>
    array(4) {
      ["route"]=>
      string(16) "default/homepage"
      ["module"]=>
      string(4) "Base"
      ["controller"]=>
      string(17) "DefaultController"
      ["action"]=>
      string(8) "homepage"
    }
    [2]=>
    array(4) {
      ["route"]=>
      string(13) "default/about"
      ["module"]=>
      string(4) "Base"
      ["controller"]=>
      string(17) "DefaultController"
      ["action"]=>
      string(5) "about"
    }
    [3]=>
    array(4) {
      ["route"]=>
      string(13) "default/users"
      ["module"]=>
      string(4) "Base"
      ["controller"]=>
      string(17) "DefaultController"
      ["action"]=>
      string(5) "users"
    }
  }
}

می‌بینیم که خطوط پنجم و ششم از فایل index.php با موفقیت اجرا شدند. در عین حال توجه داشته باشیم که فرمت فعلی در فایل composer.json اصلاً بهینه نیست چرا که با افزودن کلاس‌های جدید، به صورتی دستی می‌باید نِیم‌اِسپیس آن را تعریف کنیم! در همین راستا، محتویات این فایل را به صورت زیر ریفکتور می‌کنیم:

{
    "autoload": {
        "psr-4": {
            "Core\\": "app/Core"
        }
    }
}

همان‌طور که ملاحظه می‌شود، از کلیدی تحت عنوان autoload استفاده کرده‌ایم که خود حاوی کلید دیگری به نام psr-4 است که در این آرایه کلیدی تحت عنوان \\Core تعریف کرده‌ایم و علت آنکه پس از نِیم‌اِسپیس Core دو علامت \\ قرار داده‌ایم آن است که یکی از این دو \ به منظور اصطلاحاً Escape کردن دیگری است. در ادامه، به عنوان مقدار این کلید، مسیر app/Core را در نظر گرفته‌ایم که دقیقاً همان مسیری است که پوشهٔ Core در آن قرار دارد. در این مرحله از کار،‌ مجدد نیاز خواهیم داشت تا دستور dump-autoload را اجرا نماییم تا اطلاعات فایل autoload_classmap.php آپدیت شوند:

/var/www/mvc$ composer dump-autoload -o

لازم به یادآوری است که هیچ تغییری در فایل autoload_classmap.php مشاهده نخواهد شد اما از آنجا که شیوهٔ Autoloading در فایل composer.json را دستخوش تغییر کرده‌ایم، نیاز است تا مجدد این فرمان اجرا گردد و این در حالی است که اگر مجدد صفحه را رِفرش نماییم، همان خروجی قبل را مشاهده خواهیم کرد.

از این پس هر فایل جدیدی که در نِیم‌اِسپیس Core ایجاد کرده سپس مورد استفاده قرار دهیم به صورت خودکار به فایل مد نظرمان ایمپورت خواهد شد. برای مثال،‌ داخل پوشهٔ Core کلاس موقتی جدیدی به نام Tmp می‌سازیم:

<?php
namespace Core;

class Tmp
{
    public function __construct()
    {
        echo 'hi';
    }
}

اکنون این کلاس را داخل فایل index.php به صورت زیر مورد استفاده قرار می‌دهیم:

<?php
ini_set('display_errors', '1');
require_once __DIR__ . '/../vendor/autoload.php';

$routingObj = new Core\Routing();
$tmpObj = new Core\Tmp();
dd($tmpObj);

function dd($input)
{
    echo "<pre>";
    var_dump($input);
    echo "</pre>";
    die;
}

و به عنوان خروجی خواهیم داشت:

hi

با توجه به اینکه کلاس Tmp صرفاً جهت تست بود، آن را از پروژه حذف می‌کنیم. نکته‌ای که در ارتباط با نحوهٔ استفاده از کلاس‌ها وجود دارد آن است که می‌توانیم به جای نوشتن نام کامل کلاس پس از کیورد new به منظور ساخت یک آبجکت جدید، ابتدا کلاس مذکور را use کرده سپس صرفاً نام کلاس را نوشت:

<?php
ini_set('display_errors', '1');
use Core\Routing;
require_once __DIR__ . '/../vendor/autoload.php';

$routingObj = new Routing();
dd($routingObj);

function dd($input)
{
    echo "<pre>";
    var_dump($input);
    echo "</pre>";
    die;
}

همان‌طور که ملاحظه می‌شود، ابتدا کیورد use را نوشته سپس نِیم‌اِسپیس کامل کلاس مد نظر خود را می‌نویسیم که در این مثال Core\Routing است. حال می‌بینیم که در خط ششم صرفاً نام کلاس را نوشته و از روی آن یک آبجکت جدید ساخته‌ایم.

نحوهٔ ساخت Alias برای نِیم‌اِسپیس

در توسعهٔ نرم‌افزار گاهی پیش می‌آید که کلاس‌های هم‌نامی داشته باشیم. مثلاً فرض کنیم دو نِیم‌اِسپیس به صورت زیر داریم:

use Core\Routing;
use SokanAcademy\Routing;

در چنین مواردی به منظور جلوگیری از هر گونه کانفلیکت (تداخل)، می‌توان یا برای هر دو کلاس و یا برای یکی از آن‌ها از یک به اصطلاح Alias یا «نام مستعار» استفاده کرد. برای مثال چنانچه به فایل index.php بازگردیم خواهیم داشت:

<?php
ini_set('display_errors', '1');
use Core\Routing as MyRoutingClass;

require_once __DIR__ . '/../vendor/autoload.php';

$routingObj = new MyRoutingClass();
dd($routingObj);

function dd($input)
{
    echo "<pre>";
    var_dump($input);
    echo "</pre>";
    die;
}

برای این منظور، پس از نِیم‌اِسپیس مد نظر خود ابتدا کلیدواژهٔ as را نوشته سپس نامی‌ دلخواه همچون MyRoutingClass می‌نویسیم. همان‌طور که در خط ششم می‌بینیم، نام مستعار جدید را به منظور ساخت آبجکت در نظر گرفته‌ایم.

در این آموزش دیدیم که به چه شکل می‌توان یک کلاس جدید ساخته و آن را با استفاده از کامپوزر به صورت خودکار ایمپورت کرد. همچنین به بررسی این موضوع پرداختیم که بر اساس استاندارد PSR-4: Autoloader به چه شکل می‌توان به روشی بهینه اقدام به ایمپورت کردن کلاس‌ها بر اساس نِیم‌اِسپیس نمود.

online-support-icon