MissingArgumentException
last analyzed

Complexity

Total Complexity 0

Size/Duplication

Total Lines 3
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 0

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 0
c 0
b 0
f 0
lcom 0
cbo 0
dl 0
loc 3
ccs 0
cts 0
cp 0
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\ContainerAwareTrait;
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
69
{
70
    use ContainerAwareTrait;
71
72
    /**
73
     * Parameters specified by the route
74
     * @var ParameterBag
75
     */
76
    protected $parameters;
77
78
    /**
79
     * The first controller that was invoked
80
     * @var Controller
81
     */
82
    protected $parent;
83
84
    /*
85
     * An array of data to pass between different parts of the application
86
     *
87
     * @var ParameterBag
88
     */
89
    public $data;
90
91
    /**
92
     * @param ParameterBag    $parameters The array returned by $request->attributes
93
     * @param Controller|null $parent     The controller who invoked this controller
94
     */
95 23
    public function __construct($parameters, Controller $parent = null)
96
    {
97 23
        $this->parameters = $parameters;
98 23
        $this->parent = $parent ?: $this;
99 23
        $this->data = new ParameterBag();
100
101 23
        $this->setContainer(Service::getContainer());
102 23
    }
103
104
    /**
105
     * Returns the controller that is assigned to a route
106
     *
107
     * @param  ParameterBag $parameters The array returned by $request->attributes
108
     * @return Controller   The controller
109
     */
110 23
    public static function getController($parameters)
111
    {
112 23
        $ref = new ReflectionClass($parameters->get('_controller') . 'Controller');
113 23
        $controller = $ref->newInstance($parameters);
114
115 23
        return $controller;
116
    }
117
118
    /**
119
     * Call the controller's action specified by the $parameters array
120
     *
121
     * @param  string|null $action The action name to call (e.g. `show`), null to invoke the default one
122
     * @return Response    The action's response
123
     */
124 23
    public function callAction($action = null)
125
    {
126 23
        $this->setup();
127 23
        $response = $this->forward($action);
128 22
        $this->cleanup();
129
130 22
        return $response->prepare($this->getRequest());
131
    }
132
133
    /**
134
     * Get the controller's default action name
135
     *
136
     * @return string The action's name without the `Action` suffix
137
     */
138 23
    protected function getDefaultActionName()
139
    {
140 23
        return $this->parameters->get('_action') ?: 'default';
141
    }
142
143
    /**
144
     * Get a controller's action
145
     *
146
     * @param  string|null       $action The action name to call (e.g. `show`), null to invoke the default one
147
     * @return \ReflectionMethod The action method
148
     */
149 23
    public function getAction($action = null)
150
    {
151 23
        if (!$action) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $action of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
152
            $action = $this->getDefaultActionName();
153
        }
154
155 23
        return new ReflectionMethod($this, $action . 'Action');
156
    }
157
158
    /**
159
     * Forward the request to another action
160
     *
161
     * Please note that this doesn't generate an HTTP redirect, but an
162
     * internal one - the user sees the original URL, but a different page
163
     *
164
     * @param  string $action The action to forward the request to
165
     * @param  array  $params An additional associative array of parameters to
166
     *                        provide to the action
167
     * @param  string|null $controllerName The name of the controller of the
168
     *                                     action, without the 'Controller'
169
     *                                     suffix (defaults to the current
170
     *                                     controller)
171
     * @return Response
172
     */
173 23
    protected function forward($action, $params = array(), $controllerName = null)
174
    {
175 23
        if (!$action) {
176 23
            $action = $this->getDefaultActionName();
177
        }
178
179 23
        $args = clone $this->parameters;
180 23
        $args->add($params);
181
182 23
        if ($controllerName === null) {
183 23
            $controller = $this;
184
        } else {
185
            $ref = new ReflectionClass($controllerName . 'Controller');
186
            $controller = $ref->newInstance($args, $this->parent);
187
        }
188
189 23
        $ret = $controller->callMethod($controller->getAction($action), $args);
190
191 22
        return $controller->handleReturnValue($ret, $action);
192
    }
193
194
    /**
195
     * Method that will be called before any action
196
     *
197
     * @return void
198
     */
199 1
    public function setup()
200
    {
201 1
    }
202
203
    /**
204
     * Method that will be called after all actions
205
     *
206
     * @return void
207
     */
208 22
    public function cleanup()
209
    {
210 22
    }
211
212
    /**
213
     * Call one of the controller's methods
214
     *
215
     * The arguments are passed dynamically to the method, based on its
216
     * definition - check the description of the Controller class for more
217
     * information
218
     *
219
     * @param  \ReflectionMethod $method     The method
220
     * @param  ParameterBag      $parameters The parameter bag representing the route's parameters
221
     * @return mixed             The return value of the called method
222
     */
