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 Klein 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 Klein, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class Klein |
||
31 | { |
||
32 | |||
33 | /** |
||
34 | * Class constants |
||
35 | */ |
||
36 | |||
37 | /** |
||
38 | * The regular expression used to compile and match URL's |
||
39 | * |
||
40 | * @type string |
||
41 | */ |
||
42 | const ROUTE_COMPILE_REGEX = '`(\\\?(?:/|\.|))(?:\[([^:\]]*)(?::([^:\]]*))?\])(\?|)`'; |
||
43 | |||
44 | /** |
||
45 | * The regular expression used to escape the non-named param section of a route URL |
||
46 | * |
||
47 | * @type string |
||
48 | */ |
||
49 | const ROUTE_ESCAPE_REGEX = '`(?<=^|\])[^\]\[\?]+?(?=\[|$)`'; |
||
50 | |||
51 | /** |
||
52 | * Dispatch route output handling |
||
53 | * |
||
54 | * Don't capture anything. Behave as normal. |
||
55 | * |
||
56 | * @type int |
||
57 | */ |
||
58 | const DISPATCH_NO_CAPTURE = 0; |
||
59 | |||
60 | /** |
||
61 | * Dispatch route output handling |
||
62 | * |
||
63 | * Capture all output and return it from dispatch |
||
64 | * |
||
65 | * @type int |
||
66 | */ |
||
67 | const DISPATCH_CAPTURE_AND_RETURN = 1; |
||
68 | |||
69 | /** |
||
70 | * Dispatch route output handling |
||
71 | * |
||
72 | * Capture all output and replace the response body with it |
||
73 | * |
||
74 | * @type int |
||
75 | */ |
||
76 | const DISPATCH_CAPTURE_AND_REPLACE = 2; |
||
77 | |||
78 | /** |
||
79 | * Dispatch route output handling |
||
80 | * |
||
81 | * Capture all output and prepend it to the response body |
||
82 | * |
||
83 | * @type int |
||
84 | */ |
||
85 | const DISPATCH_CAPTURE_AND_PREPEND = 3; |
||
86 | |||
87 | /** |
||
88 | * Dispatch route output handling |
||
89 | * |
||
90 | * Capture all output and append it to the response body |
||
91 | * |
||
92 | * @type int |
||
93 | */ |
||
94 | const DISPATCH_CAPTURE_AND_APPEND = 4; |
||
95 | |||
96 | |||
97 | /** |
||
98 | * Class properties |
||
99 | */ |
||
100 | |||
101 | /** |
||
102 | * The types to detect in a defined match "block" |
||
103 | * |
||
104 | * Examples of these blocks are as follows: |
||
105 | * |
||
106 | * - integer: '[i:id]' |
||
107 | * - alphanumeric: '[a:username]' |
||
108 | * - hexadecimal: '[h:color]' |
||
109 | * - slug: '[s:article]' |
||
110 | * |
||
111 | * @type array |
||
112 | */ |
||
113 | protected $match_types = array( |
||
114 | 'i' => '[0-9]++', |
||
115 | 'a' => '[0-9A-Za-z]++', |
||
116 | 'h' => '[0-9A-Fa-f]++', |
||
117 | 's' => '[0-9A-Za-z-_]++', |
||
118 | '*' => '.+?', |
||
119 | '**' => '.++', |
||
120 | '' => '[^/]+?' |
||
121 | ); |
||
122 | |||
123 | /** |
||
124 | * Collection of the routes to match on dispatch |
||
125 | * |
||
126 | * @type RouteCollection |
||
127 | */ |
||
128 | protected $routes; |
||
129 | |||
130 | /** |
||
131 | * The Route factory object responsible for creating Route instances |
||
132 | * |
||
133 | * @type AbstractRouteFactory |
||
134 | */ |
||
135 | protected $route_factory; |
||
136 | |||
137 | /** |
||
138 | * An array of error callback callables |
||
139 | * |
||
140 | * @type array[callable] |
||
141 | */ |
||
142 | protected $errorCallbacks = array(); |
||
143 | |||
144 | /** |
||
145 | * An array of HTTP error callback callables |
||
146 | * |
||
147 | * @type array[callable] |
||
148 | */ |
||
149 | protected $httpErrorCallbacks = array(); |
||
150 | |||
151 | /** |
||
152 | * An array of callbacks to call after processing the dispatch loop |
||
153 | * and before the response is sent |
||
154 | * |
||
155 | * @type array[callable] |
||
156 | */ |
||
157 | protected $afterFilterCallbacks = array(); |
||
158 | |||
159 | |||
160 | /** |
||
161 | * Route objects |
||
162 | */ |
||
163 | |||
164 | /** |
||
165 | * The Request object passed to each matched route |
||
166 | * |
||
167 | * @type Request |
||
168 | */ |
||
169 | protected $request; |
||
170 | |||
171 | /** |
||
172 | * The Response object passed to each matched route |
||
173 | * |
||
174 | * @type AbstractResponse |
||
175 | */ |
||
176 | protected $response; |
||
177 | |||
178 | /** |
||
179 | * The service provider object passed to each matched route |
||
180 | * |
||
181 | * @type ServiceProvider |
||
182 | */ |
||
183 | protected $service; |
||
184 | |||
185 | /** |
||
186 | * A generic variable passed to each matched route |
||
187 | * |
||
188 | * @type mixed |
||
189 | */ |
||
190 | protected $app; |
||
191 | |||
192 | |||
193 | /** |
||
194 | * Methods |
||
195 | */ |
||
196 | |||
197 | /** |
||
198 | * Constructor |
||
199 | * |
||
200 | * Create a new Klein instance with optionally injected dependencies |
||
201 | * This DI allows for easy testing, object mocking, or class extension |
||
202 | * |
||
203 | * @param ServiceProvider $service Service provider object responsible for utilitarian behaviors |
||
204 | * @param mixed $app An object passed to each route callback, defaults to an App instance |
||
205 | * @param RouteCollection $routes Collection object responsible for containing all route instances |
||
206 | * @param AbstractRouteFactory $route_factory A factory class responsible for creating Route instances |
||
207 | */ |
||
208 | public function __construct( |
||
220 | |||
221 | /** |
||
222 | * Returns the routes object |
||
223 | * |
||
224 | * @return RouteCollection |
||
225 | */ |
||
226 | public function routes() |
||
230 | |||
231 | /** |
||
232 | * Returns the request object |
||
233 | * |
||
234 | * @return Request |
||
235 | */ |
||
236 | public function request() |
||
240 | |||
241 | /** |
||
242 | * Returns the response object |
||
243 | * |
||
244 | * @return Response |
||
245 | */ |
||
246 | public function response() |
||
250 | |||
251 | /** |
||
252 | * Returns the service object |
||
253 | * |
||
254 | * @return ServiceProvider |
||
255 | */ |
||
256 | public function service() |
||
260 | |||
261 | /** |
||
262 | * Returns the app object |
||
263 | * |
||
264 | * @return mixed |
||
265 | */ |
||
266 | public function app() |
||
270 | |||
271 | /** |
||
272 | * Parse our extremely loose argument order of our "respond" method and its aliases |
||
273 | * |
||
274 | * This method takes its arguments in a loose format and order. |
||
275 | * The method signature is simply there for documentation purposes, but allows |
||
276 | * for the minimum of a callback to be passed in its current configuration. |
||
277 | * |
||
278 | * @see Klein::respond() |
||
279 | * @param mixed $args An argument array. Hint: This works well when passing "func_get_args()" |
||
280 | * @named string | array $method HTTP Method to match |
||
281 | * @named string $path Route URI path to match |
||
282 | * @named callable $callback Callable callback method to execute on route match |
||
283 | * @return array A named parameter array containing the keys: 'method', 'path', and 'callback' |
||
284 | */ |
||
285 | protected function parseLooseArgumentOrder(array $args) |
||
299 | |||
300 | /** |
||
301 | * Add a new route to be matched on dispatch |
||
302 | * |
||
303 | * Essentially, this method is a standard "Route" builder/factory, |
||
304 | * allowing a loose argument format and a standard way of creating |
||
305 | * Route instances |
||
306 | * |
||
307 | * This method takes its arguments in a very loose format |
||
308 | * The only "required" parameter is the callback (which is very strange considering the argument definition order) |
||
309 | * |
||
310 | * <code> |
||
311 | * $router = new Klein(); |
||
312 | * |
||
313 | * $router->respond( function() { |
||
314 | * echo 'this works'; |
||
315 | * }); |
||
316 | * $router->respond( '/endpoint', function() { |
||
317 | * echo 'this also works'; |
||
318 | * }); |
||
319 | * $router->respond( 'POST', '/endpoint', function() { |
||
320 | * echo 'this also works!!!!'; |
||
321 | * }); |
||
322 | * </code> |
||
323 | * |
||
324 | * @param string|array $method HTTP Method to match |
||
325 | * @param string $path Route URI path to match |
||
326 | * @param callable $callback Callable callback method to execute on route match |
||
327 | * @return Route |
||
328 | */ |
||
329 | public function respond($method, $path = '*', $callback = null) |
||
343 | |||
344 | /** |
||
345 | * Collect a set of routes under a common namespace |
||
346 | * |
||
347 | * The routes may be passed in as either a callable (which holds the route definitions), |
||
348 | * or as a string of a filename, of which to "include" under the Klein router scope |
||
349 | * |
||
350 | * <code> |
||
351 | * $router = new Klein(); |
||
352 | * |
||
353 | * $router->with('/users', function($router) { |
||
354 | * $router->respond( '/', function() { |
||
355 | * // do something interesting |
||
356 | * }); |
||
357 | * $router->respond( '/[i:id]', function() { |
||
358 | * // do something different |
||
359 | * }); |
||
360 | * }); |
||
361 | * |
||
362 | * $router->with('/cars', __DIR__ . '/routes/cars.php'); |
||
363 | * </code> |
||
364 | * |
||
365 | * @param string $namespace The namespace under which to collect the routes |
||
366 | * @param callable|string $routes The defined routes callable or filename to collect under the namespace |
||
367 | * @return void |
||
368 | */ |
||
369 | public function with($namespace, $routes) |
||
387 | |||
388 | /** |
||
389 | * Dispatch the request to the appropriate route(s) |
||
390 | * |
||
391 | * Dispatch with optionally injected dependencies |
||
392 | * This DI allows for easy testing, object mocking, or class extension |
||
393 | * |
||
394 | * @param Request $request The request object to give to each callback |
||
395 | * @param AbstractResponse $response The response object to give to each callback |
||
396 | * @param boolean $send_response Whether or not to "send" the response after the last route has been matched |
||
397 | * @param int $capture Specify a DISPATCH_* constant to change the output capturing behavior |
||
398 | * @return void|string |
||
399 | */ |
||
400 | public function dispatch( |
||
689 | |||
690 | /** |
||
691 | * Compiles a route string to a regular expression |
||
692 | * |
||
693 | * @param string $route The route string to compile |
||
694 | * @return string |
||
695 | */ |
||
696 | protected function compileRoute($route) |
||
741 | |||
742 | /** |
||
743 | * Validate a regular expression |
||
744 | * |
||
745 | * This simply checks if the regular expression is able to be compiled |
||
746 | * and converts any warnings or notices in the compilation to an exception |
||
747 | * |
||
748 | * @param string $regex The regular expression to validate |
||
749 | * @throws RegularExpressionCompilationException If the expression can't be compiled |
||
750 | * @return boolean |
||
751 | */ |
||
752 | private function validateRegularExpression($regex) |
||
779 | |||
780 | /** |
||
781 | * Get the path for a given route |
||
782 | * |
||
783 | * This looks up the route by its passed name and returns |
||
784 | * the path/url for that route, with its URL params as |
||
785 | * placeholders unless you pass a valid key-value pair array |
||
786 | * of the placeholder params and their values |
||
787 | * |
||
788 | * If a pathname is a complex/custom regular expression, this |
||
789 | * method will simply return the regular expression used to |
||
790 | * match the request pathname, unless an optional boolean is |
||
791 | * passed "flatten_regex" which will flatten the regular |
||
792 | * expression into a simple path string |
||
793 | * |
||
794 | * This method, and its style of reverse-compilation, was originally |
||
795 | * inspired by a similar effort by Gilles Bouthenot (@gbouthenot) |
||
796 | * |
||
797 | * @link https://github.com/gbouthenot |
||
798 | * @param string $route_name The name of the route |
||
799 | * @param array $params The array of placeholder fillers |
||
800 | * @param boolean $flatten_regex Optionally flatten custom regular expressions to "/" |
||
801 | * @throws OutOfBoundsException If the route requested doesn't exist |
||
802 | * @return string |
||
803 | */ |
||
804 | public function getPathFor($route_name, array $params = null, $flatten_regex = true) |
||
843 | |||
844 | /** |
||
845 | * Handle a route's callback |
||
846 | * |
||
847 | * This handles common exceptions and their output |
||
848 | * to keep the "dispatch()" method DRY |
||
849 | * |
||
850 | * @param Route $route |
||
851 | * @param RouteCollection $matched |
||
852 | * @param array $methods_matched |
||
853 | * @return void |
||
854 | */ |
||
855 | protected function handleRouteCallback(Route $route, RouteCollection $matched, array $methods_matched) |
||
880 | |||
881 | /** |
||
882 | * Adds an error callback to the stack of error handlers |
||
883 | * |
||
884 | * @param callable $callback The callable function to execute in the error handling chain |
||
885 | * @return boolean|void |
||
886 | */ |
||
887 | public function onError($callback) |
||
891 | |||
892 | /** |
||
893 | * Routes an exception through the error callbacks |
||
894 | * |
||
895 | * @param Exception $err The exception that occurred |
||
896 | * @throws UnhandledException If the error/exception isn't handled by an error callback |
||
897 | * @return void |
||
898 | */ |
||
899 | protected function error(Exception $err) |
||
932 | |||
933 | /** |
||
934 | * Adds an HTTP error callback to the stack of HTTP error handlers |
||
935 | * |
||
936 | * @param callable $callback The callable function to execute in the error handling chain |
||
937 | * @return void |
||
938 | */ |
||
939 | public function onHttpError($callback) |
||
943 | |||
944 | /** |
||
945 | * Handles an HTTP error exception through our HTTP error callbacks |
||
946 | * |
||
947 | * @param HttpExceptionInterface $http_exception The exception that occurred |
||
948 | * @param RouteCollection $matched The collection of routes that were matched in dispatch |
||
949 | * @param array $methods_matched The HTTP methods that were matched in dispatch |
||
950 | * @return void |
||
951 | */ |
||
952 | protected function httpError(HttpExceptionInterface $http_exception, RouteCollection $matched, $methods_matched) |
||
989 | |||
990 | /** |
||
991 | * Adds a callback to the stack of handlers to run after the dispatch |
||
992 | * loop has handled all of the route callbacks and before the response |
||
993 | * is sent |
||
994 | * |
||
995 | * @param callable $callback The callable function to execute in the after route chain |
||
996 | * @return void |
||
997 | */ |
||
998 | public function afterDispatch($callback) |
||
1002 | |||
1003 | /** |
||
1004 | * Runs through and executes the after dispatch callbacks |
||
1005 | * |
||
1006 | * @return void |
||
1007 | */ |
||
1008 | protected function callAfterDispatchCallbacks() |
||
1026 | |||
1027 | |||
1028 | /** |
||
1029 | * Method aliases |
||
1030 | */ |
||
1031 | |||
1032 | /** |
||
1033 | * Quick alias to skip the current callback/route method from executing |
||
1034 | * |
||
1035 | * @throws DispatchHaltedException To halt/skip the current dispatch loop |
||
1036 | * @return void |
||
1037 | */ |
||
1038 | public function skipThis() |
||
1042 | |||
1043 | /** |
||
1044 | * Quick alias to skip the next callback/route method from executing |
||
1045 | * |
||
1046 | * @param int $num The number of next matches to skip |
||
1047 | * @throws DispatchHaltedException To halt/skip the current dispatch loop |
||
1048 | * @return void |
||
1049 | */ |
||
1050 | public function skipNext($num = 1) |
||
1057 | |||
1058 | /** |
||
1059 | * Quick alias to stop the remaining callbacks/route methods from executing |
||
1060 | * |
||
1061 | * @throws DispatchHaltedException To halt/skip the current dispatch loop |
||
1062 | * @return void |
||
1063 | */ |
||
1064 | public function skipRemaining() |
||
1068 | |||
1069 | /** |
||
1070 | * Alias to set a response code, lock the response, and halt the route matching/dispatching |
||
1071 | * |
||
1072 | * @param int $code Optional HTTP status code to send |
||
1073 | * @throws DispatchHaltedException To halt/skip the current dispatch loop |
||
1074 | * @return void |
||
1075 | */ |
||
1076 | public function abort($code = null) |
||
1084 | |||
1085 | /** |
||
1086 | * OPTIONS alias for "respond()" |
||
1087 | * |
||
1088 | * @see Klein::respond() |
||
1089 | * @param string $path |
||
1090 | * @param callable $callback |
||
1091 | * @return Route |
||
1092 | */ |
||
1093 | View Code Duplication | public function options($path = '*', $callback = null) |
|
1103 | |||
1104 | /** |
||
1105 | * HEAD alias for "respond()" |
||
1106 | * |
||
1107 | * @see Klein::respond() |
||
1108 | * @param string $path |
||
1109 | * @param callable $callback |
||
1110 | * @return Route |
||
1111 | */ |
||
1112 | View Code Duplication | public function head($path = '*', $callback = null) |
|
1122 | |||
1123 | /** |
||
1124 | * GET alias for "respond()" |
||
1125 | * |
||
1126 | * @see Klein::respond() |
||
1127 | * @param string $path |
||
1128 | * @param callable $callback |
||
1129 | * @return Route |
||
1130 | */ |
||
1131 | View Code Duplication | public function get($path = '*', $callback = null) |
|
1141 | |||
1142 | /** |
||
1143 | * POST alias for "respond()" |
||
1144 | * |
||
1145 | * @see Klein::respond() |
||
1146 | * @param string $path |
||
1147 | * @param callable $callback |
||
1148 | * @return Route |
||
1149 | */ |
||
1150 | View Code Duplication | public function post($path = '*', $callback = null) |
|
1160 | |||
1161 | /** |
||
1162 | * PUT alias for "respond()" |
||
1163 | * |
||
1164 | * @see Klein::respond() |
||
1165 | * @param string $path |
||
1166 | * @param callable $callback |
||
1167 | * @return Route |
||
1168 | */ |
||
1169 | View Code Duplication | public function put($path = '*', $callback = null) |
|
1179 | |||
1180 | /** |
||
1181 | * DELETE alias for "respond()" |
||
1182 | * |
||
1183 | * @see Klein::respond() |
||
1184 | * @param string $path |
||
1185 | * @param callable $callback |
||
1186 | * @return Route |
||
1187 | */ |
||
1188 | View Code Duplication | public function delete($path = '*', $callback = null) |
|
1198 | |||
1199 | /** |
||
1200 | * PATCH alias for "respond()" |
||
1201 | * |
||
1202 | * PATCH was added to HTTP/1.1 in RFC5789 |
||
1203 | * |
||
1204 | * @link http://tools.ietf.org/html/rfc5789 |
||
1205 | * @see Klein::respond() |
||
1206 | * @param string $path |
||
1207 | * @param callable $callback |
||
1208 | * @return Route |
||
1209 | */ |
||
1210 | View Code Duplication | public function patch($path = '*', $callback = null) |
|
1220 | } |
||
1221 |