Skip to content

Commit 7d1aa98

Browse files
PawelSuwinskidunglassoyuka
authored
operation openapi property support (#430)
Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> Co-authored-by: soyuka <soyuka@users.noreply.github.com>
1 parent 77f1ca5 commit 7d1aa98

12 files changed

Lines changed: 369 additions & 18 deletions

scoper.inc.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
ApiPlatform\Core\Annotation\ApiResource::class,
1818
],
1919
'patchers' => [
20-
function (string $filePath, string $prefix, string $content): string {
20+
static function (string $filePath, string $prefix, string $content): string {
2121
//
2222
// PHP-CS-Fixer patch
2323
//
@@ -35,7 +35,7 @@ function (string $filePath, string $prefix, string $content): string {
3535

3636
// TODO: Temporary patch until the issue is fixed upstream
3737
// @link https://github.com/humbug/php-scoper/issues/285
38-
function (string $filePath, string $prefix, string $content): string {
38+
static function (string $filePath, string $prefix, string $content): string {
3939
if (!str_contains($content, '@')) {
4040
return $content;
4141
}
@@ -51,7 +51,7 @@ function (string $filePath, string $prefix, string $content): string {
5151
$content
5252
);
5353
},
54-
function (string $filePath, string $prefix, string $content): string {
54+
static function (string $filePath, string $prefix, string $content): string {
5555
if (!str_starts_with($filePath, 'src/AnnotationGenerator/')) {
5656
return $content;
5757
}

src/AttributeGenerator/ApiPlatformCoreAttributeGenerator.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
use ApiPlatform\Metadata\Patch;
2424
use ApiPlatform\Metadata\Post;
2525
use ApiPlatform\Metadata\Put;
26+
use ApiPlatform\OpenApi\Model\Operation;
27+
use ApiPlatform\OpenApi\Model\Parameter;
28+
use ApiPlatform\OpenApi\Model\Response;
2629
use ApiPlatform\SchemaGenerator\Model\Attribute;
2730
use ApiPlatform\SchemaGenerator\Model\Class_;
2831
use ApiPlatform\SchemaGenerator\Model\Property;
@@ -39,6 +42,21 @@
3942
*/
4043
final class ApiPlatformCoreAttributeGenerator extends AbstractAttributeGenerator
4144
{
45+
/**
46+
* Hints for not typed array parameters.
47+
*/
48+
private const PRAMETER_TYPE_HINTS = [
49+
Operation::class => [
50+
'responses' => Response::class.'[]',
51+
'parameters' => Parameter::class.'[]',
52+
],
53+
];
54+
55+
/**
56+
* @var array<class-string, array<string, string|null>>
57+
*/
58+
private static array $parameterTypes = [];
59+
4260
public function generateClassAttributes(Class_ $class): array
4361
{
4462
if ($class->hasChild || $class->isEnum()) {
@@ -85,6 +103,22 @@ public function generateClassAttributes(Class_ $class): array
85103
unset($methodConfig['class']);
86104
}
87105

106+
if (\is_array($methodConfig['openapi'] ?? null)) {
107+
$methodConfig['openapi'] = Literal::new(
108+
'Operation',
109+
self::extractParameters(Operation::class, $methodConfig['openapi'])
110+
);
111+
$class->addUse(new Use_(Operation::class));
112+
array_walk_recursive(
113+
self::$parameterTypes,
114+
static function (?string $type) use ($class): void {
115+
if (null !== $type) {
116+
$class->addUse(new Use_(str_replace('[]', '', $type)));
117+
}
118+
}
119+
);
120+
}
121+
88122
$arguments['operations'][] = new Literal(\sprintf('new %s(...?:)',
89123
$operationMetadataClass,
90124
), [$methodConfig ?? []]);
@@ -95,6 +129,80 @@ public function generateClassAttributes(Class_ $class): array
95129
return [new Attribute('ApiResource', $arguments)];
96130
}
97131

132+
/**
133+
* @param class-string $type
134+
* @param mixed[] $values
135+
*
136+
* @return mixed[]
137+
*/
138+
private static function extractParameters(string $type, array $values): array
139+
{
140+
$types = self::$parameterTypes[$type] ??=
141+
(static::PRAMETER_TYPE_HINTS[$type] ?? []) + array_reduce(
142+
(new \ReflectionClass($type))->getConstructor()?->getParameters() ?? [],
143+
static fn (array $types, \ReflectionParameter $refl): array => $types + [
144+
$refl->getName() => $refl->getType() instanceof \ReflectionNamedType
145+
&& !$refl->getType()->isBuiltin()
146+
? $refl->getType()->getName()
147+
: null,
148+
],
149+
[]
150+
);
151+
if (isset(self::$parameterTypes[$type])) {
152+
$types = self::$parameterTypes[$type];
153+
} else {
154+
$types = static::PRAMETER_TYPE_HINTS[$type] ?? [];
155+
$parameterRefls = (new \ReflectionClass($type))
156+
->getConstructor()
157+
?->getParameters() ?? [];
158+
foreach ($parameterRefls as $refl) {
159+
$paramName = $refl->getName();
160+
if (\array_key_exists($paramName, $types)) {
161+
continue;
162+
}
163+
$paramType = $refl->getType();
164+
if ($paramType instanceof \ReflectionNamedType && !$paramType->isBuiltin()) {
165+
$types[$paramName] = $paramType->getName();
166+
} else {
167+
$types[$paramName] = null;
168+
}
169+
}
170+
self::$parameterTypes[$type] = $types;
171+
}
172+
173+
$parameters = array_intersect_key($values, $types);
174+
foreach ($parameters as $name => $parameter) {
175+
$type = $types[$name];
176+
if (null === $type || !\is_array($parameter)) {
177+
continue;
178+
}
179+
$isArrayType = str_ends_with($type, '[]');
180+
/**
181+
* @var class-string
182+
*/
183+
$type = $isArrayType ? substr($type, 0, -2) : $type;
184+
$shortName = (new \ReflectionClass($type))->getShortName();
185+
if ($isArrayType) {
186+
$parameters[$name] = [];
187+
foreach ($parameter as $key => $values) {
188+
$parameters[$name][$key] = Literal::new(
189+
$shortName,
190+
self::extractParameters($type, $values)
191+
);
192+
}
193+
} else {
194+
$parameters[$name] = Literal::new(
195+
$shortName,
196+
\ArrayObject::class === $type
197+
? [$parameter]
198+
: self::extractParameters($type, $parameter)
199+
);
200+
}
201+
}
202+
203+
return $parameters;
204+
}
205+
98206
/**
99207
* Verifies that the operations' config is valid.
100208
*

src/AttributeGenerator/ConfigurationAttributeGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function generateClassAttributes(Class_ $class): array
3131

3232
$getAttributesNames = static fn (array $config) => $config === [[]]
3333
? []
34-
: array_unique(array_map(fn (array $v) => array_keys($v)[0], $config));
34+
: array_unique(array_map(static fn (array $v) => array_keys($v)[0], $config));
3535
$typeAttributesNames = $getAttributesNames($typeAttributes);
3636
$vocabAttributesNames = $getAttributesNames($vocabAttributes);
3737

src/AttributeGenerator/DoctrineMongoDBAttributeGenerator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,14 @@ public function generateClassAttributes(Class_ $class): array
5454
$directChildren = array_merge($directChildren, array_filter($this->classes, fn (Class_ $childClass) => $parentName === $childClass->parent()));
5555
}
5656
$parentNames = array_keys($directChildren);
57-
$childNames = array_merge($childNames, array_keys(array_filter($directChildren, fn (Class_ $childClass) => !$childClass->isAbstract)));
57+
$childNames = array_merge($childNames, array_keys(array_filter($directChildren, static fn (Class_ $childClass) => !$childClass->isAbstract)));
5858
}
5959
$mapNames = array_merge([$class->name()], $childNames);
6060

6161
$attributes[] = new Attribute('MongoDB\Document');
6262
$attributes[] = new Attribute('MongoDB\InheritanceType', [\in_array($this->config['doctrine']['inheritanceType'], ['SINGLE_COLLECTION', 'COLLECTION_PER_CLASS', 'NONE'], true) ? $this->config['doctrine']['inheritanceType'] : 'SINGLE_COLLECTION']);
6363
$attributes[] = new Attribute('MongoDB\DiscriminatorField', ['discr']);
64-
$attributes[] = new Attribute('MongoDB\DiscriminatorMap', [array_reduce($mapNames, fn (array $map, string $mapName) => $map + [u($mapName)->camel()->toString() => new Literal(\sprintf('%s::class', $mapName))], [])]);
64+
$attributes[] = new Attribute('MongoDB\DiscriminatorMap', [array_reduce($mapNames, static fn (array $map, string $mapName) => $map + [u($mapName)->camel()->toString() => new Literal(\sprintf('%s::class', $mapName))], [])]);
6565
} else {
6666
$attributes[] = new Attribute('MongoDB\Document');
6767
}

src/AttributeGenerator/DoctrineOrmAttributeGenerator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,14 @@ public function generateClassAttributes(Class_ $class): array
6262
$directChildren = array_merge($directChildren, array_filter($this->classes, fn (Class_ $childClass) => $parentName === $childClass->parent()));
6363
}
6464
$parentNames = array_keys($directChildren);
65-
$childNames = array_merge($childNames, array_keys(array_filter($directChildren, fn (Class_ $childClass) => !$childClass->isAbstract)));
65+
$childNames = array_merge($childNames, array_keys(array_filter($directChildren, static fn (Class_ $childClass) => !$childClass->isAbstract)));
6666
}
6767
$mapNames = array_merge([$class->name()], $childNames);
6868

6969
$attributes[] = new Attribute('ORM\Entity');
7070
$attributes[] = new Attribute('ORM\InheritanceType', [\in_array($this->config['doctrine']['inheritanceType'], ['JOINED', 'SINGLE_TABLE', 'TABLE_PER_CLASS', 'NONE'], true) ? $this->config['doctrine']['inheritanceType'] : 'JOINED']);
7171
$attributes[] = new Attribute('ORM\DiscriminatorColumn', ['name' => 'discr']);
72-
$attributes[] = new Attribute('ORM\DiscriminatorMap', [array_reduce($mapNames, fn (array $map, string $mapName) => $map + [u($mapName)->camel()->toString() => new Literal(\sprintf('%s::class', $mapName))], [])]);
72+
$attributes[] = new Attribute('ORM\DiscriminatorMap', [array_reduce($mapNames, static fn (array $map, string $mapName) => $map + [u($mapName)->camel()->toString() => new Literal(\sprintf('%s::class', $mapName))], [])]);
7373
} else {
7474
$attributes[] = new Attribute('ORM\Entity');
7575
}

src/ClassMutator/ClassPropertiesAppender.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ private function getParentClasses(array $graphs, RdfResource $resource, array $p
158158
return $this->getParentClasses($graphs, $resource, [$resource]);
159159
}
160160

161-
$filterBNodes = fn ($parentClasses) => array_filter($parentClasses, fn ($parentClass) => !$parentClass->isBNode());
161+
$filterBNodes = static fn ($parentClasses) => array_filter($parentClasses, static fn ($parentClass) => !$parentClass->isBNode());
162162
if (!$subclasses = $resource->all('rdfs:subClassOf', 'resource')) {
163163
return $filterBNodes($parentClasses);
164164
}

src/Model/AddAttributeTrait.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public function addAttribute(Attribute $attribute): self
2525
}
2626
} else {
2727
$this->attributes = array_map(
28-
fn (Attribute $attr) => $attr->name() === $attribute->name()
28+
static fn (Attribute $attr) => $attr->name() === $attribute->name()
2929
? new Attribute($attr->name(), array_merge(
3030
$attr->args(),
3131
$attribute->args(),
@@ -44,7 +44,7 @@ public function getAttributeWithName(string $name): ?Attribute
4444
{
4545
return array_values(array_filter(
4646
$this->attributes,
47-
fn (Attribute $attr) => $attr->name() === $name
47+
static fn (Attribute $attr) => $attr->name() === $name
4848
))[0] ?? null;
4949
}
5050
}

src/Model/Type/UnionType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function __construct(array $types)
2828

2929
public function __toString(): string
3030
{
31-
return implode('|', array_map(fn (Type $type) => $type instanceof CompositeType ? '('.$type.')' : $type, $this->types));
31+
return implode('|', array_map(static fn (Type $type) => $type instanceof CompositeType ? '('.$type.')' : $type, $this->types));
3232
}
3333

3434
public function getPhp(): string
@@ -39,6 +39,6 @@ public function getPhp(): string
3939
$phpTypes[$type->getPhp()] = $type;
4040
}
4141

42-
return implode('|', array_map(fn (Type $type) => $type instanceof CompositeType ? '('.$type->getPhp().')' : $type->getPhp(), $phpTypes));
42+
return implode('|', array_map(static fn (Type $type) => $type instanceof CompositeType ? '('.$type->getPhp().')' : $type->getPhp(), $phpTypes));
4343
}
4444
}

src/SchemaGeneratorConfiguration.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ public function getConfigTreeBuilder(): TreeBuilder
4646
$namespacePrefix = $this->defaultPrefix ?? 'App\\';
4747

4848
/* @see https://yaml.org/type/omap.html */
49-
$transformOmap = fn (array $nodeConfig) => array_map(
50-
fn ($v, $k) => \is_int($k) ? $v : [$k => $v],
49+
$transformOmap = static fn (array $nodeConfig) => array_map(
50+
static fn ($v, $k) => \is_int($k) ? $v : [$k => $v],
5151
array_values($nodeConfig),
5252
array_keys($nodeConfig)
5353
);
5454

5555
// @phpstan-ignore-next-line node is not null
56-
$attributesNode = fn () => (new NodeBuilder())
56+
$attributesNode = static fn () => (new NodeBuilder())
5757
->arrayNode('attributes')
5858
->info('Attributes (merged with generated attributes)')
5959
->variablePrototype()->end()
@@ -78,7 +78,7 @@ public function getConfigTreeBuilder(): TreeBuilder
7878
->defaultValue([self::SCHEMA_ORG_URI => ['format' => 'rdfxml']])
7979
->beforeNormalization()
8080
->ifArray()
81-
->then(fn (array $v) => array_map(fn ($rdf) => \is_scalar($rdf) ? ['uri' => $rdf] : $rdf, $v))
81+
->then(static fn (array $v) => array_map(static fn ($rdf) => \is_scalar($rdf) ? ['uri' => $rdf] : $rdf, $v))
8282
->end()
8383
->useAttributeAsKey('uri')
8484
->arrayPrototype()

src/TypesGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ private function getParentClasses(array $graphs, RdfResource $resource, array $p
313313
return $this->getParentClasses($graphs, $resource, [$resource]);
314314
}
315315

316-
$filterBNodes = fn ($parentClasses) => array_filter($parentClasses, fn ($parentClass) => !$parentClass->isBNode());
316+
$filterBNodes = static fn ($parentClasses) => array_filter($parentClasses, static fn ($parentClass) => !$parentClass->isBNode());
317317
if (!$subclasses = $resource->all('rdfs:subClassOf', 'resource')) {
318318
return $filterBNodes($parentClasses);
319319
}

0 commit comments

Comments
 (0)