From 6395e1d80962ec842228c126ce048593dc3ed248 Mon Sep 17 00:00:00 2001 From: Andrej Jursa Date: Thu, 4 Dec 2025 11:32:21 +0100 Subject: [PATCH 1/3] PHP 8.4 support and code refactoring using rector --- CHANGELOG.md | 2 ++ composer.json | 3 +- rector.php | 31 +++++++++++++++++ src/Api.php | 3 -- src/ApiDecider.php | 10 +++--- .../ApiAuthorizationInterface.php | 4 --- src/Authorization/BasicAuthentication.php | 2 +- .../BearerTokenAuthorization.php | 8 ++--- .../CookieApiKeyAuthentication.php | 1 + .../HeaderApiKeyAuthentication.php | 1 + .../QueryApiKeyAuthentication.php | 1 + src/Authorization/TokenAuthorization.php | 14 +++----- src/Component/ApiConsoleControl.php | 4 ++- src/Component/ApiListingControl.php | 3 +- .../DefaultApiConsoleFormFactory.php | 1 + src/EndpointIdentifier.php | 4 ++- src/Error/DefaultErrorHandler.php | 21 +++++------- src/Handlers/ApiHandlerInterface.php | 8 ----- src/Handlers/ApiListingHandler.php | 8 +---- src/Handlers/BaseHandler.php | 7 ++-- src/Handlers/CorsPreflightHandler.php | 1 + src/Handlers/OpenApiHandler.php | 34 +++++++++++++++---- src/Link/ApiLink.php | 3 -- src/Link/ApiLinkMacro.php | 1 + src/Misc/ConsoleRequest.php | 26 ++++++++------ src/Misc/ConsoleResponse.php | 2 ++ src/Misc/IpDetector.php | 1 + src/Misc/IpDetectorInterface.php | 2 -- src/Misc/OpenApiTransform.php | 2 ++ src/Misc/StaticIpDetector.php | 2 -- src/Misc/StaticTokenRepository.php | 5 +-- src/Misc/TokenRepositoryInterface.php | 5 --- src/Output/AbstractOutput.php | 4 +-- src/Output/Configurator/EnvConfigurator.php | 3 ++ src/Output/Configurator/QueryConfigurator.php | 2 ++ src/Output/JsonOutput.php | 1 + src/Output/RedirectOutput.php | 2 ++ src/Params/CookieInputParam.php | 3 +- src/Params/FileInputParam.php | 2 ++ src/Params/GetInputParam.php | 1 + src/Params/InputParam.php | 21 ++++++++---- src/Params/JsonInputParam.php | 3 ++ src/Params/ParamsProcessor.php | 1 + src/Params/PostInputParam.php | 1 + src/Presenters/ApiPresenter.php | 22 ++++++++---- src/Response/JsonApiResponse.php | 1 + src/Response/ResponseInterface.php | 2 -- src/Response/TextApiResponse.php | 1 - src/Response/XmlApiResponse.php | 1 + src/Validation/JsonSchemaValidator.php | 4 +-- tests/Handler/TestHandler.php | 1 + 51 files changed, 177 insertions(+), 119 deletions(-) create mode 100644 rector.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bfbecd..69769e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles. ## [Unreleased][unreleased] +### Changed +* Code refactored to support PHP 7.1 to 8.4 ## 3.1.0 diff --git a/composer.json b/composer.json index 9c52e1f..066b3ff 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,8 @@ "latte/latte": "^2.4 | ^3.0", "phpunit/phpunit": ">7.0 <10.0", "symfony/yaml": "^4.4|^5.0|^6.0", - "squizlabs/php_codesniffer": "^3.2" + "squizlabs/php_codesniffer": "^3.2", + "rector/rector": "^2.2" }, "autoload": { "psr-4": { diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..7a88e2d --- /dev/null +++ b/rector.php @@ -0,0 +1,31 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withSkip([ + __DIR__ . '/vendor', + \Rector\CodingStyle\Rector\FuncCall\StrictArraySearchRector::class, + \Rector\DeadCode\Rector\FunctionLike\NarrowWideUnionReturnTypeRector::class, + \Rector\CodingStyle\Rector\String_\SimplifyQuoteEscapeRector::class, + \Rector\DeadCode\Rector\Cast\RecastingRemovalRector::class, + \Rector\DeadCode\Rector\Assign\RemoveUnusedVariableAssignRector::class, + \Rector\CodingStyle\Rector\If_\NullableCompareToNullRector::class, + \Rector\CodingStyle\Rector\Assign\SplitDoubleAssignRector::class, + \Rector\DeadCode\Rector\PropertyProperty\RemoveNullPropertyInitializationRector::class, + \Rector\EarlyReturn\Rector\StmtsAwareInterface\ReturnEarlyIfVariableRector::class, + ]) + ->withSets([ + LevelSetList::UP_TO_PHP_71, + SetList::DEAD_CODE, + SetList::CODING_STYLE, + SetList::EARLY_RETURN, + ]); \ No newline at end of file diff --git a/src/Api.php b/src/Api.php index 5c53491..0abd9a1 100644 --- a/src/Api.php +++ b/src/Api.php @@ -20,10 +20,7 @@ class Api private $rateLimit; /** - * @param EndpointInterface $endpoint * @param ApiHandlerInterface|string $handler - * @param ApiAuthorizationInterface $authorization - * @param RateLimitInterface|null $rateLimit */ public function __construct( EndpointInterface $endpoint, diff --git a/src/ApiDecider.php b/src/ApiDecider.php index 541bd2e..c6b0c1a 100644 --- a/src/ApiDecider.php +++ b/src/ApiDecider.php @@ -34,9 +34,6 @@ public function __construct(Container $container) * Get api handler that match input method, version, package and apiAction. * If decider cannot find handler for given handler, returns defaults. * - * @param string $method - * @param string $version - * @param string $package * @param string $apiAction * * @return Api @@ -54,10 +51,12 @@ public function getApi(string $method, string $version, string $package, ?string $handler->setEndpointIdentifier($endpointIdentifier); return new Api($api->getEndpoint(), $handler, $api->getAuthorization(), $api->getRateLimit()); } + if ($method === 'OPTIONS' && $this->globalPreflightHandler && $identifier->getVersion() === $version && $identifier->getPackage() === $package && $identifier->getApiAction() === $apiAction) { return new Api(new EndpointIdentifier('OPTIONS', $version, $package, $apiAction), $this->globalPreflightHandler, new NoAuthorization()); } } + return new Api(new EndpointIdentifier($method, $version, $package, $apiAction), new DefaultHandler(), new NoAuthorization()); } @@ -66,17 +65,15 @@ public function enableGlobalPreflight(CorsPreflightHandlerInterface $corsHandler if (!$corsHandler) { $corsHandler = new CorsPreflightHandler(new Response()); } + $this->globalPreflightHandler = $corsHandler; } /** * Register new api handler * - * @param EndpointInterface $endpointIdentifier * @param ApiHandlerInterface|string $handler - * @param ApiAuthorizationInterface $apiAuthorization * @param RateLimitInterface|null $rateLimit - * @return self */ public function addApi(EndpointInterface $endpointIdentifier, $handler, ApiAuthorizationInterface $apiAuthorization, RateLimitInterface $rateLimit = null): self { @@ -96,6 +93,7 @@ public function getApis(): array $handler = $this->getHandler($api); $apis[] = new Api($api->getEndpoint(), $handler, $api->getAuthorization(), $api->getRateLimit()); } + return $apis; } diff --git a/src/Authorization/ApiAuthorizationInterface.php b/src/Authorization/ApiAuthorizationInterface.php index db37947..8dc12cf 100644 --- a/src/Authorization/ApiAuthorizationInterface.php +++ b/src/Authorization/ApiAuthorizationInterface.php @@ -8,16 +8,12 @@ interface ApiAuthorizationInterface { /** * Main method to check if this authorization authorize actual request. - * - * @return boolean */ public function authorized(): bool; /** * If authorization deny acces, this method should provide additional information * abount cause of restriction. - * - * @return string|null */ public function getErrorMessage(): ?string; } diff --git a/src/Authorization/BasicAuthentication.php b/src/Authorization/BasicAuthentication.php index e34a225..33531ae 100644 --- a/src/Authorization/BasicAuthentication.php +++ b/src/Authorization/BasicAuthentication.php @@ -16,7 +16,6 @@ class BasicAuthentication implements ApiAuthorizationInterface /** * @param array $autentications - available username - password pairs - * @param IRequest $httpRequest */ public function __construct(array $autentications, IRequest $httpRequest) { @@ -34,6 +33,7 @@ public function authorized(): bool if (!$authentication) { return false; } + return $authentication === $urlScript->getPassword(); } diff --git a/src/Authorization/BearerTokenAuthorization.php b/src/Authorization/BearerTokenAuthorization.php index a5cd14b..536482b 100644 --- a/src/Authorization/BearerTokenAuthorization.php +++ b/src/Authorization/BearerTokenAuthorization.php @@ -11,9 +11,6 @@ class BearerTokenAuthorization extends TokenAuthorization { /** * BearerTokenAuthorization constructor. - * - * @param TokenRepositoryInterface $tokenRepository - * @param IpDetectorInterface $ipDetector */ public function __construct(TokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector) { @@ -23,8 +20,6 @@ public function __construct(TokenRepositoryInterface $tokenRepository, IpDetecto /** * Read HTTP reader with authorization token * If everything is ok, it return token. In other situations returns false and set errorMessage. - * - * @return string|null */ protected function readAuthorizationToken(): ?string { @@ -32,15 +27,18 @@ protected function readAuthorizationToken(): ?string $this->errorMessage = 'Authorization header HTTP_Authorization is not set'; return null; } + $parts = explode(' ', $_SERVER['HTTP_AUTHORIZATION']); if (count($parts) !== 2) { $this->errorMessage = 'Authorization header contains invalid structure'; return null; } + if (strtolower($parts[0]) !== 'bearer') { $this->errorMessage = 'Authorization header doesn\'t contain bearer token'; return null; } + return $parts[1]; } } diff --git a/src/Authorization/CookieApiKeyAuthentication.php b/src/Authorization/CookieApiKeyAuthentication.php index 4b95051..69bd1bb 100644 --- a/src/Authorization/CookieApiKeyAuthentication.php +++ b/src/Authorization/CookieApiKeyAuthentication.php @@ -24,6 +24,7 @@ protected function readAuthorizationToken(): ?string $this->errorMessage = 'API key is not set'; return null; } + return $apiKey; } diff --git a/src/Authorization/HeaderApiKeyAuthentication.php b/src/Authorization/HeaderApiKeyAuthentication.php index 0843ddc..4693944 100644 --- a/src/Authorization/HeaderApiKeyAuthentication.php +++ b/src/Authorization/HeaderApiKeyAuthentication.php @@ -25,6 +25,7 @@ protected function readAuthorizationToken(): ?string $this->errorMessage = 'API key is not set'; return null; } + return $apiKey; } diff --git a/src/Authorization/QueryApiKeyAuthentication.php b/src/Authorization/QueryApiKeyAuthentication.php index 43a9ff4..22c6b52 100644 --- a/src/Authorization/QueryApiKeyAuthentication.php +++ b/src/Authorization/QueryApiKeyAuthentication.php @@ -24,6 +24,7 @@ protected function readAuthorizationToken(): ?string $this->errorMessage = 'API key is not set'; return null; } + return $apiKey; } diff --git a/src/Authorization/TokenAuthorization.php b/src/Authorization/TokenAuthorization.php index 6378f7d..f6a5e97 100644 --- a/src/Authorization/TokenAuthorization.php +++ b/src/Authorization/TokenAuthorization.php @@ -24,10 +24,6 @@ abstract class TokenAuthorization implements ApiAuthorizationInterface */ protected $ipDetector; - /** - * @param TokenRepositoryInterface $tokenRepository - * @param IpDetectorInterface $ipDetector - */ public function __construct(TokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector) { $this->tokenRepository = $tokenRepository; @@ -74,17 +70,17 @@ public function getErrorMessage(): ?string * '127.0.0.1,127.0.02' - accessible from multiple IP, separator could be new line or space * '127.0.0.1/32' - accessible from ip range * null - disabled access - * - * @return boolean */ private function isValidIp(?string $ipRestrictions): bool { if ($ipRestrictions === null) { return false; } + if ($ipRestrictions === '*' || $ipRestrictions === '') { return true; } + $ip = $this->ipDetector->getRequestIp(); $ipWhiteList = str_replace([',', ' ', "\n"], '#', $ipRestrictions); @@ -93,6 +89,7 @@ private function isValidIp(?string $ipRestrictions): bool if ($whiteIp === $ip) { return true; } + if (strpos($whiteIp, '/') !== false) { return $this->ipInRange($ip, $whiteIp); } @@ -106,14 +103,13 @@ private function isValidIp(?string $ipRestrictions): bool * * @param string $ip this ip will be verified * @param string $range is in IP/CIDR format eg 127.0.0.1/24 - * @return boolean */ private function ipInRange(string $ip, string $range): bool { - list($range, $netmask) = explode('/', $range, 2); + [$range, $netmask] = explode('/', $range, 2); $range_decimal = ip2long($range); $ipDecimal = ip2long($ip); - $wildcard_decimal = pow(2, (32 - (int)$netmask)) - 1; + $wildcard_decimal = 2 ** (32 - (int)$netmask) - 1; $netmask_decimal = ~ $wildcard_decimal; return (($ipDecimal & $netmask_decimal) === ($range_decimal & $netmask_decimal)); } diff --git a/src/Component/ApiConsoleControl.php b/src/Component/ApiConsoleControl.php index c35d67d..bf1432c 100644 --- a/src/Component/ApiConsoleControl.php +++ b/src/Component/ApiConsoleControl.php @@ -63,7 +63,7 @@ protected function createComponentConsoleForm(): Form { $form = $this->formFactory->create($this->request, $this->endpoint, $this->handler, $this->authorization, $this->apiLink); $form->setRenderer($this->getFormRenderer()); - $form->onSuccess[] = array($this, 'formSucceeded'); + $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } @@ -142,8 +142,10 @@ private function filterFormValues(array $values): array if ($values['do_not_send_empty_value_for_' . $key] === true && $values[$key] === '') { unset($values[$key]); } + unset($values['do_not_send_empty_value_for_' . $key]); } + return $values; } } diff --git a/src/Component/ApiListingControl.php b/src/Component/ApiListingControl.php index 4965a98..66ddf3e 100644 --- a/src/Component/ApiListingControl.php +++ b/src/Component/ApiListingControl.php @@ -44,7 +44,6 @@ public function handleSelect(string $method, $version, string $package, ?string /** * @param Api[] $handlers - * @return array */ private function groupApis(array $handlers): array { @@ -54,8 +53,10 @@ private function groupApis(array $handlers): array if (!isset($versionHandlers[$endPoint->getVersion()])) { $versionHandlers[$endPoint->getVersion()] = []; } + $versionHandlers[$endPoint->getVersion()][] = $handler; } + return $versionHandlers; } diff --git a/src/Component/DefaultApiConsoleFormFactory.php b/src/Component/DefaultApiConsoleFormFactory.php index 7c48715..0f42e04 100644 --- a/src/Component/DefaultApiConsoleFormFactory.php +++ b/src/Component/DefaultApiConsoleFormFactory.php @@ -66,6 +66,7 @@ protected function getUrl(IRequest $request, EndpointInterface $endpoint, ?ApiLi if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { $scheme = $_SERVER['HTTP_X_FORWARDED_PROTO']; } + $port = ''; if ($uri->scheme === 'http' && $uri->port !== 80) { $port = ':' . $uri->port; diff --git a/src/EndpointIdentifier.php b/src/EndpointIdentifier.php index 43fae40..c3631b6 100644 --- a/src/EndpointIdentifier.php +++ b/src/EndpointIdentifier.php @@ -29,6 +29,7 @@ public function __construct(string $method, $version, string $package, ?string $ if (strpos($version, '/') !== false) { throw new InvalidArgumentException('Version must have semantic numbering. For example "1", "1.1", "0.13.2" etc.'); } + $this->version = $version; $this->package = $package; $this->apiAction = $apiAction; @@ -54,11 +55,12 @@ public function getApiAction(): ?string if ($this->apiAction === '') { return null; } + return $this->apiAction; } public function getUrl(): string { - return "v{$this->version}/{$this->package}/{$this->apiAction}"; + return sprintf('v%s/%s/%s', $this->version, $this->package, $this->apiAction); } } diff --git a/src/Error/DefaultErrorHandler.php b/src/Error/DefaultErrorHandler.php index 9dcb512..4d5d87f 100644 --- a/src/Error/DefaultErrorHandler.php +++ b/src/Error/DefaultErrorHandler.php @@ -25,21 +25,19 @@ public function handle(Throwable $exception, array $params): JsonApiResponse { Debugger::log($exception, Debugger::EXCEPTION); if ($this->outputConfigurator->showErrorDetail()) { - $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $exception->getMessage()]); - } else { - $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error']); + return new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $exception->getMessage()]); } - return $response; + + return new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error']); } public function handleInputParams(array $errors): JsonApiResponse { if ($this->outputConfigurator->showErrorDetail()) { - $response = new JsonApiResponse(Response::S400_BAD_REQUEST, ['status' => 'error', 'message' => 'wrong input', 'detail' => $errors]); - } else { - $response = new JsonApiResponse(Response::S400_BAD_REQUEST, ['status' => 'error', 'message' => 'wrong input']); + return new JsonApiResponse(Response::S400_BAD_REQUEST, ['status' => 'error', 'message' => 'wrong input', 'detail' => $errors]); } - return $response; + + return new JsonApiResponse(Response::S400_BAD_REQUEST, ['status' => 'error', 'message' => 'wrong input']); } public function handleSchema(array $errors, array $params): JsonApiResponse @@ -47,11 +45,10 @@ public function handleSchema(array $errors, array $params): JsonApiResponse Debugger::log($errors, Debugger::ERROR); if ($this->outputConfigurator->showErrorDetail()) { - $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $errors]); - } else { - $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error']); + return new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $errors]); } - return $response; + + return new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error']); } public function handleAuthorization(ApiAuthorizationInterface $auth, array $params): JsonApiResponse diff --git a/src/Handlers/ApiHandlerInterface.php b/src/Handlers/ApiHandlerInterface.php index b2ed174..1370f84 100644 --- a/src/Handlers/ApiHandlerInterface.php +++ b/src/Handlers/ApiHandlerInterface.php @@ -13,13 +13,11 @@ interface ApiHandlerInterface { /** * Summary of handler - short description of handler - * @return string */ public function summary(): string; /** * Description of handler - * @return string */ public function description(): string; @@ -32,13 +30,11 @@ public function params(): array; /** * Returns list of tags for handler - * @return array */ public function tags(): array; /** * Marks handler as deprecated - * @return bool */ public function deprecated(): bool; @@ -46,9 +42,7 @@ public function deprecated(): bool; * Main handle method that will be executed when api * endpoint contected with this handler will be triggered * - * @param array $params * - * @return ResponseInterface */ public function handle(array $params): ResponseInterface; @@ -56,9 +50,7 @@ public function handle(array $params): ResponseInterface; * Set actual endpoint identifier to hnadler. * It is neccesary for link creation. * - * @param EndpointInterface $endpoint * - * @return void */ public function setEndpointIdentifier(EndpointInterface $endpoint): void; diff --git a/src/Handlers/ApiListingHandler.php b/src/Handlers/ApiListingHandler.php index 0d0be0e..382310e 100644 --- a/src/Handlers/ApiListingHandler.php +++ b/src/Handlers/ApiListingHandler.php @@ -25,9 +25,6 @@ class ApiListingHandler extends BaseHandler /** * ApiListingHandler constructor. - * - * @param ApiDecider $apiDecider - * @param ApiLink $apiLink */ public function __construct(ApiDecider $apiDecider, ApiLink $apiLink) { @@ -49,9 +46,7 @@ public function handle(array $params): ResponseInterface /** * Create handler list for specified version * - * @param integer $version * - * @return array */ private function getApiList(string $version): array { @@ -75,9 +70,7 @@ private function getApiList(string $version): array /** * Create array with params for specified handler * - * @param ApiHandlerInterface $handler * - * @return array */ private function createParamsList(ApiHandlerInterface $handler): array { @@ -90,6 +83,7 @@ private function createParamsList(ApiHandlerInterface $handler): array if ($param->getAvailableValues()) { $parameter['available_values'] = $param->getAvailableValues(); } + return $parameter; }, $handler->params()); } diff --git a/src/Handlers/BaseHandler.php b/src/Handlers/BaseHandler.php index 727fc7e..bb895a0 100644 --- a/src/Handlers/BaseHandler.php +++ b/src/Handlers/BaseHandler.php @@ -87,6 +87,7 @@ protected function getFractal(): Manager if (!$this->fractal) { throw new InvalidStateException("Fractal manager isn't initialized. Did you call parent::__construct() in your handler constructor?"); } + return $this->fractal; } @@ -106,9 +107,7 @@ final public function getEndpoint(): ?EndpointInterface /** * Set link generator to handler * - * @param LinkGenerator $linkGenerator * - * @return self */ final public function setupLinkGenerator(LinkGenerator $linkGenerator): self { @@ -119,9 +118,7 @@ final public function setupLinkGenerator(LinkGenerator $linkGenerator): self /** * Create link to actual handler endpoint * - * @param array $params * - * @return string * @throws InvalidLinkException if handler doesn't have linkgenerator or endpoint */ final public function createLink(array $params = []): string @@ -129,9 +126,11 @@ final public function createLink(array $params = []): string if (!$this->linkGenerator) { throw new InvalidStateException("You have setupLinkGenerator for this handler if you want to generate link in this handler"); } + if (!$this->endpoint) { throw new InvalidStateException("You have setEndpoint() for this handler if you want to generate link in this handler"); } + $params = array_merge([ 'version' => $this->endpoint->getVersion(), 'package' => $this->endpoint->getPackage(), diff --git a/src/Handlers/CorsPreflightHandler.php b/src/Handlers/CorsPreflightHandler.php index 1213798..dfa71ac 100644 --- a/src/Handlers/CorsPreflightHandler.php +++ b/src/Handlers/CorsPreflightHandler.php @@ -36,6 +36,7 @@ public function handle(array $params): ResponseInterface $this->response->addHeader($name, $value); } } + return new JsonApiResponse(Response::S200_OK, []); } } diff --git a/src/Handlers/OpenApiHandler.php b/src/Handlers/OpenApiHandler.php index 2cd69d8..3e567af 100644 --- a/src/Handlers/OpenApiHandler.php +++ b/src/Handlers/OpenApiHandler.php @@ -46,9 +46,6 @@ class OpenApiHandler extends BaseHandler /** * OpenApiHandler constructor. - * @param ApiDecider $apiDecider - * @param ApiLink $apiLink - * @param Request $request * @param array $initData - structured data for initialization response */ public function __construct( @@ -70,6 +67,7 @@ public function params(): array if (class_exists(Yaml::class)) { $availableFormats[] = 'yaml'; } + return [ (new GetInputParam('format'))->setAvailableValues($availableFormats)->setDescription('Response format'), ]; @@ -113,6 +111,7 @@ public function handle(array $params): ResponseInterface ]; continue; } + if ($authorization instanceof BearerTokenAuthorization) { $securitySchemes['Bearer'] = [ 'type' => 'http', @@ -120,6 +119,7 @@ public function handle(array $params): ResponseInterface ]; continue; } + if ($authorization instanceof QueryApiKeyAuthentication) { $queryParamName = $authorization->getQueryParamName(); $securitySchemes[$this->normalizeSecuritySchemeName('query', $queryParamName)] = [ @@ -129,6 +129,7 @@ public function handle(array $params): ResponseInterface ]; continue; } + if ($authorization instanceof HeaderApiKeyAuthentication) { $headerName = $authorization->getHeaderName(); $securitySchemes[$this->normalizeSecuritySchemeName('header', $headerName)] = [ @@ -138,6 +139,7 @@ public function handle(array $params): ResponseInterface ]; continue; } + if ($authorization instanceof CookieApiKeyAuthentication) { $cookieName = $authorization->getCookieName(); $securitySchemes[$this->normalizeSecuritySchemeName('cookie', $cookieName)] = [ @@ -224,6 +226,7 @@ public function handle(array $params): ResponseInterface if ($params['format'] === 'yaml') { return new TextApiResponse(IResponse::S200_OK, Yaml::dump($data, PHP_INT_MAX, 2, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE)); } + return new JsonApiResponse(IResponse::S200_OK, $data); } @@ -236,9 +239,6 @@ private function getApis(string $version): array /** * @param Api[] $versionApis - * @param string $baseUrl - * @param string $basePath - * @return array * @throws InvalidLinkException */ private function getPaths(array $versionApis, string $baseUrl, string $basePath): array @@ -333,6 +333,7 @@ private function getPaths(array $versionApis, string $baseUrl, string $basePath) ]; $responses[$output->getCode()]['content']['application/json; charset=utf-8']['schema']['oneOf'][] = $tmp; } + $responses[$output->getCode()]['content']['application/json; charset=utf-8']['schema']['oneOf'][] = $schema; } } @@ -391,9 +392,11 @@ private function getPaths(array $versionApis, string $baseUrl, string $basePath) ], ]; } + $settings['responses'] = $responses; $list[$path][strtolower($api->getEndpoint()->getMethod())] = $settings; } + return $list; } @@ -403,6 +406,7 @@ private function getBasePath(array $apis, string $baseUrl): string foreach ($apis as $handler) { $basePath = $this->getLongestCommonSubstring($basePath, $this->apiLink->link($handler->getEndpoint())); } + return rtrim(str_replace($baseUrl, '', $basePath), '/'); } @@ -411,21 +415,23 @@ private function getLongestCommonSubstring($path1, $path2) if ($path1 === null) { return $path2; } + $commonSubstring = ''; $shortest = min(strlen($path1), strlen($path2)); for ($i = 0; $i <= $shortest; ++$i) { if (substr($path1, 0, $i) !== substr($path2, 0, $i)) { break; } + $commonSubstring = substr($path1, 0, $i); } + return $commonSubstring; } /** * Create array with params for specified handler * - * @param ApiHandlerInterface $handler * * @return array */ @@ -449,10 +455,12 @@ private function createParamsList(ApiHandlerInterface $handler) if ($param->isMulti()) { $schema['items'] = ['type' => 'string']; } + $descriptionParts = []; if ($param->getDescription()) { $descriptionParts[] = $param->getDescription(); } + $availableValues = $param->getAvailableValues(); if ($availableValues) { $schema['enum'] = array_keys($availableValues); @@ -462,6 +470,7 @@ private function createParamsList(ApiHandlerInterface $handler) } } } + $parameter['schema'] = $schema; if ($descriptionParts !== []) { $parameter['description'] = implode("\n", $descriptionParts); @@ -473,6 +482,7 @@ private function createParamsList(ApiHandlerInterface $handler) $parameters[] = $parameter; } + return $parameters; } @@ -504,6 +514,7 @@ private function createRequestBody(ApiHandlerInterface $handler) 'schema' => $this->transformSchema($schema), ]; } + if ($param instanceof RawInputParam) { $schema = [ 'type' => 'string', @@ -515,12 +526,14 @@ private function createRequestBody(ApiHandlerInterface $handler) $schema['examples'] = $examples; } } + $result['description'] = $result['description'] ?? $param->getDescription(); $result['required'] = $result['required'] ?? $param->isRequired(); $result['content']['text/plain'] = [ 'schema' => $schema, ]; } + if ($param->getType() === InputParam::TYPE_POST || $param->getType() === InputParam::TYPE_PUT) { $property = [ 'type' => $param->isMulti() ? 'array' : 'string', @@ -528,10 +541,12 @@ private function createRequestBody(ApiHandlerInterface $handler) if ($param->isMulti()) { $property['items'] = ['type' => 'string']; } + $descriptionParts = []; if ($param->getDescription()) { $descriptionParts[] = $param->getDescription(); } + $availableValues = $param->getAvailableValues(); if ($availableValues) { $property['enum'] = array_keys($availableValues); @@ -603,9 +618,11 @@ private function createIn($type) if ($type == InputParam::TYPE_GET) { return 'query'; } + if ($type == InputParam::TYPE_COOKIE) { return 'cookie'; } + return 'body'; } @@ -617,8 +634,10 @@ private function transformSchema(array $schema) foreach ($schema['definitions'] as $name => $definition) { $this->addDefinition($name, $this->transformSchema($definition)); } + unset($schema['definitions']); } + return json_decode(str_replace('#/definitions/', '#/components/schemas/', json_encode($schema, JSON_UNESCAPED_SLASHES)), true); } @@ -627,6 +646,7 @@ private function addDefinition($name, $definition) if (isset($this->definitions[$name]) && $this->definitions[$name] !== $definition) { throw new InvalidArgumentException('Definition with name ' . $name . ' already exists. Rename it.'); } + $this->definitions[$name] = $definition; } diff --git a/src/Link/ApiLink.php b/src/Link/ApiLink.php index c9a8e1b..288d0b7 100644 --- a/src/Link/ApiLink.php +++ b/src/Link/ApiLink.php @@ -15,8 +15,6 @@ class ApiLink /** * Create ApiLink - * - * @param LinkGenerator $linkGenerator */ public function __construct(LinkGenerator $linkGenerator) { @@ -26,7 +24,6 @@ public function __construct(LinkGenerator $linkGenerator) /** * Create link to specified api endpoint * - * @param EndpointInterface $endpoint * @param array $params * * @return string diff --git a/src/Link/ApiLinkMacro.php b/src/Link/ApiLinkMacro.php index 2e63f7b..45e56d8 100644 --- a/src/Link/ApiLinkMacro.php +++ b/src/Link/ApiLinkMacro.php @@ -32,6 +32,7 @@ public static function start(MacroNode $node, PhpWriter $writer) if (!Debugger::$productionMode) { throw new InvalidLinkException($message); } + Debugger::log($message, Debugger::EXCEPTION); return ''; } diff --git a/src/Misc/ConsoleRequest.php b/src/Misc/ConsoleRequest.php index f9ab891..caad265 100644 --- a/src/Misc/ConsoleRequest.php +++ b/src/Misc/ConsoleRequest.php @@ -31,7 +31,7 @@ public function __construct(ApiHandlerInterface $handler, ?EndpointInterface $en public function makeRequest(string $url, string $method, array $values, array $additionalValues = [], ?string $token = null): ConsoleResponse { - list($postFields, $getFields, $cookieFields, $rawPost, $putFields) = $this->processValues($values); + [$postFields, $getFields, $cookieFields, $rawPost, $putFields] = $this->processValues($values); $postFields = array_merge($postFields, $additionalValues['postFields'] ?? []); $getFields = array_merge($getFields, $additionalValues['getFields'] ?? []); @@ -44,10 +44,10 @@ public function makeRequest(string $url, string $method, array $values, array $a if ($this->endpoint && $this->apiLink) { $url = $this->apiLink->link($this->endpoint, $getFields); - } elseif (count($getFields)) { + } elseif ($getFields !== []) { $parts = []; foreach ($getFields as $key => $value) { - $parts[] = "$key=$value"; + $parts[] = sprintf('%s=%s', $key, $value); } $parsedUrl = parse_url($url); @@ -56,11 +56,12 @@ public function makeRequest(string $url, string $method, array $values, array $a } $putRawPost = null; - if (count($putFields)) { + if ($putFields !== []) { $parts = []; foreach ($putFields as $key => $value) { - $parts[] = "$key=$value"; + $parts[] = sprintf('%s=%s', $key, $value); } + $putRawPost = implode('&', $parts); } @@ -82,16 +83,19 @@ public function makeRequest(string $url, string $method, array $values, array $a } $headers = $additionalValues['headers'] ?? []; - if (count($cookieFields)) { + if ($cookieFields !== []) { $parts = []; foreach ($cookieFields as $key => $value) { - $parts[] = "$key=$value"; + $parts[] = sprintf('%s=%s', $key, $value); } + $headers[] = "Cookie: " . implode('&', $parts); } + if ($token !== null && $token !== false) { $headers[] = 'Authorization: Bearer ' . $token; } + if (count($headers)) { curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); } @@ -99,7 +103,7 @@ public function makeRequest(string $url, string $method, array $values, array $a $basicAuthUsername = $values['basic_authentication_username'] ?? null; $basicAuthPassword = $values['basic_authentication_password'] ?? null; if ($basicAuthUsername && $basicAuthPassword) { - curl_setopt($curl, CURLOPT_USERPWD, "$basicAuthUsername:$basicAuthPassword"); + curl_setopt($curl, CURLOPT_USERPWD, sprintf('%s:%s', $basicAuthUsername, $basicAuthPassword)); } $consoleResponse = new ConsoleResponse( @@ -138,16 +142,14 @@ public function makeRequest(string $url, string $method, array $values, array $a /** * Process given values to POST and GET fields * - * @param array $values * - * @return array */ private function processValues(array $values): array { $params = $this->handler->params(); $postFields = []; - $rawPost = isset($values['post_raw']) ? $values['post_raw'] : null; + $rawPost = $values['post_raw'] ?? null; $getFields = []; $putFields = []; $cookieFields = []; @@ -219,6 +221,7 @@ private function processParam(ParamInterface $param, string $key, $value) return $valueData; } + return null; } @@ -237,6 +240,7 @@ private function normalizeValues(array $values): array } } } + return $result; } } diff --git a/src/Misc/ConsoleResponse.php b/src/Misc/ConsoleResponse.php index e231ef9..1eb858e 100644 --- a/src/Misc/ConsoleResponse.php +++ b/src/Misc/ConsoleResponse.php @@ -117,10 +117,12 @@ public function getFormattedJsonBody(): string if ($body === null) { return ''; } + $decoded = json_decode($body); if ($decoded) { $body = json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } + return $body; } diff --git a/src/Misc/IpDetector.php b/src/Misc/IpDetector.php index 36bea48..e37b780 100644 --- a/src/Misc/IpDetector.php +++ b/src/Misc/IpDetector.php @@ -20,6 +20,7 @@ public function getRequestIp(): string } else { $ip = 'cli'; } + return $ip; } } diff --git a/src/Misc/IpDetectorInterface.php b/src/Misc/IpDetectorInterface.php index 806fd11..a5ea8c8 100644 --- a/src/Misc/IpDetectorInterface.php +++ b/src/Misc/IpDetectorInterface.php @@ -8,8 +8,6 @@ interface IpDetectorInterface { /** * Get actual request IP. - * - * @return string */ public function getRequestIp(): string; } diff --git a/src/Misc/OpenApiTransform.php b/src/Misc/OpenApiTransform.php index 6afbfdf..c3993e8 100644 --- a/src/Misc/OpenApiTransform.php +++ b/src/Misc/OpenApiTransform.php @@ -17,12 +17,14 @@ public static function transformTypes(array &$schema, $parent = null): void unset($value[array_search('null', $value)]); $schema['nullable'] = true; } + if (count($value) === 1) { $value = implode(',', $value); } elseif (count($value) > 1) { foreach ($schema['type'] as $type) { $schema['oneOf'][] = ['type' => $type]; } + unset($schema['type']); } } elseif (is_array($value)) { diff --git a/src/Misc/StaticIpDetector.php b/src/Misc/StaticIpDetector.php index 2287b33..bf7150a 100644 --- a/src/Misc/StaticIpDetector.php +++ b/src/Misc/StaticIpDetector.php @@ -12,8 +12,6 @@ class StaticIpDetector implements IpDetectorInterface /** * Create Static Ip Detector * Ip that will be in constructor will return as actual request IP. - * - * @param string $ip */ public function __construct(string $ip) { diff --git a/src/Misc/StaticTokenRepository.php b/src/Misc/StaticTokenRepository.php index a9aa642..13d6402 100644 --- a/src/Misc/StaticTokenRepository.php +++ b/src/Misc/StaticTokenRepository.php @@ -47,9 +47,6 @@ public function validToken(string $token): bool */ public function ipRestrictions(string $token): ?string { - if (isset($this->validTokens[$token])) { - return $this->validTokens[$token]; - } - return null; + return $this->validTokens[$token] ?? null; } } diff --git a/src/Misc/TokenRepositoryInterface.php b/src/Misc/TokenRepositoryInterface.php index 5615718..2ca735e 100644 --- a/src/Misc/TokenRepositoryInterface.php +++ b/src/Misc/TokenRepositoryInterface.php @@ -8,9 +8,6 @@ interface TokenRepositoryInterface { /** * Return true if token is valid, otherwise return false - * - * @param string $token - * @return bool */ public function validToken(string $token): bool; @@ -23,9 +20,7 @@ public function validToken(string $token): bool; * '156.26.252/32' - access from ip range * false - if token doesn't exists * - * @param string $token * - * @return string|null */ public function ipRestrictions(string $token): ?string; } diff --git a/src/Output/AbstractOutput.php b/src/Output/AbstractOutput.php index ed2a0f2..abd154a 100644 --- a/src/Output/AbstractOutput.php +++ b/src/Output/AbstractOutput.php @@ -32,7 +32,6 @@ public function getDescription(): string /** * @param string $name Example name * @param mixed $example Example - * @return Self */ public function addExample(string $name, $example): self { @@ -43,7 +42,6 @@ public function addExample(string $name, $example): self /** * Set default example * @param mixed $example - * @return self * @deprecated Use addExample instead */ public function setExample($example): self @@ -61,12 +59,12 @@ public function getExample() if (empty($this->examples)) { return null; } + return reset($this->examples); } /** * Returns all examples - * @return array */ public function getExamples(): array { diff --git a/src/Output/Configurator/EnvConfigurator.php b/src/Output/Configurator/EnvConfigurator.php index 51d07f4..8dc5cf9 100644 --- a/src/Output/Configurator/EnvConfigurator.php +++ b/src/Output/Configurator/EnvConfigurator.php @@ -7,6 +7,7 @@ class EnvConfigurator implements ConfiguratorInterface { private $envVariable = 'APP_ENV'; + private $productionValue = 'production'; /** @@ -25,6 +26,7 @@ public function validateSchema(): bool if ($appEnv === $this->productionValue) { return false; } + return true; } @@ -34,6 +36,7 @@ public function showErrorDetail(): bool if ($appEnv === $this->productionValue) { return false; } + return true; } } diff --git a/src/Output/Configurator/QueryConfigurator.php b/src/Output/Configurator/QueryConfigurator.php index 0a4fba1..805752b 100644 --- a/src/Output/Configurator/QueryConfigurator.php +++ b/src/Output/Configurator/QueryConfigurator.php @@ -9,7 +9,9 @@ class QueryConfigurator implements ConfiguratorInterface { private $schemaValidateParam = 'schema_validate'; + private $errorDetailParam = 'error_detail'; + public $request = null; /** diff --git a/src/Output/JsonOutput.php b/src/Output/JsonOutput.php index 38bcf68..eabac04 100644 --- a/src/Output/JsonOutput.php +++ b/src/Output/JsonOutput.php @@ -25,6 +25,7 @@ public function validate(ResponseInterface $response): ValidationResultInterface if (!$response instanceof JsonApiResponse) { return new ValidationResult(ValidationResult::STATUS_ERROR); } + if ($this->code !== $response->getCode()) { return new ValidationResult(ValidationResult::STATUS_ERROR, ['Response code doesn\'t match']); } diff --git a/src/Output/RedirectOutput.php b/src/Output/RedirectOutput.php index 0613c75..c38e4d2 100644 --- a/src/Output/RedirectOutput.php +++ b/src/Output/RedirectOutput.php @@ -16,9 +16,11 @@ public function validate(ResponseInterface $response): ValidationResultInterface if (!$response instanceof RedirectResponse) { return new ValidationResult(ValidationResult::STATUS_ERROR); } + if ($this->code !== $response->getCode()) { return new ValidationResult(ValidationResult::STATUS_ERROR, ['Response code doesn\'t match']); } + return new ValidationResult(ValidationResult::STATUS_OK); } } diff --git a/src/Params/CookieInputParam.php b/src/Params/CookieInputParam.php index acdd90f..6691c18 100644 --- a/src/Params/CookieInputParam.php +++ b/src/Params/CookieInputParam.php @@ -13,7 +13,8 @@ public function getValue() if (!filter_has_var(INPUT_COOKIE, $this->key) && isset($_COOKIE[$this->key])) { return $_COOKIE[$this->key]; } + $value = filter_input(INPUT_COOKIE, $this->key); - return $value !== null ? $value : $this->default; + return $value ?? $this->default; } } diff --git a/src/Params/FileInputParam.php b/src/Params/FileInputParam.php index 11df2f8..1e06842 100644 --- a/src/Params/FileInputParam.php +++ b/src/Params/FileInputParam.php @@ -21,6 +21,7 @@ public function getValue() if (isset($_FILES[$this->key])) { return $this->isMulti() ? $this->processMultiFileUploads($_FILES[$this->key]) : $_FILES[$this->key]; } + return $this->default; } @@ -32,6 +33,7 @@ private function processMultiFileUploads($files) $result[$index][$key] = $value; } } + return $result; } } diff --git a/src/Params/GetInputParam.php b/src/Params/GetInputParam.php index b6b7a9c..98d17d5 100644 --- a/src/Params/GetInputParam.php +++ b/src/Params/GetInputParam.php @@ -13,6 +13,7 @@ public function getValue() if (!filter_has_var(INPUT_GET, $this->key) && isset($_GET[$this->key])) { return $_GET[$this->key]; } + $value = $this->isMulti() ? filter_input(INPUT_GET, $this->key, FILTER_DEFAULT, FILTER_REQUIRE_ARRAY) : filter_input(INPUT_GET, $this->key); return $value !== null && $value !== false ? $value : $this->default; } diff --git a/src/Params/InputParam.php b/src/Params/InputParam.php index 468520f..6b81776 100644 --- a/src/Params/InputParam.php +++ b/src/Params/InputParam.php @@ -13,14 +13,21 @@ abstract class InputParam implements ParamInterface { const TYPE_POST = 'POST'; + const TYPE_GET = 'GET'; + const TYPE_PUT = 'PUT'; + const TYPE_FILE = 'FILE'; + const TYPE_COOKIE = 'COOKIE'; + const TYPE_POST_RAW = 'POST_RAW'; + const TYPE_POST_JSON = 'POST_JSON'; const OPTIONAL = false; + const REQUIRED = true; /** @var string */ @@ -63,6 +70,7 @@ public function setAvailableValues(array $availableValues): self if ($availableValues === array_values($availableValues)) { $availableValues = array_combine($availableValues, $availableValues); } + $this->availableValues = $availableValues; return $this; } @@ -111,7 +119,6 @@ public function getDescription(): string /** * @param mixed $default - * @return self */ public function setDefault($default): self { @@ -131,7 +138,6 @@ public function getDefault() * Add example, can be used multiple times to add many examples * @param string $name Example name * @param mixed $example Example - * @return Self */ public function addExample(string $name, $example): self { @@ -142,7 +148,6 @@ public function addExample(string $name, $example): self /** * Set default example * @param mixed $example - * @return self * @deprecated Use addExample instead */ public function setExample($example): self @@ -160,12 +165,12 @@ public function getExample() if (empty($this->examples)) { return null; } + return reset($this->examples); } /** * Returns all examples - * @return array */ public function getExamples(): array { @@ -180,16 +185,19 @@ public function updateConsoleForm(Form $form): void if ($this->isMulti()) { $key = $key . '___' . $i; } + $input = $this->addFormInput($form, $key); if ($this->description) { $input->setOption('description', Html::el('div', ['class' => 'param-description'])->setHtml($this->description)); } + if ($this->getExample() || $this->getDefault()) { $default = $this->getExample() ?: $this->getDefault(); $default = is_array($default) ? ($default[$i] ?? null) : $default; $input->setDefaultValue($default); } } + $form->addCheckbox('do_not_send_empty_value_for_' . $this->getKey(), 'Do not send empty value for ' . $this->getLabel()); } @@ -199,6 +207,7 @@ protected function addFormInput(Form $form, string $key): BaseControl return $form->addSelect($key, $this->getParamLabel(), $this->getAvailableValues()) ->setPrompt('Select ' . $this->getLabel()); } + return $form->addText($key, $this->getParamLabel()); } @@ -213,8 +222,8 @@ protected function getParamLabel(): string if ($this->isRequired()) { $title .= ' *'; } - $title .= ' (' . $this->getType() . ')'; - return $title; + + return $title . (' (' . $this->getType() . ')'); } /** diff --git a/src/Params/JsonInputParam.php b/src/Params/JsonInputParam.php index f7ab440..ba94da2 100644 --- a/src/Params/JsonInputParam.php +++ b/src/Params/JsonInputParam.php @@ -36,6 +36,7 @@ public function getValue() if ($input === null) { $input = ''; } + return json_decode($input, true); } @@ -93,6 +94,7 @@ protected function addFormInput(Form $form, string $key): BaseControl HTML; } + $this->description .= <<< HTML