Test Setup Failed
Push — master ( 35b383...476e0e )
by Dimitrios
05:52 queued 02:23
created

Translatable::attributesToArray()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2888
c 0
b 0
f 0
cc 5
nc 4
nop 0
1
<?php
2
3
namespace Dimsav\Translatable;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Database\Query\JoinClause;
8
use Illuminate\Database\Eloquent\Relations\Relation;
9
use Illuminate\Database\Query\Builder as QueryBuilder;
10
use Dimsav\Translatable\Exception\LocalesNotDefinedException;
11
12
trait Translatable
13
{
14
    protected $defaultLocale;
15
16
    /**
17
     * Alias for getTranslation().
18
     *
19
     * @param string|null $locale
20
     * @param bool        $withFallback
21
     *
22
     * @return \Illuminate\Database\Eloquent\Model|null
23
     */
24
    public function translate($locale = null, $withFallback = false)
25
    {
26
        return $this->getTranslation($locale, $withFallback);
27
    }
28
29
    /**
30
     * Alias for getTranslation().
31
     *
32
     * @param string $locale
33
     *
34
     * @return \Illuminate\Database\Eloquent\Model|null
35
     */
36
    public function translateOrDefault($locale)
37
    {
38
        return $this->getTranslation($locale, true);
39
    }
40
41
    /**
42
     * Alias for getTranslationOrNew().
43
     *
44
     * @param string $locale
45
     *
46
     * @return \Illuminate\Database\Eloquent\Model|null
47
     */
48
    public function translateOrNew($locale)
49
    {
50
        return $this->getTranslationOrNew($locale);
51
    }
52
53
    /**
54
     * @param string|null $locale
55
     * @param bool        $withFallback
56
     *
57
     * @return \Illuminate\Database\Eloquent\Model|null
58
     */
59
    public function getTranslation($locale = null, $withFallback = null)
60
    {
61
        $configFallbackLocale = $this->getFallbackLocale();
62
        $locale = $locale ?: $this->locale();
63
        $withFallback = $withFallback === null ? $this->useFallback() : $withFallback;
64
        $fallbackLocale = $this->getFallbackLocale($locale);
65
66
        if ($translation = $this->getTranslationByLocaleKey($locale)) {
67
            return $translation;
68
        }
69
        if ($withFallback && $fallbackLocale) {
70
            if ($translation = $this->getTranslationByLocaleKey($fallbackLocale)) {
71
                return $translation;
72
            }
73
            if ($fallbackLocale !== $configFallbackLocale && $translation = $this->getTranslationByLocaleKey($configFallbackLocale)) {
74
                return $translation;
75
            }
76
        }
77
78
        return null;
79
    }
80
81
    /**
82
     * @param string|null $locale
83
     *
84
     * @return bool
85
     */
86
    public function hasTranslation($locale = null)
87
    {
88
        $locale = $locale ?: $this->locale();
89
90
        foreach ($this->translations as $translation) {
0 ignored issues
show
Bug introduced by
The property translations 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...
91
            if ($translation->getAttribute($this->getLocaleKey()) == $locale) {
92
                return true;
93
            }
94
        }
95
96
        return false;
97
    }
98
99
    /**
100
     * @return string
101
     */
102
    public function getTranslationModelName()
103
    {
104
        return $this->translationModel ?: $this->getTranslationModelNameDefault();
0 ignored issues
show
Bug introduced by
The property translationModel 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...
105
    }
106
107
    /**
108
     * @return string
109
     */
110
    public function getTranslationModelNameDefault()
111
    {
112
        return get_class($this).config('translatable.translation_suffix', 'Translation');
113
    }
114
115
    /**
116
     * @return string
117
     */
118
    public function getRelationKey()
119
    {
120
        if ($this->translationForeignKey) {
121
            $key = $this->translationForeignKey;
0 ignored issues
show
Bug introduced by
The property translationForeignKey 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...
122
        } elseif ($this->primaryKey !== 'id') {
0 ignored issues
show
Bug introduced by
The property primaryKey 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...
123
            $key = $this->primaryKey;
124
        } else {
125
            $key = $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...
126
        }
127
128
        return $key;
129
    }
130
131
    /**
132
     * @return string
133
     */
134
    public function getLocaleKey()
135
    {
136
        return $this->localeKey ?: config('translatable.locale_key', 'locale');
0 ignored issues
show
Bug introduced by
The property localeKey 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...
137
    }
138
139
    /**
140
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
141
     */
142
    public function translations()
143
    {
144
        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...
145
    }
146
147
    /**
148
     * @return bool
149
     */
150
    private function usePropertyFallback()
151
    {
152
        return $this->useFallback() && config('translatable.use_property_fallback', false);
153
    }
154
155
    /**
156
     * Returns the attribute value from fallback translation if value of attribute
157
     * is empty and the property fallback is enabled in the configuration.
158
     * in model.
159
     * @param $locale
160
     * @param $attribute
161
     * @return mixed
162
     */
163
    private function getAttributeOrFallback($locale, $attribute)
164
    {
165
        $translation = $this->getTranslation($locale);
166
167
        if (
168
            (
169
                ! $translation instanceof Model ||
170
                empty($translation->$attribute)
171
            ) &&
172
            $this->usePropertyFallback()
173
        ) {
174
            $translation = $this->getTranslation($this->getFallbackLocale(), true);
175
        }
176
177
        if ($translation instanceof Model) {
178
            return $translation->$attribute;
179
        }
180
181
        return null;
182
    }
183
184
    /**
185
     * @param string $key
186
     *
187
     * @return mixed
188
     */
189
    public function getAttribute($key)
190
    {
191
        list($attribute, $locale) = $this->getAttributeAndLocale($key);
192
193
        if ($this->isTranslationAttribute($attribute)) {
194
            if ($this->getTranslation($locale) === null) {
195
                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...
196
            }
197
198
            // If the given $attribute has a mutator, we push it to $attributes and then call getAttributeValue
199
            // on it. This way, we can use Eloquent's checking for Mutation, type casting, and
200
            // Date fields.
201
            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...
202
                $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...
203
204
                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...
205
            }
206
207
            return $this->getAttributeOrFallback($locale, $attribute);
208
        }
209
210
        return parent::getAttribute($key);
211
    }
