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

تکمیل کلاس App

در آموزش ساخت کلاس App به نوشتن بخش ابتدایی این کلاس پرداختیم به طوری که تا این مرحله به صورت زیر است:

<?php
namespace Core;

class App
{
    private $controller;
    private $action;
    private $params = [];

    private function parseUrl()
    {
        $request = trim($_SERVER['REQUEST_URI'], '/');
        $request = filter_var($request, FILTER_SANITIZE_URL);
        $request = explode('/', $request);
        return $request;
    }
}

در پروسهٔ تکمیل این کلاس، نیاز داریم تا یک کانستراکتور تعریف کنیم که به محض ساخت آبجکتی از روی این کلاس فراخوانی شده و دستورات داخل آن اجرا گردند. به همین منظور، ابتدا کلاس فوق را به صورت زیر تکمیل نموده سپس به تفسیر کدها خواهیم پرداخت:

<?php
namespace Core;

use Base\Controllers\DefaultController;

class App
{
    private $controller;
    private $action;
    private $params = [];

    private function parseUrl()
    {
        $request = trim($_SERVER['REQUEST_URI'], '/');
        $request = filter_var($request, FILTER_SANITIZE_URL);
        $request = explode('/', $request);
        return $request;
    }

    public function __construct()
    {
        $url = $this->parseUrl();
        $routing = new Routing;

        if ($routing->routes) {
            $this->controller = new DefaultController;
            $this->action = 'homepage';
            $controllerPlusAction = (isset($url[0]) ? $url[0] : '') . (isset($url[1]) ? '/' . $url[1] : '');
            foreach ($routing->routes as $route) {
                if ($route['route'] == $controllerPlusAction) {
                    if (file_exists('../app/' . $route['module'] . '/Controllers/' . $route['controller'] . '.php')) {
                        $dynamicControllerName = "\\" . $route['module'] . "\Controllers\\" . $route['controller'];
                        $this->controller = new $dynamicControllerName;
                        $this->action = $route['action'];
                        unset($url[0], $url[1]);
                        break;
                    } else {
                        $this->action = 'homepage';
                    }
                } else {
                    $this->action = 'notfound';
                }
            }
        }

        $this->params = $url ? array_values($url) : [];
        call_user_func_array([$this->controller, $this->action], $this->params);
    }
}

نیاز به توضیح نیست که به منظور ساخت کانستراکتور در زبان پی‌اچ‌پی می‌باید از ساختار ()construct__ استفاده نماییم. پیش از هر چیز، در بدنهٔ این کانستراکتور متغیری تحت عنوان url$ ساخته و تابع ()parseUrl که پیش این نوشته بودیم را به آن منتسب می‌کنیم و در ادامه متغیر دیگری به نام routing$ ساخته و مقدار آن را برابر با نمونه‌ای از روی کلاس Routing قرار می‌دهیم سپس با استفاده از یک دستور شرطی چک کرده‌ایم ببینیم که آیا آرایهٔ routes$ که داخل کلاس Routing قرار داد حاوی مقداری است یا خیر که اگر اینچنین بود بلافاصله پراپرتی‌های controller$ و action$ را به ترتیب با ساخت یک آبجکت از روی کلاس DefaultController و همچنین استرینگ homepage مقداردهی می‌کنیم.

در خط بیست‌وهشتم متغیری به نام controllerPlusAction$ می‌سازیم که قرار است تا دربرگیرندهٔ نام کنترلر + یک اَکشن باشد. برای همین منظور، با استفاده از تابع ()isset چک کرده‌ایم ببینیم که آیا خانهٔ صِفرم آرایهٔ url$ حاوی مقداری است یا خیر به طوری که اگر پُر بود آن را چاپ می‌کنیم و در غیر این صورت مقدار خالی '' را برایش در نظر می‌گیریم (لازم به یادآوری است که خانهٔ صفر متعلق به کنترلر و خانه یک متعلق به اَکشن است.) سپس یک علامت . قرار داده و همین روش را برای خانهٔ یکم این آرایه چک می‌کنیم به طوری که اگر پُر بود ابتدا یک علامت / چاپ کرده سپس خانهٔ یکم آرایهٔ url$ را چاپ می‌کنیم و در غیر این صورت مقدار خالی '' را در نظر می‌گیریم.

