@@ -244,9 +244,12 @@ protected function buildAnnotationForMethod(array $rules, string $pluginName, \R
244244 $ existing = $ reflectionMethod ->getDocComment ();
245245 // Skip methods which have been marked as internal or auto annotations disabled
246246 if (
247- $ existing !== false && (stripos ($ existing , 'OA-AUTO:OFF ' ) !== false
248- || stripos ($ existing , '@internal ' ) !== false
249- || stripos ($ existing , '@hide ' ) !== false )
247+ $ existing !== false
248+ && (
249+ stripos ($ existing , '@internal ' ) !== false
250+ || stripos ($ existing , '@hide ' ) !== false
251+ || stripos ($ existing , '@deprecated ' ) !== false
252+ )
250253 ) {
251254 return [];
252255 }
@@ -947,17 +950,23 @@ public function convertExampleXmlToObject(string $xml): array
947950 $ root = new \SimpleXMLElement ($ xml );
948951
949952 $ toArray = function (\SimpleXMLElement $ node ) use (&$ toArray ) {
950- if (!count ($ node ->children ())) {
953+ if (!count ($ node ->children ()) && ! count ( $ node -> attributes ()) ) {
951954 return trim ((string )$ node );
952955 }
953- // Group children by tag name; repeated names become arrays
956+
957+ // Handle any attributes
954958 $ grouped = [];
959+ foreach ($ node ->attributes () as $ attribute ) {
960+ $ grouped [OpenApiDocs::OA_XML_ATTRIBUTES_TEMP_PROPERTY_NAME ][] = [$ attribute ->getName () => (string ) $ attribute ];
961+ }
962+
963+ // Group children by tag name; repeated names become arrays
955964 foreach ($ node ->children () as $ child ) {
956965 $ name = $ child ->getName ();
957966 $ grouped [$ name ][] = $ toArray ($ child );
958967 }
959968 return array_map (function ($ items ) {
960- return (count ($ items ) === 1 ) ? $ items[ 0 ] : $ items ;
969+ return (count ($ items ) === 1 ) ? array_pop ( $ items) : $ items ;
961970 }, $ grouped );
962971 };
963972
@@ -1011,7 +1020,7 @@ protected function determineResponses(array $rules, string $plugin, string $meth
10111020
10121021 // If the return type is void, use the generic response type
10131022 if (empty ($ successArray ['ref ' ]) && !empty ($ returnType ) && strval ($ returnType ) === 'void ' ) {
1014- $ successArray ['ref ' ] = '#/components/responses/GenericSuccessNoBody ' ;
1023+ $ successArray ['ref ' ] = '#/components/responses/GenericSuccess ' ;
10151024 }
10161025
10171026 // If it's a generic type and there's no custom description, use one of the global generic responses
@@ -1133,8 +1142,13 @@ protected function buildMediaTypePropertiesArray(string $format, string $example
11331142 {
11341143 $ contentType = $ format === 'json ' ? 'application/json ' : ($ format === 'xml ' ? 'text/xml ' : 'application/vnd.ms-excel ' );
11351144
1136- $ jsonSchema = $ format === 'json ' ? $ this ->buildSchemaAnnotationFromJsonExample (json_decode ($ exampleValue , true ) ?? []) : [];
1137- $ 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+ }
11381152
11391153 if (in_array ($ format , ['json ' , 'xml ' ])) {
11401154 // The annotation expects objects and not arrays, so replace [] with {}
@@ -1323,38 +1337,68 @@ public function buildPropertyAnnotationFromJsonExample(string $propName, array $
13231337 /**
13241338 * Take the deserialised structure of an XML node and build the lines of an OA\Schema annotation object for it.
13251339 *
1326- * @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.
13271342 * @param string $root Name of the root element. The default is 'result'.
13281343 *
13291344 * @return array Collection of potentially nested arrays representing an OA\Property annotation object.
13301345 */
1331- public function buildSchemaAnnotationFromXmlExample (array $ xmlArrayObject , string $ root = 'result ' ): array
1346+ public function buildSchemaAnnotationFromXmlExample (array & $ xmlArrayObject , string $ root = 'result ' ): array
13321347 {
13331348 $ lines = [
13341349 'type="object", ' ,
13351350 sprintf ('@OA\Xml(name="%s"), ' , $ root ),
13361351 ];
13371352
1338- foreach ($ xmlArrayObject as $ key => $ value ) {
1353+ foreach ($ xmlArrayObject as $ key => & $ value ) {
13391354 // If the value is not an array, skip
13401355 if (!is_array ($ value )) {
13411356 continue ;
13421357 }
13431358
13441359 if (count ($ value ) === 1 ) {
13451360 $ keys = array_keys ($ value );
1346- // Skip if it's not a named property
1361+ // Skip if it's not a named property and isn't an array
13471362 if (!is_string (reset ($ keys )) && !is_array (reset ($ value ))) {
13481363 continue ;
13491364 }
13501365 }
13511366
13521367 $ lines [] = $ this ->buildPropertyAnnotationFromXmlExample ($ key , $ value );
1368+
1369+ // Recursively remove all instances of the temporary XML attributes property
1370+ $ this ->removeTempOaXmlAttributeProperty ($ value );
13531371 }
13541372
13551373 return ['@OA\Schema ' => $ lines ];
13561374 }
13571375
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+ // Add the attributes as actual properties so that they are visible in the example
1390+ foreach ($ value as $ attributeName => $ attributeValue ) {
1391+ $ decodedExampleValue [$ attributeName ] = $ attributeValue ;
1392+ }
1393+ continue ;
1394+ }
1395+
1396+ if (is_array ($ value )) {
1397+ $ this ->removeTempOaXmlAttributeProperty ($ value );
1398+ }
1399+ }
1400+ }
1401+
13581402 /**
13591403 * Take the deserialised structure of an XML node and build the lines of an OA\Property annotation object for it.
13601404 *
@@ -1366,9 +1410,17 @@ public function buildSchemaAnnotationFromXmlExample(array $xmlArrayObject, strin
13661410 public function buildPropertyAnnotationFromXmlExample (string $ propName , array $ values ): array
13671411 {
13681412 $ type = 'object ' ;
1413+ $ originalValues = $ values ;
13691414 if ($ propName === 'row ' ) {
13701415 $ type = 'array ' ;
1371- $ values = is_array ($ values [0 ] ?? null ) ? $ values [0 ] : [];
1416+ // Merge the rows together to get as many properties as possible
1417+ $ mergedValues = [];
1418+ foreach ($ values as $ value ) {
1419+ if (is_array ($ value )) {
1420+ $ mergedValues = array_merge ($ mergedValues , $ value );
1421+ }
1422+ }
1423+ $ values = $ mergedValues ;
13721424 }
13731425
13741426 // Set the common properties
@@ -1377,6 +1429,7 @@ public function buildPropertyAnnotationFromXmlExample(string $propName, array $v
13771429 sprintf ('type="%s", ' , $ type ),
13781430 ];
13791431
1432+ $ hasAttributes = false ;
13801433 $ childLines = [];
13811434 // Recursively check if any of the children are arrays
13821435 foreach ($ values as $ key => $ value ) {
@@ -1385,6 +1438,13 @@ public function buildPropertyAnnotationFromXmlExample(string $propName, array $v
13851438 continue ;
13861439 }
13871440
1441+ // Special handling for XML attributes
1442+ if ($ key === OpenApiDocs::OA_XML_ATTRIBUTES_TEMP_PROPERTY_NAME ) {
1443+ $ hasAttributes = true ;
1444+ $ childLines = array_merge ($ childLines , $ this ->buildXmlAttributeSchemaLines ($ value ));
1445+ continue ;
1446+ }
1447+
13881448 // Handle nested arrays
13891449 if (!is_string ($ key )) {
13901450 if (!is_array (reset ($ value ))) {
@@ -1408,8 +1468,8 @@ public function buildPropertyAnnotationFromXmlExample(string $propName, array $v
14081468 ];
14091469
14101470 // Handle arrays of strings which don't have named properties
1411- $ keys = array_keys ($ values );
1412- if (!is_string (reset ($ keys )) && count ( $ values ) === 1 ) {
1471+ $ originalKeys = array_keys ($ originalValues );
1472+ if (!is_string (reset ($ originalKeys )) && ! is_string ( reset ( $ values )) && ! $ hasAttributes ) {
14131473 $ itemProperties = ['type="string" ' ];
14141474 }
14151475
@@ -1419,6 +1479,43 @@ public function buildPropertyAnnotationFromXmlExample(string $propName, array $v
14191479 return ['@OA\Property ' => array_merge ($ propertyLines , $ childLines )];
14201480 }
14211481
1482+ /**
1483+ * Build the array of lines for the attribute properties of an XML schema annotation object. It accepts an array of
1484+ * arrays representing the attributes of an XML node. It can also handle a single array of key/value pairs.
1485+ *
1486+ * @param array $attributes Collection of attributes and values. E.g. [['key1' => 'value1'],['key2' => 'value2']] or
1487+ * ['key1' => 'value1', 'key2' => 'value2']
1488+ *
1489+ * @return array The lines defining the property annotation objects for the XML attributes.
1490+ * E.g. [['@OA\Property' => ['property="idgoal",', 'type="string",', '@OA\Xml(attribute=true),', 'example="2"']]]
1491+ */
1492+ public function buildXmlAttributeSchemaLines (array $ attributes ): array
1493+ {
1494+ $ attributeSchemaLines = [];
1495+ foreach ($ attributes as $ index => $ attribute ) {
1496+ $ keys = is_array ($ attribute ) ? array_keys ($ attribute ) : [];
1497+ $ key = count ($ keys ) === 1 ? $ keys [0 ] : $ index ;
1498+ $ value = trim (is_array ($ attribute ) ? $ attribute [$ key ] ?? '' : $ attribute );
1499+ // Allow attributes with empty values, but an attribute must always have a name
1500+ if (empty ($ key )) {
1501+ continue ;
1502+ }
1503+ // Initialise with the lines that will always be present
1504+ $ propertyLines = [
1505+ sprintf ('property="%s", ' , $ key ),
1506+ 'type="string", ' ,
1507+ '@OA\Xml(attribute=true), ' ,
1508+ ];
1509+ // Add the example line if there's an actual value
1510+ if (!empty ($ value ) || strlen ($ value ) > 0 ) {
1511+ $ propertyLines [] = sprintf ('example="%s" ' , $ value );
1512+ }
1513+ $ attributeSchemaLines [] = ['@OA\Property ' => $ propertyLines ];
1514+ }
1515+
1516+ return $ attributeSchemaLines ;
1517+ }
1518+
14221519 /**
14231520 * Take a list of lines and remove the trailing comma from the last line.
14241521 *
@@ -1463,6 +1560,9 @@ public function buildLinesForAnnotationObject(string $objectName, array $objectP
14631560
14641561 // If it's not an object, then it's an array of similarly named objects, like parameters
14651562 foreach ($ property as $ subPropIndex => $ subProperty ) {
1563+ if (!is_string ($ subPropIndex )) {
1564+ continue ;
1565+ }
14661566 $ lines = array_merge ($ lines , $ this ->buildLinesForAnnotationObject ($ subPropIndex , $ subProperty , $ indent + 1 ));
14671567 }
14681568 }
@@ -1549,12 +1649,13 @@ public function wrapStringWithQuotes(string $string, string $type, string $quote
15491649 */
15501650 public function shouldIncludeDefault (string $ type , string $ default = NoDefaultValue::class): bool
15511651 {
1552- if ($ default === NoDefaultValue::class) {
1553- return false ;
1554- }
1555-
1556- // Don't use true or false for default if it's not a boolean type
1557- if ($ type !== 'boolean ' && in_array (strtolower ($ default ), ['false ' , 'true ' ])) {
1652+ if (
1653+ $ default === NoDefaultValue::class
1654+ || ($ type === 'number ' && !is_numeric ($ default ))
1655+ || ($ type === 'integer ' && !\ctype_digit ($ default ))
1656+ || ($ type !== 'string ' && $ default === '' )
1657+ || ($ type !== 'boolean ' && in_array (strtolower ($ default ), ['false ' , 'true ' ]))
1658+ ) {
15581659 return false ;
15591660 }
15601661
0 commit comments