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
![]() |
|||||||||
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
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
![]() |
|||||||||
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
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
![]() |
|||||||||
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
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
![]() |
|||||||||
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
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
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. ![]() 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
![]() |
|||||||||
242 | ); |
||||||||
243 | |||||||||
244 | 24 | foreach ($attributes as $key => $value) { |
|||||||
245 | 24 | $this->setAttribute($key, $value); |
|||||||
0 ignored issues
–
show
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
![]() |
|||||||||
246 | } |
||||||||
247 | |||||||||
248 | 24 | $this->syncOriginal(); |
|||||||
0 ignored issues
–
show
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
![]() |
|||||||||
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
|
|||||||||
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
|
|||||||||
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
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
![]() |
|||||||||
306 | |||||||||
307 | 2 | return $this->newQuery()->where($this->getTable() . '.' . $field, $value)->first(); |
|||||||
0 ignored issues
–
show
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
![]() |
|||||||||
308 | } |
||||||||
309 | } |
||||||||
310 |