212
213
    /**
214
     * @param string $key
215
     * @param mixed  $value
216
     *
217
     * @return $this
218
     */
219
    public function setAttribute($key, $value)
220
    {
221
        list($attribute, $locale) = $this->getAttributeAndLocale($key);
222
223
        if ($this->isTranslationAttribute($attribute)) {
224
            $this->getTranslationOrNew($locale)->$attribute = $value;
225
        } else {
226
            return parent::setAttribute($key, $value);
227
        }
228
229
        return $this;
230
    }
231
232
    /**
233
     * @param array $options
234
     *
235
     * @return bool
236
     */
237
    public function save(array $options = [])
238
    {
239
        if ($this->exists) {
0 ignored issues
show
Bug introduced by
The property exists 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...
240
            if ($this->isDirty()) {
0 ignored issues
show
Bug introduced by
It seems like isDirty() 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...
241
                // If $this->exists and dirty, parent::save() has to return true. If not,
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
242
                // an error has occurred. Therefore we shouldn't save the translations.
243
                if (parent::save($options)) {
244
                    return $this->saveTranslations();
245
                }
246
247
                return false;
248
            } else {
249
                // If $this->exists and not dirty, parent::save() skips saving and returns
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
250
                // false. So we have to save the translations
251
                if ($this->fireModelEvent('saving') === false) {
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() 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...
252
                    return false;
253
                }
254
255
                if ($saved = $this->saveTranslations()) {
256
                    $this->fireModelEvent('saved', false);
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() 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...
257
                    $this->fireModelEvent('updated', false);
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() 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...
258
                }
259
260
                return $saved;
261
            }
262
        } elseif (parent::save($options)) {
263
            // We save the translations only if the instance is saved in the database.
264
            return $this->saveTranslations();
265
        }
266
267
        return false;
268
    }
269
270
    /**
271
     * @param string $locale
272
     *
273
     * @return \Illuminate\Database\Eloquent\Model
274
     */
275
    protected function getTranslationOrNew($locale)
276
    {
277
        if (($translation = $this->getTranslation($locale, false)) === null) {
278
            $translation = $this->getNewTranslation($locale);
279
        }
280
281
        return $translation;
282
    }
