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 Translatable 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 Translatable, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
13 | trait Translatable |
||
14 | { |
||
15 | /** |
||
16 | * Alias for getTranslation(). |
||
17 | * |
||
18 | * @param string|null $locale |
||
19 | * @param bool $withFallback |
||
20 | * |
||
21 | * @return \Illuminate\Database\Eloquent\Model|null |
||
22 | */ |
||
23 | public function translate($locale = null, $withFallback = false) |
||
24 | { |
||
25 | return $this->getTranslation($locale, $withFallback); |
||
26 | } |
||
27 | |||
28 | /** |
||
29 | * Alias for getTranslation(). |
||
30 | * |
||
31 | * @param string $locale |
||
32 | * |
||
33 | * @return \Illuminate\Database\Eloquent\Model|null |
||
34 | */ |
||
35 | public function translateOrDefault($locale) |
||
39 | |||
40 | /** |
||
41 | * Alias for getTranslationOrNew(). |
||
42 | * |
||
43 | * @param string $locale |
||
44 | * |
||
45 | * @return \Illuminate\Database\Eloquent\Model|null |
||
46 | */ |
||
47 | public function translateOrNew($locale) |
||
51 | |||
52 | /** |
||
53 | * @param string|null $locale |
||
54 | * @param bool $withFallback |
||
55 | * |
||
56 | * @return \Illuminate\Database\Eloquent\Model|null |
||
57 | */ |
||
58 | public function getTranslation($locale = null, $withFallback = null) |
||
59 | { |
||
60 | $configFallbackLocale = $this->getFallbackLocale($locale); |
||
|
|||
61 | $locale = $locale ?: $this->locale(); |
||
62 | $withFallback = $withFallback === null ? $this->useFallback() : $withFallback; |
||
63 | $fallbackLocale = $this->getFallbackLocale($locale); |
||
64 | |||
65 | if ($translation = $this->getTranslationByLocaleKey($locale)) { |
||
66 | return $translation; |
||
67 | } |
||
68 | if ($withFallback && $fallbackLocale) { |
||
69 | if ($translation = $this->getTranslationByLocaleKey($fallbackLocale)) { |
||
70 | return $translation; |
||
71 | } |
||
72 | if ($translation = $this->getTranslationByLocaleKey($configFallbackLocale)) { |
||
73 | return $translation; |
||
74 | } |
||
75 | } |
||
76 | |||
77 | return null; |
||
78 | } |
||
79 | |||
80 | /** |
||
81 | * @param string|null $locale |
||
82 | * |
||
83 | * @return bool |
||
84 | */ |
||
85 | public function hasTranslation($locale = null) |
||
97 | |||
98 | /** |
||
99 | * @return string |
||
100 | */ |
||
101 | public function getTranslationModelName() |
||
105 | |||
106 | /** |
||
107 | * @return string |
||
108 | */ |
||
109 | public function getTranslationModelNameDefault() |
||
115 | |||
116 | /** |
||
117 | * @return string |
||
118 | */ |
||
119 | public function getRelationKey() |
||
131 | |||
132 | /** |
||
133 | * @return string |
||
134 | */ |
||
135 | public function getLocaleKey() |
||
136 | { |
||
137 | $config = app()->make('config'); |
||
138 | |||
139 | return $this->localeKey ?: $config->get('translatable.locale_key', 'locale'); |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * @return \Illuminate\Database\Eloquent\Relations\HasMany |
||
144 | */ |
||
145 | public function translations() |
||
149 | |||
150 | /** |
||
151 | * @param string $key |
||
152 | * |
||
153 | * @return mixed |
||
154 | */ |
||
155 | public function getAttribute($key) |
||
182 | |||
183 | /** |
||
184 | * @param string $key |
||
185 | * @param mixed $value |
||
186 | */ |
||
187 | public function setAttribute($key, $value) |
||
203 | |||
204 | /** |
||
205 | * @param array $options |
||
206 | * |
||
207 | * @return bool |
||
208 | */ |
||
209 | public function save(array $options = []) |
||
210 | { |
||
211 | if ($this->exists) { |
||
212 | if (count($this->getDirty()) > 0) { |
||
213 | // If $this->exists and dirty, parent::save() has to return true. If not, |
||
214 | // an error has occurred. Therefore we shouldn't save the translations. |
||
215 | if (parent::save($options)) { |
||
216 | return $this->saveTranslations(); |
||
217 | } |
||
218 | |||
219 | return false; |
||
220 | } else { |
||
221 | // If $this->exists and not dirty, parent::save() skips saving and returns |
||
222 | // false. So we have to save the translations |
||
223 | if ($saved = $this->saveTranslations()) { |
||
224 | $this->fireModelEvent('saved', false); |
||
225 | $this->fireModelEvent('updated', false); |
||
226 | } |
||
227 | |||
228 | return $saved; |
||
229 | } |
||
230 | } elseif (parent::save($options)) { |
||
231 | // We save the translations only if the instance is saved in the database. |
||
232 | return $this->saveTranslations(); |
||
233 | } |
||
234 | |||
235 | return false; |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * @param string $locale |
||
240 | * |
||
241 | * @return \Illuminate\Database\Eloquent\Model|null |
||
242 | */ |
||
243 | protected function getTranslationOrNew($locale) |
||
251 | |||
252 | /** |
||
253 | * @param array $attributes |
||
254 | * |
||
255 | * @throws \Illuminate\Database\Eloquent\MassAssignmentException |
||
256 | * |
||
257 | * @return $this |
||
258 | */ |
||
259 | public function fill(array $attributes) |
||
260 | { |
||
261 | $totallyGuarded = $this->totallyGuarded(); |
||
262 | |||
263 | foreach ($attributes as $key => $values) { |
||
264 | if ($this->isKeyALocale($key)) { |
||
265 | foreach ($values as $translationAttribute => $translationValue) { |
||
266 | if ($this->alwaysFillable() || $this->isFillable($translationAttribute)) { |
||
267 | $this->getTranslationOrNew($key)->$translationAttribute = $translationValue; |
||
268 | } elseif ($totallyGuarded) { |
||
269 | throw new MassAssignmentException($key); |
||
270 | } |
||
271 | } |
||
272 | unset($attributes[$key]); |
||
273 | } |
||
274 | } |
||
275 | |||
276 | return parent::fill($attributes); |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * @param string $key |
||
281 | */ |
||
282 | private function getTranslationByLocaleKey($key) |
||
292 | |||
293 | /** |
||
294 | * @param null $locale |
||
295 | * |
||
296 | * @return string |
||
297 | */ |
||
298 | private function getFallbackLocale($locale = null) |
||
308 | |||
309 | /** |
||
310 | * @param $locale |
||
311 | * |
||
312 | * @return bool |
||
313 | */ |
||
314 | private function isLocaleCountryBased($locale) |
||
318 | |||
319 | /** |
||
320 | * @param $locale |
||
321 | * |
||
322 | * @return string |
||
323 | */ |
||
324 | private function getLanguageFromCountryBasedLocale($locale) |
||
330 | |||
331 | /** |
||
332 | * @return bool|null |
||
333 | */ |
||
334 | private function useFallback() |
||
342 | |||
343 | /** |
||
344 | * @param string $key |
||
345 | * |
||
346 | * @return bool |
||
347 | */ |
||
348 | public function isTranslationAttribute($key) |
||
352 | |||
353 | /** |
||
354 | * @param string $key |
||
355 | * |
||
356 | * @throws \Dimsav\Translatable\Exception\LocalesNotDefinedException |
||
357 | * |
||
358 | * @return bool |
||
359 | */ |
||
360 | protected function isKeyALocale($key) |
||
366 | |||
367 | /** |
||
368 | * @throws \Dimsav\Translatable\Exception\LocalesNotDefinedException |
||
369 | * |
||
370 | * @return array |
||
371 | */ |
||
372 | protected function getLocales() |
||
395 | |||
396 | /** |
||
397 | * @return string |
||
398 | */ |
||
399 | protected function getLocaleSeparator() |
||
403 | |||
404 | /** |
||
405 | * @return bool |
||
406 | */ |
||
407 | protected function saveTranslations() |
||
419 | |||
420 | /** |
||
421 | * @param \Illuminate\Database\Eloquent\Model $translation |
||
422 | * |
||
423 | * @return bool |
||
424 | */ |
||
425 | protected function isTranslationDirty(Model $translation) |
||
432 | |||
433 | /** |
||
434 | * @param string $locale |
||
435 | * |
||
436 | * @return \Illuminate\Database\Eloquent\Model |
||
437 | */ |
||
438 | public function getNewTranslation($locale) |
||
447 | |||
448 | /** |
||
449 | * @param $key |
||
450 | * |
||
451 | * @return bool |
||
452 | */ |
||
453 | public function __isset($key) |
||
457 | |||
458 | /** |
||
459 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
460 | * @param string $locale |
||
461 | * |
||
462 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
463 | */ |
||
464 | View Code Duplication | public function scopeTranslatedIn(Builder $query, $locale = null) |
|
472 | |||
473 | /** |
||
474 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
475 | * @param string $locale |
||
476 | * |
||
477 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
478 | */ |
||
479 | View Code Duplication | public function scopeNotTranslatedIn(Builder $query, $locale = null) |
|
487 | |||
488 | /** |
||
489 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
490 | * |
||
491 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
492 | */ |
||
493 | public function scopeTranslated(Builder $query) |
||
497 | |||
498 | /** |
||
499 | * Adds scope to get a list of translated attributes, using the current locale. |
||
500 | * |
||
501 | * Example usage: Country::listsTranslations('name')->get()->toArray() |
||
502 | * Will return an array with items: |
||
503 | * [ |
||
504 | * 'id' => '1', // The id of country |
||
505 | * 'name' => 'Griechenland' // The translated name |
||
506 | * ] |
||
507 | * |
||
508 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
509 | * @param string $translationField |
||
510 | */ |
||
511 | public function scopeListsTranslations(Builder $query, $translationField) |
||
532 | |||
533 | /** |
||
534 | * This scope eager loads the translations for the default and the fallback locale only. |
||
535 | * We can use this as a shortcut to improve performance in our application. |
||
536 | * |
||
537 | * @param Builder $query |
||
538 | */ |
||
539 | public function scopeWithTranslation(Builder $query) |
||
549 | |||
550 | /** |
||
551 | * This scope filters results by checking the translation fields. |
||
552 | * |
||
553 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
554 | * @param string $key |
||
555 | * @param string $value |
||
556 | * @param string $locale |
||
557 | * |
||
558 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
559 | */ |
||
560 | View Code Duplication | public function scopeWhereTranslation(Builder $query, $key, $value, $locale = null) |
|
569 | |||
570 | /** |
||
571 | * This scope filters results by checking the translation fields. |
||
572 | * |
||
573 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
574 | * @param string $key |
||
575 | * @param string $value |
||
576 | * @param string $locale |
||
577 | * |
||
578 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
579 | */ |
||
580 | View Code Duplication | public function scopeWhereTranslationLike(Builder $query, $key, $value, $locale = null) |
|
589 | |||
590 | /** |
||
591 | * @return array |
||
592 | */ |
||
593 | public function toArray() |
||
611 | |||
612 | /** |
||
613 | * @return bool |
||
614 | */ |
||
615 | private function alwaysFillable() |
||
619 | |||
620 | /** |
||
621 | * @return string |
||
622 | */ |
||
623 | private function getTranslationsTable() |
||
627 | |||
628 | /** |
||
629 | * @return string |
||
630 | */ |
||
631 | protected function locale() |
||
636 | |||
637 | /** |
||
638 | * Deletes all translations for this model. |
||
639 | * |
||
640 | * @param string|array|null $locales The locales to be deleted (array or single string) |
||
641 | * (e.g., ["en", "de"] would remove these translations). |
||
642 | */ |
||
643 | public function deleteTranslations($locales = null) |
||
656 | |||
657 | /** |
||
658 | * Deletes the translations for this model, which are not listed in $locales. |
||
659 | * |
||
660 | * @param mixed $locales The locales to be left untouched (array or single string) |
||
661 | * (e.g., ["en", "de"] would remove all locales but these). |
||
662 | */ |
||
663 | public function syncTranslations($locales) |
||
676 | } |
||
677 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.