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 Subsite 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 Subsite, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
8 | class Subsite extends DataObject |
||
9 | { |
||
10 | /** |
||
11 | * @var $use_session_subsiteid Boolean Set to TRUE when using the CMS and FALSE |
||
12 | * when browsing the frontend of a website. |
||
13 | * |
||
14 | * @todo Remove flag once the Subsite CMS works without session state, |
||
15 | * similarly to the Translatable module. |
||
16 | */ |
||
17 | public static $use_session_subsiteid = false; |
||
18 | |||
19 | /** |
||
20 | * @var boolean $disable_subsite_filter If enabled, bypasses the query decoration |
||
21 | * to limit DataObject::get*() calls to a specific subsite. Useful for debugging. |
||
22 | */ |
||
23 | public static $disable_subsite_filter = false; |
||
24 | |||
25 | /** |
||
26 | * Allows you to force a specific subsite ID, or comma separated list of IDs. |
||
27 | * Only works for reading. An object cannot be written to more than 1 subsite. |
||
28 | */ |
||
29 | public static $force_subsite = null; |
||
30 | |||
31 | /** |
||
32 | * |
||
33 | * @var boolean |
||
34 | */ |
||
35 | public static $write_hostmap = true; |
||
36 | |||
37 | /** |
||
38 | * Memory cache of accessible sites |
||
39 | * |
||
40 | * @array |
||
41 | */ |
||
42 | private static $_cache_accessible_sites = array(); |
||
43 | |||
44 | /** |
||
45 | * Memory cache of subsite id for domains |
||
46 | * |
||
47 | * @var array |
||
48 | */ |
||
49 | private static $_cache_subsite_for_domain = array(); |
||
50 | |||
51 | /** |
||
52 | * @var array $allowed_themes Numeric array of all themes which are allowed to be selected for all subsites. |
||
53 | * Corresponds to subfolder names within the /themes folder. By default, all themes contained in this folder |
||
54 | * are listed. |
||
55 | */ |
||
56 | private static $allowed_themes = array(); |
||
57 | |||
58 | /** |
||
59 | * @var Boolean If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same. |
||
60 | * Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com') |
||
61 | * in both TRUE or FALSE setting. |
||
62 | */ |
||
63 | public static $strict_subdomain_matching = false; |
||
64 | |||
65 | /** |
||
66 | * @var boolean Respects the IsPublic flag when retrieving subsites |
||
67 | */ |
||
68 | public static $check_is_public = true; |
||
69 | |||
70 | /** |
||
71 | * Set allowed themes |
||
72 | * |
||
73 | * @param array $themes - Numeric array of all themes which are allowed to be selected for all subsites. |
||
74 | */ |
||
75 | public static function set_allowed_themes($themes) |
||
79 | |||
80 | /** |
||
81 | * Gets the subsite currently set in the session. |
||
82 | * |
||
83 | * @uses ControllerSubsites->controllerAugmentInit() |
||
84 | * @return Subsite |
||
85 | */ |
||
86 | public static function currentSubsite() |
||
91 | |||
92 | /** |
||
93 | * This function gets the current subsite ID from the session. It used in the backend so Ajax requests |
||
94 | * use the correct subsite. The frontend handles subsites differently. It calls getSubsiteIDForDomain |
||
95 | * directly from ModelAsController::getNestedController. Only gets Subsite instances which have their |
||
96 | * {@link IsPublic} flag set to TRUE. |
||
97 | * |
||
98 | * You can simulate subsite access without creating virtual hosts by appending ?SubsiteID=<ID> to the request. |
||
99 | * |
||
100 | * @todo Pass $request object from controller so we don't have to rely on $_GET |
||
101 | * |
||
102 | * @param boolean $cache |
||
|
|||
103 | * @return int ID of the current subsite instance |
||
104 | */ |
||
105 | public static function currentSubsiteID() |
||
121 | |||
122 | /** |
||
123 | * Switch to another subsite through storing the subsite identifier in the current PHP session. |
||
124 | * Only takes effect when {@link Subsite::$use_session_subsiteid} is set to TRUE. |
||
125 | * |
||
126 | * @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself |
||
127 | */ |
||
128 | public static function changeSubsite($subsite) |
||
154 | |||
155 | /** |
||
156 | * Get a matching subsite for the given host, or for the current HTTP_HOST. |
||
157 | * Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string, |
||
158 | * for example matching all subdomains on *.example.com with one subsite, |
||
159 | * and all subdomains on *.example.org on another. |
||
160 | * |
||
161 | * @param $host The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used. |
||
162 | * @return int Subsite ID |
||
163 | */ |
||
164 | public static function getSubsiteIDForDomain($host = null, $checkPermissions = true) |
||
216 | |||
217 | /** |
||
218 | * |
||
219 | * @param string $className |
||
220 | * @param string $filter |
||
221 | * @param string $sort |
||
222 | * @param string $join |
||
223 | * @param string $limit |
||
224 | * @return DataList |
||
225 | */ |
||
226 | public static function get_from_all_subsites($className, $filter = "", $sort = "", $join = "", $limit = "") |
||
232 | |||
233 | /** |
||
234 | * Disable the sub-site filtering; queries will select from all subsites |
||
235 | */ |
||
236 | public static function disable_subsite_filter($disabled = true) |
||
240 | |||
241 | /** |
||
242 | * Flush caches on database reset |
||
243 | */ |
||
244 | public static function on_db_reset() |
||
249 | |||
250 | /** |
||
251 | * Return all subsites, regardless of permissions (augmented with main site). |
||
252 | * |
||
253 | * @return SS_List List of {@link Subsite} objects (DataList or ArrayList). |
||
254 | */ |
||
255 | public static function all_sites($includeMainSite = true, $mainSiteTitle = "Main site") |
||
271 | |||
272 | /* |
||
273 | * Returns an ArrayList of the subsites accessible to the current user. |
||
274 | * It's enough for any section to be accessible for the site to be included. |
||
275 | * |
||
276 | * @return ArrayList of {@link Subsite} instances. |
||
277 | */ |
||
278 | public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) |
||
312 | |||
313 | /** |
||
314 | * Return the subsites that the current user can access by given permission. |
||
315 | * Sites will only be included if they have a Title. |
||
316 | * |
||
317 | * @param $permCode array|string Either a single permission code or an array of permission codes. |
||
318 | * @param $includeMainSite If true, the main site will be included if appropriate. |
||
319 | * @param $mainSiteTitle The label to give to the main site |
||
320 | * @param $member |
||
321 | * @return DataList of {@link Subsite} instances |
||
322 | */ |
||
323 | public static function accessible_sites($permCode, $includeMainSite = true, $mainSiteTitle = "Main site", $member = null) |
||
401 | |||
402 | /** |
||
403 | * Write a host->domain map to subsites/host-map.php |
||
404 | * |
||
405 | * This is used primarily when using subsites in conjunction with StaticPublisher |
||
406 | * |
||
407 | * @param string $file - filepath of the host map to be written |
||
408 | * @return void |
||
409 | */ |
||
410 | public static function writeHostMap($file = null) |
||
449 | |||
450 | /** |
||
451 | * Checks if a member can be granted certain permissions, regardless of the subsite context. |
||
452 | * Similar logic to {@link Permission::checkMember()}, but only returns TRUE |
||
453 | * if the member is part of a group with the "AccessAllSubsites" flag set. |
||
454 | * If more than one permission is passed to the method, at least one of them must |
||
455 | * be granted for if to return TRUE. |
||
456 | * |
||
457 | * @todo Allow permission inheritance through group hierarchy. |
||
458 | * |
||
459 | * @param Member Member to check against. Defaults to currently logged in member |
||
460 | * @param Array Permission code strings. Defaults to "ADMIN". |
||
461 | * @return boolean |
||
462 | */ |
||
463 | public static function hasMainSitePermission($member = null, $permissionCodes = array('ADMIN')) |
||
511 | |||
512 | /** |
||
513 | * |
||
514 | * @var array |
||
515 | */ |
||
516 | private static $db = array( |
||
517 | 'Title' => 'Varchar(255)', |
||
518 | 'RedirectURL' => 'Varchar(255)', |
||
519 | 'DefaultSite' => 'Boolean', |
||
520 | 'Theme' => 'Varchar', |
||
521 | 'Language' => 'Varchar(6)', |
||
522 | |||
523 | // Used to hide unfinished/private subsites from public view. |
||
524 | // If unset, will default to true |
||
525 | 'IsPublic' => 'Boolean', |
||
526 | |||
527 | // Comma-separated list of disallowed page types |
||
528 | 'PageTypeBlacklist' => 'Text', |
||
529 | ); |
||
530 | |||
531 | /** |
||
532 | * |
||
533 | * @var array |
||
534 | */ |
||
535 | private static $has_many = array( |
||
536 | 'Domains' => 'SubsiteDomain', |
||
537 | ); |
||
538 | |||
539 | /** |
||
540 | * |
||
541 | * @var array |
||
542 | */ |
||
543 | private static $belongs_many_many = array( |
||
544 | "Groups" => "Group", |
||
545 | ); |
||
546 | |||
547 | /** |
||
548 | * |
||
549 | * @var array |
||
550 | */ |
||
551 | private static $defaults = array( |
||
552 | 'IsPublic' => 1 |
||
553 | ); |
||
554 | |||
555 | /** |
||
556 | * |
||
557 | * @var array |
||
558 | */ |
||
559 | private static $searchable_fields = array( |
||
560 | 'Title', |
||
561 | 'Domains.Domain', |
||
562 | 'IsPublic', |
||
563 | ); |
||
564 | |||
565 | /** |
||
566 | * |
||
567 | * @var string |
||
568 | */ |
||
569 | private static $default_sort = "\"Title\" ASC"; |
||
570 | |||
571 | /** |
||
572 | * @todo Possible security issue, don't grant edit permissions to everybody. |
||
573 | * @return boolean |
||
574 | */ |
||
575 | public function canEdit($member = false) |
||
579 | |||
580 | /** |
||
581 | * Show the configuration fields for each subsite |
||
582 | * |
||
583 | * @return FieldList |
||
584 | */ |
||
585 | public function getCMSFields() |
||
657 | |||
658 | /** |
||
659 | * |
||
660 | * @param boolean $includerelations |
||
661 | * @return array |
||
662 | */ |
||
663 | public function fieldLabels($includerelations = true) |
||
678 | |||
679 | /** |
||
680 | * |
||
681 | * @return array |
||
682 | */ |
||
683 | public function summaryFields() |
||
691 | |||
692 | /** |
||
693 | * Return the themes that can be used with this subsite, as an array of themecode => description |
||
694 | * |
||
695 | * @return array |
||
696 | */ |
||
697 | public function allowedThemes() |
||
716 | |||
717 | /** |
||
718 | * @return string Current locale of the subsite |
||
719 | */ |
||
720 | public function getLanguage() |
||
728 | |||
729 | /** |
||
730 | * |
||
731 | * @return ValidationResult |
||
732 | */ |
||
733 | public function validate() |
||
741 | |||
742 | /** |
||
743 | * Whenever a Subsite is written, rewrite the hostmap |
||
744 | * |
||
745 | * @return void |
||
746 | */ |
||
747 | public function onAfterWrite() |
||
752 | |||
753 | /** |
||
754 | * Return the primary domain of this site. Tries to "normalize" the domain name, |
||
755 | * by replacing potential wildcards. |
||
756 | * |
||
757 | * @return string The full domain name of this subsite (without protocol prefix) |
||
758 | */ |
||
759 | public function domain() |
||
782 | |||
783 | /** |
||
784 | * |
||
785 | * @return string - The full domain name of this subsite (without protocol prefix) |
||
786 | */ |
||
787 | public function getPrimaryDomain() |
||
791 | |||
792 | /** |
||
793 | * |
||
794 | * @return string |
||
795 | */ |
||
796 | public function absoluteBaseURL() |
||
800 | |||
801 | /** |
||
802 | * @todo getClassName is redundant, already stored as a database field? |
||
803 | */ |
||
804 | public function getClassName() |
||
808 | |||
809 | /** |
||
810 | * Javascript admin action to duplicate this subsite |
||
811 | * |
||
812 | * @return string - javascript |
||
813 | */ |
||
814 | public function adminDuplicate() |
||
828 | |||
829 | /** |
||
830 | * Make this subsite the current one |
||
831 | */ |
||
832 | public function activate() |
||
836 | |||
837 | /** |
||
838 | * |
||
839 | * @param array $permissionCodes |
||
840 | * @return DataList |
||
841 | */ |
||
842 | public function getMembersByPermission($permissionCodes = array('ADMIN')) |
||
860 | |||
861 | /** |
||
862 | * Duplicate this subsite |
||
863 | */ |
||
864 | public function duplicate($doWrite = true) |
||
902 | } |
||
903 |
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.
Consider the following example. The parameter
$italy
is not defined by the methodfinale(...)
.The most likely cause is that the parameter was removed, but the annotation was not.