در آموزش ساخت کلاس 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
در زبان پیاچپی آشنا شدیم به طوری که این امکان را در اختیار توسعهدهندگان این زبان برنامهنویسی میگذارد تا بتوانند اقدام به فراخوانی متدهای مختلف یک آبجکت کرده و به صورت کاملاً دینامیک مقادیر مد نظر خود را به آنها پاس دهند.