ساخت Router اختصاصی در PHP
ساخت یک Router اختصاصی در PHP به شما کنترل کامل روی نحوهٔ ثبت، مطابقت و اجرا کردن مسیرها (routes) میدهد. برای پروژههای کوچک و متوسط، یک Router سبک، سریع و قابل توسعه میتواند جایگزین فریمورکهای سنگین شود. در این مقاله به طراحی، پیادهسازی، بهینهسازی و نمونههای عملی از یک Router اختصاصی میپردازیم.
مفاهیم پایهای
- مسیر (Route): الگوی URL که با درخواست تطبیق مییابد.
- Dispatcher: کدی که بعد از مطابقت مسیر، کنترل را به تابع/کلاس مربوطه میدهد.
- پارامترهای پویا: مقادیری که از URL استخراج میشوند (/user/{id}).
- میانافزار (Middleware): توابعی که قبل/بعد از کنترلر اجرا میشوند.
پیادهسازی ساده: Router پایه
class Router {
protected $routes = [];
public function add($method, $path, $handler) {
$method = strtoupper($method);
$this->routes[$method][$path] = $handler;
}
public function dispatch($method, $uri) {
$method = strtoupper($method);
$uri = parse_url($uri, PHP_URL_PATH);
if (isset($this->routes[$method][$uri])) {
return call_user_func($this->routes[$method][$uri]);
}
http_response_code(404);
echo "404 Not Found";
}
}
// استفاده:
$router = new Router();
$router->add('GET', '/', function(){ echo "Home"; });
$router->add('GET', '/about', function(){ echo "About"; });
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);این کد یک Router خیلی ساده را نشان میدهد که فقط مسیرهای دقیق (exact match) را پشتیبانی میکند. برای هر روش HTTP یک آرایه نگه میدارد و در صورت پیدا نکردن مسیر، 404 برمیگرداند. این نسخه برای کاربردهای پایهای مناسب است اما قابلیت مسیرهای پویا یا پارامترها را ندارد.
افزودن پارامترهای پویا و الگوها (Regex)
class Router {
protected $routes = [];
public function add($method, $path, $handler) {
$method = strtoupper($method);
$pattern = preg_replace('#{([w]+)}#', '(?P[^/]+)', $path);
$pattern = '#^' . $pattern . '$#';
$this->routes[$method][] = ['pattern' => $pattern, 'handler' => $handler, 'path' => $path];
}
public function dispatch($method, $uri) {
$method = strtoupper($method);
$uri = parse_url($uri, PHP_URL_PATH);
if (!isset($this->routes[$method])) {
return $this->notFound();
}
foreach ($this->routes[$method] as $route) {
if (preg_match($route['pattern'], $uri, $matches)) {
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
return call_user_func_array($route['handler'], [$params]);
}
}
return $this->notFound();
}
protected function notFound() {
http_response_code(404);
echo "404 Not Found";
}
}
// استفاده:
$router = new Router();
$router->add('GET', '/user/{id}', function($params){ echo "User: " . $params['id']; });
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);در این نسخه از preg_replace و الگوهای منظم برای تبدیل الگوهای {param} به گروههای نامدار استفاده شده است. وقتی مسیر تطبیق مییابد، پارامترها در آرایهای با کلیدهای متنی قرار میگیرند و به هندلر پاس داده میشوند. این ساختار انعطافپذیر است و امکان قرار دادن چند پارامتر را میدهد.
واسطهها (Middleware) و چینش اجرای توابع
class Router {
protected $routes = [];
protected $middleware = [];
public function add($method, $path, $handler, $middleware = []) {
$method = strtoupper($method);
$pattern = preg_replace('#{([w]+)}#', '(?P[^/]+)', $path);
$pattern = '#^' . $pattern . '$#';
$this->routes[$method][] = ['pattern' => $pattern, 'handler' => $handler, 'middleware' => $middleware];
}
public function use($callable) {
$this->middleware[] = $callable;
}
protected function runMiddleware($middlewareStack, $request, $next) {
$middleware = array_shift($middlewareStack);
if (!$middleware) return $next($request);
return $middleware($request, function($req) use ($middlewareStack, $next) {
return $this->runMiddleware($middlewareStack, $req, $next);
});
}
public function dispatch($method, $uri) {
// مشابه مثال قبلی: پیدا کردن route و اجرای middlewareها قبل از handler
}
}در این نمونه الگوی اجرای middleware نشان داده شده است: یک صف از توابع که هر کدام میتوانند درخواست را قبل از رسیدن به هندلر تغییر دهند یا متوقف کنند. در پیادهسازی کامل باید هنگام پیدا کردن مسیر، middleware مربوطه و middleware عمومی را با هم ترکیب کنید و سپس مانند الگوی بالا آنها را اجرا کنید.
بهینهسازیها و نکات حرفهای
- کش کردن الگوهای تولید شده: برای پروژههای پر تراکنش، تبدیل مسیرها به Regex را یکبار انجام دهید و در فایل کش یا APCu ذخیره کنید.
- روشهای HTTP: سعی کنید برای هر route متودهای صحیح (GET, POST, PUT, DELETE) را مشخص کنید.
- نیمرخای خطا: صفحهٔ 404، 405 (Method Not Allowed) و 500 را جدی بگیرید و پاسخ JSON برای APIها فراهم کنید.
- امنیت: پارامترهای ورودی را اعتبارسنجی و در صورت نیاز فیلتر کنید تا از حملات تزریق جلوگیری شود.
- مسیرهای گروهی و پیشوندها: برای خوانایی و نگهداری، گروههایی با پیشوند مشترک بسازید (مثلاً /admin).
نمونه جدول مقایسه روشها
| ویژگی | Router ساده | Router پیشرفته |
|---|---|---|
| پشتیبانی از پارامترها | خیر | بله (Regex) |
| Middleware | خیر | بله |
| کشینگ | خیر | پیشنهادی |
| سفارشیسازی | محدود | قابل توسعه |
مثال کامل استفاده (Router با پارامتر و middleware)
// فرض: Router کامل ساخته شده مشابه نمونههای بالا
$router = new Router();
// middleware سراسری
$router->use(function($req, $next){
// مثال لاگینگ ساده
error_log($req['method'] . ' ' . $req['uri']);
return $next($req);
});
// routes
$router->add('GET', '/', function($params){ echo "Welcome"; });
$router->add('GET', '/post/{slug}', function($params){ echo "Post: " . htmlspecialchars($params['slug']); }, ['authMiddleware']);
$router->add('POST', '/api/data', function($params){ echo json_encode(['status' => 'ok']); });
// dispatch
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);در این قطعه فرض کردهایم Router کاملتر امکانات middleware سراسری و اختصاصی را دارد. مثالها نشان میدهند چگونه میتوان مسیر GET برای نمایش پست بر اساس slug و یک مسیر API برای دریافت دادهها تعریف کرد. همچنین از htmlspecialchars برای جلوگیری از XSS استفاده شده است.
نتیجهگیری و پیشنهادات نهایی
ساخت یک Router اختصاصی در PHP فرصتی است برای درک عمیقتر نحوهٔ کار فریمورکها و ایجاد راهحلهای سبک و بهینه برای نیازهای خاص. با افزودن ویژگیهایی مثل پارامترهای نامدار، middleware، کش و مدیریت خطا میتوان یک Router حرفهای و قابل توسعه ساخت. اگر پروژهٔ شما رشد کند، میتوانید از الگوهای تستپذیر، PSR-7 برای درخواست/پاسخ و کتابخانههای معتبر برای اجزاء مربوط به امنیت و کش بهره ببرید.
نکتهٔ پایانی: قبل از بازنویسی کامل یک Router، بررسی کنید آیا نیاز واقعی به سفارشیسازی وجود دارد یا استفاده از یک Router آماده (مثل FastRoute) میتواند زمان توسعه و خطاها را کاهش دهد.
آیا این مطلب برای شما مفید بود ؟



