koenhoeijmakers /
laravel-translatable
| 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
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
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
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
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
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
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
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
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. Loading history...
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
Loading history...
|
|||||||||
| 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
Loading history...
|
|||||||||
| 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
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
|
|||||||||
| 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
Loading history...
|
|||||||||
| 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
Loading history...
|
|||||||||
| 308 | } |
||||||||
| 309 | } |
||||||||
| 310 |