From 06bb7a2dea169a04e048468611b3d9a4b938e0dc Mon Sep 17 00:00:00 2001 From: Manuel Rossard Date: Thu, 5 Sep 2024 09:15:45 +0200 Subject: [PATCH 1/6] feat: better path sorting for openapi UIs --- src/OpenApi/Model/Paths.php | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/OpenApi/Model/Paths.php b/src/OpenApi/Model/Paths.php index 2b28d2efd48..82aa10800dd 100644 --- a/src/OpenApi/Model/Paths.php +++ b/src/OpenApi/Model/Paths.php @@ -20,8 +20,33 @@ final class Paths public function addPath(string $path, PathItem $pathItem): void { $this->paths[$path] = $pathItem; + } + + private function comparePathsByKey($keyA, $keyB): int + { + $a = $this->paths[$keyA]; + $b = $this->paths[$keyB]; - ksort($this->paths); + $tagsA = [ + ...($a->getGet()?->getTags() ?? []), + ...($a->getPost()?->getTags() ?? []), + ...($a->getPut()?->getTags() ?? []), + ...($a->getDelete()?->getTags() ?? []), + ]; + sort($tagsA); + + $tagsB = [ + ...($b->getGet()?->getTags() ?? []), + ...($b->getPost()?->getTags() ?? []), + ...($b->getPut()?->getTags() ?? []), + ...($b->getDelete()?->getTags() ?? []), + ]; + sort($tagsB); + + return match(true) { + current($tagsA) === current($tagsB) => $keyA <=> $keyB, + default => current($tagsA) <=> current($tagsB), + }; } public function getPath(string $path): ?PathItem @@ -31,6 +56,9 @@ public function getPath(string $path): ?PathItem public function getPaths(): array { + // sort paths by tags, then by path for each tag + uksort($this->paths, $this->comparePathsByKey(...)); + return $this->paths; } } From 295bee457d1e63070bd56e1a3cd36f93c1ab8ff2 Mon Sep 17 00:00:00 2001 From: Manuel Rossard Date: Thu, 5 Sep 2024 09:25:41 +0200 Subject: [PATCH 2/6] fix: cs fixer --- src/OpenApi/Model/Paths.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenApi/Model/Paths.php b/src/OpenApi/Model/Paths.php index 82aa10800dd..b4f350c2c0f 100644 --- a/src/OpenApi/Model/Paths.php +++ b/src/OpenApi/Model/Paths.php @@ -43,7 +43,7 @@ private function comparePathsByKey($keyA, $keyB): int ]; sort($tagsB); - return match(true) { + return match (true) { current($tagsA) === current($tagsB) => $keyA <=> $keyB, default => current($tagsA) <=> current($tagsB), }; From 1cf103389e87f694dd237adb67efde6740d3bf85 Mon Sep 17 00:00:00 2001 From: Manuel Rossard Date: Thu, 5 Sep 2024 13:02:24 +0200 Subject: [PATCH 3/6] fix: move the paths sorting logic to the normalizer --- src/OpenApi/Model/Paths.php | 30 ---------------- src/OpenApi/Serializer/OpenApiNormalizer.php | 37 +++++++++++++++++++- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/OpenApi/Model/Paths.php b/src/OpenApi/Model/Paths.php index b4f350c2c0f..aaf5a5ede11 100644 --- a/src/OpenApi/Model/Paths.php +++ b/src/OpenApi/Model/Paths.php @@ -22,33 +22,6 @@ public function addPath(string $path, PathItem $pathItem): void $this->paths[$path] = $pathItem; } - private function comparePathsByKey($keyA, $keyB): int - { - $a = $this->paths[$keyA]; - $b = $this->paths[$keyB]; - - $tagsA = [ - ...($a->getGet()?->getTags() ?? []), - ...($a->getPost()?->getTags() ?? []), - ...($a->getPut()?->getTags() ?? []), - ...($a->getDelete()?->getTags() ?? []), - ]; - sort($tagsA); - - $tagsB = [ - ...($b->getGet()?->getTags() ?? []), - ...($b->getPost()?->getTags() ?? []), - ...($b->getPut()?->getTags() ?? []), - ...($b->getDelete()?->getTags() ?? []), - ]; - sort($tagsB); - - return match (true) { - current($tagsA) === current($tagsB) => $keyA <=> $keyB, - default => current($tagsA) <=> current($tagsB), - }; - } - public function getPath(string $path): ?PathItem { return $this->paths[$path] ?? null; @@ -56,9 +29,6 @@ public function getPath(string $path): ?PathItem public function getPaths(): array { - // sort paths by tags, then by path for each tag - uksort($this->paths, $this->comparePathsByKey(...)); - return $this->paths; } } diff --git a/src/OpenApi/Serializer/OpenApiNormalizer.php b/src/OpenApi/Serializer/OpenApiNormalizer.php index b2d179450a9..60d7c63786c 100644 --- a/src/OpenApi/Serializer/OpenApiNormalizer.php +++ b/src/OpenApi/Serializer/OpenApiNormalizer.php @@ -39,7 +39,42 @@ public function __construct(private readonly NormalizerInterface $decorated) */ public function normalize(mixed $object, ?string $format = null, array $context = []): array { - $pathsCallback = static fn ($decoratedObject): array => $decoratedObject instanceof Paths ? $decoratedObject->getPaths() : []; + $pathsCallback = static function ($decoratedObject): array { + if($decoratedObject instanceof Paths) { + $paths = $decoratedObject->getPaths(); + + // sort paths by tags, then by path for each tag + uksort($paths, function ($keyA, $keyB) use ($paths) { + $a = $paths[$keyA]; + $b = $paths[$keyB]; + + $tagsA = [ + ...($a->getGet()?->getTags() ?? []), + ...($a->getPost()?->getTags() ?? []), + ...($a->getPut()?->getTags() ?? []), + ...($a->getDelete()?->getTags() ?? []), + ]; + sort($tagsA); + + $tagsB = [ + ...($b->getGet()?->getTags() ?? []), + ...($b->getPost()?->getTags() ?? []), + ...($b->getPut()?->getTags() ?? []), + ...($b->getDelete()?->getTags() ?? []), + ]; + sort($tagsB); + + return match (true) { + current($tagsA) === current($tagsB) => $keyA <=> $keyB, + default => current($tagsA) <=> current($tagsB), + }; + }); + + return $paths; + } + + return []; + }; $context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] = true; $context[AbstractObjectNormalizer::SKIP_NULL_VALUES] = true; $context[AbstractNormalizer::CALLBACKS] = [ From 9747b510a11130ecadda32c41bfb95d723a6bb90 Mon Sep 17 00:00:00 2001 From: Manuel Rossard Date: Thu, 5 Sep 2024 13:06:40 +0200 Subject: [PATCH 4/6] fix: cs fixer --- src/OpenApi/Serializer/OpenApiNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenApi/Serializer/OpenApiNormalizer.php b/src/OpenApi/Serializer/OpenApiNormalizer.php index 60d7c63786c..f75365df30b 100644 --- a/src/OpenApi/Serializer/OpenApiNormalizer.php +++ b/src/OpenApi/Serializer/OpenApiNormalizer.php @@ -40,7 +40,7 @@ public function __construct(private readonly NormalizerInterface $decorated) public function normalize(mixed $object, ?string $format = null, array $context = []): array { $pathsCallback = static function ($decoratedObject): array { - if($decoratedObject instanceof Paths) { + if ($decoratedObject instanceof Paths) { $paths = $decoratedObject->getPaths(); // sort paths by tags, then by path for each tag From f6a80acd1956b1877c00d13be9bc9be802ffc051 Mon Sep 17 00:00:00 2001 From: Manuel Rossard Date: Thu, 5 Sep 2024 13:13:30 +0200 Subject: [PATCH 5/6] fix: refactoring for readability --- src/OpenApi/Serializer/OpenApiNormalizer.php | 77 +++++++++++--------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/src/OpenApi/Serializer/OpenApiNormalizer.php b/src/OpenApi/Serializer/OpenApiNormalizer.php index f75365df30b..3e98e271485 100644 --- a/src/OpenApi/Serializer/OpenApiNormalizer.php +++ b/src/OpenApi/Serializer/OpenApiNormalizer.php @@ -39,42 +39,7 @@ public function __construct(private readonly NormalizerInterface $decorated) */ public function normalize(mixed $object, ?string $format = null, array $context = []): array { - $pathsCallback = static function ($decoratedObject): array { - if ($decoratedObject instanceof Paths) { - $paths = $decoratedObject->getPaths(); - - // sort paths by tags, then by path for each tag - uksort($paths, function ($keyA, $keyB) use ($paths) { - $a = $paths[$keyA]; - $b = $paths[$keyB]; - - $tagsA = [ - ...($a->getGet()?->getTags() ?? []), - ...($a->getPost()?->getTags() ?? []), - ...($a->getPut()?->getTags() ?? []), - ...($a->getDelete()?->getTags() ?? []), - ]; - sort($tagsA); - - $tagsB = [ - ...($b->getGet()?->getTags() ?? []), - ...($b->getPost()?->getTags() ?? []), - ...($b->getPut()?->getTags() ?? []), - ...($b->getDelete()?->getTags() ?? []), - ]; - sort($tagsB); - - return match (true) { - current($tagsA) === current($tagsB) => $keyA <=> $keyB, - default => current($tagsA) <=> current($tagsB), - }; - }); - - return $paths; - } - - return []; - }; + $pathsCallback = $this->getPathsCallBack(); $context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] = true; $context[AbstractObjectNormalizer::SKIP_NULL_VALUES] = true; $context[AbstractNormalizer::CALLBACKS] = [ @@ -130,4 +95,44 @@ public function hasCacheableSupportsMethod(): bool return true; } + + private function getPathsCallBack(): \Closure + { + return static function ($decoratedObject): array { + if ($decoratedObject instanceof Paths) { + $paths = $decoratedObject->getPaths(); + + // sort paths by tags, then by path for each tag + uksort($paths, function ($keyA, $keyB) use ($paths) { + $a = $paths[$keyA]; + $b = $paths[$keyB]; + + $tagsA = [ + ...($a->getGet()?->getTags() ?? []), + ...($a->getPost()?->getTags() ?? []), + ...($a->getPut()?->getTags() ?? []), + ...($a->getDelete()?->getTags() ?? []), + ]; + sort($tagsA); + + $tagsB = [ + ...($b->getGet()?->getTags() ?? []), + ...($b->getPost()?->getTags() ?? []), + ...($b->getPut()?->getTags() ?? []), + ...($b->getDelete()?->getTags() ?? []), + ]; + sort($tagsB); + + return match (true) { + current($tagsA) === current($tagsB) => $keyA <=> $keyB, + default => current($tagsA) <=> current($tagsB), + }; + }); + + return $paths; + } + + return []; + }; + } } From dca8cff1ae6bc6b5d98a38733758d4398e0f3b1a Mon Sep 17 00:00:00 2001 From: Manuel Rossard Date: Fri, 6 Sep 2024 12:27:41 +0200 Subject: [PATCH 6/6] fix: missing the tags for patch --- src/OpenApi/Serializer/OpenApiNormalizer.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/OpenApi/Serializer/OpenApiNormalizer.php b/src/OpenApi/Serializer/OpenApiNormalizer.php index 3e98e271485..d061d566ae3 100644 --- a/src/OpenApi/Serializer/OpenApiNormalizer.php +++ b/src/OpenApi/Serializer/OpenApiNormalizer.php @@ -110,6 +110,7 @@ private function getPathsCallBack(): \Closure $tagsA = [ ...($a->getGet()?->getTags() ?? []), ...($a->getPost()?->getTags() ?? []), + ...($a->getPatch()?->getTags() ?? []), ...($a->getPut()?->getTags() ?? []), ...($a->getDelete()?->getTags() ?? []), ]; @@ -118,6 +119,7 @@ private function getPathsCallBack(): \Closure $tagsB = [ ...($b->getGet()?->getTags() ?? []), ...($b->getPost()?->getTags() ?? []), + ...($b->getPatch()?->getTags() ?? []), ...($b->getPut()?->getTags() ?? []), ...($b->getDelete()?->getTags() ?? []), ];