diff --git a/CHANGELOG.md b/CHANGELOG.md index 82d22eb0e..568eb9a97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ Changelog ---------- +## 0.9.5 + +* Implemented @SWG\Info & @SWG\Authorizations which can be used to augment the api-docs.json with metadata. +* Updated @link references to the github.com/wordnik/swagger-spec repository + ## 0.9.4 * Fixed regressions #145 & #146 @@ -28,7 +33,7 @@ Changelog * New processing architecture allowing swagger-php to act on other annotations (https://github.com/zircote/swagger-php/pull/121) * Improved model referencing: $ref allowed with complex modelnames (https://github.com/zircote/swagger-php/pull/122 and https://github.com/zircote/swagger-php/pull/123) -* Updated depedencies creating a 50% smaller swagger.phar +* Updated dependencies creating a 50% smaller swagger.phar ## 0.8.3 @@ -46,7 +51,7 @@ Detect @SWG\Parameter type and @SWG\Items based on `@var Type[]` phpdoc comments ## 0.8.0 * Compatible with swagger-ui 2.0 -* Upgraded annotations to the [Swagger 1.2 Specification](https://github.com/wordnik/swagger-core/wiki/1.2-transition) +* Upgraded annotations to the [Swagger 1.2 Specification](https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md) * Use swagger-php 0.7.x to generate 1.1 or older swagger specs. * Simplified API. * Removed many methods from the Swagger class. @@ -105,4 +110,4 @@ Detect @SWG\Parameter type and @SWG\Items based on `@var Type[]` phpdoc comments * An @Api can be nested inside a @Resource and @Property can be nested inside a @Model. ## Olders versions -Checkout the [commit log](https://github.com/zircote/swagger-php/commits) for the changes from 0.1 to 0.4 \ No newline at end of file +Checkout the [commit log](https://github.com/zircote/swagger-php/commits) for the changes from 0.1 to 0.4 diff --git a/Examples/Facet/Resources/FacetResource.php b/Examples/Facet/Resources/FacetResource.php index 659da5216..a3fd55aef 100644 --- a/Examples/Facet/Resources/FacetResource.php +++ b/Examples/Facet/Resources/FacetResource.php @@ -14,7 +14,8 @@ * apiVersion="0.2", * swaggerVersion="1.2", * resourcePath="/facet", - * basePath="http://facetstore.zircote.com/swagger-php/api" + * basePath="http://facetstore.zircote.com/swagger-php/api", + * produces="['application/json']" * ) */ class FacetResource @@ -24,7 +25,6 @@ class FacetResource * @SWG\Api( * path="/facet.{format}/{facetId}", * description="Operations about facets", - * produces="['application/json']", * @SWG\Operations( * @SWG\Operation( * method="GET", diff --git a/library/Swagger/Annotations/Api.php b/library/Swagger/Annotations/Api.php index 27aad84f1..855c76c7d 100644 --- a/library/Swagger/Annotations/Api.php +++ b/library/Swagger/Annotations/Api.php @@ -48,31 +48,8 @@ class Api extends AbstractAnnotation */ public $operations = array(); - /** - * @var array - */ - public $produces; - - /** - * @var array - */ - public $consumes; - - /** - * Undocumented - * @var bool - */ - public $deprecated; - - /** - * @var Undocumented - */ - public $defaultValue; - protected static $mapAnnotations = array( '\Swagger\Annotations\Operation' => 'operations[]', - '\Swagger\Annotations\Produces' => 'produces[]', - '\Swagger\Annotations\Consumes' => 'consumes[]', ); public function __construct(array $values = array()) @@ -106,8 +83,6 @@ public function validate() Logger::notice('Api "'.$this->path.'" doesn\'t have any valid operations'); return false; } - Produces::validateContainer($this); - Consumes::validateContainer($this); return true; } diff --git a/library/Swagger/Annotations/Authorization.php b/library/Swagger/Annotations/Authorization.php new file mode 100644 index 000000000..23d620763 --- /dev/null +++ b/library/Swagger/Annotations/Authorization.php @@ -0,0 +1,85 @@ + 'scopes[]' + ); + + public function validate() { + if (in_array($this->type, array('basicAuth', 'apiKey', 'oauth2')) === false) { + Logger::warning('Unexpected '.$this->identity().'->type "'.$this->type.'", expection "basicAuth", "apiKey" or "oauth2" in '.$this->_context); + return false; + } + if ($this->type === 'apiKey' && (empty($this->passAs) || empty($this->keyname))) { + Logger::notice('Fields "passAs" and "keyname" are required for '.$this->identity().'->type "apiKey" in '.$this->_context); + } + return true; + } + +} diff --git a/library/Swagger/Annotations/DataType.php b/library/Swagger/Annotations/DataType.php index 2a2a45285..f1fbd1f50 100644 --- a/library/Swagger/Annotations/DataType.php +++ b/library/Swagger/Annotations/DataType.php @@ -28,13 +28,14 @@ /** * Baseclass for the @SWG\Parameter & @SWG\Property annotations. - * @link https://github.com/wordnik/swagger-core/wiki/datatypes + * https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#43-data-types * * @package * @category * @subpackage * * @Annotation + * @link https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#43-data-types */ abstract class DataType extends AbstractAnnotation { @@ -55,17 +56,19 @@ abstract class DataType extends AbstractAnnotation public $type; /** - * + * Fine-tuned primitive type definition * @var string */ public $format; /** + * The type definition of the values in the container. * @var Items */ public $items; /** + * A flag to note whether the container allows duplicate values or not. * @var bool */ public $uniqueItems; @@ -76,24 +79,25 @@ abstract class DataType extends AbstractAnnotation public $required; /** - * + * The minimum valid value for the type, inclusive. * @var mixed */ public $minimum; /** - * + * The maximum valid value for the type, inclusive. * @var mixed */ public $maximum; /** + * A fixed list of possible values. * @var array */ public $enum; /** - * Undocumented + * The default value to be used for the field. * @var mixed */ public $defaultValue; diff --git a/library/Swagger/Annotations/Info.php b/library/Swagger/Annotations/Info.php new file mode 100644 index 000000000..ab9d0f0da --- /dev/null +++ b/library/Swagger/Annotations/Info.php @@ -0,0 +1,86 @@ +$required)) { + Logger::notice('Required field "'.$required.'" is missing for "'.$this->identity().'" in '.$this->_context); + return false; + } + } + return true; + } +} diff --git a/library/Swagger/Annotations/Model.php b/library/Swagger/Annotations/Model.php index 35a51cbf8..77ef33f8b 100644 --- a/library/Swagger/Annotations/Model.php +++ b/library/Swagger/Annotations/Model.php @@ -30,29 +30,34 @@ * @subpackage * * @Annotation + * @link https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#527-model-object */ class Model extends AbstractAnnotation { /** + * A unique identifier for the model. * @var string */ public $id; /** + * A brief description of this model. * @var string */ public $description; /** + * A definition of which properties must exist when a model instance is produced. * @var array */ - public $properties = array(); + public $required; /** - * @var array + * A list of properties (fields) that are part of the model. + * @var Property[] */ - public $required; - + public $properties = array(); + protected static $mapAnnotations = array( '\Swagger\Annotations\Property' => 'properties[]' ); diff --git a/library/Swagger/Annotations/Operation.php b/library/Swagger/Annotations/Operation.php index 73ad39de4..a308afd32 100644 --- a/library/Swagger/Annotations/Operation.php +++ b/library/Swagger/Annotations/Operation.php @@ -43,7 +43,7 @@ class Operation extends AbstractAnnotation { /** - * This is the HTTP method required to invoke this operation--the allowable values are GET, POST, PUT, DELETE. + * This is the HTTP method required to invoke this operation--the allowable values are GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS. * @var string */ public $method; @@ -100,7 +100,13 @@ class Operation extends AbstractAnnotation public $consumes; /** - * Undocumented + * A list of authorizations required to execute this operation. + * @var string|array + */ + public $authorizations; + + /** + * Declares this operation to be deprecated. * @var bool */ public $deprecated; @@ -111,6 +117,7 @@ class Operation extends AbstractAnnotation '\Swagger\Annotations\Produces' => 'produces[]', '\Swagger\Annotations\Consumes' => 'consumes[]', '\Swagger\Annotations\Items' => 'items', + '\Swagger\Annotations\Authorizations' => 'authorizations', ); /** diff --git a/library/Swagger/Annotations/Resource.php b/library/Swagger/Annotations/Resource.php index 9f0763907..da950a604 100644 --- a/library/Swagger/Annotations/Resource.php +++ b/library/Swagger/Annotations/Resource.php @@ -82,47 +82,58 @@ class Resource extends AbstractAnnotation */ public $description; + /** + * @var Authorizations + */ + public $authorizations; + protected static $mapAnnotations = array( '\Swagger\Annotations\Api' => 'apis[]', '\Swagger\Annotations\Produces' => 'produces[]', '\Swagger\Annotations\Consumes' => 'consumes[]', + '\Swagger\Annotations\Authorizations' => 'authorizations', ); public function validate() { + if (empty($this->resourcePath)) { + Logger::warning('@SWG\Resource() is missing "resourcePath" in '.$this->_context); + return false; + } if ($this->swaggerVersion) { if (version_compare($this->swaggerVersion, '1.2', '<')) { Logger::warning('swaggerVersion: '.$this->swaggerVersion.' is no longer supported. Use 1.2 or higher'); $this->swaggerVersion = null; } } - $apis = array(); + $validApis = array(); foreach ($this->apis as $api) { - if ($api->validate()) { - $append = true; - foreach ($apis as $validApi) { - if ($validApi->path === $api->path && $validApi->description === $api->description) { // A similar api call? - $append = false; - // merge operations - foreach ($api->operations as $operation) { - $validApi->operations[] = $operation; - } + $append = true; + foreach ($validApis as $validApi) { + if ($api->path === $validApi->path) { // The same api path? + $append = false; + // merge operations + foreach ($api->operations as $operation) { + $validApi->operations[] = $operation; } - } - if ($append) { - $apis[] = $api; + // merge description + if ($validApi->description === null) { + $validApi->description = $api->description; + } elseif ($api->description !== null && $api->description !== $validApi->description){ + Logger::notice('Competing description for '.$validApi->identity().' in '.$validApi->_context.' and '.$api->_context); + } + break; } } + if ($api->validate() && $append) { + $validApis[] = $api; + } } - if (count($apis) === 0 && count($this->_partials) === 0) { + if (count($validApis) === 0 && count($this->_partials) === 0) { Logger::warning($this->identity().' doesn\'t have any valid api calls'); return false; } - if (empty($this->resourcePath)) { - Logger::warning('@SWG\Resource() is missing "resourcePath" in '.$this->_context); - return false; - } - $this->apis = $apis; + $this->apis = $validApis; Produces::validateContainer($this); Consumes::validateContainer($this); return true; diff --git a/library/Swagger/Annotations/ResponseMessage.php b/library/Swagger/Annotations/ResponseMessage.php index 520292e74..ee3beb2b7 100644 --- a/library/Swagger/Annotations/ResponseMessage.php +++ b/library/Swagger/Annotations/ResponseMessage.php @@ -29,7 +29,7 @@ * @subpackage * * @Annotation - * @link https://github.com/wordnik/swagger-core/wiki/Response-Messages + * @link https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#525-response-message-object */ class ResponseMessage extends AbstractAnnotation { diff --git a/library/Swagger/Annotations/Scope.php b/library/Swagger/Annotations/Scope.php new file mode 100644 index 000000000..3db429d2b --- /dev/null +++ b/library/Swagger/Annotations/Scope.php @@ -0,0 +1,61 @@ +scope)) { + Logger::warning('Required field "scope" is missing for "'.$this->identity().'" in '.$this->_context); + return false; + } + return true; + } + +} diff --git a/library/Swagger/Context.php b/library/Swagger/Context.php index 8c263ab8c..71488a96b 100644 --- a/library/Swagger/Context.php +++ b/library/Swagger/Context.php @@ -156,8 +156,6 @@ public function __toString() { return $this->getDebugLocation(); } - - /** * @return string|null */ diff --git a/library/Swagger/Parser.php b/library/Swagger/Parser.php index a5c1ee569..f66d162c3 100644 --- a/library/Swagger/Parser.php +++ b/library/Swagger/Parser.php @@ -40,6 +40,18 @@ class Parser */ public static $context; + /** + * The metadata about the API + * @var Annotations\Info + */ + protected $info = null; + + /** + * The authentication config + * @var Annotations\Authorizations + */ + protected $authorizations = null; + /** * All detected resources * @var Resource[] @@ -143,6 +155,22 @@ public function setPartial($key, $annotation) $this->partials[$key] = $annotation; } + /** + * @return Annotations\Info + */ + public function getInfo() + { + return $this->info; + } + + /** + * @return Annotations\Authorizations + */ + public function getAuthorizations() + { + return $this->authorizations; + } + /** * Extract and process all doc-comments from a file. * @param string $filename Path to a php file. @@ -299,6 +327,13 @@ protected function parseTokens(TokenParser $tokenParser, $parseContext) if ($comment) { // File ends with a T_DOC_COMMENT $this->parseContext(new Context(array('comment' => $comment, 'line' => $line), $classContext)); } + $rootContext = $parseContext->getRootContext(); + if ($rootContext->info) { + $this->info = $rootContext->info; + } + if ($rootContext->authorizations) { + $this->authorizations = $rootContext->authorizations; + } } /** diff --git a/library/Swagger/Processors/AuthorizationProcessor.php b/library/Swagger/Processors/AuthorizationProcessor.php new file mode 100644 index 000000000..4fb644dc1 --- /dev/null +++ b/library/Swagger/Processors/AuthorizationProcessor.php @@ -0,0 +1,44 @@ +validate()) { + $root = $context->getRootContext(); + if ($root->authorizations === null) { + $root->authorizations = array(); + } + $root->authorizations[] = $annotation; + } + } +} diff --git a/library/Swagger/Processors/InfoProcessor.php b/library/Swagger/Processors/InfoProcessor.php new file mode 100644 index 000000000..4308392eb --- /dev/null +++ b/library/Swagger/Processors/InfoProcessor.php @@ -0,0 +1,45 @@ +validate()) { + $root = $context->getRootContext(); + if ($root->info !== null) { + Logger::notice('Overwriting '.$annotation->identity().': "'.$root->info->_context.'" with "'.$annotation->_context.'"'); + } + $root->info = $annotation; + } + } +} diff --git a/library/Swagger/Processors/NestingProcessor.php b/library/Swagger/Processors/NestingProcessor.php index 1c7836c8d..349e217d6 100644 --- a/library/Swagger/Processors/NestingProcessor.php +++ b/library/Swagger/Processors/NestingProcessor.php @@ -41,7 +41,9 @@ public function process($annotation, $context) 'Swagger\Annotations\Resource', 'Swagger\Annotations\Api', 'Swagger\Annotations\Model', - 'Swagger\Annotations\Property' + 'Swagger\Annotations\Property', + 'Swagger\Annotations\Info', + 'Swagger\Annotations\Authorization', ); if (in_array(get_class($annotation), $whitelist)) { return; diff --git a/library/Swagger/Swagger.php b/library/Swagger/Swagger.php index f324f92cb..6e2985e04 100755 --- a/library/Swagger/Swagger.php +++ b/library/Swagger/Swagger.php @@ -29,6 +29,16 @@ */ class Swagger { + /** + * @var array|Annotations\Info + */ + public $info; + + /** + * @var Annotations\Authorization[] + */ + public $authorizations = array(); + /** * @var Resource[] */ @@ -146,6 +156,12 @@ public function getResourceList($options = array()) if ($result['basePath'] === null) { unset($result['basePath']); } + if ($this->info !== null) { + $result['info'] = $this->info; + } + if (count($this->authorizations) !== 0) { + $result['authorizations'] = $this->authorizations; + } switch ($options['output']) { case 'array': return $result; @@ -264,6 +280,23 @@ public function examine($contents, $context = null) */ protected function processParser($parser) { + $info = $parser->getInfo(); + if ($info) { + if ($this->info !== null) { + Logger::notice('Overwriting '.$info->identity().' "'.$this->info->_context.'" with "'.$info->_context.'"'); + } + $this->info = $info; + } + $authorizations = $parser->getAuthorizations(); + if ($authorizations) { + foreach ($authorizations as $authorization) { + if (isset($this->authorizations[$authorization->type])) { + Logger::notice('Overwriting '.$authorization->identity().' "'.$this->authorizations[$authorization->type]->_context.'" with "'.$authorization->_context.'"'); + } + $this->authorizations[$authorization->type] = $authorization; + } + + } foreach ($parser->getResources() as $resource) { if (array_key_exists($resource->resourcePath, $this->registry)) { $this->registry[$resource->resourcePath]->merge($resource); @@ -297,10 +330,12 @@ protected function processResults() } foreach ($this->registry as $resource) { + $this->applyAuthorization($resource); $models = array(); foreach ($resource->apis as $api) { foreach ($api->operations as $operation) { + $this->applyAuthorization($operation); $model = $this->resolveModel($operation->type); if ($model) { $models[] = $model; @@ -387,6 +422,52 @@ protected function applyPartials($node, $depth = 0) } } + /** + * Expand the authorizations fields with the data from @SWG\Authorization(). + * @param AbstractAnnotation $annotation + * @return void + */ + protected function applyAuthorization($annotation) { + if ($annotation->authorizations === null) { + return; + } + if (is_array($annotation->authorizations) && count($annotation->authorizations) === 0) { + $annotation->authorizations = new \stdClass(); + return; + } + if (is_string($annotation->authorizations) === false) { + return; + } + $keys = explode(',', $annotation->authorizations); + $annotation->authorizations = new \stdClass(); + foreach ($keys as $key) { + $key = trim($key); + $registered = false; + if (isset($this->authorizations[$key])) { + $annotation->authorizations->$key = array(); + continue; + } + + $pos = strpos($key, '.'); + if ($pos !== false) { + $scope = substr($key, $pos + 1); + $oauth2 = substr($key, 0, $pos); + if (isset($annotation->authorizations->$oauth2) === false) { + $annotation->authorizations->$oauth2 = array(); + } + if (isset($this->authorizations[$oauth2]) && is_array($this->authorizations[$oauth2]->scopes)) { + foreach ($this->authorizations[$oauth2]->scopes as $scopeObj) { + if ($scope === $scopeObj->scope) { + array_push($annotation->authorizations->$oauth2, $scopeObj); + continue 2; + } + } + } + } + Logger::notice('Authorization: "'.$key.'" not registered, used in '.$annotation->_context); + } + } + /** * * @param string|null $model @@ -498,7 +579,7 @@ public static function isPrimitive($type) /** * Log a notice when the type doesn't exactly match the Swagger spec. - * @link https://github.com/wordnik/swagger-core/wiki/Datatypes + * @link https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#43-data-types * * @param string $type * @param Context $context @@ -552,6 +633,9 @@ public static function export($data) } else { $data = get_object_vars($data); } + if (count($data) === 0) { + return (object) $data; // empty object + } } if (is_array($data) === false) { return $data; @@ -651,7 +735,7 @@ public function setRegistry($registry) protected function inheritProperties($model) { $context = $model->_context; - if ($context->is('class') && $context->extends === null && $context->propertiesInherited !== null) { + if ($context->is('class') === false || $context->extends === null || $context->propertiesInherited !== null) { return; // model doesn't have a superclass or is already resolved } $parent = false; @@ -717,6 +801,9 @@ public static function getDefaultProcessors() new Processors\ApiProcessor(), new Processors\ModelProcessor(), new Processors\PropertyProcessor(), + new Processors\ResourceProcessor(), + new Processors\InfoProcessor(), + new Processors\AuthorizationProcessor(), new Processors\NestingProcessor(), ); } diff --git a/package.xml b/package.xml index 9596ef7d2..8932fdacf 100644 --- a/package.xml +++ b/package.xml @@ -38,7 +38,7 @@ Features More on Swagger: * http://swagger.wordnik.com/ - * https://github.com/wordnik/swagger-core/wiki + * https://github.com/wordnik/swagger-spec/ * https://github.com/outeredge/SwaggerModule a ZF2 Module implementing swagger-php diff --git a/readme.md b/readme.md index 298f85207..c73b8e483 100644 --- a/readme.md +++ b/readme.md @@ -32,7 +32,7 @@ Features More on Swagger: * http://swagger.wordnik.com/ - * https://github.com/wordnik/swagger-core/wiki + * https://github.com/wordnik/swagger-spec/ * https://github.com/outeredge/SwaggerModule a ZF2 Module implementing swagger-php [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/zircote/swagger-php/trend.png)](https://bitdeli.com/free "Bitdeli Badge") diff --git a/tests/ExamplesOutput/Facet/facet.json b/tests/ExamplesOutput/Facet/facet.json index bf7a645c8..ce7f29540 100644 --- a/tests/ExamplesOutput/Facet/facet.json +++ b/tests/ExamplesOutput/Facet/facet.json @@ -3,11 +3,11 @@ "swaggerVersion":"1.2", "basePath":"http://facetstore.zircote.com/swagger-php/api", "resourcePath":"/facet", + "produces": ["application/json"], "apis":[ { "path":"/facet.{format}/{facetId}", "description":"Operations about facets", - "produces": ["application/json"], "operations":[ { "nickname":"getfacetById", diff --git a/tests/SwaggerTests/SwaggerTest.php b/tests/SwaggerTests/SwaggerTest.php index dbb1613aa..3bdccaa98 100644 --- a/tests/SwaggerTests/SwaggerTest.php +++ b/tests/SwaggerTests/SwaggerTest.php @@ -228,7 +228,7 @@ public function assertOutputEqualsJson($outputFile, $json, $message = '') if (file_exists($this->outputDir($outputFile))) { $outputFile = $this->outputDir($outputFile); } - $expectedArray = json_decode(file_get_contents($outputFile), true); + $expected = json_decode(file_get_contents($outputFile)); $error = json_last_error(); if ($error !== JSON_ERROR_NONE) { $this->fail('File: "'.$outputFile.'" doesn\'t contain valid json, error '.$error); @@ -236,75 +236,62 @@ public function assertOutputEqualsJson($outputFile, $json, $message = '') if (is_string($json) === false) { $this->fail('Not a (json) string'); } - $actualArray = json_decode($json, true); + $actual = json_decode($json); $error = json_last_error(); if ($error !== JSON_ERROR_NONE) { $this->fail('invalid json, error '.$error); } - $this->sort($expectedArray, '"'.$outputFile.'"'); - $this->sort($actualArray, 'generated json'); - $expectedJson = Swagger::jsonEncode($expectedArray, true); - $actualJson = Swagger::jsonEncode($actualArray, true); + $expectedJson = Swagger::jsonEncode($this->sort($expected, '"'.$outputFile.'" '), true); + $actualJson = Swagger::jsonEncode($this->sort($actual, 'generated json '), true); return $this->assertEquals($expectedJson, $actualJson, $message); } /** * Sorts the array to improve matching and debugging the differrences. * Used by assertOutputEqualsJson - * @param array $array - * @retrun void + * @param object $object + * @return The sorted object */ - private function sort(&$array, $origin = 'unknown') + private function sort(\stdClass $object, $origin = 'unknown') { - ksort($array); - if (isset($array['properties'])) { // a model? - ksort($array['properties']); - foreach (array_keys($array['properties']) as $property) { - ksort($array['properties'][$property]); - } + static $sortMap = null; + if ($sortMap === null) { + $sortMap = array( + // property -> algorithm + 'apis' => function ($a, $b) { return strcasecmp($a->path, $b->path); }, + 'operations' => function ($a, $b) { return strcasecmp($a->nickname, $b->nickname); }, + 'parameters' => function ($a, $b) { return strcasecmp($a->name, $b->name); }, + 'responseMessages' => function ($a, $b) { return strcasecmp($a->code, $b->code); }, + 'produces' => 'strcasecmp', + 'consumes' => 'strcasecmp', + 'required' => 'strcasecmp', + 'enum' => 'strcasecmp', + 'scopes' => function ($a, $b) { return strcasecmp($a->scope, $b->scope); }, + 'oauth2' => function ($a, $b) { return strcasecmp($a->scope, $b->scope); }, + ); } - if (isset($array['apis'])) { // a resource? - usort($array['apis'], function ($a, $b) { - return strcmp($a['path'], $b['path']); - }); - foreach (array_keys($array['apis']) as $api) { - ksort($array['apis'][$api]); - if (isset($array['apis'][$api]['operations'])) { - usort($array['apis'][$api]['operations'], function ($a, $b) { - return strcmp($a['method'].' '.$a['nickname'], $b['method'].' '.$b['nickname']); - }); - foreach (array_keys($array['apis'][$api]['operations']) as $operation) { - ksort($array['apis'][$api]['operations'][$operation]); - if (isset($array['apis'][$api]['operations'][$operation]['parameters'])) { - usort($array['apis'][$api]['operations'][$operation]['parameters'], function ($a, $b) { - $aName = array_key_exists('name', $a) ? $a['name'] : ''; - $bName = array_key_exists('name', $b) ? $b['name'] : ''; - return strcmp($aName, $bName); - }); - foreach (array_keys($array['apis'][$api]['operations'][$operation]['parameters']) as $parameter) { - ksort($array['apis'][$api]['operations'][$operation]['parameters'][$parameter]); - } - } - if (isset($array['apis'][$api]['operations'][$operation]['responseMessages'])) { - usort($array['apis'][$api]['operations'][$operation]['responseMessages'], function ($a, $b) { - return strcmp($a['code'], $b['code']); - }); - foreach (array_keys($array['apis'][$api]['operations'][$operation]['responseMessages']) as $responseMessage) { - ksort($array['apis'][$api]['operations'][$operation]['responseMessages'][$responseMessage]); - } - } + $data = get_object_vars($object); + ksort($data); + foreach ($data as $property => $value) { + if (is_object($value)) { + $data[$property] = $this->sort($value, $origin .'->'.$property); + } + if (is_array($value)) { + $sort = @$sortMap[$property]; + if ($sort) { + usort($value, $sort); + $data[$property] = $value; + } else { + // echo 'no sort for '.$origin.'->'.$property."\n";die; + } + foreach ($value as $i => $element) { + if (is_object($element)) { + $data[$property][$i] = $this->sort($element, $origin.'->'.$property.'['.$i.']'); } - } elseif (array_key_exists('models', $array) || count($array['apis'][$api]) > 2) { // not a resource-listing? - $this->fail('No operations in api "'.$array['apis'][$api]['path'].'" for "'.$origin.'"'); } } } - if (isset($array['models'])) { // models inside a resource? - ksort($array['models']); - foreach (array_keys($array['models']) as $model) { - $this->sort($array['models'][$model], $origin); - } - } + return (object)$data; } protected function examplesDir($example = '')