diff --git a/src/Compiler/Mapper/Object/MapDiscriminatedObject.php b/src/Compiler/Mapper/Object/MapDiscriminatedObject.php index 3835aa9..5d597b6 100644 --- a/src/Compiler/Mapper/Object/MapDiscriminatedObject.php +++ b/src/Compiler/Mapper/Object/MapDiscriminatedObject.php @@ -42,19 +42,17 @@ public function __construct( public function compile(Expr $value, Expr $path, PhpCodeBuilder $builder): CompiledExpr { - $objectMapperMethods = []; + $objectMapperMethodCalls = []; foreach ($this->objectMappers as $key => $objectMapper) { $objectMapperMethodName = $builder->uniqMethodName('map' . ucfirst($key)); $objectMapperMethod = $builder->mapperMethod($objectMapperMethodName, $objectMapper)->makePrivate()->getNode(); - $objectMapperMethods[$key] = $objectMapperMethodName; + $objectMapperMethodCall = $builder->methodCall($builder->var('this'), $objectMapperMethodName, [$value, $path]); + $objectMapperMethodCalls[$key] = $objectMapperMethodCall; $builder->addMethod($objectMapperMethod); } - $validMappingKeysConstName = $builder->uniqConstantName('VALID_MAPPINGS', $objectMapperMethods); - $builder->addConstant($validMappingKeysConstName, $objectMapperMethods); - $statements = [ $builder->if($builder->not($builder->funcCall($builder->importFunction('is_array'), [$value])), [ $builder->throw( @@ -108,10 +106,19 @@ public function compile(Expr $value, Expr $path, PhpCodeBuilder $builder): Compi ), ]); - $selectedMapperMethodName = $builder->arrayDimFetch($builder->classConstFetch('self', $validMappingKeysConstName), $discriminatorMapperCall); + $subtypeMatchArms = []; + + foreach ($objectMapperMethodCalls as $key => $objectMapperMethodCall) { + $subtypeMatchArms[] = $builder->matchArm( + $builder->val($key), + $objectMapperMethodCall, + ); + } + + $matchedSubtype = $builder->match($discriminatorMapperCall, $subtypeMatchArms); return new CompiledExpr( - $builder->methodCall($builder->var('this'), $selectedMapperMethodName, [$value, $path]), + $matchedSubtype, $statements, ); } diff --git a/src/Compiler/Php/PhpCodeBuilder.php b/src/Compiler/Php/PhpCodeBuilder.php index bfd8cc7..f34433e 100644 --- a/src/Compiler/Php/PhpCodeBuilder.php +++ b/src/Compiler/Php/PhpCodeBuilder.php @@ -24,8 +24,10 @@ use PhpParser\Node\Expr\BinaryOp\SmallerOrEqual; use PhpParser\Node\Expr\BooleanNot; use PhpParser\Node\Expr\Instanceof_; +use PhpParser\Node\Expr\Match_; use PhpParser\Node\Expr\PreInc; use PhpParser\Node\Expr\Ternary; +use PhpParser\Node\MatchArm; use PhpParser\Node\Name; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_ as ClassNode; @@ -216,6 +218,22 @@ public function if(Expr $if, array $then, ?array $else = null): If_ return new If_($if, ['stmts' => $then, 'elseifs' => $elseIfClauses, 'else' => $elseClause]); } + /** + * @param list $arms + */ + public function match(Expr $cond, array $arms = []): Match_ + { + return new Match_($cond, $arms); + } + + public function matchArm(?Expr $cond, Expr $body): MatchArm + { + return new MatchArm( + $cond !== null ? [$cond] : null, + $body, + ); + } + /** * @param list $statements */ @@ -260,6 +278,11 @@ public function throw(Expr $expr): Throw_ return new Throw_($expr); } + public function throwExpr(Expr $expr): Expr\Throw_ + { + return new Expr\Throw_($expr); + } + public function assign(Expr $var, Expr $expr): Expression { return new Expression(new Assign($var, $expr)); diff --git a/tests/Compiler/Mapper/Object/Data/HierarchicalParentInputMapper.php b/tests/Compiler/Mapper/Object/Data/HierarchicalParentInputMapper.php index 9f4cc1b..9712882 100644 --- a/tests/Compiler/Mapper/Object/Data/HierarchicalParentInputMapper.php +++ b/tests/Compiler/Mapper/Object/Data/HierarchicalParentInputMapper.php @@ -25,8 +25,6 @@ */ class HierarchicalParentInputMapper implements Mapper { - private const VALID_MAPPINGS = ['childOne' => 'mapChildOne', 'childTwo' => 'mapChildTwo']; - public function __construct(private readonly MapperProvider $provider) { } @@ -49,7 +47,10 @@ public function map(mixed $data, array $path = []): HierarchicalParentInput throw MappingFailedException::incorrectValue($data['type'], [...$path, 'type'], 'one of ' . implode(', ', ['childOne', 'childTwo'])); } - return $this->{self::VALID_MAPPINGS[$this->mapType3($data['type'], [...$path, 'type'])]}($data, $path); + return match ($this->mapType3($data['type'], [...$path, 'type'])) { + 'childOne' => $this->mapChildOne($data, $path), + 'childTwo' => $this->mapChildTwo($data, $path), + }; } /** diff --git a/tests/Compiler/Mapper/Object/Data/HierarchicalWithEnumParentInputMapper.php b/tests/Compiler/Mapper/Object/Data/HierarchicalWithEnumParentInputMapper.php index 6f93f32..e00f517 100644 --- a/tests/Compiler/Mapper/Object/Data/HierarchicalWithEnumParentInputMapper.php +++ b/tests/Compiler/Mapper/Object/Data/HierarchicalWithEnumParentInputMapper.php @@ -24,8 +24,6 @@ */ class HierarchicalWithEnumParentInputMapper implements Mapper { - private const VALID_MAPPINGS = ['childOne' => 'mapChildOne']; - public function __construct(private readonly MapperProvider $provider) { } @@ -48,7 +46,9 @@ public function map(mixed $data, array $path = []): HierarchicalWithEnumParentIn throw MappingFailedException::incorrectValue($data['type'], [...$path, 'type'], 'one of ' . implode(', ', ['childOne'])); } - return $this->{self::VALID_MAPPINGS[$this->mapType2($data['type'], [...$path, 'type'])]}($data, $path); + return match ($this->mapType2($data['type'], [...$path, 'type'])) { + 'childOne' => $this->mapChildOne($data, $path), + }; } /** diff --git a/tests/Compiler/Mapper/Object/Data/ParentMapper.php b/tests/Compiler/Mapper/Object/Data/ParentMapper.php deleted file mode 100644 index 8e011f9..0000000 --- a/tests/Compiler/Mapper/Object/Data/ParentMapper.php +++ /dev/null @@ -1,283 +0,0 @@ - - */ -class ParentMapper implements Mapper -{ - private const VALID_MAPPINGS = ['childOne' => 'mapChildOne', 'childTwo' => 'mapChildTwo']; - - public function __construct(private readonly MapperProvider $provider) - { - } - - /** - * @param list $path - * @throws MappingFailedException - */ - public function map(mixed $data, array $path = []): HierarchicalParentInput - { - if (!is_array($data)) { - throw MappingFailedException::incorrectType($data, $path, 'array'); - } - - if (!array_key_exists('type', $data)) { - throw MappingFailedException::missingKey($path, 'type'); - } - - if (!in_array($data['type'], ['childOne', 'childTwo'], true)) { - throw MappingFailedException::incorrectValue($data['type'], [...$path, 'type'], 'one of ' . implode(', ', ['childOne', 'childTwo'])); - } - - return $this->{self::VALID_MAPPINGS[$this->mapType3($data['type'], [...$path, 'type'])]}($data, $path); - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapId(mixed $data, array $path = []): int - { - if (!is_int($data)) { - throw MappingFailedException::incorrectType($data, $path, 'int'); - } - - return $data; - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapName(mixed $data, array $path = []): string - { - if (!is_string($data)) { - throw MappingFailedException::incorrectType($data, $path, 'string'); - } - - return $data; - } - - /** - * @param list $path - * @return OptionalSome - * @throws MappingFailedException - */ - private function mapAge(mixed $data, array $path = []): OptionalSome - { - if (!is_int($data)) { - throw MappingFailedException::incorrectType($data, $path, 'int'); - } - - return Optional::of($data); - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapType(mixed $data, array $path = []): string - { - if (!is_string($data)) { - throw MappingFailedException::incorrectType($data, $path, 'string'); - } - - return $data; - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapChildOneField(mixed $data, array $path = []): string - { - if (!is_string($data)) { - throw MappingFailedException::incorrectType($data, $path, 'string'); - } - - return $data; - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapChildOne(mixed $data, array $path = []): HierarchicalChildOneInput - { - if (!is_array($data)) { - throw MappingFailedException::incorrectType($data, $path, 'array'); - } - - if (!array_key_exists('id', $data)) { - throw MappingFailedException::missingKey($path, 'id'); - } - - if (!array_key_exists('name', $data)) { - throw MappingFailedException::missingKey($path, 'name'); - } - - if (!array_key_exists('type', $data)) { - throw MappingFailedException::missingKey($path, 'type'); - } - - if (!array_key_exists('childOneField', $data)) { - throw MappingFailedException::missingKey($path, 'childOneField'); - } - - $knownKeys = ['id' => true, 'name' => true, 'age' => true, 'type' => true, 'childOneField' => true]; - $extraKeys = array_diff_key($data, $knownKeys); - - if (count($extraKeys) > 0) { - throw MappingFailedException::extraKeys($path, array_keys($extraKeys)); - } - - return new HierarchicalChildOneInput( - $this->mapId($data['id'], [...$path, 'id']), - $this->mapName($data['name'], [...$path, 'name']), - array_key_exists('age', $data) ? $this->mapAge($data['age'], [...$path, 'age']) : Optional::none($path, 'age'), - $this->mapType($data['type'], [...$path, 'type']), - $this->mapChildOneField($data['childOneField'], [...$path, 'childOneField']), - ); - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapId2(mixed $data, array $path = []): int - { - if (!is_int($data)) { - throw MappingFailedException::incorrectType($data, $path, 'int'); - } - - return $data; - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapName2(mixed $data, array $path = []): string - { - if (!is_string($data)) { - throw MappingFailedException::incorrectType($data, $path, 'string'); - } - - return $data; - } - - /** - * @param list $path - * @return OptionalSome - * @throws MappingFailedException - */ - private function mapAge2(mixed $data, array $path = []): OptionalSome - { - if (!is_int($data)) { - throw MappingFailedException::incorrectType($data, $path, 'int'); - } - - return Optional::of($data); - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapType2(mixed $data, array $path = []): string - { - if (!is_string($data)) { - throw MappingFailedException::incorrectType($data, $path, 'string'); - } - - return $data; - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapChildTwoField(mixed $data, array $path = []): int - { - if (!is_int($data)) { - throw MappingFailedException::incorrectType($data, $path, 'int'); - } - - return $data; - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapChildTwo(mixed $data, array $path = []): HierarchicalChildTwoInput - { - if (!is_array($data)) { - throw MappingFailedException::incorrectType($data, $path, 'array'); - } - - if (!array_key_exists('id', $data)) { - throw MappingFailedException::missingKey($path, 'id'); - } - - if (!array_key_exists('name', $data)) { - throw MappingFailedException::missingKey($path, 'name'); - } - - if (!array_key_exists('type', $data)) { - throw MappingFailedException::missingKey($path, 'type'); - } - - if (!array_key_exists('childTwoField', $data)) { - throw MappingFailedException::missingKey($path, 'childTwoField'); - } - - $knownKeys = ['id' => true, 'name' => true, 'age' => true, 'type' => true, 'childTwoField' => true]; - $extraKeys = array_diff_key($data, $knownKeys); - - if (count($extraKeys) > 0) { - throw MappingFailedException::extraKeys($path, array_keys($extraKeys)); - } - - return new HierarchicalChildTwoInput( - $this->mapId2($data['id'], [...$path, 'id']), - $this->mapName2($data['name'], [...$path, 'name']), - array_key_exists('age', $data) ? $this->mapAge2($data['age'], [...$path, 'age']) : Optional::none($path, 'age'), - $this->mapType2($data['type'], [...$path, 'type']), - $this->mapChildTwoField($data['childTwoField'], [...$path, 'childTwoField']), - ); - } - - /** - * @param list $path - * @throws MappingFailedException - */ - private function mapType3(mixed $data, array $path = []): string - { - if (!is_string($data)) { - throw MappingFailedException::incorrectType($data, $path, 'string'); - } - - return $data; - } -}