13
13
14
14
namespace ApiPlatform \Hal \JsonSchema ;
15
15
16
+ use ApiPlatform \JsonSchema \DefinitionNameFactory ;
17
+ use ApiPlatform \JsonSchema \DefinitionNameFactoryInterface ;
18
+ use ApiPlatform \JsonSchema \ResourceMetadataTrait ;
16
19
use ApiPlatform \JsonSchema \Schema ;
17
20
use ApiPlatform \JsonSchema \SchemaFactoryAwareInterface ;
18
21
use ApiPlatform \JsonSchema \SchemaFactoryInterface ;
22
+ use ApiPlatform \JsonSchema \SchemaUriPrefixTrait ;
19
23
use ApiPlatform \Metadata \Operation ;
24
+ use ApiPlatform \Metadata \Resource \Factory \ResourceMetadataCollectionFactoryInterface ;
20
25
21
26
/**
22
27
* Decorator factory which adds HAL properties to the JSON Schema document.
26
31
*/
27
32
final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareInterface
28
33
{
34
+ use ResourceMetadataTrait;
35
+ use SchemaUriPrefixTrait;
36
+
37
+ private const COLLECTION_BASE_SCHEMA_NAME = 'HalCollectionBaseSchema ' ;
38
+
29
39
private const HREF_PROP = [
30
40
'href ' => [
31
41
'type ' => 'string ' ,
@@ -44,8 +54,12 @@ final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareI
44
54
],
45
55
];
46
56
47
- public function __construct (private readonly SchemaFactoryInterface $ schemaFactory )
57
+ public function __construct (private readonly SchemaFactoryInterface $ schemaFactory, private ? DefinitionNameFactoryInterface $ definitionNameFactory = null , ? ResourceMetadataCollectionFactoryInterface $ resourceMetadataFactory = null )
48
58
{
59
+ if (!$ definitionNameFactory ) {
60
+ $ this ->definitionNameFactory = new DefinitionNameFactory ();
61
+ }
62
+ $ this ->resourceMetadataFactory = $ resourceMetadataFactory ;
49
63
if ($ this ->schemaFactory instanceof SchemaFactoryAwareInterface) {
50
64
$ this ->schemaFactory ->setSchemaFactory ($ this );
51
65
}
@@ -56,79 +70,131 @@ public function __construct(private readonly SchemaFactoryInterface $schemaFacto
56
70
*/
57
71
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
58
72
{
59
- $ schema = $ this ->schemaFactory ->buildSchema ($ className , $ format , $ type , $ operation , $ schema , $ serializerContext , $ forceCollection );
60
73
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 );
62
85
}
63
86
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 );
64
93
$ 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 ;
67
101
68
102
return $ schema ;
69
103
}
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 ;
72
122
}
73
123
74
124
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 ' => [
83
151
'type ' => 'object ' ,
84
152
'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 ,
88
172
],
89
173
],
90
174
],
91
- ['type ' => 'object ' ],
92
175
],
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
+ [
103
187
'type ' => 'object ' ,
104
188
'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
+ ],
124
194
],
125
195
],
126
196
],
127
197
];
128
- $ schema ['required ' ] = [
129
- '_links ' ,
130
- '_embedded ' ,
131
- ];
132
198
133
199
return $ schema ;
134
200
}
0 commit comments