Skip to content

Commit

Permalink
Merge pull request #114 from bennett-treptow/main
Browse files Browse the repository at this point in the history
Add Dependency Injection for `rules`, `messages`, and `attributes` methods
  • Loading branch information
rubenvanassche authored Apr 6, 2022
2 parents d877c49 + 0399146 commit 1ae6fe6
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 18 deletions.
66 changes: 66 additions & 0 deletions docs/as-a-data-transfer-object/request-to-data-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,28 @@ class SongData extends Data
```
Rules defined within the `rules` method will always overwrite automatically generated rules.

You can even add dependencies to be automatically injected:
```php
use SongSettingsRepository;

class SongData extends Data
{
public function __construct(
public string $title,
public string $artist,
) {
}

public static function rules(SongSettingsRepository $settings): array
{
return [
'title' => [new RequiredIf($settings->forUser(auth()->user())->title_required), new StringType()],
'artist' => [new Required(), new StringType()],
];
}
}
```

## Mapping a request onto a data object

By default, the package will do a one to one mapping from request to the data object, which means that for each property within the data object, a value with the same key will be searched within the request values.
Expand Down Expand Up @@ -297,6 +319,28 @@ class SongData extends Data
}
}
```
You can also provide dependencies to be injected:

```php
use SongSettingsRepository;

class SongData extends Data
{
public function __construct(
public string $title,
public string $artist,
) {
}

public static function messages(SongSettingsRepository $repository): array
{
return [
'title.required' => 'A '. $repository->forUser(auth()->user())->title_word .' is required',
'artist.required' => 'An artist is required',
];
}
}
```

### Overwriting attributes

Expand All @@ -321,6 +365,28 @@ class SongData extends Data
}
```

You can also provide dependencies to be injected:

```php
use SongSettingsRepository;
class SongData extends Data
{
public function __construct(
public string $title,
public string $artist,
) {
}

public static function attributes(SongSettingsRepository $repository): array
{
return [
'title' => 'titel',
'artist' => 'artiest',
] + $repository->getAttributes();
}
}
```

## Authorizing a request

Just like with Laravel requests, it is possible to authorize an action for certain people only:
Expand Down
20 changes: 5 additions & 15 deletions src/Concerns/ValidateableData.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
use Illuminate\Validation\Validator;
use Spatie\LaravelData\Resolvers\DataValidatorResolver;

/**
* @method static array rules(...$args)
* @method static array messages(...$args)
* @method static array attributes(...$args)
*/
trait ValidateableData
{
public static function validate(Arrayable|array $payload): static
Expand All @@ -17,21 +22,6 @@ public static function validate(Arrayable|array $payload): static
return static::from($payload);
}

public static function rules(): array
{
return [];
}

public static function messages(): array
{
return [];
}

public static function attributes(): array
{
return [];
}

public static function withValidator(Validator $validator): void
{
return;
Expand Down
5 changes: 4 additions & 1 deletion src/Resolvers/DataValidationRulesResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ public function execute(string $class, bool $nullable = false): Collection
{
$resolver = app(DataPropertyValidationRulesResolver::class);

$overWrittenRules = [];
/** @var class-string<\Spatie\LaravelData\Data> $class */
$overWrittenRules = $class::rules();
if(method_exists($class, 'rules')) {
$overWrittenRules = app()->call([$class, 'rules']);
}

return $this->dataConfig->getDataClass($class)
->properties()
Expand Down
4 changes: 2 additions & 2 deletions src/Resolvers/DataValidatorResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public function execute(string $dataClass, Arrayable|array $payload): Validator
$validator = ValidatorFacade::make(
$payload instanceof Arrayable ? $payload->toArray() : $payload,
$rules,
$dataClass::messages(),
$dataClass::attributes()
method_exists($dataClass, 'messages') ? app()->call([$dataClass, 'messages']) : [],
method_exists($dataClass, 'attributes') ? app()->call([$dataClass, 'attributes']) : []
);

$dataClass::withValidator($validator);
Expand Down
51 changes: 51 additions & 0 deletions tests/Resolvers/DataFromSomethingResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use PHPUnit\Util\Exception;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Resolvers\DataFromSomethingResolver;
use Spatie\LaravelData\Tests\Fakes\DummyDto;
use Spatie\LaravelData\Tests\Fakes\DummyModel;
use Spatie\LaravelData\Tests\Fakes\DummyModelWithCasts;
Expand Down Expand Up @@ -143,4 +146,52 @@ public static function optionalArray(array $payload)

$this->assertNull($data::optional(null));
}
/** @test */
public function it_can_resolve_validation_dependencies_for_messages(){
$requestMock = $this->mock(Request::class);
$requestMock->expects('input')->andReturns('value');
$this->app->bind(Request::class, fn() => $requestMock);

$data = new class () extends Data {
public string $name;
public static function rules(){
return [
'name' => ['required']
];
}
public static function messages(Request $request): array
{
return [
'name.required' => $request->input('key') === 'value' ? 'Name is required' : 'Bad'
];
}
};
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('Name is required');
$data::validate(['name' => '']);
}
/** @test */
public function it_can_resolve_validation_dependencies_for_attributes(){
$requestMock = $this->mock(Request::class);
$requestMock->expects('input')->andReturns('value');
$this->app->bind(Request::class, fn() => $requestMock);

$data = new class () extends Data {
public string $name;
public static function rules(){
return [
'name' => ['required']
];
}
public static function attributes(Request $request): array
{
return [
'name' => $request->input('key') === 'value' ? 'Another name' : 'Bad'
];
}
};
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('The Another name field is required');
$data::validate(['name' => '']);
}
}
24 changes: 24 additions & 0 deletions tests/Resolvers/DataValidationRulesResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Spatie\LaravelData\Tests\Resolvers;

use Illuminate\Http\Request;
use Spatie\LaravelData\Attributes\WithoutValidation;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
Expand Down Expand Up @@ -108,4 +109,27 @@ public function it_can_skip_certain_properties_from_being_validated()
'age' => ['numeric', 'nullable'],
], $this->resolver->execute($data::class)->all());
}

/** @test */
public function it_can_resolve_dependencies_when_calling_rules()
{
$requestMock = $this->mock(Request::class);
$requestMock->expects('input')->andReturns('value');
$this->app->bind(Request::class, fn() => $requestMock);

$data = new class () extends Data {
public string $name;

public static function rules(Request $request): array
{
return [
'name' => $request->input('key') === 'value' ? ['required'] : ['bail']
];
}
};

$this->assertEquals([
'name' => ['required'],
], $this->resolver->execute($data::class)->all());
}
}

0 comments on commit 1ae6fe6

Please sign in to comment.