@@ -957,7 +957,7 @@ public function convertExampleXmlToObject(string $xml): array
957957 // Handle any attributes
958958 $ grouped = [];
959959 foreach ($ node ->attributes () as $ attribute ) {
960- $ grouped [' oaXmlAttributes ' ][] = [$ attribute ->getName () => (string ) $ attribute ];
960+ $ grouped [OpenApiDocs:: OA_XML_ATTRIBUTES_TEMP_PROPERTY_NAME ][] = [$ attribute ->getName () => (string ) $ attribute ];
961961 }
962962
963963 // Group children by tag name; repeated names become arrays
@@ -1142,8 +1142,13 @@ protected function buildMediaTypePropertiesArray(string $format, string $example
11421142 {
11431143 $ contentType = $ format === 'json ' ? 'application/json ' : ($ format === 'xml ' ? 'text/xml ' : 'application/vnd.ms-excel ' );
11441144
1145- $ jsonSchema = $ format === 'json ' ? $ this ->buildSchemaAnnotationFromJsonExample (json_decode ($ exampleValue , true ) ?? []) : [];
1146- $ xmlSchema = $ format === 'xml ' ? $ this ->buildSchemaAnnotationFromXmlExample (json_decode ($ exampleValue , true ) ?? []) : [];
1145+ $ decodedExampleValue = json_decode ($ exampleValue , true ) ?? [];
1146+ $ jsonSchema = $ format === 'json ' ? $ this ->buildSchemaAnnotationFromJsonExample ($ decodedExampleValue ) : [];
1147+ $ xmlSchema = $ format === 'xml ' ? $ this ->buildSchemaAnnotationFromXmlExample ($ decodedExampleValue ) : [];
1148+ // If the XML example contains the temporary property to assist in building XML attributes in the schema, replace with newly encoded array with property removed
1149+ if ($ format === 'xml ' && strpos ($ exampleValue , OpenApiDocs::OA_XML_ATTRIBUTES_TEMP_PROPERTY_NAME ) !== false ) {
1150+ $ exampleValue = json_encode ($ decodedExampleValue );
1151+ }
11471152
11481153 if (in_array ($ format , ['json ' , 'xml ' ])) {
11491154 // The annotation expects objects and not arrays, so replace [] with {}
@@ -1332,38 +1337,64 @@ public function buildPropertyAnnotationFromJsonExample(string $propName, array $
13321337 /**
13331338 * Take the deserialised structure of an XML node and build the lines of an OA\Schema annotation object for it.
13341339 *
1335- * @param array $xmlArrayObject Nested array of properties of the XML node.
1340+ * @param array $xmlArrayObject Nested array of properties of the XML node. Passed by reference so that temporary
1341+ * properties can be removed before the example is included in the annotations.
13361342 * @param string $root Name of the root element. The default is 'result'.
13371343 *
13381344 * @return array Collection of potentially nested arrays representing an OA\Property annotation object.
13391345 */
1340- public function buildSchemaAnnotationFromXmlExample (array $ xmlArrayObject , string $ root = 'result ' ): array
1346+ public function buildSchemaAnnotationFromXmlExample (array & $ xmlArrayObject , string $ root = 'result ' ): array
13411347 {
13421348 $ lines = [
13431349 'type="object", ' ,
13441350 sprintf ('@OA\Xml(name="%s"), ' , $ root ),
13451351 ];
13461352
1347- foreach ($ xmlArrayObject as $ key => $ value ) {
1353+ foreach ($ xmlArrayObject as $ key => & $ value ) {
13481354 // If the value is not an array, skip
13491355 if (!is_array ($ value )) {
13501356 continue ;
13511357 }
13521358
13531359 if (count ($ value ) === 1 ) {
13541360 $ keys = array_keys ($ value );
1355- // Skip if it's not a named property
1361+ // Skip if it's not a named property and isn't an array
13561362 if (!is_string (reset ($ keys )) && !is_array (reset ($ value ))) {
13571363 continue ;
13581364 }
13591365 }
13601366
13611367 $ lines [] = $ this ->buildPropertyAnnotationFromXmlExample ($ key , $ value );
1368+
1369+ // Recursively remove all instances of the temporary XML attributes property
1370+ $ this ->removeTempOaXmlAttributeProperty ($ value );
13621371 }
13631372
13641373 return ['@OA\Schema ' => $ lines ];
13651374 }
13661375
1376+ /**
1377+ * Iterate over a nested array representing an example response object and recursively remove all occurrences of the
1378+ * temporary property used to help build the schema for XML attributes.
1379+ *
1380+ * @param array $decodedExampleValue The reference to the nested array to remove the temporary property from.
1381+ *
1382+ * @return void
1383+ */
1384+ protected function removeTempOaXmlAttributeProperty (array &$ decodedExampleValue ): void
1385+ {
1386+ foreach ($ decodedExampleValue as $ key => &$ value ) {
1387+ if ($ key === OpenApiDocs::OA_XML_ATTRIBUTES_TEMP_PROPERTY_NAME ) {
1388+ unset($ decodedExampleValue [$ key ]);
1389+ continue ;
1390+ }
1391+
1392+ if (is_array ($ value )) {
1393+ $ this ->removeTempOaXmlAttributeProperty ($ value );
1394+ }
1395+ }
1396+ }
1397+
13671398 /**
13681399 * Take the deserialised structure of an XML node and build the lines of an OA\Property annotation object for it.
13691400 *
@@ -1380,10 +1411,9 @@ public function buildPropertyAnnotationFromXmlExample(string $propName, array $v
13801411 $ type = 'array ' ;
13811412 // Merge the rows together to get as many properties as possible
13821413 $ mergedValues = [];
1383- foreach ($ values as $ key => $ value ) {
1384- $ valueArray = is_array ($ value ) ? $ value : [$ value ];
1414+ foreach ($ values as $ value ) {
13851415 if (is_array ($ value )) {
1386- $ mergedValues = array_merge ($ mergedValues , $ valueArray );
1416+ $ mergedValues = array_merge ($ mergedValues , $ value );
13871417 }
13881418 }
13891419 $ values = $ mergedValues ;
@@ -1405,7 +1435,7 @@ public function buildPropertyAnnotationFromXmlExample(string $propName, array $v
14051435 }
14061436
14071437 // Special handling for XML attributes
1408- if ($ key === ' oaXmlAttributes ' ) {
1438+ if ($ key === OpenApiDocs:: OA_XML_ATTRIBUTES_TEMP_PROPERTY_NAME ) {
14091439 $ hasAttributes = true ;
14101440 $ childLines [] = $ this ->buildXmlAttributeSchemaLines ($ value );
14111441 continue ;
@@ -1445,18 +1475,38 @@ public function buildPropertyAnnotationFromXmlExample(string $propName, array $v
14451475 return ['@OA\Property ' => array_merge ($ propertyLines , $ childLines )];
14461476 }
14471477
1478+ /**
1479+ * Build the array of lines for the attribute properties of an XML schema annotation object. It accepts an array of
1480+ * arrays representing the attributes of an XML node. It can also handle a single array of key/value pairs.
1481+ *
1482+ * @param array $attributes Collection of attributes and values. E.g. [['key1' => 'value1'],['key2' => 'value2']] or
1483+ * ['key1' => 'value1', 'key2' => 'value2']
1484+ *
1485+ * @return array The lines defining the property annotation objects for the XML attributes.
1486+ * E.g. [['@OA\Property' => ['property="idgoal",', 'type="string",', '@OA\Xml(attribute=true),', 'example="2"']]]
1487+ */
14481488 public function buildXmlAttributeSchemaLines (array $ attributes ): array
14491489 {
14501490 $ attributeSchemaLines = [];
14511491 foreach ($ attributes as $ index => $ attribute ) {
1452- $ key = is_array ($ attribute ) ? array_keys ($ attribute )[0 ] : $ index ;
1453- $ value = is_array ($ attribute ) ? $ attribute [$ key ] : $ attribute ;
1454- $ attributeSchemaLines [] = ['@OA\Property ' => [
1455- "property= \"$ key \", " ,
1492+ $ keys = is_array ($ attribute ) ? array_keys ($ attribute ) : [];
1493+ $ key = count ($ keys ) === 1 ? $ keys [0 ] : $ index ;
1494+ $ value = trim (is_array ($ attribute ) ? $ attribute [$ key ] ?? '' : $ attribute );
1495+ // Allow attributes with empty values, but an attribute must always have a name
1496+ if (empty ($ key )) {
1497+ continue ;
1498+ }
1499+ // Initialise with the lines that will always be present
1500+ $ propertyLines = [
1501+ sprintf ('property="%s", ' , $ key ),
14561502 'type="string", ' ,
14571503 '@OA\Xml(attribute=true), ' ,
1458- "example= \"$ value \"" ,
1459- ]];
1504+ ];
1505+ // Add the example line if there's an actual value
1506+ if (!empty ($ value ) || strlen ($ value ) > 0 ) {
1507+ $ propertyLines [] = sprintf ('example="%s" ' , $ value );
1508+ }
1509+ $ attributeSchemaLines [] = ['@OA\Property ' => $ propertyLines ];
14601510 }
14611511
14621512 return $ attributeSchemaLines ;
@@ -1506,6 +1556,9 @@ public function buildLinesForAnnotationObject(string $objectName, array $objectP
15061556
15071557 // If it's not an object, then it's an array of similarly named objects, like parameters
15081558 foreach ($ property as $ subPropIndex => $ subProperty ) {
1559+ if (!is_string ($ subPropIndex )) {
1560+ continue ;
1561+ }
15091562 $ lines = array_merge ($ lines , $ this ->buildLinesForAnnotationObject ($ subPropIndex , $ subProperty , $ indent + 1 ));
15101563 }
15111564 }
0 commit comments