HasTranslations   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 20
Bugs 2 Features 0
Metric Value
wmc 32
eloc 63
c 20
b 2
f 0
dl 0
loc 291
ccs 82
cts 82
cp 1
rs 9.84

21 Methods

Rating   Name   Duplication   Size   Complexity  
A purgeTranslations() 0 3 1
A getTranslationTable() 0 5 1
A storeTranslation() 0 13 2
A getTranslationModel() 0 5 2
A getTranslatableAttributes() 0 3 1
A getTranslationForeignKey() 0 3 2
A bootHasTranslations() 0 19 2
A translationExists() 0 3 1
A getTranslationValue() 0 3 1
A getLocaleKeyName() 0 5 2
A getTranslationModelSuffix() 0 3 1
A getTranslatable() 0 7 2
A translations() 0 3 1
A getLocale() 0 5 2
A storeTranslations() 0 7 2
A getTranslation() 0 3 1
A formatTranslatableColumnsForSelect() 0 7 1
A refreshTranslation() 0 17 3
A newQueryWithoutScopes() 0 4 1
A resolveRouteBinding() 0 5 1
A translate() 0 9 2
1
<?php declare(strict_types=1);
2
3
namespace KoenHoeijmakers\LaravelTranslatable;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Relations\HasMany;
7
use Illuminate\Support\Arr;
8
use KoenHoeijmakers\LaravelTranslatable\Exceptions\MissingTranslationsException;
9
use KoenHoeijmakers\LaravelTranslatable\Scopes\JoinTranslationScope;
10
use KoenHoeijmakers\LaravelTranslatable\Services\TranslationSavingService;
11
12
/**
13
 * Trait HasTranslations
14
 *
15
 * @mixin \Illuminate\Database\Eloquent\Model
16
 */
