Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions src/Resolvers/DataValidationMessagesAndAttributesResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
class DataValidationMessagesAndAttributesResolver
{
public function __construct(
protected DataConfig $dataConfig,
protected DataConfig $dataConfig,
protected DataMorphClassResolver $dataMorphClassResolver,
) {
}

public function execute(
string $class,
array $fullPayload,
string $class,
array $fullPayload,
ValidationPath $path,
array $nestingChain = [],
array $nestingChain = [],
): array {
$dataClass = $this->dataConfig->getDataClass($class);

Expand Down Expand Up @@ -74,15 +74,44 @@ public function execute(
continue;
}

$collected = $this->execute(
$dataProperty->type->dataClass,
$fullPayload,
$propertyPath->property('*'),
[...$nestingChain, $dataProperty->type->dataClass],
);
$collectedDataClass = $this->dataConfig->getDataClass($dataProperty->type->dataClass);

if ($collectedDataClass->isAbstract && $collectedDataClass->propertyMorphable) {
$items = Arr::get($fullPayload, $propertyPath->get(), []);

foreach ($items as $index => $item) {
$morphedClass = $this->dataMorphClassResolver->execute(
$collectedDataClass,
[$item],
);

$messages[] = $collected['messages'];
$attributes[] = $collected['attributes'];
if (! $morphedClass) {
$morphedClass = $dataProperty->type->dataClass;
}

$collected = $this->execute(
$morphedClass,
$fullPayload,
$propertyPath->property($index),
[...$nestingChain, $dataProperty->type->dataClass, $morphedClass],
);

$messages[] = $collected['messages'];
$attributes[] = $collected['attributes'];
}

} else {
$collected = $this->execute(
$dataProperty->type->dataClass,
$fullPayload,
$propertyPath->property('*'),
[...$nestingChain, $dataProperty->type->dataClass],
);

$messages[] = $collected['messages'];
$attributes[] = $collected['attributes'];

}
}
}

Expand Down
168 changes: 157 additions & 11 deletions tests/ValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2549,11 +2549,11 @@ class TestDataWithMergedRuleset extends Data
{
public function __construct(
#[Max(10)]
public string $array_rules,
public string $array_rules,
#[Max(10)]
public string $string_rules,
public string $string_rules,
#[WithoutValidation]
public string $without_validation,
public string $without_validation,
public TestNestedDataWithMergedRules $nested
) {
}
Expand Down Expand Up @@ -2613,7 +2613,7 @@ enum TestValidationPropertyMorphableEnum: string
{
case A = 'a';
case B = 'b';
};
}

abstract class TestValidationAbstractPropertyMorphableData extends Data implements PropertyMorphableData
{
Expand Down Expand Up @@ -2699,7 +2699,7 @@ public function __construct(
public ?DataCollection $nestedCollection,
) {
}
};
}

DataValidationAsserter::for(TestValidationNestedPropertyMorphableData::class)
->assertErrors([
Expand Down Expand Up @@ -2739,8 +2739,8 @@ public function __construct(
#[PropertyForMorph]
public TestValidationPropertyMorphableEnum $variant,
#[Max(1)]
public int $abstract_integer,
public string $abstract_string,
public int $abstract_integer,
public string $abstract_string,
) {
}

Expand Down Expand Up @@ -2770,10 +2770,10 @@ public static function attributes()
class TestValidationCustomMessagePropertyMorphableDataA extends TestValidationCustomMessageAbstractPropertyMorphableData
{
public function __construct(
int $abstract_integer,
string $abstract_string,
int $abstract_integer,
string $abstract_string,
#[Max(1)]
public int $concrete_integer,
public int $concrete_integer,
public string $concrete_string,
) {
parent::__construct(TestValidationPropertyMorphableEnum::A, $abstract_integer, $abstract_string);
Expand All @@ -2796,7 +2796,7 @@ public static function attributes()
}
}

DataValidationAsserter::for(TestValidationCustomMessageAbstractPropertyMorphableData::class)
DataValidationAsserter::for(TestValidationCustomMessagePropertyMorphableDataA::class)
->assertErrors([
'variant' => 'a',
'abstract_integer' => 2,
Expand All @@ -2808,4 +2808,150 @@ public static function attributes()
'abstract_string' => ['The [Abstract String] field is required.'],
]);
});

