Test Setup Failed
Push — ft-php71-type-hints ( a9ea11...6460c4 )
by Tom
61:49
created

Translatable::save()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.027

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 10
cts 11
cp 0.9091
rs 8.9137
c 0
b 0
f 0
cc 6
nc 5
nop 1
crap 6.027

3 Methods

Rating   Name   Duplication   Size   Complexity  
A Translatable::useFallback() 0 8 3
A Translatable::isTranslationAttribute() 0 4 1
A Translatable::isKeyALocale() 0 4 1
1
<?php
2
3
namespace Dimsav\Translatable;
4
5
use Illuminate\Database\Eloquent\Collection;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\Relations\HasMany;
9
use Illuminate\Database\Query\JoinClause;
10
use Illuminate\Database\Eloquent\Relations\Relation;
11
use Illuminate\Database\Query\Builder as QueryBuilder;
12
use Illuminate\Support\Str;
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
    public static function bootTranslatable(): void
30
    {
31 148
        static::saved(function (Model $model) {
32
            /* @var Translatable $model */
33 148
            return $model->saveTranslations();
34
        });
35
    }
36 48
37
    public function translate(?string $locale = null, bool $withFallback = false): ?Model
38 48
    {
39
        return $this->getTranslation($locale, $withFallback);
40
    }
41 48
42 48
    public function translateOrDefault(?string $locale = null): ?Model
43
    {
44 16
        return $this->getTranslation($locale, true);
45
    }
46
47 32
    public function translateOrNew(?string $locale = null): Model
48
    {
49 32
        return $this->getTranslationOrNew($locale);
50 32
    }
51 4
52
    public function getTranslation(?string $locale = null, bool $withFallback = null): ?Model
53
    {
54 32
        $configFallbackLocale = $this->getFallbackLocale();
55
        $locale = $locale ?: $this->locale();
56
        $withFallback = $withFallback === null ? $this->useFallback() : $withFallback;
57 32
        $fallbackLocale = $this->getFallbackLocale($locale);
58
59
        if ($translation = $this->getTranslationByLocaleKey($locale)) {
60 4
            return $translation;
61
        }
62 4
        if ($withFallback && $fallbackLocale) {
63 4
            if ($translation = $this->getTranslationByLocaleKey($fallbackLocale)) {
64
                return $translation;
65 12
            }
66
            if ($fallbackLocale !== $configFallbackLocale && $translation = $this->getTranslationByLocaleKey($configFallbackLocale)) {
67 12
                return $translation;
68 4
            }
69
        }
70 8
71
        return null;
72
    }
73 12
74 8
    public function hasTranslation(?string $locale = null): bool
75
    {
76
        $locale = $locale ?: $this->locale();
77
78
        foreach ($this->translations as $translation) {
79 12
            if ($translation->getAttribute($this->getLocaleKey()) == $locale) {
80 12
                return true;
81
            }
82 4
        }
83
84 4
        return false;
85 4
    }
86
87
    public function getTranslationModelName(): string
88
    {
89
        return $this->translationModel ?: $this->getTranslationModelNameDefault();
90
    }
91
92 388
    public function getTranslationModelNameDefault(): string
93
    {
94 388
        $modelName = get_class($this);
95 112
96 48
        if ($namespace = $this->getTranslationModelNamespace()) {
97 40
            $modelName = $namespace.'\\'.class_basename(get_class($this));
98
        }
99 100
100 100
        return $modelName.config('translatable.translation_suffix', 'Translation');
101 48
    }
102 48
103
    public function getTranslationModelNamespace(): ?string
104
    {
105
        return config('translatable.translation_model_namespace');
106
    }
107 388
108
    public function getRelationKey(): string
109
    {
110 388
        if ($this->translationForeignKey) {
111
            return $this->translationForeignKey;
112 388
        }
113
114 388
        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 72
    }
116 12
117
    public function getLocaleKey(): string
118
    {
119
        return $this->localeKey ?: config('translatable.locale_key', 'locale');
120
    }
121
122 60
    public function translations(): HasMany
123 4
    {
124
        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 4
    }
126
127
    private function usePropertyFallback(): bool
128 56
    {
129
        return $this->useFallback() && config('translatable.use_property_fallback', false);
130
    }
131 388
132
    private function getAttributeOrFallback(?string $locale, string $attribute)
