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 Swagger 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 Swagger, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
28 | class Swagger extends SwaggerObject |
||
29 | { |
||
30 | |||
31 | /** |
||
32 | * @param Application $app |
||
33 | * @param ControllerContainer[] $controllers |
||
34 | */ |
||
35 | 1 | public function appendControllers(Application $app, $controllers) |
|
41 | |||
42 | /** |
||
43 | * @param Application $app |
||
44 | * @param ControllerContainer $controller |
||
45 | */ |
||
46 | 1 | public function appendController(Application $app, ControllerContainer $controller) |
|
47 | { |
||
48 | //tags |
||
49 | 1 | $tag = new TagObject(); |
|
50 | 1 | $tag->name = $controller->getSummary(); |
|
51 | 1 | $tag->description = $controller->getDescription(); |
|
52 | 1 | $this->tags[] = $tag; |
|
53 | |||
54 | 1 | foreach ($controller->getRoutes() as $action => $route) { |
|
55 | 1 | $op = new OperationObject(); |
|
56 | 1 | $op->tags = [$controller->getSummary()]; |
|
57 | 1 | $op->summary = $route->getSummary(); |
|
58 | 1 | $op->description = $route->getDescription(); |
|
59 | |||
60 | 1 | $op->parameters = $this->getParamsSchema($app, $controller, $action, $route); |
|
61 | 1 | if($this->hasFileParam($route)){ |
|
62 | 1 | $op->consumes = ['multipart/form-data']; |
|
63 | 1 | } |
|
64 | |||
65 | 1 | if ($returnSchema = $this->getReturnSchema($app, $controller, $action, $route)) { |
|
66 | 1 | $op->responses['200'] = $returnSchema; |
|
67 | 1 | } |
|
68 | 1 | $op->responses += $this->getExceptionsSchema($app, $controller, $action, $route); |
|
69 | 1 | $uri = $app->getFullUri($route->getUri()); |
|
70 | 1 | if (!isset($this->paths[$uri])) { |
|
71 | 1 | $this->paths[$uri] = []; |
|
72 | 1 | } |
|
73 | 1 | $method = strtolower($route->getMethod()); |
|
74 | 1 | $this->paths[$uri][$method] = $op; |
|
75 | 1 | } |
|
76 | 1 | } |
|
77 | |||
78 | /** |
||
79 | * @return string |
||
80 | */ |
||
81 | public function toJson() |
||
82 | { |
||
83 | $json = $this->toArray(); |
||
84 | return json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * @return array |
||
89 | */ |
||
90 | 1 | public function toArray() |
|
94 | |||
95 | /** |
||
96 | * @param $object |
||
97 | * @return array |
||
98 | */ |
||
99 | 1 | static public function objectToArray($object) |
|
117 | |||
118 | /** |
||
119 | * @param Application $app |
||
120 | * @param ControllerContainer $controller |
||
121 | * @param $action |
||
122 | * @param Route $route |
||
123 | * @return array |
||
124 | */ |
||
125 | 1 | public function getExceptionsSchema(Application $app, |
|
166 | |||
167 | /** |
||
168 | * @param Application $app |
||
169 | * @param ControllerContainer $controller |
||
170 | * @param $action |
||
171 | * @param Route $route |
||
172 | * @return null|ResponseObject |
||
173 | */ |
||
174 | 1 | public function getReturnSchema(Application $app, |
|
208 | |||
209 | /** |
||
210 | * @param $content |
||
211 | */ |
||
212 | public function makeExample($content) |
||
227 | /** |
||
228 | * @param Application $app |
||
229 | * @param ControllerContainer $controller |
||
230 | * @param $action |
||
231 | * @param Route $route |
||
232 | * @param array $arr |
||
233 | * @param string $suffix |
||
234 | * @return RefSchemaObject |
||
235 | */ |
||
236 | 1 | public function makeTempSchema(Application $app, |
|
237 | ControllerContainer $controller, |
||
238 | $action, |
||
239 | Route $route, |
||
240 | array $arr, $suffix) |
||
241 | { |
||
242 | 1 | $className = self::getShortClassName($controller->getClassName()); |
|
243 | 1 | $name = $className . ucfirst($action) . $suffix; |
|
244 | |||
245 | 1 | $schema = new SimpleModelSchemaObject(); |
|
246 | |||
247 | 1 | foreach ($arr as $k => $v) { |
|
248 | 1 | if (is_array($v)) { |
|
249 | 1 | $schema->properties[$k] = $this->makeTempSchema($app, $controller, $action, $route, $v, $suffix); |
|
250 | 1 | } elseif ($v instanceof ReturnMeta) { |
|
251 | 1 | $sub = $this->getAnySchema($app, $controller, $action, $route, $v->container); |
|
252 | 1 | $sub->description = $v->description; |
|
253 | 1 | $schema->properties[$k] = $sub; |
|
254 | 1 | } elseif ($v instanceof ParamMeta) { |
|
255 | 1 | View Code Duplication | if ($v->container instanceof ArrayContainer) { |
256 | 1 | $sub = $this->getArraySchema($app, $controller, $action, $route, $v->container); |
|
257 | //TODO array for validation |
||
258 | 1 | } elseif ($v->container instanceof EntityContainer) { |
|
259 | 1 | $sub = $this->getRefSchema($app, $controller, $action, $route, $v->container); |
|
260 | //TODO array for validation |
||
261 | 1 | } else { |
|
262 | 1 | $sub = new PrimitiveSchemaObject(); |
|
263 | 1 | $sub->type = self::mapType($v->type); |
|
264 | 1 | self::mapValidation($v->validation, $sub); |
|
265 | 1 | unset($sub->required); |
|
266 | } |
||
267 | 1 | $sub->description = $v->description; |
|
268 | 1 | $sub->default = $v->default; |
|
269 | 1 | if (!$v->isOptional) { |
|
270 | 1 | $schema->required[] = $k; |
|
271 | 1 | } |
|
272 | 1 | $schema->properties[$k] = $sub; |
|
273 | 1 | } else { |
|
274 | //TODO how to do? |
||
275 | } |
||
276 | 1 | } |
|
277 | 1 | $unused = $name; |
|
278 | 1 | $tempId = 0; |
|
279 | 1 | while (isset($this->definitions[$unused])) { |
|
280 | 1 | $unused = $name . $tempId; |
|
281 | 1 | $tempId++; |
|
282 | 1 | } |
|
283 | 1 | $this->definitions[$unused] = $schema; |
|
284 | 1 | return new RefSchemaObject("#/definitions/$unused"); |
|
285 | } |
||
286 | |||
287 | /** |
||
288 | * @param Application $app |
||
289 | * @param ControllerContainer $controller |
||
290 | * @param $action |
||
291 | * @param Route $route |
||
292 | * @param EntityContainer $container |
||
293 | * @return RefSchemaObject |
||
294 | */ |
||
295 | 1 | public function getRefSchema(Application $app, |
|
307 | |||
308 | 1 | public function getParamsSchema(Application $app, |
|
309 | ControllerContainer $controller, |
||
310 | $action, |
||
311 | Route $route) |
||
312 | { |
||
313 | 1 | $params = $route->getRequestHandler()->getParamMetas(); |
|
314 | 1 | $parameters = []; |
|
315 | 1 | $body = []; |
|
316 | 1 | $in = 'query'; |
|
317 | |||
318 | 1 | $bodyType = 'body'; // 当有文件上传时, 必须是formData方式 |
|
319 | 1 | if($this->hasFileParam($route)){ |
|
320 | 1 | $bodyType = 'formData'; |
|
321 | 1 | } |
|
322 | |||
323 | 1 | foreach ($params as $name => $param) { |
|
324 | 1 | $isFile = false; |
|
325 | 1 | if ($param->isPassedByReference) { |
|
326 | 1 | continue; |
|
327 | } |
||
328 | 1 | if ($param->source == 'request.request') { |
|
329 | $in = $bodyType; |
||
330 | $name = ''; |
||
331 | 1 | View Code Duplication | } elseif (strpos($param->source, 'request.request.') === 0 |
332 | 1 | || $param->source == 'request.request' |
|
333 | 1 | ) { |
|
334 | 1 | $in = $bodyType; |
|
335 | 1 | $name = substr($param->source, strlen('request.request.')); |
|
336 | 1 | } elseif (strpos($param->source, 'request.query.') === 0) { |
|
337 | 1 | $in = 'query'; |
|
338 | 1 | $name = substr($param->source, strlen('request.query.')); |
|
339 | 1 | } elseif (strpos($param->source, 'request.cookies.') === 0) { |
|
340 | 1 | $in = 'cookie'; |
|
341 | 1 | $name = substr($param->source, strlen('request.cookies.')); |
|
342 | 1 | } elseif (strpos($param->source, 'request.headers.') === 0) { |
|
343 | 1 | $in = 'header'; |
|
344 | 1 | $name = substr($param->source, strlen('request.headers.')); |
|
345 | 1 | View Code Duplication | } elseif (strpos($param->source, 'request.files.') === 0) { |
346 | 1 | $isFile = true; |
|
347 | 1 | $in = $bodyType; |
|
348 | 1 | $name = substr($param->source, strlen('request.files.')); |
|
349 | 1 | } elseif (strpos($param->source, 'request.') === 0) { |
|
350 | 1 | $name = substr($param->source, strlen('request.')); |
|
351 | 1 | if ($route->hasPathParam($param->name)) { |
|
352 | 1 | $in = 'path'; |
|
353 | 1 | } elseif ($route->getMethod() == 'POST' |
|
354 | 1 | || $route->getMethod() == 'PUT' |
|
355 | 1 | || $route->getMethod() == 'PATCH' |
|
356 | 1 | ) { |
|
357 | 1 | $in = $bodyType; |
|
358 | 1 | } else { |
|
359 | 1 | $in = 'query'; |
|
360 | } |
||
361 | 1 | } |
|
362 | 1 | if ($in != 'body') { |
|
363 | 1 | View Code Duplication | if ($param->container instanceof ArrayContainer) { |
364 | 1 | $paramSchema = $this->getArraySchema($app, $controller, $action, $route, $param->container); |
|
365 | //TODO array for validation |
||
366 | 1 | } elseif ($param->container instanceof EntityContainer) { |
|
367 | 1 | $paramSchema = $this->getRefSchema($app, $controller, $action, $route, $param->container); |
|
368 | //TODO array for validation |
||
369 | 1 | } else { |
|
370 | 1 | $paramSchema = new PrimitiveSchemaObject(); |
|
371 | 1 | if($isFile){ |
|
372 | 1 | $paramSchema->type = 'file'; |
|
373 | 1 | }else{ |
|
374 | 1 | $paramSchema->type = self::mapType($param->type); |
|
375 | 1 | self::mapValidation($param->validation, $paramSchema); |
|
376 | } |
||
377 | |||
378 | } |
||
379 | 1 | $paramSchema->in = $in; |
|
380 | 1 | $paramSchema->name = $name; |
|
381 | 1 | $paramSchema->description = $param->description; |
|
382 | 1 | $paramSchema->default = $param->default; |
|
383 | 1 | $paramSchema->required = !$param->isOptional; |
|
384 | 1 | $parameters[] = $paramSchema; |
|
385 | 1 | } else { |
|
386 | 1 | if (!$name) { |
|
387 | $body = $param; |
||
388 | } else { |
||
389 | 1 | ArrayHelper::set($body, $name, $param); |
|
390 | } |
||
391 | |||
392 | } |
||
393 | 1 | } |
|
394 | 1 | if ($body && $bodyType == 'body') { |
|
395 | |||
396 | 1 | $paramSchema = new BodyParameterObject(); |
|
397 | 1 | $paramSchema->name = 'body'; |
|
398 | 1 | $paramSchema->in = 'body'; |
|
399 | 1 | if (is_array($body)) { |
|
400 | 1 | $paramSchema->schema = $this->makeTempSchema($app, $controller, $action, $route, $body, 'Req'); |
|
401 | 1 | } else { |
|
402 | $paramSchema->schema = $this->getAnySchema($app, $controller, $action, $route, $body->container); |
||
403 | } |
||
404 | |||
405 | 1 | $parameters[] = $paramSchema; |
|
406 | 1 | } |
|
407 | |||
408 | 1 | return $parameters; |
|
409 | } |
||
410 | |||
411 | /** |
||
412 | * @param Application $app |
||
413 | * @param ControllerContainer $controller |
||
414 | * @param $action |
||
415 | * @param Route $route |
||
416 | * @param TypeContainerInterface $container |
||
417 | * @return ArraySchemaObject|PrimitiveSchemaObject|RefSchemaObject |
||
418 | */ |
||
419 | 1 | public function getAnySchema(Application $app, ControllerContainer $controller, $action, Route $route, $container) |
|
420 | { |
||
421 | 1 | if ($container instanceof EntityContainer) { |
|
422 | 1 | $schema = $this->getRefSchema($app, $controller, $action, $route, $container); |
|
423 | 1 | } elseif ($container instanceof ArrayContainer) { |
|
424 | 1 | $schema = $this->getArraySchema($app, $controller, $action, $route, $container); |
|
425 | 1 | } elseif ($container instanceof ScalarTypeContainer) { |
|
426 | 1 | $schema = new PrimitiveSchemaObject(); |
|
427 | 1 | $schema->type = self::mapType($container->getType()); |
|
428 | 1 | } elseif($container == null){ |
|
429 | $schema = null ;//new PrimitiveSchemaObject(); |
||
430 | //$schema->type = null; |
||
431 | }else { |
||
432 | 1 | $schema = new PrimitiveSchemaObject(); |
|
433 | //$schema->type = 'mixed'; |
||
434 | } |
||
435 | 1 | return $schema; |
|
436 | } |
||
437 | |||
438 | /** |
||
439 | * @param Application $app |
||
440 | * @param ControllerContainer $controller |
||
441 | * @param $action |
||
442 | * @param Route $route |
||
443 | * @param ArrayContainer $container |
||
444 | * @return ArraySchemaObject |
||
445 | */ |
||
446 | 1 | public function getArraySchema(Application $app, |
|
447 | ControllerContainer $controller, |
||
448 | $action, |
||
449 | Route $route, |
||
450 | ArrayContainer $container) |
||
451 | { |
||
452 | 1 | $schema = new ArraySchemaObject(); |
|
453 | 1 | $itemContainer = $container->getContainer(); |
|
454 | 1 | if ($itemContainer instanceof EntityContainer) { |
|
455 | 1 | $itemSchema = $this->getRefSchema($app, $controller, $action, $route, $itemContainer); |
|
456 | 1 | } elseif ($itemContainer instanceof ArrayContainer) { |
|
457 | $itemSchema = $this->getArraySchema($app, $controller, $action, $route, $itemContainer); |
||
458 | 1 | } elseif ($itemContainer instanceof ScalarTypeContainer) { |
|
459 | 1 | $itemSchema = new PrimitiveSchemaObject(); |
|
460 | 1 | $itemSchema->type = self::mapType($itemContainer->getType()); |
|
461 | 1 | } else { |
|
462 | $itemSchema = new PrimitiveSchemaObject(); |
||
463 | //$itemSchema->type = 'mixed'; |
||
464 | } |
||
465 | 1 | $schema->items = $itemSchema; |
|
466 | 1 | return $schema; |
|
467 | } |
||
468 | |||
469 | 1 | public function getObjectSchema(Application $app, |
|
499 | |||
500 | 1 | public function hasFileParam(Route $route) |
|
510 | /** |
||
511 | * @param string $v |
||
512 | * @param PrimitiveSchemaObject $schemaObject |
||
513 | * @return PrimitiveSchemaObject |
||
514 | */ |
||
515 | 1 | static public function mapValidation($v, PrimitiveSchemaObject $schemaObject) |
|
549 | |||
550 | /** |
||
551 | * @param string $type |
||
552 | * @return string |
||
553 | */ |
||
554 | 1 | static public function mapType($type) |
|
568 | |||
569 | /** |
||
570 | * @param $className |
||
571 | * @return string |
||
572 | */ |
||
573 | 1 | static public function getShortClassName($className) |
|
579 | |||
580 | static public function implode($glue , array $pieces ) |
||
585 | } |