Translatable   F
last analyzed

Complexity

Total Complexity 120

Size/Duplication

Total Lines 560
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 95.9%

Importance

Changes 0
Metric Value
wmc 120
lcom 1
cbo 7
dl 0
loc 560
ccs 257
cts 268
cp 0.959
rs 2
c 0
b 0
f 0

56 Methods

Rating   Name   Duplication   Size   Complexity  
A translateOrDefault() 0 4 1
A hasTranslation() 0 12 4
A getRelationKey() 0 8 2
A isEmptyTranslatableAttribute() 0 4 1
A getTranslationByLocaleKey() 0 10 3
A getLanguageFromCountryBasedLocale() 0 4 1
A isTranslationAttribute() 0 4 1
A isKeyALocale() 0 4 1
A __isset() 0 4 2
A scopeTranslatedIn() 0 8 2
A getTranslationsArray() 0 12 3
A getTranslationsTable() 0 4 1
A setDefaultLocale() 0 6 1
A toArrayAlwaysLoadsTranslations() 0 4 1
A getLocalesHelper() 0 4 1
A bootTranslatable() 0 7 1
A translate() 0 4 1
A translateOrNew() 0 4 1
B getTranslation() 0 21 9
A getTranslationModelName() 0 4 2
A getTranslationModelNameDefault() 0 10 2
A getTranslationModelNamespace() 0 4 1
A getLocaleKey() 0 4 2
A translations() 0 4 1
A usePropertyFallback() 0 4 2
A getAttributeOrFallback() 0 20 5
A getAttribute() 0 23 4
A setAttribute() 0 12 2
A getTranslationOrNew() 0 10 3
A fill() 0 17 5
A getFallbackLocale() 0 10 4
A isLocaleCountryBased() 0 4 1
A useFallback() 0 8 3
A getLocales() 0 4 1
A getLocaleSeparator() 0 4 1
B saveTranslations() 0 21 6
A replicateWithTranslations() 0 12 2
A isTranslationDirty() 0 7 1
A getNewTranslation() 0 9 1
A scopeNotTranslatedIn() 0 8 2
A scopeTranslated() 0 4 1
A scopeListsTranslations() 0 24 2
A scopeWithTranslation() 0 16 2
A scopeWhereTranslation() 0 9 2
A scopeOrWhereTranslation() 0 9 2
A scopeWhereTranslationLike() 0 9 2
A scopeOrWhereTranslationLike() 0 9 2
A scopeOrderByTranslation() 0 17 1
B attributesToArray() 0 23 7
A locale() 0 8 2
A getDefaultLocale() 0 4 1
A deleteTranslations() 0 16 3
A getAttributeAndLocale() 0 8 2
A enableAutoloadTranslations() 0 4 1
A defaultAutoloadTranslations() 0 4 1
A disableAutoloadTranslations() 0 4 1

How to fix   Complexity   

Complex Class

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
2
3
namespace Dimsav\Translatable;
4
5
use Illuminate\Support\Str;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Query\JoinClause;
9
use Illuminate\Database\Eloquent\Collection;
10
use Illuminate\Database\Eloquent\Relations\HasMany;
11
use Illuminate\Database\Eloquent\Relations\Relation;
12
use Illuminate\Database\Query\Builder as QueryBuilder;
13
14
/**
15
 * @property-read Collection|Model[] $translations
16
 * @property-read string $translationModel
17
 * @property-read string $translationForeignKey
18
 * @property-read string $localeKey
19
 * @property-read bool $useTranslationFallback
20
 *
21
 * @mixin Model
22
 */
