Skip to content
Draft
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
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"type": "library",
"require": {
"php": ">=8.2",
"ramsey/collection": "^2.1.1"
"ramsey/collection": "^2.1.1",
"thecodingmachine/safe": "^2 || ^3.0",
"nette/utils": "^4.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.74",
Expand Down
24 changes: 21 additions & 3 deletions src/Attribute/ObjectType/Slots.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +18,7 @@
use Pinto\Slots\RenameSlots;
use Pinto\Slots\Slot;
use Pinto\Slots\SlotList;
use Pinto\Slots\Validation;

/**
* An attribute representing an object with slots.
Expand Down Expand Up @@ -122,22 +124,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<array{string, string, string}> $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
Expand All @@ -162,8 +179,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()) {
Expand All @@ -174,6 +190,8 @@ public function getDefinition(ObjectListInterface $case, \Reflector $r): mixed
$args['fillValueFromThemeObjectClassPropertyWhenEmpty'] = $rParam->name;
}

$args['validation'] = Validation\PhpType::fromReflection($rParam);

$slots[] = new Slot(...$args);
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/Exception/Slots/BuildValidation.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<array{string, string, string}> $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)));
}
}
2 changes: 1 addition & 1 deletion src/PintoMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* @param array<class-string<ObjectListInterface>> $enumClasses
* @param array<
* class-string,
* array{class-string<\Pinto\List\ObjectListInterface>, string}
* array{class-string<ObjectListInterface>, string}
* > $enums
* @param array<class-string, mixed> $definitions
* @param array<class-string, string> $buildInvokers
Expand Down
1 change: 1 addition & 0 deletions src/Slots/Slot.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions src/Slots/Validation/NoValidation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Pinto\Slots\Validation;

/**
* Represents a slot with no default value.
*
* @internal
*/
final class NoValidation
{
}
30 changes: 30 additions & 0 deletions src/Slots/Validation/PhpType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Pinto\Slots\Validation;

use Nette\Utils\Type;

/**
* Represents validation against a PHP type.
*
* @internal
*/
final class PhpType
{
private function __construct(
public readonly string $type,
) {
}

public static function fromReflection(\ReflectionParameter $r): static
{
return new static((string) Type::fromReflection($r));
}

public static function fromString(string $string): static
{
return new static($string);
}
}
Loading
Loading