1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file contains the skeleton for all of the controllers |
5
|
|
|
* |
6
|
|
|
* @package BZiON\Controllers |
7
|
|
|
* @license https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3 |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
use BZIon\Debug\Debug; |
11
|
|
|
use Symfony\Component\DependencyInjection\ContainerAware; |
12
|
|
|
use Symfony\Component\EventDispatcher\Event; |
13
|
|
|
use Symfony\Component\HttpFoundation\ParameterBag; |
14
|
|
|
use Symfony\Component\HttpFoundation\Response; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* The Controller class represents a bunch of pages relating to the same |
18
|
|
|
* subject (Model in most cases) - for example, there is a PlayerController, |
19
|
|
|
* a TeamController and a HomeController. |
20
|
|
|
* |
21
|
|
|
* Controllers contain special methods called 'actions', which are essentially |
22
|
|
|
* different pages performing different actions - for example, the |
23
|
|
|
* TeamController might contain a 'show' action, which renders the team's page, |
24
|
|
|
* and a 'new' action, which renders the page that is shown to the user when |
25
|
|
|
* they want to create a new team. |
26
|
|
|
* |
27
|
|
|
* Actions have some unique characteristics. Take a look at this sample action: |
28
|
|
|
* |
29
|
|
|
* <pre><code>public function showAction(Request $request, Team $team) { |
30
|
|
|
* return array('team' => $team); |
31
|
|
|
* } |
32
|
|
|
* </code></pre> |
33
|
|
|
* |
34
|
|
|
* The following route will make sure that `showAction()` handles the request: |
35
|
|
|
* |
36
|
|
|
* <pre><code>team_show: |
37
|
|
|
* pattern: /teams/{team} |
38
|
|
|
* defaults: { _controller: 'Team', _action: 'show' } |
39
|
|
|
* </code></pre> |
40
|
|
|
* |
41
|
|
|
* First of all, the method's name should end with `Action`. The parameters |
42
|
|
|
* are passed dynamically, and the order is insignificant. |
43
|
|
|
* |
44
|
|
|
* You can request Symfony's Request or Session class, or even a model, which |
45
|
|
|
* will be generated based on the route parameters. For example, the route |
46
|
|
|
* pattern `/posts/{post}/comments/{commentId}` (note how you can use both |
47
|
|
|
* `comment` and `commentId` as parameters - just make sure to use the correct |
48
|
|
|
* variable name on the method later) and can be used with actions like these: |
49
|
|
|
* |
50
|
|
|
* <code> |
51
|
|
|
* public function sampleAction |
52
|
|
|
* (Request $request, NewsArticle $post, Comment $comment) |
53
|
|
|
* </code> |
54
|
|
|
* |
55
|
|
|
* <code> |
56
|
|
|
* public function sampleAction |
57
|
|
|
* (NewsArticle $post, Session $session, Request $request, Comment $comment) |
58
|
|
|
* </code> |
59
|
|
|
* |
60
|
|
|
* A method's return value can be: |
61
|
|
|
* - Symfony's Response Class |
62
|
|
|
* - A string representing the text you want the user to see |
63
|
|
|
* - An array representing the variables you want to pass to the controller's |
64
|
|
|
* view, so that it can be rendered |
65
|
|
|
* |
66
|
|
|
* @package BZiON\Controllers |
67
|
|
|
*/ |
68
|
|
|
abstract class Controller extends ContainerAware |
|
|
|
|
69
|
|
|
{ |
70
|
|
|
/** |
71
|
|
|
* Parameters specified by the route |
72
|
|
|
* @var ParameterBag |
73
|
|
|
*/ |
74
|
|
|
protected $parameters; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* The first controller that was invoked |
78
|
|
|
* @var Controller |
79
|
|
|
*/ |
80
|
|
|
protected $parent; |
81
|
|
|
|
82
|
|
|
/* |
83
|
|
|
* An array of data to pass between different parts of the application |
84
|
|
|
* |
85
|
|
|
* @var ParameterBag |
86
|
|
|
*/ |
87
|
|
|
public $data; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @param ParameterBag $parameters The array returned by $request->attributes |
91
|
|
|
* @param Controller|null $parent The controller who invoked this controller |
92
|
|
|
*/ |
93
|
1 |
|
public function __construct($parameters, Controller $parent = null) |
94
|
|
|
{ |
95
|
1 |
|
$this->parameters = $parameters; |
96
|
1 |
|
$this->parent = $parent ?: $this; |
97
|
1 |
|
$this->data = new ParameterBag(); |
98
|
|
|
|
99
|
1 |
|
$this->setContainer(Service::getContainer()); |
100
|
1 |
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Returns the controller that is assigned to a route |
104
|
|
|
* |
105
|
|
|
* @param ParameterBag $parameters The array returned by $request->attributes |
106
|
|
|
* @return Controller The controller |
107
|
|
|
*/ |
108
|
1 |
|
public static function getController($parameters) |
109
|
|
|
{ |
110
|
1 |
|
$ref = new ReflectionClass($parameters->get('_controller') . 'Controller'); |
111
|
1 |
|
$controller = $ref->newInstance($parameters); |
112
|
|
|
|
113
|
1 |
|
return $controller; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Call the controller's action specified by the $parameters array |
118
|
|
|
* |
119
|
|
|
* @param string|null $action The action name to call (e.g. `show`), null to invoke the default one |
120
|
|
|
* @return Response The action's response |
121
|
|
|
*/ |
122
|
1 |
|
public function callAction($action = null) |
123
|
|
|
{ |
124
|
1 |
|
$this->setup(); |
125
|
1 |
|
$response = $this->forward($action); |
126
|
1 |
|
$this->cleanup(); |
127
|
|
|
|
128
|
1 |
|
return $response->prepare($this->getRequest()); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Get the controller's default action name |
133
|
|
|
* |
134
|
|
|
* @return string The action's name without the `Action` suffix |
135
|
|
|
*/ |
136
|
1 |
|
protected function getDefaultActionName() |
137
|
|
|
{ |
138
|
1 |
|
return $this->parameters->get('_action') ?: 'default'; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Get a controller's action |
143
|
|
|
* |
144
|
|
|
* @param string|null $action The action name to call (e.g. `show`), null to invoke the default one |
145
|
|
|
* @return \ReflectionMethod The action method |
146
|
|
|
*/ |
147
|
1 |
|
public function getAction($action = null) |
148
|
|
|
{ |
149
|
1 |
|
if (!$action) { |
|
|
|
|
150
|
|
|
$action = $this->getDefaultActionName(); |
151
|
|
|
} |
152
|
|
|
|
153
|
1 |
|
return new ReflectionMethod($this, $action . 'Action'); |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Forward the request to another action |
158
|
|
|
* |
159
|
|
|
* Please note that this doesn't generate an HTTP redirect, but an |
160
|
|
|
* internal one - the user sees the original URL, but a different page |
161
|
|
|
* |
162
|
|
|
* @param string $action The action to forward the request to |
163
|
|
|
* @param array $params An additional associative array of parameters to |
164
|
|
|
* provide to the action |
165
|
|
|
* @param string|null $controllerName The name of the controller of the |
166
|
|
|
* action, without the 'Controller' |
167
|
|
|
* suffix (defaults to the current |
168
|
|
|
* controller) |
169
|
|
|
* @return Response |
170
|
|
|
*/ |
171
|
1 |
|
protected function forward($action, $params = array(), $controllerName = null) |
172
|
|
|
{ |
173
|
1 |
|
if (!$action) { |
174
|
1 |
|
$action = $this->getDefaultActionName(); |
175
|
|
|
} |
176
|
|
|
|
177
|
1 |
|
$args = clone $this->parameters; |
178
|
1 |
|
$args->add($params); |
179
|
|
|
|
180
|
1 |
|
if ($controllerName === null) { |
181
|
1 |
|
$controller = $this; |
182
|
|
|
} else { |
183
|
|
|
$ref = new ReflectionClass($controllerName . 'Controller'); |
184
|
|
|
$controller = $ref->newInstance($args, $this->parent); |
185
|
|
|
} |
186
|
|
|
|
187
|
1 |
|
$ret = $controller->callMethod($controller->getAction($action), $args); |
188
|
|
|
|
189
|
1 |
|
return $controller->handleReturnValue($ret, $action); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Method that will be called before any action |
194
|
|
|
* |
195
|
|
|
* @return void |
196
|
|
|
*/ |
197
|
1 |
|
public function setup() |
198
|
|
|
{ |
199
|
1 |
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Method that will be called after all actions |
203
|
|
|
* |
204
|
|
|
* @return void |
205
|
|
|
*/ |
206
|
1 |
|
public function cleanup() |
207
|
|
|
{ |
208
|
1 |
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Call one of the controller's methods |
212
|
|
|
* |
213
|
|
|
* The arguments are passed dynamically to the method, based on its |
214
|
|
|
* definition - check the description of the Controller class for more |
215
|
|
|
* information |
216
|
|
|
* |
217
|
|
|
* @param \ReflectionMethod $method The method |
218
|
|
|
* @param ParameterBag $parameters The parameter bag representing the route's parameters |
219
|
|
|
* @return mixed The return value of the called method |
220
|
|
|
*/ |
221
|
1 |
|
protected function callMethod($method, $parameters) |
222
|
|
|
{ |
223
|
1 |
|
$params = array(); |
224
|
|
|
|
225
|
1 |
|
foreach ($method->getParameters() as $p) { |
226
|
1 |
|
if ($model = $this->getObjectFromParameters($p, $parameters)) { |
227
|
1 |
|
$params[] = $model; |
228
|
1 |
|
} elseif ($parameters->has($p->name)) { |
229
|
|
|
$params[] = $parameters->get($p->name); |
230
|
1 |
|
} elseif ($p->isOptional()) { |
231
|
1 |
|
$params[] = $p->getDefaultValue(); |
232
|
|
|
} else { |
233
|
1 |
|
throw new MissingArgumentException("Missing parameter '$p->name'"); |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
|
237
|
1 |
|
return $method->invokeArgs($this, $params); |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* Find what to pass as an argument on an action |
242
|
|
|
* |
243
|
|
|
* @param ReflectionParameter $modelParameter The model's parameter we want to investigate |
244
|
|
|
* @param ParameterBag $routeParameters The route's parameters |
245
|
|
|
*/ |
246
|
1 |
|
protected function getObjectFromParameters($modelParameter, $routeParameters) |
247
|
|
|
{ |
248
|
1 |
|
$refClass = $modelParameter->getClass(); |
249
|
1 |
|
$paramName = $modelParameter->getName(); |
250
|
|
|
|
251
|
1 |
|
if ($refClass !== null && $refClass->isSubclassOf("Model")) { |
252
|
|
|
// Look for the object's ID/slugs in the routeParameters array |
253
|
1 |
|
$model = $this->findModelInParameters($modelParameter, $routeParameters); |
254
|
|
|
|
255
|
1 |
|
if ($model !== null) { |
256
|
1 |
|
return $model; |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
// $me -> currently logged in user |
261
|
1 |
|
if ($paramName == "me") { |
262
|
1 |
|
return self::getMe(); |
263
|
|
|
} |
264
|
|
|
|
265
|
1 |
|
if ($refClass === null) { |
266
|
|
|
// No class provived by the method's definition, we don't know |
267
|
|
|
// what we should pass |
268
|
|
|
return null; |
269
|
|
|
} |
270
|
|
|
|
271
|
1 |
|
switch ($refClass->getName()) { |
272
|
1 |
|
case "Symfony\Component\HttpFoundation\Request": |
273
|
1 |
|
return $this->getRequest(); |
274
|
1 |
|
case "Symfony\Component\HttpFoundation\Session\Session": |
275
|
1 |
|
return $this->getRequest()->getSession(); |
276
|
1 |
|
case "Symfony\Component\HttpFoundation\Session\Flash\FlashBag": |
277
|
|
|
return $this->getRequest()->getSession()->getFlashBag(); |
278
|
1 |
|
case "Monolog\Logger": |
279
|
|
|
return $this->getLogger(); |
280
|
1 |
|
case "Symfony\Component\Form\FormFactory": |
281
|
|
|
return Service::getFormFactory(); |
282
|
|
|
} |
283
|
|
|
|
284
|
1 |
|
return null; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* Try locating a method's parameter in an array |
289
|
|
|
* |
290
|
|
|
* @param ReflectionParameter $modelParameter The model's parameter we want to investigate |
291
|
|
|
* @param ParameterBag $routeParameters The route's parameters |
292
|
|
|
* @return Model|null A Model or null if it couldn't be found |
293
|
|
|
*/ |
294
|
1 |
|
protected function findModelInParameters($modelParameter, $routeParameters) |
295
|
|
|
{ |
296
|
1 |
|
$refClass = $modelParameter->getClass(); |
297
|
1 |
|
$paramName = $modelParameter->getName(); |
298
|
|
|
|
299
|
1 |
|
if ($routeParameters->has($paramName)) { |
300
|
1 |
|
$parameter = $routeParameters->get($paramName); |
301
|
1 |
|
if (is_object($parameter) && $refClass->getName() === get_class($parameter)) { |
302
|
|
|
// The model has already been instantiated - we don't need to do anything |
303
|
|
|
return $parameter; |
304
|
|
|
} |
305
|
|
|
|
306
|
1 |
|
return $refClass->getMethod("fetchFromSlug")->invoke(null, $parameter); |
307
|
|
|
} |
308
|
|
|
|
309
|
1 |
|
if ($routeParameters->has($paramName . 'Id')) { |
310
|
|
|
return $refClass->getMethod('get') |
311
|
|
|
->invoke(null, $routeParameters->get($paramName . 'Id')); |
312
|
|
|
} |
313
|
1 |
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Render the action's template |
317
|
|
|
* @param array $params The variables to pass to the template |
318
|
|
|
* @param string $action The controller's action |
319
|
|
|
* @return string The content |
320
|
|
|
*/ |
321
|
1 |
|
protected function renderDefault($params, $action) |
322
|
|
|
{ |
323
|
1 |
|
$templatePath = $this->getName() . "/$action.html.twig"; |
324
|
|
|
|
325
|
1 |
|
return $this->render($templatePath, $params); |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Get a Response from the return value of an action |
330
|
|
|
* @param mixed $return Whatever the method returned |
331
|
|
|
* @param string $action The name of the action |
332
|
|
|
* @return Response The response that the controller wants us to send to the client |
333
|
|
|
*/ |
334
|
1 |
|
protected function handleReturnValue($return, $action) |
335
|
|
|
{ |
336
|
1 |
|
if ($return instanceof Response) { |
337
|
1 |
|
return $return; |
338
|
|
|
} |
339
|
|
|
|
340
|
1 |
|
$content = null; |
341
|
1 |
|
if (is_array($return)) { |
342
|
|
|
// The controller is probably expecting us to show a view to the |
343
|
|
|
// user, using the array provided to set variables for the template |
344
|
1 |
|
$content = $this->renderDefault($return, $action); |
345
|
|
|
} elseif (is_string($return)) { |
346
|
1 |
|
$content = $return; |
347
|
|
|
} |
348
|
|
|
|
349
|
1 |
|
return new Response($content); |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Returns the name of the controller without the "Controller" part |
354
|
|
|
* @return string |
355
|
|
|
*/ |
356
|
1 |
|
public static function getName() |
357
|
|
|
{ |
358
|
1 |
|
return preg_replace('/Controller$/', '', get_called_class()); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Returns a configured QueryBuilder for the corresponding model |
363
|
|
|
* |
364
|
|
|
* The returned QueryBuilder will only show models visible to the currently |
365
|
|
|
* logged in user |
366
|
|
|
* |
367
|
|
|
* @param string|null The model whose query builder we should get (null |
368
|
|
|
* to get the builder of the controller's model) |
369
|
|
|
* @param string $type |
370
|
|
|
* @return QueryBuilder |
371
|
|
|
*/ |
372
|
1 |
|
public static function getQueryBuilder($type = null) |
373
|
|
|
{ |
374
|
1 |
|
$type = ($type) ?: static::getName(); |
375
|
|
|
|
376
|
1 |
|
return $type::getQueryBuilder() |
377
|
1 |
|
->visibleTo(static::getMe(), static::getRequest()->get('showDeleted')); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* Generates a URL from the given parameters. |
382
|
|
|
* @param string $name The name of the route |
383
|
|
|
* @param mixed $parameters An array of parameters |
384
|
|
|
* @param bool $absolute Whether to generate an absolute URL |
385
|
|
|
* @return string The generated URL |
386
|
|
|
*/ |
387
|
|
|
public static function generate($name, $parameters = array(), $absolute = false) |
388
|
|
|
{ |
389
|
|
|
return Service::getGenerator()->generate($name, $parameters, $absolute); |
|
|
|
|
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
/** |
393
|
|
|
* Gets the browser's request |
394
|
|
|
* @return Symfony\Component\HttpFoundation\Request |
395
|
|
|
*/ |
396
|
1 |
|
public static function getRequest() |
397
|
|
|
{ |
398
|
1 |
|
return Service::getRequest(); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Gets the currently logged in player |
403
|
|
|
* |
404
|
|
|
* If the user is not logged in, a Player object that is invalid will be |
405
|
|
|
* returned |
406
|
|
|
* |
407
|
|
|
* @return Player |
408
|
|
|
*/ |
409
|
1 |
|
public static function getMe() |
410
|
|
|
{ |
411
|
1 |
|
return Player::get(self::getRequest()->getSession()->get('playerId')); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* Find out whether debugging is enabled |
416
|
|
|
* |
417
|
|
|
* @return bool |
418
|
|
|
*/ |
419
|
1 |
|
public function isDebug() |
420
|
|
|
{ |
421
|
1 |
|
return $this->container->getParameter('kernel.debug'); |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* Gets the monolog logger |
426
|
|
|
* |
427
|
|
|
* @param string $channel The log channel, defaults to the Controller's default |
428
|
|
|
* @return Monolog\Logger |
429
|
|
|
*/ |
430
|
|
|
protected static function getLogger($channel = null) |
431
|
|
|
{ |
432
|
|
|
if (!$channel) { |
|
|
|
|
433
|
|
|
$channel = static::getLogChannel(); |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
return Service::getContainer()->get("monolog.logger.$channel"); |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
/** |
440
|
|
|
* Gets the logging channel for monolog |
441
|
|
|
* @return string |
442
|
|
|
*/ |
443
|
|
|
protected static function getLogChannel() |
444
|
|
|
{ |
445
|
|
|
return 'app'; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** |
449
|
|
|
* Uses symfony's dispatcher to announce an event |
450
|
|
|
* @param string $eventName The name of the event to dispatch. |
451
|
|
|
* @param Event $event The event to pass to the event handlers/listeners. |
452
|
|
|
* @return Event |
453
|
|
|
*/ |
454
|
1 |
|
protected function dispatch($eventName, Event $event = null) |
455
|
|
|
{ |
456
|
1 |
|
return $this->container->get('event_dispatcher')->dispatch($eventName, $event); |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Renders a view |
461
|
|
|
* @param string $view The view name |
462
|
|
|
* @param array $parameters An array of parameters to pass to the view |
463
|
|
|
* @return string The rendered view |
464
|
|
|
*/ |
465
|
1 |
|
protected function render($view, $parameters = array()) |
466
|
|
|
{ |
467
|
1 |
|
Debug::startStopwatch('view.render'); |
468
|
|
|
|
469
|
1 |
|
$ret = $this->container->get('twig')->render($view, $parameters); |
470
|
|
|
|
471
|
1 |
|
Debug::finishStopwatch('view.render'); |
472
|
|
|
|
473
|
1 |
|
return $ret; |
474
|
|
|
} |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
class MissingArgumentException extends Exception |
478
|
|
|
{ |
479
|
|
|
} |
480
|
|
|
|
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.