it('can validate collections of property-morphable data with custom messages and attributes', function () {
class TestCustomMessagesInCollectionOfPropertyMorphableData extends Data
{
public function __construct(

/**
* @var array<TestCustomMessageOfPropertyMorphableData>
*/
public array $items,
) {
}
}

abstract class TestCustomMessageOfPropertyMorphableData extends Data implements PropertyMorphableData
{
public function __construct(
#[PropertyForMorph]
public TestValidationPropertyMorphableEnum $variant,
#[Max(1)]
public int $abstract_integer,
public string $abstract_string,
) {
}

public static function morph(array $properties): ?string
{
return match ($properties['variant']) {
TestValidationPropertyMorphableEnum::A => TestCustomMessagePropertyMorphableDataA::class,
TestValidationPropertyMorphableEnum::B => TestCustomMessagePropertyMorphableDataB::class,
default => null,
};
}

public static function messages()
{
return [
'abstract_integer.max' => 'Abstract class integer test message.',
];
}

public static function attributes()
{
return [
'abstract_string' => '[Abstract String]',
];
}
}

class TestCustomMessagePropertyMorphableDataA extends TestCustomMessageOfPropertyMorphableData
{
public function __construct(
int $abstract_integer,
string $abstract_string,
#[Max(1)]
public int $concrete_integer,
public string $concrete_string,
) {
parent::__construct(TestValidationPropertyMorphableEnum::A, $abstract_integer, $abstract_string);
}

public static function messages()
{
return [
...parent::messages(),
'concrete_integer.max' => 'Concrete class A integer test message.',
];
}

public static function attributes()
{
return [
...parent::attributes(),
'concrete_string' => '[Concrete String A]',
];
}
}

class TestCustomMessagePropertyMorphableDataB extends TestCustomMessageOfPropertyMorphableData
{
public function __construct(
int $abstract_integer,
string $abstract_string,
#[Max(1)]
public int $concrete_integer,
public string $concrete_string,
) {
parent::__construct(TestValidationPropertyMorphableEnum::B, $abstract_integer, $abstract_string);
}

public static function messages()
{
return [
...parent::messages(),
'abstract_integer.max' => 'Concrete Class B override of Abstract class integer test message.',
'concrete_integer.max' => 'Concrete class B integer test message.',
];
}

public static function attributes()
{
return [
...parent::attributes(),
'concrete_string' => '[Concrete String B]',
];
}
}


DataValidationAsserter::for(TestCustomMessagesInCollectionOfPropertyMorphableData::class)
->assertErrors([
'items' => [
[
'variant' => 'a',
'abstract_integer' => 2,
'concrete_integer' => 2,
],
[
'variant' => 'invalid',
'abstract_integer' => 2,
'concrete_integer' => 2,
],
[
'variant' => 'b',
'abstract_integer' => 2,
'concrete_integer' => 2,
],
]], [
'items.0.concrete_integer' => ['Concrete class A integer test message.'],
'items.0.concrete_string' => ['The [Concrete String A] field is required.'],
'items.0.abstract_integer' => ['Abstract class integer test message.'],
'items.0.abstract_string' => ['The [Abstract String] field is required.'],

'items.1.variant' => [
'The selected items.1.variant is invalid.',
'The selected items.1.variant is invalid for morph.',
],
'items.1.abstract_integer' => ['Abstract class integer test message.'],
'items.1.abstract_string' => ['The [Abstract String] field is required.'],

'items.2.concrete_integer' => ['Concrete class B integer test message.'],
'items.2.concrete_string' => ['The [Concrete String B] field is required.'],
'items.2.abstract_integer' => ['Concrete Class B override of Abstract class integer test message.'],
'items.2.abstract_string' => ['The [Abstract String] field is required.'],
]);
});
});