Completed
Pull Request — master (#20)
by Alexander
01:34
created

Dispatch::route()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace alkemann\h2l;
4
5
use alkemann\h2l\exceptions\InvalidUrl;
6
use alkemann\h2l\exceptions\NoRouteSetError;
7
8
/**
9
 * Class Request
10
 *
11
 * @package alkemann\h2l
12
 */
13
class Dispatch
14
{
15
    private $middlewares = [];
16
17
    /**
18
     * @var interfaces\Session
19
     */
20
    private $session;
21
22
    /**
23
     * @var Request
24
     */
25
    protected $request;
26
27
    /**
28
     * Analyze request, provided $_REQUEST, $_SERVER [, $_GET, $_POST] to identify Route
29
     *
30
     * Response type can be set from HTTP_ACCEPT header
31
     * Call setRoute or setRouteFromRouter to set a route
32
     *
33
     * @param array $request $_REQUEST
34
     * @param array $server $_SERVER
35
     * @param array $get $_GET
36
     * @param array $post $_POST
37
     * @param null|interfaces\Session $session if null, a default Session with $_SESSION will be created
38
     */
39
    public function __construct(
40
        array $request = [],
41
        array $server = [],
42
        array $get = [],
43
        array $post = [],
44
        interfaces\Session $session = null
45
    ) {
46
        unset($get['url']); // @TODO Use a less important keyword, as it blocks that _GET param?
47
        $this->request = (new Request)
48
            ->withRequestParams($request)
49
            ->withServerParams($server)
50
            ->withGetData($get)
51
            ->withPostData($post)
52
53
            // @TODO Always do this? Can we know from $_SERVER or $_REQUEST ?
54
            ->withBody(file_get_contents('php://input'));
55
        ;
56
57
        if (is_null($session)) {
58
            $this->session = new Session;
59
        } else {
60
            $this->session = $session;
61
        }
62
    }
63
64
    public function setRouteFromRouter(string $router = Router::class): bool
65
    {
66
        $route = $router::match($this->request->url(), $this->request->method());
67
        if (is_null($route)) {
68
            return false;
69
        }
70
        $this->request = $this->request->withRoute($route);
71
        return true;
72
    }
73
74
    /**
75
     * @return interfaces\Route|null Route identified for request if set
76
     */
77
    public function route(): ?interfaces\Route
78
    {
79
        return $this->request->route();
80
    }
81
82
    /**
83
     * @param interfaces\Route $route
84
     */
85
    public function setRoute(interfaces\Route $route): void
86
    {
87
        if (!$this->request) {
88
            $this->request = new Request;
89
        }
90
        $this->request = $this->request->withRoute($route);
91
    }
92
93
    /**
94
     * Execute the Route's callback and return the Response object that the callback is required to return
95
     *
96
     * Catches InvalidUrl exceptions and returns a response\Error with 404 instead
97
     *
98
     * @return Response|null
99
     * @throws NoRouteSetError if a route has not been set prior to calling this, or by a middleware
100
     */
101
    public function response(): ?Response
102
    {
103
        $cbs = $this->middlewares;
104
        $call_eventual_route_at_end_of_chain = function (Request $request, Chain $chain): ?Response {
0 ignored issues
show
Unused Code introduced by
The parameter $chain is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

104
        $call_eventual_route_at_end_of_chain = function (Request $request, /** @scrutinizer ignore-unused */ Chain $chain): ?Response {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
105
            $route = $request->route();
106
            if (is_null($route)) {
107
                throw new NoRouteSetError("Response called without Route set");
108
            }
109
            try {
110
                return $route($request);
111
            } catch (InvalidUrl $e) {
112
                return new response\Error(['message' => $e->getMessage()],
113
                    ['code' => 404, 'request' => $this->request]);
114
            }
115
        };
116
        array_push($cbs, $call_eventual_route_at_end_of_chain);
117
        $response = (new Chain($cbs))->next($this->request);
118
        return ($response instanceof Response) ? $response : null;
119
    }
120
121
    /**
122
     * Add a closure to wrap the Route callback in to be called during Request::response
123
     *
124
     * @param callable|callable[] ...$cbs
125
     */
126
    public function registerMiddle(callable ...$cbs): void
127
    {
128
        foreach ($cbs as $cb) {
129
            $this->middlewares[] = $cb;
130
        }
131
    }
132
133
    /**
134
     * Returns the session var at $key or the Session object
135
     *
136
     * First call to this method will initiate the session
137
     *
138
     * @param string $key Dot notation for deeper values, i.e. `user.email`
139
     * @return mixed|interfaces\Session
140
     */
141
    public function session(?string $key = null)
142
    {
143
        if (is_null($key)) {
144
            $this->session->startIfNotStarted();
145
            return $this->session;
146
        }
147
        return $this->session->get($key);
148
    }
149
150
    /**
151
     * Redirect NOW the request to $url
152
     *
153
     * @codeCoverageIgnore
154
     * @param $url
155
     */
156
    public function redirect($url)
157
    {
158
        // @TODO add support for reverse route match
159
        header("Location: " . $url);
160
        exit;
161
    }
162
}
163