Completed
Push — fm-matches ( f867e2...7fc64f )
by Vladimir
14:12
created

Controller::getObjectFromParameters()   C

Complexity

Conditions 11
Paths 17

Size

Total Lines 40
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 11.6363

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 40
ccs 19
cts 23
cp 0.8261
rs 5.2653
cc 11
eloc 23
nc 17
nop 2
crap 11.6363

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Depend...njection\ContainerAware has been deprecated with message: since version 2.8, to be removed in 3.0. Use the ContainerAwareTrait instead.

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.

Loading history...
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) {
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...
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);
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...
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) {
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...
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