Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Route 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 Route, and based on these observations, apply Extract Interface, too.
1 | <?php namespace Myth; |
||
43 | class Route |
||
44 | { |
||
45 | |||
46 | // Our routes, ripe for the picking. |
||
47 | public $routes = array(); |
||
48 | |||
49 | // Holds key/value pairs of named routes |
||
50 | public static $names = array(); |
||
51 | |||
52 | // Used for grouping routes together. |
||
53 | public $group = null; |
||
54 | |||
55 | // Holds the 'areas' of the site. |
||
56 | public static $areas = array(); |
||
57 | |||
58 | // The default controller to use in case |
||
59 | // 'default_controller' is not in the routes file. |
||
60 | protected $default_home = 'home'; |
||
61 | |||
62 | // The default constraint to use in route building |
||
63 | protected $default_constraint = 'any'; |
||
64 | |||
65 | protected $constraints = [ |
||
66 | 'any' => '(:any)', |
||
67 | 'num' => '(:num)', |
||
68 | 'id' => '(:num)', |
||
69 | 'name' => "([a-zA-Z']+)" |
||
70 | ]; |
||
71 | |||
72 | protected $current_subdomain = null; |
||
73 | |||
74 | //-------------------------------------------------------------------- |
||
75 | |||
76 | /** |
||
77 | * Combines the routes that we've defined with the Route class with the |
||
78 | * routes passed in. This is intended to be used after all routes have been |
||
79 | * defined to merge CI's default $route array with our routes. |
||
80 | * |
||
81 | * Example: |
||
82 | * $route['default_controller'] = 'home'; |
||
83 | * Route::resource('posts'); |
||
84 | * $route = Route::map($route); |
||
85 | * |
||
86 | * @param array $routes |
||
87 | * @internal param array $route The array to merge |
||
88 | * @return array The merge route array. |
||
89 | */ |
||
90 | public function map($routes = array()) |
||
102 | |||
103 | //-------------------------------------------------------------------- |
||
104 | |||
105 | /** |
||
106 | * A single point to the basic routing. Can be used in place of CI's $route |
||
107 | * array if desired. Used internally by many of the methods. |
||
108 | * |
||
109 | * Available options are currently: |
||
110 | * 'as' - remembers the route via a name that can be called outside of it. |
||
111 | * 'offset' - Offsets and parameters ($1, $2, etc) in routes by the specified amount. |
||
112 | * Useful while doing versioning of API's, etc. |
||
113 | * |
||
114 | * Example: |
||
115 | * $route->any('news', 'posts/index'); |
||
116 | * |
||
117 | * @param string $from |
||
118 | * @param string $to |
||
119 | * @param array $options |
||
120 | * @return void |
||
121 | */ |
||
122 | public function any($from, $to, $options = array()) |
||
126 | |||
127 | //-------------------------------------------------------------------- |
||
128 | |||
129 | /** |
||
130 | * Sets the default constraint to be used in the system. Typically |
||
131 | * for use with the 'resources' method. |
||
132 | * |
||
133 | * @param $constraint |
||
134 | */ |
||
135 | public function setDefaultConstraint($constraint) |
||
141 | |||
142 | //-------------------------------------------------------------------- |
||
143 | |||
144 | /** |
||
145 | * Registers a new constraint to be used internally. Useful for creating |
||
146 | * very specific regex patterns, or simply to allow your routes to be |
||
147 | * a tad more readable. |
||
148 | * |
||
149 | * Example: |
||
150 | * $route->registerConstraint('hero', '(^.*)'); |
||
151 | * |
||
152 | * $route->any('home/{hero}', 'heroes/journey'); |
||
153 | * |
||
154 | * // Route then looks like: |
||
155 | * $route['home/(^.*)'] = 'heroes/journey'; |
||
156 | * |
||
157 | * @param $name |
||
158 | * @param $pattern |
||
159 | * @param bool $overwrite |
||
160 | */ |
||
161 | public function registerConstraint($name, $pattern, $overwrite = false) |
||
179 | |||
180 | //-------------------------------------------------------------------- |
||
181 | |||
182 | //-------------------------------------------------------------------- |
||
183 | // Named Routes |
||
184 | //-------------------------------------------------------------------- |
||
185 | |||
186 | /** |
||
187 | * Returns the value of a named route. Useful for getting named |
||
188 | * routes for use while building with site_url() or in templates |
||
189 | * where you don't need to instantiate the route class. |
||
190 | * |
||
191 | * Example: |
||
192 | * $route->any('news', 'posts/index', ['as' => 'blog']); |
||
193 | * |
||
194 | * // Returns http://mysite.com/news |
||
195 | * site_url( Route::named('blog') ); |
||
196 | * |
||
197 | * @param [type] $name [description] |
||
198 | * @return [type] [description] |
||
199 | */ |
||
200 | public static function named($name) |
||
208 | |||
209 | //-------------------------------------------------------------------- |
||
210 | |||
211 | //-------------------------------------------------------------------- |
||
212 | // Grouping Routes |
||
213 | //-------------------------------------------------------------------- |
||
214 | |||
215 | /** |
||
216 | * Group a series of routes under a single URL segment. This is handy |
||
217 | * for grouping items into an admin area, like: |
||
218 | * |
||
219 | * Example: |
||
220 | * $route->group('admin', function() { |
||
221 | * $route->resources('users'); |
||
222 | * }); |
||
223 | * |
||
224 | * @param string $name The name to group/prefix the routes with. |
||
225 | * @param \Closure $callback An anonymous function that allows you route inside of this group. |
||
226 | * @return void |
||
227 | */ |
||
228 | public function group($name, \Closure $callback) |
||
242 | |||
243 | //-------------------------------------------------------------------- |
||
244 | |||
245 | //-------------------------------------------------------------------- |
||
246 | // HTTP Verb-based routing |
||
247 | //-------------------------------------------------------------------- |
||
248 | // Routing works here because, as the routes config file is read in, |
||
249 | // the various HTTP verb-based routes will only be added to the in-memory |
||
250 | // routes if it is a call that should respond to that verb. |
||
251 | // |
||
252 | // The options array is typically used to pass in an 'as' or var, but may |
||
253 | // be expanded in the future. See the docblock for 'any' method above for |
||
254 | // current list of globally available options. |
||
255 | // |
||
256 | |||
257 | /** |
||
258 | * Specifies a single route to match for multiple HTTP Verbs. |
||
259 | * |
||
260 | * Example: |
||
261 | * $route->match( ['get', 'post'], 'users/(:num)', 'users/$1); |
||
262 | * |
||
263 | * @param array $verbs |
||
264 | * @param $from |
||
265 | * @param $to |
||
266 | * @param array $options |
||
267 | */ |
||
268 | public function match($verbs = [], $from, $to, $options = []) |
||
276 | |||
277 | //-------------------------------------------------------------------- |
||
278 | |||
279 | /** |
||
280 | * Specifies a route that is only available to GET requests. |
||
281 | * |
||
282 | * @param $from |
||
283 | * @param $to |
||
284 | * @param array $options |
||
285 | */ |
||
286 | public function get($from, $to, $options = []) |
||
292 | |||
293 | //-------------------------------------------------------------------- |
||
294 | |||
295 | /** |
||
296 | * Specifies a route that is only available to POST requests. |
||
297 | * |
||
298 | * @param $from |
||
299 | * @param $to |
||
300 | * @param array $options |
||
301 | */ |
||
302 | public function post($from, $to, $options = []) |
||
308 | |||
309 | //-------------------------------------------------------------------- |
||
310 | |||
311 | /** |
||
312 | * Specifies a route that is only available to PUT requests. |
||
313 | * |
||
314 | * @param $from |
||
315 | * @param $to |
||
316 | * @param array $options |
||
317 | */ |
||
318 | public function put($from, $to, $options = []) |
||
324 | |||
325 | //-------------------------------------------------------------------- |
||
326 | |||
327 | /** |
||
328 | * Specifies a route that is only available to DELETE requests. |
||
329 | * |
||
330 | * @param $from |
||
331 | * @param $to |
||
332 | * @param array $options |
||
333 | */ |
||
334 | public function delete($from, $to, $options = []) |
||
340 | |||
341 | //-------------------------------------------------------------------- |
||
342 | |||
343 | /** |
||
344 | * Specifies a route that is only available to HEAD requests. |
||
345 | * |
||
346 | * @param $from |
||
347 | * @param $to |
||
348 | * @param array $options |
||
349 | */ |
||
350 | public function head($from, $to, $options = []) |
||
356 | |||
357 | //-------------------------------------------------------------------- |
||
358 | |||
359 | /** |
||
360 | * Specifies a route that is only available to PATCH requests. |
||
361 | * |
||
362 | * @param $from |
||
363 | * @param $to |
||
364 | * @param array $options |
||
365 | */ |
||
366 | public function patch($from, $to, $options = []) |
||
372 | |||
373 | //-------------------------------------------------------------------- |
||
374 | |||
375 | /** |
||
376 | * Specifies a route that is only available to OPTIONS requests. |
||
377 | * |
||
378 | * @param $from |
||
379 | * @param $to |
||
380 | * @param array $options |
||
381 | */ |
||
382 | public function options($from, $to, $options = []) |
||
388 | |||
389 | //-------------------------------------------------------------------- |
||
390 | |||
391 | /** |
||
392 | * Creates a collections of HTTP-verb based routes for a controller. |
||
393 | * |
||
394 | * Possible Options: |
||
395 | * 'controller' - Customize the name of the controller used in the 'to' route |
||
396 | * 'module' - Prepend a module name to the generate 'to' routes |
||
397 | * 'constraint' - The regex used by the Router. Defaults to '(:any)' |
||
398 | * |
||
399 | * Example: |
||
400 | * $route->resources('photos'); |
||
401 | * |
||
402 | * // Generates the following routes: |
||
403 | * HTTP Verb | Path | Action | Used for... |
||
404 | * ----------+-------------+---------------+----------------- |
||
405 | * GET /photos index display a list of photos |
||
406 | * GET /photos/new creation_form return an HTML form for creating a new photo |
||
407 | * GET /photos/{id} show display a specific photo |
||
408 | * GET /photos/{id}/edit editing_form return an HTML form for editing the photo |
||
409 | * POST /photos create create a new photo |
||
410 | * PUT /photos/{id} update update an existing photo |
||
411 | * DELETE /photos/{id}/delete delete delete an existing photo |
||
412 | * |
||
413 | * @param string $name The name of the controller to route to. |
||
414 | * @param array $options An list of possible ways to customize the routing. |
||
415 | */ |
||
416 | public function resources($name, $options = []) |
||
451 | |||
452 | //-------------------------------------------------------------------- |
||
453 | |||
454 | /** |
||
455 | * Lets the system know about different 'areas' within the site, like |
||
456 | * the admin area, that maps to certain controllers. |
||
457 | * |
||
458 | * @param string $area The name of the area. |
||
459 | * @param string $controller The controller name to look for. |
||
460 | * @param $options |
||
461 | */ |
||
462 | public function area($area, $controller = null, $options = []) |
||
477 | |||
478 | //-------------------------------------------------------------------- |
||
479 | |||
480 | /** |
||
481 | * Returns the name of the area based on the controller name. |
||
482 | * |
||
483 | * @param string $controller The name of the controller |
||
484 | * @return string The name of the corresponding area |
||
485 | */ |
||
486 | public static function getAreaName($controller) |
||
496 | |||
497 | //-------------------------------------------------------------------- |
||
498 | |||
499 | /** |
||
500 | * Limits the routes to a specified ENVIRONMENT or they won't run. |
||
501 | * |
||
502 | * @param $env |
||
503 | * @param callable $callback |
||
504 | * |
||
505 | * @return bool|null |
||
506 | */ |
||
507 | public function environment($env, \Closure $callback) |
||
517 | |||
518 | //-------------------------------------------------------------------- |
||
519 | |||
520 | |||
521 | |||
522 | /** |
||
523 | * Allows you to easily block access to any number of routes by setting |
||
524 | * that route to an empty path (''). |
||
525 | * |
||
526 | * Example: |
||
527 | * Route::block('posts', 'photos/(:num)'); |
||
528 | * |
||
529 | * // Same as... |
||
530 | * $route['posts'] = ''; |
||
531 | * $route['photos/(:num)'] = ''; |
||
532 | */ |
||
533 | public function block() |
||
545 | |||
546 | //-------------------------------------------------------------------- |
||
547 | |||
548 | /** |
||
549 | * Empties all named and un-named routes from the system. |
||
550 | * |
||
551 | * @return void |
||
552 | */ |
||
553 | public function reset() |
||
560 | |||
561 | //-------------------------------------------------------------------- |
||
562 | |||
563 | //-------------------------------------------------------------------- |
||
564 | // Private Methods |
||
565 | //-------------------------------------------------------------------- |
||
566 | |||
567 | /** |
||
568 | * Does the heavy lifting of creating an actual route. You must specify |
||
569 | * the request method(s) that this route will work for. They can be separated |
||
570 | * by a pipe character "|" if there is more than one. |
||
571 | * |
||
572 | * @param string $from |
||
573 | * @param array $to |
||
574 | * @param array $options |
||
575 | * |
||
576 | * @return array The built route. |
||
577 | */ |
||
578 | private function create($from, $to, $options = array()) |
||
624 | |||
625 | //-------------------------------------------------------------------- |
||
626 | |||
627 | /** |
||
628 | * Compares the subdomain(s) passed in against the current subdomain |
||
629 | * on this page request. |
||
630 | * |
||
631 | * @param $subdomains |
||
632 | * @return bool |
||
633 | */ |
||
634 | private function checkSubdomains($subdomains) |
||
657 | |||
658 | //-------------------------------------------------------------------- |
||
659 | |||
660 | /** |
||
661 | * Examines the HTTP_HOST to get a best match for the subdomain. It |
||
662 | * won't be perfect, but should work for our needs. |
||
663 | */ |
||
664 | private function determineCurrentSubdomain() |
||
684 | //-------------------------------------------------------------------- |
||
685 | |||
686 | } |
||
687 |