283
284
    /**
285
     * @param array $attributes
286
     *
287
     * @throws \Illuminate\Database\Eloquent\MassAssignmentException
288
     * @return $this
289
     */
290
    public function fill(array $attributes)
291
    {
292
        foreach ($attributes as $key => $values) {
293
            if ($this->isKeyALocale($key)) {
294
                $this->getTranslationOrNew($key)->fill($values);
295
                unset($attributes[$key]);
296
            } else {
297
                list($attribute, $locale) = $this->getAttributeAndLocale($key);
298
                if ($this->isTranslationAttribute($attribute) and $this->isKeyALocale($locale)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
299
                    $this->getTranslationOrNew($locale)->fill([$attribute => $values]);
300
                    unset($attributes[$key]);
301
                }
302
            }
303
        }
304
305
        return parent::fill($attributes);
306
    }
307
308
    /**
309
     * @param string $key
310
     */
311
    private function getTranslationByLocaleKey($key)
312
    {
313
        foreach ($this->translations as $translation) {
314
            if ($translation->getAttribute($this->getLocaleKey()) == $key) {
315
                return $translation;
316
            }
317
        }
318
319
        return null;
320
    }
321
322
    /**
323
     * @param null $locale
324
     *
325
     * @return string
326
     */
327
    private function getFallbackLocale($locale = null)
328
    {
329
        if ($locale && $this->isLocaleCountryBased($locale)) {
330
            if ($fallback = $this->getLanguageFromCountryBasedLocale($locale)) {
331
                return $fallback;
332
            }
333
        }
334
335
        return config('translatable.fallback_locale');
336
    }
337
338
    /**
339
     * @param $locale
340
     *
341
     * @return bool
342
     */
343
    private function isLocaleCountryBased($locale)
344
    {
345
        return strpos($locale, $this->getLocaleSeparator()) !== false;
346
    }
347
348
    /**
349
     * @param $locale
350
     *
351
     * @return string
352
     */
353
    private function getLanguageFromCountryBasedLocale($locale)
354
    {
355
        $parts = explode($this->getLocaleSeparator(), $locale);
356
357
        return array_get($parts, 0);
358
    }
359
360
    /**
361
     * @return bool|null
362
     */
363
    private function useFallback()
364
    {
365
        if (isset($this->useTranslationFallback) && $this->useTranslationFallback !== null) {
366
            return $this->useTranslationFallback;
0 ignored issues
show
Bug introduced by
The property useTranslationFallback 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...
367
        }
368
369
        return config('translatable.use_fallback');
370
    }
371
372
    /**
373
     * @param string $key
374
     *
375
     * @return bool
376
     */
377
    public function isTranslationAttribute($key)
378
    {
379
        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...
380
    }
381
382
    /**
383
     * @param string $key
384
     *
385
     * @throws \Dimsav\Translatable\Exception\LocalesNotDefinedException
386
     * @return bool
387
     */
388
    protected function isKeyALocale($key)
389
    {
390
        $locales = $this->getLocales();
391
392
        return in_array($key, $locales);
393
    }
394
395
    /**
396
     * @throws \Dimsav\Translatable\Exception\LocalesNotDefinedException
397
     * @return array
398
     */
399
    protected function getLocales()
400
    {
401
        $localesConfig = (array) config('translatable.locales');
402
403
        if (empty($localesConfig)) {
404
            throw new LocalesNotDefinedException('Please make sure you have run "php artisan config:publish dimsav/laravel-translatable" '.
405
                ' and that the locales configuration is defined.');
406
        }
407
408
        $locales = [];
409
        foreach ($localesConfig as $key => $locale) {
410
            if (is_array($locale)) {
411
                $locales[] = $key;
412
                foreach ($locale as $countryLocale) {
413
                    $locales[] = $key.$this->getLocaleSeparator().$countryLocale;
414
                }
415
            } else {
416
                $locales[] = $locale;
417
            }
418
        }
419
420
        return $locales;
421
    }
422
423
    /**
424
     * @return string
425
     */
426
    protected function getLocaleSeparator()
427
    {
428
        return config('translatable.locale_separator', '-');
429
    }