سپس آرایهٔ routes$ را با استفاده یک حلقه به اِلِمان‌های تشکیل‌دهنده‌اش تفکیک کرده و داخل بدنهٔ foreach با استفاده از یک دستور شرطی چک می‌کنیم ببینیم که آیا در کلید route در اِلِمان‌های آرایهٔ routes$ مقداری برابر با مقدار در نظر گرفته‌ شده برای controllerPlusAction$ وجود دارد یا خیر که اگر چنین مقداری وجود نداشت وارد دستور else مرتبط با این دستور شرطی شده و مقدار استرینگ notfound را برای پراپرتی action$ در نظر می‌گیریم که مرتبط با ویوی notfound.php به منظور نمایش صفحهٔ ۴۰۴ خواهد بود اما اگر چنین مقداری وجود داشت، بلافاصله با استفاده از یک دستور شرطی دیگر و همچنین تابع ()file_exsits چک می‌کنیم ببینیم که آیا در ماژول مربوطه کنترلی هم‌نام با کنترلری که در کلید controller متغیر route$ درج شده وجود دارد یا خیر که اگر نتیجه false بود وارد دستور else شده و استرینگ homepage به پراپرتی action$ اختصاص خواهد یافت و اگر نتیجه true بود، متغیری به صورت زیر می‌سازیم:

$dynamicControllerName = "\\" . $route['module'] . "\Controllers\\" . $route['controller'];

پیش از توضیح پیرامون مقدار در نظر گرفته شده برای این متغیر، فرض کنیم که در لینک http://mvc.local/default/users قرار داریم. مقدار اختصاص‌یافته به متغیر فوق برای چنین یوآرالی برابر با استرینگ Base\Controllers\DefaultController\ خواهد بود. به نظر می‌رسد حال که می‌دانیم خروجی به چه شکل باید باشد، می‌توانیم به شکل بهتری دست به تفسیر مقدار اختصاص‌یافته برای متغیر dynamicControllerName$ بزنیم.

در واقع، در ابتدای رشته دو علامت \\ قرار داده‌ایم که علامت \ اول به منظور اصطلاحاً Escape کردن علامت \ دوم به کار رفته است و برای همین منظور از دو علامت به صورت "\\" استفاده می‌کنیم به طوری که علامت \ اول منجر به بی‌تأثیر شدن علامت \ گردیده و صرفاً آن را چاپ می‌کند. سپس مقدار در نظر گرفته شده برای کلید module در متغیر route$ را هدف قرار گرفته‌ایم و در ادامه مجدد یک . قرار داده و استرینگ \\controllers\ را نوشته‌ایم که مجدداً علامت \ اول به منظور اِسکِیپ کردن علامت \ دوم به کار رفته است و در نهایت یک . قرار داده و مقدار در نظر گرفته شده برای کلید controller در آرایهٔ route$ را درج کرده‌ایم.

در این مرحله از کار، توانسته‌ایم به صورت دینامیک (پویا) اقدام به یافتن نِیم‌اِسپیسی که کنترلر در آن قرار دارد نماییم و در ادامه می‌توانیم به سادگی با استفاده از کیورد new نمونه‌ای از روی این کنترلر ساخته و آن را به پراپرتی controller$ اختصاص داده سپس مقدار موجود در کلید action در آرایهٔ route$ را نیز به پراپرتی action$ منتسب می‌کنیم.

همچنین می‌بینیم که در خط سی‌وپنجم با استفاده از متد ()unset اِلِمان‌های صفرم و یکم آرایهٔ url$ را خالی کرده‌ایم و این بدین دلیل است که چنانچه هر گونه پارامتری به عنوان اِلِمان سوم سِت شده بود، به سادگی در خط چهل‌وششم به پراپرتی params$ اختصاص یابد.

آنچه در اینجا بسیار حائز اهمیت می‌باشد، استفادهٔ صحیح از کیورد break است. در واقع، کاری که این دستور داخل حلقه‌ها انجا می‌دهد آن است که چنانچه شرطی خاص برآورده شود، می‌توان در انتهای شرط مذکور دستور break را نوشته تا دیگر پروسهٔ چرخیدن داخل آرایهٔ ورودی برای حلقهٔ foreach خاتمه یابد و در ارتباط با اسکریپت فوق، این دقیقاً همان چیزی است که به آن نیاز داریم؛‌ به عبارتی،‌ نیاز داریم تا به محض آنکه کنترلری در یوآرالی که توسط کاربر وارد شده با یکی از مقادیر موجود در کلید controller در آرایهٔ routes$ دقیقاً‌ یکسان بود، این حلقه دیگر ادامه نیابد.

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