133
    {
134
        $translation = $this->getTranslation($locale);
135
136
        if (
137
            (
138
                ! $translation instanceof Model
139 292
                || $this->isEmptyTranslatableAttribute($attribute, $translation->$attribute)
140
            )
141 292
            && $this->usePropertyFallback()
142
        ) {
143
            $translation = $this->getTranslation($this->getFallbackLocale(), false);
144 128
        }
145
146 128
        if ($translation instanceof Model) {
147 128
            return $translation->$attribute;
148 128
        }
149 128
150
        return null;
151 128
    }
152
153
    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 312
    {
155
        return empty($value);
156 312
    }
157 24
158 292
    public function getAttribute(string $key)
159
    {
160
        [$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 292
        if ($this->isTranslationAttribute($attribute)) {
163
            if ($this->getTranslation($locale) === null) {
164
                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 248
            }
166
167 248
            // If the given $attribute has a mutator, we push it to $attributes and then call getAttributeValue
168 248
            // on it. This way, we can use Eloquent's checking for Mutation, type casting, and
169 248
            // Date fields.
170 248
            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
                $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 248
173 140
                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 168
            return $this->getAttributeOrFallback($locale, $attribute);
177 28
        }
178 16
179
        return parent::getAttribute($key);
180
    }
181 12
182 8
    public function setAttribute(string $key, $value)
183
    {
184
        [$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 164
        if ($this->isTranslationAttribute($attribute)) {
187
            $this->getTranslationOrNew($locale)->$attribute = $value;
188
        } else {
189 312
            return parent::setAttribute($key, $value);
190
        }
191 312
192
        return $this;
193
    }
194 304
195
    protected function getTranslationOrNew(?string $locale = null): Model
196 304
    {
197
        $locale = $locale ?: $this->locale();
198 304
199 4
        if (($translation = $this->getTranslation($locale, false)) === null) {
200
            $translation = $this->getNewTranslation($locale);
201
        }
202 304
203
        return $translation;
204
    }
205 304
206
    public function fill(array $attributes)
207 304
    {
208
        foreach ($attributes as $key => $values) {
209
            if ($this->isKeyALocale($key)) {
210 4
                $this->getTranslationOrNew($key)->fill($values);
211
                unset($attributes[$key]);
212 4
            } else {
213
                [$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 4
                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 4
                    $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 4
                    unset($attributes[$key]);
217
                }
218
            }
219
        }
220 4
221
        return parent::fill($attributes);
222
    }
223 12
224
    private function getTranslationByLocaleKey(string $key): ?Model
225 12
    {
226
        foreach ($this->translations as $translation) {
227 12
            if ($translation->getAttribute($this->getLocaleKey()) == $key) {
228 4
                return $translation;
229 4
            }
230
        }
231
232
        return null;
233 12
    }
234
235
    private function getFallbackLocale(?string $locale = null): ?string
236 388
    {
237
        if ($locale && $this->isLocaleCountryBased($locale)) {
238 388
            if ($fallback = $this->getLanguageFromCountryBasedLocale($locale)) {
239
                return $fallback;
240
            }
241 4
        }
242
243 4
        return config('translatable.fallback_locale');
244
    }
245 4
246 4
    private function isLocaleCountryBased(string $locale): bool
247 4
    {
248 4
        return $this->getLocalesHelper()->isLocaleCountryBased($locale);
249
    }
250
251 4
    private function getLanguageFromCountryBasedLocale(string $locale): string
252
    {
253
        return $this->getLocalesHelper()->getLanguageFromCountryBasedLocale($locale);
254 388
    }
255
256 388
    private function useFallback(): bool
257
    {
258
        if (isset($this->useTranslationFallback) && $this->useTranslationFallback !== null) {
259 24
            return $this->useTranslationFallback;
260
        }
261
262
        return (bool) config('translatable.use_fallback');
263 24
    }
264 24
265 24
    public function isTranslationAttribute(string $key): bool
266
    {
267
        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 24
    }
269
270
    protected function isKeyALocale(string $key): bool
271
    {
272 388
        return $this->getLocalesHelper()->has($key);
273 388
    }
274
275
    protected function getLocales(): array
276 8
    {
277
        return $this->getLocalesHelper()->all();
278
    }
279 12
280
    protected function getLocaleSeparator(): string
281 12
    {
282 12
        return $this->getLocalesHelper()->getLocaleSeparator();
283 12
    }
284
285
    protected function saveTranslations(): bool
286 12
    {
287 12
        $saved = true;
288 12
289 12
        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
            return $saved;
291 4
        }
292
293 4
        foreach ($this->translations as $translation) {
294 4
            if ($saved && $this->isTranslationDirty($translation)) {
295
                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 4
                    $translation->setConnection($connectionName);
297 4
                }
298 4
299 4
                $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 4
                $saved = $translation->save();
301
            }
302
        }
303 12
304
        return $saved;
305
    }
