diff --git a/src/Annotations/PathItem.php b/src/Annotations/PathItem.php
index cfeeedda..c99efd5c 100644
--- a/src/Annotations/PathItem.php
+++ b/src/Annotations/PathItem.php
@@ -155,4 +155,21 @@ class PathItem extends AbstractAnnotation
public static $_parents = [
OpenApi::class,
];
+
+ /**
+ * Returns a list of all operations (all methods) for this path item.
+ *
+ * @return Operation[]
+ */
+ public function operations(): array
+ {
+ $operations = [];
+ foreach (PathItem::$_nested as $className => $property) {
+ if (is_subclass_of($className, Operation::class) && !Generator::isDefault($this->{$property})) {
+ $operations[] = $this->{$property};
+ }
+ }
+
+ return $operations;
+ }
}
diff --git a/src/Generator.php b/src/Generator.php
index ebcbf320..16d61acd 100644
--- a/src/Generator.php
+++ b/src/Generator.php
@@ -277,6 +277,8 @@ public function getProcessorPipeline(): Pipeline
new Processors\OperationId(),
new Processors\AugmentTags(),
new Processors\CleanUnmerged(),
+ new Processors\PathFilter(),
+ new Processors\CleanUnusedComponents(),
]);
}
diff --git a/src/Processors/AugmentParameters.php b/src/Processors/AugmentParameters.php
index cb5818e1..47670724 100644
--- a/src/Processors/AugmentParameters.php
+++ b/src/Processors/AugmentParameters.php
@@ -30,9 +30,11 @@ public function isAugmentOperationParameters(): bool
/**
* If set to true
try to find operation parameter descriptions in the operation docblock.
*/
- public function setAugmentOperationParameters(bool $augmentOperationParameters): void
+ public function setAugmentOperationParameters(bool $augmentOperationParameters): AugmentParameters
{
$this->augmentOperationParameters = $augmentOperationParameters;
+
+ return $this;
}
public function __invoke(Analysis $analysis)
diff --git a/src/Processors/CleanUnusedComponents.php b/src/Processors/CleanUnusedComponents.php
index 4a7303c3..624ae7e4 100644
--- a/src/Processors/CleanUnusedComponents.php
+++ b/src/Processors/CleanUnusedComponents.php
@@ -10,17 +10,42 @@
use OpenApi\Annotations as OA;
use OpenApi\Generator;
+/**
+ * Tracks the use of all Components
and removed unused schemas.
+ */
class CleanUnusedComponents implements ProcessorInterface
{
- use Concerns\CollectorTrait;
+ use Concerns\AnnotationTrait;
+
+ /**
+ * @var bool
+ */
+ protected $enabled = false;
+
+ public function __construct(bool $enabled = false)
+ {
+ $this->enabled = $enabled;
+ }
+
+ public function isEnabled(): bool
+ {
+ return $this->enabled;
+ }
+
+ public function setEnabled(bool $enabled): CleanUnusedComponents
+ {
+ $this->enabled = $enabled;
+
+ return $this;
+ }
public function __invoke(Analysis $analysis)
{
- if (Generator::isDefault($analysis->openapi->components)) {
+ if (!$this->enabled || Generator::isDefault($analysis->openapi->components)) {
return;
}
- $analysis->annotations = $this->collect($analysis->annotations);
+ $analysis->annotations = $this->collectAnnotations($analysis->annotations);
// allow multiple runs to catch nested dependencies
for ($ii = 0; $ii < 10; ++$ii) {
@@ -83,10 +108,12 @@ protected function cleanup(Analysis $analysis): bool
foreach ($analysis->openapi->components->{$componentType} as $ii => $component) {
if ($component->{$nameProperty} == $name) {
$annotation = $analysis->openapi->components->{$componentType}[$ii];
- foreach ($this->collect([$annotation]) as $unused) {
- $analysis->annotations->detach($unused);
- }
+ $this->removeAnnotation($analysis->annotations, $annotation);
unset($analysis->openapi->components->{$componentType}[$ii]);
+
+ if (!$analysis->openapi->components->{$componentType}) {
+ $analysis->openapi->components->{$componentType} = Generator::UNDEFINED;
+ }
}
}
}
diff --git a/src/Processors/Concerns/AnnotationTrait.php b/src/Processors/Concerns/AnnotationTrait.php
new file mode 100644
index 00000000..0db40707
--- /dev/null
+++ b/src/Processors/Concerns/AnnotationTrait.php
@@ -0,0 +1,67 @@
+traverseAnnotations($root, function ($item) use (&$storage) {
+ if ($item instanceof OA\AbstractAnnotation && !$storage->contains($item)) {
+ $storage->attach($item);
+ }
+ });
+
+ return $storage;
+ }
+
+ /**
+ * Remove all annotations that are part of the `$annotation` tree.
+ */
+ public function removeAnnotation(iterable $root, OA\AbstractAnnotation $annotation): void
+ {
+ $remove = $this->collectAnnotations($annotation);
+ $this->traverseAnnotations($root, function ($item) use ($remove) {
+ if ($item instanceof \SplObjectStorage) {
+ foreach ($remove as $annotation) {
+ $item->detach($annotation);
+ }
+ }
+ });
+ }
+
+ /**
+ * @param string|array|iterable|OA\AbstractAnnotation $root
+ */
+ public function traverseAnnotations($root, callable $callable): void
+ {
+ $callable($root);
+
+ if (is_iterable($root)) {
+ foreach ($root as $value) {
+ $this->traverseAnnotations($value, $callable);
+ }
+ } elseif ($root instanceof OA\AbstractAnnotation) {
+ foreach (array_merge($root::$_nested, ['allOf', 'anyOf', 'oneOf', 'callbacks']) as $properties) {
+ foreach ((array) $properties as $property) {
+ if (isset($root->{$property})) {
+ $this->traverseAnnotations($root->{$property}, $callable);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Processors/Concerns/CollectorTrait.php b/src/Processors/Concerns/CollectorTrait.php
deleted file mode 100644
index 8e2ef8c1..00000000
--- a/src/Processors/Concerns/CollectorTrait.php
+++ /dev/null
@@ -1,48 +0,0 @@
-traverse($root, function (OA\AbstractAnnotation $annotation) use (&$storage) {
- $storage->attach($annotation);
- });
-
- return $storage;
- }
-
- /**
- * @param string|array|OA\AbstractAnnotation $root
- */
- public function traverse($root, callable $callable): void
- {
- if (is_iterable($root)) {
- foreach ($root as $value) {
- $this->traverse($value, $callable);
- }
- } elseif ($root instanceof OA\AbstractAnnotation) {
- $callable($root);
-
- foreach (array_merge($root::$_nested, ['allOf', 'anyOf', 'oneOf', 'callbacks']) as $properties) {
- foreach ((array) $properties as $property) {
- if (isset($root->{$property})) {
- $this->traverse($root->{$property}, $callable);
- }
- }
- }
- }
- }
-}
diff --git a/src/Processors/PathFilter.php b/src/Processors/PathFilter.php
new file mode 100644
index 00000000..13c3fd5e
--- /dev/null
+++ b/src/Processors/PathFilter.php
@@ -0,0 +1,105 @@
+tags = $tags;
+ $this->paths = $paths;
+ }
+
+ public function getTags(): array
+ {
+ return $this->tags;
+ }
+
+ /**
+ * A list of regular expressions to match tags
to include.
+ *
+ * @param array $tags
+ */
+ public function setTags(array $tags): PathFilter
+ {
+ $this->tags = $tags;
+
+ return $this;
+ }
+
+ public function getPaths(): array
+ {
+ return $this->paths;
+ }
+
+ /**
+ * A list of regular expressions to match paths
to include.
+ *
+ * @param array $paths
+ */
+ public function setPaths(array $paths): PathFilter
+ {
+ $this->paths = $paths;
+
+ return $this;
+ }
+
+ public function __invoke(Analysis $analysis)
+ {
+ if (($this->tags || $this->paths) && !Generator::isDefault($analysis->openapi->paths)) {
+ $filtered = [];
+ foreach ($analysis->openapi->paths as $pathItem) {
+ $matched = null;
+ foreach ($this->tags as $pattern) {
+ foreach ($pathItem->operations() as $operation) {
+ if (!Generator::isDefault($operation->tags)) {
+ foreach ($operation->tags as $tag) {
+ if (preg_match($pattern, $tag)) {
+ $matched = $pathItem;
+ break 3;
+ }
+ }
+ }
+ }
+ }
+
+ foreach ($this->paths as $pattern) {
+ if (preg_match($pattern, $pathItem->path)) {
+ $matched = $pathItem;
+ break;
+ }
+ }
+
+ if ($matched) {
+ $filtered[] = $matched;
+ } else {
+ $this->removeAnnotation($analysis->annotations, $pathItem);
+ }
+ }
+
+ $analysis->openapi->paths = $filtered;
+ }
+ }
+}
diff --git a/tests/Processors/CleanUnusedComponentsTest.php b/tests/Processors/CleanUnusedComponentsTest.php
index ee9f51bc..bcc0fcb6 100644
--- a/tests/Processors/CleanUnusedComponentsTest.php
+++ b/tests/Processors/CleanUnusedComponentsTest.php
@@ -6,6 +6,7 @@
namespace OpenApi\Tests\Processors;
+use OpenApi\Generator;
use OpenApi\Processors\CleanUnusedComponents;
use OpenApi\Tests\OpenApiTestCase;
@@ -17,9 +18,9 @@ public static function countCases(): iterable
return [
'var-default' => [$defaultProcessors, 'UsingVar.php', 2, 5],
- 'var-clean' => [array_merge($defaultProcessors, [new CleanUnusedComponents()]), 'UsingVar.php', 0, 2],
+ 'var-clean' => [array_merge($defaultProcessors, [new CleanUnusedComponents(true)]), 'UsingVar.php', 0, 2],
'unreferenced-default' => [$defaultProcessors, 'Unreferenced.php', 2, 11],
- 'unreferenced-clean' => [array_merge($defaultProcessors, [new CleanUnusedComponents()]), 'Unreferenced.php', 0, 5],
+ 'unreferenced-clean' => [array_merge($defaultProcessors, [new CleanUnusedComponents(true)]), 'Unreferenced.php', 0, 5],
];
}
@@ -30,7 +31,11 @@ public function testCounts(array $processors, string $fixture, int $expectedSche
{
$analysis = $this->analysisFromFixtures([$fixture], $processors);
- $this->assertCount($expectedSchemaCount, $analysis->openapi->components->schemas);
+ if ($expectedSchemaCount === 0) {
+ $this->assertTrue(Generator::isDefault($analysis->openapi->components->schemas));
+ } else {
+ $this->assertCount($expectedSchemaCount, $analysis->openapi->components->schemas);
+ }
$this->assertCount($expectedAnnotationCount, $analysis->annotations);
}
}