14
14
use ApiPlatform \Core \Exception \InvalidArgumentException ;
15
15
use ApiPlatform \Core \Exception \ResourceClassNotFoundException ;
16
16
use ApiPlatform \Core \Metadata \Resource \ResourceMetadata ;
17
+ use Symfony \Component \Config \Util \XmlUtils ;
17
18
18
19
/**
19
- * Creates a resource metadata from xml {@see Resource} configuration.
20
+ * Creates a resource metadata from XML {@see Resource} configuration.
20
21
*
21
22
* @author Antoine Bluchet <[email protected] >
23
+ * @author Kévin Dunglas <[email protected] >
22
24
*/
23
25
final class XmlResourceMetadataFactory implements ResourceMetadataFactoryInterface
24
26
{
25
- private $ xmlParser ;
27
+ const RESOURCE_SCHEMA = __DIR__ .'/../../schema/metadata.xsd ' ;
28
+
26
29
private $ paths ;
27
30
private $ decorated ;
28
31
29
- const RESOURCE_SCHEMA = __DIR__ .'/../../../schema/metadata.xsd ' ;
30
-
31
32
/**
32
33
* @param string[] $paths
33
34
* @param ResourceMetadataFactoryInterface|null $decorated
34
35
*/
35
36
public function __construct (array $ paths , ResourceMetadataFactoryInterface $ decorated = null )
36
37
{
37
- $ this ->xmlParser = new \DOMDocument ();
38
38
$ this ->paths = $ paths ;
39
39
$ this ->decorated = $ decorated ;
40
40
}
@@ -53,170 +53,63 @@ public function create(string $resourceClass) : ResourceMetadata
53
53
}
54
54
}
55
55
56
- try {
57
- new \ReflectionClass ($ resourceClass );
58
- } catch (\ReflectionException $ reflectionException ) {
59
- return $ this ->handleNotFound ($ parentResourceMetadata , $ resourceClass );
60
- }
61
-
62
- $ metadata = null ;
63
-
64
- foreach ($ this ->paths as $ path ) {
65
- $ resources = $ this ->getResourcesDom ($ path );
66
-
67
- $ internalErrors = libxml_use_internal_errors (true );
68
-
69
- if (false === @$ resources ->schemaValidate (self ::RESOURCE_SCHEMA )) {
70
- throw new InvalidArgumentException (sprintf ('XML Schema loaded from path %s is not valid! Errors: %s ' , realpath ($ path ), implode ("\n" , $ this ->getXmlErrors ($ internalErrors ))));
71
- }
72
-
73
- libxml_clear_errors ();
74
- libxml_use_internal_errors ($ internalErrors );
75
-
76
- foreach ($ resources ->getElementsByTagName ('resource ' ) as $ resource ) {
77
- $ class = $ resource ->getAttribute ('class ' );
78
-
79
- if ($ resourceClass !== $ class ) {
80
- continue ;
81
- }
82
-
83
- $ metadata = $ resource ;
84
-
85
- break 2 ;
86
- }
87
- }
88
-
89
- if (null === $ metadata ) {
56
+ if (!class_exists ($ resourceClass ) || empty ($ metadata = $ this ->getMetadata ($ resourceClass ))) {
90
57
return $ this ->handleNotFound ($ parentResourceMetadata , $ resourceClass );
91
58
}
92
59
93
- $ xpath = new \DOMXpath ($ resources );
94
-
95
- $ metadata = [
96
- 'shortName ' => $ metadata ->getAttribute ('shortName ' ) ?: null ,
97
- 'description ' => $ metadata ->getAttribute ('description ' ) ?: null ,
98
- 'iri ' => $ metadata ->getAttribute ('iri ' ) ?: null ,
99
- 'itemOperations ' => $ this ->getOperations ($ xpath ->query ('./itemOperations/operation ' , $ metadata )) ?: null ,
100
- 'collectionOperations ' => $ this ->getOperations ($ xpath ->query ('./collectionOperations/operation ' , $ metadata )) ?: null ,
101
- 'attributes ' => $ this ->getAttributes ($ xpath ->query ('./attributes/attribute ' , $ metadata )),
102
- ];
103
-
104
- if (!$ parentResourceMetadata ) {
105
- return new ResourceMetadata (
106
- $ metadata ['shortName ' ],
107
- $ metadata ['description ' ],
108
- $ metadata ['iri ' ],
109
- $ metadata ['itemOperations ' ],
110
- $ metadata ['collectionOperations ' ],
111
- $ metadata ['attributes ' ]
112
- );
113
- }
114
-
115
- $ resourceMetadata = $ parentResourceMetadata ;
116
-
117
- foreach (['shortName ' , 'description ' , 'itemOperations ' , 'collectionOperations ' , 'iri ' , 'attributes ' ] as $ property ) {
118
- if (!isset ($ metadata [$ property ])) {
119
- continue ;
120
- }
121
-
122
- $ resourceMetadata = $ this ->createWith ($ resourceMetadata , $ property , $ metadata [$ property ]);
123
- }
124
-
125
- return $ resourceMetadata ;
60
+ return null === $ parentResourceMetadata ? new ResourceMetadata (...$ metadata ) : $ this ->update ($ parentResourceMetadata , $ metadata );
126
61
}
127
62
128
63
/**
129
- * Creates a DOMDocument based on `resource` tags of a file-loaded xml document .
64
+ * Extracts metadata from the XML tree .
130
65
*
131
- * @param string $path the xml file path
66
+ * @param string $resourceClass
132
67
*
133
- * @return \DOMDocument
68
+ * @return array
134
69
*/
135
- private function getResourcesDom (string $ path ) : \ DOMDocument
70
+ private function getMetadata (string $ resourceClass ) : array
136
71
{
137
- $ doc = new \DOMDocument ('1.0 ' , 'utf-8 ' );
138
- $ root = $ doc ->createElement ('resources ' );
139
- $ doc ->appendChild ($ root );
140
-
141
- $ this ->xmlParser ->loadXML (file_get_contents ($ path ));
142
-
143
- $ xpath = new \DOMXpath ($ this ->xmlParser );
144
- $ resources = $ xpath ->query ('//resource ' );
145
-
146
- foreach ($ resources as $ resource ) {
147
- $ root ->appendChild ($ doc ->importNode ($ resource , true ));
148
- }
149
-
150
- return $ doc ;
151
- }
152
-
153
- /**
154
- * Get operations from xml.
155
- *
156
- * @param \DOMNodeList $query
157
- *
158
- * @return array|null
159
- */
160
- private function getOperations (\DOMNodeList $ query )
161
- {
162
- $ operations = [];
163
- foreach ($ query as $ operation ) {
164
- $ key = $ operation ->getAttribute ('key ' );
165
- $ operations [$ key ] = [
166
- 'method ' => $ operation ->getAttribute ('method ' ),
167
- ];
72
+ foreach ($ this ->paths as $ path ) {
73
+ try {
74
+ $ domDocument = XmlUtils::loadFile ($ path , self ::RESOURCE_SCHEMA );
75
+ } catch (\InvalidArgumentException $ e ) {
76
+ throw new InvalidArgumentException ($ e ->getMessage (), $ e ->getCode (), $ e );
77
+ }
168
78
169
- $ path = $ operation ->getAttribute ('path ' );
79
+ $ xml = simplexml_import_dom ($ domDocument );
80
+ foreach ($ xml ->resource as $ resource ) {
81
+ if ($ resourceClass !== (string ) $ resource ['class ' ]) {
82
+ continue ;
83
+ }
170
84
171
- if ($ path ) {
172
- $ operations [$ key ]['path ' ] = $ path ;
85
+ return [
86
+ (string ) $ resource ['shortName ' ] ?? null ,
87
+ (string ) $ resource ['description ' ] ?? null ,
88
+ (string ) $ resource ['iri ' ] ?? null ,
89
+ $ this ->getAttributes ($ resource , 'itemOperation ' ) ?: null ,
90
+ $ this ->getAttributes ($ resource , 'collectionOperation ' ) ?: null ,
91
+ $ this ->getAttributes ($ resource , 'attribute ' ) ?: null ,
92
+ ];
173
93
}
174
94
}
175
95
176
- return $ operations ?: null ;
96
+ return [] ;
177
97
}
178
98
179
99
/**
180
- * Get Attributes .
100
+ * Recursively transforms an attribute structure into an associative array .
181
101
*
182
- * @param \DOMNodeList $query
102
+ * @param \SimpleXMLElement $resource
103
+ * @param string $elementName
183
104
*
184
- * @return array|null
105
+ * @return array
185
106
*/
186
- private function getAttributes (\DOMNodeList $ query )
107
+ private function getAttributes (\SimpleXMLElement $ resource , string $ elementName ) : array
187
108
{
188
109
$ attributes = [];
189
- foreach ($ query as $ attribute ) {
190
- $ key = $ attribute ->getAttribute ('key ' );
191
- $ attributes [$ key ] = $ this ->recursiveAttributes ($ attribute , $ attributes [$ key ]);
192
- }
193
-
194
- return $ attributes ?: null ;
195
- }
196
-
197
- /**
198
- * Transforms random attributes in an array
199
- * <element (key="key"|int)>\DOMNodeList|\DOMText</element>.
200
- *
201
- * @param \DOMElement $element
202
- * @param array
203
- *
204
- * @return array|string
205
- */
206
- private function recursiveAttributes (\DOMElement $ element , &$ attributes )
207
- {
208
- foreach ($ element ->childNodes as $ child ) {
209
- if ($ child instanceof \DOMText) {
210
- if ($ child ->isWhitespaceInElementContent ()) {
211
- continue ;
212
- }
213
-
214
- $ attributes = $ child ->nodeValue ;
215
- break ;
216
- }
217
-
218
- $ key = $ child ->getAttribute ('key ' ) ?: count ($ attributes );
219
- $ attributes [$ key ] = $ child ->childNodes ->length ? $ this ->recursiveAttributes ($ child , $ attributes [$ key ]) : $ child ->value ;
110
+ foreach ($ resource ->$ elementName as $ attribute ) {
111
+ $ value = isset ($ attribute ->attribute [0 ]) ? $ this ->getAttributes ($ attribute , 'attribute ' ) : (string ) $ attribute ;
112
+ isset ($ attribute ['name ' ]) ? $ attributes [(string ) $ attribute ['name ' ]] = $ value : $ attributes [] = $ value ;
220
113
}
221
114
222
115
return $ attributes ;
@@ -245,47 +138,20 @@ private function handleNotFound(ResourceMetadata $parentPropertyMetadata = null,
245
138
* Creates a new instance of metadata if the property is not already set.
246
139
*
247
140
* @param ResourceMetadata $resourceMetadata
248
- * @param string $property
249
- * @param mixed $value
141
+ * @param array $metadata
250
142
*
251
143
* @return ResourceMetadata
252
144
*/
253
- private function createWith (ResourceMetadata $ resourceMetadata , string $ property , $ value ) : ResourceMetadata
145
+ private function update (ResourceMetadata $ resourceMetadata , array $ metadata ) : ResourceMetadata
254
146
{
255
- $ getter = 'get ' .ucfirst ($ property );
256
-
257
- if (null !== $ resourceMetadata ->$ getter ()) {
258
- return $ resourceMetadata ;
259
- }
260
-
261
- $ wither = 'with ' .ucfirst ($ property );
262
-
263
- return $ resourceMetadata ->$ wither ($ value );
264
- }
147
+ foreach (['shortName ' , 'description ' , 'iri ' , 'itemOperations ' , 'collectionOperations ' , 'attributes ' ] as $ key => $ property ) {
148
+ if (null === $ metadata [$ key ] || null !== $ resourceMetadata ->{'get ' .ucfirst ($ property )}()) {
149
+ continue ;
150
+ }
265
151
266
- /**
267
- * Returns the XML errors of the internal XML parser.
268
- *
269
- * @param bool $internalErrors
270
- *
271
- * @return array An array of errors
272
- */
273
- private function getXmlErrors ($ internalErrors )
274
- {
275
- $ errors = [];
276
- foreach (libxml_get_errors () as $ error ) {
277
- $ errors [] = sprintf ('[%s %s] %s (in %s - line %d, column %d) ' ,
278
- LIBXML_ERR_WARNING == $ error ->level ? 'WARNING ' : 'ERROR ' ,
279
- $ error ->code ,
280
- trim ($ error ->message ),
281
- $ error ->file ?: 'n/a ' ,
282
- $ error ->line ,
283
- $ error ->column
284
- );
152
+ $ resourceMetadata = $ resourceMetadata ->{'with ' .ucfirst ($ property )}($ metadata [$ key ]);
285
153
}
286
- libxml_clear_errors ();
287
- libxml_use_internal_errors ($ internalErrors );
288
154
289
- return $ errors ;
155
+ return $ resourceMetadata ;
290
156
}
291
157
}
0 commit comments