From df6ded6495d663885a6f8c51bc50fde9758d78e8 Mon Sep 17 00:00:00 2001 From: Gummibeer Date: Thu, 20 Jun 2019 10:12:10 +0200 Subject: [PATCH 1/8] refactor translatable trait --- src/Translatable/Traits/Relationship.php | 47 ++++++ src/Translatable/Traits/Scopes.php | 124 +++++++++++++++ src/Translatable/Translatable.php | 190 +---------------------- 3 files changed, 179 insertions(+), 182 deletions(-) create mode 100644 src/Translatable/Traits/Relationship.php create mode 100644 src/Translatable/Traits/Scopes.php diff --git a/src/Translatable/Traits/Relationship.php b/src/Translatable/Traits/Relationship.php new file mode 100644 index 00000000..697cbe63 --- /dev/null +++ b/src/Translatable/Traits/Relationship.php @@ -0,0 +1,47 @@ +hasMany($this->getTranslationModelName(), $this->getRelationKey()); + } + + public function getTranslationModelName(): string + { + return $this->translationModel ?: $this->getTranslationModelNameDefault(); + } + + public function getTranslationModelNameDefault(): string + { + $modelName = get_class($this); + + if ($namespace = $this->getTranslationModelNamespace()) { + $modelName = $namespace.'\\'.class_basename(get_class($this)); + } + + return $modelName.config('translatable.translation_suffix', 'Translation'); + } + + public function getTranslationModelNamespace(): ?string + { + return config('translatable.translation_model_namespace'); + } + + public function getRelationKey(): string + { + if ($this->translationForeignKey) { + return $this->translationForeignKey; + } + + return $this->getForeignKey(); + } +} diff --git a/src/Translatable/Traits/Scopes.php b/src/Translatable/Traits/Scopes.php new file mode 100644 index 00000000..f594b76c --- /dev/null +++ b/src/Translatable/Traits/Scopes.php @@ -0,0 +1,124 @@ +locale(); + + return $query->whereHas('translations', function (Builder $q) use ($locale) { + $q->where($this->getLocaleKey(), '=', $locale); + }); + } + + public function scopeNotTranslatedIn(Builder $query, ?string $locale = null) + { + $locale = $locale ?: $this->locale(); + + return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) { + $q->where($this->getLocaleKey(), '=', $locale); + }); + } + + public function scopeTranslated(Builder $query) + { + return $query->has('translations'); + } + + public function scopeListsTranslations(Builder $query, string $translationField) + { + $withFallback = $this->useFallback(); + $translationTable = $this->getTranslationsTable(); + $localeKey = $this->getLocaleKey(); + + $query + ->select($this->getTable().'.'.$this->getKeyName(), $translationTable.'.'.$translationField) + ->leftJoin($translationTable, $translationTable.'.'.$this->getRelationKey(), '=', $this->getTable().'.'.$this->getKeyName()) + ->where($translationTable.'.'.$localeKey, $this->locale()); + if ($withFallback) { + $query->orWhere(function (Builder $q) use ($translationTable, $localeKey) { + $q->where($translationTable.'.'.$localeKey, $this->getFallbackLocale()) + ->whereNotIn($translationTable.'.'.$this->getRelationKey(), function (QueryBuilder $q) use ( + $translationTable, + $localeKey + ) { + $q->select($translationTable.'.'.$this->getRelationKey()) + ->from($translationTable) + ->where($translationTable.'.'.$localeKey, $this->locale()); + }); + }); + } + } + + public function scopeWithTranslation(Builder $query) + { + $query->with([ + 'translations' => function (Relation $query) { + if ($this->useFallback()) { + $locale = $this->locale(); + $countryFallbackLocale = $this->getFallbackLocale($locale); // e.g. de-DE => de + $locales = array_unique([$locale, $countryFallbackLocale, $this->getFallbackLocale()]); + + return $query->whereIn($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locales); + } + + return $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale()); + }, + ]); + } + + public function scopeWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null, string $method = 'whereHas', string $operator = '=') + { + return $query->$method('translations', function (Builder $query) use ($translationField, $value, $locale, $operator) { + $query->where($this->getTranslationsTable().'.'.$translationField, $operator, $value); + if ($locale) { + $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $operator, $locale); + } + }); + } + + public function scopeOrWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null) + { + return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'orWhereHas'); + } + + public function scopeWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null) + { + return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'whereHas', 'LIKE'); + } + + public function scopeOrWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null) + { + return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'orWhereHas', 'LIKE'); + } + + public function scopeOrderByTranslation(Builder $query, string $translationField, string $sortMethod = 'asc') + { + $translationTable = $this->getTranslationsTable(); + $localeKey = $this->getLocaleKey(); + $table = $this->getTable(); + $keyName = $this->getKeyName(); + + return $query + ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) { + $join + ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName) + ->where($translationTable.'.'.$localeKey, $this->locale()); + }) + ->orderBy($translationTable.'.'.$translationField, $sortMethod) + ->select($table.'.*') + ->with('translations'); + } + + private function getTranslationsTable(): string + { + return app()->make($this->getTranslationModelName())->getTable(); + } +} diff --git a/src/Translatable/Translatable.php b/src/Translatable/Translatable.php index f5548576..de68cf0c 100644 --- a/src/Translatable/Translatable.php +++ b/src/Translatable/Translatable.php @@ -2,14 +2,11 @@ namespace Astrotomic\Translatable; +use Astrotomic\Translatable\Traits\Relationship; +use Astrotomic\Translatable\Traits\Scopes; use Illuminate\Support\Str; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Relations\Relation; -use Illuminate\Database\Query\Builder as QueryBuilder; /** * @property-read Collection|Model[] $translations @@ -22,6 +19,8 @@ */ trait Translatable { + use Scopes, Relationship; + protected static $autoloadTranslations = null; protected $defaultLocale; @@ -88,46 +87,11 @@ public function hasTranslation(?string $locale = null): bool return false; } - public function getTranslationModelName(): string - { - return $this->translationModel ?: $this->getTranslationModelNameDefault(); - } - - public function getTranslationModelNameDefault(): string - { - $modelName = get_class($this); - - if ($namespace = $this->getTranslationModelNamespace()) { - $modelName = $namespace.'\\'.class_basename(get_class($this)); - } - - return $modelName.config('translatable.translation_suffix', 'Translation'); - } - - public function getTranslationModelNamespace(): ?string - { - return config('translatable.translation_model_namespace'); - } - - public function getRelationKey(): string - { - if ($this->translationForeignKey) { - return $this->translationForeignKey; - } - - return $this->getForeignKey(); - } - public function getLocaleKey(): string { return $this->localeKey ?: config('translatable.locale_key', 'locale'); } - public function translations(): HasMany - { - return $this->hasMany($this->getTranslationModelName(), $this->getRelationKey()); - } - private function usePropertyFallback(): bool { return $this->useFallback() && config('translatable.use_property_fallback', false); @@ -210,12 +174,12 @@ protected function getTranslationOrNew(?string $locale = null): Model public function fill(array $attributes) { foreach ($attributes as $key => $values) { - if ($this->isKeyALocale($key)) { + if ($this->getLocalesHelper()->has($key)) { $this->getTranslationOrNew($key)->fill($values); unset($attributes[$key]); } else { [$attribute, $locale] = $this->getAttributeAndLocale($key); - if ($this->isTranslationAttribute($attribute) and $this->isKeyALocale($locale)) { + if ($this->isTranslationAttribute($attribute) and $this->getLocalesHelper()->has($locale)) { $this->getTranslationOrNew($locale)->fill([$attribute => $values]); unset($attributes[$key]); } @@ -238,8 +202,8 @@ private function getTranslationByLocaleKey(string $key): ?Model private function getFallbackLocale(?string $locale = null): ?string { - if ($locale && $this->isLocaleCountryBased($locale)) { - if ($fallback = $this->getLanguageFromCountryBasedLocale($locale)) { + if ($locale && $this->getLocalesHelper()->isLocaleCountryBased($locale)) { + if ($fallback = $this->getLocalesHelper()->getLanguageFromCountryBasedLocale($locale)) { return $fallback; } } @@ -247,16 +211,6 @@ private function getFallbackLocale(?string $locale = null): ?string return config('translatable.fallback_locale'); } - private function isLocaleCountryBased(string $locale): bool - { - return $this->getLocalesHelper()->isLocaleCountryBased($locale); - } - - private function getLanguageFromCountryBasedLocale(string $locale): string - { - return $this->getLocalesHelper()->getLanguageFromCountryBasedLocale($locale); - } - private function useFallback(): bool { if (isset($this->useTranslationFallback) && $this->useTranslationFallback !== null) { @@ -271,21 +225,6 @@ public function isTranslationAttribute(string $key): bool return in_array($key, $this->translatedAttributes); } - protected function isKeyALocale(string $key): bool - { - return $this->getLocalesHelper()->has($key); - } - - protected function getLocales(): array - { - return $this->getLocalesHelper()->all(); - } - - protected function getLocaleSeparator(): string - { - return $this->getLocalesHelper()->getLocaleSeparator(); - } - protected function saveTranslations(): bool { $saved = true; @@ -346,114 +285,6 @@ public function __isset($key) return $this->isTranslationAttribute($key) || parent::__isset($key); } - public function scopeTranslatedIn(Builder $query, ?string $locale = null) - { - $locale = $locale ?: $this->locale(); - - return $query->whereHas('translations', function (Builder $q) use ($locale) { - $q->where($this->getLocaleKey(), '=', $locale); - }); - } - - public function scopeNotTranslatedIn(Builder $query, ?string $locale = null) - { - $locale = $locale ?: $this->locale(); - - return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) { - $q->where($this->getLocaleKey(), '=', $locale); - }); - } - - public function scopeTranslated(Builder $query) - { - return $query->has('translations'); - } - - public function scopeListsTranslations(Builder $query, string $translationField) - { - $withFallback = $this->useFallback(); - $translationTable = $this->getTranslationsTable(); - $localeKey = $this->getLocaleKey(); - - $query - ->select($this->getTable().'.'.$this->getKeyName(), $translationTable.'.'.$translationField) - ->leftJoin($translationTable, $translationTable.'.'.$this->getRelationKey(), '=', $this->getTable().'.'.$this->getKeyName()) - ->where($translationTable.'.'.$localeKey, $this->locale()); - if ($withFallback) { - $query->orWhere(function (Builder $q) use ($translationTable, $localeKey) { - $q->where($translationTable.'.'.$localeKey, $this->getFallbackLocale()) - ->whereNotIn($translationTable.'.'.$this->getRelationKey(), function (QueryBuilder $q) use ( - $translationTable, - $localeKey - ) { - $q->select($translationTable.'.'.$this->getRelationKey()) - ->from($translationTable) - ->where($translationTable.'.'.$localeKey, $this->locale()); - }); - }); - } - } - - public function scopeWithTranslation(Builder $query) - { - $query->with([ - 'translations' => function (Relation $query) { - if ($this->useFallback()) { - $locale = $this->locale(); - $countryFallbackLocale = $this->getFallbackLocale($locale); // e.g. de-DE => de - $locales = array_unique([$locale, $countryFallbackLocale, $this->getFallbackLocale()]); - - return $query->whereIn($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locales); - } - - return $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale()); - }, - ]); - } - - public function scopeWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null, string $method = 'whereHas', string $operator = '=') - { - return $query->$method('translations', function (Builder $query) use ($translationField, $value, $locale, $operator) { - $query->where($this->getTranslationsTable().'.'.$translationField, $operator, $value); - if ($locale) { - $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $operator, $locale); - } - }); - } - - public function scopeOrWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null) - { - return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'orWhereHas'); - } - - public function scopeWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null) - { - return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'whereHas', 'LIKE'); - } - - public function scopeOrWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null) - { - return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'orWhereHas', 'LIKE'); - } - - public function scopeOrderByTranslation(Builder $query, string $translationField, string $sortMethod = 'asc') - { - $translationTable = $this->getTranslationsTable(); - $localeKey = $this->getLocaleKey(); - $table = $this->getTable(); - $keyName = $this->getKeyName(); - - return $query - ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) { - $join - ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName) - ->where($translationTable.'.'.$localeKey, $this->locale()); - }) - ->orderBy($translationTable.'.'.$translationField, $sortMethod) - ->select($table.'.*') - ->with('translations'); - } - public function attributesToArray() { $attributes = parent::attributesToArray(); @@ -491,11 +322,6 @@ public function getTranslationsArray(): array return $translations; } - private function getTranslationsTable(): string - { - return app()->make($this->getTranslationModelName())->getTable(); - } - protected function locale(): string { if ($this->defaultLocale) { From 24567f9740a04a613962535ba3869e12c5e255b3 Mon Sep 17 00:00:00 2001 From: Gummibeer Date: Thu, 20 Jun 2019 10:26:34 +0200 Subject: [PATCH 2/8] order all methods --- .php_cs | 32 ++ composer.json | 3 + src/Translatable/Locales.php | 74 ++-- src/Translatable/Traits/Relationship.php | 16 +- src/Translatable/Traits/Scopes.php | 114 ++--- src/Translatable/Translatable.php | 410 +++++++++--------- .../TranslatableServiceProvider.php | 18 +- 7 files changed, 351 insertions(+), 316 deletions(-) create mode 100644 .php_cs diff --git a/.php_cs b/.php_cs new file mode 100644 index 00000000..5f1f6f23 --- /dev/null +++ b/.php_cs @@ -0,0 +1,32 @@ +in('src/Translatable'); + +return PhpCsFixer\Config::create() + ->setRules([ + 'ordered_class_elements' => [ + 'order' => [ + 'use_trait', + 'constant_public', + 'constant_protected', + 'constant_private', + 'property_public', + 'property_protected', + 'property_private', + 'construct', + 'method_public', + 'method_protected', + 'method_private', + 'destruct', + 'magic', + ], + 'sortAlgorithm' => 'alpha', + ], + 'yoda_style' => [ + 'equal' => false, + 'identical' => false, + 'less_and_greater' => null, + ], + ]) + ->setFinder($finder); diff --git a/composer.json b/composer.json index 3bc6db6b..f7003fb2 100644 --- a/composer.json +++ b/composer.json @@ -48,5 +48,8 @@ }, "config": { "sort-packages": true + }, + "scripts": { + "csfix": "php-cs-fixer fix --using-cache=no" } } diff --git a/src/Translatable/Locales.php b/src/Translatable/Locales.php index e56bac95..d7a2a4b4 100644 --- a/src/Translatable/Locales.php +++ b/src/Translatable/Locales.php @@ -16,14 +16,14 @@ class Locales implements Arrayable, ArrayAccess protected $config; /** - * @var TranslatorContract + * @var array */ - protected $translator; + protected $locales = []; /** - * @var array + * @var TranslatorContract */ - protected $locales = []; + protected $translator; public function __construct(ConfigContract $config, TranslatorContract $translator) { @@ -33,26 +33,9 @@ public function __construct(ConfigContract $config, TranslatorContract $translat $this->load(); } - public function load(): void + public function add(string $locale): void { - $localesConfig = (array) $this->config->get('translatable.locales', []); - - if (empty($localesConfig)) { - throw new LocalesNotDefinedException('Please make sure you have run "php artisan config:publish astrotomic/laravel-translatable" and that the locales configuration is defined.'); - } - - $this->locales = []; - foreach ($localesConfig as $key => $locale) { - if (is_string($key) && is_array($locale)) { - $this->locales[$key] = $key; - foreach ($locale as $country) { - $countryLocale = $this->getCountryLocale($key, $country); - $this->locales[$countryLocale] = $countryLocale; - } - } elseif (is_string($locale)) { - $this->locales[$locale] = $locale; - } - } + $this->locales[$locale] = $locale; } public function all(): array @@ -65,9 +48,9 @@ public function current() return $this->config->get('translatable.locale') ?: $this->translator->getLocale(); } - public function has(string $locale): bool + public function forget(string $locale): void { - return isset($this->locales[$locale]); + unset($this->locales[$locale]); } public function get(string $locale): ?string @@ -75,14 +58,14 @@ public function get(string $locale): ?string return $this->locales[$locale] ?? null; } - public function add(string $locale): void + public function getCountryLocale(string $locale, string $country): string { - $this->locales[$locale] = $locale; + return $locale.$this->getLocaleSeparator().$country; } - public function forget(string $locale): void + public function getLanguageFromCountryBasedLocale(string $locale): string { - unset($this->locales[$locale]); + return explode($this->getLocaleSeparator(), $locale)[0]; } public function getLocaleSeparator(): string @@ -90,9 +73,9 @@ public function getLocaleSeparator(): string return $this->config->get('translatable.locale_separator') ?: '-'; } - public function getCountryLocale(string $locale, string $country): string + public function has(string $locale): bool { - return $locale.$this->getLocaleSeparator().$country; + return isset($this->locales[$locale]); } public function isLocaleCountryBased(string $locale): bool @@ -100,14 +83,26 @@ public function isLocaleCountryBased(string $locale): bool return strpos($locale, $this->getLocaleSeparator()) !== false; } - public function getLanguageFromCountryBasedLocale(string $locale): string + public function load(): void { - return explode($this->getLocaleSeparator(), $locale)[0]; - } + $localesConfig = (array) $this->config->get('translatable.locales', []); - public function toArray(): array - { - return $this->all(); + if (empty($localesConfig)) { + throw new LocalesNotDefinedException('Please make sure you have run "php artisan config:publish astrotomic/laravel-translatable" and that the locales configuration is defined.'); + } + + $this->locales = []; + foreach ($localesConfig as $key => $locale) { + if (is_string($key) && is_array($locale)) { + $this->locales[$key] = $key; + foreach ($locale as $country) { + $countryLocale = $this->getCountryLocale($key, $country); + $this->locales[$countryLocale] = $countryLocale; + } + } elseif (is_string($locale)) { + $this->locales[$locale] = $locale; + } + } } public function offsetExists($key): bool @@ -133,4 +128,9 @@ public function offsetUnset($key) { $this->forget($key); } + + public function toArray(): array + { + return $this->all(); + } } diff --git a/src/Translatable/Traits/Relationship.php b/src/Translatable/Traits/Relationship.php index 697cbe63..ca1b862e 100644 --- a/src/Translatable/Traits/Relationship.php +++ b/src/Translatable/Traits/Relationship.php @@ -10,9 +10,13 @@ */ trait Relationship { - public function translations(): HasMany + public function getRelationKey(): string { - return $this->hasMany($this->getTranslationModelName(), $this->getRelationKey()); + if ($this->translationForeignKey) { + return $this->translationForeignKey; + } + + return $this->getForeignKey(); } public function getTranslationModelName(): string @@ -36,12 +40,8 @@ public function getTranslationModelNamespace(): ?string return config('translatable.translation_model_namespace'); } - public function getRelationKey(): string + public function translations(): HasMany { - if ($this->translationForeignKey) { - return $this->translationForeignKey; - } - - return $this->getForeignKey(); + return $this->hasMany($this->getTranslationModelName(), $this->getRelationKey()); } } diff --git a/src/Translatable/Traits/Scopes.php b/src/Translatable/Traits/Scopes.php index f594b76c..a18d439e 100644 --- a/src/Translatable/Traits/Scopes.php +++ b/src/Translatable/Traits/Scopes.php @@ -9,28 +9,6 @@ trait Scopes { - public function scopeTranslatedIn(Builder $query, ?string $locale = null) - { - $locale = $locale ?: $this->locale(); - - return $query->whereHas('translations', function (Builder $q) use ($locale) { - $q->where($this->getLocaleKey(), '=', $locale); - }); - } - - public function scopeNotTranslatedIn(Builder $query, ?string $locale = null) - { - $locale = $locale ?: $this->locale(); - - return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) { - $q->where($this->getLocaleKey(), '=', $locale); - }); - } - - public function scopeTranslated(Builder $query) - { - return $query->has('translations'); - } public function scopeListsTranslations(Builder $query, string $translationField) { @@ -57,21 +35,54 @@ public function scopeListsTranslations(Builder $query, string $translationField) } } - public function scopeWithTranslation(Builder $query) + public function scopeNotTranslatedIn(Builder $query, ?string $locale = null) { - $query->with([ - 'translations' => function (Relation $query) { - if ($this->useFallback()) { - $locale = $this->locale(); - $countryFallbackLocale = $this->getFallbackLocale($locale); // e.g. de-DE => de - $locales = array_unique([$locale, $countryFallbackLocale, $this->getFallbackLocale()]); + $locale = $locale ?: $this->locale(); - return $query->whereIn($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locales); - } + return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) { + $q->where($this->getLocaleKey(), '=', $locale); + }); + } - return $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale()); - }, - ]); + public function scopeOrderByTranslation(Builder $query, string $translationField, string $sortMethod = 'asc') + { + $translationTable = $this->getTranslationsTable(); + $localeKey = $this->getLocaleKey(); + $table = $this->getTable(); + $keyName = $this->getKeyName(); + + return $query + ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) { + $join + ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName) + ->where($translationTable.'.'.$localeKey, $this->locale()); + }) + ->orderBy($translationTable.'.'.$translationField, $sortMethod) + ->select($table.'.*') + ->with('translations'); + } + + public function scopeOrWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null) + { + return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'orWhereHas'); + } + + public function scopeOrWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null) + { + return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'orWhereHas', 'LIKE'); + } + + public function scopeTranslated(Builder $query) + { + return $query->has('translations'); + } + public function scopeTranslatedIn(Builder $query, ?string $locale = null) + { + $locale = $locale ?: $this->locale(); + + return $query->whereHas('translations', function (Builder $q) use ($locale) { + $q->where($this->getLocaleKey(), '=', $locale); + }); } public function scopeWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null, string $method = 'whereHas', string $operator = '=') @@ -84,37 +95,26 @@ public function scopeWhereTranslation(Builder $query, string $translationField, }); } - public function scopeOrWhereTranslation(Builder $query, string $translationField, $value, ?string $locale = null) - { - return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'orWhereHas'); - } - public function scopeWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null) { return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'whereHas', 'LIKE'); } - public function scopeOrWhereTranslationLike(Builder $query, string $translationField, $value, ?string $locale = null) + public function scopeWithTranslation(Builder $query) { - return $this->scopeWhereTranslation($query, $translationField, $value, $locale, 'orWhereHas', 'LIKE'); - } + $query->with([ + 'translations' => function (Relation $query) { + if ($this->useFallback()) { + $locale = $this->locale(); + $countryFallbackLocale = $this->getFallbackLocale($locale); // e.g. de-DE => de + $locales = array_unique([$locale, $countryFallbackLocale, $this->getFallbackLocale()]); - public function scopeOrderByTranslation(Builder $query, string $translationField, string $sortMethod = 'asc') - { - $translationTable = $this->getTranslationsTable(); - $localeKey = $this->getLocaleKey(); - $table = $this->getTable(); - $keyName = $this->getKeyName(); + return $query->whereIn($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locales); + } - return $query - ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) { - $join - ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName) - ->where($translationTable.'.'.$localeKey, $this->locale()); - }) - ->orderBy($translationTable.'.'.$translationField, $sortMethod) - ->select($table.'.*') - ->with('translations'); + return $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale()); + }, + ]); } private function getTranslationsTable(): string diff --git a/src/Translatable/Translatable.php b/src/Translatable/Translatable.php index de68cf0c..6d3d4f1a 100644 --- a/src/Translatable/Translatable.php +++ b/src/Translatable/Translatable.php @@ -25,6 +25,30 @@ trait Translatable protected $defaultLocale; + public function attributesToArray() + { + $attributes = parent::attributesToArray(); + + if ( + (! $this->relationLoaded('translations') && ! $this->toArrayAlwaysLoadsTranslations() && is_null(self::$autoloadTranslations)) + || self::$autoloadTranslations === false + ) { + return $attributes; + } + + $hiddenAttributes = $this->getHidden(); + + foreach ($this->translatedAttributes as $field) { + if (in_array($field, $hiddenAttributes)) { + continue; + } + + $attributes[$field] = $this->getAttributeOrFallback(null, $field); + } + + return $attributes; + } + public static function bootTranslatable(): void { static::saved(function (Model $model) { @@ -33,19 +57,103 @@ public static function bootTranslatable(): void }); } - public function translate(?string $locale = null, bool $withFallback = false): ?Model + public static function defaultAutoloadTranslations(): void { - return $this->getTranslation($locale, $withFallback); + self::$autoloadTranslations = null; } - public function translateOrDefault(?string $locale = null): ?Model + /** + * @param string|array|null $locales The locales to be deleted + */ + public function deleteTranslations($locales = null) { - return $this->getTranslation($locale, true); + if ($locales === null) { + $translations = $this->translations()->get(); + } else { + $locales = (array) $locales; + $translations = $this->translations()->whereIn($this->getLocaleKey(), $locales)->get(); + } + foreach ($translations as $translation) { + $translation->delete(); + } + + // we need to manually "reload" the collection built from the relationship + // otherwise $this->translations()->get() would NOT be the same as $this->translations + $this->load('translations'); } - public function translateOrNew(?string $locale = null): Model + public static function disableAutoloadTranslations(): void { - return $this->getTranslationOrNew($locale); + self::$autoloadTranslations = false; + } + + public static function enableAutoloadTranslations(): void + { + self::$autoloadTranslations = true; + } + + public function fill(array $attributes) + { + foreach ($attributes as $key => $values) { + if ($this->getLocalesHelper()->has($key)) { + $this->getTranslationOrNew($key)->fill($values); + unset($attributes[$key]); + } else { + [$attribute, $locale] = $this->getAttributeAndLocale($key); + if ($this->isTranslationAttribute($attribute) and $this->getLocalesHelper()->has($locale)) { + $this->getTranslationOrNew($locale)->fill([$attribute => $values]); + unset($attributes[$key]); + } + } + } + + return parent::fill($attributes); + } + + public function getAttribute($key) + { + [$attribute, $locale] = $this->getAttributeAndLocale($key); + + if ($this->isTranslationAttribute($attribute)) { + if ($this->getTranslation($locale) === null) { + return $this->getAttributeValue($attribute); + } + + // If the given $attribute has a mutator, we push it to $attributes and then call getAttributeValue + // on it. This way, we can use Eloquent's checking for Mutation, type casting, and + // Date fields. + if ($this->hasGetMutator($attribute)) { + $this->attributes[$attribute] = $this->getAttributeOrFallback($locale, $attribute); + + return $this->getAttributeValue($attribute); + } + + return $this->getAttributeOrFallback($locale, $attribute); + } + + return parent::getAttribute($key); + } + + public function getDefaultLocale(): ?string + { + return $this->defaultLocale; + } + + public function getLocaleKey(): string + { + return $this->localeKey ?: config('translatable.locale_key', 'locale'); + } + + public function getNewTranslation(string $locale): Model + { + $modelName = $this->getTranslationModelName(); + + /** @var Model $translation */ + $translation = new $modelName(); + $translation->setAttribute($this->getLocaleKey(), $locale); + $this->translations->add($translation); + + return $translation; } public function getTranslation(?string $locale = null, bool $withFallback = null): ?Model @@ -74,77 +182,59 @@ public function getTranslation(?string $locale = null, bool $withFallback = null return null; } - public function hasTranslation(?string $locale = null): bool + public function getTranslationOrNew(?string $locale = null): Model { $locale = $locale ?: $this->locale(); - foreach ($this->translations as $translation) { - if ($translation->getAttribute($this->getLocaleKey()) == $locale) { - return true; - } + if (($translation = $this->getTranslation($locale, false)) === null) { + $translation = $this->getNewTranslation($locale); } - return false; + return $translation; } - public function getLocaleKey(): string + public function getTranslationsArray(): array { - return $this->localeKey ?: config('translatable.locale_key', 'locale'); - } + $translations = []; - private function usePropertyFallback(): bool - { - return $this->useFallback() && config('translatable.use_property_fallback', false); + foreach ($this->translations as $translation) { + foreach ($this->translatedAttributes as $attr) { + $translations[$translation->{$this->getLocaleKey()}][$attr] = $translation->{$attr}; + } + } + + return $translations; } - private function getAttributeOrFallback(?string $locale, string $attribute) + public function hasTranslation(?string $locale = null): bool { - $translation = $this->getTranslation($locale); - - if ( - ( - ! $translation instanceof Model - || $this->isEmptyTranslatableAttribute($attribute, $translation->$attribute) - ) - && $this->usePropertyFallback() - ) { - $translation = $this->getTranslation($this->getFallbackLocale(), false); - } + $locale = $locale ?: $this->locale(); - if ($translation instanceof Model) { - return $translation->$attribute; + foreach ($this->translations as $translation) { + if ($translation->getAttribute($this->getLocaleKey()) == $locale) { + return true; + } } - return null; + return false; } - protected function isEmptyTranslatableAttribute(string $key, $value): bool + public function isTranslationAttribute(string $key): bool { - return empty($value); + return in_array($key, $this->translatedAttributes); } - public function getAttribute($key) + public function replicateWithTranslations(array $except = null): Model { - [$attribute, $locale] = $this->getAttributeAndLocale($key); - - if ($this->isTranslationAttribute($attribute)) { - if ($this->getTranslation($locale) === null) { - return $this->getAttributeValue($attribute); - } - - // If the given $attribute has a mutator, we push it to $attributes and then call getAttributeValue - // on it. This way, we can use Eloquent's checking for Mutation, type casting, and - // Date fields. - if ($this->hasGetMutator($attribute)) { - $this->attributes[$attribute] = $this->getAttributeOrFallback($locale, $attribute); - - return $this->getAttributeValue($attribute); - } + $newInstance = $this->replicate($except); - return $this->getAttributeOrFallback($locale, $attribute); + unset($newInstance->translations); + foreach ($this->translations as $translation) { + $newTranslation = $translation->replicate(); + $newInstance->translations->add($newTranslation); } - return parent::getAttribute($key); + return $newInstance; } public function setAttribute($key, $value) @@ -160,69 +250,53 @@ public function setAttribute($key, $value) return parent::setAttribute($key, $value); } - protected function getTranslationOrNew(?string $locale = null): Model + public function setDefaultLocale(?string $locale) { - $locale = $locale ?: $this->locale(); - - if (($translation = $this->getTranslation($locale, false)) === null) { - $translation = $this->getNewTranslation($locale); - } + $this->defaultLocale = $locale; - return $translation; + return $this; } - public function fill(array $attributes) + public function translate(?string $locale = null, bool $withFallback = false): ?Model { - foreach ($attributes as $key => $values) { - if ($this->getLocalesHelper()->has($key)) { - $this->getTranslationOrNew($key)->fill($values); - unset($attributes[$key]); - } else { - [$attribute, $locale] = $this->getAttributeAndLocale($key); - if ($this->isTranslationAttribute($attribute) and $this->getLocalesHelper()->has($locale)) { - $this->getTranslationOrNew($locale)->fill([$attribute => $values]); - unset($attributes[$key]); - } - } - } - - return parent::fill($attributes); + return $this->getTranslation($locale, $withFallback); } - private function getTranslationByLocaleKey(string $key): ?Model + public function translateOrDefault(?string $locale = null): ?Model { - foreach ($this->translations as $translation) { - if ($translation->getAttribute($this->getLocaleKey()) == $key) { - return $translation; - } - } + return $this->getTranslation($locale, true); + } - return null; + public function translateOrNew(?string $locale = null): Model + { + return $this->getTranslationOrNew($locale); } - private function getFallbackLocale(?string $locale = null): ?string + protected function getLocalesHelper(): Locales { - if ($locale && $this->getLocalesHelper()->isLocaleCountryBased($locale)) { - if ($fallback = $this->getLocalesHelper()->getLanguageFromCountryBasedLocale($locale)) { - return $fallback; - } - } + return app(Locales::class); + } - return config('translatable.fallback_locale'); + protected function isEmptyTranslatableAttribute(string $key, $value): bool + { + return empty($value); } - private function useFallback(): bool + protected function isTranslationDirty(Model $translation): bool { - if (isset($this->useTranslationFallback) && $this->useTranslationFallback !== null) { - return $this->useTranslationFallback; - } + $dirtyAttributes = $translation->getDirty(); + unset($dirtyAttributes[$this->getLocaleKey()]); - return (bool) config('translatable.use_fallback'); + return count($dirtyAttributes) > 0; } - public function isTranslationAttribute(string $key): bool + protected function locale(): string { - return in_array($key, $this->translatedAttributes); + if ($this->defaultLocale) { + return $this->defaultLocale; + } + + return $this->getLocalesHelper()->current(); } protected function saveTranslations(): bool @@ -247,129 +321,56 @@ protected function saveTranslations(): bool return $saved; } - public function replicateWithTranslations(array $except = null): Model + private function getAttributeAndLocale(string $key): array { - $newInstance = $this->replicate($except); - - unset($newInstance->translations); - foreach ($this->translations as $translation) { - $newTranslation = $translation->replicate(); - $newInstance->translations->add($newTranslation); + if (Str::contains($key, ':')) { + return explode(':', $key); } - return $newInstance; - } - - protected function isTranslationDirty(Model $translation): bool - { - $dirtyAttributes = $translation->getDirty(); - unset($dirtyAttributes[$this->getLocaleKey()]); - - return count($dirtyAttributes) > 0; - } - - public function getNewTranslation(string $locale): Model - { - $modelName = $this->getTranslationModelName(); - - /** @var Model $translation */ - $translation = new $modelName(); - $translation->setAttribute($this->getLocaleKey(), $locale); - $this->translations->add($translation); - - return $translation; - } - - public function __isset($key) - { - return $this->isTranslationAttribute($key) || parent::__isset($key); + return [$key, $this->locale()]; } - public function attributesToArray() + private function getAttributeOrFallback(?string $locale, string $attribute) { - $attributes = parent::attributesToArray(); + $translation = $this->getTranslation($locale); if ( - (! $this->relationLoaded('translations') && ! $this->toArrayAlwaysLoadsTranslations() && is_null(self::$autoloadTranslations)) - || self::$autoloadTranslations === false + ( + ! $translation instanceof Model + || $this->isEmptyTranslatableAttribute($attribute, $translation->$attribute) + ) + && $this->usePropertyFallback() ) { - return $attributes; + $translation = $this->getTranslation($this->getFallbackLocale(), false); } - $hiddenAttributes = $this->getHidden(); - - foreach ($this->translatedAttributes as $field) { - if (in_array($field, $hiddenAttributes)) { - continue; - } - - $attributes[$field] = $this->getAttributeOrFallback(null, $field); + if ($translation instanceof Model) { + return $translation->$attribute; } - return $attributes; + return null; } - public function getTranslationsArray(): array + private function getFallbackLocale(?string $locale = null): ?string { - $translations = []; - - foreach ($this->translations as $translation) { - foreach ($this->translatedAttributes as $attr) { - $translations[$translation->{$this->getLocaleKey()}][$attr] = $translation->{$attr}; + if ($locale && $this->getLocalesHelper()->isLocaleCountryBased($locale)) { + if ($fallback = $this->getLocalesHelper()->getLanguageFromCountryBasedLocale($locale)) { + return $fallback; } } - return $translations; - } - - protected function locale(): string - { - if ($this->defaultLocale) { - return $this->defaultLocale; - } - - return $this->getLocalesHelper()->current(); - } - - public function setDefaultLocale(?string $locale) - { - $this->defaultLocale = $locale; - - return $this; - } - - public function getDefaultLocale(): ?string - { - return $this->defaultLocale; - } - - /** - * @param string|array|null $locales The locales to be deleted - */ - public function deleteTranslations($locales = null) - { - if ($locales === null) { - $translations = $this->translations()->get(); - } else { - $locales = (array) $locales; - $translations = $this->translations()->whereIn($this->getLocaleKey(), $locales)->get(); - } - foreach ($translations as $translation) { - $translation->delete(); - } - - // we need to manually "reload" the collection built from the relationship - // otherwise $this->translations()->get() would NOT be the same as $this->translations - $this->load('translations'); + return config('translatable.fallback_locale'); } - private function getAttributeAndLocale(string $key): array + private function getTranslationByLocaleKey(string $key): ?Model { - if (Str::contains($key, ':')) { - return explode(':', $key); + foreach ($this->translations as $translation) { + if ($translation->getAttribute($this->getLocaleKey()) == $key) { + return $translation; + } } - return [$key, $this->locale()]; + return null; } private function toArrayAlwaysLoadsTranslations(): bool @@ -377,23 +378,22 @@ private function toArrayAlwaysLoadsTranslations(): bool return config('translatable.to_array_always_loads_translations', true); } - public static function enableAutoloadTranslations(): void + private function useFallback(): bool { - self::$autoloadTranslations = true; - } + if (isset($this->useTranslationFallback) && $this->useTranslationFallback !== null) { + return $this->useTranslationFallback; + } - public static function defaultAutoloadTranslations(): void - { - self::$autoloadTranslations = null; + return (bool) config('translatable.use_fallback'); } - public static function disableAutoloadTranslations(): void + private function usePropertyFallback(): bool { - self::$autoloadTranslations = false; + return $this->useFallback() && config('translatable.use_property_fallback', false); } - protected function getLocalesHelper(): Locales + public function __isset($key) { - return app(Locales::class); + return $this->isTranslationAttribute($key) || parent::__isset($key); } } diff --git a/src/Translatable/TranslatableServiceProvider.php b/src/Translatable/TranslatableServiceProvider.php index 05e9f163..520e9911 100644 --- a/src/Translatable/TranslatableServiceProvider.php +++ b/src/Translatable/TranslatableServiceProvider.php @@ -13,6 +13,14 @@ public function boot() ], 'translatable'); } + public function provides() + { + return [ + 'translatable.locales', + Locales::class, + ]; + } + public function register() { $this->mergeConfigFrom( @@ -22,17 +30,9 @@ public function register() $this->registerTranslatableHelper(); } - public function registerTranslatableHelper() + protected function registerTranslatableHelper() { $this->app->singleton('translatable.locales', Locales::class); $this->app->singleton(Locales::class); } - - public function provides() - { - return [ - 'translatable.locales', - Locales::class, - ]; - } } From 213ef75fec90cf5a1d11969a9632c3c991d5fa6f Mon Sep 17 00:00:00 2001 From: Gummibeer Date: Thu, 20 Jun 2019 10:27:57 +0200 Subject: [PATCH 3/8] fix php cs --- src/Translatable/Traits/Scopes.php | 4 ++-- src/Translatable/Translatable.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Translatable/Traits/Scopes.php b/src/Translatable/Traits/Scopes.php index a18d439e..9756f36a 100644 --- a/src/Translatable/Traits/Scopes.php +++ b/src/Translatable/Traits/Scopes.php @@ -3,13 +3,12 @@ namespace Astrotomic\Translatable\Traits; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Query\Builder as QueryBuilder; -use Illuminate\Database\Query\JoinClause; trait Scopes { - public function scopeListsTranslations(Builder $query, string $translationField) { $withFallback = $this->useFallback(); @@ -76,6 +75,7 @@ public function scopeTranslated(Builder $query) { return $query->has('translations'); } + public function scopeTranslatedIn(Builder $query, ?string $locale = null) { $locale = $locale ?: $this->locale(); diff --git a/src/Translatable/Translatable.php b/src/Translatable/Translatable.php index 6d3d4f1a..883c0e26 100644 --- a/src/Translatable/Translatable.php +++ b/src/Translatable/Translatable.php @@ -2,11 +2,11 @@ namespace Astrotomic\Translatable; -use Astrotomic\Translatable\Traits\Relationship; -use Astrotomic\Translatable\Traits\Scopes; use Illuminate\Support\Str; use Illuminate\Database\Eloquent\Model; +use Astrotomic\Translatable\Traits\Scopes; use Illuminate\Database\Eloquent\Collection; +use Astrotomic\Translatable\Traits\Relationship; /** * @property-read Collection|Model[] $translations From 88e715358930e85952a1f6a2e9c6054dbf738d9c Mon Sep 17 00:00:00 2001 From: Gummibeer Date: Thu, 20 Jun 2019 10:29:43 +0200 Subject: [PATCH 4/8] sometimes an else makes things easier --- .codeclimate.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index afbc22ee..5db12e6b 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -40,5 +40,7 @@ plugins: enabled: false CyclomaticComplexity: enabled: false + CleanCode/ElseExpression: + enabled: false phan: enabled: false From 8fc3504a28610fc4bdb046c48f9e68a48be985a6 Mon Sep 17 00:00:00 2001 From: Gummibeer Date: Thu, 20 Jun 2019 10:43:40 +0200 Subject: [PATCH 5/8] scopes should always return a query builder --- src/Translatable/Traits/Scopes.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Translatable/Traits/Scopes.php b/src/Translatable/Traits/Scopes.php index 9756f36a..c94e834b 100644 --- a/src/Translatable/Traits/Scopes.php +++ b/src/Translatable/Traits/Scopes.php @@ -32,6 +32,8 @@ public function scopeListsTranslations(Builder $query, string $translationField) }); }); } + + return $query; } public function scopeNotTranslatedIn(Builder $query, ?string $locale = null) From e9c5dc30e0be1c934fe10b7eb3eecfed9dcd6256 Mon Sep 17 00:00:00 2001 From: Gummibeer Date: Thu, 20 Jun 2019 11:03:51 +0200 Subject: [PATCH 6/8] add interface and flag public but not api methods as internal --- .php_cs | 3 + src/Translatable/Contracts/Translatable.php | 41 +++++++++++++ src/Translatable/Traits/Relationship.php | 32 +++++++++-- src/Translatable/Traits/Scopes.php | 14 +++-- src/Translatable/Translatable.php | 57 +++++++++++-------- tests/models/City.php | 3 +- tests/models/Continent.php | 3 +- tests/models/Country.php | 3 +- tests/models/CountryGuarded.php | 3 +- tests/models/CountryStrict.php | 3 +- tests/models/CountryWithCustomLocaleKey.php | 3 +- .../CountryWithCustomTranslationModel.php | 3 +- tests/models/Food.php | 3 +- tests/models/Person.php | 3 +- tests/models/Vegetable.php | 3 +- 15 files changed, 131 insertions(+), 46 deletions(-) create mode 100644 src/Translatable/Contracts/Translatable.php diff --git a/.php_cs b/.php_cs index 5f1f6f23..f310dd9b 100644 --- a/.php_cs +++ b/.php_cs @@ -15,8 +15,11 @@ return PhpCsFixer\Config::create() 'property_protected', 'property_private', 'construct', + 'method_public_static', 'method_public', + 'method_protected_static', 'method_protected', + 'method_private_static', 'method_private', 'destruct', 'magic', diff --git a/src/Translatable/Contracts/Translatable.php b/src/Translatable/Contracts/Translatable.php new file mode 100644 index 00000000..424baf07 --- /dev/null +++ b/src/Translatable/Contracts/Translatable.php @@ -0,0 +1,41 @@ +translationForeignKey) { - return $this->translationForeignKey; - } - - return $this->getForeignKey(); + return $this->getTranslationRelationKey(); } + /** + * @internal will change to protected + */ public function getTranslationModelName(): string { return $this->translationModel ?: $this->getTranslationModelNameDefault(); } + /** + * @internal will change to private + */ public function getTranslationModelNameDefault(): string { $modelName = get_class($this); @@ -35,13 +40,28 @@ public function getTranslationModelNameDefault(): string return $modelName.config('translatable.translation_suffix', 'Translation'); } + /** + * @internal will change to private + */ public function getTranslationModelNamespace(): ?string { return config('translatable.translation_model_namespace'); } + /** + * @internal will change to protected + */ + public function getTranslationRelationKey(): string + { + if ($this->translationForeignKey) { + return $this->translationForeignKey; + } + + return $this->getForeignKey(); + } + public function translations(): HasMany { - return $this->hasMany($this->getTranslationModelName(), $this->getRelationKey()); + return $this->hasMany($this->getTranslationModelName(), $this->getTranslationRelationKey()); } } diff --git a/src/Translatable/Traits/Scopes.php b/src/Translatable/Traits/Scopes.php index c94e834b..58d933eb 100644 --- a/src/Translatable/Traits/Scopes.php +++ b/src/Translatable/Traits/Scopes.php @@ -17,16 +17,19 @@ public function scopeListsTranslations(Builder $query, string $translationField) $query ->select($this->getTable().'.'.$this->getKeyName(), $translationTable.'.'.$translationField) - ->leftJoin($translationTable, $translationTable.'.'.$this->getRelationKey(), '=', $this->getTable().'.'.$this->getKeyName()) + ->leftJoin($translationTable, $translationTable.'.'.$this->getTranslationRelationKey(), '=', $this->getTable().'.'.$this->getKeyName()) ->where($translationTable.'.'.$localeKey, $this->locale()); + if ($withFallback) { $query->orWhere(function (Builder $q) use ($translationTable, $localeKey) { - $q->where($translationTable.'.'.$localeKey, $this->getFallbackLocale()) - ->whereNotIn($translationTable.'.'.$this->getRelationKey(), function (QueryBuilder $q) use ( + $q + ->where($translationTable.'.'.$localeKey, $this->getFallbackLocale()) + ->whereNotIn($translationTable.'.'.$this->getTranslationRelationKey(), function (QueryBuilder $q) use ( $translationTable, $localeKey ) { - $q->select($translationTable.'.'.$this->getRelationKey()) + $q + ->select($translationTable.'.'.$this->getTranslationRelationKey()) ->from($translationTable) ->where($translationTable.'.'.$localeKey, $this->locale()); }); @@ -55,7 +58,7 @@ public function scopeOrderByTranslation(Builder $query, string $translationField return $query ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) { $join - ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName) + ->on($translationTable.'.'.$this->getTranslationRelationKey(), '=', $table.'.'.$keyName) ->where($translationTable.'.'.$localeKey, $this->locale()); }) ->orderBy($translationTable.'.'.$translationField, $sortMethod) @@ -91,6 +94,7 @@ public function scopeWhereTranslation(Builder $query, string $translationField, { return $query->$method('translations', function (Builder $query) use ($translationField, $value, $locale, $operator) { $query->where($this->getTranslationsTable().'.'.$translationField, $operator, $value); + if ($locale) { $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $operator, $locale); } diff --git a/src/Translatable/Translatable.php b/src/Translatable/Translatable.php index 883c0e26..b8c0e10b 100644 --- a/src/Translatable/Translatable.php +++ b/src/Translatable/Translatable.php @@ -25,6 +25,29 @@ trait Translatable protected $defaultLocale; + public static function bootTranslatable(): void + { + static::saved(function (Model $model) { + /* @var Translatable $model */ + return $model->saveTranslations(); + }); + } + + public static function defaultAutoloadTranslations(): void + { + self::$autoloadTranslations = null; + } + + public static function disableAutoloadTranslations(): void + { + self::$autoloadTranslations = false; + } + + public static function enableAutoloadTranslations(): void + { + self::$autoloadTranslations = true; + } + public function attributesToArray() { $attributes = parent::attributesToArray(); @@ -49,23 +72,10 @@ public function attributesToArray() return $attributes; } - public static function bootTranslatable(): void - { - static::saved(function (Model $model) { - /* @var Translatable $model */ - return $model->saveTranslations(); - }); - } - - public static function defaultAutoloadTranslations(): void - { - self::$autoloadTranslations = null; - } - /** * @param string|array|null $locales The locales to be deleted */ - public function deleteTranslations($locales = null) + public function deleteTranslations($locales = null): void { if ($locales === null) { $translations = $this->translations()->get(); @@ -73,6 +83,7 @@ public function deleteTranslations($locales = null) $locales = (array) $locales; $translations = $this->translations()->whereIn($this->getLocaleKey(), $locales)->get(); } + foreach ($translations as $translation) { $translation->delete(); } @@ -82,16 +93,6 @@ public function deleteTranslations($locales = null) $this->load('translations'); } - public static function disableAutoloadTranslations(): void - { - self::$autoloadTranslations = false; - } - - public static function enableAutoloadTranslations(): void - { - self::$autoloadTranslations = true; - } - public function fill(array $attributes) { foreach ($attributes as $key => $values) { @@ -134,11 +135,17 @@ public function getAttribute($key) return parent::getAttribute($key); } + /** + * @internal will change to protected + */ public function getDefaultLocale(): ?string { return $this->defaultLocale; } + /** + * @internal will change to protected + */ public function getLocaleKey(): string { return $this->localeKey ?: config('translatable.locale_key', 'locale'); @@ -313,7 +320,7 @@ protected function saveTranslations(): bool $translation->setConnection($connectionName); } - $translation->setAttribute($this->getRelationKey(), $this->getKey()); + $translation->setAttribute($this->getTranslationRelationKey(), $this->getKey()); $saved = $translation->save(); } } diff --git a/tests/models/City.php b/tests/models/City.php index 4ae80fae..ad621b59 100644 --- a/tests/models/City.php +++ b/tests/models/City.php @@ -4,8 +4,9 @@ use Astrotomic\Translatable\Translatable; use Illuminate\Database\Eloquent\Model as Eloquent; +use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; -class City extends Eloquent +class City extends Eloquent implements TranslatableContract { use Translatable; diff --git a/tests/models/Continent.php b/tests/models/Continent.php index b33657c6..fe46487a 100644 --- a/tests/models/Continent.php +++ b/tests/models/Continent.php @@ -4,11 +4,12 @@ use Astrotomic\Translatable\Translatable; use Illuminate\Database\Eloquent\Model as Eloquent; +use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; /** * A test class that has no required properties. */ -class Continent extends Eloquent +class Continent extends Eloquent implements TranslatableContract { use Translatable; diff --git a/tests/models/Country.php b/tests/models/Country.php index 9b2cd40e..bd89e6cd 100644 --- a/tests/models/Country.php +++ b/tests/models/Country.php @@ -4,8 +4,9 @@ use Astrotomic\Translatable\Translatable; use Illuminate\Database\Eloquent\Model as Eloquent; +use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; -class Country extends Eloquent +class Country extends Eloquent implements TranslatableContract { use Translatable; diff --git a/tests/models/CountryGuarded.php b/tests/models/CountryGuarded.php index 2397aae7..36fa86cc 100644 --- a/tests/models/CountryGuarded.php +++ b/tests/models/CountryGuarded.php @@ -4,8 +4,9 @@ use Astrotomic\Translatable\Translatable; use Illuminate\Database\Eloquent\Model as Eloquent; +use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; -class CountryGuarded extends Eloquent +class CountryGuarded extends Eloquent implements TranslatableContract { use Translatable; diff --git a/tests/models/CountryStrict.php b/tests/models/CountryStrict.php index 46f5af26..76787697 100644 --- a/tests/models/CountryStrict.php +++ b/tests/models/CountryStrict.php @@ -4,8 +4,9 @@ use Astrotomic\Translatable\Translatable; use Illuminate\Database\Eloquent\Model as Eloquent; +use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; -class CountryStrict extends Eloquent +class CountryStrict extends Eloquent implements TranslatableContract { use Translatable; diff --git a/tests/models/CountryWithCustomLocaleKey.php b/tests/models/CountryWithCustomLocaleKey.php index db8a8925..b7a32515 100644 --- a/tests/models/CountryWithCustomLocaleKey.php +++ b/tests/models/CountryWithCustomLocaleKey.php @@ -4,8 +4,9 @@ use Astrotomic\Translatable\Translatable; use Illuminate\Database\Eloquent\Model as Eloquent; +use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; -class CountryWithCustomLocaleKey extends Eloquent +class CountryWithCustomLocaleKey extends Eloquent implements TranslatableContract { use Translatable; diff --git a/tests/models/CountryWithCustomTranslationModel.php b/tests/models/CountryWithCustomTranslationModel.php index 81e94364..5cd22dec 100644 --- a/tests/models/CountryWithCustomTranslationModel.php +++ b/tests/models/CountryWithCustomTranslationModel.php @@ -3,8 +3,9 @@ namespace Astrotomic\Translatable\Test\Model; use Astrotomic\Translatable\Translatable; +use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; -class CountryWithCustomTranslationModel extends Country +class CountryWithCustomTranslationModel extends Country implements TranslatableContract { use Translatable; diff --git a/tests/models/Food.php b/tests/models/Food.php index 2f77a36f..88b235b5 100644 --- a/tests/models/Food.php +++ b/tests/models/Food.php @@ -4,8 +4,9 @@ use Astrotomic\Translatable\Translatable; use Illuminate\Database\Eloquent\Model as Eloquent; +use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; -class Food extends Eloquent +class Food extends Eloquent implements TranslatableContract { use Translatable; diff --git a/tests/models/Person.php b/tests/models/Person.php index 05367433..736440f5 100644 --- a/tests/models/Person.php +++ b/tests/models/Person.php @@ -4,8 +4,9 @@ use Astrotomic\Translatable\Translatable; use Illuminate\Database\Eloquent\Model as Eloquent; +use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; -class Person extends Eloquent +class Person extends Eloquent implements TranslatableContract { protected $table = 'people'; diff --git a/tests/models/Vegetable.php b/tests/models/Vegetable.php index 625205ae..95df6ab2 100644 --- a/tests/models/Vegetable.php +++ b/tests/models/Vegetable.php @@ -4,8 +4,9 @@ use Astrotomic\Translatable\Translatable; use Illuminate\Database\Eloquent\Model as Eloquent; +use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; -class Vegetable extends Eloquent +class Vegetable extends Eloquent implements TranslatableContract { use Translatable; From 4f4580540d1ad8bb7095732d7e1ac26e7d6d2a98 Mon Sep 17 00:00:00 2001 From: Gummibeer Date: Thu, 20 Jun 2019 11:06:00 +0200 Subject: [PATCH 7/8] add missing method to contract --- src/Translatable/Contracts/Translatable.php | 2 ++ src/Translatable/Translatable.php | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Translatable/Contracts/Translatable.php b/src/Translatable/Contracts/Translatable.php index 424baf07..612cd19e 100644 --- a/src/Translatable/Contracts/Translatable.php +++ b/src/Translatable/Contracts/Translatable.php @@ -17,6 +17,8 @@ public function translations(): HasMany; public function deleteTranslations($locales = null): void; + public function getDefaultLocale(): ?string; + public function getNewTranslation(string $locale): Model; public function getTranslation(?string $locale = null, bool $withFallback = null): ?Model; diff --git a/src/Translatable/Translatable.php b/src/Translatable/Translatable.php index b8c0e10b..fa3b0985 100644 --- a/src/Translatable/Translatable.php +++ b/src/Translatable/Translatable.php @@ -135,9 +135,6 @@ public function getAttribute($key) return parent::getAttribute($key); } - /** - * @internal will change to protected - */ public function getDefaultLocale(): ?string { return $this->defaultLocale; From 8752e7e0c93ad10d4d34d8bf4bd014f6532ad08b Mon Sep 17 00:00:00 2001 From: Gummibeer Date: Thu, 20 Jun 2019 11:07:15 +0200 Subject: [PATCH 8/8] boolean control flags are sometimes required --- .codeclimate.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index 5db12e6b..1d955bb3 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -42,5 +42,7 @@ plugins: enabled: false CleanCode/ElseExpression: enabled: false + CleanCode/BooleanArgumentFlag: + enabled: false phan: enabled: false