Passed
Push — master ( 39ef33...5f4097 )
by Arman
03:00 queued 15s
created

RouteDispatcher   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 123
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 34
c 2
b 0
f 0
dl 0
loc 123
rs 10
wmc 13

5 Methods

Rating   Name   Duplication   Size   Complexity  
B handle() 0 32 7
A getController() 0 13 1
A getArgs() 0 3 1
A routeParams() 0 5 1
A getAction() 0 9 3
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 2.9.5
13
 */
14
15
namespace Quantum\Mvc;
16
17
use Quantum\Exceptions\ControllerException;
18
use Quantum\Middleware\MiddlewareExecutor;
19
use Quantum\Handlers\ViewCacheHandler;
20
use Quantum\Router\RouteController;
21
use Quantum\Libraries\Csrf\Csrf;
22
use Quantum\Http\Response;
23
use Quantum\Loader\Loader;
24
use Quantum\Http\Request;
25
use Quantum\Di\Di;
26
27
class RouteDispatcher
28
{
29
    /**
30
     * Handles the incoming HTTP request and generates a response.
31
     *
32
     * This includes:
33
     * 1. Executing registered middleware.
34
     * 2. Attempting to serve a cached view.
35
     * 3. Handling routing through either a route callback or a controller action.
36
     *
37
     * Controller lifecycle hooks `__before` and `__after` will be invoked if defined.
38
     * CSRF token verification is performed for applicable HTTP methods.
39
     *
40
     * @param Request  $request  The incoming HTTP request object.
41
     * @param Response $response The HTTP response object to be sent.
42
     *
43
     * @return void
44
     */
45
    public static function handle(Request $request, Response $response): void
46
    {
47
        // 1. Apply middleware
48
        [$request, $response] = (new MiddlewareExecutor())->execute($request, $response);
49
50
        // 2. Try serving from view cache
51
        $viewCacheHandler = new ViewCacheHandler();
52
        if ($viewCacheHandler->serveCachedView(route_uri(), $response)) {
53
            return;
54
        }
55
56
        // 3. Route callback or controller handling
57
        $callback = route_callback();
58
59
        if ($callback) {
60
            call_user_func_array($callback, self::getArgs($callback));
61
        } else {
62
            $controller = self::getController();
63
            $action = self::getAction($controller);
64
65
            if ($controller->csrfVerification && in_array($request->getMethod(), Csrf::METHODS)) {
66
                csrf()->checkToken($request);
67
            }
68
69
            if (method_exists($controller, '__before')) {
70
                call_user_func_array([$controller, '__before'], self::getArgs([$controller, '__before']));
71
            }
72
73
            call_user_func_array([$controller, $action], self::getArgs([$controller, $action]));
74
75
            if (method_exists($controller, '__after')) {
76
                call_user_func_array([$controller, '__after'], self::getArgs([$controller, '__after']));
77
            }
78
        }
79
    }
80
81
    /**
82
     * Loads and returns the current route's controller instance.
83
     *
84
     * Will throw a ControllerException if the controller is not found or not defined properly.
85
     *
86
     * @return RouteController The loaded controller instance.
87
     *
88
     * @throws ControllerException
89
     */
90
    private static function getController(): RouteController
91
    {
92
        $controllerPath = modules_dir() . DS . current_module() . DS . 'Controllers' . DS . current_controller() . '.php';
93
94
        $loader = Di::get(Loader::class);
95
96
        return $loader->loadClassFromFile(
97
            $controllerPath,
98
            function () {
99
                return ControllerException::controllerNotFound(current_controller());
100
            },
101
            function () {
102
                return ControllerException::controllerNotDefined(current_controller());
103
            }
104
        );
105
    }
106
107
    /**
108
     * Retrieves the current route's action (method name) for the controller.
109
     *
110
     * @param RouteController $controller The controller instance to check against.
111
     *
112
     * @return string|null The action method name, or null if none is defined.
113
     *
114
     * @throws ControllerException If the action method does not exist in the controller.
115
     */
116
117
    private static function getAction(RouteController $controller): ?string
118
    {
119
        $action = current_action();
120
121
        if ($action && !method_exists($controller, $action)) {
122
            throw ControllerException::actionNotDefined($action);
123
        }
124
125
        return $action;
126
    }
127
128
    /**
129
     * Resolves and returns the arguments for a given callable using dependency injection.
130
     *
131
     * @param callable $callable The function or method to be invoked.
132
     *
133
     * @return array The resolved arguments for the callable.
134
     */
135
    private static function getArgs(callable $callable): array
136
    {
137
        return Di::autowire($callable, self::routeParams());
138
    }
139
140
    /**
141
     * Retrieves the route parameters from the current route.
142
     *
143
     * @return array An array of parameter values for the current route.
144
     */
145
    private static function routeParams(): array
146
    {
147
        return array_map(function ($param) {
148
            return $param['value'];
149
        }, route_params());
150
    }
151
}
152