306 8
307
    public function replicateWithTranslations(array $except = null): Model
308 8
    {
309
        $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 8
        unset($newInstance->translations);
312 8
        foreach ($this->translations as $translation) {
313
            $newTranslation = $translation->replicate();
314
            $newInstance->translations->add($newTranslation);
315 8
        }
316
317 8
        return $newInstance;
318 8
    }
319 8
320 8
    protected function isTranslationDirty(Model $translation): bool
321
    {
322
        $dirtyAttributes = $translation->getDirty();
323
        unset($dirtyAttributes[$this->getLocaleKey()]);
324
325 8
        return count($dirtyAttributes) > 0;
326 8
    }
327 8
328 8
    public function getNewTranslation(string $locale): Model
329 8
    {
330 8
        $modelName = $this->getTranslationModelName();
331
        $translation = new $modelName();
332
        $translation->setAttribute($this->getLocaleKey(), $locale);
333 4
        $this->translations->add($translation);
334
335 4
        return $translation;
336
    }
337
338 4
    public function __isset($key)
339
    {
340 4
        return $this->isTranslationAttribute($key) || parent::__isset($key);
341
    }
342
343 4
    public function scopeTranslatedIn(Builder $query, ?string $locale = null)
344
    {
345 4
        $locale = $locale ?: $this->locale();
346
347
        return $query->whereHas('translations', function (Builder $q) use ($locale) {
348 8
            $q->where($this->getLocaleKey(), '=', $locale);
349
        });
350 8
    }
351
352
    public function scopeNotTranslatedIn(Builder $query, ?string $locale = null)
353 8
    {
354 8
        $locale = $locale ?: $this->locale();
355
356
        return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) {
357 24
            $q->where($this->getLocaleKey(), '=', $locale);
358
        });
359
    }
360 24
361 24
    public function scopeTranslated(Builder $query)
362 8
    {
363
        return $query->has('translations');
364 24
    }
365
366
    public function scopeListsTranslations(Builder $query, string $translationField)
367 12
    {
368
        $withFallback = $this->useFallback();
369 12
        $translationTable = $this->getTranslationsTable();
370
        $localeKey = $this->getLocaleKey();
371
372 12
        $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
            ->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
            ->where($translationTable.'.'.$localeKey, $this->locale());
376 12
        if ($withFallback) {
377 8
            $query->orWhere(function (Builder $q) use ($translationTable, $localeKey) {
378 8
                $q->where($translationTable.'.'.$localeKey, $this->getFallbackLocale())
379 8
                  ->whereNotIn($translationTable.'.'.$this->getRelationKey(), function (QueryBuilder $q) use (
380
                      $translationTable,
381 8
                      $localeKey
382
                  ) {
383
                      $q->select($translationTable.'.'.$this->getRelationKey())
384 4
                        ->from($translationTable)
385 12
                        ->where($translationTable.'.'.$localeKey, $this->locale());
386
                  });
387
            });
388
        }
389 388
    }
390
391 388
    public function scopeWithTranslation(Builder $query)
392
    {
393 388
        $query->with([
394 40
            'translations' => function (Relation $query) {
395
                if ($this->useFallback()) {
396 388
                    $locale = $this->locale();
397
                    $countryFallbackLocale = $this->getFallbackLocale($locale); // e.g. de-DE => de
398
                    $locales = array_unique([$locale, $countryFallbackLocale, $this->getFallbackLocale()]);
399 40
400
                    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 4
403
                return $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale());
404 4
            },
405
        ]);
406 4
    }
407
408
    public function scopeWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null)
409 52
    {
410
        return $query->whereHas('translations', function (Builder $query) use ($translationField, $value, $locale) {
411 52
            $query->where($this->getTranslationsTable().'.'.$translationField, $value);
412
            if ($locale) {
413
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale);
414 4
            }
415
        });
416 4
    }
417
418
    public function scopeOrWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null)
419 4
    {
420
        return $query->orWhereHas('translations', function (Builder $query) use ($translationField, $value, $locale) {
421 4
            $query->where($this->getTranslationsTable().'.'.$translationField, $value);
422
            if ($locale) {
423
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale);
424 292
            }
425
        });
426 292
    }
