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 SemRush 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 SemRush, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | class SemRush extends SEOstats |
||
20 | { |
||
21 | 1 | public static function getDBs() |
|
37 | |||
38 | 1 | public static function getParams() |
|
69 | |||
70 | /** |
||
71 | * Returns the SEMRush main report data. |
||
72 | * (Only main report is public available.) |
||
73 | * |
||
74 | * @access public |
||
75 | * @param url string Domain name only, eg. "ebay.com" (/wo quotes). |
||
76 | * @param db string Optional: The database to use. Valid values are: |
||
77 | * au, br, ca, de, es, fr, it, ru, uk, us, us.bing (us is default) |
||
78 | * @return array Returns an array containing the main report data. |
||
79 | * @link http://www.semrush.com/api.html |
||
80 | */ |
||
81 | 6 | public static function getDomainRank($url = false, $db = false) |
|
82 | { |
||
83 | 6 | $data = self::getBackendData($url, $db, 'domain_rank'); |
|
84 | |||
85 | return is_array($data) ? $data['rank']['data'][0] : $data; |
||
86 | } |
||
87 | |||
88 | 6 | public static function getDomainRankHistory($url = false, $db = false) |
|
89 | { |
||
90 | 6 | $data = self::getBackendData($url, $db, 'domain_rank_history'); |
|
91 | |||
92 | return is_array($data) ? $data['rank_history'] : $data; |
||
93 | } |
||
94 | |||
95 | 4 | public static function getOrganicKeywords($url = false, $db = false) |
|
99 | |||
100 | 4 | public static function getCompetitors($url = false, $db = false) |
|
104 | |||
105 | 10 | public static function getDomainGraph($reportType = 1, $url = false, $db = false, $w = 400, $h = 300, $lc = 'e43011', $dc = 'e43011', $lang = 'en', $html = true) |
|
106 | { |
||
107 | 10 | $domain = static::getDomainFromUrl($url); |
|
108 | $database = static::getValidDatabase($db); |
||
109 | |||
110 | static::guardValidArgsForGetDomainGraph($reportType, $w, $h, $lang); |
||
111 | |||
112 | $imgUrl = sprintf(Config\Services::SEMRUSH_GRAPH_URL, |
||
113 | $domain, $database, $reportType, $w, $h, $lc, $dc, $lang); |
||
114 | |||
115 | if (! $html) { |
||
116 | return $imgUrl; |
||
117 | } else { |
||
118 | $imgTag = '<img src="%s" width="%s" height="%s" alt="SEMRush Domain Trend Graph for %s"/>'; |
||
119 | return sprintf($imgTag, $imgUrl, $w, $h, $domain); |
||
120 | } |
||
121 | } |
||
122 | |||
123 | 1 | protected static function getApiData($url) |
|
128 | |||
129 | protected static function getSemRushDatabase($db) |
||
130 | { |
||
131 | return false !== $db |
||
132 | ? $db |
||
133 | : Config\DefaultSettings::SEMRUSH_DB; |
||
134 | } |
||
135 | |||
136 | 36 | protected static function guardDomainIsValid($domain) |
|
137 | { |
||
138 | 36 | if (false == $domain) { |
|
139 | 36 | self::exc('Invalid domain name.'); |
|
140 | } |
||
141 | } |
||
142 | |||
143 | protected static function guardDatabaseIsValid($database) |
||
144 | { |
||
145 | if (false === $database) { |
||
146 | self::exc('db'); |
||
147 | } |
||
148 | } |
||
149 | |||
150 | protected static function guardValidArgsForGetDomainGraph($reportType, $width, $height, $lang) |
||
151 | { |
||
152 | if ($reportType > 5 || $reportType < 1) { |
||
153 | self::exc('Report type must be between 1 (one) and 5 (five).'); |
||
154 | } |
||
155 | |||
156 | if ($width > 400 || $width < 200) { |
||
157 | self::exc('Image width must be between 200 and 400 px.'); |
||
158 | } |
||
159 | |||
160 | if ($height > 300 || $height < 150) { |
||
161 | self::exc('Image height must be between 150 and 300 px.'); |
||
162 | } |
||
163 | |||
164 | if (strlen($lang) != 2) { |
||
165 | self::exc('You must specify a valid language code.'); |
||
166 | } |
||
167 | } |
||
168 | |||
169 | 12 | protected static function getBackendData($url, $db, $reportType) |
|
170 | { |
||
171 | 12 | $db = false !== $db ? $db : Config\DefaultSettings::SEMRUSH_DB; |
|
172 | 12 | $dataUrl = self::getBackendUrl($url, $db, $reportType); |
|
173 | $data = self::getApiData($dataUrl); |
||
174 | |||
175 | if (!is_array($data)) { |
||
176 | $data = self::getApiData(str_replace('.backend.', '.api.', $dataUrl)); |
||
177 | if (!is_array($data)) { |
||
178 | return parent::noDataDefaultValue(); |
||
|
|||
179 | } |
||
180 | } |
||
181 | |||
182 | return $data; |
||
183 | } |
||
184 | |||
185 | 15 | View Code Duplication | protected static function getBackendUrl($url, $db, $reportType) |
186 | { |
||
187 | 15 | $domain = static::getDomainFromUrl($url); |
|
188 | $database = static::getValidDatabase($db); |
||
189 | |||
190 | $backendUrl = Config\Services::SEMRUSH_BE_URL; |
||
191 | return sprintf($backendUrl, $database, $reportType, $domain); |
||
192 | } |
||
193 | |||
194 | 11 | View Code Duplication | protected static function getWidgetUrl($url, $db, $reportType) |
195 | { |
||
196 | 11 | $domain = static::getDomainFromUrl($url); |
|
197 | $database = static::getValidDatabase($db); |
||
198 | |||
199 | $widgetUrl = Config\Services::SEMRUSH_WIDGET_URL; |
||
200 | return sprintf($widgetUrl, $reportType, $database, $domain); |
||
201 | } |
||
202 | |||
203 | 8 | protected static function getWidgetData($url, $db, $reportType, $valueKey) |
|
204 | { |
||
205 | 8 | $db = false !== $db ? $db : Config\DefaultSettings::SEMRUSH_DB; |
|
206 | 8 | $dataUrl = self::getWidgetUrl($url, $db, $reportType); |
|
207 | $data = self::getApiData($dataUrl); |
||
208 | |||
209 | return !is_array($data) ? parent::noDataDefaultValue() : $data[ $valueKey ]; |
||
210 | } |
||
211 | |||
212 | protected static function checkDatabase($db) |
||
213 | { |
||
214 | return !in_array($db, self::getDBs()) ? false : $db; |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * |
||
219 | * @throws E |
||
220 | */ |
||
221 | 36 | View Code Duplication | protected static function exc($err) |
228 | |||
229 | 36 | protected static function getDomainFromUrl($url) |
|
230 | { |
||
231 | 36 | $url = parent::getUrl($url); |
|
232 | 36 | $domain = Helper\Url::parseHost($url); |
|
233 | 36 | static::guardDomainIsValid($domain); |
|
234 | |||
235 | return $domain; |
||
236 | } |
||
237 | |||
238 | protected static function getValidDatabase($db) |
||
239 | { |
||
240 | $db = self::getSemRushDatabase($db); |
||
241 | $database = self::checkDatabase($db); |
||
246 | } |
||
247 |
This check looks for a call to a parent method whose name is different than the method from which it is called.
Consider the following code:
The
getFirstName()
method in theSon
calls the wrong method in the parent class.