Skip to content

Commit ad6369d

Browse files
authored
Merge pull request #406 from amjadbanimattar/main
Enhance Custom Rules: Introduce translatableExists and translatableUnique for Validating Translatable Attributes
2 parents a375546 + 33bcf27 commit ad6369d

File tree

11 files changed

+576
-9
lines changed

11 files changed

+576
-9
lines changed

docs/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@
2020
- [Attributes](usage/attributes.md)
2121
- [Forms](usage/forms.md)
2222
- [Pivot Model](usage/pivot-model.md)
23+
- [Custom Validation Rules](usage/custom-validation-rule.md)
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
### **Validating Unique and Exists Rule**
2+
3+
You can use custom rules to validate unique and exists rules for translatable attributes.
4+
5+
#### TranslatableUnique
6+
7+
Ensure that the attribute value is unique by checking its absence in the database; if the value already exists, raise a validation exception.
8+
9+
##### Option 1
10+
11+
```php
12+
use Astrotomic\Translatable\Validation\Rules\TranslatableUnique;
13+
...
14+
15+
$person = new Person(['name' => 'john doe']);
16+
$person->save();
17+
18+
$data = [
19+
'name' => 'john doe',
20+
'email' => '[email protected]'
21+
];
22+
$validator = Validator::make($data, [
23+
'name' => ['required', new TranslatableUnique(Person::class, 'name')],
24+
]);
25+
26+
```
27+
28+
##### Option 2
29+
30+
```php
31+
use Astrotomic\Translatable\Validation\Rules\TranslatableUnique;
32+
...
33+
34+
$person = new Person(['name' => 'john doe']);
35+
$person->save();
36+
37+
$data = [
38+
'name:en' => 'john doe',
39+
'email' => '[email protected]'
40+
];
41+
42+
$validator = Validator::make($data, [
43+
'name:en' => ['required', Rule::translatableUnique(Person::class, 'name:en')],
44+
]);
45+
46+
```
47+
48+
##### Option 2
49+
50+
```php
51+
use Illuminate\Validation\Rule;
52+
...
53+
54+
$person = new Person(['name' => 'john doe']);
55+
$person->save();
56+
57+
$data = [
58+
'name:en' => 'john doe',
59+
'email' => '[email protected]'
60+
];
61+
62+
$validator = Validator::make($data, [
63+
'name:en' => ['required', Rule::translatableUnique(Person::class, 'name:en')],
64+
]);
65+
66+
```
67+
68+
69+
#### TranslatableExists
70+
71+
Verify if the attribute value exists by confirming its presence in the database; if the value does not exist, raise a validation exception.
72+
73+
74+
##### Option 1
75+
```php
76+
use Astrotomic\Translatable\Validation\Rules\TranslatableExists;
77+
...
78+
79+
$person = new Person(['name' => 'john doe']);
80+
$person->save();
81+
82+
$data = [
83+
'name' => 'john doe',
84+
'email' => '[email protected]'
85+
];
86+
$validator = Validator::make($data, [
87+
'name' => ['required', new TranslatableExists(Person::class, 'name')],
88+
]);
89+
```
90+
91+
##### Option 2
92+
```php
93+
use Illuminate\Validation\Rule;
94+
...
95+
96+
$person = new Person(['name' => 'john doe']);
97+
$person->save();
98+
99+
$data = [
100+
'name:en' => 'john doe',
101+
'email' => '[email protected]'
102+
];
103+
104+
$validator = Validator::make($data, [
105+
'name:en' => ['required', Rule::translatableExists(Person::class, 'name:en')],
106+
]);
107+
```

src/Translatable/Contracts/Translatable.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ public static function disableDeleteTranslationsCascade(): void;
1818

1919
public static function enableDeleteTranslationsCascade(): void;
2020

21-
public function deleteTranslations($locales = null): void;
21+
/**
22+
* @param string|array<string>|null $locales
23+
*/
24+
public function deleteTranslations(string|array|null $locales = null): void;
2225

2326
public function getDefaultLocale(): ?string;
2427

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

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

34+
/**
35+
* @return array<string,array<string,mixed>>
36+
*/
3137
public function getTranslationsArray(): array;
3238

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

3541
public function isTranslationAttribute(string $key): bool;
3642

43+
/**
44+
* @param null|array<string> $except
45+
*/
3746
public function replicateWithTranslations(?array $except = null): Model;
3847

39-
public function setDefaultLocale(?string $locale);
48+
public function setDefaultLocale(?string $locale): self;
4049

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

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

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

56+
/**
57+
* @return HasOne<Model>
58+
*/
4759
public function translation(): HasOne;
4860

61+
/**
62+
* @return HasMany<Model>
63+
*/
4964
public function translations(): HasMany;
5065
}

