sunrise-php /
http-router
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * It's free open-source software released under the MIT License. |
||
| 5 | * |
||
| 6 | * @author Anatoly Nekhay <[email protected]> |
||
| 7 | * @copyright Copyright (c) 2018, Anatoly Nekhay |
||
| 8 | * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE |
||
| 9 | * @link https://github.com/sunrise-php/http-router |
||
| 10 | */ |
||
| 11 | |||
| 12 | declare(strict_types=1); |
||
| 13 | |||
| 14 | namespace Sunrise\Http\Router\OpenApi; |
||
| 15 | |||
| 16 | use ReflectionAttribute; |
||
| 17 | use RuntimeException; |
||
| 18 | use Sunrise\Coder\CodecManagerInterface; |
||
| 19 | use Sunrise\Http\Router\Helper\ReflectorHelper; |
||
| 20 | use Sunrise\Http\Router\Helper\RouteSimplifier; |
||
| 21 | use Sunrise\Http\Router\OpenApi\Annotation\Operation; |
||
| 22 | use Sunrise\Http\Router\RequestHandlerReflectorInterface; |
||
| 23 | use Sunrise\Http\Router\RouteInterface; |
||
| 24 | use Throwable; |
||
| 25 | |||
| 26 | use function array_replace_recursive; |
||
| 27 | use function array_walk_recursive; |
||
| 28 | use function dirname; |
||
| 29 | use function file_put_contents; |
||
| 30 | use function fopen; |
||
| 31 | use function is_readable; |
||
| 32 | use function is_writable; |
||
| 33 | use function strtolower; |
||
| 34 | |||
| 35 | /** |
||
| 36 | * @link https://spec.openapis.org/oas/v3.1.0 |
||
| 37 | * |
||
| 38 | * @since 3.0.0 |
||
| 39 | */ |
||
| 40 | final class OpenApiDocumentManager implements OpenApiDocumentManagerInterface |
||
| 41 | { |
||
| 42 | 5 | public function __construct( |
|
| 43 | private readonly OpenApiConfiguration $openApiConfiguration, |
||
| 44 | private readonly OpenApiPhpTypeSchemaResolverManagerInterface $openApiPhpTypeSchemaResolverManager, |
||
| 45 | private readonly OpenApiOperationEnricherManagerInterface $openApiOperationEnricherManager, |
||
| 46 | private readonly RequestHandlerReflectorInterface $requestHandlerReflector, |
||
| 47 | private readonly CodecManagerInterface $codecManager, |
||
| 48 | ) { |
||
| 49 | 5 | } |
|
| 50 | |||
| 51 | /** |
||
| 52 | * @inheritDoc |
||
| 53 | */ |
||
| 54 | 3 | public function buildDocument(array $routes): array |
|
| 55 | { |
||
| 56 | 3 | $document = $this->openApiConfiguration->initialDocument; |
|
| 57 | |||
| 58 | 3 | foreach ($routes as $route) { |
|
| 59 | 3 | $this->describeRoute($route, $document); |
|
| 60 | } |
||
| 61 | |||
| 62 | 3 | $this->openApiPhpTypeSchemaResolverManager->enrichDocumentWithDefinitions($document); |
|
| 63 | |||
| 64 | 3 | return $document; |
|
| 65 | } |
||
| 66 | |||
| 67 | /** |
||
| 68 | * @inheritDoc |
||
| 69 | */ |
||
| 70 | 4 | public function saveDocument(array $document): void |
|
| 71 | { |
||
| 72 | 4 | $filename = $this->openApiConfiguration->getDocumentFilename(); |
|
| 73 | |||
| 74 | 4 | if (!is_writable(dirname($filename))) { |
|
| 75 | 1 | throw new RuntimeException('The directory for the OpenAPI document is not writable.'); |
|
| 76 | } |
||
| 77 | |||
| 78 | 3 | $contents = $this->codecManager->encode( |
|
| 79 | 3 | $this->openApiConfiguration->documentMediaType, |
|
| 80 | 3 | $document, |
|
| 81 | 3 | $this->openApiConfiguration->documentEncodingContext, |
|
| 82 | 3 | ); |
|
| 83 | |||
| 84 | try { |
||
| 85 | 3 | $result = @file_put_contents($filename, $contents); |
|
| 86 | 1 | } catch (Throwable) { |
|
| 87 | 1 | $result = false; |
|
| 88 | } |
||
| 89 | |||
| 90 | 3 | if ($result === false) { |
|
| 91 | 1 | throw new RuntimeException('The OpenAPI document could not be saved.'); |
|
| 92 | } |
||
| 93 | } |
||
| 94 | |||
| 95 | /** |
||
| 96 | * @inheritDoc |
||
| 97 | */ |
||
| 98 | 2 | public function openDocument() |
|
| 99 | { |
||
| 100 | 2 | $filename = $this->openApiConfiguration->getDocumentFilename(); |
|
| 101 | |||
| 102 | 2 | if (!is_readable($filename)) { |
|
| 103 | 1 | throw new RuntimeException('The OpenAPI document was not saved or is unavailable.'); |
|
| 104 | } |
||
| 105 | |||
| 106 | // @codeCoverageIgnoreStart |
||
| 107 | |||
| 108 | try { |
||
| 109 | $result = @fopen($filename, 'rb'); |
||
| 110 | } catch (Throwable) { |
||
| 111 | $result = false; |
||
| 112 | } |
||
| 113 | |||
| 114 | if ($result === false) { |
||
| 115 | throw new RuntimeException('The OpenAPI document could not be read.'); |
||
| 116 | } |
||
| 117 | |||
| 118 | // @codeCoverageIgnoreEnd |
||
| 119 | |||
| 120 | 1 | return $result; |
|
| 121 | } |
||
| 122 | |||
| 123 | /** |
||
| 124 | * @param array<array-key, mixed> $document |
||
|
0 ignored issues
–
show
Documentation
Bug
introduced
by
Loading history...
|
|||
| 125 | * @param-out array<array-key, mixed> $document |
||
| 126 | */ |
||
| 127 | 3 | private function describeRoute(RouteInterface $route, array &$document): void |
|
| 128 | { |
||
| 129 | 3 | $operation = $this->openApiConfiguration->initialOperation; |
|
| 130 | |||
| 131 | 3 | $requestHandler = $this->requestHandlerReflector->reflectRequestHandler($route->getRequestHandler()); |
|
| 132 | |||
| 133 | 3 | foreach (ReflectorHelper::getAncestry($requestHandler) as $member) { |
|
| 134 | /** @var ReflectionAttribute<Operation> $annotation */ |
||
| 135 | 3 | foreach ($member->getAttributes(Operation::class, ReflectionAttribute::IS_INSTANCEOF) as $annotation) { |
|
| 136 | 3 | $operation = array_replace_recursive($operation, $annotation->newInstance()->value); |
|
| 137 | } |
||
| 138 | } |
||
| 139 | |||
| 140 | 3 | array_walk_recursive($operation, function (mixed &$value) use ($requestHandler): void { |
|
| 141 | 3 | if ($value instanceof Type) { |
|
| 142 | 3 | $value = $this->openApiPhpTypeSchemaResolverManager->resolvePhpTypeSchema($value, $requestHandler); |
|
| 143 | } |
||
| 144 | 3 | }); |
|
| 145 | |||
| 146 | 3 | $this->openApiOperationEnricherManager->enrichOperation($route, $requestHandler, $operation); |
|
| 147 | |||
| 148 | 3 | $operation['operationId'] = $route->getName(); |
|
| 149 | 3 | $operation['tags'] = $route->getTags(); |
|
| 150 | 3 | $operation['summary'] = $route->getSummary(); |
|
| 151 | 3 | $operation['description'] = $route->getDescription(); |
|
| 152 | |||
| 153 | 3 | if ($route->isDeprecated()) { |
|
| 154 | 3 | $operation['deprecated'] = true; |
|
| 155 | } |
||
| 156 | |||
| 157 | 3 | $path = RouteSimplifier::simplifyRoute($route->getPath()); |
|
| 158 | 3 | foreach ($route->getMethods() as $method) { |
|
| 159 | 3 | $document['paths'][$path][strtolower($method)] = $operation; |
|
| 160 | } |
||
| 161 | } |
||
| 162 | } |
||
| 163 |