23
trait Translatable
24
{
25
    protected static $autoloadTranslations = null;
26
27
    protected $defaultLocale;
28
29 480
    public static function bootTranslatable(): void
30
    {
31
        static::saved(function (Model $model) {
32
            /* @var Translatable $model */
33 480
            return $model->saveTranslations();
34 480
        });
35 480
    }
36
37 52
    public function translate(?string $locale = null, bool $withFallback = false): ?Model
38
    {
39 52
        return $this->getTranslation($locale, $withFallback);
40
    }
41
42 4
    public function translateOrDefault(?string $locale = null): ?Model
43
    {
44 4
        return $this->getTranslation($locale, true);
45
    }
46
47 4
    public function translateOrNew(?string $locale = null): Model
48
    {
49 4
        return $this->getTranslationOrNew($locale);
50
    }
51
52 252
    public function getTranslation(?string $locale = null, bool $withFallback = null): ?Model
53
    {
54 252
        $configFallbackLocale = $this->getFallbackLocale();
55 252
        $locale = $locale ?: $this->locale();
56 252
        $withFallback = $withFallback === null ? $this->useFallback() : $withFallback;
57 252
        $fallbackLocale = $this->getFallbackLocale($locale);
58
59 252
        if ($translation = $this->getTranslationByLocaleKey($locale)) {
60 144
            return $translation;
61
        }
62 172
        if ($withFallback && $fallbackLocale) {
63 28
            if ($translation = $this->getTranslationByLocaleKey($fallbackLocale)) {
64 16
                return $translation;
65
            }
66 12
            if ($fallbackLocale !== $configFallbackLocale && $translation = $this->getTranslationByLocaleKey($configFallbackLocale)) {
67 8
                return $translation;
68
            }
69
        }
70
71 168
        return null;
72
    }
73
74 12
    public function hasTranslation(?string $locale = null): bool
75
    {
76 12
        $locale = $locale ?: $this->locale();
77
78 12
        foreach ($this->translations as $translation) {
79 4
            if ($translation->getAttribute($this->getLocaleKey()) == $locale) {
80 4
                return true;
81
            }
82
        }
83
84 12
        return false;
85
    }
86
87 316
    public function getTranslationModelName(): string
88
    {
89 316
        return $this->translationModel ?: $this->getTranslationModelNameDefault();
90
    }
91
92 304
    public function getTranslationModelNameDefault(): string
93
    {
94 304
        $modelName = get_class($this);
95
96 304
        if ($namespace = $this->getTranslationModelNamespace()) {
97 4
            $modelName = $namespace.'\\'.class_basename(get_class($this));
98
        }
99
100 304
        return $modelName.config('translatable.translation_suffix', 'Translation');
101
    }
102
103 304
    public function getTranslationModelNamespace(): ?string
104
    {
105 304
        return config('translatable.translation_model_namespace');
106
    }
107
108 316
    public function getRelationKey(): string
109
    {
110 316
        if ($this->translationForeignKey) {
111 28
            return $this->translationForeignKey;
112
        }
113
114 292
        return $this->getForeignKey();
0 ignored issues
show
Bug introduced by
It seems like getForeignKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
115
    }
116
117 296
    public function getLocaleKey(): string
118
    {
119 296
        return $this->localeKey ?: config('translatable.locale_key', 'locale');
120
    }
121
122 296
    public function translations(): HasMany
123
    {
124 296
        return $this->hasMany($this->getTranslationModelName(), $this->getRelationKey());
0 ignored issues
show
Bug introduced by
It seems like hasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
125
    }
126
127 16
    private function usePropertyFallback(): bool
128
    {
129 16
        return $this->useFallback() && config('translatable.use_property_fallback', false);
130
    }
131
132 96
    private function getAttributeOrFallback(?string $locale, string $attribute)
133
    {
134 96
        $translation = $this->getTranslation($locale);
135
136
        if (
137
            (
138 96
                ! $translation instanceof Model
139 96
                || $this->isEmptyTranslatableAttribute($attribute, $translation->$attribute)
140
            )
141 96
            && $this->usePropertyFallback()
142
        ) {
143 12
            $translation = $this->getTranslation($this->getFallbackLocale(), false);
144
        }
145
146 96
        if ($translation instanceof Model) {
147 92
            return $translation->$attribute;
148
        }
149
150 8
        return null;
151
    }
152
153 88
    protected function isEmptyTranslatableAttribute(string $key, $value): bool
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
154
    {
155 88
        return empty($value);
156
    }
157
158 480
    public function getAttribute($key)
159
    {
160 480
        [$attribute, $locale] = $this->getAttributeAndLocale($key);
0 ignored issues
show
Bug introduced by
The variable $attribute does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $locale does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
161
162 480
        if ($this->isTranslationAttribute($attribute)) {
163 76
            if ($this->getTranslation($locale) === null) {
164 12
                return $this->getAttributeValue($attribute);
0 ignored issues
show
Bug introduced by
The method getAttributeValue() does not exist on Dimsav\Translatable\Translatable. Did you maybe mean getAttribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
165
            }
166
167
            // If the given $attribute has a mutator, we push it to $attributes and then call getAttributeValue
168
            // on it. This way, we can use Eloquent's checking for Mutation, type casting, and
169
            // Date fields.
170 64
            if ($this->hasGetMutator($attribute)) {
0 ignored issues
show
Bug introduced by
It seems like hasGetMutator() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
171 4
                $this->attributes[$attribute] = $this->getAttributeOrFallback($locale, $attribute);
0 ignored issues
show
Bug introduced by
The property attributes does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
172
173 4
                return $this->getAttributeValue($attribute);
0 ignored issues
show
Bug introduced by
The method getAttributeValue() does not exist on Dimsav\Translatable\Translatable. Did you maybe mean getAttribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
174
            }
175
176 60
            return $this->getAttributeOrFallback($locale, $attribute);
177
        }
178
179 480
        return parent::getAttribute($key);
180
    }
181
182 480
    public function setAttribute($key, $value)
183
    {
184 480
        [$attribute, $locale] = $this->getAttributeAndLocale($key);
0 ignored issues
show
Bug introduced by
The variable $attribute does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $locale does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
185
186 480
        if ($this->isTranslationAttribute($attribute)) {
187 40
            $this->getTranslationOrNew($locale)->$attribute = $value;
188
        } else {
189 480
            return parent::setAttribute($key, $value);
190
        }
191
192 40
        return $this;
193
    }
194
195 144
    protected function getTranslationOrNew(?string $locale = null): Model
196
    {
197 144
        $locale = $locale ?: $this->locale();
198
199 144
        if (($translation = $this->getTranslation($locale, false)) === null) {
200 128
            $translation = $this->getNewTranslation($locale);
201
        }
202
203 144
        return $translation;
204
    }
205
206 480
    public function fill(array $attributes)
207
    {
208 480
        foreach ($attributes as $key => $values) {
209 112
            if ($this->isKeyALocale($key)) {
210 52
                $this->getTranslationOrNew($key)->fill($values);
211 44
                unset($attributes[$key]);
212
            } else {
213 104
                [$attribute, $locale] = $this->getAttributeAndLocale($key);
0 ignored issues
show
Bug introduced by
The variable $attribute does not exist. Did you mean $attributes?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
Bug introduced by
The variable $locale does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
214 104
                if ($this->isTranslationAttribute($attribute) and $this->isKeyALocale($locale)) {
0 ignored issues
show
Bug introduced by
The variable $attribute does not exist. Did you mean $attributes?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
215 48
                    $this->getTranslationOrNew($locale)->fill([$attribute => $values]);
0 ignored issues
show
Bug introduced by
The variable $attribute does not exist. Did you mean $attributes?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
216 48
                    unset($attributes[$key]);
217
                }
218
            }
219
        }
220
221 480
        return parent::fill($attributes);
222
    }
223
224 252
    private function getTranslationByLocaleKey(string $key): ?Model
225
    {
226 252
        foreach ($this->translations as $translation) {
227 200
            if ($translation->getAttribute($this->getLocaleKey()) == $key) {
228 168
                return $translation;
229
            }
230
        }
231
232 172
        return null;
233
    }
234
235 256
    private function getFallbackLocale(?string $locale = null): ?string
236
    {
237 256
        if ($locale && $this->isLocaleCountryBased($locale)) {
238 28
            if ($fallback = $this->getLanguageFromCountryBasedLocale($locale)) {
239 28
                return $fallback;
240
            }
241
        }
242
243 256
        return config('translatable.fallback_locale');
244
    }
245
246 252
    private function isLocaleCountryBased(string $locale): bool
247
    {
248 252
        return $this->getLocalesHelper()->isLocaleCountryBased($locale);
249
    }
250
251 28
    private function getLanguageFromCountryBasedLocale(string $locale): string
252
    {
253 28
        return $this->getLocalesHelper()->getLanguageFromCountryBasedLocale($locale);
254
    }
255
256 148
    private function useFallback(): bool
257
    {
258 148
        if (isset($this->useTranslationFallback) && $this->useTranslationFallback !== null) {
259 12
            return $this->useTranslationFallback;
260
        }
261
262 136
        return (bool) config('translatable.use_fallback');
263
    }
264
265 480
    public function isTranslationAttribute(string $key): bool
266
    {
267 480
        return in_array($key, $this->translatedAttributes);
0 ignored issues
show
Bug introduced by
The property translatedAttributes does not seem to exist. Did you mean attributes?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
268
    }
269
270 112
    protected function isKeyALocale(string $key): bool
271
    {
272 112
        return $this->getLocalesHelper()->has($key);
273
    }
274
275
    protected function getLocales(): array
276
    {
277
        return $this->getLocalesHelper()->all();
278
    }
279
280
    protected function getLocaleSeparator(): string
281
    {
282
        return $this->getLocalesHelper()->getLocaleSeparator();
283
    }
284
285 480
    protected function saveTranslations(): bool
286
    {
287 480
        $saved = true;
288
289 480
        if (! $this->relationLoaded('translations')) {
0 ignored issues
show
Bug introduced by
It seems like relationLoaded() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
290 480
            return $saved;
291
        }
292
293 104
        foreach ($this->translations as $translation) {
294 104
            if ($saved && $this->isTranslationDirty($translation)) {
295 104
                if (! empty($connectionName = $this->getConnectionName())) {
0 ignored issues
show
Bug introduced by
It seems like getConnectionName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
296 104
                    $translation->setConnection($connectionName);
297
                }
298
299 104
                $translation->setAttribute($this->getRelationKey(), $this->getKey());
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
300 104
                $saved = $translation->save();
301
            }
302
        }
303
304 100
        return $saved;
305
    }
306
307 4
    public function replicateWithTranslations(array $except = null): Model
308
    {
309 4
        $newInstance = parent::replicate($except);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (replicate() instead of replicateWithTranslations()). Are you sure this is correct? If so, you might want to change this to $this->replicate().

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:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
310
311 4
        unset($newInstance->translations);
312 4
        foreach ($this->translations as $translation) {
313 4
            $newTranslation = $translation->replicate();
314 4
            $newInstance->translations->add($newTranslation);
315
        }
316
317 4
        return $newInstance;
318
    }
319
320 104
    protected function isTranslationDirty(Model $translation): bool
321
    {
322 104
        $dirtyAttributes = $translation->getDirty();
323 104
        unset($dirtyAttributes[$this->getLocaleKey()]);
324
325 104
        return count($dirtyAttributes) > 0;
326
    }
327
328 132
    public function getNewTranslation(string $locale): Model
329
    {
330 132
        $modelName = $this->getTranslationModelName();
331 132
        $translation = new $modelName();
332 132
        $translation->setAttribute($this->getLocaleKey(), $locale);
333 132
        $this->translations->add($translation);
334
335 132
        return $translation;
336
    }
337
338 152
    public function __isset($key)
339
    {
340 152
        return $this->isTranslationAttribute($key) || parent::__isset($key);
341
    }
342
343 8
    public function scopeTranslatedIn(Builder $query, ?string $locale = null)
344
    {
345 8
        $locale = $locale ?: $this->locale();
346
347
        return $query->whereHas('translations', function (Builder $q) use ($locale) {
348 8
            $q->where($this->getLocaleKey(), '=', $locale);
349 8
        });
350
    }
351
352 8
    public function scopeNotTranslatedIn(Builder $query, ?string $locale = null)
353
    {
354 8
        $locale = $locale ?: $this->locale();
355
356
        return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) {
357 8
            $q->where($this->getLocaleKey(), '=', $locale);
358 8
        });
