Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
81 / 81
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
PolymorphicEntitySchemaProvider
100.00% covered (success)
100.00%
81 / 81
100.00% covered (success)
100.00%
4 / 4
10
100.00% covered (success)
100.00%
1 / 1
 supports
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 fillInDiscriminator
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 addDisplaySchemaFor
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
3
 addCreationSchemaFor
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2namespace Apie\SchemaGenerator\SchemaProviders;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\Entities\PolymorphicEntityInterface;
6use Apie\Core\Metadata\MetadataFactory;
7use Apie\Core\Other\DiscriminatorMapping;
8use Apie\SchemaGenerator\Builders\ComponentsBuilder;
9use Apie\SchemaGenerator\Interfaces\SchemaProvider;
10use cebe\openapi\spec\Components;
11use cebe\openapi\spec\Discriminator;
12use cebe\openapi\spec\Reference;
13use cebe\openapi\spec\Schema;
14use ReflectionClass;
15
16/**
17 * @implements SchemaProvider<PolymorphicEntityInterface>
18 */
19class PolymorphicEntitySchemaProvider implements SchemaProvider
20{
21    public function supports(ReflectionClass $class): bool
22    {
23        if (!$class->implementsInterface(PolymorphicEntityInterface::class)) {
24            return false;
25        }
26        $method = $class->getMethod('getDiscriminatorMapping');
27        return $method->getDeclaringClass()->name === $class->name && !$method->isAbstract();
28    }
29
30    private function fillInDiscriminator(Schema $schema, string $propertyName, string $propertyValue): void
31    {
32        $properties = $schema->properties ?? [];
33        $properties[$propertyName] = new Schema([
34            'type' => 'string',
35            'enum' => [$propertyValue],
36            'nullable' => false,
37        ]);
38        $schema->properties = $properties;
39    }
40
41    public function addDisplaySchemaFor(
42        ComponentsBuilder $componentsBuilder,
43        string $componentIdentifier,
44        ReflectionClass $class,
45        bool $nullable = false
46    ): Components {
47        $relations = [];
48        $method = $class->getMethod('getDiscriminatorMapping');
49        /** @var DiscriminatorMapping */
50        $discriminatorMapping = $method->invoke(null);
51
52        foreach ($discriminatorMapping->getConfigs() as $config) {
53            $key = $config->getDiscriminator();
54            $value = $componentsBuilder->addDisplaySchemaFor($config->getClassName(), $discriminatorMapping->getPropertyName(), nullable: $nullable);
55            assert($value instanceof Reference);
56            $relations[$key] = $value;
57            $schema = $componentsBuilder->getSchemaForReference($value);
58            if ($schema) {
59                $this->fillInDiscriminator($schema, $discriminatorMapping->getPropertyName(), $config->getDiscriminator());
60            }
61        }
62        $schema = new Schema([
63            'type' => 'object',
64            'oneOf' => array_values($relations),
65            'discriminator' => new Discriminator([
66                'propertyName' => $discriminatorMapping->getPropertyName(),
67                'mapping' => array_map(
68                    function (Reference $ref) {
69                        return $ref->getReference();
70                    },
71                    $relations
72                ),
73            ]),
74        ]);
75        ComponentsBuilder::addDescriptionOfObject($schema, $class);
76        MetadataSchemaProvider::applyPropertiesToSchema(
77            $schema,
78            $componentsBuilder,
79            MetadataFactory::getResultMetadata($class, new ApieContext()),
80            true,
81            $nullable
82        );
83        
84        $schema->required = [$discriminatorMapping->getPropertyName()];
85
86        $componentsBuilder->setSchema($componentIdentifier, $schema);
87        return $componentsBuilder->getComponents();
88    }
89
90    public function addCreationSchemaFor(
91        ComponentsBuilder $componentsBuilder,
92        string $componentIdentifier,
93        ReflectionClass $class,
94        bool $nullable = false
95    ): Components {
96        $relations = [];
97        $method = $class->getMethod('getDiscriminatorMapping');
98        /** @var DiscriminatorMapping */
99        $discriminatorMapping = $method->invoke(null);
100        foreach ($discriminatorMapping->getConfigs() as $config) {
101            $key = $config->getDiscriminator();
102            $value = $componentsBuilder->addCreationSchemaFor($config->getClassName(), $discriminatorMapping->getPropertyName());
103            assert($value instanceof Reference);
104            $relations[$key] = $value;
105            $schema = $componentsBuilder->getSchemaForReference($value);
106            if ($schema) {
107                $this->fillInDiscriminator($schema, $discriminatorMapping->getPropertyName(), $config->getDiscriminator());
108            }
109        }
110        $schema = new Schema([
111            'type' => 'object',
112            'oneOf' => array_values($relations),
113            'discriminator' => new Discriminator([
114                'propertyName' => $discriminatorMapping->getPropertyName(),
115                'mapping' => array_map(
116                    function (Reference $ref) {
117                        return $ref->getReference();
118                    },
119                    $relations
120                ),
121            ]),
122        ]);
123        ComponentsBuilder::addDescriptionOfObject($schema, $class);
124        MetadataSchemaProvider::applyPropertiesToSchema(
125            $schema,
126            $componentsBuilder,
127            MetadataFactory::getCreationMetadata($class, new ApieContext()),
128            false,
129            $nullable
130        );
131        $schema->required = [$discriminatorMapping->getPropertyName()];
132
133        $componentsBuilder->setSchema($componentIdentifier, $schema);
134        return $componentsBuilder->getComponents();
135    }
136}