From 375bacaf1cd6e581ca77545ef8012f49845e5c40 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 2 Jun 2026 20:03:36 -0400 Subject: [PATCH] Add support for single-parameter cast functions in class-level casting Handle cast functions with varying parameter counts by detecting the number of parameters and passing only the required arguments. Add test coverage for readonly properties with class-level cast using native PHP functions like `strtolower`. --- src/DataModel.php | 17 +++++++++++----- .../ClassLevelCastReadonly/ClaimSync.php | 20 +++++++++++++++++++ .../ClassLevelCastReadonly/ClaimSyncTest.php | 18 +++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 tests/Unit/Describe/ClassLevelCastReadonly/ClaimSync.php create mode 100644 tests/Unit/Describe/ClassLevelCastReadonly/ClaimSyncTest.php diff --git a/src/DataModel.php b/src/DataModel.php index 930152f..550bce9 100644 --- a/src/DataModel.php +++ b/src/DataModel.php @@ -2,13 +2,13 @@ namespace Zerotoprod\DataModel; +use Closure; use ReflectionAttribute; use ReflectionClass; use ReflectionException; use ReflectionFunction; use ReflectionMethod; use ReflectionUnionType; -use Closure; use UnitEnum; use function is_array; @@ -87,9 +87,9 @@ trait DataModel * @return self * * @throws PropertyRequiredException When a required property key is missing. - * @throws DuplicateDescribeAttributeException When two methods target the same property. + * @throws DuplicateDescribeAttributeException|ReflectionException When two methods target the same property. * - * @see Describe + * @see Describe * @link https://github.com/zero-to-prod/data-model */ public static function from(array|object|null|string $context = [], mixed $instance = null): self @@ -281,8 +281,15 @@ public static function from(array|object|null|string $context = [], mixed $insta } /** Class-level cast */ if ($ClassDescribe?->cast[$property_type] ?? false) { - $self->{$property_name} = - $ClassDescribe?->cast[$property_type]($context[$context_key], $context, $ClassDescribeArguments); + $cast = $ClassDescribe->cast[$property_type]; + $param_count = ($cast instanceof Closure + ? new ReflectionFunction($cast) + : new (is_array($cast) ? ReflectionMethod::class : ReflectionFunction::class)(...(array)$cast)) + ->getNumberOfParameters(); + + $self->{$property_name} = $param_count === 1 + ? $cast($context[$context_key]) + : $cast($context[$context_key], $context, $ClassDescribeArguments); continue; } diff --git a/tests/Unit/Describe/ClassLevelCastReadonly/ClaimSync.php b/tests/Unit/Describe/ClassLevelCastReadonly/ClaimSync.php new file mode 100644 index 0000000..1e5c43b --- /dev/null +++ b/tests/Unit/Describe/ClassLevelCastReadonly/ClaimSync.php @@ -0,0 +1,20 @@ + [ + 'string' => 'strtolower' + ] +])] +class ClaimSync +{ + use DataModel; + + public const name = 'name'; + + readonly public string $name; +} \ No newline at end of file diff --git a/tests/Unit/Describe/ClassLevelCastReadonly/ClaimSyncTest.php b/tests/Unit/Describe/ClassLevelCastReadonly/ClaimSyncTest.php new file mode 100644 index 0000000..71ba687 --- /dev/null +++ b/tests/Unit/Describe/ClassLevelCastReadonly/ClaimSyncTest.php @@ -0,0 +1,18 @@ + 'HELLO World', + ]); + + $this->assertEquals('hello world', $ClaimSync->name); + } +} \ No newline at end of file