430
431
    /**
432
     * @return bool
433
     */
434
    protected function saveTranslations()
435
    {
436
        $saved = true;
437
        foreach ($this->translations as $translation) {
438
            if ($saved && $this->isTranslationDirty($translation)) {
439
                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...
440
                    $translation->setConnection($connectionName);
441
                }
442
443
                $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...
444
                $saved = $translation->save();
445
            }
446
        }
447
448
        return $saved;
449
    }
450
451
    /**
452
     * @param array
453
     *
454
     * @return \Illuminate\Database\Eloquent\Model
455
     */
456
    public function replicateWithTranslations(array $except = null)
457
    {
458
        $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...
459
460
        unset($newInstance->translations);
461
        foreach ($this->translations as $translation) {
462
            $newTranslation = $translation->replicate();
463
            $newInstance->translations->add($newTranslation);
464
        }
465
466
        return  $newInstance;
467
    }
468
469
    /**
470
     * @param \Illuminate\Database\Eloquent\Model $translation
471
     *
472
     * @return bool
473
     */
474
    protected function isTranslationDirty(Model $translation)
475
    {
476
        $dirtyAttributes = $translation->getDirty();
477
        unset($dirtyAttributes[$this->getLocaleKey()]);
478
479
        return count($dirtyAttributes) > 0;
480
    }
481
482
    /**
483
     * @param string $locale
484
     *
485
     * @return \Illuminate\Database\Eloquent\Model
486
     */
487
    public function getNewTranslation($locale)
488
    {
489
        $modelName = $this->getTranslationModelName();
490
        $translation = new $modelName();
491
        $translation->setAttribute($this->getLocaleKey(), $locale);
492
        $this->translations->add($translation);
493
494
        return $translation;
495
    }
496
497
    /**
498
     * @param $key
499
     *
500
     * @return bool
501
     */
502
    public function __isset($key)
503
    {
504
        return $this->isTranslationAttribute($key) || parent::__isset($key);
505
    }
506
507
    /**
508
     * @param \Illuminate\Database\Eloquent\Builder $query
509
     * @param string                                $locale
510
     *
511
     * @return \Illuminate\Database\Eloquent\Builder|static
512
     */
513 View Code Duplication
    public function scopeTranslatedIn(Builder $query, $locale = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
514
    {
515
        $locale = $locale ?: $this->locale();
516
517
        return $query->whereHas('translations', function (Builder $q) use ($locale) {
518
            $q->where($this->getLocaleKey(), '=', $locale);
519
        });
520
    }
521
522
    /**
523
     * @param \Illuminate\Database\Eloquent\Builder $query
524
     * @param string                                $locale
525
     *
526
     * @return \Illuminate\Database\Eloquent\Builder|static
527
     */
528 View Code Duplication
    public function scopeNotTranslatedIn(Builder $query, $locale = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
529
    {
530
        $locale = $locale ?: $this->locale();
531
532
        return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) {
533
            $q->where($this->getLocaleKey(), '=', $locale);
534
        });
535
    }
536
537
    /**
538
     * @param \Illuminate\Database\Eloquent\Builder $query
539
     *
540
     * @return \Illuminate\Database\Eloquent\Builder|static
541
     */
542
    public function scopeTranslated(Builder $query)
543
    {
544
        return $query->has('translations');
545
    }
546
547
    /**
548
     * Adds scope to get a list of translated attributes, using the current locale.
549
     * Example usage: Country::listsTranslations('name')->get()->toArray()
550
     * Will return an array with items:
551
     *  [
552
     *      'id' => '1',                // The id of country
553
     *      'name' => 'Griechenland'    // The translated name
554
     *  ].
555
     *
556
     * @param \Illuminate\Database\Eloquent\Builder $query
557
     * @param string                                $translationField
558
     */
559
    public function scopeListsTranslations(Builder $query, $translationField)
