From d6355f7d25cf4a241bd529bc1fc31f17d2506022 Mon Sep 17 00:00:00 2001 From: dpi Date: Thu, 10 Apr 2025 19:55:40 +0800 Subject: [PATCH 1/6] record types, validate --- composer.json | 3 +- src/Attribute/ObjectType/Slots.php | 24 ++++++- src/Exception/Slots/BuildValidation.php | 14 ++++ src/Slots/Slot.php | 1 + src/Slots/Validation/NoValidation.php | 14 ++++ src/Slots/Validation/PhpType.php | 30 ++++++++ tests/PintoSlotsTest.php | 72 ++++++++++++------- tests/fixtures/Lists/PintoListSlots.php | 3 + ...ntoObjectSlotsValidationFailurePhpType.php | 55 ++++++++++++++ 9 files changed, 187 insertions(+), 29 deletions(-) create mode 100644 src/Slots/Validation/NoValidation.php create mode 100644 src/Slots/Validation/PhpType.php create mode 100644 tests/fixtures/Objects/Slots/PintoObjectSlotsValidationFailurePhpType.php diff --git a/composer.json b/composer.json index c914e0e..d03df56 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,8 @@ "require": { "php": ">=8.2", "ramsey/collection": "^2.1.1", - "thecodingmachine/safe": "^2 || ^3.0" + "thecodingmachine/safe": "^2 || ^3.0", + "nette/utils": "^4.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.74", diff --git a/src/Attribute/ObjectType/Slots.php b/src/Attribute/ObjectType/Slots.php index b0a6db5..b37752f 100644 --- a/src/Attribute/ObjectType/Slots.php +++ b/src/Attribute/ObjectType/Slots.php @@ -4,6 +4,7 @@ namespace Pinto\Attribute\ObjectType; +use Nette\Utils\Type; use Pinto\Exception\PintoThemeDefinition; use Pinto\Exception\Slots\BuildValidation; use Pinto\List\ObjectListInterface; @@ -15,6 +16,7 @@ use Pinto\Slots\NoDefaultValue; use Pinto\Slots\Slot; use Pinto\Slots\SlotList; +use Pinto\Slots\Validation; /** * An attribute representing an object with slots. @@ -120,22 +122,37 @@ public static function lateBindObjectToBuild(mixed $build, mixed $definition, ob public static function validateBuild(mixed $build, mixed $definition, string $objectClassName): void { - // @todo validate typing? assert($build instanceof Build); assert($definition instanceof Definition); $missingSlots = []; + /** @var array $validationFailures */ + $validationFailures = []; foreach ($definition->slots as $slot) { // When there is no default, the slot must be defined: if ($slot->defaultValue instanceof NoDefaultValue && false === $build->pintoHas($slot->name)) { $missingSlots[] = $slot->name instanceof \UnitEnum ? $slot->name->name : $slot->name; } + + if (!$slot->validation instanceof Validation\NoValidation && true === $build->pintoHas($slot->name)) { + // @todo this might need to be optional... + $v = $build->pintoGet($slot->name); + $expectedType = Type::fromString($slot->validation->type); + $actualType = \is_object($v) ? $v::class : \gettype($v); + if (!$expectedType->allows($actualType)) { + $validationFailures[] = [$slot->name instanceof \UnitEnum ? $slot->name->name : $slot->name, $slot->validation->type, $actualType]; + } + } } if ([] !== $missingSlots) { throw BuildValidation::missingSlots($objectClassName, $missingSlots); } + + if ([] !== $validationFailures) { + throw BuildValidation::validation($objectClassName, $validationFailures); + } } public function getDefinition(ObjectListInterface $case, \Reflector $r): mixed @@ -160,8 +177,7 @@ public function getDefinition(ObjectListInterface $case, \Reflector $r): mixed $parametersFrom = false === $this->bindPromotedProperties ? $reflectionMethod : ($reflectionMethod->getDeclaringClass()->getConstructor() ?? throw new \LogicException('A constructor must be defined to use `bindPromotedProperties`')); foreach ($parametersFrom->getParameters() as $rParam) { $paramType = $rParam->getType(); - if ($paramType instanceof \ReflectionNamedType) { - // @todo use the type @ $paramType->getName() + if ($paramType instanceof \ReflectionNamedType || $paramType instanceof \ReflectionUnionType) { $args = ['name' => $rParam->getName()]; // Default should only be set if there is a default. if ($rParam->isDefaultValueAvailable()) { @@ -172,6 +188,8 @@ public function getDefinition(ObjectListInterface $case, \Reflector $r): mixed $args['fillValueFromThemeObjectClassPropertyWhenEmpty'] = $rParam->name; } + $args['validation'] = Validation\PhpType::fromReflection($rParam); + $slots[] = new Slot(...$args); } } diff --git a/src/Exception/Slots/BuildValidation.php b/src/Exception/Slots/BuildValidation.php index aa964ca..4cace82 100644 --- a/src/Exception/Slots/BuildValidation.php +++ b/src/Exception/Slots/BuildValidation.php @@ -14,4 +14,18 @@ public static function missingSlots(string $objectClassName, array $missingSlots { return new static(sprintf('Build for %s missing values for %s: `%s`', $objectClassName, 1 === count($missingSlots) ? 'slot' : 'slots', \implode('`, `', $missingSlots))); } + + /** + * @param class-string $objectClassName + * @param array $validationFailures + */ + public static function validation(string $objectClassName, array $validationFailures): static + { + $messages = []; + foreach ($validationFailures as [$slotName, $expectedType, $actualType]) { + $messages[] = sprintf('`%s` expects `%s`, but got `%s`', $slotName, $expectedType, $actualType); + } + + return new static(sprintf('Build for %s failed validation: %s', $objectClassName, \implode(', ', $messages))); + } } diff --git a/src/Slots/Slot.php b/src/Slots/Slot.php index d866caa..589f3df 100644 --- a/src/Slots/Slot.php +++ b/src/Slots/Slot.php @@ -13,6 +13,7 @@ public function __construct( string $useNamedParameters = self::useNamedParameters, public readonly mixed $defaultValue = new NoDefaultValue(), public readonly ?string $fillValueFromThemeObjectClassPropertyWhenEmpty = null, + public readonly Validation\NoValidation|Validation\PhpType $validation = new Validation\NoValidation(), ) { if (self::useNamedParameters !== $useNamedParameters) { throw new \LogicException(self::useNamedParameters); diff --git a/src/Slots/Validation/NoValidation.php b/src/Slots/Validation/NoValidation.php new file mode 100644 index 0000000..469ac30 --- /dev/null +++ b/src/Slots/Validation/NoValidation.php @@ -0,0 +1,14 @@ +cases expansion.. - [1 => $slotsDefinition] = Pinto\ObjectType\ObjectTypeDiscovery::definitionForThemeObject(PintoObjectSlotsExplicitEnumClass::class, PintoListSlots::PintoObjectSlotsExplicitEnumClass, definitionDiscovery: new Pinto\DefinitionDiscovery()); + [1 => $slotsDefinition] = ObjectTypeDiscovery::definitionForThemeObject(PintoObjectSlotsExplicitEnumClass::class, PintoListSlots::PintoObjectSlotsExplicitEnumClass, definitionDiscovery: new DefinitionDiscovery()); static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); static::assertEquals(new SlotList([ @@ -92,13 +95,13 @@ public function testSlotsExplicitEnumClass(): void */ public function testPintoObjectSlotsBindPromotedPublic(): void { - [1 => $slotsDefinition] = Pinto\ObjectType\ObjectTypeDiscovery::definitionForThemeObject(PintoObjectSlotsBindPromotedPublic::class, PintoListSlots::PintoObjectSlotsBindPromotedPublic, definitionDiscovery: new Pinto\DefinitionDiscovery()); + [1 => $slotsDefinition] = ObjectTypeDiscovery::definitionForThemeObject(PintoObjectSlotsBindPromotedPublic::class, PintoListSlots::PintoObjectSlotsBindPromotedPublic, definitionDiscovery: new DefinitionDiscovery()); static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); static::assertEquals(new SlotList([ - new Slots\Slot(name: 'aPublic', fillValueFromThemeObjectClassPropertyWhenEmpty: 'aPublic'), - new Slots\Slot(name: 'aPublicAndSetInInvoker', fillValueFromThemeObjectClassPropertyWhenEmpty: 'aPublicAndSetInInvoker'), - new Slots\Slot(name: 'aPrivate'), + new Slots\Slot(name: 'aPublic', fillValueFromThemeObjectClassPropertyWhenEmpty: 'aPublic', validation: Slots\Validation\PhpType::fromString('string')), + new Slots\Slot(name: 'aPublicAndSetInInvoker', fillValueFromThemeObjectClassPropertyWhenEmpty: 'aPublicAndSetInInvoker', validation: Slots\Validation\PhpType::fromString('string')), + new Slots\Slot(name: 'aPrivate', validation: Slots\Validation\PhpType::fromString('string')), ]), $slotsDefinition->slots); $object = new PintoObjectSlotsBindPromotedPublic('the public', 'public but also overridden in invoker', 'the private'); @@ -114,7 +117,7 @@ public function testPintoObjectSlotsBindPromotedPublic(): void */ public function PintoObjectSlotsBindPromotedPublicNonConstructor(): void { - [1 => $slotsDefinition] = Pinto\ObjectType\ObjectTypeDiscovery::definitionForThemeObject(PintoObjectSlotsBindPromotedPublicNonConstructor::class, PintoListSlots::PintoObjectSlotsBindPromotedPublicNonConstructor, definitionDiscovery: new Pinto\DefinitionDiscovery()); + [1 => $slotsDefinition] = ObjectTypeDiscovery::definitionForThemeObject(PintoObjectSlotsBindPromotedPublicNonConstructor::class, PintoListSlots::PintoObjectSlotsBindPromotedPublicNonConstructor, definitionDiscovery: new DefinitionDiscovery()); static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); static::assertEquals(new SlotList([ @@ -133,7 +136,7 @@ public function testPintoObjectSlotsBindPromotedPublicWithDefinedSlots(): void { static::expectException(Pinto\Exception\PintoThemeDefinition::class); static::expectExceptionMessage('Slots must use reflection (no explicitly defined `$slots`) when promoted properties bind is on.'); - Pinto\ObjectType\ObjectTypeDiscovery::definitionForThemeObject(Pinto\tests\fixtures\Objects\Faulty\PintoObjectSlotsBindPromotedPublicWithDefinedSlots::class, Lists\PintoFaultyList::PintoObjectSlotsBindPromotedPublicWithDefinedSlots, definitionDiscovery: new Pinto\DefinitionDiscovery()); + ObjectTypeDiscovery::definitionForThemeObject(Pinto\tests\fixtures\Objects\Faulty\PintoObjectSlotsBindPromotedPublicWithDefinedSlots::class, Lists\PintoFaultyList::PintoObjectSlotsBindPromotedPublicWithDefinedSlots, definitionDiscovery: new DefinitionDiscovery()); } public function testSlotsExplicitIgnoresReflection(): void @@ -153,6 +156,25 @@ public function testSlotsBuildMissingValue(): void $object(); } + /** + * @covers \Pinto\Attribute\ObjectType\Slots::validateBuild + * @covers \Pinto\Exception\Slots\BuildValidation::validation + */ + public function testSlotsBuildMissingValueValidationFailurePhpType(): void + { + [1 => $slotsDefinition] = ObjectTypeDiscovery::definitionForThemeObject(PintoObjectSlotsValidationFailurePhpType::class, PintoListSlots::PintoObjectSlotsValidationFailurePhpType, new DefinitionDiscovery()); + + static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); + static::assertEquals(new SlotList([ + new Slots\Slot(name: 'number', validation: Slots\Validation\PhpType::fromString('int')), + ]), $slotsDefinition->slots); + + $object = new PintoObjectSlotsValidationFailurePhpType(123); + static::expectException(Pinto\Exception\Slots\BuildValidation::class); + static::expectExceptionMessage(sprintf('Build for %s failed validation: `number` expects `int`, but got `string`', PintoObjectSlotsValidationFailurePhpType::class)); + $object(); + } + /** * Tests no constructor is required when #[Slots(slots)] is provided. * @@ -162,7 +184,7 @@ public function testSlotsBuildMissingValue(): void */ public function testSlotsNoConstructor(): void { - $definitions = PintoListSlots::definitions(new Pinto\DefinitionDiscovery()); + $definitions = PintoListSlots::definitions(new DefinitionDiscovery()); // Assert anything (no exception thrown): static::assertGreaterThan(0, count($definitions)); } @@ -179,52 +201,52 @@ public function testSlotsBuildMissingValueWithDefault(): void public function testDefinitionsSlotsAttrOnObject(): void { - $themeDefinitions = PintoListSlots::definitions(new Pinto\DefinitionDiscovery()); - static::assertCount(8, $themeDefinitions); + $themeDefinitions = PintoListSlots::definitions(new DefinitionDiscovery()); + static::assertCount(9, $themeDefinitions); $slotsDefinition = $themeDefinitions[PintoListSlots::Slots]; static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); static::assertEquals(new SlotList([ - new Slots\Slot(name: 'text'), - new Slots\Slot(name: 'number', defaultValue: 3), + new Slots\Slot(name: 'text', validation: Slots\Validation\PhpType::fromString('string')), + new Slots\Slot(name: 'number', defaultValue: 3, validation: Slots\Validation\PhpType::fromString('int')), ]), $slotsDefinition->slots); $slotsDefinition = $themeDefinitions[PintoListSlots::SlotsAttributeOnMethod]; static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); static::assertEquals(new SlotList([ - new Slots\Slot(name: 'foo', defaultValue: null), - new Slots\Slot(name: 'arr', defaultValue: []), + new Slots\Slot(name: 'foo', defaultValue: null, validation: Slots\Validation\PhpType::fromString('?string')), + new Slots\Slot(name: 'arr', defaultValue: [], validation: Slots\Validation\PhpType::fromString('?array')), ]), $slotsDefinition->slots); } public function testDefinitionsSlotsAttrOnList(): void { - $themeDefinitions = Lists\PintoListSlotsOnEnum::definitions(new Pinto\DefinitionDiscovery()); + $themeDefinitions = Lists\PintoListSlotsOnEnum::definitions(new DefinitionDiscovery()); static::assertCount(1, $themeDefinitions); $slotsDefinition = $themeDefinitions[Lists\PintoListSlotsOnEnum::SlotsOnEnum]; static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); static::assertEquals(new SlotList([ - new Slots\Slot(name: 'fooFromList'), - new Slots\Slot(name: 'number', defaultValue: 4), + new Slots\Slot(name: 'fooFromList', validation: Slots\Validation\PhpType::fromString('string')), + new Slots\Slot(name: 'number', defaultValue: 4, validation: Slots\Validation\PhpType::fromString('int')), ]), $slotsDefinition->slots); } public function testDefinitionsSlotsAttrOnListCase(): void { - $themeDefinitions = Lists\PintoListSlotsOnEnumCase::definitions(new Pinto\DefinitionDiscovery()); + $themeDefinitions = Lists\PintoListSlotsOnEnumCase::definitions(new DefinitionDiscovery()); static::assertCount(1, $themeDefinitions); $slotsDefinition = $themeDefinitions[Lists\PintoListSlotsOnEnumCase::SlotsOnEnumCase]; static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); static::assertEquals(new SlotList([ - new Slots\Slot(name: 'fooFromListCase'), + new Slots\Slot(name: 'fooFromListCase', validation: Slots\Validation\PhpType::fromString('string')), ]), $slotsDefinition->slots); } public function testDefinitionsSlotsAttrByInheritance(): void { - $definitionDiscovery = new Pinto\DefinitionDiscovery(); + $definitionDiscovery = new DefinitionDiscovery(); $definitionDiscovery[PintoObjectSlotsByInheritanceChild::class] = Lists\PintoListSlotsByInheritance::SlotsByInheritanceChild; $definitionDiscovery[PintoObjectSlotsByInheritanceGrandParent::class] = Lists\PintoListSlotsByInheritance::SlotsByInheritanceGrandParent; $themeDefinitions = Lists\PintoListSlotsByInheritance::definitions($definitionDiscovery); @@ -233,14 +255,14 @@ public function testDefinitionsSlotsAttrByInheritance(): void $slotsDefinition = $themeDefinitions[Lists\PintoListSlotsByInheritance::SlotsByInheritanceChild]; static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); static::assertEquals(new SlotList([ - new Slots\Slot(name: 'fooFromGrandParent'), + new Slots\Slot(name: 'fooFromGrandParent', validation: Slots\Validation\PhpType::fromString('string')), ]), $slotsDefinition->slots); } public function testDefinitionsSlotsAttrByInheritanceGrandParentUnregistered(): void { // It the parent isn't registered to an enum, no object type is determined. - $definitionDiscovery = new Pinto\DefinitionDiscovery(); + $definitionDiscovery = new DefinitionDiscovery(); // Normally parent is set here. $definitionDiscovery[PintoObjectSlotsByInheritanceChild::class] = Lists\PintoListSlotsByInheritance::SlotsByInheritanceChild; @@ -250,13 +272,13 @@ public function testDefinitionsSlotsAttrByInheritanceGrandParentUnregistered(): public function testDefinitionsSlotsAttrOnListMethodSpecified(): void { - $themeDefinitions = Lists\PintoListSlotsOnEnumMethodSpecified::definitions(new Pinto\DefinitionDiscovery()); + $themeDefinitions = Lists\PintoListSlotsOnEnumMethodSpecified::definitions(new DefinitionDiscovery()); static::assertCount(1, $themeDefinitions); $slotsDefinition = $themeDefinitions[Lists\PintoListSlotsOnEnumMethodSpecified::SlotsOnEnumMethodSpecified]; static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); static::assertEquals(new SlotList([ - new Slots\Slot(name: 'create', defaultValue: 'from method specified on enum #[Slots]'), + new Slots\Slot(name: 'create', defaultValue: 'from method specified on enum #[Slots]', validation: Slots\Validation\PhpType::fromString('?string')), ]), $slotsDefinition->slots); } @@ -293,7 +315,7 @@ public function testSlotAttributeNamedParameters(): void */ public function testRenameSlots(): void { - $definitionDiscovery = new Pinto\DefinitionDiscovery(); + $definitionDiscovery = new DefinitionDiscovery(); $definitionDiscovery[PintoObjectSlotsRenameParent::class] = Lists\PintoListSlotsRename::SlotsRenameParent; $definitionDiscovery[PintoObjectSlotsRenameChild::class] = Lists\PintoListSlotsRename::SlotsRenameChild; $themeDefinitions = Lists\PintoListSlotsRename::definitions($definitionDiscovery); diff --git a/tests/fixtures/Lists/PintoListSlots.php b/tests/fixtures/Lists/PintoListSlots.php index 2e609b8..0d5556b 100644 --- a/tests/fixtures/Lists/PintoListSlots.php +++ b/tests/fixtures/Lists/PintoListSlots.php @@ -22,6 +22,9 @@ enum PintoListSlots implements ObjectListInterface #[Definition(Slots\PintoObjectSlotsMissingSlotValue::class)] case SlotMissingValue; + #[Definition(Slots\PintoObjectSlotsValidationFailurePhpType::class)] + case PintoObjectSlotsValidationFailurePhpType; + #[Definition(Slots\PintoObjectSlotsMissingSlotValueWithDefault::class)] case PintoObjectSlotsMissingSlotValueWithDefault; diff --git a/tests/fixtures/Objects/Slots/PintoObjectSlotsValidationFailurePhpType.php b/tests/fixtures/Objects/Slots/PintoObjectSlotsValidationFailurePhpType.php new file mode 100644 index 0000000..c4f1cd1 --- /dev/null +++ b/tests/fixtures/Objects/Slots/PintoObjectSlotsValidationFailurePhpType.php @@ -0,0 +1,55 @@ +pintoBuild(function (Build $build): Build { + return $build + ->set('number', 'Foo') + ; + }); + } + + private function pintoMapping(): PintoMapping + { + return new PintoMapping( + enumClasses: [], + enums: [ + static::class => [PintoListSlots::class, PintoListSlots::PintoObjectSlotsValidationFailurePhpType->name], + ], + definitions: [ + static::class => new Slots\Definition(new Slots\SlotList([ + new Slots\Slot(name: 'number', defaultValue: null, validation: Slots\Validation\PhpType::fromString('int')), + ])), + ], + buildInvokers: [ + static::class => '__invoke', + ], + types: [static::class => ObjectType\Slots::class], + lsbFactoryCanonicalObjectClasses: [], + ); + } +} From 86d3aa8b6c0057ad8cfca8cfbdf84204648da887 Mon Sep 17 00:00:00 2001 From: dpi Date: Sat, 19 Apr 2025 20:08:21 +0800 Subject: [PATCH 2/6] version --- .github/workflows/workflows.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/workflows.yml b/.github/workflows/workflows.yml index 403df58..4b966f7 100644 --- a/.github/workflows/workflows.yml +++ b/.github/workflows/workflows.yml @@ -65,8 +65,7 @@ jobs: - name: "Run tests" run: | ./vendor/bin/phpunit - - name: "Publish coverage report to Codecov" - uses: "codecov/codecov-action@v1" - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: build/logs/clover.xml + - name: Upload coverage reports to Codecov with GitHub Action + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From bc789988fc52a2692ecfc0c078919d137ab5fd04 Mon Sep 17 00:00:00 2001 From: dpi Date: Wed, 27 Aug 2025 17:55:42 +0800 Subject: [PATCH 3/6] lint --- tests/PintoSlotsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PintoSlotsTest.php b/tests/PintoSlotsTest.php index 925cf65..c46b899 100644 --- a/tests/PintoSlotsTest.php +++ b/tests/PintoSlotsTest.php @@ -283,7 +283,7 @@ public function testModifySlotsAttributeAddMissingSlots(): void public function testDefinitionsSlotsAttrByInheritanceModifiedSlots(): void { - $definitionDiscovery = new Pinto\DefinitionDiscovery(); + $definitionDiscovery = new DefinitionDiscovery(); $definitionDiscovery[PintoObjectSlotsByInheritanceChild::class] = Lists\PintoListSlotsByInheritance::SlotsByInheritanceChild; $definitionDiscovery[PintoObjectSlotsByInheritanceChildModifySlots::class] = Lists\PintoListSlotsByInheritance::PintoObjectSlotsByInheritanceChildModifySlots; $definitionDiscovery[PintoObjectSlotsByInheritanceGrandParent::class] = Lists\PintoListSlotsByInheritance::SlotsByInheritanceGrandParent; From efd1f1a54c00d09f48c66d9a4d0a2d836cf62a7a Mon Sep 17 00:00:00 2001 From: dpi Date: Wed, 27 Aug 2025 17:57:34 +0800 Subject: [PATCH 4/6] lint --- src/PintoMapping.php | 2 +- .../Objects/Slots/PintoObjectSlotsValidationFailurePhpType.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PintoMapping.php b/src/PintoMapping.php index e20e2ba..ffae0be 100644 --- a/src/PintoMapping.php +++ b/src/PintoMapping.php @@ -16,7 +16,7 @@ * @param array> $enumClasses * @param array< * class-string, - * array{class-string<\Pinto\List\ObjectListInterface>, string} + * array{class-string, string} * > $enums * @param array $definitions * @param array $buildInvokers diff --git a/tests/fixtures/Objects/Slots/PintoObjectSlotsValidationFailurePhpType.php b/tests/fixtures/Objects/Slots/PintoObjectSlotsValidationFailurePhpType.php index c4f1cd1..3407f9a 100644 --- a/tests/fixtures/Objects/Slots/PintoObjectSlotsValidationFailurePhpType.php +++ b/tests/fixtures/Objects/Slots/PintoObjectSlotsValidationFailurePhpType.php @@ -20,7 +20,7 @@ final class PintoObjectSlotsValidationFailurePhpType use ObjectTrait; public function __construct( - readonly int $number, + public readonly int $number, ) { } From 0b55f6e2aea862d4081eb9ca1b6778f12599f220 Mon Sep 17 00:00:00 2001 From: dpi Date: Wed, 27 Aug 2025 18:05:35 +0800 Subject: [PATCH 5/6] PintoObjectSlotsByInheritanceGrandParent 000d3f73ea056b8c754ee516c4f10271252e2947 change --- tests/PintoSlotsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PintoSlotsTest.php b/tests/PintoSlotsTest.php index c46b899..3901d33 100644 --- a/tests/PintoSlotsTest.php +++ b/tests/PintoSlotsTest.php @@ -293,7 +293,7 @@ public function testDefinitionsSlotsAttrByInheritanceModifiedSlots(): void $slotsDefinition = $themeDefinitions[Lists\PintoListSlotsByInheritance::PintoObjectSlotsByInheritanceChildModifySlots]; static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); static::assertEquals(new SlotList([ - new Slots\Slot(name: 'fooFromGrandParent'), + new Slots\Slot(name: 'fooFromGrandParent', validation: Slots\Validation\PhpType::fromString('string')), new Slots\Slot(name: 'new_slot'), ]), $slotsDefinition->slots); } From 0d409a76648a8bb2ab50c0c11a97aae974abf93d Mon Sep 17 00:00:00 2001 From: dpi Date: Wed, 27 Aug 2025 19:00:19 +0800 Subject: [PATCH 6/6] disable validation --- src/Attribute/ObjectType/Slots.php | 20 ++++++++--------- tests/PintoSlotsTest.php | 36 +++++++++++++++--------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Attribute/ObjectType/Slots.php b/src/Attribute/ObjectType/Slots.php index 9f4eeee..83d074d 100644 --- a/src/Attribute/ObjectType/Slots.php +++ b/src/Attribute/ObjectType/Slots.php @@ -136,16 +136,16 @@ public static function validateBuild(mixed $build, mixed $definition, string $ob if ($slot->defaultValue instanceof NoDefaultValue && false === $build->pintoHas($slot->name)) { $missingSlots[] = $slot->name instanceof \UnitEnum ? $slot->name->name : $slot->name; } - - if (!$slot->validation instanceof Validation\NoValidation && true === $build->pintoHas($slot->name)) { - // @todo this might need to be optional... - $v = $build->pintoGet($slot->name); - $expectedType = Type::fromString($slot->validation->type); - $actualType = \is_object($v) ? $v::class : \gettype($v); - if (!$expectedType->allows($actualType)) { - $validationFailures[] = [$slot->name instanceof \UnitEnum ? $slot->name->name : $slot->name, $slot->validation->type, $actualType]; - } - } + // + // if (!$slot->validation instanceof Validation\NoValidation && true === $build->pintoHas($slot->name)) { + // // @todo this might need to be optional... + // $v = $build->pintoGet($slot->name); + // $expectedType = Type::fromString($slot->validation->type); + // $actualType = \is_object($v) ? $v::class : \gettype($v); + // if (!$expectedType->allows($actualType)) { + // $validationFailures[] = [$slot->name instanceof \UnitEnum ? $slot->name->name : $slot->name, $slot->validation->type, $actualType]; + // } + // } } if ([] !== $missingSlots) { diff --git a/tests/PintoSlotsTest.php b/tests/PintoSlotsTest.php index 3901d33..0e24af1 100644 --- a/tests/PintoSlotsTest.php +++ b/tests/PintoSlotsTest.php @@ -158,24 +158,24 @@ public function testSlotsBuildMissingValue(): void $object(); } - /** - * @covers \Pinto\Attribute\ObjectType\Slots::validateBuild - * @covers \Pinto\Exception\Slots\BuildValidation::validation - */ - public function testSlotsBuildMissingValueValidationFailurePhpType(): void - { - [1 => $slotsDefinition] = ObjectTypeDiscovery::definitionForThemeObject(PintoObjectSlotsValidationFailurePhpType::class, PintoListSlots::PintoObjectSlotsValidationFailurePhpType, new DefinitionDiscovery()); - - static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); - static::assertEquals(new SlotList([ - new Slots\Slot(name: 'number', validation: Slots\Validation\PhpType::fromString('int')), - ]), $slotsDefinition->slots); - - $object = new PintoObjectSlotsValidationFailurePhpType(123); - static::expectException(Pinto\Exception\Slots\BuildValidation::class); - static::expectExceptionMessage(sprintf('Build for %s failed validation: `number` expects `int`, but got `string`', PintoObjectSlotsValidationFailurePhpType::class)); - $object(); - } + // /** + // * @covers \Pinto\Attribute\ObjectType\Slots::validateBuild + // * @covers \Pinto\Exception\Slots\BuildValidation::validation + // */ + // public function testSlotsBuildMissingValueValidationFailurePhpType(): void + // { + // [1 => $slotsDefinition] = ObjectTypeDiscovery::definitionForThemeObject(PintoObjectSlotsValidationFailurePhpType::class, PintoListSlots::PintoObjectSlotsValidationFailurePhpType, new DefinitionDiscovery()); + // + // static::assertInstanceOf(Slots\Definition::class, $slotsDefinition); + // static::assertEquals(new SlotList([ + // new Slots\Slot(name: 'number', validation: Slots\Validation\PhpType::fromString('int')), + // ]), $slotsDefinition->slots); + // + // $object = new PintoObjectSlotsValidationFailurePhpType(123); + // static::expectException(Pinto\Exception\Slots\BuildValidation::class); + // static::expectExceptionMessage(sprintf('Build for %s failed validation: `number` expects `int`, but got `string`', PintoObjectSlotsValidationFailurePhpType::class)); + // $object(); + // } /** * Tests no constructor is required when #[Slots(slots)] is provided.