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 TimeField 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 TimeField, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
20 | class TimeField extends TextField |
||
21 | { |
||
22 | protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_TIME; |
||
23 | |||
24 | /** |
||
25 | * Override locale. If empty will default to current locale |
||
26 | * |
||
27 | * @var string |
||
28 | */ |
||
29 | protected $locale = null; |
||
30 | |||
31 | /** |
||
32 | * Override time format. If empty will default to that used by the current locale. |
||
33 | * |
||
34 | * @var string |
||
35 | */ |
||
36 | protected $timeFormat = null; |
||
37 | |||
38 | /** |
||
39 | * Length of this date (full, short, etc). |
||
40 | * |
||
41 | * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants |
||
42 | * @var int |
||
43 | */ |
||
44 | protected $timeLength = null; |
||
45 | |||
46 | /** |
||
47 | * Unparsed value, used exclusively for comparing with internal value |
||
48 | * to detect invalid values. |
||
49 | * |
||
50 | * @var mixed |
||
51 | */ |
||
52 | protected $rawValue = null; |
||
53 | |||
54 | /** |
||
55 | * Set custom timezone |
||
56 | * |
||
57 | * @var string |
||
58 | */ |
||
59 | protected $timezone = null; |
||
60 | |||
61 | /** |
||
62 | * Use HTML5-based input fields (and force ISO 8601 time formats). |
||
63 | * |
||
64 | * @var bool |
||
65 | */ |
||
66 | protected $html5 = true; |
||
67 | |||
68 | /** |
||
69 | * @return bool |
||
70 | */ |
||
71 | public function getHTML5() |
||
75 | |||
76 | /** |
||
77 | * @param boolean $bool |
||
78 | * @return $this |
||
79 | */ |
||
80 | public function setHTML5($bool) |
||
85 | |||
86 | /** |
||
87 | * Get time format in CLDR standard format |
||
88 | * |
||
89 | * This can be set explicitly. If not, this will be generated from the current locale |
||
90 | * with the current time length. |
||
91 | * |
||
92 | * @see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Field-Symbol-Table |
||
93 | */ |
||
94 | public function getTimeFormat() |
||
108 | |||
109 | /** |
||
110 | * Set time format in CLDR standard format. |
||
111 | * Only applicable with {@link setHTML5(false)}. |
||
112 | * |
||
113 | * @see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Field-Symbol-Table |
||
114 | * @param string $format |
||
115 | * @return $this |
||
116 | */ |
||
117 | public function setTimeFormat($format) |
||
122 | |||
123 | /** |
||
124 | * Get length of the time format to use. One of: |
||
125 | * |
||
126 | * - IntlDateFormatter::SHORT E.g. '6:31 PM' |
||
127 | * - IntlDateFormatter::MEDIUM E.g. '6:30:48 PM' |
||
128 | * - IntlDateFormatter::LONG E.g. '6:32:09 PM NZDT' |
||
129 | * - IntlDateFormatter::FULL E.g. '6:32:24 PM New Zealand Daylight Time' |
||
130 | * |
||
131 | * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants |
||
132 | * @return int |
||
133 | */ |
||
134 | public function getTimeLength() |
||
141 | |||
142 | /** |
||
143 | * Get length of the time format to use. |
||
144 | * Only applicable with {@link setHTML5(false)}. |
||
145 | * |
||
146 | * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants |
||
147 | * |
||
148 | * @param int $length |
||
149 | * @return $this |
||
150 | */ |
||
151 | public function setTimeLength($length) |
||
156 | |||
157 | /** |
||
158 | * Get time formatter with the standard locale / date format |
||
159 | * |
||
160 | * @return IntlDateFormatter |
||
161 | */ |
||
162 | View Code Duplication | protected function getFrontendFormatter() |
|
|
|||
163 | { |
||
164 | if ($this->getHTML5() && $this->timeFormat && $this->timeFormat !== DBTime::ISO_TIME) { |
||
165 | throw new \LogicException( |
||
166 | 'Please opt-out of HTML5 processing of ISO 8601 times via setHTML5(false) if using setTimeFormat()' |
||
167 | ); |
||
168 | } |
||
169 | |||
170 | if ($this->getHTML5() && $this->timeLength) { |
||
171 | throw new \LogicException( |
||
172 | 'Please opt-out of HTML5 processing of ISO 8601 times via setHTML5(false) if using setTimeLength()' |
||
173 | ); |
||
174 | } |
||
175 | |||
176 | if ($this->getHTML5() && $this->locale) { |
||
177 | throw new \LogicException( |
||
178 | 'Please opt-out of HTML5 processing of ISO 8601 times via setHTML5(false) if using setLocale()' |
||
179 | ); |
||
180 | } |
||
181 | |||
182 | $formatter = IntlDateFormatter::create( |
||
183 | $this->getLocale(), |
||
184 | IntlDateFormatter::NONE, |
||
185 | $this->getTimeLength(), |
||
186 | $this->getTimezone() |
||
187 | ); |
||
188 | |||
189 | if ($this->getHTML5()) { |
||
190 | // Browsers expect ISO 8601 times, localisation is handled on the client |
||
191 | $formatter->setPattern(DBTime::ISO_TIME); |
||
192 | // Don't invoke getTimeFormat() directly to avoid infinite loop |
||
193 | } elseif ($this->timeFormat) { |
||
194 | $ok = $formatter->setPattern($this->timeFormat); |
||
195 | if (!$ok) { |
||
196 | throw new InvalidArgumentException("Invalid time format {$this->timeFormat}"); |
||
197 | } |
||
198 | } |
||
199 | return $formatter; |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * Get a time formatter for the ISO 8601 format |
||
204 | * |
||
205 | * @return IntlDateFormatter |
||
206 | */ |
||
207 | View Code Duplication | protected function getInternalFormatter() |
|
208 | { |
||
209 | $formatter = IntlDateFormatter::create( |
||
210 | i18n::config()->uninherited('default_locale'), |
||
211 | IntlDateFormatter::NONE, |
||
212 | IntlDateFormatter::MEDIUM, |
||
213 | date_default_timezone_get() // Default to server timezone |
||
214 | ); |
||
215 | $formatter->setLenient(false); |
||
216 | |||
217 | // Note we omit timezone from this format, and we assume server TZ always. |
||
218 | $formatter->setPattern(DBTime::ISO_TIME); |
||
219 | |||
220 | return $formatter; |
||
221 | } |
||
222 | |||
223 | public function getAttributes() |
||
233 | |||
234 | public function getSchemaDataDefaults() |
||
235 | { |
||
244 | |||
245 | public function Type() |
||
249 | |||
250 | /** |
||
251 | * Assign value posted from form submission |
||
252 | * |
||
253 | * @param mixed $value |
||
254 | * @param mixed $data |
||
255 | * @return $this |
||
256 | */ |
||
257 | public function setSubmittedValue($value, $data = null) |
||
266 | |||
267 | /** |
||
268 | * Set time assigned from database value |
||
269 | * |
||
270 | * @param mixed $value |
||
271 | * @param mixed $data |
||
272 | * @return $this |
||
273 | */ |
||
274 | View Code Duplication | public function setValue($value, $data = null) |
|
289 | |||
290 | public function Value() |
||
300 | |||
301 | /** |
||
302 | * Show midnight in current format (adjusts for timezone) |
||
303 | * |
||
304 | * @return string |
||
305 | */ |
||
306 | public function getMidnight() |
||
314 | |||
315 | /** |
||
316 | * Validate this field |
||
317 | * |
||
318 | * @param Validator $validator |
||
319 | * @return bool |
||
320 | */ |
||
321 | public function validate($validator) |
||
342 | |||
343 | /** |
||
344 | * @return string |
||
345 | */ |
||
346 | public function getLocale() |
||
350 | |||
351 | /** |
||
352 | * Determines the presented/processed format based on locale defaults, |
||
353 | * instead of explicitly setting {@link setTimeFormat()}. |
||
354 | * Only applicable with {@link setHTML5(false)}. |
||
355 | * |
||
356 | * @param string $locale |
||
357 | * @return $this |
||
358 | */ |
||
359 | public function setLocale($locale) |
||
364 | |||
365 | /** |
||
366 | * Creates a new readonly field specified below |
||
367 | * |
||
368 | * @return TimeField_Readonly |
||
369 | */ |
||
370 | public function performReadonlyTransformation() |
||
376 | |||
377 | /** |
||
378 | * Convert frontend time to the internal representation (ISO 8601). |
||
379 | * The frontend time is also in ISO 8601 when $html5=true. |
||
380 | * |
||
381 | * @param string $time |
||
382 | * @return string The formatted time, or null if not a valid time |
||
383 | */ |
||
384 | View Code Duplication | protected function frontendToInternal($time) |
|
407 | |||
408 | /** |
||
409 | * Convert the internal time representation (ISO 8601) to a format used by the frontend, |
||
410 | * as defined by {@link $timeFormat}. With $html5=true, the frontend time will also be |
||
411 | * in ISO 8601. |
||
412 | * |
||
413 | * @param string $time |
||
414 | * @return string |
||
415 | */ |
||
416 | View Code Duplication | protected function internalToFrontend($time) |
|
430 | |||
431 | |||
432 | |||
433 | /** |
||
434 | * Tidy up the internal time representation (ISO 8601), |
||
435 | * and fall back to strtotime() if there's parsing errors. |
||
436 | * |
||
437 | * @param string $time Time in ISO 8601 or approximate form |
||
438 | * @return string ISO 8601 time, or null if not valid |
||
439 | */ |
||
440 | View Code Duplication | protected function tidyInternal($time) |
|
457 | |||
458 | /** |
||
459 | * @return string |
||
460 | */ |
||
461 | public function getTimezone() |
||
465 | |||
466 | /** |
||
467 | * @param string $timezone |
||
468 | * @return $this |
||
469 | */ |
||
470 | View Code Duplication | public function setTimezone($timezone) |
|
478 | |||
479 | |||
480 | /** |
||
481 | * Run a callback within a specific timezone |
||
482 | * |
||
483 | * @param string $timezone |
||
484 | * @param callable $callback |
||
485 | */ |
||
486 | protected function withTimezone($timezone, $callback) |
||
501 | } |
||
502 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.