17
trait HasTranslations
18
{
19
    /**
20
     * The current locale, used to handle internal states.
21
     *
22
     * @var string|null
23
     */
24
    protected $currentLocale = null;
25
26
    /**
27
     * Boot the translatable trait.
28
     *
29
     * @return void
30
     */
31 34
    public static function bootHasTranslations()
32
    {
33 34
        if (config('translatable.use_saving_service', true)) {
34
            self::saving(function ($model) {
35 24
                app(TranslationSavingService::class)->rememberTranslationForModel($model);
36 34
            });
37
38
            self::saved(function ($model) {
39 24
                app(TranslationSavingService::class)->storeTranslationOnModel($model);
40
41 24
                $model->refreshTranslation();
42 34
            });
43
        }
44
45
        self::deleting(function ($model) {
46 2
            $model->purgeTranslations();
47 34
        });
48
49 34
        self::addGlobalScope(new JoinTranslationScope());
50 34
    }
51
52
    /**
53
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
54
     */
55 24
    public function translations(): HasMany
56
    {
57 24
        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

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

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

147
        return Arr::only($this->/** @scrutinizer ignore-call */ getAttributes(), $this->translatable);
Loading history...
148
    }
149
150
    /**
151
     * @param  string               $locale
152
     * @param  array<string, mixed> $attributes
153
     * @return \Illuminate\Database\Eloquent\Model
154
     */
155 24
    public function storeTranslation(string $locale, array $attributes = [])
156
    {
157 24
        if (! is_null($model = $this->translations()->where($this->getLocaleKeyName(), $locale)->first())) {
158 2
            $model->update($attributes);
159
160 2
            return $model;
161
        }
162
163 24
        $model = $this->translations()->make($attributes);
164 24
        $model->setAttribute($this->getLocaleKeyName(), $locale);
165 24
        $model->save();
166
167 24
        return $model;
168
    }
169
170
    /**
171
     * Store many translations at once.
172
     *
173
     * @param  array<string, array> $translations
174
     * @return $this
175
     */
176 2
    public function storeTranslations(array $translations)
177
    {
178 2
        foreach ($translations as $locale => $translation) {
179 2
            $this->storeTranslation($locale, $translation);
180
        }
181
182 2
        return $this;
183
    }
184
185
    /**
186
     * @param  string $locale
187
     * @return \Illuminate\Database\Eloquent\Model|self
188
     */
189 4
    public function getTranslation(string $locale)
190
    {
191 4
        return $this->translations()->where($this->getLocaleKeyName(), $locale)->first();
192
    }
193
194
    /**
195
     * @param  string $locale
196
     * @param  string $name
197
     * @return mixed
198
     */
199 2
    public function getTranslationValue(string $locale, string $name)
200
    {
201 2
        return $this->translations()->where($this->getLocaleKeyName(), $locale)->value($name);
202
    }
203
204
    /**
205
     * The locale key name.
206
     *
207
     * @return string
208
     */
209 26
    public function getLocaleKeyName(): string
210
    {
211 26
        return property_exists($this, 'localeKeyName')
212 2
            ? $this->localeKeyName
213 26
            : config()->get('translatable.locale_key_name', 'locale');
214
    }
215
216
    /**
217
     * Get the locale.
218
     *
219
     * @return string
220
     */
221 24
    public function getLocale(): string
222
    {
223 24
        return null !== $this->currentLocale
224 4
            ? $this->currentLocale
225 24
            : app()->getLocale();
0 ignored issues
show
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

225
            : app()->/** @scrutinizer ignore-call */ getLocale();
Loading history...
226
    }
227
228
    /**
229
     * Refresh the translation (in the current locale).
230
     *
231
     * @return \Illuminate\Database\Eloquent\Model|null|HasTranslations
232
     * @throws \KoenHoeijmakers\LaravelTranslatable\Exceptions\MissingTranslationsException
233
     */
234 26
    public function refreshTranslation()
235
    {
236 26
        if (! $this->exists) {
237 2
            return null;
238
        }
239
240 24
        $attributes = Arr::only(
241 24
            $this->newQuery()->findOrFail($this->getKey())->attributes, $this->getTranslatable()
0 ignored issues
show
Bug introduced by
The method newQuery() does not exist on KoenHoeijmakers\LaravelT...latable\HasTranslations. Did you maybe mean newQueryWithoutScopes()? ( Ignorable by Annotation )

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

241
            $this->/** @scrutinizer ignore-call */ 
242
                   newQuery()->findOrFail($this->getKey())->attributes, $this->getTranslatable()

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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

241
            $this->newQuery()->findOrFail($this->/** @scrutinizer ignore-call */ getKey())->attributes, $this->getTranslatable()
Loading history...
242
        );
243
244 24
        foreach ($attributes as $key => $value) {
245 24
            $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

245
            $this->/** @scrutinizer ignore-call */ 
246
                   setAttribute($key, $value);
Loading history...
246
        }
247
248 24
        $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

248
        $this->/** @scrutinizer ignore-call */ 
249
               syncOriginal();
Loading history...
249
250 24
        return $this;
251
    }
252
253
    /**
254
     * Translate the model to the given locale.
255
     *
256
     * @param  string $locale
257
     * @return \Illuminate\Database\Eloquent\Model|null
258
     */
259 6
    public function translate(string $locale)
260
    {
261 6
        if (! $this->exists) {
262 2
            return null;
263
        }
264
265 4
        $this->currentLocale = $locale;
266
267 4
        return $this->refreshTranslation();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->refreshTranslation() also could return the type KoenHoeijmakers\LaravelT...latable\HasTranslations which is incompatible with the documented return type Illuminate\Database\Eloquent\Model|null.
Loading history...
268
    }
269
270
    /**
271
     * Format the translated columns.
272
     *
273
     * @return array
274
     * @throws \KoenHoeijmakers\LaravelTranslatable\Exceptions\MissingTranslationsException
275
     */
276 24
    public function formatTranslatableColumnsForSelect(): array
277
    {
278 24
        $table = $this->getTranslationTable();
279
280
        return array_map(function ($item) use ($table) {
281 24
            return $table . '.' . $item;
282 24
        }, $this->getTranslatable());
283
    }
284
285
    /**
286
     * Get a new query builder that doesn't have any global scopes (except the JoinTranslationScope).
287
     *
288
     * @return \Illuminate\Database\Eloquent\Builder
289
     */
290 24
    public function newQueryWithoutScopes(): Builder
291
    {
292 24
        return parent::newQueryWithoutScopes()
293 24
            ->withGlobalScope(JoinTranslationScope::class, new JoinTranslationScope());
294
    }
295
296
    /**
297
     * Retrieve the model for a bound value.
298
     *
299
     * @param  mixed $value
300
     * @param  null  $field
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $field is correct as it would always require null to be passed?
Loading history...
301
     * @return \Illuminate\Database\Eloquent\Model|null
302
     */
303 2
    public function resolveRouteBinding($value, $field = null)
304
    {
305 2
        $field = $field ?? $this->getRouteKeyName();
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

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

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

307
        return $this->newQuery()->where($this->/** @scrutinizer ignore-call */ getTable() . '.' . $field, $value)->first();
Loading history...
308
    }
309
}
310