560
    {
561
        $withFallback = $this->useFallback();
562
        $translationTable = $this->getTranslationsTable();
563
        $localeKey = $this->getLocaleKey();
564
565
        $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...
566
            ->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...
567
            ->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...
568
            ->where($translationTable.'.'.$localeKey, $this->locale());
569
        if ($withFallback) {
570
            $query->orWhere(function (Builder $q) use ($translationTable, $localeKey) {
571
                $q->where($translationTable.'.'.$localeKey, $this->getFallbackLocale())
572
                  ->whereNotIn($translationTable.'.'.$this->getRelationKey(), function (QueryBuilder $q) use (
573
                      $translationTable,
574
                      $localeKey
575
                  ) {
576
                      $q->select($translationTable.'.'.$this->getRelationKey())
577
                        ->from($translationTable)
578
                        ->where($translationTable.'.'.$localeKey, $this->locale());
579
                  });
580
            });
581
        }
582
    }
583
584
    /**
585
     * This scope eager loads the translations for the default and the fallback locale only.
586
     * We can use this as a shortcut to improve performance in our application.
587
     *
588
     * @param Builder $query
589
     */
590
    public function scopeWithTranslation(Builder $query)
591
    {
592
        $query->with([
593
            'translations' => function (Relation $query) {
594
                if ($this->useFallback()) {
595
                    $locale = $this->locale();
596
                    $countryFallbackLocale = $this->getFallbackLocale($locale); // e.g. de-DE => de
597
                    $locales = array_unique([$locale, $countryFallbackLocale, $this->getFallbackLocale()]);
598
599
                    return $query->whereIn($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locales);
600
                }
601
602
                return $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale());
603
            },
604
        ]);
605
    }
606
607
    /**
608
     * This scope filters results by checking the translation fields.
609
     *
610
     * @param \Illuminate\Database\Eloquent\Builder $query
611
     * @param string                                $key
612
     * @param string                                $value
613
     * @param string                                $locale
614
     *
615
     * @return \Illuminate\Database\Eloquent\Builder|static
616
     */
617 View Code Duplication
    public function scopeWhereTranslation(Builder $query, $key, $value, $locale = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
618
    {
619
        return $query->whereHas('translations', function (Builder $query) use ($key, $value, $locale) {
620
            $query->where($this->getTranslationsTable().'.'.$key, $value);
621
            if ($locale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locale of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
622
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale);
623
            }
624
        });
625
    }
626
627
    /**
628
     * This scope filters results by checking the translation fields.
629
     *
630
     * @param \Illuminate\Database\Eloquent\Builder $query
631
     * @param string                                $key
632
     * @param string                                $value
633
     * @param string                                $locale
634
     *
635
     * @return \Illuminate\Database\Eloquent\Builder|static
636
     */
637 View Code Duplication
    public function scopeOrWhereTranslation(Builder $query, $key, $value, $locale = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
638
    {
639
        return $query->orWhereHas('translations', function (Builder $query) use ($key, $value, $locale) {
640
            $query->where($this->getTranslationsTable().'.'.$key, $value);
641
            if ($locale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locale of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
642
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale);
643
            }
644
        });
645
    }
646
647
    /**
648
     * This scope filters results by checking the translation fields.
649
     *
650
     * @param \Illuminate\Database\Eloquent\Builder $query
651
     * @param string                                $key
652
     * @param string                                $value
653
     * @param string                                $locale
654
     *
655
     * @return \Illuminate\Database\Eloquent\Builder|static
656
     */
657 View Code Duplication
    public function scopeWhereTranslationLike(Builder $query, $key, $value, $locale = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
658
    {
659
        return $query->whereHas('translations', function (Builder $query) use ($key, $value, $locale) {
660
            $query->where($this->getTranslationsTable().'.'.$key, 'LIKE', $value);
661
            if ($locale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locale of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
662
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale);
663
            }
664
        });
665
    }
666
667
    /**
668
     * This scope filters results by checking the translation fields.
669
     *
670
     * @param \Illuminate\Database\Eloquent\Builder $query
671
     * @param string                                $key
672
     * @param string                                $value
673
     * @param string                                $locale
674
     *
675
     * @return \Illuminate\Database\Eloquent\Builder|static
676
     */
677 View Code Duplication
    public function scopeOrWhereTranslationLike(Builder $query, $key, $value, $locale = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
678
    {
679
        return $query->orWhereHas('translations', function (Builder $query) use ($key, $value, $locale) {
680
            $query->where($this->getTranslationsTable().'.'.$key, 'LIKE', $value);
681
            if ($locale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locale of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
682
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale);
683
            }
684
        });
685
    }
