Completed
Pull Request — master (#179)
by
unknown
01:34
created

HasTranslations::bootHasTranslations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Spatie\Translatable;
4
5
use Illuminate\Support\Str;
6
use Illuminate\Support\Facades\Config;
7
use Spatie\Translatable\Scopes\WhereScope;
8
use Spatie\Translatable\Events\TranslationHasBeenSet;
9
use Spatie\Translatable\Exceptions\AttributeIsNotTranslatable;
10
11
trait HasTranslations
12
{
13
    /**
14
     * The "booting" method of the model.
15
     *
16
     * @return void
17
     */
18
    protected static function bootHasTranslations()
19
    {
20
        static::addGlobalScope(new WhereScope);
21
    }
22
23
    /**
24
     * @param $key
25
     * @return string
26
     */
27
    public function getAttributeValue($key)
28
    {
29
        if (!$this->isTranslatableAttribute($key)) {
30
            return parent::getAttributeValue($key);
31
        }
32
33
        return $this->getTranslation($key, $this->getLocale());
34
    }
35
36
    /**
37
     * @param $key
38
     * @param $value
39
     * @return HasTranslations
0 ignored issues
show
Comprehensibility Bug introduced by
The return type HasTranslations is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?

In PHP traits cannot be used for type-hinting as they do not define a well-defined structure. This is because any class that uses a trait can rename that trait’s methods.

If you would like to return an object that has a guaranteed set of methods, you could create a companion interface that lists these methods explicitly.

Loading history...
40
     */
41
    public function setAttribute($key, $value)
42
    {
43
        // Pass arrays and untranslatable attributes to the parent method.
44
        if (!$this->isTranslatableAttribute($key) || is_array($value)) {
45
            return parent::setAttribute($key, $value);
46
        }
47
48
        // If the attribute is translatable and not already translated, set a
49
        // translation for the current app locale.
50
        return $this->setTranslation($key, $this->getLocale(), $value);
51
    }
52
53
    /**
54
     * @param string $key
55
     * @param string $locale
56
     * @param bool $useFallbackLocale
57
     * @return string
58
     */
59
    public function translate(string $key, string $locale = '', bool $useFallbackLocale = true): string
60
    {
61
        return $this->getTranslation($key, $locale, $useFallbackLocale);
62
    }
63
64
    /**
65
     * @param string $key
66
     * @param string $locale
67
     * @param bool $useFallbackLocale
68
     * @return string
69
     */
70
    public function getTranslation(string $key, string $locale, bool $useFallbackLocale = true)
71
    {
72
        $locale = $this->normalizeLocale($key, $locale, $useFallbackLocale);
73
74
        $translations = $this->getTranslations($key);
75
76
        $translation = $translations[$locale] ?? '';
77
78
        if ($this->hasGetMutator($key)) {
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...
79
            return $this->mutateAttribute($key, $translation);
0 ignored issues
show
Bug introduced by
It seems like mutateAttribute() 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...
80
        }
81
82
        return $translation;
83
    }
84
85
    /**
86
     * @param string $key
87
     * @param string $locale
88
     * @return string
89
     */
90
    public function getTranslationWithFallback(string $key, string $locale): string
91
    {
92
        return $this->getTranslation($key, $locale, true);
93
    }
94
95
    /**
96
     * @param string $key
97
     * @param string $locale
98
     * @return string
99
     */
100
    public function getTranslationWithoutFallback(string $key, string $locale)
101
    {
102
        return $this->getTranslation($key, $locale, false);
103
    }
104
105
    /**
106
     * @param string|null $key
107
     * @return array
108
     */
109
    public function getTranslations(string $key = null): array
110
    {
111
        if ($key !== null) {
112
            $this->guardAgainstNonTranslatableAttribute($key);
113
114
            return array_filter(json_decode($this->getAttributes()[$key] ?? '' ?: '{}', true) ?: [], function ($value) {
0 ignored issues
show
Bug introduced by
It seems like getAttributes() 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
                return $value !== null && $value !== '';
116
            });
117
        }
118
119
        return array_reduce($this->getTranslatableAttributes(), function ($result, $item) {
120
            $result[$item] = $this->getTranslations($item);
121
122
            return $result;
123
        });
124
    }
125
126
    /**
127
     * @param string $key
128
     * @param string $locale
129
     * @param $value
130
     * @return HasTranslations
0 ignored issues
show
Comprehensibility Bug introduced by
The return type HasTranslations is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?

In PHP traits cannot be used for type-hinting as they do not define a well-defined structure. This is because any class that uses a trait can rename that trait’s methods.

If you would like to return an object that has a guaranteed set of methods, you could create a companion interface that lists these methods explicitly.

Loading history...
131
     */
132
    public function setTranslation(string $key, string $locale, $value): self
133
    {
134
        $this->guardAgainstNonTranslatableAttribute($key);
135
136
        $translations = $this->getTranslations($key);
137
138
        $oldValue = $translations[$locale] ?? '';
139
140
        if ($this->hasSetMutator($key)) {
0 ignored issues
show
Bug introduced by
It seems like hasSetMutator() 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...
141
            $method = 'set' . Str::studly($key) . 'Attribute';
142
143
            $this->{$method}($value, $locale);
144
145
            $value = $this->attributes[$key];
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...
146
        }
147
148
        $translations[$locale] = $value;
149
150
        $this->attributes[$key] = $this->asJson($translations);
0 ignored issues
show
Bug introduced by
It seems like asJson() 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...
151
152
        event(new TranslationHasBeenSet($this, $key, $locale, $oldValue, $value));
153
154
        return $this;
155
    }
156
157
    /**
158
     * @param string $key
159
     * @param array $translations
160
     * @return HasTranslations
0 ignored issues
show
Comprehensibility Bug introduced by
The return type HasTranslations is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?

In PHP traits cannot be used for type-hinting as they do not define a well-defined structure. This is because any class that uses a trait can rename that trait’s methods.

If you would like to return an object that has a guaranteed set of methods, you could create a companion interface that lists these methods explicitly.

Loading history...
161
     */
162
    public function setTranslations(string $key, array $translations): self
163
    {
164
        $this->guardAgainstNonTranslatableAttribute($key);
165
166
        foreach ($translations as $locale => $translation) {
167
            $this->setTranslation($key, $locale, $translation);
168
        }
169
170
        return $this;
171
    }
172
173
    /**
174
     * @param string $key
175
     * @param string $locale
176
     * @return HasTranslations
0 ignored issues
show
Comprehensibility Bug introduced by
The return type HasTranslations is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?

In PHP traits cannot be used for type-hinting as they do not define a well-defined structure. This is because any class that uses a trait can rename that trait’s methods.

If you would like to return an object that has a guaranteed set of methods, you could create a companion interface that lists these methods explicitly.

Loading history...
177
     */
178
    public function forgetTranslation(string $key, string $locale): self
179
    {
180
        $translations = $this->getTranslations($key);
181
182
        unset($translations[$locale]);
183
184
        $this->setAttribute($key, $translations);
185
186
        return $this;
187
    }
188
189
    /**
190
     * @param string $locale
191
     * @return HasTranslations
0 ignored issues
show
Comprehensibility Bug introduced by
The return type HasTranslations is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?

In PHP traits cannot be used for type-hinting as they do not define a well-defined structure. This is because any class that uses a trait can rename that trait’s methods.

If you would like to return an object that has a guaranteed set of methods, you could create a companion interface that lists these methods explicitly.

Loading history...
192
     */
193
    public function forgetAllTranslations(string $locale): self
194
    {
195
        collect($this->getTranslatableAttributes())->each(function (string $attribute) use ($locale) {
196
            $this->forgetTranslation($attribute, $locale);
197
        });
198
199
        return $this;
200
    }
201
202
    /**
203
     * @param string $key
204
     * @return array
205
     */
206
    public function getTranslatedLocales(string $key): array
207
    {
208
        return array_keys($this->getTranslations($key));
209
    }
210
211
    /**
212
     * @param string $key
213
     * @return bool
214
     */
215
    public function isTranslatableAttribute(string $key): bool
216
    {
217
        return in_array($key, $this->getTranslatableAttributes());
218
    }
219
220
    /**
221
     * @param string $key
222
     * @param string|null $locale
223
     * @return bool
224
     */
225
    public function hasTranslation(string $key, string $locale = null): bool
226
    {
227
        $locale = $locale ?: $this->getLocale();
228
229
        return isset($this->getTranslations($key)[$locale]);
230
    }
231
232
    /**
233
     * @param string $key
234
     */
235
    protected function guardAgainstNonTranslatableAttribute(string $key)
236
    {
237
        if (!$this->isTranslatableAttribute($key)) {
238
            throw AttributeIsNotTranslatable::make($key, $this);
239
        }
240
    }
241
242
    /**
243
     * @param string $key
244
     * @param string $locale
245
     * @param bool $useFallbackLocale
246
     * @return string
247
     */
248
    protected function normalizeLocale(string $key, string $locale, bool $useFallbackLocale): string
249
    {
250
        if (in_array($locale, $this->getTranslatedLocales($key))) {
251
            return $locale;
252
        }
253
254
        if (!$useFallbackLocale) {
255
            return $locale;
256
        }
257
258
        if (!is_null($fallbackLocale = Config::get('translatable.fallback_locale'))) {
259
            return $fallbackLocale;
260
        }
261
262
        if (!is_null($fallbackLocale = Config::get('app.fallback_locale'))) {
263
            return $fallbackLocale;
264
        }
265
266
        return $locale;
267
    }
268
269
    /**
270
     * @return string
271
     */
272
    protected function getLocale(): string
273
    {
274
        return Config::get('app.locale');
275
    }
276
277
    /**
278
     * @return array
279
     */
280
    public function getTranslatableAttributes(): array
281
    {
282
        return is_array($this->translatable)
0 ignored issues
show
Bug introduced by
The property translatable 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...
283
            ? $this->translatable
284
            : [];
285
    }
286
287
    /**
288
     * @return array
289
     */
290
    public function getTranslationsAttribute(): array
291
    {
292
        return collect($this->getTranslatableAttributes())
293
            ->mapWithKeys(function (string $key) {
294
                return [$key => $this->getTranslations($key)];
295
            })
296
            ->toArray();
297
    }
298
299
    /**
300
     * @return array
301
     */
302
    public function getCasts(): array
303
    {
304
        return array_merge(
305
            parent::getCasts(),
306
            array_fill_keys($this->getTranslatableAttributes(), 'array')
307
        );
308
    }
309
}
310