TranslateableTrait::setAttribute()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
cc 4
nc 5
nop 2
1
<?php
2
3
namespace RichanFongdasen\I18n\Eloquent\Extensions;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Relations\HasMany;
8
use Illuminate\Support\Str;
9
use RichanFongdasen\I18n\Eloquent\Observer;
10
use RichanFongdasen\I18n\Eloquent\TranslationModel;
11
use RichanFongdasen\I18n\Eloquent\TranslationScope;
12
use RichanFongdasen\I18n\Locale;
13
14
trait TranslateableTrait
15
{
16
    /**
17
     * Fallback translation object. The model will use
18
     * the value from this object, if there was no
19
     * translated attribute value for current locale.
20
     *
21
     * @var \RichanFongdasen\I18n\Eloquent\TranslationModel
22
     */
23
    protected $fallbackTranslation;
24
25
    /**
26
     * Current selected locale.
27
     *
28
     * @var \RichanFongdasen\I18n\Locale
29
     */
30
    protected $locale;
31
32
    /**
33
     * Default language key.
34
     *
35
     * @var string
36
     */
37
    protected static $localeKey;
38
39
    /**
40
     * Translation object for the current selected
41
     * locale.
42
     *
43
     * @var \RichanFongdasen\I18n\Eloquent\TranslationModel
44
     */
45
    protected $translation;
46
47
    /**
48
     * Convert the model's attributes to an array.
49
     *
50
     * @return array
51
     */
52
    public function attributesToArray(): array
53
    {
54
        $attributes = parent::attributesToArray();
55
56
        foreach ($this->getTranslateableAttributes() as $key) {
57
            $attributes[$key] = $this->getAttribute($key);
58
        }
59
60
        return $attributes;
61
    }
62
63
    /**
64
     * Boot the TranslateableTrait model extension.
65
     *
66
     * @return void
67
     */
68
    public static function bootTranslateableTrait(): void
69
    {
70
        static::addGlobalScope(new TranslationScope());
71
        static::observe(app(Observer::class));
72
        static::$localeKey = \I18n::getConfig('language_key');
73
    }
74
75
    /**
76
     * Create a new translation for the given locale.
77
     *
78
     * @param \RichanFongdasen\I18n\Locale $locale
79
     *
80
     * @return \RichanFongdasen\I18n\Eloquent\TranslationModel
81
     */
82
    protected function createTranslation(Locale $locale): TranslationModel
83
    {
84
        $conditions = [
85
            $this->getForeignKey() => $this->getKey(),
86
            'locale'               => $locale->{self::$localeKey},
87
        ];
88
89
        $model = (new TranslationModel())
90
            ->fill($conditions);
91
92
        $this->translations->push($model);
93
94
        return $model;
95
    }
96
97
    /**
98
     * Fill the model with an array of attributes.
99
     *
100
     * @param array $attributes
101
     *
102
     * @return $this
0 ignored issues
show
Documentation introduced by
Should the return type not be \self?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
103
     */
104
    public function fill(array $attributes): self
105
    {
106
        foreach ($this->getTranslateableAttributes() as $key) {
107
            if (isset($attributes[$key])) {
108
                $this->setTranslateableAttribute($key, $attributes[$key]);
109
            }
110
        }
111
112
        return parent::fill($attributes);
113
    }
114
115
    /**
116
     * Get an attribute from the model.
117
     *
118
     * @param string $key
119
     *
120
     * @return mixed
121
     */
122
    public function getAttribute($key)
123
    {
124
        if ($this->isTranslateableAttribute($key)) {
125
            return $this->getTranslated($key);
126
        }
127
128
        return parent::getAttribute($key);
129
    }
130
131
    /**
132
     * Get join table attributes.
133
     *
134
     * @return string[]
135
     */
136
    protected function getJoinAttributes(): array
137
    {
138
        $attributes = [$this->getTable().'.*'];
139
140
        foreach ($this->getTranslateableAttributes() as $attribute) {
141
            $attributes[] = $this->getTranslationTable().'.'.$attribute;
142
        }
143
144
        return $attributes;
145
    }
146
147
    /**
148
     * Get all of translateable attributes.
149
     *
150
     * @return array
151
     */
152
    public function getTranslateableAttributes(): array
153
    {
154
        return is_array($this->translateFields) ? $this->translateFields : [];
155
    }
156
157
    /**
158
     * Get a translated attribute value from
159
     * the model.
160
     *
161
     * @param string $key
162
     *
163
     * @return mixed
164
     */
165
    protected function getTranslated(string $key)
166
    {
167
        if (!$this->locale) {
168
            $this->translate();
169
        }
170
171
        if ($result = $this->getTranslatedValue($this->translation, $key)) {
172
            return $result;
173
        }
174
175
        return $this->getTranslatedValue($this->fallbackTranslation, $key);
176
    }
177
178
    /**
179
     * Get a translated attribute value from
180
     * the given translation model.
181
     *
182
     * @param mixed  $translation
183
     * @param string $key
184
     *
185
     * @return mixed
186
     */
187
    protected function getTranslatedValue($translation, string $key)
188
    {
189
        if (!$translation instanceof Model) {
190
            return null;
191
        }
192
193
        return $translation->getAttribute($key);
194
    }
195
196
    /**
197
     * Get existing translation or create a
198
     * new one.
199
     *
200
     * @param \RichanFongdasen\I18n\Locale $locale
201
     *
202
     * @return \RichanFongdasen\I18n\Eloquent\TranslationModel
203
     */
204
    protected function getTranslation(Locale $locale): TranslationModel
205
    {
206
        $this->translate($locale);
207
208
        if ($this->translation) {
209
            return $this->translation;
210
        }
211
212
        return $this->translation = $this->createTranslation($locale);
213
    }
214
215
    /**
216
     * Find locale object based on the given
217
     * key value.
218
     *
219
     * @param mixed $key
220
     *
221
     * @return \RichanFongdasen\I18n\Locale
222
     */
223
    protected function getTranslationLocale($key = null): Locale
224
    {
225
        if ($key instanceof Locale) {
226
            return $key;
227
        }
228
229
        if (empty($key)) {
230
            $key = \App::getLocale();
231
        }
232
233
        if (!$locale = \I18n::getLocale($key)) {
234
            $locale = \I18n::defaultLocale();
235
        }
236
237
        return $locale;
238
    }
239
240
    /**
241
     * Get translation table.
242
     *
243
     * @return string
244
     */
245
    public function getTranslationTable(): string
246
    {
247
        if (!isset($this->translationTable)) {
248
            $suffix = \I18n::getConfig('translation_table_suffix');
249
250
            return Str::snake(class_basename($this)).'_'.$suffix;
251
        }
252
253
        return $this->translationTable;
254
    }
255
256
    /**
257
     * Check whether the given attribute key is
258
     * translateable.
259
     *
260
     * @param string $key
261
     *
262
     * @return bool
263
     */
264
    protected function isTranslateableAttribute(string $key): bool
265
    {
266
        $fields = $this->getTranslateableAttributes();
267
268
        return in_array($key, $fields);
269
    }
270
271
    /**
272
     * Add and additional scope to join the translation table
273
     * and make the translation content more easier to search.
274
     *
275
     * @param \Illuminate\Database\Eloquent\Builder $query
276
     *
277
     * @return \Illuminate\Database\Eloquent\Builder
278
     */
279
    public function scopeJoinTranslation(Builder $query): Builder
280
    {
281
        $attributes = $this->getJoinAttributes();
282
283
        return $query->leftJoin(
284
            $this->getTranslationTable(),
285
            $this->getTable().'.'.$this->getKeyName(),
286
            '=',
287
            $this->getTranslationTable().'.'.$this->getForeignKey()
288
        )->select($attributes)
289
        ->where($this->getTranslationTable().'.locale', \App::getLocale());
290
    }
291
292
    /**
293
     * Set a given attribute on the model.
294
     *
295
     * @param string $key
296
     * @param mixed  $value
297
     *
298
     * @return mixed
299
     */
300
    public function setAttribute($key, $value)
301
    {
302
        if ($this->isTranslateableAttribute($key)) {
303
            if (!$this->locale) {
304
                $this->translate();
305
            }
306
            if (!$this->translation instanceof TranslationModel) {
307
                $this->translation = $this->getTranslation($this->locale);
308
            }
309
310
            $this->translation->setAttribute($key, $value);
311
            $this->updateTimestamps();
312
313
            return $this;
314
        }
315
316
        return parent::setAttribute($key, $value);
317
    }
318
319
    /**
320
     * Set fallback translation model.
321
     *
322
     * @return void
323
     */
324
    protected function setFallbackTranslation(): void
325
    {
326
        $locale = \I18n::defaultLocale()->{self::$localeKey};
327
        $this->fallbackTranslation = $this->translations->where('locale', $locale)->first();
328
    }
329
330
    /**
331
     * Set translateable attribute based on the
332
     * given key.
333
     *
334
     * @param string $key
335
     * @param mixed  $data
336
     * @param mixed  $locale
337
     *
338
     * @return $this
339
     */
340
    protected function setTranslateableAttribute(string $key, $data, $locale = null): self
341
    {
342
        if (is_array($data)) {
343
            foreach ($data as $language => $value) {
344
                $this->setTranslateableAttribute($key, $value, $language);
345
            }
346
347
            return $this;
348
        }
349
        if (!$locale && $this->locale) {
350
            $locale = $this->locale;
351
        }
352
        $this->translate($locale);
353
        $this->setAttribute($key, $data);
354
355
        return $this;
356
    }
357
358
    /**
359
     * Translate current model.
360
     *
361
     * @param mixed $key
362
     *
363
     * @return $this
364
     */
365
    public function translate($key = null): self
366
    {
367
        if (!$this->fallbackTranslation) {
368
            $this->setFallbackTranslation();
369
        }
370
371
        $this->locale = $this->getTranslationLocale($key);
372
373
        $key = $this->locale->{self::$localeKey};
374
        $this->translation = $this->translations->where('locale', $key)->first();
375
376
        return $this;
377
    }
378
379
    /**
380
     * Define HasMany model relationship
381
     * with its translation model.
382
     *
383
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
384
     */
385
    public function translations(): HasMany
386
    {
387
        $model = new TranslationModel();
388
        $model->setTable($this->getTranslationTable());
389
390
        return new HasMany(
391
            $model->newQuery(),
392
            $this,
393
            $this->getForeignKey(),
394
            $this->getKeyName()
395
        );
396
    }
397
398
    /**
399
     * Get the default foreign key name for the model.
400
     *
401
     * @return string
402
     */
403
    abstract public function getForeignKey();
404
405
    /**
406
     * Get the value of the model's primary key.
407
     *
408
     * @return mixed
409
     */
410
    abstract public function getKey();
411
412
    /**
413
     * Get the primary key for the model.
414
     *
415
     * @return string
416
     */
417
    abstract public function getKeyName();
418
419
    /**
420
     * Get the table associated with the model.
421
     *
422
     * @return string
423
     */
424
    abstract public function getTable();
425
426
    /**
427
     * Update the creation and update timestamps.
428
     *
429
     * @return void
430
     */
431
    abstract protected function updateTimestamps();
432
}
433