Skip to content

Commit

Permalink
Merge pull request #123 from innocenzi/feat/payload-in-validation
Browse files Browse the repository at this point in the history
feat(rules): support `$payload` as a dependency
  • Loading branch information
rubenvanassche authored May 16, 2022
2 parents b8e8cc1 + 50ee40f commit ab99a9f
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 8 deletions.
21 changes: 21 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 @@ -148,6 +148,27 @@ class SongData extends Data
}
```

Additionally, if you need to access the data payload, you can use `$payload` parameter:

```php
class PaymentData extends Data
{
public function __construct(
public string $payment_method,
public ?string $paypal_email,
) {
}

public static function rules(array $payload): array
{
return [
'payment_method' => ['required'],
'paypal_email' => Rule::requiredIf($payload['payment_method'] === 'paypal'),
];
}
}
```

## 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
7 changes: 4 additions & 3 deletions src/Resolvers/DataPropertyValidationRulesResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ public function __construct(
) {
}

public function execute(DataProperty $property, bool $nullable = false): Collection
public function execute(DataProperty $property, array $payload = [], bool $nullable = false): Collection
{
if ($property->isData() || $property->isDataCollection()) {
return $this->getNestedRules($property, $nullable);
return $this->getNestedRules($property, $payload, $nullable);
}

return collect([$property->name() => $this->getRulesForProperty($property, $nullable)]);
}

private function getNestedRules(DataProperty $property, bool $nullable): Collection
private function getNestedRules(DataProperty $property, array $payload = [], bool $nullable): Collection
{
$prefix = match (true) {
$property->isData() => "{$property->name()}.",
Expand All @@ -45,6 +45,7 @@ private function getNestedRules(DataProperty $property, bool $nullable): Collect
return $this->dataValidationRulesResolver
->execute(
$property->dataClassName(),
$payload,
$nullable || ($property->isData() && $property->isNullable())
)
->mapWithKeys(fn (array $rules, string $name) => [
Expand Down
8 changes: 5 additions & 3 deletions src/Resolvers/DataValidationRulesResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,22 @@ public function __construct(protected DataConfig $dataConfig)
{
}

public function execute(string $class, bool $nullable = false): Collection
public function execute(string $class, array $payload = [], bool $nullable = false): Collection
{
$resolver = app(DataPropertyValidationRulesResolver::class);

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

return $this->dataConfig->getDataClass($class)
->properties()
->reject(fn (DataProperty $property) => array_key_exists($property->name(), $overWrittenRules) || ! $property->shouldValidateProperty())
->mapWithKeys(fn (DataProperty $property) => $resolver->execute($property, $nullable))
->mapWithKeys(fn (DataProperty $property) => $resolver->execute($property, $payload, $nullable))
->merge($overWrittenRules);
}
}
5 changes: 3 additions & 2 deletions src/Resolvers/DataValidatorResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ public function __construct(protected DataValidationRulesResolver $dataValidatio
/** @param class-string<\Spatie\LaravelData\Data> $dataClass */
public function execute(string $dataClass, Arrayable|array $payload): Validator
{
$payload = $payload instanceof Arrayable ? $payload->toArray() : $payload;
$rules = app(DataValidationRulesResolver::class)
->execute($dataClass)
->execute($dataClass, $payload)
->toArray();

$validator = ValidatorFacade::make(
$payload instanceof Arrayable ? $payload->toArray() : $payload,
$payload,
$rules,
method_exists($dataClass, 'messages') ? app()->call([$dataClass, 'messages']) : [],
method_exists($dataClass, 'attributes') ? app()->call([$dataClass, 'attributes']) : []
Expand Down
28 changes: 28 additions & 0 deletions tests/Resolvers/DataFromSomethingResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use PHPUnit\Util\Exception;
use Spatie\LaravelData\Data;
Expand Down Expand Up @@ -197,4 +198,31 @@ public static function attributes(Request $request): array
$this->expectExceptionMessage('The Another name field is required');
$data::validate(['name' => '']);
}

/** @test */
public function it_can_resolve_payload_dependency_for_rules()
{
$data = new class () extends Data {
public string $payment_method;
public ?string $paypal_email;

public static function rules(array $payload)
{
return [
'payment_method' => ['required'],
'paypal_email' => Rule::requiredIf($payload['payment_method'] === 'paypal'),
];
}
};

$result = $data::validate(['payment_method' => 'credit_card']);
$this->assertEquals([
'payment_method' => 'credit_card',
'paypal_email' => null,
], $result->toArray());

$this->expectException(ValidationException::class);
$this->expectExceptionMessage('The paypal email field is required');
$data::validate(['payment_method' => 'paypal']);
}
}
19 changes: 19 additions & 0 deletions tests/Resolvers/DataValidationRulesResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,23 @@ public static function rules(Request $request): array
'name' => ['required'],
], $this->resolver->execute($data::class)->all());
}

/** @test */
public function it_can_resolve_payload_when_calling_rules()
{
$data = new class () extends Data {
public string $name;

public static function rules(array $payload): array
{
return [
'name' => $payload['name'] === 'foo' ? ['required'] : ['sometimes'],
];
}
};

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

0 comments on commit ab99a9f

Please sign in to comment.