223 23
    protected function callMethod($method, $parameters)
224
    {
225 23
        $params = array();
226
227 23
        foreach ($method->getParameters() as $p) {
228 23
            if ($model = $this->getObjectFromParameters($p, $parameters)) {
229 23
                $params[] = $model;
230 1
            } elseif ($parameters->has($p->name)) {
231
                $params[] = $parameters->get($p->name);
232 1
            } elseif ($p->isOptional()) {
233 1
                $params[] = $p->getDefaultValue();
234
            } else {
235
                throw new MissingArgumentException(
236
                    "Missing parameter '$p->name' for " . $this->getName() . "::"
237 23
                    . $method->getName() . "()"
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
238
                );
239
            }
240
        }
241
242 23
        return $method->invokeArgs($this, $params);
243
    }
244
245
    /**
246
     * Find what to pass as an argument on an action
247
     *
248
     * @param ReflectionParameter $modelParameter  The model's parameter we want to investigate
249
     * @param ParameterBag        $routeParameters The route's parameters
250
     *
251
     * @return object|null
252
     */
253 23
    protected function getObjectFromParameters($modelParameter, $routeParameters)
254
    {
255 23
        $refClass = $modelParameter->getClass();
256 23
        $paramName  = $modelParameter->getName();
0 ignored issues
show
Bug introduced by
Consider using $modelParameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
257
258 23
        if ($refClass !== null && $refClass->isSubclassOf("Model")) {
259
            // Look for the object's ID/slugs in the routeParameters array
260 1
            $model = $this->findModelInParameters($modelParameter, $routeParameters);
261
262 1
            if ($model !== null) {
263 1
                return $model;
264
            }
265
        }
266
267
        // $me -> currently logged in user
268 23
        if ($paramName == "me") {
269 1
            return self::getMe();
270
        }
271
272 23
        if ($refClass === null) {
273
            // No class provived by the method's definition, we don't know
274
            // what we should pass
275
            return null;
276
        }
277
278 23
        switch ($refClass->getName()) {
0 ignored issues
show
Bug introduced by
Consider using $refClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
279 23
            case 'Symfony\Component\HttpFoundation\Request':
280 23
                return $this->getRequest();
281 23
            case 'Symfony\Component\HttpFoundation\Session\Session':
282 1
                return $this->getRequest()->getSession();
283 23
            case 'Symfony\Component\HttpFoundation\Session\Flash\FlashBag':
284
                return $this->getRequest()->getSession()->getFlashBag();
285 23
            case 'Monolog\Logger':
286 22
                return $this->getLogger();
287 1
            case 'Symfony\Component\Form\FormFactory':
288
                return Service::getFormFactory();
289
        }
290
291 1
        return null;
292
    }
293
294
    /**
295
     * Try locating a method's parameter in an array
296
     *
297
     * @param  ReflectionParameter $modelParameter  The model's parameter we want to investigate
298
     * @param  ParameterBag        $routeParameters The route's parameters
299
     * @return Model|null          A Model or null if it couldn't be found
300
     */
301 1
    protected function findModelInParameters($modelParameter, $routeParameters)
302
    {
303 1
        $refClass = $modelParameter->getClass();
304 1
        $paramName  = $modelParameter->getName();
0 ignored issues
show
Bug introduced by
Consider using $modelParameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
305
306 1
        if ($routeParameters->has($paramName)) {
307 1
            $parameter = $routeParameters->get($paramName);
308 1
            if (is_object($parameter) && $refClass->getName() === get_class($parameter)) {
0 ignored issues
show
Bug introduced by
Consider using $refClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
309
                // The model has already been instantiated - we don't need to do anything
310
                return $parameter;
311
            }
312
313 1
            return $refClass->getMethod("fetchFromSlug")->invoke(null, $parameter);
314
        }
315
316 1
        if ($routeParameters->has($paramName . 'Id')) {
317
            return $refClass->getMethod('get')
318
                            ->invoke(null, $routeParameters->get($paramName . 'Id'));
319
        }
320
321 1
        return null;
322
    }
323
324
    /**
325
     * Render the action's template
326
     * @param  array  $params The variables to pass to the template
327
     * @param  string $action The controller's action
328
     * @return string The content
329
     */
330 1
    protected function renderDefault($params, $action)
331
    {
332 1
        $templatePath = $this->getName() . "/$action.html.twig";
333
334 1
        return $this->render($templatePath, $params);
335
    }
336
337
    /**
338
     * Get a Response from the return value of an action
339
     * @param  mixed    $return Whatever the method returned
340
     * @param  string   $action The name of the action
341
     * @return Response The response that the controller wants us to send to the client
342
     */
