Complex classes like Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Controller, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
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) |
|
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) |
|
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) |
|
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() |
|
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) |
|
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) |
|
193 | |||
194 | /** |
||
195 | * Method that will be called before any action |
||
196 | * |
||
197 | * @return void |
||
198 | */ |
||
199 | 1 | public function setup() |
|
202 | |||
203 | /** |
||
204 | * Method that will be called after all actions |
||
205 | * |
||
206 | * @return void |
||
207 | */ |
||
208 | 22 | public function cleanup() |
|
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() . "()" |
|
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(); |
|
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()) { |
|
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(); |
|
305 | |||
306 | 1 | if ($routeParameters->has($paramName)) { |
|
307 | 1 | $parameter = $routeParameters->get($paramName); |
|
308 | 1 | if (is_object($parameter) && $refClass->getName() === get_class($parameter)) { |
|
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) |
|
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() |
|
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) |
||
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) |
||
399 | |||
400 | /** |
||
401 | * Gets the browser's request |
||
402 | * @return Symfony\Component\HttpFoundation\Request |
||
403 | */ |
||
404 | public static function getRequest() |
||
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() |
||
421 | |||
422 | /** |
||
423 | * Find out whether debugging is enabled |
||
424 | * |
||
425 | * @return bool |
||
426 | */ |
||
427 | public function isDebug() |
||
431 | |||
432 | /** |
||
433 | * Find out whether the site is in demo mode |
||
434 | * |
||
435 | * @return bool |
||
436 | */ |
||
437 | public function isDemoMode() |
||
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) |
||
456 | |||
457 | /** |
||
458 | * Gets the logging channel for monolog |
||
459 | * @return string |
||
460 | */ |
||
461 | protected static function getLogChannel() |
||
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) |
||
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()) |
||
493 | } |
||
494 | |||
498 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
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: