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 Content 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 Content, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class Content extends RestController |
||
31 | { |
||
32 | /** |
||
33 | * Loads a content info by remote ID. |
||
34 | * |
||
35 | * @throws \eZ\Publish\Core\REST\Server\Exceptions\BadRequestException |
||
36 | * |
||
37 | * @return \eZ\Publish\Core\REST\Server\Values\TemporaryRedirect |
||
38 | */ |
||
39 | View Code Duplication | public function redirectContent(Request $request) |
|
58 | |||
59 | /** |
||
60 | * Loads a content info, potentially with the current version embedded. |
||
61 | * |
||
62 | * @param mixed $contentId |
||
63 | * @param \Symfony\Component\HttpFoundation\Request $request |
||
64 | * |
||
65 | * @return \eZ\Publish\Core\REST\Server\Values\RestContent |
||
66 | */ |
||
67 | public function loadContent($contentId, Request $request) |
||
108 | |||
109 | /** |
||
110 | * Updates a content's metadata. |
||
111 | * |
||
112 | * @param mixed $contentId |
||
113 | * |
||
114 | * @return \eZ\Publish\Core\REST\Server\Values\RestContent |
||
115 | */ |
||
116 | public function updateContentMetadata($contentId, Request $request) |
||
158 | |||
159 | /** |
||
160 | * Loads a specific version of a given content object. |
||
161 | * |
||
162 | * @param mixed $contentId |
||
163 | * |
||
164 | * @return \eZ\Publish\Core\REST\Server\Values\TemporaryRedirect |
||
165 | */ |
||
166 | View Code Duplication | public function redirectCurrentVersion($contentId) |
|
180 | |||
181 | /** |
||
182 | * Loads a specific version of a given content object. |
||
183 | * |
||
184 | * @param mixed $contentId |
||
185 | * @param int $versionNumber |
||
186 | * |
||
187 | * @return \eZ\Publish\Core\REST\Server\Values\Version |
||
188 | */ |
||
189 | public function loadContentInVersion($contentId, $versionNumber, Request $request) |
||
221 | |||
222 | /** |
||
223 | * Creates a new content draft assigned to the authenticated user. |
||
224 | * If a different userId is given in the input it is assigned to the |
||
225 | * given user but this required special rights for the authenticated |
||
226 | * user (this is useful for content staging where the transfer process |
||
227 | * does not have to authenticate with the user which created the content |
||
228 | * object in the source server). The user has to publish the content if |
||
229 | * it should be visible. |
||
230 | * |
||
231 | * @param \Symfony\Component\HttpFoundation\Request $request |
||
232 | * |
||
233 | * @return \eZ\Publish\Core\REST\Server\Values\CreatedContent |
||
234 | */ |
||
235 | public function createContent(Request $request) |
||
241 | |||
242 | /** |
||
243 | * The content is deleted. If the content has locations (which is required in 4.x) |
||
244 | * on delete all locations assigned the content object are deleted via delete subtree. |
||
245 | * |
||
246 | * @param mixed $contentId |
||
247 | * |
||
248 | * @return \eZ\Publish\Core\REST\Server\Values\NoContent |
||
249 | */ |
||
250 | public function deleteContent($contentId) |
||
258 | |||
259 | /** |
||
260 | * Creates a new content object as copy under the given parent location given in the destination header. |
||
261 | * |
||
262 | * @param mixed $contentId |
||
263 | * |
||
264 | * @return \eZ\Publish\Core\REST\Server\Values\ResourceCreated |
||
265 | */ |
||
266 | public function copyContent($contentId, Request $request) |
||
283 | |||
284 | /** |
||
285 | * Deletes a translation from all the Versions of the given Content Object. |
||
286 | * |
||
287 | * If any non-published Version contains only the Translation to be deleted, that entire Version will be deleted |
||
288 | * |
||
289 | * @param int $contentId |
||
290 | * @param string $languageCode |
||
291 | * |
||
292 | * @return \eZ\Publish\Core\REST\Server\Values\NoContent |
||
293 | * |
||
294 | * @throws \Exception |
||
295 | */ |
||
296 | public function deleteContentTranslation($contentId, $languageCode) |
||
316 | |||
317 | /** |
||
318 | * Returns a list of all versions of the content. This method does not |
||
319 | * include fields and relations in the Version elements of the response. |
||
320 | * |
||
321 | * @param mixed $contentId |
||
322 | * |
||
323 | * @return \eZ\Publish\Core\REST\Server\Values\VersionList |
||
324 | */ |
||
325 | public function loadContentVersions($contentId, Request $request) |
||
334 | |||
335 | /** |
||
336 | * The version is deleted. |
||
337 | * |
||
338 | * @param mixed $contentId |
||
339 | * @param mixed $versionNumber |
||
340 | * |
||
341 | * @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException |
||
342 | * |
||
343 | * @return \eZ\Publish\Core\REST\Server\Values\NoContent |
||
344 | */ |
||
345 | View Code Duplication | public function deleteContentVersion($contentId, $versionNumber) |
|
362 | |||
363 | /** |
||
364 | * Remove the given Translation from the given Version Draft. |
||
365 | * |
||
366 | * @param int $contentId |
||
367 | * @param int $versionNumber |
||
368 | * @param string $languageCode |
||
369 | * |
||
370 | * @return \eZ\Publish\Core\REST\Server\Values\NoContent |
||
371 | * |
||
372 | * @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException |
||
373 | */ |
||
374 | public function deleteTranslationFromDraft($contentId, $versionNumber, $languageCode) |
||
387 | |||
388 | /** |
||
389 | * The system creates a new draft version as a copy from the given version. |
||
390 | * |
||
391 | * @param mixed $contentId |
||
392 | * @param mixed $versionNumber |
||
393 | * |
||
394 | * @return \eZ\Publish\Core\REST\Server\Values\CreatedVersion |
||
395 | */ |
||
396 | View Code Duplication | public function createDraftFromVersion($contentId, $versionNumber) |
|
415 | |||
416 | /** |
||
417 | * The system creates a new draft version as a copy from the current version. |
||
418 | * |
||
419 | * @param mixed $contentId |
||
420 | * |
||
421 | * @throws ForbiddenException if the current version is already a draft |
||
422 | * |
||
423 | * @return \eZ\Publish\Core\REST\Server\Values\CreatedVersion |
||
424 | */ |
||
425 | View Code Duplication | public function createDraftFromCurrentVersion($contentId) |
|
449 | |||
450 | /** |
||
451 | * A specific draft is updated. |
||
452 | * |
||
453 | * @param mixed $contentId |
||
454 | * @param mixed $versionNumber |
||
455 | * |
||
456 | * @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException |
||
457 | * @throws \eZ\Publish\Core\REST\Server\Exceptions\BadRequestException |
||
458 | * |
||
459 | * @return \eZ\Publish\Core\REST\Server\Values\Version |
||
460 | */ |
||
461 | public function updateVersion($contentId, $versionNumber, Request $request) |
||
518 | |||
519 | /** |
||
520 | * The content version is published. |
||
521 | * |
||
522 | * @param mixed $contentId |
||
523 | * @param mixed $versionNumber |
||
524 | * |
||
525 | * @throws ForbiddenException if version $versionNumber isn't a draft |
||
526 | * |
||
527 | * @return \eZ\Publish\Core\REST\Server\Values\NoContent |
||
528 | */ |
||
529 | View Code Duplication | public function publishVersion($contentId, $versionNumber) |
|
546 | |||
547 | /** |
||
548 | * Redirects to the relations of the current version. |
||
549 | * |
||
550 | * @param mixed $contentId |
||
551 | * |
||
552 | * @return \eZ\Publish\Core\REST\Server\Values\TemporaryRedirect |
||
553 | */ |
||
554 | View Code Duplication | public function redirectCurrentVersionRelations($contentId) |
|
568 | |||
569 | /** |
||
570 | * Loads the relations of the given version. |
||
571 | * |
||
572 | * @param mixed $contentId |
||
573 | * @param mixed $versionNumber |
||
574 | * |
||
575 | * @return \eZ\Publish\Core\REST\Server\Values\RelationList |
||
576 | */ |
||
577 | public function loadVersionRelations($contentId, $versionNumber, Request $request) |
||
609 | |||
610 | /** |
||
611 | * Loads a relation for the given content object and version. |
||
612 | * |
||
613 | * @param mixed $contentId |
||
614 | * @param int $versionNumber |
||
615 | * @param mixed $relationId |
||
616 | * |
||
617 | * @throws \eZ\Publish\Core\REST\Common\Exceptions\NotFoundException |
||
618 | * |
||
619 | * @return \eZ\Publish\Core\REST\Server\Values\RestRelation |
||
620 | */ |
||
621 | public function loadVersionRelation($contentId, $versionNumber, $relationId, Request $request) |
||
645 | |||
646 | /** |
||
647 | * Deletes a relation of the given draft. |
||
648 | * |
||
649 | * @param mixed $contentId |
||
650 | * @param int $versionNumber |
||
651 | * @param mixed $relationId |
||
652 | * |
||
653 | * @throws \eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException |
||
654 | * @throws \eZ\Publish\Core\REST\Common\Exceptions\NotFoundException |
||
655 | * |
||
656 | * @return \eZ\Publish\Core\REST\Server\Values\NoContent |
||
657 | */ |
||
658 | public function removeRelation($contentId, $versionNumber, $relationId, Request $request) |
||
684 | |||
685 | /** |
||
686 | * Creates a new relation of type COMMON for the given draft. |
||
687 | * |
||
688 | * @param mixed $contentId |
||
689 | * @param int $versionNumber |
||
690 | * |
||
691 | * @throws ForbiddenException if version $versionNumber isn't a draft |
||
692 | * @throws ForbiddenException if a relation to the same content already exists |
||
693 | * |
||
694 | * @return \eZ\Publish\Core\REST\Server\Values\CreatedRelation |
||
695 | */ |
||
696 | public function createRelation($contentId, $versionNumber, Request $request) |
||
732 | |||
733 | /** |
||
734 | * Creates and executes a content view. |
||
735 | * |
||
736 | * @deprecated Since platform 1.0. Forwards the request to the new /views location, but returns a 301. |
||
737 | * |
||
738 | * @return \eZ\Publish\Core\REST\Server\Values\RestExecutedView |
||
739 | */ |
||
740 | public function createView() |
||
750 | |||
751 | /** |
||
752 | * @param string $controller |
||
753 | * |
||
754 | * @return \Symfony\Component\HttpFoundation\Response |
||
755 | */ |
||
756 | protected function forward($controller) |
||
763 | |||
764 | /** |
||
765 | * @param \Symfony\Component\HttpFoundation\Request $request |
||
766 | * |
||
767 | * @return mixed |
||
768 | */ |
||
769 | protected function parseCreateContentRequest(Request $request) |
||
778 | |||
779 | /** |
||
780 | * @param \Symfony\Component\HttpFoundation\Request $request |
||
781 | * @param \eZ\Publish\Core\REST\Server\Values\RestContentCreateStruct $contentCreate |
||
782 | * |
||
783 | * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException |
||
784 | * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException |
||
785 | * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException |
||
786 | * |
||
787 | * @return \eZ\Publish\Core\REST\Server\Values\CreatedContent |
||
788 | */ |
||
789 | protected function doCreateContent(Request $request, RestContentCreateStruct $contentCreate) |
||
825 | } |
||
826 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.