From 5465f4eb9fb1de1f40c0271e932ee08deca23caa Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Thu, 20 Jun 2024 02:47:40 +0300 Subject: [PATCH] Get tests is ready --- src/Casts/ColumnCast.php | 22 -- src/Exceptions/UnavailableLocaleException.php | 2 +- src/HasTranslations.php | 255 ++++++++++-------- src/Services/Registry.php | 17 ++ src/Services/Relation.php | 46 ++++ stubs/helper.stub | 3 +- tests/Fixtures/Models/TestModel.php | 4 +- .../Fixtures/Models/TestModelTranslation.php | 6 - tests/Helpers/models.php | 26 +- tests/Unit/Models/GetTest.php | 27 +- ..._212226_create_test_translations_table.php | 31 +++ 11 files changed, 255 insertions(+), 184 deletions(-) delete mode 100644 src/Casts/ColumnCast.php create mode 100644 src/Services/Registry.php create mode 100644 src/Services/Relation.php create mode 100644 tests/database/migrations/2024_06_19_212226_create_test_translations_table.php diff --git a/src/Casts/ColumnCast.php b/src/Casts/ColumnCast.php deleted file mode 100644 index 0e7c91f..0000000 --- a/src/Casts/ColumnCast.php +++ /dev/null @@ -1,22 +0,0 @@ -getLocale(); - } -} diff --git a/src/Exceptions/UnavailableLocaleException.php b/src/Exceptions/UnavailableLocaleException.php index 80112ac..ce2444c 100644 --- a/src/Exceptions/UnavailableLocaleException.php +++ b/src/Exceptions/UnavailableLocaleException.php @@ -23,6 +23,6 @@ public function __construct(Locale|string|null $locale) protected function available(): string { - return Locales::installed()->pluck('locale.code')->filter()->implode(', '); + return Locales::installed()->pluck('code')->filter()->implode(', '); } } diff --git a/src/HasTranslations.php b/src/HasTranslations.php index e3100ae..43c7666 100644 --- a/src/HasTranslations.php +++ b/src/HasTranslations.php @@ -4,8 +4,18 @@ namespace LaravelLang\Models; -use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Arr; use LaravelLang\Config\Facades\Config; +use LaravelLang\LocaleList\Locale; +use LaravelLang\Locales\Facades\Locales; +use LaravelLang\Models\Eloquent\Translation; +use LaravelLang\Models\Events\TranslationHasBeenSetEvent; +use LaravelLang\Models\Exceptions\AttributeIsNotTranslatableException; +use LaravelLang\Models\Exceptions\UnavailableLocaleException; +use LaravelLang\Models\Services\Registry; +use LaravelLang\Models\Services\Relation; /** * @mixin \Illuminate\Database\Eloquent\Concerns\HasAttributes @@ -13,75 +23,53 @@ */ trait HasTranslations { - //public static function bootHasTranslations(): void - //{ - // static::saved(function (Model $model) { - // // @var \LaravelLang\Models\HasTranslations $model - // $model->translation?->setAttribute('model_id', $model->getKey()); - // $model->translation?->save(); - // - // if (! $model->translation) { - // $model->setRelation('translation', $model->translation()->make()); - // } - // }); - // - // static::deleting(function (Model $model) { - // // @var \LaravelLang\Models\HasTranslations $model - // return $model->translation?->delete() ?? $model->translation()->delete(); - // }); - // - // if (method_exists(static::class, 'forceDeleted')) { - // static::forceDeleted(function (Model $model) { - // // @var \LaravelLang\Models\HasTranslations $model - // return $model->translation?->forceDelete() ?? $model->translation()->forceDelete(); - // }); - // } - // - // if (method_exists(static::class, 'restored')) { - // static::restored(function (Model $model) { - // // @var \LaravelLang\Models\HasTranslations $model - // $model->translation()->onlyTrashed()?->restore(); - // }); - // } - //} - // - public function translation(): HasOne + public static function bootHasTranslations(): void { - $suffix = Config::shared()->models->suffix; + static::retrieved(function (Model $model) { + Relation::initializeModel($model); + }); - return $this->hasOne(static::class . $suffix, 'item_id'); + static::saved(function (Model $model) { + /** @var \LaravelLang\Models\HasTranslations $model */ + $model->translations?->each?->save(); + }); + // + // static::deleting(function (Model $model) { + // // @var \LaravelLang\Models\HasTranslations $model + // return $model->translation?->delete() ?? $model->translation()->delete(); + // }); + // + // if (method_exists(static::class, 'forceDeleted')) { + // static::forceDeleted(function (Model $model) { + // // @var \LaravelLang\Models\HasTranslations $model + // return $model->translation?->forceDelete() ?? $model->translation()->forceDelete(); + // }); + // } + // + // if (method_exists(static::class, 'restored')) { + // static::restored(function (Model $model) { + // // @var \LaravelLang\Models\HasTranslations $model + // $model->translation()->onlyTrashed()?->restore(); + // }); + // } } + + protected static function translationModelName(): string + { + return static::class . Config::shared()->models->suffix; + } + // - //public function setTranslation( - // string $column, - // array|ContentData|float|int|string|null $value, - // Locale|string|null $locale = null - //): static { - // $this->validateTranslationColumn($column, $locale, true); - // - // if (is_null($this->translation)) { - // $this->setRelation('translation', $this->translation()->make()); - // } - // - // TranslationHasBeenSetEvent::dispatch( - // $this, - // $column, - // $locale?->value ?? $locale, - // $this->getTranslation($column, $locale), - // $value - // ); - // - // $this->translation->content->set($column, $value, $locale); - // - // return $this; - //} - // - //public function getTranslation(string $column, Locale|string|null $locale = null): float|int|string|null - //{ - // $this->validateTranslationColumn($column, $locale); - // - // return $this->translation?->content?->get($column, $locale); - //} + + public function initializeHasTranslations(): void + { + $this->with = array_unique($this->with + ['translations']); + } + + public function translations(): HasMany + { + return $this->hasMany(static::translationModelName(), 'item_id'); + } // //public function hasTranslated(string $column, Locale|string|null $locale = null): bool //{ @@ -90,10 +78,48 @@ public function translation(): HasOne // return $this->translation->content?->has($column, $locale) ?? false; //} // - //public function isTranslatable(string $column): bool - //{ - // return in_array($column, $this->translatable(), true); - //} + // + public function setTranslation( + string $column, + array|float|int|string|null $value, + Locale|string|null $locale = null + ): static { + $this->validateTranslationColumn($column, $locale); + + Relation::initializeModel($this); + + TranslationHasBeenSetEvent::dispatch( + $this, + $column, + $locale?->value ?? $locale, + $this->getTranslation($column, $locale), + $value + ); + + $this->translation($locale)->setAttribute($column, $value); + + return $this; + } + + public function getTranslation(string $column, Locale|string|null $locale = null): float|int|string|null + { + $this->validateTranslationColumn($column, $locale); + + if (! $locale) { + $current = Locales::getCurrent()->code; + $fallback = Locales::getFallback()->code; + + return $this->translation($current)?->getAttribute($column) + ?? $this->translation($fallback)?->getAttribute($column); + } + + return $this->translation($locale)?->getAttribute($column); + } + + public function isTranslatable(string $column): bool + { + return in_array($column, $this->translatable(), true); + } // //public function forgetTranslation(string $column, Locale|string|null $locale = null): void //{ @@ -111,45 +137,54 @@ public function translation(): HasOne // AllTranslationsHasBeenForgetEvent::dispatch($this); //} // - //public function translatable(): array - //{ - // return []; - //} - // - //public function getAttribute($key): mixed - //{ - // if ($this->isTranslatable($key)) { - // return $this->getTranslation($key); - // } - // - // return parent::getAttribute($key); - //} - // - //public function newInstance($attributes = [], $exists = false): static - //{ - // $basic = Arr::except($attributes, $this->translatable()); - // $translatable = Arr::only($attributes, $this->translatable()); - // - // $model = parent::newInstance($basic, $exists); - // - // foreach ($translatable as $key => $value) { - // $model->setTranslation($key, $value); - // } - // - // return $model; - //} - // - //protected function validateTranslationColumn( - // string $column, - // Locale|string|null $locale, - // bool $withInstalled = false - //): void { - // if (! $this->isTranslatable($column)) { - // throw new AttributeIsNotTranslatableException($column, $this); - // } + + public function getAttribute($key): mixed + { + if ($this->isTranslatable($key)) { + return $this->getTranslation($key); + } + + return parent::getAttribute($key); + } + // - // if ($locale && ! $withInstalled && ! Locales::isInstalled($locale)) { - // throw new UnavailableLocaleException($locale); - // } - //} + + public function newInstance($attributes = [], $exists = false): static + { + $basic = Arr::except($attributes, $this->translatable()); + $translatable = Arr::only($attributes, $this->translatable()); + + $model = parent::newInstance($basic, $exists); + + foreach ($translatable as $key => $value) { + $model->setTranslation($key, $value); + } + + return $model; + } + + public function translatable(): array + { + return Registry::get(__METHOD__, function () { + return (new (static::translationModelName())())->translatable(); + }); + } + + protected function translation(Locale|string|null $locale = null): ?Translation + { + return $this->translations->get( + Locales::get($locale)->code + ); + } + + protected function validateTranslationColumn(string $column, Locale|string|null $locale): void + { + if (! $this->isTranslatable($column)) { + throw new AttributeIsNotTranslatableException($column, $this); + } + + if ($locale && ! Locales::isInstalled($locale)) { + throw new UnavailableLocaleException($locale); + } + } } diff --git a/src/Services/Registry.php b/src/Services/Registry.php new file mode 100644 index 0000000..fc14b03 --- /dev/null +++ b/src/Services/Registry.php @@ -0,0 +1,17 @@ +translations) && blank($model->load('translations')->translations)) { + $model->setRelation('translations', new DBCollection()); + } + + static::locales()->each(function (LocaleData $locale) use ($model) { + if (! $model->translations?->has($locale->code)) { + $model->translations->put($locale->code, static::initializeLocale($model, $locale->code)); + } + }); + } + + public static function initializeLocale(Model $model, string $locale): Translation + { + return (new (static::modelName($model))()) + ->setAttribute('item_id', $model->getKey()) + ->setAttribute('locale', $locale); + } + + protected static function modelName(Model $model): string + { + return get_class($model) . Config::shared()->models->suffix; + } + + protected static function locales(): Collection + { + return Locales::installed(); + } +} diff --git a/stubs/helper.stub b/stubs/helper.stub index c97612e..5b3470f 100644 --- a/stubs/helper.stub +++ b/stubs/helper.stub @@ -4,11 +4,12 @@ namespace {{namespace}} { use {{translationNamespace}}; + use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; /** {{properties}} - * @property-read {{translationModel}} $translation + * @property-read Collection<{{translationModel}}> $translations */ class {{model}} extends Model {} } diff --git a/tests/Fixtures/Models/TestModel.php b/tests/Fixtures/Models/TestModel.php index c38cc62..35f2c88 100644 --- a/tests/Fixtures/Models/TestModel.php +++ b/tests/Fixtures/Models/TestModel.php @@ -4,7 +4,7 @@ namespace Tests\Fixtures\Models; -use App\Models\TestTranslation; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use LaravelLang\Models\HasTranslations; @@ -13,7 +13,7 @@ * @property string $key * @property string|null $title * @property string|null $description - * @property-read TestTranslation $translation + * @property-read Collection $translations */ class TestModel extends Model { diff --git a/tests/Fixtures/Models/TestModelTranslation.php b/tests/Fixtures/Models/TestModelTranslation.php index c0519c5..9dca705 100644 --- a/tests/Fixtures/Models/TestModelTranslation.php +++ b/tests/Fixtures/Models/TestModelTranslation.php @@ -4,7 +4,6 @@ namespace Tests\Fixtures\Models; -use LaravelLang\Models\Casts\ColumnCast; use LaravelLang\Models\Eloquent\Translation; class TestModelTranslation extends Translation @@ -14,9 +13,4 @@ class TestModelTranslation extends Translation 'title', 'description', ]; - - protected $casts = [ - 'title' => ColumnCast::class, - 'description' => ColumnCast::class, - ]; } diff --git a/tests/Helpers/models.php b/tests/Helpers/models.php index c387cc3..725e45c 100644 --- a/tests/Helpers/models.php +++ b/tests/Helpers/models.php @@ -2,7 +2,6 @@ declare(strict_types=1); -use Illuminate\Support\Collection; use Tests\Constants\FakeValue; use Tests\Fixtures\Models\TestModel; @@ -10,35 +9,24 @@ function fakeModel( ?string $key = null, ?string $main = null, ?string $fallback = null, - ?string $custom = null, - ?string $uninstalled = null + ?string $custom = null ): TestModel { $key ??= fake()->word; $model = TestModel::create(compact('key')); - if ($main || $fallback || $custom || $uninstalled) { + if ($main || $fallback || $custom) { fakeTranslation($model, $main, $fallback, $custom); } return $model; } -function fakeTranslation( - TestModel $model, - ?string $text = null, - ?string $fallback = null, - ?string $custom = null, - ?string $uninstalled = null -): void { - $model->translation->fill([ - FakeValue::ColumnTitle => collect() - ->when($text, fn (Collection $values) => $values->put(FakeValue::LocaleMain, $text)) - ->when($fallback, fn (Collection $values) => $values->put(FakeValue::LocaleFallback, $fallback)) - ->when($custom, fn (Collection $values) => $values->put(FakeValue::LocaleCustom, $custom)) - ->when($uninstalled, fn (Collection $values) => $values->put(FakeValue::LocaleUninstalled, $uninstalled)) - ->all() - ])->save(); +function fakeTranslation(TestModel $model, ?string $text = null, ?string $fallback = null, ?string $custom = null): void +{ + $model->setTranslation(FakeValue::ColumnTitle, $text, FakeValue::LocaleMain); + $model->setTranslation(FakeValue::ColumnTitle, $fallback, FakeValue::LocaleFallback); + $model->setTranslation(FakeValue::ColumnTitle, $custom, FakeValue::LocaleCustom); } function findFakeModel(): TestModel diff --git a/tests/Unit/Models/GetTest.php b/tests/Unit/Models/GetTest.php index e83738d..741e48b 100644 --- a/tests/Unit/Models/GetTest.php +++ b/tests/Unit/Models/GetTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -use LaravelLang\Models\Eloquent\Translation; use LaravelLang\Models\Exceptions\AttributeIsNotTranslatableException; use LaravelLang\Models\Exceptions\UnavailableLocaleException; use Tests\Constants\FakeValue; +use Tests\Fixtures\Models\TestModelTranslation; use function Pest\Laravel\assertDatabaseEmpty; @@ -15,11 +15,7 @@ $model = fakeModel(main: $text); expect($model->title)->toBeString()->toBe($text); - - expect($model->translation->content->get(FakeValue::ColumnTitle))->toBe($text); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleMain))->toBe($text); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleFallback))->toBeNull(); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleCustom))->toBeNull(); + expect($model->description)->toBeNull(); expect($model->getTranslation(FakeValue::ColumnTitle))->toBe($text); expect($model->getTranslation(FakeValue::ColumnTitle, FakeValue::LocaleMain))->toBe($text); @@ -34,11 +30,6 @@ expect($model->title)->toBeString()->toBe($text); - expect($model->translation->content->get(FakeValue::ColumnTitle))->toBe($text); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleMain))->toBeNull(); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleFallback))->toBe($text); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleCustom))->toBeNull(); - expect($model->getTranslation(FakeValue::ColumnTitle))->toBe($text); expect($model->getTranslation(FakeValue::ColumnTitle, FakeValue::LocaleMain))->toBeNull(); expect($model->getTranslation(FakeValue::ColumnTitle, FakeValue::LocaleFallback))->toBe($text); @@ -52,11 +43,6 @@ expect($model->title)->toBeNull(); - expect($model->translation->content->get(FakeValue::ColumnTitle))->toBeNull(); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleMain))->toBeNull(); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleFallback))->toBeNull(); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleCustom))->toBe($text); - expect($model->getTranslation(FakeValue::ColumnTitle))->toBeNull(); expect($model->getTranslation(FakeValue::ColumnTitle, FakeValue::LocaleMain))->toBeNull(); expect($model->getTranslation(FakeValue::ColumnTitle, FakeValue::LocaleFallback))->toBeNull(); @@ -64,7 +50,7 @@ }); test('uninstalled', function () { - $model = fakeModel(uninstalled: fake()->paragraph); + $model = fakeModel(); $model->getTranslation(FakeValue::ColumnTitle, FakeValue::LocaleUninstalled); })->throws(UnavailableLocaleException::class); @@ -72,15 +58,10 @@ test('without translations model', function () { $model = fakeModel(); - assertDatabaseEmpty(Translation::class); + assertDatabaseEmpty(TestModelTranslation::class); expect($model->title)->toBeNull(); - expect($model->translation->content->get(FakeValue::ColumnTitle))->toBeNull(); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleMain))->toBeNull(); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleFallback))->toBeNull(); - expect($model->translation->content->get(FakeValue::ColumnTitle, FakeValue::LocaleCustom))->toBeNull(); - expect($model->getTranslation(FakeValue::ColumnTitle))->toBeNull(); expect($model->getTranslation(FakeValue::ColumnTitle, FakeValue::LocaleMain))->toBeNull(); expect($model->getTranslation(FakeValue::ColumnTitle, FakeValue::LocaleFallback))->toBeNull(); diff --git a/tests/database/migrations/2024_06_19_212226_create_test_translations_table.php b/tests/database/migrations/2024_06_19_212226_create_test_translations_table.php new file mode 100644 index 0000000..0d1f3ff --- /dev/null +++ b/tests/database/migrations/2024_06_19_212226_create_test_translations_table.php @@ -0,0 +1,31 @@ +id(); + + $table->foreignIdFor(TestModel::class, 'item_id')->constrained()->cascadeOnDelete(); + + $table->string('locale'); + + $table->string('title')->nullable(); + $table->string('description')->nullable(); + + $table->unique(['item_id', 'locale']); + }); + } + + public function down(): void + { + Schema::dropIfExists('test_model_translations'); + } +};