359
    }
360
361 4
    public function scopeTranslated(Builder $query)
362
    {
363 4
        return $query->has('translations');
364
    }
365
366 12
    public function scopeListsTranslations(Builder $query, string $translationField)
367
    {
368 12
        $withFallback = $this->useFallback();
369 12
        $translationTable = $this->getTranslationsTable();
370 12
        $localeKey = $this->getLocaleKey();
371
372
        $query
0 ignored issues
show
Bug introduced by
The method select() does not exist on Illuminate\Database\Eloquent\Builder. Did you maybe mean createSelectWithConstraint()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
373 12
            ->select($this->getTable().'.'.$this->getKeyName(), $translationTable.'.'.$translationField)
0 ignored issues
show
Bug introduced by
It seems like getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
Bug introduced by
It seems like getKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
374 12
            ->leftJoin($translationTable, $translationTable.'.'.$this->getRelationKey(), '=', $this->getTable().'.'.$this->getKeyName())
0 ignored issues
show
Bug introduced by
It seems like getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
Bug introduced by
It seems like getKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
375 12
            ->where($translationTable.'.'.$localeKey, $this->locale());
376 12
        if ($withFallback) {
377
            $query->orWhere(function (Builder $q) use ($translationTable, $localeKey) {
378 4
                $q->where($translationTable.'.'.$localeKey, $this->getFallbackLocale())
379
                  ->whereNotIn($translationTable.'.'.$this->getRelationKey(), function (QueryBuilder $q) use (
380 4
                      $translationTable,
381 4
                      $localeKey
382
                  ) {
383 4
                      $q->select($translationTable.'.'.$this->getRelationKey())
384 4
                        ->from($translationTable)
385 4
                        ->where($translationTable.'.'.$localeKey, $this->locale());
386 4
                  });
387 4
            });
388
        }
389 12
    }
