Controller::action()
last analyzed

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 1
nc 1
1
<?php
2
3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ICanBoogie\Routing;
13
14
use ICanBoogie\HTTP\RedirectResponse;
15
use ICanBoogie\HTTP\Request;
16
use ICanBoogie\HTTP\Response;
17
use ICanBoogie\HTTP\Status;
18
use ICanBoogie\Prototyped;
19
use ICanBoogie\Routing\Controller\BeforeActionEvent;
20
use ICanBoogie\Routing\Controller\ActionEvent;
21
22
/**
23
 * A route controller.
24
 *
25
 * # Accessing the application's properties
26
 *
27
 * The class tries to retrieve undefined properties from the application, so the following code
28
 * yields the same results:
29
 *
30
 * ```php
31
 * <?php
32
 *
33
 * $this->app->models
34
 * # or
35
 * $this->models
36
 * ```
37
 *
38
 * But because `request` is defined by the controller the following code might not yield the same
39
 * results:
40
 *
41
 * ```php
42
 * <?php
43
 *
44
 * $this->app->request
45
 * # or
46
 * $this->request
47
 * ```
48
 *
49
 * @property-read string $name The name of the controller.
50
 * @property-read Request $request The request being dispatched.
51
 * @property-read Route $route The route being dispatched.
52
 * @property Response $response
53
 */
54
abstract class Controller extends Prototyped
55
{
56
	/**
57
	 * Return the name of the controller, extracted from its class name.
58
	 *
59
	 * @return string|null The underscored name of the controller, or `null` if it cannot be
60
	 * extracted.
61
	 */
62
	protected function get_name()
63
	{
64
		$controller_class = get_class($this);
65
66
		if (preg_match('/(\w+)Controller$/', $controller_class, $matches))
67
		{
68
			return \ICanBoogie\underscore($matches[1]);
69
		}
70
71
		return null;
72
	}
73
74
	/**
75
	 * @var Request
76
	 */
77
	private $request;
78
79
	/**
80
	 * @return Request
81
	 */
82
	protected function get_request()
83
	{
84
		return $this->request;
85
	}
86
87
	/**
88
	 * @return Route
89
	 */
90
	protected function get_route()
91
	{
92
		return $this->request->context->route;
93
	}
94
95
	/**
96
	 * @return Response
97
	 */
98
	protected function lazy_get_response()
99
	{
100
		return new Response(null, Status::OK, [
101
102
			'Content-Type' => 'text/html; charset=utf-8'
103
104
		]);
105
	}
106
107
	/**
108
	 * Controls the route and returns a response.
109
	 *
110
	 * The response is obtained by invoking `action()`. When the result is a {@link Response}
111
	 * instance it is returned as is, when the `$response` property has been initialized the result
112
	 * is used as its body and the response is returned, otherwise the result is returned as is.
113
	 *
114
	 * The `ICanBoogie\Routing\Controller::action:before` event of class
115
	 * {@link Controller\BeforeActionEvent} is fired before invoking `action()`, the
116
	 * `ICanBoogie\Routing\Controller::action:before` event of class
117
	 * {@link Controller\ActionEvent} is fired after.
118
	 *
119
	 * @param Request $request
120
	 *
121
	 * @return \ICanBoogie\HTTP\Response|mixed
122
	 */
123
	final public function __invoke(Request $request)
124
	{
125
		$this->request = $request;
126
127
		$result = null;
128
129
		new BeforeActionEvent($this, $result);
130
131
		if (!$result)
132
		{
133
			$result = $this->action($request);
134
		}
135
136
		new ActionEvent($this, $result);
137
138
		if ($result instanceof Response)
139
		{
140
			return $result;
141
		}
142
143
		if ($result && isset($this->response))
144
		{
145
			$this->response->body = $result;
146
147
			return $this->response;
148
		}
149
150
		return $result;
151
	}
152
153
	/**
154
	 * Performs the proper action for the request.
155
	 *
156
	 * @param Request $request
157
	 *
158
	 * @return \ICanBoogie\HTTP\Response|mixed
159
	 */
160
	abstract protected function action(Request $request);
161
162
	/**
163
	 * Redirects the request.
164
	 *
165
	 * @param Route|string $url The URL to redirect the request to.
166
	 * @param int $status Status code (defaults to {@link Status::FOUND}, 302).
167
	 * @param array $headers Additional headers.
168
	 *
169
	 * @return RedirectResponse
170
	 */
171
	public function redirect($url, $status = Status::FOUND, array $headers = [])
172
	{
173
		if ($url instanceof Route)
174
		{
175
			$url = $url->url;
176
		}
177
178
		return new RedirectResponse($url, $status, $headers);
179
	}
180
181
	/**
182
	 * Forwards the request.
183
	 *
184
	 * @param Route|mixed $destination
185
	 *
186
	 * @return mixed
187
	 */
188
	public function forward_to($destination)
189
	{
190
		if ($destination instanceof Route)
191
		{
192
			return $this->forward_to_route($destination);
193
		}
194
195
		if (is_object($destination))
196
		{
197
			$destination = "instance of " . get_class($destination);
198
		}
199
		else if (is_array($destination))
200
		{
201
			$destination = json_encode($destination);
202
		}
203
204
		throw new \InvalidArgumentException("Don't know how to forward to: $destination.");
205
	}
206
207
	/**
208
	 * Forwards dispatching to another router.
209
	 *
210
	 * @param Route $route
211
	 *
212
	 * @return Response|mixed
213
	 */
214
	protected function forward_to_route(Route $route)
215
	{
216
		$route->pattern->match($this->request->uri, $captured);
217
218
		$request = $this->request->with([
219
220
			'path_params' => $captured
221
222
		]);
223
224
		$request->context->route = $route;
225
226
		$controller = $route->controller;
227
228
		if (!is_callable($controller))
229
		{
230
			$controller = new $controller;
231
		}
232
233
		return $controller($request);
234
	}
235
}
236