Passed
Pull Request — master (#6)
by Koen
04:51
created

HasTranslations   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 59
dl 0
loc 283
rs 9.84
c 0
b 0
f 0
wmc 32

21 Methods

Rating   Name   Duplication   Size   Complexity  
A translationExists() 0 3 1
A translations() 0 3 1
A purgeTranslations() 0 3 1
A getTranslatableAttributes() 0 3 1
A getTranslationForeignKey() 0 3 2
A getTranslationModelSuffix() 0 3 1
A getTranslation() 0 3 1
A formatTranslatableColumnsForSelect() 0 7 1
A getTranslationTable() 0 5 1
A storeTranslation() 0 13 2
A refreshTranslation() 0 17 3
A getTranslationModel() 0 5 2
A bootHasTranslations() 0 19 2
A newQueryWithoutScopes() 0 4 1
A resolveRouteBinding() 0 3 1
A getTranslationValue() 0 3 1
A getLocaleKeyName() 0 4 2
A getTranslatable() 0 7 2
A getLocale() 0 3 2
A translate() 0 9 2
A storeTranslations() 0 7 2
1
<?php
2
3
namespace KoenHoeijmakers\LaravelTranslatable;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Support\Arr;
7
use KoenHoeijmakers\LaravelTranslatable\Exceptions\MissingTranslationsException;
8
use KoenHoeijmakers\LaravelTranslatable\Scopes\JoinTranslationScope;
9
use KoenHoeijmakers\LaravelTranslatable\Services\TranslationSavingService;
10
11
/**
12
 * Trait HasTranslations
13
 *
14
 * @mixin \Illuminate\Database\Eloquent\Model
15
 */
16
trait HasTranslations
17
{
18
    /**
19
     * The current locale, used to handle internal states.
20
     *
21
     * @var string|null
22
     */
23
    protected $currentLocale = null;
24
25
    /**
26
     * Boot the translatable trait.
27
     *
28
     * @return void
29
     */
30
    public static function bootHasTranslations()
31
    {
32
        if (config('translatable.use_saving_service', true)) {
33
            static::saving(function (self $model) {
34
                app(TranslationSavingService::class)->rememberTranslationForModel($model);
0 ignored issues
show
Bug introduced by
$model of type KoenHoeijmakers\LaravelT...latable\HasTranslations is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $model of KoenHoeijmakers\LaravelT...erTranslationForModel(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

34
                app(TranslationSavingService::class)->rememberTranslationForModel(/** @scrutinizer ignore-type */ $model);
Loading history...
35
            });
36
37
            static::saved(function (self $model) {
38
                app(TranslationSavingService::class)->storeTranslationOnModel($model);
0 ignored issues
show
Bug introduced by
$model of type KoenHoeijmakers\LaravelT...latable\HasTranslations is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $model of KoenHoeijmakers\LaravelT...oreTranslationOnModel(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

38
                app(TranslationSavingService::class)->storeTranslationOnModel(/** @scrutinizer ignore-type */ $model);
Loading history...
39
40
                $model->refreshTranslation();
41
            });
42
        }
43
44
        static::deleting(function (self $model) {
45
            $model->purgeTranslations();
46
        });
47
48
        static::addGlobalScope(new JoinTranslationScope());
49
    }
50
51
    /**
52
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
53
     */
54
    public function translations()
55
    {
56
        return $this->hasMany($this->getTranslationModel(), $this->getTranslationForeignKey());
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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

56
        return $this->/** @scrutinizer ignore-call */ hasMany($this->getTranslationModel(), $this->getTranslationForeignKey());
Loading history...
57
    }
58
59
    /**
60
     * Check if the translation by the given locale exists.
61
     *
62
     * @param string $locale
63
     * @return bool
64
     */
65
    public function translationExists(string $locale): bool
66
    {
67
        return $this->translations()->where($this->getLocaleKeyName(), $locale)->exists();
68
    }
69
70
    /**
71
     * Purge the translations.
72
     *
73
     * @return mixed
74
     */
75
    public function purgeTranslations()
76
    {
77
        return $this->translations()->delete();
78
    }
79
80
    /**
81
     * Get the translation model.
82
     *
83
     * @return string
84
     */
85
    public function getTranslationModel(): string
86
    {
87
        return property_exists($this, 'translationModel')
88
            ? $this->translationModel
89
            : get_class($this) . $this->getTranslationModelSuffix();
90
    }
91
92
    /**
93
     * Get the translation model suffix.
94
     *
95
     * @return string
96
     */
97
    protected function getTranslationModelSuffix(): string
98
    {
99
        return 'Translation';
100
    }
101
102
    /**
103
     * Get the translation table.
104
     *
105
     * @return string
106
     */
107
    public function getTranslationTable(): string
108
    {
109
        $model = $this->getTranslationModel();
110
111
        return (new $model())->getTable();
112
    }
113
114
    /**
115
     * Get the translation foreign key.
116
     *
117
     * @return string
118
     */
119
    public function getTranslationForeignKey()
120
    {
121
        return property_exists($this, 'translationForeignKey') ? $this->translationForeignKey : $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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

121
        return property_exists($this, 'translationForeignKey') ? $this->translationForeignKey : $this->/** @scrutinizer ignore-call */ getForeignKey();
Loading history...
122
    }
123
124
    /**
125
     * Get the translatable.
126
     *
127
     * @return array
128
     * @throws \KoenHoeijmakers\LaravelTranslatable\Exceptions\MissingTranslationsException
129
     */
130
    public function getTranslatable(): array
131
    {
132
        if (!isset($this->translatable)) {
133
            throw new MissingTranslationsException('Model "' . static::class . '" is missing translations');
134
        }
135
136
        return $this->translatable;
137
    }
138
139
    /**
140
     * Get the translatable attributes.
141
     *
142
     * @return array
143
     */
144
    public function getTranslatableAttributes(): array
145
    {
146
        return Arr::only($this->getAttributes(), $this->translatable);
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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

146
        return Arr::only($this->/** @scrutinizer ignore-call */ getAttributes(), $this->translatable);
Loading history...
147
    }
148
149
    /**
150
     * @param string $locale
151
     * @param array<string, mixed> $attributes
152
     * @return \Illuminate\Database\Eloquent\Model
153
     */
154
    public function storeTranslation(string $locale, array $attributes = [])
155
    {
156
        if (!is_null($model = $this->translations()->where($this->getLocaleKeyName(), $locale)->first())) {
157
            $model->update($attributes);
158
159
            return $model;
160
        }
161
162
        $model = $this->translations()->make($attributes);
163
        $model->setAttribute($this->getLocaleKeyName(), $locale);
164
        $model->save();
165
166
        return $model;
167
    }
168
169
    /**
170
     * Store many translations at once.
171
     *
172
     * @param array<string, array> $translations
173
     * @return $this
174
     */
175
    public function storeTranslations(array $translations)
176
    {
177
        foreach ($translations as $locale => $translation) {
178
            $this->storeTranslation($locale, $translation);
179
        }
180
181
        return $this;
182
    }
183
184
    /**
185
     * @param string $locale
186
     * @return \Illuminate\Database\Eloquent\Model|self
187
     */
188
    public function getTranslation(string $locale)
189
    {
190
        return $this->translations()->where($this->getLocaleKeyName(), $locale)->first();
191
    }
192
193
    /**
194
     * @param string $locale
195
     * @param string $name
196
     * @return mixed
197
     */
198
    public function getTranslationValue(string $locale, string $name)
199
    {
200
        return $this->translations()->where($this->getLocaleKeyName(), $locale)->value($name);
201
    }
202
203
    /**
204
     * The locale key name.
205
     *
206
     * @return string
207
     */
208
    public function getLocaleKeyName(): string
209
    {
210
        return property_exists($this, 'localeKeyName') ? $this->localeKeyName
211
            : config('translatable.locale_key_name', 'locale');
212
    }
213
214
    /**
215
     * Get the locale.
216
     *
217
     * @return string
218
     */
219
    public function getLocale(): string
220
    {
221
        return property_exists($this, 'currentLocale') ? $this->currentLocale : app()->getLocale();
0 ignored issues
show
Bug Best Practice introduced by
The expression return property_exists($...le : app()->getLocale() could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
introduced by
The method getLocale() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
        return property_exists($this, 'currentLocale') ? $this->currentLocale : app()->/** @scrutinizer ignore-call */ getLocale();
Loading history...
222
    }
223
224
    /**
225
     * Refresh the translation (in the current locale).
226
     *
227
     * @return \Illuminate\Database\Eloquent\Model|null
228
     */
229
    public function refreshTranslation()
230
    {
231
        if (!$this->exists) {
232
            return null;
233
        }
234
235
        $attributes = Arr::only(
236
            static::findOrFail($this->getKey())->attributes, $this->getTranslatable()
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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

236
            static::findOrFail($this->/** @scrutinizer ignore-call */ getKey())->attributes, $this->getTranslatable()
Loading history...
237
        );
238
239
        foreach ($attributes as $key => $value) {
240
            $this->setAttribute($key, $value);
0 ignored issues
show
Bug introduced by
It seems like setAttribute() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

240
            $this->/** @scrutinizer ignore-call */ 
241
                   setAttribute($key, $value);
Loading history...
241
        }
242
243
        $this->syncOriginal();
0 ignored issues
show
Bug introduced by
It seems like syncOriginal() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

243
        $this->/** @scrutinizer ignore-call */ 
244
               syncOriginal();
Loading history...
244
245
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type KoenHoeijmakers\LaravelT...latable\HasTranslations which is incompatible with the documented return type null|Illuminate\Database\Eloquent\Model.
Loading history...
246
    }
247
248
    /**
249
     * Translate the model to the given locale.
250
     *
251
     * @param string $locale
252
     * @return \Illuminate\Database\Eloquent\Model|null
253
     */
254
    public function translate(string $locale)
255
    {
256
        if (!$this->exists) {
257
            return null;
258
        }
259
260
        $this->currentLocale = $locale;
261
262
        return $this->refreshTranslation();
263
    }
264
265
    /**
266
     * Format the translated columns.
267
     *
268
     * @return array
269
     */
270
    public function formatTranslatableColumnsForSelect(): array
271
    {
272
        $table = $this->getTranslationTable();
273
274
        return array_map(function ($item) use ($table) {
275
            return $table . '.' . $item;
276
        }, $this->getTranslatable());
277
    }
278
279
    /**
280
     * Get a new query builder that doesn't have any global scopes (except the JoinTranslationScope).
281
     *
282
     * @return \Illuminate\Database\Eloquent\Builder
283
     */
284
    public function newQueryWithoutScopes(): Builder
285
    {
286
        return parent::newQueryWithoutScopes()
287
            ->withGlobalScope(JoinTranslationScope::class, new JoinTranslationScope());
288
    }
289
290
    /**
291
     * Retrieve the model for a bound value.
292
     *
293
     * @param  mixed  $value
294
     * @return \Illuminate\Database\Eloquent\Model|null
295
     */
296
    public function resolveRouteBinding($value)
297
    {
298
        return $this->where($this->getTable() . '.' . $this->getRouteKeyName(), $value)->first();
0 ignored issues
show
Bug introduced by
It seems like getRouteKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

298
        return $this->where($this->getTable() . '.' . $this->/** @scrutinizer ignore-call */ getRouteKeyName(), $value)->first();
Loading history...
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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

298
        return $this->where($this->/** @scrutinizer ignore-call */ getTable() . '.' . $this->getRouteKeyName(), $value)->first();
Loading history...
Bug introduced by
It seems like where() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

298
        return $this->/** @scrutinizer ignore-call */ where($this->getTable() . '.' . $this->getRouteKeyName(), $value)->first();
Loading history...
299
    }
300
}
301