390
391 12
    public function scopeWithTranslation(Builder $query)
392
    {
393 12
        $query->with([
394
            'translations' => function (Relation $query) {
395 12
                if ($this->useFallback()) {
396 8
                    $locale = $this->locale();
397 8
                    $countryFallbackLocale = $this->getFallbackLocale($locale); // e.g. de-DE => de
398 8
                    $locales = array_unique([$locale, $countryFallbackLocale, $this->getFallbackLocale()]);
399
400 8
                    return $query->whereIn($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locales);
0 ignored issues
show
Bug introduced by
The method whereIn() does not exist on Illuminate\Database\Eloquent\Relations\Relation. Did you maybe mean whereInMethod()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
401
                }
402
403 4
                return $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale());
404 12
            },
405
        ]);
406 12
    }
407
408 12
    public function scopeWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null)
409
    {
410
        return $query->whereHas('translations', function (Builder $query) use ($translationField, $value, $locale) {
411 12
            $query->where($this->getTranslationsTable().'.'.$translationField, $value);
412 12
            if ($locale) {
413 4
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale);
414
            }
415 12
        });
416
    }
417
418 4
    public function scopeOrWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null)
419
    {
420
        return $query->orWhereHas('translations', function (Builder $query) use ($translationField, $value, $locale) {
421 4
            $query->where($this->getTranslationsTable().'.'.$translationField, $value);
422 4
            if ($locale) {
423
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale);
424
            }
425 4
        });