427
428
    public function scopeWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null)
429 112
    {
430
        return $query->whereHas('translations', function (Builder $query) use ($translationField, $value, $locale) {
431 112
            $query->where($this->getTranslationsTable().'.'.$translationField, 'LIKE', $value);
432
            if ($locale) {
433 112
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale);
434 4
            }
435
        });
436
    }
437 108
438 108
    public function scopeOrWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null)
439 108
    {
440 20
        return $query->orWhereHas('translations', function (Builder $query) use ($translationField, $value, $locale) {
441 20
            $query->where($this->getTranslationsTable().'.'.$translationField, 'LIKE', $value);
442 20
            if ($locale) {
443
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale);
444
            }
445 100
        });
446
    }
447
448
    public function scopeOrderByTranslation(Builder $query, string $translationField, string $sortMethod = 'asc')
449 108
    {
450
        $translationTable = $this->getTranslationsTable();
451
        $localeKey = $this->getLocaleKey();
452 248
        $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
        $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 248
455
        return $query
456
            ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) {
457 140
                $join
458
                    ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName)
459 140
                    ->where($translationTable.'.'.$localeKey, $this->locale());
460
            })
461 140
            ->orderBy($translationTable.'.'.$translationField, $sortMethod)
462 124
            ->select($table.'.*')
463
            ->with('translations');
464
    }
465 140
466
    public function attributesToArray()
467
    {
468 112
        $attributes = parent::attributesToArray();
469
470 112
        if (
471
            (! $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 108
            || self::$autoloadTranslations === false
473
        ) {
474
            return $attributes;
475 100
        }
476
477 100
        $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 100
479
        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 100
            if (in_array($field, $hiddenAttributes)) {
481
                continue;
482
            }
483 388
484
            $attributes[$field] = $this->getAttributeOrFallback(null, $field);
485 388
        }
486 4
487
        return $attributes;
488
    }
489 388
490 388
    public function getTranslationsArray(): array
491
    {
492
        $translations = [];
493 388
494
        foreach ($this->translations as $translation) {
495 388
            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
                $translations[$translation->{$this->getLocaleKey()}][$attr] = $translation->{$attr};
497 388
            }
498 388
        }
499
500
        return $translations;
501 100
    }
502 100
503 100
    private function getTranslationsTable(): string
504 100
    {
505
        return app()->make($this->getTranslationModelName())->getTable();
506
    }
507 100
508 100
    protected function locale(): string
509
    {
510
        if ($this->defaultLocale) {
511
            return $this->defaultLocale;
512 96
        }
513
514
        return $this->getLocalesHelper()->current();
515 388
    }
516
517 388
    public function setDefaultLocale(string $locale)
518 44
    {
519
        $this->defaultLocale = $locale;
520
521 388
        return $this;
522
    }
523
524 92
    public function getDefaultLocale(): ?string
525
    {
526 92
        return $this->defaultLocale;
527
    }
528
529
    /**
530 92
     * @param string|array|null $locales The locales to be deleted
531 92
     */
532
    public function deleteTranslations($locales = null)
533 92
    {
534
        if ($locales === null) {
535 8
            $translations = $this->translations()->get();
536
        } else {
537
            $locales = (array) $locales;
538 92
            $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 88
        }
540
        foreach ($translations as $translation) {
541
            $translation->delete();
542 8
        }
543
544
        // we need to manually "reload" the collection built from the relationship
545 252
        // otherwise $this->translations()->get() would NOT be the same as $this->translations
546
        $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 252
    }
548 28
549 28
    private function getAttributeAndLocale(string $key): array
550
    {
551
        if (Str::contains($key, ':')) {
552
            return explode(':', $key);
553 252
        }
554
555
        return [$key, $this->locale()];
556 28
    }
557
558 28
    private function toArrayAlwaysLoadsTranslations(): bool
559
    {
560 28
        return config('translatable.to_array_always_loads_translations', true);
561
    }
562
563 248
    public static function enableAutoloadTranslations(): void
564
    {
565 248
        self::$autoloadTranslations = true;
566 196
    }
567 164
568
    public static function defaultAutoloadTranslations(): void
569
    {
570
        self::$autoloadTranslations = null;
571 168
    }
572
573
    public static function disableAutoloadTranslations(): void
574 56
    {
575
        self::$autoloadTranslations = false;
576 56
    }
577
578
    protected function getLocalesHelper(): Locales
579 248
    {
580
        return app(Locales::class);
581 248
    }
582
}
583