PHP Classes

File: App/Core/Routing/Router.php

Recommend this page to a friend!
  Packages of Amirreza Ebrahimi   URL Shortener Application   App/Core/Routing/Router.php   Download  
File: App/Core/Routing/Router.php
Role: Class source
Content type: text/plain
Description: Class source
Class: URL Shortener Application
Application to create and redirect short URLs
Author: By
Last change:
Date: 7 months ago
Size: 5,841 bytes
 

Contents

Class file image Download
<?php

namespace App\Core\Routing;

use
App\Core\Request;

class
Router
{
    private
Request $request; # Stores the current request object
   
private array $routes; # Stores all the defined routes
   
private ?array $currentRoute; # Stores the current matched route, if any
   
const NAMESPACE = 'App\Controllers\\'; # Controller namespace for routing

    /**
     * Router constructor.
     * Initializes the request object and fetches the defined routes.
     * Attempts to match the current request to a route.
     */
   
public function __construct()
    {
       
$this->request = new Request(); # Instantiate the Request object
       
$this->routes = Route::routers(); # Fetch all defined routes
       
$this->currentRoute = $this->findRoute($this->request) ?? null; # Find the matching route or set to null
   
}

   
/**
     * Find a matching route for the given request.
     *
     * @param Request $request The current request
     * @return array|null The matched route or null if no match
     */
   
private function findRoute(Request $request): array|null
   
{
        foreach (
$this->routes as $route) {
           
# Match the HTTP method
           
if ($request->method() == $route['method']) {
               
# Check for dynamic parameters in the URI
               
if (str_contains($route['uri'], '{')) {
                   
# Create a regex pattern to match dynamic parameters in the URI
                   
$pattern = "/^" . str_replace(['/', '{', '}'], ['\/', '(?<', '>[a-zA-Z0-9-]+)'], $route['uri']) . "$/";
                    if (
preg_match($pattern, $request->uri(), $matches)) {
                       
# Add dynamic parameters to the request
                       
foreach ($matches as $key => $value) {
                            if (
is_string($key) && is_string($value)) {
                               
$request->addParam($key, $value);
                            }
                        }
                        return
$route; # Return the matched route
                   
}
                } else {
                   
# Exact URI match
                   
if ($request->uri() == $route['uri']) {
                        return
$route; # Return the matched route
                   
}
                }
            }
        }
        return
null; # Return null if no route matches
   
}

   
/**
     * Check if the request has a valid URI but an invalid method (405 Method Not Allowed).
     *
     * @param Request $request The current request
     * @return bool True if the URI is found but the method is invalid
     */
   
private function invalidRequest(Request $request): bool
   
{
       
$foundRoute = false;
        foreach (
$this->routes as $route) {
           
# Convert route URI patterns to regex
           
$pattern = preg_replace('/\{([a-zA-Z_]+)\}/', '([a-zA-Z0-9-]+)', $route['uri']);
            if (
preg_match("#^$pattern$#", $request->uri())) {
                if (
$request->method() != $route['method']) {
                   
$foundRoute = true; # URI found but method does not match
               
}
            }
        }
        return
$foundRoute; # Return true if method mismatch is found
   
}

   
/**
     * Dispatch the matched route.
     *
     * @throws \Exception If the class or method does not exist
     */
   
private function dispatch(): void
   
{
       
$action = $this->currentRoute['action'];

        if (
is_null($action) || empty($action)) {
            return;
# If no action is defined, return early
       
}

        if (
is_callable($action)) {
           
$action(); # Execute callable actions
       
}

        if (
is_string($action)) {
           
$action = explode('@', $action); # Split controller and method by '@'
           
$action[0] = self::NAMESPACE . $action[0]; # Prepend the namespace to the controller
       
}

        if (
is_array($action)) {
           
$className = $action[0];
           
$methodName = $action[1];

           
# Throw an exception if the class does not exist
           
if (!class_exists($className)) {
                throw new \
Exception("Class '$className' Not Exists");
            }

           
$controller = new $className(); # Instantiate the controller

            # Throw an exception if the method does not exist
           
if (!method_exists($controller, $methodName)) {
                throw new \
Exception("Method '$methodName' Not Exists In Class '$className'");
            }

           
# Call the controller method and pass the request
           
$controller->{$methodName}($this->request);
        }
    }

   
/**
     * Dispatch a 404 Not Found response if no route matches.
     * @throws \Exception
     */
   
private function dispatch404(): void
   
{
       
header("HTTP/1.1 404 Not Found");
       
view('errors.404'); # Display the 404 error view
       
die();
    }

   
/**
     * Dispatch a 405 Method Not Allowed response if the method is invalid.
     * @throws \Exception
     */
   
private function dispatch405(): void
   
{
       
header("HTTP/1.1 405 Method Not Allowed");
       
view('errors.405'); # Display the 405 error view
       
die();
    }

   
/**
     * Run the router to handle the request and route matching.
     * @throws \Exception
     */
   
public function run(): void
   
{
       
# Check for invalid requests (URI exists but method is invalid)
       
if ($this->invalidRequest($this->request) && !$this->currentRoute) {
           
$this->dispatch405(); # Dispatch 405 if method does not match
           
die();
        }

       
# If no route is matched, dispatch 404
       
if (is_null($this->currentRoute)) {
           
$this->dispatch404(); # Dispatch 404 if route is not found
       
}

       
# Dispatch the matched route
       
$this->dispatch();
    }
}