src/Translatable/Locales.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
use Illuminate\Contracts\Support\Arrayable;
99
use Illuminate\Contracts\Translation\Translator as TranslatorContract;
1010

11+
/**
12+
* @implements Arrayable<string, string>
13+
* @implements ArrayAccess<string, string>
14+
*/
1115
class Locales implements Arrayable, ArrayAccess
1216
{
1317
/**
@@ -16,7 +20,7 @@ class Locales implements Arrayable, ArrayAccess
1620
protected $config;
1721

1822
/**
19-
* @var array
23+
* @var array<string,string>
2024
*/
2125
protected $locales = [];
2226

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

45+
/**
46+
* @return array<string>
47+
*/
4148
public function all(): array
4249
{
4350
return array_values($this->locales);

src/Translatable/Translatable.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Astrotomic\Translatable;
44

5+
use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract;
56
use Astrotomic\Translatable\Traits\Relationship;
67
use Astrotomic\Translatable\Traits\Scopes;
78
use Illuminate\Database\Eloquent\Collection;
@@ -314,7 +315,7 @@ public function setAttribute($key, $value)
314315
return parent::setAttribute($key, $value);
315316
}
316317

317-
public function setDefaultLocale(?string $locale)
318+
public function setDefaultLocale(?string $locale): TranslatableContract
318319
{
319320
$this->defaultLocale = $locale;
320321

src/Translatable/TranslatableServiceProvider.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,43 @@
22

33
namespace Astrotomic\Translatable;
44

5+
use Astrotomic\Translatable\Validation\Rules\TranslatableExists;
6+
use Astrotomic\Translatable\Validation\Rules\TranslatableUnique;
57
use Illuminate\Support\ServiceProvider;
8+
use Illuminate\Validation\Rule;
69

710
class TranslatableServiceProvider extends ServiceProvider
811
{
9-
public function boot()
12+
public function boot(): void
1013
{
1114
$this->publishes([
1215
__DIR__.'/../config/translatable.php' => config_path('translatable.php'),
1316
], 'translatable');
17+
18+
$this->loadTranslationsFrom(__DIR__.'/../lang', 'translatable');
19+
$this->publishes([
20+
__DIR__.'/../lang' => $this->app->langPath('vendor/translatable'),
21+
], 'translatable-lang');
1422
}
1523

16-
public function register()
24+
public function register(): void
1725
{
1826
$this->mergeConfigFrom(
19-
__DIR__.'/../config/translatable.php', 'translatable'
27+
__DIR__.'/../config/translatable.php',
28+
'translatable'
2029
);
2130

31+
Rule::macro('translatableUnique', function (string $model, string $field): TranslatableUnique {
32+
return new TranslatableUnique($model, $field);
33+
});
34+
Rule::macro('translatableExists', function (string $model, string $field): TranslatableExists {
35+
return new TranslatableExists($model, $field);
36+
});
37+
2238
$this->registerTranslatableHelper();
2339
}
2440

25-
protected function registerTranslatableHelper()
41+
protected function registerTranslatableHelper(): void
2642
{
2743
$this->app->singleton('translatable.locales', Locales::class);
2844
$this->app->singleton(Locales::class);

src/Translatable/Validation/RuleFactory.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class RuleFactory
2828
protected $suffix;
2929

3030
/**
31-
* @var null|array
31+
* @var null|array<string>
3232
*/
3333
protected $locales = null;
3434

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

42+
/**
43+
* Create a set of validation rules.
44+
*
45+
* @param array<mixed> $rules The validation rules to be parsed.
46+
* @param int|null $format The format to be used for parsing (e.g., 'dot' or 'bracket').
47+
* @param string|null $prefix The prefix to be applied to each rule key.
48+
* @param string|null $suffix The suffix to be applied to each rule key.
49+
* @param array<string>|null $locales The locales to be used for translating rule attributes.
50+
* @return array<string,mixed> The parsed validation rules.
51+
*/
4252
public static function make(array $rules, ?int $format = null, ?string $prefix = null, ?string $suffix = null, ?array $locales = null): array
4353
{
4454
/** @var RuleFactory $factory */
@@ -49,6 +59,13 @@ public static function make(array $rules, ?int $format = null, ?string $prefix =
4959
return $factory->parse($rules);
5060
}
5161

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

91+
/**
92+
* Parse the input array of rules, applying format and translation to translatable attributes.
93+
*
94+
* @param array<mixed> $input The input array of rules to be parsed.
95+
* @return array<mixed> The parsed array of rules.
96+
*/
7497
public function parse(array $input): array
7598
{
7699
$rules = [];

0 commit comments

Comments
 (0)