Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
99 / 99
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
PolymorphicEntitySchemaProvider
100.00% covered (success)
100.00%
99 / 99
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%
44 / 44
100.00% covered (success)
100.00%
1 / 1
3
 addCreationSchemaFor
100.00% covered (success)
100.00%
44 / 44
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        $mapping = [];
53        foreach ($discriminatorMapping->getConfigs() as $config) {
54            $key = $config->getDiscriminator();
55            $value = $componentsBuilder->addDisplaySchemaFor($config->getClassName(), $discriminatorMapping->getPropertyName(), nullable: $nullable);
56            assert($value instanceof Reference);
57            $relations[$key] = $value;
58            $mapping[] = $config->getDiscriminator();
59            $schema = $componentsBuilder->getSchemaForReference($value);
60            if ($schema) {
61                $this->fillInDiscriminator($schema, $discriminatorMapping->getPropertyName(), $config->getDiscriminator());
62            }
63        }
64        $schema = new Schema([
65            'type' => 'object',
66            'oneOf' => array_values($relations),
67            'discriminator' => new Discriminator([
68                'propertyName' => $discriminatorMapping->getPropertyName(),
69                'mapping' => array_map(
70                    function (Reference $ref) {
71                        return $ref->getReference();
72                    },
73                    $relations
74                ),
75            ]),
76        ]);
77        ComponentsBuilder::addDescriptionOfObject($schema, $class);
78        MetadataSchemaProvider::applyPropertiesToSchema(
79            $schema,
80            $componentsBuilder,
81            MetadataFactory::getResultMetadata($class, new ApieContext()),
82            true,
83            $nullable
84        );
85        $properties = $schema->properties ?? [];
86        $properties[$discriminatorMapping->getPropertyName()] = new Schema([
87            'type' => 'string',
88            'enum' => $mapping,
89            'nullable' => false,
90        ]);
91        $schema->properties = $properties;
92        $schema->required = [$discriminatorMapping->getPropertyName()];
93
94        $componentsBuilder->setSchema($componentIdentifier, $schema);
95        return $componentsBuilder->getComponents();
96    }
97
98    public function addCreationSchemaFor(
99        ComponentsBuilder $componentsBuilder,
100        string $componentIdentifier,
101        ReflectionClass $class,
102        bool $nullable = false
103    ): Components {
104        
105        $relations = [];
106        $method = $class->getMethod('getDiscriminatorMapping');
107        /** @var DiscriminatorMapping */
108        $discriminatorMapping = $method->invoke(null);
109        $mapping = [];
110        foreach ($discriminatorMapping->getConfigs() as $config) {
111            $key = $config->getDiscriminator();
112            $value = $componentsBuilder->addCreationSchemaFor($config->getClassName(), $discriminatorMapping->getPropertyName());
113            assert($value instanceof Reference);
114            $relations[$key] = $value;
115            $mapping[] = $config->getDiscriminator();
116            $schema = $componentsBuilder->getSchemaForReference($value);
117            if ($schema) {
118                $this->fillInDiscriminator($schema, $discriminatorMapping->getPropertyName(), $config->getDiscriminator());
119            }
120        }
121        $schema = new Schema([
122            'type' => 'object',
123            'oneOf' => array_values($relations),
124            'discriminator' => new Discriminator([
125                'propertyName' => $discriminatorMapping->getPropertyName(),
126                'mapping' => array_map(
127                    function (Reference $ref) {
128                        return $ref->getReference();
129                    },
130                    $relations
131                ),
132            ]),
133        ]);
134        ComponentsBuilder::addDescriptionOfObject($schema, $class);
135        MetadataSchemaProvider::applyPropertiesToSchema(
136            $schema,
137            $componentsBuilder,
138            MetadataFactory::getCreationMetadata($class, new ApieContext()),
139            false,
140            $nullable
141        );
142        $properties = $schema->properties ?? [];
143        $properties[$discriminatorMapping->getPropertyName()] = new Schema([
144            'type' => 'string',
145            'enum' => $mapping,
146            'nullable' => false,
147        ]);
148        $schema->properties = $properties;
149        $schema->required = [$discriminatorMapping->getPropertyName()];
150
151        $componentsBuilder->setSchema($componentIdentifier, $schema);
152        return $componentsBuilder->getComponents();
153    }
154}