From bca367aaba9d4ce49df10c0f7969d5c124f58dd8 Mon Sep 17 00:00:00 2001 From: Christoph Kappestein Date: Sat, 23 Sep 2023 19:07:57 +0200 Subject: [PATCH] popo parser add namespace context --- src/Parser/Context/NamespaceContext.php | 53 ++++++++++++++++++ src/Parser/Popo.php | 73 +++++++++++++++---------- src/Parser/Popo/TypeNameBuilder.php | 41 ++++++++++++++ tests/Parser/Popo/TypeNameTest.php | 67 +++++++++++++++++++++++ 4 files changed, 205 insertions(+), 29 deletions(-) create mode 100644 src/Parser/Context/NamespaceContext.php create mode 100644 src/Parser/Popo/TypeNameBuilder.php create mode 100644 tests/Parser/Popo/TypeNameTest.php diff --git a/src/Parser/Context/NamespaceContext.php b/src/Parser/Context/NamespaceContext.php new file mode 100644 index 00000000..d19d25bd --- /dev/null +++ b/src/Parser/Context/NamespaceContext.php @@ -0,0 +1,53 @@ + + * + * Copyright 2010-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Schema\Parser\Context; + +use PSX\Schema\Parser\ContextInterface; + +/** + * NamespaceContext + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://phpsx.org + */ +class NamespaceContext implements ContextInterface +{ + private int $level; + + /** + * The level variable indicates how many parts of the class name is included in the type name. I.e. for a class + * "Acme\My\SubSystem\Model" separate levels would result in the following names: + * + * - level 1 => "Model" + * - level 2 => "SubSystem_Model" + * - level 3 => "My_SubSystem_Model" + */ + public function __construct(int $level = 1) + { + $this->level = $level; + } + + public function getLevel(): int + { + return $this->level; + } +} diff --git a/src/Parser/Popo.php b/src/Parser/Popo.php index ae4f2811..30827efb 100644 --- a/src/Parser/Popo.php +++ b/src/Parser/Popo.php @@ -25,7 +25,9 @@ use PSX\Schema\DefinitionsInterface; use PSX\Schema\Exception\ParserException; use PSX\Schema\Format; +use PSX\Schema\Parser\Context\NamespaceContext; use PSX\Schema\Parser\Popo\ResolverInterface; +use PSX\Schema\Parser\Popo\TypeNameBuilder; use PSX\Schema\ParserInterface; use PSX\Schema\Schema; use PSX\Schema\SchemaInterface; @@ -53,10 +55,12 @@ class Popo implements ParserInterface { private ResolverInterface $resolver; + private TypeNameBuilder $typeNameBuilder; public function __construct() { $this->resolver = self::createDefaultResolver(); + $this->typeNameBuilder = new TypeNameBuilder(); } /** @@ -67,20 +71,21 @@ public function parse(string $schema, ?ContextInterface $context = null): Schema $className = str_replace('.', '\\', $schema); $definitions = new Definitions(); - $this->parseClass($className, $definitions); + $this->parseClass($className, $definitions, $context); - $name = (new ReflectionClass($className))->getShortName(); + $name = $this->getTypeName(new ReflectionClass($className), $context); $type = TypeFactory::getReference($name); return new Schema($type, $definitions); } - protected function parseClass(string $className, DefinitionsInterface $definitions): TypeInterface + protected function parseClass(string $className, DefinitionsInterface $definitions, ?ContextInterface $context = null): TypeInterface { $class = new ReflectionClass($className); - if ($definitions->hasType($class->getShortName())) { - return $definitions->getType($class->getShortName()); + $typeName = $this->getTypeName($class, $context); + if ($definitions->hasType($typeName)) { + return $definitions->getType($typeName); } $type = $this->resolver->resolveClass($class); @@ -90,14 +95,14 @@ protected function parseClass(string $className, DefinitionsInterface $definitio $annotations[] = $attribute->newInstance(); } - $definitions->addType($class->getShortName(), $type); + $definitions->addType($typeName, $type); if ($type instanceof StructType) { $parent = $class->getParentClass(); if ($parent instanceof \ReflectionClass) { $extends = $this->parseClass($parent->getName(), $definitions); if ($extends instanceof StructType) { - $type->setExtends($parent->getShortName()); + $type->setExtends($this->getTypeName($parent, $context)); } } } @@ -108,12 +113,12 @@ protected function parseClass(string $className, DefinitionsInterface $definitio if ($type instanceof StructType) { $this->parseStructAnnotations($annotations, $type); - $this->parseProperties($class, $type, $definitions); + $this->parseProperties($class, $type, $definitions, $context); } elseif ($type instanceof MapType) { $this->parseMapAnnotations($annotations, $type); - $this->parseReferences($type, $definitions); + $this->parseReferences($type, $definitions, $context); } elseif ($type instanceof ReferenceType) { - $this->parseReferences($type, $definitions); + $this->parseReferences($type, $definitions, $context); } else { throw new ParserException('Could not determine class type'); } @@ -123,7 +128,7 @@ protected function parseClass(string $className, DefinitionsInterface $definitio return $type; } - private function parseProperties(ReflectionClass $class, StructType $property, DefinitionsInterface $definitions) + private function parseProperties(ReflectionClass $class, StructType $property, DefinitionsInterface $definitions, ?ContextInterface $context): void { $properties = Popo\ObjectReader::getProperties($class); $mapping = []; @@ -140,7 +145,7 @@ private function parseProperties(ReflectionClass $class, StructType $property, D $type = $this->parseProperty($reflection); if ($type instanceof TypeInterface) { - $this->parseReferences($type, $definitions); + $this->parseReferences($type, $definitions, $context); $property->addProperty($key, $type); } @@ -189,7 +194,7 @@ private function parseProperty(\ReflectionProperty $reflection): ?TypeInterface return $type; } - private function parseCommonAnnotations(array $annotations, TypeAbstract $type) + private function parseCommonAnnotations(array $annotations, TypeAbstract $type): void { foreach ($annotations as $annotation) { if ($annotation instanceof Attribute\Title) { @@ -206,7 +211,7 @@ private function parseCommonAnnotations(array $annotations, TypeAbstract $type) } } - private function parseScalarAnnotations(array $annotations, ScalarType $type) + private function parseScalarAnnotations(array $annotations, ScalarType $type): void { foreach ($annotations as $annotation) { if ($annotation instanceof Attribute\Format) { @@ -220,7 +225,7 @@ private function parseScalarAnnotations(array $annotations, ScalarType $type) } } - private function parseStructAnnotations(array $annotations, StructType $type) + private function parseStructAnnotations(array $annotations, StructType $type): void { foreach ($annotations as $annotation) { if ($annotation instanceof Attribute\Required) { @@ -229,7 +234,7 @@ private function parseStructAnnotations(array $annotations, StructType $type) } } - private function parseMapAnnotations(array $annotations, MapType $type) + private function parseMapAnnotations(array $annotations, MapType $type): void { foreach ($annotations as $annotation) { if ($annotation instanceof Attribute\MinProperties) { @@ -240,7 +245,7 @@ private function parseMapAnnotations(array $annotations, MapType $type) } } - private function parseArrayAnnotations(array $annotations, ArrayType $type) + private function parseArrayAnnotations(array $annotations, ArrayType $type): void { foreach ($annotations as $annotation) { if ($annotation instanceof Attribute\MinItems) { @@ -253,7 +258,7 @@ private function parseArrayAnnotations(array $annotations, ArrayType $type) } } - private function parseStringAnnotations(array $annotations, StringType $type) + private function parseStringAnnotations(array $annotations, StringType $type): void { foreach ($annotations as $annotation) { if ($annotation instanceof Attribute\MinLength) { @@ -271,7 +276,7 @@ private function parseStringAnnotations(array $annotations, StringType $type) } } - private function parseNumberAnnotations(array $annotations, NumberType $type) + private function parseNumberAnnotations(array $annotations, NumberType $type): void { foreach ($annotations as $annotation) { if ($annotation instanceof Attribute\Minimum) { @@ -288,7 +293,7 @@ private function parseNumberAnnotations(array $annotations, NumberType $type) } } - private function parseUnionAnnotations(array $annotations, UnionType $type) + private function parseUnionAnnotations(array $annotations, UnionType $type): void { foreach ($annotations as $annotation) { if ($annotation instanceof Attribute\Discriminator) { @@ -297,43 +302,43 @@ private function parseUnionAnnotations(array $annotations, UnionType $type) } } - private function parseReferences(TypeInterface $type, DefinitionsInterface $definitions) + private function parseReferences(TypeInterface $type, DefinitionsInterface $definitions, ?ContextInterface $context): void { if ($type instanceof MapType) { $additionalProperties = $type->getAdditionalProperties(); if ($additionalProperties instanceof TypeInterface) { - $this->parseReferences($additionalProperties, $definitions); + $this->parseReferences($additionalProperties, $definitions, $context); } } elseif ($type instanceof ArrayType) { $items = $type->getItems(); if ($items instanceof TypeInterface) { - $this->parseReferences($items, $definitions); + $this->parseReferences($items, $definitions, $context); } } elseif ($type instanceof UnionType) { $items = $type->getOneOf(); foreach ($items as $item) { if ($item instanceof ReferenceType) { - $this->parseReferences($item, $definitions); + $this->parseReferences($item, $definitions, $context); } } } elseif ($type instanceof IntersectionType) { $items = $type->getAllOf(); foreach ($items as $item) { if ($item instanceof ReferenceType) { - $this->parseReferences($item, $definitions); + $this->parseReferences($item, $definitions, $context); } } } elseif ($type instanceof ReferenceType) { - $this->parseRef($type, $definitions); + $this->parseRef($type, $definitions, $context); } } - private function parseRef(ReferenceType $type, DefinitionsInterface $definitions) + private function parseRef(ReferenceType $type, DefinitionsInterface $definitions, ?ContextInterface $context): void { $className = $type->getRef(); try { $reflection = new ReflectionClass($className); - $type->setRef($reflection->getShortName()); + $type->setRef($this->getTypeName($reflection, $context)); $this->parseClass($className, $definitions); } catch (\ReflectionException $e) { @@ -346,7 +351,7 @@ private function parseRef(ReferenceType $type, DefinitionsInterface $definitions foreach ($template as $key => $className) { try { $reflection = new ReflectionClass($className); - $result[$key] = $reflection->getShortName(); + $result[$key] = $this->getTypeName($reflection, $context); $this->parseClass($className, $definitions); } catch (\ReflectionException $e) { // in this case the class does not exist @@ -356,6 +361,16 @@ private function parseRef(ReferenceType $type, DefinitionsInterface $definitions } } + private function getTypeName(ReflectionClass $reflection, ?ContextInterface $context): string + { + $level = 1; + if ($context instanceof NamespaceContext) { + $level = $context->getLevel(); + } + + return $this->typeNameBuilder->build($reflection, $level); + } + public static function createDefaultResolver(): Popo\ResolverInterface { return new Popo\Resolver\Composite( diff --git a/src/Parser/Popo/TypeNameBuilder.php b/src/Parser/Popo/TypeNameBuilder.php new file mode 100644 index 00000000..9d199269 --- /dev/null +++ b/src/Parser/Popo/TypeNameBuilder.php @@ -0,0 +1,41 @@ + + * + * Copyright 2010-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Schema\Parser\Popo; + +/** + * TypeNameBuilder + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://phpsx.org + */ +class TypeNameBuilder +{ + public function build(\ReflectionClass $reflection, int $level): string + { + if ($level > 1) { + $parts = explode('\\', $reflection->getName()); + return implode('_', array_slice($parts, $level * -1)); + } + + return $reflection->getShortName(); + } +} diff --git a/tests/Parser/Popo/TypeNameTest.php b/tests/Parser/Popo/TypeNameTest.php new file mode 100644 index 00000000..ba982aea --- /dev/null +++ b/tests/Parser/Popo/TypeNameTest.php @@ -0,0 +1,67 @@ + + * + * Copyright 2010-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Schema\Tests\Parser\Popo; + +use PHPUnit\Framework\TestCase; +use PSX\DateTime\LocalDate; +use PSX\DateTime\LocalDateTime; +use PSX\DateTime\LocalTime; +use PSX\DateTime\Period; +use PSX\Record\RecordInterface; +use PSX\Schema\Parser\Popo\Dumper; +use PSX\Schema\Parser\Popo\TypeNameBuilder; +use PSX\Uri\Uri; + +/** + * TypeNameTest + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://phpsx.org + */ +class TypeNameTest extends TestCase +{ + /** + * @dataProvider nameProvider + */ + public function testBuild(string $class, int $level, string $expect): void + { + $builder = new TypeNameBuilder(); + $actual = $builder->build(new \ReflectionClass($class), $level); + + $this->assertSame($expect, $actual); + } + + public function nameProvider(): array + { + return [ + [self::class, -1, 'TypeNameTest'], + [self::class, 0, 'TypeNameTest'], + [self::class, 1, 'TypeNameTest'], + [self::class, 2, 'Popo_TypeNameTest'], + [self::class, 3, 'Parser_Popo_TypeNameTest'], + [self::class, 4, 'Tests_Parser_Popo_TypeNameTest'], + [self::class, 5, 'Schema_Tests_Parser_Popo_TypeNameTest'], + [self::class, 6, 'PSX_Schema_Tests_Parser_Popo_TypeNameTest'], + [self::class, 7, 'PSX_Schema_Tests_Parser_Popo_TypeNameTest'], + ]; + } +}