426
    }
427
428 12
    public function scopeWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null)
429
    {
430
        return $query->whereHas('translations', function (Builder $query) use ($translationField, $value, $locale) {
431 12
            $query->where($this->getTranslationsTable().'.'.$translationField, 'LIKE', $value);
432 12
            if ($locale) {
433 4
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale);
434
            }
435 12
        });
436
    }
437
438 4
    public function scopeOrWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null)
439
    {
440
        return $query->orWhereHas('translations', function (Builder $query) use ($translationField, $value, $locale) {
441 4
            $query->where($this->getTranslationsTable().'.'.$translationField, 'LIKE', $value);
442 4
            if ($locale) {
443
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale);
444
            }
445 4
        });
446
    }
447
448 8
    public function scopeOrderByTranslation(Builder $query, string $translationField, string $sortMethod = 'asc')
449
    {
450 8
        $translationTable = $this->getTranslationsTable();
451 8
        $localeKey = $this->getLocaleKey();
452 8
        $table = $this->getTable();
0 ignored issues
show
Bug introduced by
It seems like getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
453 8
        $keyName = $this->getKeyName();
0 ignored issues
show
Bug introduced by
It seems like getKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
454
455
        return $query
456
            ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) {
457
                $join
458 8
                    ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName)
459 8
                    ->where($translationTable.'.'.$localeKey, $this->locale());
460 8
            })
461 8
            ->orderBy($translationTable.'.'.$translationField, $sortMethod)
