1313
1414namespace ApiPlatform \Hal \JsonSchema ;
1515
16+ use ApiPlatform \JsonSchema \DefinitionNameFactory ;
17+ use ApiPlatform \JsonSchema \DefinitionNameFactoryInterface ;
18+ use ApiPlatform \JsonSchema \ResourceMetadataTrait ;
1619use ApiPlatform \JsonSchema \Schema ;
1720use ApiPlatform \JsonSchema \SchemaFactoryAwareInterface ;
1821use ApiPlatform \JsonSchema \SchemaFactoryInterface ;
22+ use ApiPlatform \JsonSchema \SchemaUriPrefixTrait ;
1923use ApiPlatform \Metadata \Operation ;
24+ use ApiPlatform \Metadata \Resource \Factory \ResourceMetadataCollectionFactoryInterface ;
2025
2126/**
2227 * Decorator factory which adds HAL properties to the JSON Schema document.
2631 */
2732final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareInterface
2833{
34+ use ResourceMetadataTrait;
35+ use SchemaUriPrefixTrait;
36+
37+ private const COLLECTION_BASE_SCHEMA_NAME = 'HalCollectionBaseSchema ' ;
38+
2939 private const HREF_PROP = [
3040 'href ' => [
3141 'type ' => 'string ' ,
@@ -44,8 +54,12 @@ final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareI
4454 ],
4555 ];
4656
47- public function __construct (private readonly SchemaFactoryInterface $ schemaFactory )
57+ public function __construct (private readonly SchemaFactoryInterface $ schemaFactory, private ? DefinitionNameFactoryInterface $ definitionNameFactory = null , ? ResourceMetadataCollectionFactoryInterface $ resourceMetadataFactory = null )
4858 {
59+ if (!$ definitionNameFactory ) {
60+ $ this ->definitionNameFactory = new DefinitionNameFactory ();
61+ }
62+ $ this ->resourceMetadataFactory = $ resourceMetadataFactory ;
4963 if ($ this ->schemaFactory instanceof SchemaFactoryAwareInterface) {
5064 $ this ->schemaFactory ->setSchemaFactory ($ this );
5165 }
@@ -56,79 +70,131 @@ public function __construct(private readonly SchemaFactoryInterface $schemaFacto
5670 */
5771 public function buildSchema (string $ className , string $ format = 'jsonhal ' , string $ type = Schema::TYPE_OUTPUT , ?Operation $ operation = null , ?Schema $ schema = null , ?array $ serializerContext = null , bool $ forceCollection = false ): Schema
5872 {
59- $ schema = $ this ->schemaFactory ->buildSchema ($ className , $ format , $ type , $ operation , $ schema , $ serializerContext , $ forceCollection );
6073 if ('jsonhal ' !== $ format ) {
61- return $ schema ;
74+ return $ this ->schemaFactory ->buildSchema ($ className , $ format , $ type , $ operation , $ schema , $ serializerContext , $ forceCollection );
75+ }
76+
77+ if (!$ this ->isResourceClass ($ className )) {
78+ $ operation = null ;
79+ $ inputOrOutputClass = null ;
80+ $ serializerContext ??= [];
81+ } else {
82+ $ operation = $ this ->findOperation ($ className , $ type , $ operation , $ serializerContext , $ format );
83+ $ inputOrOutputClass = $ this ->findOutputClass ($ className , $ type , $ operation , $ serializerContext );
84+ $ serializerContext ??= $ this ->getSerializerContext ($ operation , $ type );
6285 }
6386
87+ if (null === $ inputOrOutputClass ) {
88+ // input or output disabled
89+ return $ this ->schemaFactory ->buildSchema ($ className , $ format , $ type , $ operation , $ schema , $ serializerContext , $ forceCollection );
90+ }
91+
92+ $ schema = $ this ->schemaFactory ->buildSchema ($ className , 'json ' , $ type , $ operation , $ schema , $ serializerContext , $ forceCollection );
6493 $ definitions = $ schema ->getDefinitions ();
65- if ($ key = $ schema ->getRootDefinitionKey ()) {
66- $ definitions [$ key ]['properties ' ] = self ::BASE_PROPS + ($ definitions [$ key ]['properties ' ] ?? []);
94+ $ definitionName = $ this ->definitionNameFactory ->create ($ className , $ format , $ className , $ operation , $ serializerContext );
95+ $ prefix = $ this ->getSchemaUriPrefix ($ schema ->getVersion ());
96+ $ collectionKey = $ schema ->getItemsDefinitionKey ();
97+
98+ // Already computed
99+ if (!$ collectionKey && isset ($ definitions [$ definitionName ])) {
100+ $ schema ['$ref ' ] = $ prefix .$ definitionName ;
67101
68102 return $ schema ;
69103 }
70- if ($ key = $ schema ->getItemsDefinitionKey ()) {
71- $ definitions [$ key ]['properties ' ] = self ::BASE_PROPS + ($ definitions [$ key ]['properties ' ] ?? []);
104+
105+ $ key = $ schema ->getRootDefinitionKey () ?? $ collectionKey ;
106+
107+ $ definitions [$ definitionName ] = [
108+ 'allOf ' => [
109+ ['type ' => 'object ' , 'properties ' => self ::BASE_PROPS ],
110+ ['$ref ' => $ prefix .$ key ],
111+ ],
112+ ];
113+
114+ if (isset ($ definitions [$ key ]['description ' ])) {
115+ $ definitions [$ definitionName ]['description ' ] = $ definitions [$ key ]['description ' ];
116+ }
117+
118+ if (!$ collectionKey ) {
119+ $ schema ['$ref ' ] = $ prefix .$ definitionName ;
120+
121+ return $ schema ;
72122 }
73123
74124 if (($ schema ['type ' ] ?? '' ) === 'array ' ) {
75- $ items = $ schema ['items ' ];
76- unset($ schema ['items ' ]);
77-
78- $ schema ['type ' ] = 'object ' ;
79- $ schema ['properties ' ] = [
80- '_embedded ' => [
81- 'anyOf ' => [
82- [
125+ if (!isset ($ definitions [self ::COLLECTION_BASE_SCHEMA_NAME ])) {
126+ $ definitions [self ::COLLECTION_BASE_SCHEMA_NAME ] = [
127+ 'type ' => 'object ' ,
128+ 'properties ' => [
129+ '_embedded ' => [
130+ 'anyOf ' => [
131+ [
132+ 'type ' => 'object ' ,
133+ 'properties ' => [
134+ 'item ' => [
135+ 'type ' => 'array ' ,
136+ ],
137+ ],
138+ ],
139+ ['type ' => 'object ' ],
140+ ],
141+ ],
142+ 'totalItems ' => [
143+ 'type ' => 'integer ' ,
144+ 'minimum ' => 0 ,
145+ ],
146+ 'itemsPerPage ' => [
147+ 'type ' => 'integer ' ,
148+ 'minimum ' => 0 ,
149+ ],
150+ '_links ' => [
83151 'type ' => 'object ' ,
84152 'properties ' => [
85- 'item ' => [
86- 'type ' => 'array ' ,
87- 'items ' => $ items ,
153+ 'self ' => [
154+ 'type ' => 'object ' ,
155+ 'properties ' => self ::HREF_PROP ,
156+ ],
157+ 'first ' => [
158+ 'type ' => 'object ' ,
159+ 'properties ' => self ::HREF_PROP ,
160+ ],
161+ 'last ' => [
162+ 'type ' => 'object ' ,
163+ 'properties ' => self ::HREF_PROP ,
164+ ],
165+ 'next ' => [
166+ 'type ' => 'object ' ,
167+ 'properties ' => self ::HREF_PROP ,
168+ ],
169+ 'previous ' => [
170+ 'type ' => 'object ' ,
171+ 'properties ' => self ::HREF_PROP ,
88172 ],
89173 ],
90174 ],
91- ['type ' => 'object ' ],
92175 ],
93- ],
94- 'totalItems ' => [
95- 'type ' => 'integer ' ,
96- 'minimum ' => 0 ,
97- ],
98- 'itemsPerPage ' => [
99- 'type ' => 'integer ' ,
100- 'minimum ' => 0 ,
101- ],
102- '_links ' => [
176+ 'required ' => ['_links ' , '_embedded ' ],
177+ ];
178+ }
179+
180+ unset($ schema ['items ' ]);
181+ unset($ schema ['type ' ]);
182+
183+ $ schema ['description ' ] = "$ definitionName collection. " ;
184+ $ schema ['allOf ' ] = [
185+ ['$ref ' => $ prefix .self ::COLLECTION_BASE_SCHEMA_NAME ],
186+ [
103187 'type ' => 'object ' ,
104188 'properties ' => [
105- 'self ' => [
106- 'type ' => 'object ' ,
107- 'properties ' => self ::HREF_PROP ,
108- ],
109- 'first ' => [
110- 'type ' => 'object ' ,
111- 'properties ' => self ::HREF_PROP ,
112- ],
113- 'last ' => [
114- 'type ' => 'object ' ,
115- 'properties ' => self ::HREF_PROP ,
116- ],
117- 'next ' => [
118- 'type ' => 'object ' ,
119- 'properties ' => self ::HREF_PROP ,
120- ],
121- 'previous ' => [
122- 'type ' => 'object ' ,
123- 'properties ' => self ::HREF_PROP ,
189+ '_embedded ' => [
190+ 'additionalProperties ' => [
191+ 'type ' => 'array ' ,
192+ 'items ' => ['$ref ' => $ prefix .$ definitionName ],
193+ ],
124194 ],
125195 ],
126196 ],
127197 ];
128- $ schema ['required ' ] = [
129- '_links ' ,
130- '_embedded ' ,
131- ];
132198
133199 return $ schema ;
134200 }
0 commit comments