Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance Custom Rules: Introduce translatableExists and translatableUnique for Validating Translatable Attributes #406

Merged
merged 7 commits into from
Aug 26, 2024
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
- [Attributes](usage/attributes.md)
- [Forms](usage/forms.md)
- [Pivot Model](usage/pivot-model.md)
- [Custom Validation Rules](usage/custom-validation-rule.md)
107 changes: 107 additions & 0 deletions docs/usage/custom-validation-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
### **Validating Unique and Exists Rule**

You can use custom rules to validate unique and exists rules for translatable attributes.

#### TranslatableUnique

Ensure that the attribute value is unique by checking its absence in the database; if the value already exists, raise a validation exception.

##### Option 1

```php
use Astrotomic\Translatable\Validation\Rules\TranslatableUnique;
...

$person = new Person(['name' => 'john doe']);
$person->save();

$data = [
'name' => 'john doe',
'email' => 'john@example.com'
];
$validator = Validator::make($data, [
'name' => ['required', new TranslatableUnique(Person::class, 'name')],
]);

```

##### Option 2

```php
use Astrotomic\Translatable\Validation\Rules\TranslatableUnique;
amjadbanimattar marked this conversation as resolved.
Show resolved Hide resolved
...

$person = new Person(['name' => 'john doe']);
$person->save();

$data = [
'name:en' => 'john doe',
'email' => 'john@example.com'
];

$validator = Validator::make($data, [
'name:en' => ['required', Rule::translatableUnique(Person::class, 'name:en')],
]);

```

##### Option 2

```php
use Illuminate\Validation\Rule;
...

$person = new Person(['name' => 'john doe']);
$person->save();

$data = [
'name:en' => 'john doe',
'email' => 'john@example.com'
];

$validator = Validator::make($data, [
'name:en' => ['required', Rule::translatableUnique(Person::class, 'name:en')],
]);

```


#### TranslatableExists

Verify if the attribute value exists by confirming its presence in the database; if the value does not exist, raise a validation exception.


##### Option 1
```php
use Astrotomic\Translatable\Validation\Rules\TranslatableExists;
...

$person = new Person(['name' => 'john doe']);
$person->save();

$data = [
'name' => 'john doe',
'email' => 'john@example.com'
];
$validator = Validator::make($data, [
'name' => ['required', new TranslatableExists(Person::class, 'name')],
]);
```

##### Option 2
```php
use Illuminate\Validation\Rule;
...

$person = new Person(['name' => 'john doe']);
$person->save();

$data = [
'name:en' => 'john doe',
'email' => 'john@example.com'
];

$validator = Validator::make($data, [
'name:en' => ['required', Rule::translatableExists(Person::class, 'name:en')],
]);
```
19 changes: 17 additions & 2 deletions src/Translatable/Contracts/Translatable.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ public static function disableDeleteTranslationsCascade(): void;

public static function enableDeleteTranslationsCascade(): void;

public function deleteTranslations($locales = null): void;
/**
* @param string|array<string>|null $locales
*/
public function deleteTranslations(string|array|null $locales = null): void;

public function getDefaultLocale(): ?string;

Expand All @@ -28,23 +31,35 @@ public function getTranslation(?string $locale = null, ?bool $withFallback = nul

public function getTranslationOrNew(?string $locale = null): Model;

/**
* @return array<string,array<string,mixed>>
*/
public function getTranslationsArray(): array;

public function hasTranslation(?string $locale = null): bool;

public function isTranslationAttribute(string $key): bool;

/**
* @param null|array<string> $except
*/
public function replicateWithTranslations(?array $except = null): Model;

public function setDefaultLocale(?string $locale);
public function setDefaultLocale(?string $locale): self;

public function translate(?string $locale = null, bool $withFallback = false): ?Model;

public function translateOrDefault(?string $locale = null): ?Model;

public function translateOrNew(?string $locale = null): Model;

/**
* @return HasOne<Model>
*/
public function translation(): HasOne;

/**
* @return HasMany<Model>
*/
public function translations(): HasMany;
}
9 changes: 8 additions & 1 deletion src/Translatable/Locales.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Translation\Translator as TranslatorContract;