686
687
    /**
688
     * This scope sorts results by the given translation field.
689
     *
690
     * @param \Illuminate\Database\Eloquent\Builder $query
691
     * @param string                                $key
692
     * @param string                                $sortmethod
693
     *
694
     * @return \Illuminate\Database\Eloquent\Builder|static
695
     */
696
    public function scopeOrderByTranslation(Builder $query, $key, $sortmethod = 'asc')
697
    {
698
        $translationTable = $this->getTranslationsTable();
699
        $localeKey = $this->getLocaleKey();
700
        $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...
701
        $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...
702
703
        return $query
704
            ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) {
705
                $join
706
                    ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName)
707
                    ->where($translationTable.'.'.$localeKey, $this->locale());
708
            })
709
            ->orderBy($translationTable.'.'.$key, $sortmethod)
710
            ->select($table.'.*')
711
            ->with('translations');
712
    }
713
714
    /**
715
     * @return array
716
     */
717
    public function attributesToArray()
718
    {
719
        $attributes = parent::attributesToArray();
720
721
        if (! $this->relationLoaded('translations') && ! $this->toArrayAlwaysLoadsTranslations()) {
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...
722
            return $attributes;
723
        }
724
725
        $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...
726
727
        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...
728
            if (in_array($field, $hiddenAttributes)) {
729
                continue;
730
            }
731
732
            $attributes[$field] = $this->getAttributeOrFallback(null, $field);
733
        }
734
735
        return $attributes;
736
    }
737
738
    /**
739
     * @return array
740
     */
741
    public function getTranslationsArray()
742
    {
743
        $translations = [];
744
745
        foreach ($this->translations as $translation) {
746
            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...
747
                $translations[$translation->{$this->getLocaleKey()}][$attr] = $translation->{$attr};
748
            }
749
        }
750
751
        return $translations;
752
    }
753
754
    /**
755
     * @return string
756
     */
757
    private function getTranslationsTable()
758
    {
759
        return app()->make($this->getTranslationModelName())->getTable();
760
    }
761
762
    /**
763
     * @return string
764
     */
765
    protected function locale()
766
    {
767
        if ($this->defaultLocale) {
768
            return $this->defaultLocale;
769
        }
770
771
        return config('translatable.locale')
772
            ?: app()->make('translator')->getLocale();
773
    }
774
775
    /**
776
     * Set the default locale on the model.
777
     *
778
     * @param $locale
779
     *
780
     * @return $this
781
     */
782
    public function setDefaultLocale($locale)
783
    {
784
        $this->defaultLocale = $locale;
785
786
        return $this;
787
    }
788
789
    /**
790
     * Get the default locale on the model.
791
     *
792
     * @return mixed
793
     */
794
    public function getDefaultLocale()
795
    {
796
        return $this->defaultLocale;
797
    }
798
799
    /**
800
     * Deletes all translations for this model.
801
     *
802
     * @param string|array|null $locales The locales to be deleted (array or single string)
803
     *                                   (e.g., ["en", "de"] would remove these translations).
804
     */
805
    public function deleteTranslations($locales = null)
806
    {
807
        if ($locales === null) {
808
            $translations = $this->translations()->get();
809
        } else {
810
            $locales = (array) $locales;
811
            $translations = $this->translations()->whereIn($this->getLocaleKey(), $locales)->get();
812
        }
813
        foreach ($translations as $translation) {
814
            $translation->delete();
815
        }
816
817
        // we need to manually "reload" the collection built from the relationship
818
        // otherwise $this->translations()->get() would NOT be the same as $this->translations
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
819
        $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...
820
    }
821
822
    /**
823
     * @param $key
824
     *
825
     * @return array
826
     */
827
    private function getAttributeAndLocale($key)
828
    {
829
        if (str_contains($key, ':')) {
830
            return explode(':', $key);
831
        }
832
833
        return [$key, $this->locale()];
834
    }
835
836
    /**
837
     * @return bool
838
     */
839
    private function toArrayAlwaysLoadsTranslations()
840
    {
841
        return config('translatable.to_array_always_loads_translations', true);
842
    }
843
}
844