$this->params = $url ? array_values($url) : [];
call_user_func_array([$this->controller, $this->action], $this->params);

در خط اول دستور داده‌ایم که اگر آرایهٔ url$ وجود داشت، مقادیر آن آرایه با استفاده از تابع ()array_values به پراپرتی params$ اختصاص یابد و در غیر این صورت، از طریق علائم [] آرایه‌ای خالی به این پراپرتی اختصاص پیدا کند و در خط دوم از هم از تابعی به نام ()call_user_func_array استفاده کرده‌ایم که درک سازوکار آن نیاز به کمی توضیح دارد که در ادامه به بررسی این مسئله خواهیم پرداخت.

آشنایی با کاربرد تابع ()call_user_func_array

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

<?php
class Behzad
{
    public function code($arg1, $arg2)
    {
        echo "Behzad codes in $arg1 and $arg2";
    }
}

کلاسی نوشته‌ایم به نام Behzad که داخلش یک فانکشن تحت عنوان ()code داریم که دو پارامتر ورودی می‌گیرد؛ سپس داخل این فانکشن استرینگی را به همراه مقادیر منتسب به پارامترهای ورودی چاپ کرده‌ایم. حال در ادامه می‌بینیم که چگونه از ()call_user_func_array در ارتباط با این کلاس می‌توانیم استفاده کنیم:

<?php
class Behzad
{
    public function code($arg1, $arg2)
    {
        echo "Behzad codes in $arg1 and $arg2";
    }
}
$behzadObj = new Behzad;
call_user_func_array([$behzadObj, "code"], ["PHP", "JS"]);

همان‌طور که ملاحظه می‌شود، از روی این کلاس آبجکتی تحت عنوان behzadObj$ ساخته‌ایم سپس به عنوان پارامتر اول ()call_user_func_array آرایه‌ای را در نظر گرفته‌ایم که اِلِمان‌ اولش آبجکت ساخته‌شده از روی کلاس Behzad است و اِلِمان دوم نیز نام متدی می‌باشد تحت عنوان ()code که داخل این کلاس تعریف کرده‌ایم. به عنوان پارامتر دوم ()call_user_func_array نیز آرایه‌ای در نظر گرفته‌ایم که حاوی دو اِلِمان PHP و JS است.

اساساً‌ کاری که ()call_user_func_array انجام می‌دهد آن است که در کلاس (آبجکت) مذکور به دنبال فانکشنی تحت عنوان ()code می‌گردد سپس مقادیر PHP و JS را به ترتیب به عنوان آرگومان‌های ورودی فانکشن ()code در نظر می‌گیرد به طور که در خروجی خواهیم داشت:

Behzad codes in PHP and JS

حال به نظر می‌رسد که با این توضیحات بتوانیم به تفسیر خطی از کانستراکتور کلاس App بپردازیم که در آن از ()call_user_func_array استفاده نموده‌ایم. در حقیقت، دو آرایه به عنوان آرگومان اول و دوم این فانکشن در نظر گرفته‌ایم به طوری که آرایه‌ٔ اول حاوی کلیدهای this->controller$ و this->action$ است مضاف بر اینکه پراپرتی this->params$ را به عنوان پارامتر دوم در نظر گرفته‌ایم به طوری که در نهایت ()call_user_func_array در کلاس منتسب به پراپرتی this->controller$ به دنبال اَکشن منتسب به پراپرتی this->action$ گشته و مقدار متنسب به this->params$ را به آن متد اختصاص داده و آن را فراخوانی می‌کند.

در این آموزش علاوه بر تکمیل کانستراکتور کلاس App، با کاربرد دستور ()call_user_func_array در زبان پی‌اچ‌پی آشنا شدیم به طوری که این امکان را در اختیار توسعه‌دهندگان این زبان برنامه‌نویسی می‌گذارد تا بتوانند اقدام به فراخوانی متدهای مختلف یک آبجکت کرده و به صورت کاملاً دینامیک مقادیر مد نظر خود را به آن‌‌ها پاس دهند.

online-support-icon