/**
* @implements Arrayable<string, string>
* @implements ArrayAccess<string, string>
*/
class Locales implements Arrayable, ArrayAccess
{
/**
Expand All @@ -16,7 +20,7 @@ class Locales implements Arrayable, ArrayAccess
protected $config;

/**
* @var array
* @var array<string,string>
*/
protected $locales = [];

Expand All @@ -38,6 +42,9 @@ public function add(string $locale): void
$this->locales[$locale] = $locale;
}

/**
* @return array<string>
*/
public function all(): array
{
return array_values($this->locales);
Expand Down
3 changes: 2 additions & 1 deletion src/Translatable/Translatable.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Astrotomic\Translatable;

use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract;
use Astrotomic\Translatable\Traits\Relationship;
use Astrotomic\Translatable\Traits\Scopes;
use Illuminate\Database\Eloquent\Collection;
Expand Down Expand Up @@ -306,7 +307,7 @@ public function setAttribute($key, $value)
return parent::setAttribute($key, $value);
}

public function setDefaultLocale(?string $locale)
public function setDefaultLocale(?string $locale): TranslatableContract
{
$this->defaultLocale = $locale;

Expand Down
24 changes: 20 additions & 4 deletions src/Translatable/TranslatableServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,43 @@

namespace Astrotomic\Translatable;

use Astrotomic\Translatable\Validation\Rules\TranslatableExists;
use Astrotomic\Translatable\Validation\Rules\TranslatableUnique;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rule;

class TranslatableServiceProvider extends ServiceProvider
{
public function boot()
public function boot(): void
{
$this->publishes([
__DIR__.'/../config/translatable.php' => config_path('translatable.php'),
], 'translatable');

$this->loadTranslationsFrom(__DIR__.'/../lang', 'translatable');
$this->publishes([
__DIR__.'/../lang' => $this->app->langPath('vendor/translatable'),
], 'translatable-lang');
}

public function register()
public function register(): void
{
$this->mergeConfigFrom(
__DIR__.'/../config/translatable.php', 'translatable'
__DIR__.'/../config/translatable.php',
'translatable'
);

Rule::macro('translatableUnique', function (string $model, string $field): TranslatableUnique {
return new TranslatableUnique($model, $field);
});
Rule::macro('translatableExists', function (string $model, string $field): TranslatableExists {
return new TranslatableExists($model, $field);
});

$this->registerTranslatableHelper();
}

protected function registerTranslatableHelper()
protected function registerTranslatableHelper(): void
{
$this->app->singleton('translatable.locales', Locales::class);
$this->app->singleton(Locales::class);
Expand Down
25 changes: 24 additions & 1 deletion src/Translatable/Validation/RuleFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class RuleFactory
protected $suffix;

/**
* @var null|array
* @var null|array<string>
*/
protected $locales = null;

Expand All @@ -39,6 +39,16 @@ public function __construct(Repository $config, ?int $format = null, ?string $pr
$this->suffix = $suffix ?? $config->get('translatable.rule_factory.suffix');
}

/**
* Create a set of validation rules.
*
* @param array<mixed> $rules The validation rules to be parsed.
* @param int|null $format The format to be used for parsing (e.g., 'dot' or 'bracket').
* @param string|null $prefix The prefix to be applied to each rule key.
* @param string|null $suffix The suffix to be applied to each rule key.
* @param array<string>|null $locales The locales to be used for translating rule attributes.
* @return array<string,mixed> The parsed validation rules.
*/
public static function make(array $rules, ?int $format = null, ?string $prefix = null, ?string $suffix = null, ?array $locales = null): array
{
/** @var RuleFactory $factory */
Expand All @@ -49,6 +59,13 @@ public static function make(array $rules, ?int $format = null, ?string $prefix =
return $factory->parse($rules);
}

/**
* Set the locales to be used for translating rule attributes.
*
* @param array<string>|null $locales The locales to be set. If null, all available locales will be used.
*
* @throws \InvalidArgumentException If a provided locale is not defined in the available locales.
*/
public function setLocales(?array $locales = null): self
{
/** @var Locales */
Expand All @@ -71,6 +88,12 @@ public function setLocales(?array $locales = null): self
return $this;
}

/**
* Parse the input array of rules, applying format and translation to translatable attributes.
*
* @param array<mixed> $input The input array of rules to be parsed.
* @return array<mixed> The parsed array of rules.
*/
public function parse(array $input): array
{
$rules = [];
Expand Down
Loading