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,130 @@ 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
+ $ serializerContext ??= [];
80
+ } else {
81
+ $ operation = $ this ->findOperation ($ className , $ type , $ operation , $ serializerContext , $ format );
82
+ $ inputOrOutputClass = $ this ->findOutputClass ($ className , $ type , $ operation , $ serializerContext );
83
+ $ serializerContext ??= $ this ->getSerializerContext ($ operation , $ type );
62
84
}
63
85
86
+ if (null === $ inputOrOutputClass ) {
87
+ // input or output disabled
88
+ return $ this ->schemaFactory ->buildSchema ($ className , $ format , $ type , $ operation , $ schema , $ serializerContext , $ forceCollection );
89
+ }
90
+
91
+ $ schema = $ this ->schemaFactory ->buildSchema ($ className , 'json ' , $ type , $ operation , $ schema , $ serializerContext , $ forceCollection );
64
92
$ definitions = $ schema ->getDefinitions ();
65
- if ($ key = $ schema ->getRootDefinitionKey ()) {
66
- $ definitions [$ key ]['properties ' ] = self ::BASE_PROPS + ($ definitions [$ key ]['properties ' ] ?? []);
93
+ $ definitionName = $ this ->definitionNameFactory ->create ($ className , $ format , $ className , $ operation , $ serializerContext ?? []);
94
+ $ prefix = $ this ->getSchemaUriPrefix ($ schema ->getVersion ());
95
+ $ collectionKey = $ schema ->getItemsDefinitionKey ();
96
+
97
+ // Already computed
98
+ if (!$ collectionKey && isset ($ definitions [$ definitionName ])) {
99
+ $ schema ['$ref ' ] = $ prefix .$ definitionName ;
67
100
68
101
return $ schema ;
69
102
}
70
- if ($ key = $ schema ->getItemsDefinitionKey ()) {
71
- $ definitions [$ key ]['properties ' ] = self ::BASE_PROPS + ($ definitions [$ key ]['properties ' ] ?? []);
103
+
104
+ $ key = $ schema ->getRootDefinitionKey () ?? $ collectionKey ;
105
+
106
+ $ definitions [$ definitionName ] = [
107
+ 'allOf ' => [
108
+ ['type ' => 'object ' , 'properties ' => self ::BASE_PROPS ],
109
+ ['$ref ' => $ prefix .$ key ],
110
+ ],
111
+ ];
112
+
113
+ if (isset ($ definitions [$ key ]['description ' ])) {
114
+ $ definitions [$ definitionName ]['description ' ] = $ definitions [$ key ]['description ' ];
115
+ }
116
+
117
+ if (!$ collectionKey ) {
118
+ $ schema ['$ref ' ] = $ prefix .$ definitionName ;
119
+
120
+ return $ schema ;
72
121
}
73
122
74
123
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
- [
124
+ if (!isset ($ definitions [self ::COLLECTION_BASE_SCHEMA_NAME ])) {
125
+ $ definitions [self ::COLLECTION_BASE_SCHEMA_NAME ] = [
126
+ 'type ' => 'object ' ,
127
+ 'properties ' => [
128
+ '_embedded ' => [
129
+ 'anyOf ' => [
130
+ [
131
+ 'type ' => 'object ' ,
132
+ 'properties ' => [
133
+ 'item ' => [
134
+ 'type ' => 'array ' ,
135
+ ],
136
+ ],
137
+ ],
138
+ ['type ' => 'object ' ],
139
+ ],
140
+ ],
141
+ 'totalItems ' => [
142
+ 'type ' => 'integer ' ,
143
+ 'minimum ' => 0 ,
144
+ ],
145
+ 'itemsPerPage ' => [
146
+ 'type ' => 'integer ' ,
147
+ 'minimum ' => 0 ,
148
+ ],
149
+ '_links ' => [
83
150
'type ' => 'object ' ,
84
151
'properties ' => [
85
- 'item ' => [
86
- 'type ' => 'array ' ,
87
- 'items ' => $ items ,
152
+ 'self ' => [
153
+ 'type ' => 'object ' ,
154
+ 'properties ' => self ::HREF_PROP ,
155
+ ],
156
+ 'first ' => [
157
+ 'type ' => 'object ' ,
158
+ 'properties ' => self ::HREF_PROP ,
159
+ ],
160
+ 'last ' => [
161
+ 'type ' => 'object ' ,
162
+ 'properties ' => self ::HREF_PROP ,
163
+ ],
164
+ 'next ' => [
165
+ 'type ' => 'object ' ,
166
+ 'properties ' => self ::HREF_PROP ,
167
+ ],
168
+ 'previous ' => [
169
+ 'type ' => 'object ' ,
170
+ 'properties ' => self ::HREF_PROP ,
88
171
],
89
172
],
90
173
],
91
- ['type ' => 'object ' ],
92
174
],
93
- ],
94
- 'totalItems ' => [
95
- 'type ' => 'integer ' ,
96
- 'minimum ' => 0 ,
97
- ],
98
- 'itemsPerPage ' => [
99
- 'type ' => 'integer ' ,
100
- 'minimum ' => 0 ,
101
- ],
102
- '_links ' => [
175
+ 'required ' => ['_links ' , '_embedded ' ],
176
+ ];
177
+ }
178
+
179
+ unset($ schema ['items ' ]);
180
+ unset($ schema ['type ' ]);
181
+
182
+ $ schema ['description ' ] = "$ definitionName collection. " ;
183
+ $ schema ['allOf ' ] = [
184
+ ['$ref ' => $ prefix .self ::COLLECTION_BASE_SCHEMA_NAME ],
185
+ [
103
186
'type ' => 'object ' ,
104
187
'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 ,
188
+ '_embedded ' => [
189
+ 'additionalProperties ' => [
190
+ 'type ' => 'array ' ,
191
+ 'items ' => ['$ref ' => $ prefix .$ definitionName ],
192
+ ],
124
193
],
125
194
],
126
195
],
127
196
];
128
- $ schema ['required ' ] = [
129
- '_links ' ,
130
- '_embedded ' ,
131
- ];
132
197
133
198
return $ schema ;
134
199
}
0 commit comments