343 22
    protected function handleReturnValue($return, $action)
344
    {
345 22
        if ($return instanceof Response) {
346 22
            return $return;
347
        }
348
349 22
        $content = null;
350 22
        if (is_array($return)) {
351
            // The controller is probably expecting us to show a view to the
352
            // user, using the array provided to set variables for the template
353 1
            $content = $this->renderDefault($return, $action);
354 22
        } elseif (is_string($return)) {
355 22
            $content = $return;
356
        }
357
358 22
        return new Response($content);
359
    }
360
361
    /**
362
     * Returns the name of the controller without the "Controller" part
363
     * @return string
364
     */
365 1
    public static function getName()
366
    {
367 1
        return preg_replace('/Controller$/', '', get_called_class());
368
    }
369
370
    /**
371
     * Returns a configured QueryBuilder for the corresponding model
372
     *
373
     * The returned QueryBuilder will only show models visible to the currently
374
     * logged in user
375
     *
376
     * @param string $type The model whose query builder we should get (null
377
     *                     to get the builder of the controller's model)
378
     * @return QueryBuilder
379
     */
380
    public static function getQueryBuilder($type = null)
381 1
    {
382
        $type = ($type) ?: static::getName();
383 1
384
        return $type::getQueryBuilder()
385 1
            ->visibleTo(static::getMe(), static::getRequest()->get('showDeleted'));
386 1
    }
387
388
     /**
389
      * Generates a URL from the given parameters.
390
      * @param  string  $name       The name of the route
391
      * @param  mixed   $parameters An array of parameters
392
      * @param  bool $absolute   Whether to generate an absolute URL
393
      * @return string  The generated URL
394
      */
395
     public static function generate($name, $parameters = array(), $absolute = false)
396
     {
397
         return Service::getGenerator()->generate($name, $parameters, $absolute);
0 ignored issues
show
Documentation introduced by
$absolute is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
398
     }
399
400
    /**
401
     * Gets the browser's request
402
     * @return Symfony\Component\HttpFoundation\Request
403
     */
404
    public static function getRequest()
405 23
    {
406
        return Service::getRequest();
407 23
    }
408
409
    /**
410
     * Gets the currently logged in player
411
     *
412
     * If the user is not logged in, a Player object that is invalid will be
413
     * returned
414
     *
415
     * @return Player
416
     */
417
    public static function getMe()
418 1
    {
419
        return Player::get(self::getRequest()->getSession()->get('playerId'));
420 1
    }
421
422
    /**
423
     * Find out whether debugging is enabled
424
     *
425
     * @return bool
426
     */
427
    public function isDebug()
428 23
    {
429
        return $this->container->getParameter('kernel.debug');
430 23
    }
431
432
    /**
433
     * Find out whether the site is in demo mode
434
     *
435
     * @return bool
436
     */
437
    public function isDemoMode()
438
    {
439 22
        return $this->container->getParameter('bzion.miscellaneous.demo_mode');
440
    }
441 22
442 22
    /**
443
     * Gets the monolog logger
444
     *
445 22
     * @param  string         $channel The log channel, defaults to the Controller's default
446
     * @return Monolog\Logger
447
     */
448
    protected static function getLogger($channel = null)
449
    {
450
        if (!$channel) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $channel of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
451
            $channel = static::getLogChannel();
452
        }
453
454
        return Service::getContainer()->get("monolog.logger.$channel");
455
    }
456
457
    /**
458
     * Gets the logging channel for monolog
459
     * @return string
460
     */
461
    protected static function getLogChannel()
462
    {
463 1
        return 'app';
464
    }
465 1
466
    /**
467
     * Uses symfony's dispatcher to announce an event
468
     * @param  string $eventName The name of the event to dispatch.
469
     * @param  Event  $event     The event to pass to the event handlers/listeners.
470
     * @return Event
471
     */
472
    protected function dispatch($eventName, Event $event = null)
473
    {
474 1
        return $this->container->get('event_dispatcher')->dispatch($eventName, $event);
475
    }
476 1
477
    /**
478 1
     * Renders a view
479
     * @param  string $view       The view name
480 1
     * @param  array  $parameters An array of parameters to pass to the view
481
     * @return string The rendered view
482 1
     */
483
    protected function render($view, $parameters = array())
484
    {
485
        Debug::startStopwatch('view.render');
486
487
        $ret = $this->container->get('twig')->render($view, $parameters);
488
489
        Debug::finishStopwatch('view.render');
490
491
        return $ret;
492
    }
493
}
494
495
class MissingArgumentException extends Exception
496
{
497
}
498