462 8
            ->select($table.'.*')
463 8
            ->with('translations');
464
    }
465
466 48
    public function attributesToArray()
467
    {
468 48
        $attributes = parent::attributesToArray();
469
470
        if (
471 48
            (! $this->relationLoaded('translations') && ! $this->toArrayAlwaysLoadsTranslations() && is_null(self::$autoloadTranslations))
0 ignored issues
show
Bug introduced by
It seems like relationLoaded() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
472 48
            || self::$autoloadTranslations === false
473
        ) {
474 16
            return $attributes;
475
        }
476
477 32
        $hiddenAttributes = $this->getHidden();
0 ignored issues
show
Bug introduced by
It seems like getHidden() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
478
479 32
        foreach ($this->translatedAttributes as $field) {
0 ignored issues
show
Bug introduced by
The property translatedAttributes does not seem to exist. Did you mean attributes?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
480 32
            if (in_array($field, $hiddenAttributes)) {
481 4
                continue;
482
            }
483
484 32
            $attributes[$field] = $this->getAttributeOrFallback(null, $field);
485
        }
486
487 32
        return $attributes;
488
    }
489
490 4
    public function getTranslationsArray(): array
491
    {
492 4
        $translations = [];
493
494 4
        foreach ($this->translations as $translation) {
495 4
            foreach ($this->translatedAttributes as $attr) {
0 ignored issues
show
Bug introduced by
The property translatedAttributes does not seem to exist. Did you mean attributes?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
496 4
                $translations[$translation->{$this->getLocaleKey()}][$attr] = $translation->{$attr};
497
            }
498
        }
499
500 4
        return $translations;
501
    }
502
503 56
    private function getTranslationsTable(): string
504
    {
505 56
        return app()->make($this->getTranslationModelName())->getTable();
506
    }
507
508 480
    protected function locale(): string
509
    {
510 480
        if ($this->defaultLocale) {
511 4
            return $this->defaultLocale;
512
        }
513
514 480
        return $this->getLocalesHelper()->current();
515
    }
516
517 4
    public function setDefaultLocale(?string $locale)
518
    {
519 4
        $this->defaultLocale = $locale;
520
521 4
        return $this;
522
    }
523
524
    public function getDefaultLocale(): ?string
525
    {
526
        return $this->defaultLocale;
527
    }
528
529
    /**
530
     * @param string|array|null $locales The locales to be deleted
531
     */
532 12
    public function deleteTranslations($locales = null)
533
    {
534 12
        if ($locales === null) {
535 4
            $translations = $this->translations()->get();
536
        } else {
537 8
            $locales = (array) $locales;
538 8
            $translations = $this->translations()->whereIn($this->getLocaleKey(), $locales)->get();
0 ignored issues
show
Bug introduced by
The method whereIn() does not exist on Illuminate\Database\Eloquent\Relations\HasMany. Did you maybe mean whereInMethod()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
539
        }
540 12
        foreach ($translations as $translation) {
541 8
            $translation->delete();
542
        }
543
544
        // we need to manually "reload" the collection built from the relationship
545
        // otherwise $this->translations()->get() would NOT be the same as $this->translations
546 12
        $this->load('translations');
0 ignored issues
show
Bug introduced by
It seems like load() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
547 12
    }
548
549 480
    private function getAttributeAndLocale(string $key): array
550
    {
551 480
        if (Str::contains($key, ':')) {
552 44
            return explode(':', $key);
553
        }
554
555 480
        return [$key, $this->locale()];
556
    }
557
558 32
    private function toArrayAlwaysLoadsTranslations(): bool
559
    {
560 32
        return config('translatable.to_array_always_loads_translations', true);
561
    }
562
563
    public static function enableAutoloadTranslations(): void
564
    {
565
        self::$autoloadTranslations = true;
566
    }
567
568 4
    public static function defaultAutoloadTranslations(): void
569
    {
570 4
        self::$autoloadTranslations = null;
571 4
    }
572
573 4
    public static function disableAutoloadTranslations(): void
574
    {
575 4
        self::$autoloadTranslations = false;
576 4
    }
577
578 480
    protected function getLocalesHelper(): Locales
579
    {
580 480
        return app(Locales::class);
581
    }
582
}
583