This repository has been archived by the owner on Nov 25, 2020. It is now read-only.
/
Controller.php
476 lines (440 loc) · 12.6 KB
/
Controller.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
<?php
/**
* Contains the T_Controller class.
*
* @package controllers
* @author Rob Tuley
* @version SVN: $Id$
* @license http://knotwerk.com/licence MIT
*/
/**
* Request Controller.
*
* This is the base controller class. The controller pass responsibility for a
* request down a chain of command until the correct controller is reached. They
* maintain a context and regsiter as the responsibility is passed forward that
* is available in each controller object.
*
* @package controllers
*/
class T_Controller implements T_Controller_Action
{
/**
* Controller Context
*
* The controller context provides access to details of the request, and
* essentially summaries the mapping that has occurred so far. Use it to
* extract the URL and subspace of the previous controller in the chain.
*
* @var T_Controller_Context
*/
protected $context;
/**
* The URL of this controller.
*
* @var T_Url
*/
protected $url;
/**
* The subspace still to be mapped after this controller.
*
* @var array
*/
protected $subspace;
/**
* Delegate to this controller.
*
* @var false|string
*/
protected $delegate_to = false;
/**
* Coerces a particular scheme.
*
* @var string
*/
protected $coerce_scheme = null;
/**
* Create controller from previous context.
*
* This default controller action clones URL and subspace from the previous
* controller context. If it is consuming the URL path (as is normal), it
* shifts a single value out from the subspace and moves it onto its own URL.
* Sometimes, the request is "delegated" sideways in the controller stack, and
* in this case the controller simply takes over from the parent and does not
* pop any bits off the pathname.
*
* @param T_Controller_Context $context context
*/
function __construct(T_Controller_Context $context)
{
$this->context = $context;
$this->url = clone($context->getUrl());
$this->subspace = $context->getSubspace();
if (!$context->isDelegated()) {
$name = array_shift($this->subspace);
if (strlen($name)==0) {
throw new T_Exception_Controller('no subspace stack');
}
$this->url->appendPath($name);
}
$this->coerceScheme($context->getCoerceScheme()); // inherit any scheme coerce
}
/**
* Gets the URL of the current context.
*
* @return T_Url URL object
*/
function getUrl()
{
return $this->url;
}
/**
* Gets the sub space of the desired URL still to be mapped.
*
* @return array path segments still to be mapped.
*/
function getSubspace()
{
return $this->subspace;
}
/**
* Delegate control to another controller, without consuming the URL.
*
* This gives the user the possibility of routing to resquest to another
* controller without it being mapped directly from the URL. The method
* should be used sparingly, but is useful when sometimes it is necessary
* to route the request to a secondary controller based on state rather
* than URL (e.g. logins).
*
* To have any effect, the delegation must occur before the controller
* handles the request..!
*
* @param string $name controller name
*/
function delegate($name)
{
$this->delegate_to = $name;
return $this;
}
/**
* Whether this controller is delegated.
*
* @return bool
*/
function isDelegated()
{
return $this->delegate_to!==false;
}
/**
* Dispatch the request to controller chain.
*
* This method parses the request, dispatches it down the chain of
* controllers and then sends the response. It is called explicitally from
* the front controller:
*
* <code>
* $app = new Root();
* $app->dispatch();
* </code>
*/
function dispatch()
{
try {
$this->setAppRoot($this->getUrl());
$response = $this->like('T_Response');
$this->handleRequest($response);
$response->send();
} catch (T_Response $alt) {
// check that previous response has been aborted.
if (isset($response) && !$response->isAborted()) {
$msg = 'Original response was never aborted.';
throw new RuntimeException($msg);
}
// send alternative response
$alt->send();
}
}
/**
* Handles the request.
*
* Method to handle the request. The function may delegate to a
* sub-controller, or execute the request itself.
*
* @param T_Response $response response to send to current request
* @throws T_Response alternative response in exceptional circumstances
*/
function handleRequest($response)
{
$next = $this->findNext($response);
if ($next===false) {
$this->execute($response);
} else {
$next->handleRequest($response);
}
}
/**
* Coerces the request into a particular scheme on controller execution.
*
* @param string $scheme
* @return T_Controller fluent interface
*/
function coerceScheme($scheme)
{
$this->coerce_scheme = $scheme;
return $this;
}
/**
* Gets the HTTP scheme this controller is coerced to.
*
* @return string
*/
function getCoerceScheme()
{
return $this->coerce_scheme;
}
/**
* Creates a class instance.
*
* @param string $class classname
* @param array $args construct args
* @return object class instance
*/
function like($class,array $args=array())
{
return $this->context->like($class,$args);
}
/**
* Configure DI container.
*
* This method can only be safely used when the base factory is a
* DI container (as the willUse method is not part of the T_Factory
* interface).
*
* @param string $class classname
* @param string $alias additional alias
*/
function willUse($class,$alias=null)
{
$this->context->willUse($class,$alias);
return $this;
}
/**
* Finds an item.
*
* @param string $query
* @param string $type
* @return mixed either the found item or false if not found
*/
function find($query,$type=null)
{
return $this->context->find($query,$type);
}
/**
* Adds a rule to the environment.
*
* @param T_Find_Rule $rule
* @return T_Environment fluent
*/
function addRule(T_Find_Rule $rule)
{
$this->context->addRule($rule);
return $this;
}
/**
* Get an environment input.
*
* @param string $name
* @return T_Cage_Array
*/
function input($name)
{
return $this->context->input($name);
}
/**
* Sets the server root URL.
*
* @param T_Url $root web root
*/
function setAppRoot($root)
{
$this->context->setAppRoot($root);
}
/**
* Gets the server root URL.
*
* @return T_Url web root
*/
function getAppRoot()
{
return $this->context->getAppRoot();
}
/**
* Gets the URL of the request.
*
* @return T_Url URL object
*/
function getRequestUrl()
{
return $this->context->getRequestUrl();
}
/**
* Gets the HTTP method.
*
* This returns the Http method (HEAD,GET,POST,PUT,DELETE) that the resource
* has been requested with.
*
* @param function $filter optional filter
* @return string HTTP request method
*/
function getMethod($filter=null)
{
return $this->context->getMethod($filter);
}
/**
* Whether the HTTPs method is a particular value.
*
* @return bool
*/
function isMethod($method)
{
return $this->context->isMethod($method);
}
/**
* Is the request a XMLHttpRequest?
*
* @return bool
*/
function isAjax()
{
return $this->context->isAjax();
}
/**
* Executes the request.
*
* The execution of the request is delegated to child routines based on the
* request method ('HEAD','GET','POST','PUT','DELETE').
*
* @param T_Response $response response to build
* @throws T_Response alternative response in exceptional circumstances
*/
protected function execute($response)
{
// redirect if not correct scheme
if (!is_null($this->coerce_scheme) &&
strcasecmp($this->getUrl()->getScheme(),$this->coerce_scheme)!==0) {
$url = clone $this->getUrl();
$url->setScheme($this->coerce_scheme);
if ($get=$this->input('GET')) $url->setParameters($get->uncage());
// maintain any GET parameters. Unfortunately, the URL fragment never
// gets sent to the server from the browser so this information is
// lost on re-direction.
$response->abort();
throw new T_Response_Redirect($url->getUrl());
}
// delegate to internal method
$method = $this->getMethod();
return $this->{$method}($response);
}
/**
* Executes a GET request.
*
* @param T_Response $response response to build.
*/
protected function GET($response)
{
$this->respondWithStatus(501,$response);
}
/**
* Executes a POST request.
*
* @param T_Response $response response to build.
*/
protected function POST($response)
{
$this->respondWithStatus(501,$response);
}
/**
* Executes a HEAD request.
*
* @param T_Response $response response to build.
*/
protected function HEAD($response)
{
$this->respondWithStatus(501,$response);
}
/**
* Executes a PUT request.
*
* @param T_Response $response response to build.
*/
protected function PUT($response)
{
$this->respondWithStatus(501,$response);
}
/**
* Executes a DELETE request.
*
* @param T_Response $response response to build.
*/
protected function DELETE($response)
{
$this->respondWithStatus(501,$response);
}
/**
* Finds the next controller.
*
* The default implementation to find the next controller is
* is to look in the subspace: if this is empty, there are no more
* controllers to be loaded. If there is some subspace, the method takes
* the next subspace entry, maps it to a controller and loads the controller.
*
* @param T_Response $response response to build
* @return T_Controller next controller or boolean false if none
* @throws T_Response alternative response in exceptional circumstances
*/
protected function findNext($response)
{
if ($this->isDelegated()) {
$classname = $this->delegate_to;
return $this->like($classname,array('context'=>$this));
} elseif (count($ss = $this->getSubspace())>0) {
$classname = $this->mapToClassname($ss[0]);
if ($classname===false) {
$this->respondWithStatus(404,$response);
} else {
return $this->like($classname,array('context'=>$this));
}
}
return false;
}
/**
* Error occurred and alternative response required.
*
* If a custom 404 response, etc. is required, this controller method
* can be overridden to build the custom response.
*
* @param int $status Status code (e.g. 404,etc)
* @param T_Response $response old response (to abort)
*/
protected function respondWithStatus($status,T_Response $response)
{
$response->abort();
throw $this->like('T_Response',array('status'=>$status));
}
/**
* This function maps a URL segment to a classname.
*
* This function maps a URL segment to an actual classname. It is where any
* validation of what children are actually allowed. If a mapping can't be
* found, the function should return false and that is the default function
* defined here.
*
* @param string $name URL segment to map to a classname
* @return string controller classname
*/
protected function mapToClassname($name)
{
return false; // no sub-nav permitted: when something is permitted, this
// function should return the controller classname
}
}