2323use ApiPlatform \Metadata \Patch ;
2424use ApiPlatform \Metadata \Post ;
2525use ApiPlatform \Metadata \Put ;
26+ use ApiPlatform \OpenApi \Model \Operation ;
27+ use ApiPlatform \OpenApi \Model \Parameter ;
28+ use ApiPlatform \OpenApi \Model \Response ;
2629use ApiPlatform \SchemaGenerator \Model \Attribute ;
2730use ApiPlatform \SchemaGenerator \Model \Class_ ;
2831use ApiPlatform \SchemaGenerator \Model \Property ;
3942 */
4043final 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 *
0 commit comments