diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlim4ServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlim4ServerCodegen.java index 7e318dee204..dc6fefcee77 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlim4ServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlim4ServerCodegen.java @@ -50,6 +50,8 @@ public class PhpSlim4ServerCodegen extends AbstractPhpCodegen { protected String artifactId = "openapi-server"; protected String authDirName = "Auth"; protected String authPackage = ""; + protected String appDirName = "App"; + protected String appPackage = ""; protected String psr7Implementation = "slim-psr7"; protected String interfacesDirName = "Interfaces"; protected String interfacesPackage = ""; @@ -92,6 +94,7 @@ public class PhpSlim4ServerCodegen extends AbstractPhpCodegen { modelPackage = invokerPackage + "\\" + modelDirName; authPackage = invokerPackage + "\\" + authDirName; interfacesPackage = invokerPackage + "\\" + interfacesDirName; + appPackage = invokerPackage + "\\" + appDirName; outputFolder = "generated-code" + File.separator + "slim4"; modelTestTemplateFiles.put("model_test.mustache", ".php"); @@ -167,6 +170,8 @@ public class PhpSlim4ServerCodegen extends AbstractPhpCodegen { authPackage = invokerPackage + "\\" + authDirName; // Update interfacesPackage interfacesPackage = invokerPackage + "\\" + interfacesDirName; + // update appPackage + appPackage = invokerPackage + "\\" + appDirName; } // make auth src path available in mustache template @@ -178,6 +183,10 @@ public class PhpSlim4ServerCodegen extends AbstractPhpCodegen { additionalProperties.put("interfacesSrcPath", "./" + toSrcPath(interfacesPackage, srcBasePath)); additionalProperties.put("interfacesTestPath", "./" + toSrcPath(interfacesPackage, testBasePath)); + // same for app classes + additionalProperties.put("appPackage", appPackage); + additionalProperties.put("appSrcPath", "./" + toSrcPath(appPackage, srcBasePath)); + if (additionalProperties.containsKey(PSR7_IMPLEMENTATION)) { this.setPsr7Implementation((String) additionalProperties.get(PSR7_IMPLEMENTATION)); } @@ -213,7 +222,9 @@ public class PhpSlim4ServerCodegen extends AbstractPhpCodegen { supportingFiles.add(new SupportingFile("composer.mustache", "", "composer.json")); supportingFiles.add(new SupportingFile("index.mustache", "public", "index.php")); supportingFiles.add(new SupportingFile(".htaccess", "public", ".htaccess")); - supportingFiles.add(new SupportingFile("SlimRouter.mustache", toSrcPath(invokerPackage, srcBasePath), "SlimRouter.php")); + supportingFiles.add(new SupportingFile("register_dependencies.mustache", toSrcPath(appPackage, srcBasePath), "RegisterDependencies.php")); + supportingFiles.add(new SupportingFile("register_middlewares.mustache", toSrcPath(appPackage, srcBasePath), "RegisterMiddlewares.php")); + supportingFiles.add(new SupportingFile("register_routes.mustache", toSrcPath(appPackage, srcBasePath), "RegisterRoutes.php")); // don't generate phpunit config when tests generation disabled if (Boolean.TRUE.equals(generateApiTests) || Boolean.TRUE.equals(generateModelTests)) { @@ -224,8 +235,8 @@ public class PhpSlim4ServerCodegen extends AbstractPhpCodegen { supportingFiles.add(new SupportingFile("phpcs.xml.mustache", "", "phpcs.xml.dist")); supportingFiles.add(new SupportingFile("htaccess_deny_all", "config", ".htaccess")); - supportingFiles.add(new SupportingFile("config_example.mustache", "config" + File.separator + "dev", "example.inc.php")); - supportingFiles.add(new SupportingFile("config_example.mustache", "config" + File.separator + "prod", "example.inc.php")); + supportingFiles.add(new SupportingFile("config_dev_default.mustache", "config" + File.separator + "dev", "default.inc.php")); + supportingFiles.add(new SupportingFile("config_prod_default.mustache", "config" + File.separator + "prod", "default.inc.php")); if (Boolean.TRUE.equals(generateModels)) { supportingFiles.add(new SupportingFile("base_model.mustache", toSrcPath(invokerPackage, srcBasePath), "BaseModel.php")); diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/.htaccess b/modules/openapi-generator/src/main/resources/php-slim4-server/.htaccess index f6a2ceb3952..57c2185f0e4 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/.htaccess +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/.htaccess @@ -1,3 +1,7 @@ + + SetEnv APP_ENV 'production' + + RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/README.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/README.mustache index d85b12e3bd4..dcd71930123 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/README.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/README.mustache @@ -15,6 +15,7 @@ This server has been generated with [Guzzle PSR-7](https://github.com/guzzle/psr {{#isZendDiactoros}} This server has been generated with [Laminas (Zend) PSR-7 implementation](https://github.com/laminas/laminas-diactoros). {{/isZendDiactoros}} +[PHP-DI](https://php-di.org/doc/frameworks/slim.html) package used as dependency container. ## Requirements @@ -34,7 +35,10 @@ $ composer install ## Add configs -Application requires at least one config file(`config/dev/config.inc.php` or `config/prod/config.inc.php`). You can use [config/dev/example.inc.php](config/dev/example.inc.php) as starting point. +[PHP-DI package](https://php-di.org/doc/getting-started.html) helps to decouple configuration from implementation. App loads configuration files in straight order(`$env` can be `prod` or `dev`): +1. `config/$env/default.inc.php` (contains safe values, can be committed to vcs) +2. `config/$env/config.inc.php` (user config, excluded from vcs, can contain sensitive values, passwords etc.) +3. `lib/App/RegisterDependencies.php` ## Start devserver @@ -103,25 +107,14 @@ $ composer phplint ## Show errors -Switch on option in your application config file like: -```diff - return [ - 'slimSettings' => [ -- 'displayErrorDetails' => false, -+ 'displayErrorDetails' => true, - 'logErrors' => true, - 'logErrorDetails' => true, - ], +Switch your app environment to development in `public/.htaccess` file: +```ini +## .htaccess + + SetEnv APP_ENV 'development' + ``` -## Mock Server -For a quick start uncomment [mocker middleware options](config/dev/example.inc.php#L67-L94) in your application config file. - -Used packages: -* [Openapi Data Mocker](https://github.com/ybelenko/openapi-data-mocker) - first implementation of OAS3 fake data generator. -* [Openapi Data Mocker Server Middleware](https://github.com/ybelenko/openapi-data-mocker-server-middleware) - PSR-15 HTTP server middleware. -* [Openapi Data Mocker Interfaces](https://github.com/ybelenko/openapi-data-mocker-interfaces) - package with mocking interfaces. - {{#generateApiDocs}} ## API Endpoints @@ -135,17 +128,22 @@ All URIs are relative to *{{{basePath}}}* namespace {{apiPackage}}; use {{apiPackage}}\AbstractPetApi; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; class PetApi extends AbstractPetApi { - - public function addPet($request, $response, $args) - { + public function addPet( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { // your implementation of addPet method here } } ``` +When you need to inject dependencies into API controller check [PHP-DI - Controllers as services](https://github.com/PHP-DI/Slim-Bridge#controllers-as-services) guide. + Place all your implementation classes in `./src` folder accordingly. For instance, when abstract class located at `./lib/Api/AbstractPetApi.php` you need to create implementation class at `./src/Api/PetApi.php`. diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/SlimRouter.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/SlimRouter.mustache deleted file mode 100644 index ded9ca8cfad..00000000000 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/SlimRouter.mustache +++ /dev/null @@ -1,331 +0,0 @@ -licenseInfo}} - -/** - * NOTE: This class is auto generated by the openapi generator program. - * https://github.com/openapitools/openapi-generator - * Do not edit the class manually. - */{{#apiInfo}} -namespace {{invokerPackage}}; - -use Slim\Factory\AppFactory; -use Slim\Interfaces\RouteInterface; -use Slim\Exception\HttpNotImplementedException; -use Psr\Container\ContainerInterface; -use InvalidArgumentException; -use Dyorg\TokenAuthentication; -use Dyorg\TokenAuthentication\TokenSearch; -use Psr\Http\Message\ServerRequestInterface; -use OpenAPIServer\Mock\OpenApiDataMocker; -use OpenAPIServer\Mock\OpenApiDataMockerRouteMiddleware; -{{#isSlimPsr7}} -use Slim\Psr7\Factory\ResponseFactory; -{{/isSlimPsr7}} -{{#isNyholmPsr7}} -use Nyholm\Psr7\Factory\Psr17Factory -{{/isNyholmPsr7}} -{{#isGuzzlePsr7}} -use GuzzleHttp\Psr7\HttpFactory; -{{/isGuzzlePsr7}} -{{#isZendDiactoros}} -use Zend\Diactoros\ResponseFactory; -{{/isZendDiactoros}} -use Exception; - -/** - * SlimRouter Class Doc Comment - * - * @package {{invokerPackage}} - * @author OpenAPI Generator team - * @link https://github.com/openapitools/openapi-generator - */ -class SlimRouter -{ - - /** @var App instance */ - private $slimApp; - - /** @var array[] list of all api operations */ - private $operations = [ - {{#apis}} - {{#operations}} - {{#operation}} - [ - 'httpMethod' => '{{httpMethod}}', - 'basePathWithoutHost' => '{{{basePathWithoutHost}}}', - 'path' => '{{{path}}}', - 'apiPackage' => '{{apiPackage}}', - 'classname' => '{{classname}}', - 'userClassname' => '{{userClassname}}', - 'operationId' => '{{operationId}}', - 'responses' => [ - {{#responses}} - '{{#isDefault}}default{{/isDefault}}{{^isDefault}}{{code}}{{/isDefault}}' => [ - 'jsonSchema' => '{{{jsonSchema}}}', - ], - {{/responses}} - ], - 'authMethods' => [ - {{#hasAuthMethods}} - {{#authMethods}} - // {{type}} security schema named '{{name}}' - {{#isBasicBasic}} - [ - 'type' => '{{type}}', - 'isBasic' => true, - 'isBearer' => false, - 'isApiKey' => false, - 'isOAuth' => false, - ], - {{/isBasicBasic}} - {{#isBasicBearer}} - [ - 'type' => '{{type}}', - 'isBasic' => true, - 'isBearer' => true, - 'isApiKey' => false, - 'isOAuth' => false, - ], - {{/isBasicBearer}} - {{#isApiKey}} - [ - 'type' => '{{type}}', - 'isBasic' => false, - 'isBearer' => false, - 'isApiKey' => true, - 'isOAuth' => false, - 'keyParamName' => '{{keyParamName}}', - 'isKeyInHeader' => {{#isKeyInHeader}}true{{/isKeyInHeader}}{{^isKeyInHeader}}false{{/isKeyInHeader}}, - 'isKeyInQuery' => {{#isKeyInQuery}}true{{/isKeyInQuery}}{{^isKeyInQuery}}false{{/isKeyInQuery}}, - 'isKeyInCookie' => {{#isKeyInCookie}}true{{/isKeyInCookie}}{{^isKeyInCookie}}false{{/isKeyInCookie}}, - ], - {{/isApiKey}} - {{#isOAuth}} - [ - 'type' => '{{type}}', - 'isBasic' => false, - 'isBearer' => false, - 'isApiKey' => false, - 'isOAuth' => true, - 'scopes' => [ - {{#scopes}} - '{{scope}}',{{#description}} // {{.}}{{/description}} - {{/scopes}} - ], - ], - {{/isOAuth}} - {{/authMethods}} - {{/hasAuthMethods}} - ], - ], - {{/operation}} - {{/operations}} - {{/apis}} - ]; - - /** - * Class constructor - * - * @param ContainerInterface|array $settings Either a ContainerInterface or an associative array of app settings - * - * @throws HttpNotImplementedException When implementation class doesn't exists - * @throws Exception when not supported authorization schema type provided - */ - public function __construct($settings = []) - { - if ($settings instanceof ContainerInterface) { - // Set container to create App with on AppFactory - AppFactory::setContainer($settings); - } - $this->slimApp = AppFactory::create(); - - // middlewares requires Psr\Container\ContainerInterface - $container = $this->slimApp->getContainer(); - - {{#hasAuthMethods}} - $authPackage = '{{authPackage}}'; - $basicAuthenticator = function (ServerRequestInterface &$request, TokenSearch $tokenSearch) use ($authPackage) { - $message = "How about extending {{abstractNamePrefix}}Authenticator{{abstractNameSuffix}} class by {$authPackage}\BasicAuthenticator?"; - throw new HttpNotImplementedException($request, $message); - }; - $apiKeyAuthenticator = function (ServerRequestInterface &$request, TokenSearch $tokenSearch) use ($authPackage) { - $message = "How about extending {{abstractNamePrefix}}Authenticator{{abstractNameSuffix}} class by {$authPackage}\ApiKeyAuthenticator?"; - throw new HttpNotImplementedException($request, $message); - }; - $oAuthAuthenticator = function (ServerRequestInterface &$request, TokenSearch $tokenSearch) use ($authPackage) { - $message = "How about extending {{abstractNamePrefix}}Authenticator{{abstractNameSuffix}} class by {$authPackage}\OAuthAuthenticator?"; - throw new HttpNotImplementedException($request, $message); - }; - {{/hasAuthMethods}} - - $userOptions = $this->getSetting($settings, 'tokenAuthenticationOptions', null); - - // mocker options - $mockerOptions = $this->getSetting($settings, 'mockerOptions', null); - $dataMocker = $mockerOptions['dataMocker'] ?? new OpenApiDataMocker(); - {{#isSlimPsr7}} - $responseFactory = new ResponseFactory(); - {{/isSlimPsr7}} - {{#isNyholmPsr7}} - $responseFactory = new Psr17Factory(); - {{/isNyholmPsr7}} - {{#isGuzzlePsr7}} - $responseFactory = new HttpFactory(); - {{/isGuzzlePsr7}} - {{#isZendDiactoros}} - $responseFactory = new ResponseFactory(); - {{/isZendDiactoros}} - $getMockStatusCodeCallback = $mockerOptions['getMockStatusCodeCallback'] ?? null; - $mockAfterCallback = $mockerOptions['afterCallback'] ?? null; - - foreach ($this->operations as $operation) { - $callback = function ($request, $response, $arguments) use ($operation) { - $message = "How about extending {$operation['classname']} by {$operation['apiPackage']}\\{$operation['userClassname']} class implementing {$operation['operationId']} as a {$operation['httpMethod']} method?"; - throw new HttpNotImplementedException($request, $message); - }; - $middlewares = []; - - if (class_exists("\\{$operation['apiPackage']}\\{$operation['userClassname']}")) { - $callback = "\\{$operation['apiPackage']}\\{$operation['userClassname']}:{$operation['operationId']}"; - } - - {{#hasAuthMethods}} - foreach ($operation['authMethods'] as $authMethod) { - switch ($authMethod['type']) { - case 'http': - $authenticatorClassname = "\\{$authPackage}\\BasicAuthenticator"; - if (class_exists($authenticatorClassname)) { - $basicAuthenticator = new $authenticatorClassname($container); - } - - $middlewares[] = new TokenAuthentication($this->getTokenAuthenticationOptions([ - 'authenticator' => $basicAuthenticator, - 'regex' => $authMethod['isBearer'] ? '/Bearer\s+(.*)$/i' : '/Basic\s+(.*)$/i', - 'header' => 'Authorization', - 'parameter' => null, - 'cookie' => null, - 'argument' => null, - ], $userOptions)); - break; - case 'apiKey': - $authenticatorClassname = "\\{$authPackage}\\ApiKeyAuthenticator"; - if (class_exists($authenticatorClassname)) { - $apiKeyAuthenticator = new $authenticatorClassname($container); - } - - $middlewares[] = new TokenAuthentication($this->getTokenAuthenticationOptions([ - 'authenticator' => $apiKeyAuthenticator, - 'regex' => '/^(.*)$/i', - 'header' => $authMethod['isKeyInHeader'] ? $authMethod['keyParamName'] : null, - 'parameter' => $authMethod['isKeyInQuery'] ? $authMethod['keyParamName'] : null, - 'cookie' => $authMethod['isKeyInCookie'] ? $authMethod['keyParamName'] : null, - 'argument' => null, - ], $userOptions)); - break; - case 'oauth2': - $authenticatorClassname = "\\{$authPackage}\\OAuthAuthenticator"; - if (class_exists($authenticatorClassname)) { - $oAuthAuthenticator = new $authenticatorClassname($container, $authMethod['scopes']); - } - - $middlewares[] = new TokenAuthentication($this->getTokenAuthenticationOptions([ - 'authenticator' => $oAuthAuthenticator, - 'regex' => '/Bearer\s+(.*)$/i', - 'header' => 'Authorization', - 'parameter' => null, - 'cookie' => null, - 'argument' => null, - ], $userOptions)); - break; - default: - throw new Exception('Unknown authorization schema type'); - } - } - {{/hasAuthMethods}} - - if (is_callable($getMockStatusCodeCallback)) { - $mockSchemaResponses = array_map(function ($item) { - return json_decode($item['jsonSchema'], true); - }, $operation['responses']); - $middlewares[] = new OpenApiDataMockerRouteMiddleware($dataMocker, $mockSchemaResponses, $responseFactory, $getMockStatusCodeCallback, $mockAfterCallback); - } - - $this->addRoute( - [$operation['httpMethod']], - "{$operation['basePathWithoutHost']}{$operation['path']}", - $callback, - $middlewares - )->setName($operation['operationId']); - } - } - - /** - * Merges user defined options with dynamic params - * - * @param array $staticOptions Required static options - * @param array $userOptions User options - * - * @return array Merged array - */ - private function getTokenAuthenticationOptions(array $staticOptions, array $userOptions = null) - { - if (is_array($userOptions) === false) { - return $staticOptions; - } - - return array_merge($userOptions, $staticOptions); - } - - /** - * Returns app setting by name. - * - * @param ContainerInterface|array $settings Either a ContainerInterface or an associative array of app settings - * @param string $settingName Setting name - * @param mixed $default Default setting value. - * - * @return mixed - */ - private function getSetting($settings, $settingName, $default = null) - { - if ($settings instanceof ContainerInterface && $settings->has($settingName)) { - return $settings->get($settingName); - } elseif (is_array($settings) && array_key_exists($settingName, $settings)) { - return $settings[$settingName]; - } - - return $default; - } - - /** - * Add route with multiple methods - * - * @param string[] $methods Numeric array of HTTP method names - * @param string $pattern The route URI pattern - * @param callable|string $callable The route callback routine - * @param array|null $middlewares List of middlewares - * - * @return RouteInterface - * - * @throws InvalidArgumentException If the route pattern isn't a string - */ - public function addRoute(array $methods, string $pattern, $callable, $middlewares = []) - { - $route = $this->slimApp->map($methods, $pattern, $callable); - foreach ($middlewares as $middleware) { - $route->add($middleware); - } - return $route; - } - - /** - * Returns Slim Framework instance - * - * @return App - */ - public function getSlimApp() - { - return $this->slimApp; - } -} -{{/apiInfo}} diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/abstract_authenticator.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/abstract_authenticator.mustache index 82fd8f648ea..3aef910d9f1 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/abstract_authenticator.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/abstract_authenticator.mustache @@ -9,7 +9,6 @@ */{{#apiInfo}} namespace {{authPackage}}; -use Psr\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; use Dyorg\TokenAuthentication; use Dyorg\TokenAuthentication\TokenSearch; @@ -24,12 +23,6 @@ use Dyorg\TokenAuthentication\Exceptions\UnauthorizedExceptionInterface; */ abstract class {{abstractNamePrefix}}Authenticator{{abstractNameSuffix}} { - - /** - * @var ContainerInterface|null Slim app container instance - */ - protected $container; - /** * @var string[]|null List of required scopes */ @@ -49,12 +42,10 @@ abstract class {{abstractNamePrefix}}Authenticator{{abstractNameSuffix}} /** * Authenticator constructor * - * @param ContainerInterface|null $container Slim app container instance - * @param string[]|null $requiredScope List of required scopes + * @param string[]|null $requiredScope List of required scopes */ - public function __construct(ContainerInterface $container = null, $requiredScope = null) + public function __construct($requiredScope = null) { - $this->container = $container; $this->requiredScope = $requiredScope; } diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/api.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/api.mustache index f76f11f29af..83de70ff5c4 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/api.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/api.mustache @@ -6,10 +6,11 @@ * NOTE: This class is auto generated by the openapi generator program. * https://github.com/openapitools/openapi-generator * Do not edit the class manually. + * Extend this class with your controller. You can inject dependencies via class constructor, + * @see https://github.com/PHP-DI/Slim-Bridge basic example. */ namespace {{apiPackage}}; -use Psr\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Slim\Exception\HttpNotImplementedException; @@ -23,25 +24,8 @@ use Slim\Exception\HttpNotImplementedException; */ abstract class {{classname}} { - - /** - * @var ContainerInterface|null Slim app container instance - */ - protected $container; - - /** - * Route Controller constructor receives container - * - * @param ContainerInterface|null $container Slim app container instance - */ - public function __construct(ContainerInterface $container = null) - { - $this->container = $container; - } - {{#operations}} {{#operation}} - /** * {{httpMethod}} {{operationId}} {{#summary}} @@ -56,7 +40,11 @@ abstract class {{classname}} * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments + {{#hasPathParams}} + {{#pathParams}} + * @param {{{dataType}}} ${{paramName}}{{#description}} {{.}}{{/description}}{{^description}} {{paramName}}{{/description}} + {{/pathParams}} + {{/hasPathParams}} * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method @@ -64,19 +52,21 @@ abstract class {{classname}} * @deprecated {{/isDeprecated}} */ - public function {{operationId}}(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function {{operationId}}( + ServerRequestInterface $request, + ResponseInterface $response{{#hasPathParams}},{{/hasPathParams}} + {{#hasPathParams}} + {{#pathParams}} + {{{dataType}}} ${{paramName}}{{^-last}},{{/-last}} + {{/pathParams}} + {{/hasPathParams}} + ): ResponseInterface { {{#hasHeaderParams}} $headers = $request->getHeaders(); {{#headerParams}} ${{paramName}} = $request->hasHeader('{{baseName}}') ? $headers['{{baseName}}'] : null; {{/headerParams}} {{/hasHeaderParams}} - {{#hasPathParams}} - {{#pathParams}} - ${{paramName}} = $args['{{baseName}}']; - {{/pathParams}} - {{/hasPathParams}} {{#hasQueryParams}} $queryParams = $request->getQueryParams(); {{#queryParams}} @@ -105,6 +95,9 @@ abstract class {{classname}} $message = "How about implementing {{nickname}} as a {{httpMethod}} method in {{apiPackage}}\{{userClassname}} class?"; throw new HttpNotImplementedException($request, $message); } + {{^-last}} + + {{/-last}} {{/operation}} {{/operations}} } diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/composer.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/composer.mustache index e514b055f54..6d8d0bcbac2 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/composer.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/composer.mustache @@ -9,30 +9,30 @@ ], "require": { "php": "^7.4 || ^8.0", - "slim/slim": "^4.5.0", "dyorg/slim-token-authentication": "dev-slim4", - "ybelenko/openapi-data-mocker": "^1.0", - "ybelenko/openapi-data-mocker-server-middleware": "^1.0", - {{#isSlimPsr7}} - "slim/psr7": "^1.1.0" - {{/isSlimPsr7}} - {{#isNyholmPsr7}} - "nyholm/psr7": "^1.3.0", - "nyholm/psr7-server": "^0.4.1" - {{/isNyholmPsr7}} {{#isGuzzlePsr7}} "guzzlehttp/psr7": "^1.6.1", - "http-interop/http-factory-guzzle": "^1.0.0" + "http-interop/http-factory-guzzle": "^1.0.0", {{/isGuzzlePsr7}} {{#isZendDiactoros}} - "laminas/laminas-diactoros": "^2.3.0" + "laminas/laminas-diactoros": "^2.3.0", {{/isZendDiactoros}} + {{#isNyholmPsr7}} + "nyholm/psr7": "^1.3.0", + "nyholm/psr7-server": "^0.4.1", + {{/isNyholmPsr7}} + "php-di/slim-bridge": "^3.2", + {{#isSlimPsr7}} + "slim/psr7": "^1.1.0", + {{/isSlimPsr7}} + "ybelenko/openapi-data-mocker": "^1.0", + "ybelenko/openapi-data-mocker-server-middleware": "^1.0" }, "require-dev": { + "overtrue/phplint": "^2.0.2", {{#generateTests}} "phpunit/phpunit": "^8.0 || ^9.0", {{/generateTests}} - "overtrue/phplint": "^2.0.2", "squizlabs/php_codesniffer": "^3.5" }, "autoload": { @@ -58,5 +58,8 @@ {{/generateTests}} "phpcs": "phpcs", "phplint": "phplint ./ --exclude=vendor" + }, + "config": { + "sort-packages": true } } diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/config_dev_default.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/config_dev_default.mustache new file mode 100644 index 00000000000..42588052160 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/config_dev_default.mustache @@ -0,0 +1,35 @@ + 'development', + + // slim framework settings + 'slim.displayErrorDetails' => true, + 'slim.logErrors' => false, + 'slim.logErrorDetails' => false, + + // PDO + 'pdo.dsn' => 'mysql:host=localhost;charset=utf8mb4', + 'pdo.username' => 'root', + 'pdo.password' => 'root', + 'pdo.options' => [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + ], +]; diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/config_example.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/config_example.mustache deleted file mode 100644 index 52755f25385..00000000000 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/config_example.mustache +++ /dev/null @@ -1,83 +0,0 @@ -licenseInfo}} - -/** - * App configuration file example. - * - * Copy file to config/dev/config.inc.php and config/prod/config.inc.php - * App loads dev config only when prod doesn't exist - * in other words if both configs presented - prod config applies - */ - -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\ResponseInterface; -use OpenAPIServer\Mock\OpenApiDataMocker; - -$mocker = new OpenApiDataMocker(); -$mocker->setModelsNamespace('{{modelPackage}}\\'); - -return [ - 'slimSettings' => [ - 'displayErrorDetails' => false, - 'logErrors' => true, - 'logErrorDetails' => true, - ], - - 'tokenAuthenticationOptions' => [ - /** - * Tokens are essentially passwords. You should treat them as such and you should always - * use HTTPS. If the middleware detects insecure usage over HTTP it will return unauthorized - * with a message Required HTTPS for token authentication. This rule is relaxed for requests - * on localhost. To allow insecure usage you must enable it manually by setting secure to - * false. - * Default: true - */ - // 'secure' => true, - - /** - * Alternatively you can list your development host to have relaxed security. - * Default: ['localhost', '127.0.0.1'] - */ - // 'relaxed' => ['localhost', '127.0.0.1'], - - /** - * By default on occurred a fail on authentication, is sent a response on json format with a - * message (`Invalid Token` or `Not found Token`) and with the token (if found), with status - * `401 Unauthorized`. You can customize it by setting a callable function on error option. - * Default: null - */ - // 'error' => null, - ], - - 'mockerOptions' => [ - // 'dataMocker' => $mocker, - - // 'getMockStatusCodeCallback' => function (ServerRequestInterface $request, array $responses) { - // // check if client clearly asks for mocked response - // $pingHeader = 'X-{{invokerPackage}}-Mock'; - // $pingHeaderCode = 'X-{{invokerPackage}}-Mock-Code'; - // if ( - // $request->hasHeader($pingHeader) - // && $request->getHeader($pingHeader)[0] === 'ping' - // ) { - // $responses = (array) $responses; - // $requestedResponseCode = ($request->hasHeader($pingHeaderCode)) ? $request->getHeader($pingHeaderCode)[0] : 'default'; - // if (array_key_exists($requestedResponseCode, $responses)) { - // return $requestedResponseCode; - // } - - // // return first response key - // reset($responses); - // return key($responses); - // } - - // return false; - // }, - - // 'afterCallback' => function (ServerRequestInterface $request, ResponseInterface $response) { - // // mark mocked response to distinguish real and fake responses - // return $response->withHeader('X-{{invokerPackage}}-Mock', 'pong'); - // }, - ], -]; diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/config_prod_default.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/config_prod_default.mustache new file mode 100644 index 00000000000..d6853fbcf53 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/config_prod_default.mustache @@ -0,0 +1,35 @@ + 'production', + + // slim framework settings + 'slim.displayErrorDetails' => false, + 'slim.logErrors' => true, + 'slim.logErrorDetails' => true, + + // PDO + 'pdo.dsn' => 'mysql:host=localhost;charset=utf8mb4', + 'pdo.username' => 'root', + 'pdo.password' => 'root', + 'pdo.options' => [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + ], +]; diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/gitignore b/modules/openapi-generator/src/main/resources/php-slim4-server/gitignore index e12b356ade0..27433d90949 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/gitignore +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/gitignore @@ -20,5 +20,5 @@ composer.phar # Application config may contain sensitive data /config/**/*.* !/config/.htaccess -!/config/dev/example.inc.php -!/config/prod/example.inc.php +!/config/dev/default.inc.php +!/config/prod/default.inc.php diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/index.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/index.mustache index 305f7cf8b56..ead7d817f87 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/index.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/index.mustache @@ -9,49 +9,64 @@ require_once __DIR__ . '/../vendor/autoload.php'; -use {{invokerPackage}}\SlimRouter; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\ResponseInterface; -use OpenAPIServer\Mock\OpenApiDataMocker; +use DI\Bridge\Slim\Bridge; +use DI\ContainerBuilder; +use {{appPackage}}\RegisterDependencies; +use {{appPackage}}\RegisterRoutes; +use {{appPackage}}\RegisterMiddlewares; +use Slim\Factory\ServerRequestCreatorFactory; +use Slim\ResponseEmitter; {{/apiInfo}} -// load config file -$config = []; -if (is_array($prodConfig = @include(__DIR__ . '/../config/prod/config.inc.php'))) { - $config = $prodConfig; -} elseif (is_array($devConfig = @include(__DIR__ . '/../config/dev/config.inc.php'))) { - $config = $devConfig; -} else { - throw new InvalidArgumentException('Config file missed or broken.'); +// Instantiate PHP-DI ContainerBuilder +$builder = new ContainerBuilder(); + +// consider prod by default +$env; +switch (strtolower($_SERVER['APP_ENV'] ?? 'prod')) { + case 'development': + case 'dev': + $env = 'dev'; + break; + case 'production': + case 'prod': + default: + $env = 'prod'; } -$router = new SlimRouter($config); -$app = $router->getSlimApp(); +// Main configuration +$builder->addDefinitions(__DIR__ . "/../config/{$env}/default.inc.php"); -// Parse json, form data and xml -$app->addBodyParsingMiddleware(); +// Config file for the environment if exists +$userConfig = __DIR__ . "/../config/{$env}/config.inc.php"; +if (file_exists($userConfig)) { + $builder->addDefinitions($userConfig); +} -/** - * The routing middleware should be added before the ErrorMiddleware - * Otherwise exceptions thrown from it will not be handled - */ -$app->addRoutingMiddleware(); +// Set up dependencies +$dependencies = new RegisterDependencies(); +$dependencies($builder); -/** - * Add Error Handling Middleware - * - * @param bool $displayErrorDetails -> Should be set to false in production - * @param bool $logErrors -> Parameter is passed to the default ErrorHandler - * @param bool $logErrorDetails -> Display error details in error log - * which can be replaced by a callable of your choice. +// Build PHP-DI Container instance +$container = $builder->build(); - * Note: This middleware should be added last. It will not handle any exceptions/errors - * for middleware added after it. - */ -$app->addErrorMiddleware( - $config['slimSettings']['displayErrorDetails'] ?? false, - $config['slimSettings']['logErrors'] ?? true, - $config['slimSettings']['logErrorDetails'] ?? true -); +// Instantiate the app +$app = Bridge::create($container); -$app->run(); +// Register middleware +$middleware = new RegisterMiddlewares(); +$middleware($app); + +// Register routes +// yes, it's anti-pattern you shouldn't get deps from container directly +$routes = $container->get(RegisterRoutes::class); +$routes($app); + +// Create Request object from globals +$serverRequestCreator = ServerRequestCreatorFactory::create(); +$request = $serverRequestCreator->createServerRequestFromGlobals(); + +// Run App & Emit Response +$response = $app->handle($request); +$responseEmitter = new ResponseEmitter(); +$responseEmitter->emit($response); diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/register_dependencies.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/register_dependencies.mustache new file mode 100644 index 00000000000..0f48fb9de1c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/register_dependencies.mustache @@ -0,0 +1,58 @@ +licenseInfo}} + +declare(strict_types=1); + +/** + * NOTE: This class is auto generated by the openapi generator program. + * https://github.com/openapitools/openapi-generator + * Do not edit the class manually. + */ +namespace {{appPackage}}; + +/** + * RegisterDependencies + * + * Recommendations from template creator: + * + * I don't use imports(eg. use Slim\Middleware\ErrorMiddleware) here because each package unlikely + * be used in code twice. It helps to keep that file short and make Git history cleaner. + * + * This class declared as final because two classes with dependency injections can cause confusion. Edit + * template of this class or use your own implementation instead(overwrite index.php to import your + * custom class). + */ +final class RegisterDependencies +{ + /** + * Adds dependency definitions. + * + * @param \DI\ContainerBuilder $containerBuilder Container builder. + * + * @see https://php-di.org/doc/php-definitions.html + */ + public function __invoke(\DI\ContainerBuilder $containerBuilder): void + { + $containerBuilder->addDefinitions([ + // Response factory required as typed argument in next ErrorMiddleware injection + \Psr\Http\Message\ResponseFactoryInterface::class => \DI\factory([\Slim\Factory\AppFactory::class, 'determineResponseFactory']), + + // Slim error middleware + // @see https://www.slimframework.com/docs/v4/middleware/error-handling.html + \Slim\Middleware\ErrorMiddleware::class => \DI\autowire() + ->constructorParameter('displayErrorDetails', \DI\get('slim.displayErrorDetails', false)) + ->constructorParameter('logErrors', \DI\get('slim.logErrors', true)) + ->constructorParameter('logErrorDetails', \DI\get('slim.logErrorDetails', true)), + + // PDO class for database managing + \PDO::class => \DI\create() + ->constructor( + \DI\get('pdo.dsn'), + \DI\get('pdo.username'), + \DI\get('pdo.password'), + \DI\get('pdo.options', null) + ), + ]); + } +} diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/register_middlewares.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/register_middlewares.mustache new file mode 100644 index 00000000000..b0f42676430 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/register_middlewares.mustache @@ -0,0 +1,51 @@ +licenseInfo}} + +declare(strict_types=1); + +/** + * NOTE: This class is auto generated by the openapi generator program. + * https://github.com/openapitools/openapi-generator + * Do not edit the class manually. + */ +namespace {{appPackage}}; + +/** + * RegisterMiddlewares + * + * Recommendations from template creator: + * + * There is no way to add route related middlewares here, add global ones. Route related middlewares + * can be applied in \{{appPackage}}\RegisterRoutes class. + * + * I add middlewares by full class names(\Slim\Middleware\ErrorMiddleware::class) because that way + * Slim initiates them with options from Container. They already configured, don't need to pass any + * options manually. + * + * I don't use imports(eg. use Slim\Middleware\ErrorMiddleware) here because each package unlikely + * be used in code twice. It helps to keep that file short and make Git history cleaner. + * + * This class declared as final because two classes with middlewares can cause confusion. Edit + * template of this class or use your own implementation instead(overwrite index.php to import your + * custom class). + */ +final class RegisterMiddlewares +{ + /** + * Adds middlewares to Slim app instance. + * + * @param \Slim\App $app App instance. + */ + public function __invoke(\Slim\App $app): void + { + // Parse json, form data and xml + $app->addBodyParsingMiddleware(); + + // Add Routing Middleware + $app->addRoutingMiddleware(); + + // Add Error Middleware + $app->add(\Slim\Middleware\ErrorMiddleware::class); + } +} diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/register_routes.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/register_routes.mustache new file mode 100644 index 00000000000..b027ebc46e7 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/register_routes.mustache @@ -0,0 +1,137 @@ +licenseInfo}} + +declare(strict_types=1); + +/** + * NOTE: This class is auto generated by the openapi generator program. + * https://github.com/openapitools/openapi-generator + * Do not edit the class manually. + */{{#apiInfo}} +namespace {{appPackage}}; + +use Slim\Exception\HttpNotImplementedException; + +/** + * RegisterRoutes Class Doc Comment + * + * @package {{invokerPackage}} + * @author OpenAPI Generator team + * @link https://github.com/openapitools/openapi-generator + */ +class RegisterRoutes +{ + /** @var array[] list of all api operations */ + private $operations = [ + {{#apis}} + {{#operations}} + {{#operation}} + [ + 'httpMethod' => '{{httpMethod}}', + 'basePathWithoutHost' => '{{{basePathWithoutHost}}}', + 'path' => '{{{path}}}', + 'apiPackage' => '{{apiPackage}}', + 'classname' => '{{classname}}', + 'userClassname' => '{{userClassname}}', + 'operationId' => '{{operationId}}', + 'responses' => [ + {{#responses}} + '{{#isDefault}}default{{/isDefault}}{{^isDefault}}{{code}}{{/isDefault}}' => [ + 'jsonSchema' => '{{{jsonSchema}}}', + ], + {{/responses}} + ], + 'authMethods' => [ + {{#hasAuthMethods}} + {{#authMethods}} + // {{type}} security schema named '{{name}}' + {{#isBasicBasic}} + [ + 'type' => '{{type}}', + 'isBasic' => true, + 'isBearer' => false, + 'isApiKey' => false, + 'isOAuth' => false, + ], + {{/isBasicBasic}} + {{#isBasicBearer}} + [ + 'type' => '{{type}}', + 'isBasic' => true, + 'isBearer' => true, + 'isApiKey' => false, + 'isOAuth' => false, + ], + {{/isBasicBearer}} + {{#isApiKey}} + [ + 'type' => '{{type}}', + 'isBasic' => false, + 'isBearer' => false, + 'isApiKey' => true, + 'isOAuth' => false, + 'keyParamName' => '{{keyParamName}}', + 'isKeyInHeader' => {{#isKeyInHeader}}true{{/isKeyInHeader}}{{^isKeyInHeader}}false{{/isKeyInHeader}}, + 'isKeyInQuery' => {{#isKeyInQuery}}true{{/isKeyInQuery}}{{^isKeyInQuery}}false{{/isKeyInQuery}}, + 'isKeyInCookie' => {{#isKeyInCookie}}true{{/isKeyInCookie}}{{^isKeyInCookie}}false{{/isKeyInCookie}}, + ], + {{/isApiKey}} + {{#isOAuth}} + [ + 'type' => '{{type}}', + 'isBasic' => false, + 'isBearer' => false, + 'isApiKey' => false, + 'isOAuth' => true, + 'scopes' => [ + {{#scopes}} + '{{scope}}',{{#description}} // {{.}}{{/description}} + {{/scopes}} + ], + ], + {{/isOAuth}} + {{/authMethods}} + {{/hasAuthMethods}} + ], + ], + {{/operation}} + {{/operations}} + {{/apis}} + ]; + + /** + * Add routes to Slim app. + * + * @param \Slim\App $app Pre-configured Slim application instance + * + * @throws HttpNotImplementedException When implementation class doesn't exists + */ + public function __invoke(\Slim\App $app): void + { + foreach ($this->operations as $operation) { + $callback = function ($request) use ($operation) { + $message = "How about extending {$operation['classname']} by {$operation['apiPackage']}\\{$operation['userClassname']} class implementing {$operation['operationId']} as a {$operation['httpMethod']} method?"; + throw new HttpNotImplementedException($request, $message); + }; + $middlewares = []; + + if (class_exists("\\{$operation['apiPackage']}\\{$operation['userClassname']}")) { + // Notice how we register the controller using the class name? + // PHP-DI will instantiate the class for us only when it's actually necessary + $callback = ["\\{$operation['apiPackage']}\\{$operation['userClassname']}", $operation['operationId']]; + } + + $route = $app->map( + [$operation['httpMethod']], + "{$operation['basePathWithoutHost']}{$operation['path']}", + $callback + )->setName($operation['operationId']); + + foreach ($middlewares as $middleware) { + $route->add($middleware); + } + } + } +} +{{/apiInfo}} diff --git a/samples/server/petstore/php-slim4/.gitignore b/samples/server/petstore/php-slim4/.gitignore index e12b356ade0..27433d90949 100644 --- a/samples/server/petstore/php-slim4/.gitignore +++ b/samples/server/petstore/php-slim4/.gitignore @@ -20,5 +20,5 @@ composer.phar # Application config may contain sensitive data /config/**/*.* !/config/.htaccess -!/config/dev/example.inc.php -!/config/prod/example.inc.php +!/config/dev/default.inc.php +!/config/prod/default.inc.php diff --git a/samples/server/petstore/php-slim4/.openapi-generator/FILES b/samples/server/petstore/php-slim4/.openapi-generator/FILES index 40c7067f108..08a1e9c294c 100644 --- a/samples/server/petstore/php-slim4/.openapi-generator/FILES +++ b/samples/server/petstore/php-slim4/.openapi-generator/FILES @@ -2,11 +2,14 @@ README.md composer.json config/.htaccess -config/dev/example.inc.php -config/prod/example.inc.php +config/dev/default.inc.php +config/prod/default.inc.php lib/Api/AbstractPetApi.php lib/Api/AbstractStoreApi.php lib/Api/AbstractUserApi.php +lib/App/RegisterDependencies.php +lib/App/RegisterMiddlewares.php +lib/App/RegisterRoutes.php lib/Auth/AbstractAuthenticator.php lib/BaseModel.php lib/Model/ApiResponse.php @@ -15,7 +18,6 @@ lib/Model/Order.php lib/Model/Pet.php lib/Model/Tag.php lib/Model/User.php -lib/SlimRouter.php phpcs.xml.dist phpunit.xml.dist public/.htaccess diff --git a/samples/server/petstore/php-slim4/README.md b/samples/server/petstore/php-slim4/README.md index ac51de45d1e..a99f4a13cc4 100644 --- a/samples/server/petstore/php-slim4/README.md +++ b/samples/server/petstore/php-slim4/README.md @@ -4,6 +4,7 @@ * [Slim 4 Documentation](https://www.slimframework.com/docs/v4/) This server has been generated with [Slim PSR-7](https://github.com/slimphp/Slim-Psr7) implementation. +[PHP-DI](https://php-di.org/doc/frameworks/slim.html) package used as dependency container. ## Requirements @@ -23,7 +24,10 @@ $ composer install ## Add configs -Application requires at least one config file(`config/dev/config.inc.php` or `config/prod/config.inc.php`). You can use [config/dev/example.inc.php](config/dev/example.inc.php) as starting point. +[PHP-DI package](https://php-di.org/doc/getting-started.html) helps to decouple configuration from implementation. App loads configuration files in straight order(`$env` can be `prod` or `dev`): +1. `config/$env/default.inc.php` (contains safe values, can be committed to vcs) +2. `config/$env/config.inc.php` (user config, excluded from vcs, can contain sensitive values, passwords etc.) +3. `lib/App/RegisterDependencies.php` ## Start devserver @@ -86,25 +90,14 @@ $ composer phplint ## Show errors -Switch on option in your application config file like: -```diff - return [ - 'slimSettings' => [ -- 'displayErrorDetails' => false, -+ 'displayErrorDetails' => true, - 'logErrors' => true, - 'logErrorDetails' => true, - ], +Switch your app environment to development in `public/.htaccess` file: +```ini +## .htaccess + + SetEnv APP_ENV 'development' + ``` -## Mock Server -For a quick start uncomment [mocker middleware options](config/dev/example.inc.php#L67-L94) in your application config file. - -Used packages: -* [Openapi Data Mocker](https://github.com/ybelenko/openapi-data-mocker) - first implementation of OAS3 fake data generator. -* [Openapi Data Mocker Server Middleware](https://github.com/ybelenko/openapi-data-mocker-server-middleware) - PSR-15 HTTP server middleware. -* [Openapi Data Mocker Interfaces](https://github.com/ybelenko/openapi-data-mocker-interfaces) - package with mocking interfaces. - ## API Endpoints All URIs are relative to *http://petstore.swagger.io/v2* @@ -117,17 +110,22 @@ All URIs are relative to *http://petstore.swagger.io/v2* namespace OpenAPIServer\Api; use OpenAPIServer\Api\AbstractPetApi; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; class PetApi extends AbstractPetApi { - - public function addPet($request, $response, $args) - { + public function addPet( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { // your implementation of addPet method here } } ``` +When you need to inject dependencies into API controller check [PHP-DI - Controllers as services](https://github.com/PHP-DI/Slim-Bridge#controllers-as-services) guide. + Place all your implementation classes in `./src` folder accordingly. For instance, when abstract class located at `./lib/Api/AbstractPetApi.php` you need to create implementation class at `./src/Api/PetApi.php`. diff --git a/samples/server/petstore/php-slim4/composer.json b/samples/server/petstore/php-slim4/composer.json index 529f8f93715..4f31f4b28ff 100644 --- a/samples/server/petstore/php-slim4/composer.json +++ b/samples/server/petstore/php-slim4/composer.json @@ -9,15 +9,15 @@ ], "require": { "php": "^7.4 || ^8.0", - "slim/slim": "^4.5.0", "dyorg/slim-token-authentication": "dev-slim4", + "php-di/slim-bridge": "^3.2", + "slim/psr7": "^1.1.0", "ybelenko/openapi-data-mocker": "^1.0", - "ybelenko/openapi-data-mocker-server-middleware": "^1.0", - "slim/psr7": "^1.1.0" + "ybelenko/openapi-data-mocker-server-middleware": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^8.0 || ^9.0", "overtrue/phplint": "^2.0.2", + "phpunit/phpunit": "^8.0 || ^9.0", "squizlabs/php_codesniffer": "^3.5" }, "autoload": { @@ -37,5 +37,8 @@ "test-models": "phpunit --testsuite Models", "phpcs": "phpcs", "phplint": "phplint ./ --exclude=vendor" + }, + "config": { + "sort-packages": true } } diff --git a/samples/server/petstore/php-slim4/config/dev/default.inc.php b/samples/server/petstore/php-slim4/config/dev/default.inc.php new file mode 100644 index 00000000000..be4bd6530b0 --- /dev/null +++ b/samples/server/petstore/php-slim4/config/dev/default.inc.php @@ -0,0 +1,35 @@ + 'development', + + // slim framework settings + 'slim.displayErrorDetails' => true, + 'slim.logErrors' => false, + 'slim.logErrorDetails' => false, + + // PDO + 'pdo.dsn' => 'mysql:host=localhost;charset=utf8mb4', + 'pdo.username' => 'root', + 'pdo.password' => 'root', + 'pdo.options' => [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + ], +]; diff --git a/samples/server/petstore/php-slim4/config/dev/example.inc.php b/samples/server/petstore/php-slim4/config/dev/example.inc.php deleted file mode 100644 index 2a2e65d4a59..00000000000 --- a/samples/server/petstore/php-slim4/config/dev/example.inc.php +++ /dev/null @@ -1,96 +0,0 @@ -setModelsNamespace('OpenAPIServer\Model\\'); - -return [ - 'slimSettings' => [ - 'displayErrorDetails' => false, - 'logErrors' => true, - 'logErrorDetails' => true, - ], - - 'tokenAuthenticationOptions' => [ - /** - * Tokens are essentially passwords. You should treat them as such and you should always - * use HTTPS. If the middleware detects insecure usage over HTTP it will return unauthorized - * with a message Required HTTPS for token authentication. This rule is relaxed for requests - * on localhost. To allow insecure usage you must enable it manually by setting secure to - * false. - * Default: true - */ - // 'secure' => true, - - /** - * Alternatively you can list your development host to have relaxed security. - * Default: ['localhost', '127.0.0.1'] - */ - // 'relaxed' => ['localhost', '127.0.0.1'], - - /** - * By default on occurred a fail on authentication, is sent a response on json format with a - * message (`Invalid Token` or `Not found Token`) and with the token (if found), with status - * `401 Unauthorized`. You can customize it by setting a callable function on error option. - * Default: null - */ - // 'error' => null, - ], - - 'mockerOptions' => [ - // 'dataMocker' => $mocker, - - // 'getMockStatusCodeCallback' => function (ServerRequestInterface $request, array $responses) { - // // check if client clearly asks for mocked response - // $pingHeader = 'X-OpenAPIServer-Mock'; - // $pingHeaderCode = 'X-OpenAPIServer-Mock-Code'; - // if ( - // $request->hasHeader($pingHeader) - // && $request->getHeader($pingHeader)[0] === 'ping' - // ) { - // $responses = (array) $responses; - // $requestedResponseCode = ($request->hasHeader($pingHeaderCode)) ? $request->getHeader($pingHeaderCode)[0] : 'default'; - // if (array_key_exists($requestedResponseCode, $responses)) { - // return $requestedResponseCode; - // } - - // // return first response key - // reset($responses); - // return key($responses); - // } - - // return false; - // }, - - // 'afterCallback' => function (ServerRequestInterface $request, ResponseInterface $response) { - // // mark mocked response to distinguish real and fake responses - // return $response->withHeader('X-OpenAPIServer-Mock', 'pong'); - // }, - ], -]; diff --git a/samples/server/petstore/php-slim4/config/prod/default.inc.php b/samples/server/petstore/php-slim4/config/prod/default.inc.php new file mode 100644 index 00000000000..333c7768b43 --- /dev/null +++ b/samples/server/petstore/php-slim4/config/prod/default.inc.php @@ -0,0 +1,35 @@ + 'production', + + // slim framework settings + 'slim.displayErrorDetails' => false, + 'slim.logErrors' => true, + 'slim.logErrorDetails' => true, + + // PDO + 'pdo.dsn' => 'mysql:host=localhost;charset=utf8mb4', + 'pdo.username' => 'root', + 'pdo.password' => 'root', + 'pdo.options' => [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + ], +]; diff --git a/samples/server/petstore/php-slim4/config/prod/example.inc.php b/samples/server/petstore/php-slim4/config/prod/example.inc.php deleted file mode 100644 index 2a2e65d4a59..00000000000 --- a/samples/server/petstore/php-slim4/config/prod/example.inc.php +++ /dev/null @@ -1,96 +0,0 @@ -setModelsNamespace('OpenAPIServer\Model\\'); - -return [ - 'slimSettings' => [ - 'displayErrorDetails' => false, - 'logErrors' => true, - 'logErrorDetails' => true, - ], - - 'tokenAuthenticationOptions' => [ - /** - * Tokens are essentially passwords. You should treat them as such and you should always - * use HTTPS. If the middleware detects insecure usage over HTTP it will return unauthorized - * with a message Required HTTPS for token authentication. This rule is relaxed for requests - * on localhost. To allow insecure usage you must enable it manually by setting secure to - * false. - * Default: true - */ - // 'secure' => true, - - /** - * Alternatively you can list your development host to have relaxed security. - * Default: ['localhost', '127.0.0.1'] - */ - // 'relaxed' => ['localhost', '127.0.0.1'], - - /** - * By default on occurred a fail on authentication, is sent a response on json format with a - * message (`Invalid Token` or `Not found Token`) and with the token (if found), with status - * `401 Unauthorized`. You can customize it by setting a callable function on error option. - * Default: null - */ - // 'error' => null, - ], - - 'mockerOptions' => [ - // 'dataMocker' => $mocker, - - // 'getMockStatusCodeCallback' => function (ServerRequestInterface $request, array $responses) { - // // check if client clearly asks for mocked response - // $pingHeader = 'X-OpenAPIServer-Mock'; - // $pingHeaderCode = 'X-OpenAPIServer-Mock-Code'; - // if ( - // $request->hasHeader($pingHeader) - // && $request->getHeader($pingHeader)[0] === 'ping' - // ) { - // $responses = (array) $responses; - // $requestedResponseCode = ($request->hasHeader($pingHeaderCode)) ? $request->getHeader($pingHeaderCode)[0] : 'default'; - // if (array_key_exists($requestedResponseCode, $responses)) { - // return $requestedResponseCode; - // } - - // // return first response key - // reset($responses); - // return key($responses); - // } - - // return false; - // }, - - // 'afterCallback' => function (ServerRequestInterface $request, ResponseInterface $response) { - // // mark mocked response to distinguish real and fake responses - // return $response->withHeader('X-OpenAPIServer-Mock', 'pong'); - // }, - ], -]; diff --git a/samples/server/petstore/php-slim4/lib/Api/AbstractPetApi.php b/samples/server/petstore/php-slim4/lib/Api/AbstractPetApi.php index ff8c7224526..7975aabc6fe 100644 --- a/samples/server/petstore/php-slim4/lib/Api/AbstractPetApi.php +++ b/samples/server/petstore/php-slim4/lib/Api/AbstractPetApi.php @@ -19,10 +19,11 @@ * NOTE: This class is auto generated by the openapi generator program. * https://github.com/openapitools/openapi-generator * Do not edit the class manually. + * Extend this class with your controller. You can inject dependencies via class constructor, + * @see https://github.com/PHP-DI/Slim-Bridge basic example. */ namespace OpenAPIServer\Api; -use Psr\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Slim\Exception\HttpNotImplementedException; @@ -36,23 +37,6 @@ use Slim\Exception\HttpNotImplementedException; */ abstract class AbstractPetApi { - - /** - * @var ContainerInterface|null Slim app container instance - */ - protected $container; - - /** - * Route Controller constructor receives container - * - * @param ContainerInterface|null $container Slim app container instance - */ - public function __construct(ContainerInterface $container = null) - { - $this->container = $container; - } - - /** * POST addPet * Summary: Add a new pet to the store @@ -60,13 +44,14 @@ abstract class AbstractPetApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function addPet(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function addPet( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $body = $request->getParsedBody(); $message = "How about implementing addPet as a POST method in OpenAPIServer\Api\PetApi class?"; throw new HttpNotImplementedException($request, $message); @@ -78,16 +63,18 @@ abstract class AbstractPetApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments + * @param int $petId Pet id to delete * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function deletePet(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function deletePet( + ServerRequestInterface $request, + ResponseInterface $response, + int $petId + ): ResponseInterface { $headers = $request->getHeaders(); $apiKey = $request->hasHeader('api_key') ? $headers['api_key'] : null; - $petId = $args['petId']; $message = "How about implementing deletePet as a DELETE method in OpenAPIServer\Api\PetApi class?"; throw new HttpNotImplementedException($request, $message); } @@ -100,13 +87,14 @@ abstract class AbstractPetApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function findPetsByStatus(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function findPetsByStatus( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $queryParams = $request->getQueryParams(); $status = (key_exists('status', $queryParams)) ? $queryParams['status'] : null; $message = "How about implementing findPetsByStatus as a GET method in OpenAPIServer\Api\PetApi class?"; @@ -121,14 +109,15 @@ abstract class AbstractPetApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method * @deprecated */ - public function findPetsByTags(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function findPetsByTags( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $queryParams = $request->getQueryParams(); $tags = (key_exists('tags', $queryParams)) ? $queryParams['tags'] : null; $message = "How about implementing findPetsByTags as a GET method in OpenAPIServer\Api\PetApi class?"; @@ -143,14 +132,16 @@ abstract class AbstractPetApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments + * @param int $petId ID of pet to return * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function getPetById(ServerRequestInterface $request, ResponseInterface $response, array $args) - { - $petId = $args['petId']; + public function getPetById( + ServerRequestInterface $request, + ResponseInterface $response, + int $petId + ): ResponseInterface { $message = "How about implementing getPetById as a GET method in OpenAPIServer\Api\PetApi class?"; throw new HttpNotImplementedException($request, $message); } @@ -162,13 +153,14 @@ abstract class AbstractPetApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function updatePet(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function updatePet( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $body = $request->getParsedBody(); $message = "How about implementing updatePet as a PUT method in OpenAPIServer\Api\PetApi class?"; throw new HttpNotImplementedException($request, $message); @@ -180,14 +172,16 @@ abstract class AbstractPetApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments + * @param int $petId ID of pet that needs to be updated * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function updatePetWithForm(ServerRequestInterface $request, ResponseInterface $response, array $args) - { - $petId = $args['petId']; + public function updatePetWithForm( + ServerRequestInterface $request, + ResponseInterface $response, + int $petId + ): ResponseInterface { $body = $request->getParsedBody(); $name = (isset($body['name'])) ? $body['name'] : null; $status = (isset($body['status'])) ? $body['status'] : null; @@ -202,14 +196,16 @@ abstract class AbstractPetApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments + * @param int $petId ID of pet to update * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function uploadFile(ServerRequestInterface $request, ResponseInterface $response, array $args) - { - $petId = $args['petId']; + public function uploadFile( + ServerRequestInterface $request, + ResponseInterface $response, + int $petId + ): ResponseInterface { $body = $request->getParsedBody(); $additionalMetadata = (isset($body['additionalMetadata'])) ? $body['additionalMetadata'] : null; $file = (key_exists('file', $request->getUploadedFiles())) ? $request->getUploadedFiles()['file'] : null; diff --git a/samples/server/petstore/php-slim4/lib/Api/AbstractStoreApi.php b/samples/server/petstore/php-slim4/lib/Api/AbstractStoreApi.php index 2542047a669..0c153911aac 100644 --- a/samples/server/petstore/php-slim4/lib/Api/AbstractStoreApi.php +++ b/samples/server/petstore/php-slim4/lib/Api/AbstractStoreApi.php @@ -19,10 +19,11 @@ * NOTE: This class is auto generated by the openapi generator program. * https://github.com/openapitools/openapi-generator * Do not edit the class manually. + * Extend this class with your controller. You can inject dependencies via class constructor, + * @see https://github.com/PHP-DI/Slim-Bridge basic example. */ namespace OpenAPIServer\Api; -use Psr\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Slim\Exception\HttpNotImplementedException; @@ -36,23 +37,6 @@ use Slim\Exception\HttpNotImplementedException; */ abstract class AbstractStoreApi { - - /** - * @var ContainerInterface|null Slim app container instance - */ - protected $container; - - /** - * Route Controller constructor receives container - * - * @param ContainerInterface|null $container Slim app container instance - */ - public function __construct(ContainerInterface $container = null) - { - $this->container = $container; - } - - /** * DELETE deleteOrder * Summary: Delete purchase order by ID @@ -60,14 +44,16 @@ abstract class AbstractStoreApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments + * @param string $orderId ID of the order that needs to be deleted * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function deleteOrder(ServerRequestInterface $request, ResponseInterface $response, array $args) - { - $orderId = $args['orderId']; + public function deleteOrder( + ServerRequestInterface $request, + ResponseInterface $response, + string $orderId + ): ResponseInterface { $message = "How about implementing deleteOrder as a DELETE method in OpenAPIServer\Api\StoreApi class?"; throw new HttpNotImplementedException($request, $message); } @@ -80,13 +66,14 @@ abstract class AbstractStoreApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function getInventory(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function getInventory( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $message = "How about implementing getInventory as a GET method in OpenAPIServer\Api\StoreApi class?"; throw new HttpNotImplementedException($request, $message); } @@ -99,14 +86,16 @@ abstract class AbstractStoreApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments + * @param int $orderId ID of pet that needs to be fetched * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function getOrderById(ServerRequestInterface $request, ResponseInterface $response, array $args) - { - $orderId = $args['orderId']; + public function getOrderById( + ServerRequestInterface $request, + ResponseInterface $response, + int $orderId + ): ResponseInterface { $message = "How about implementing getOrderById as a GET method in OpenAPIServer\Api\StoreApi class?"; throw new HttpNotImplementedException($request, $message); } @@ -118,13 +107,14 @@ abstract class AbstractStoreApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function placeOrder(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function placeOrder( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $body = $request->getParsedBody(); $message = "How about implementing placeOrder as a POST method in OpenAPIServer\Api\StoreApi class?"; throw new HttpNotImplementedException($request, $message); diff --git a/samples/server/petstore/php-slim4/lib/Api/AbstractUserApi.php b/samples/server/petstore/php-slim4/lib/Api/AbstractUserApi.php index eee2c7d2ccf..fb9b1e5c7b4 100644 --- a/samples/server/petstore/php-slim4/lib/Api/AbstractUserApi.php +++ b/samples/server/petstore/php-slim4/lib/Api/AbstractUserApi.php @@ -19,10 +19,11 @@ * NOTE: This class is auto generated by the openapi generator program. * https://github.com/openapitools/openapi-generator * Do not edit the class manually. + * Extend this class with your controller. You can inject dependencies via class constructor, + * @see https://github.com/PHP-DI/Slim-Bridge basic example. */ namespace OpenAPIServer\Api; -use Psr\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Slim\Exception\HttpNotImplementedException; @@ -36,23 +37,6 @@ use Slim\Exception\HttpNotImplementedException; */ abstract class AbstractUserApi { - - /** - * @var ContainerInterface|null Slim app container instance - */ - protected $container; - - /** - * Route Controller constructor receives container - * - * @param ContainerInterface|null $container Slim app container instance - */ - public function __construct(ContainerInterface $container = null) - { - $this->container = $container; - } - - /** * POST createUser * Summary: Create user @@ -60,13 +44,14 @@ abstract class AbstractUserApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function createUser(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function createUser( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $body = $request->getParsedBody(); $message = "How about implementing createUser as a POST method in OpenAPIServer\Api\UserApi class?"; throw new HttpNotImplementedException($request, $message); @@ -78,13 +63,14 @@ abstract class AbstractUserApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function createUsersWithArrayInput(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function createUsersWithArrayInput( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $body = $request->getParsedBody(); $message = "How about implementing createUsersWithArrayInput as a POST method in OpenAPIServer\Api\UserApi class?"; throw new HttpNotImplementedException($request, $message); @@ -96,13 +82,14 @@ abstract class AbstractUserApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function createUsersWithListInput(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function createUsersWithListInput( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $body = $request->getParsedBody(); $message = "How about implementing createUsersWithListInput as a POST method in OpenAPIServer\Api\UserApi class?"; throw new HttpNotImplementedException($request, $message); @@ -115,14 +102,16 @@ abstract class AbstractUserApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments + * @param string $username The name that needs to be deleted * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function deleteUser(ServerRequestInterface $request, ResponseInterface $response, array $args) - { - $username = $args['username']; + public function deleteUser( + ServerRequestInterface $request, + ResponseInterface $response, + string $username + ): ResponseInterface { $message = "How about implementing deleteUser as a DELETE method in OpenAPIServer\Api\UserApi class?"; throw new HttpNotImplementedException($request, $message); } @@ -134,14 +123,16 @@ abstract class AbstractUserApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments + * @param string $username The name that needs to be fetched. Use user1 for testing. * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function getUserByName(ServerRequestInterface $request, ResponseInterface $response, array $args) - { - $username = $args['username']; + public function getUserByName( + ServerRequestInterface $request, + ResponseInterface $response, + string $username + ): ResponseInterface { $message = "How about implementing getUserByName as a GET method in OpenAPIServer\Api\UserApi class?"; throw new HttpNotImplementedException($request, $message); } @@ -153,13 +144,14 @@ abstract class AbstractUserApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function loginUser(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function loginUser( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $queryParams = $request->getQueryParams(); $username = (key_exists('username', $queryParams)) ? $queryParams['username'] : null; $password = (key_exists('password', $queryParams)) ? $queryParams['password'] : null; @@ -173,13 +165,14 @@ abstract class AbstractUserApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function logoutUser(ServerRequestInterface $request, ResponseInterface $response, array $args) - { + public function logoutUser( + ServerRequestInterface $request, + ResponseInterface $response + ): ResponseInterface { $message = "How about implementing logoutUser as a GET method in OpenAPIServer\Api\UserApi class?"; throw new HttpNotImplementedException($request, $message); } @@ -191,14 +184,16 @@ abstract class AbstractUserApi * * @param ServerRequestInterface $request Request * @param ResponseInterface $response Response - * @param array|null $args Path arguments + * @param string $username name that need to be deleted * * @return ResponseInterface * @throws HttpNotImplementedException to force implementation class to override this method */ - public function updateUser(ServerRequestInterface $request, ResponseInterface $response, array $args) - { - $username = $args['username']; + public function updateUser( + ServerRequestInterface $request, + ResponseInterface $response, + string $username + ): ResponseInterface { $body = $request->getParsedBody(); $message = "How about implementing updateUser as a PUT method in OpenAPIServer\Api\UserApi class?"; throw new HttpNotImplementedException($request, $message); diff --git a/samples/server/petstore/php-slim4/lib/App/RegisterDependencies.php b/samples/server/petstore/php-slim4/lib/App/RegisterDependencies.php new file mode 100644 index 00000000000..8c21bc3dca6 --- /dev/null +++ b/samples/server/petstore/php-slim4/lib/App/RegisterDependencies.php @@ -0,0 +1,71 @@ +addDefinitions([ + // Response factory required as typed argument in next ErrorMiddleware injection + \Psr\Http\Message\ResponseFactoryInterface::class => \DI\factory([\Slim\Factory\AppFactory::class, 'determineResponseFactory']), + + // Slim error middleware + // @see https://www.slimframework.com/docs/v4/middleware/error-handling.html + \Slim\Middleware\ErrorMiddleware::class => \DI\autowire() + ->constructorParameter('displayErrorDetails', \DI\get('slim.displayErrorDetails', false)) + ->constructorParameter('logErrors', \DI\get('slim.logErrors', true)) + ->constructorParameter('logErrorDetails', \DI\get('slim.logErrorDetails', true)), + + // PDO class for database managing + \PDO::class => \DI\create() + ->constructor( + \DI\get('pdo.dsn'), + \DI\get('pdo.username'), + \DI\get('pdo.password'), + \DI\get('pdo.options', null) + ), + ]); + } +} diff --git a/samples/server/petstore/php-slim4/lib/App/RegisterMiddlewares.php b/samples/server/petstore/php-slim4/lib/App/RegisterMiddlewares.php new file mode 100644 index 00000000000..120d91dede9 --- /dev/null +++ b/samples/server/petstore/php-slim4/lib/App/RegisterMiddlewares.php @@ -0,0 +1,64 @@ +addBodyParsingMiddleware(); + + // Add Routing Middleware + $app->addRoutingMiddleware(); + + // Add Error Middleware + $app->add(\Slim\Middleware\ErrorMiddleware::class); + } +} diff --git a/samples/server/petstore/php-slim4/lib/SlimRouter.php b/samples/server/petstore/php-slim4/lib/App/RegisterRoutes.php similarity index 75% rename from samples/server/petstore/php-slim4/lib/SlimRouter.php rename to samples/server/petstore/php-slim4/lib/App/RegisterRoutes.php index 1f81b223d2b..52c5533f2ab 100644 --- a/samples/server/petstore/php-slim4/lib/SlimRouter.php +++ b/samples/server/petstore/php-slim4/lib/App/RegisterRoutes.php @@ -15,39 +15,26 @@ * Generated by: https://github.com/openapitools/openapi-generator.git */ +declare(strict_types=1); + /** * NOTE: This class is auto generated by the openapi generator program. * https://github.com/openapitools/openapi-generator * Do not edit the class manually. */ -namespace OpenAPIServer; +namespace OpenAPIServer\App; -use Slim\Factory\AppFactory; -use Slim\Interfaces\RouteInterface; use Slim\Exception\HttpNotImplementedException; -use Psr\Container\ContainerInterface; -use InvalidArgumentException; -use Dyorg\TokenAuthentication; -use Dyorg\TokenAuthentication\TokenSearch; -use Psr\Http\Message\ServerRequestInterface; -use OpenAPIServer\Mock\OpenApiDataMocker; -use OpenAPIServer\Mock\OpenApiDataMockerRouteMiddleware; -use Slim\Psr7\Factory\ResponseFactory; -use Exception; /** - * SlimRouter Class Doc Comment + * RegisterRoutes Class Doc Comment * * @package OpenAPIServer * @author OpenAPI Generator team * @link https://github.com/openapitools/openapi-generator */ -class SlimRouter +class RegisterRoutes { - - /** @var App instance */ - private $slimApp; - /** @var array[] list of all api operations */ private $operations = [ [ @@ -843,191 +830,36 @@ class SlimRouter ]; /** - * Class constructor + * Add routes to Slim app. * - * @param ContainerInterface|array $settings Either a ContainerInterface or an associative array of app settings + * @param \Slim\App $app Pre-configured Slim application instance * * @throws HttpNotImplementedException When implementation class doesn't exists - * @throws Exception when not supported authorization schema type provided */ - public function __construct($settings = []) + public function __invoke(\Slim\App $app): void { - if ($settings instanceof ContainerInterface) { - // Set container to create App with on AppFactory - AppFactory::setContainer($settings); - } - $this->slimApp = AppFactory::create(); - - // middlewares requires Psr\Container\ContainerInterface - $container = $this->slimApp->getContainer(); - - $authPackage = 'OpenAPIServer\Auth'; - $basicAuthenticator = function (ServerRequestInterface &$request, TokenSearch $tokenSearch) use ($authPackage) { - $message = "How about extending AbstractAuthenticator class by {$authPackage}\BasicAuthenticator?"; - throw new HttpNotImplementedException($request, $message); - }; - $apiKeyAuthenticator = function (ServerRequestInterface &$request, TokenSearch $tokenSearch) use ($authPackage) { - $message = "How about extending AbstractAuthenticator class by {$authPackage}\ApiKeyAuthenticator?"; - throw new HttpNotImplementedException($request, $message); - }; - $oAuthAuthenticator = function (ServerRequestInterface &$request, TokenSearch $tokenSearch) use ($authPackage) { - $message = "How about extending AbstractAuthenticator class by {$authPackage}\OAuthAuthenticator?"; - throw new HttpNotImplementedException($request, $message); - }; - - $userOptions = $this->getSetting($settings, 'tokenAuthenticationOptions', null); - - // mocker options - $mockerOptions = $this->getSetting($settings, 'mockerOptions', null); - $dataMocker = $mockerOptions['dataMocker'] ?? new OpenApiDataMocker(); - $responseFactory = new ResponseFactory(); - $getMockStatusCodeCallback = $mockerOptions['getMockStatusCodeCallback'] ?? null; - $mockAfterCallback = $mockerOptions['afterCallback'] ?? null; - foreach ($this->operations as $operation) { - $callback = function ($request, $response, $arguments) use ($operation) { + $callback = function ($request) use ($operation) { $message = "How about extending {$operation['classname']} by {$operation['apiPackage']}\\{$operation['userClassname']} class implementing {$operation['operationId']} as a {$operation['httpMethod']} method?"; throw new HttpNotImplementedException($request, $message); }; $middlewares = []; if (class_exists("\\{$operation['apiPackage']}\\{$operation['userClassname']}")) { - $callback = "\\{$operation['apiPackage']}\\{$operation['userClassname']}:{$operation['operationId']}"; + // Notice how we register the controller using the class name? + // PHP-DI will instantiate the class for us only when it's actually necessary + $callback = ["\\{$operation['apiPackage']}\\{$operation['userClassname']}", $operation['operationId']]; } - foreach ($operation['authMethods'] as $authMethod) { - switch ($authMethod['type']) { - case 'http': - $authenticatorClassname = "\\{$authPackage}\\BasicAuthenticator"; - if (class_exists($authenticatorClassname)) { - $basicAuthenticator = new $authenticatorClassname($container); - } - - $middlewares[] = new TokenAuthentication($this->getTokenAuthenticationOptions([ - 'authenticator' => $basicAuthenticator, - 'regex' => $authMethod['isBearer'] ? '/Bearer\s+(.*)$/i' : '/Basic\s+(.*)$/i', - 'header' => 'Authorization', - 'parameter' => null, - 'cookie' => null, - 'argument' => null, - ], $userOptions)); - break; - case 'apiKey': - $authenticatorClassname = "\\{$authPackage}\\ApiKeyAuthenticator"; - if (class_exists($authenticatorClassname)) { - $apiKeyAuthenticator = new $authenticatorClassname($container); - } - - $middlewares[] = new TokenAuthentication($this->getTokenAuthenticationOptions([ - 'authenticator' => $apiKeyAuthenticator, - 'regex' => '/^(.*)$/i', - 'header' => $authMethod['isKeyInHeader'] ? $authMethod['keyParamName'] : null, - 'parameter' => $authMethod['isKeyInQuery'] ? $authMethod['keyParamName'] : null, - 'cookie' => $authMethod['isKeyInCookie'] ? $authMethod['keyParamName'] : null, - 'argument' => null, - ], $userOptions)); - break; - case 'oauth2': - $authenticatorClassname = "\\{$authPackage}\\OAuthAuthenticator"; - if (class_exists($authenticatorClassname)) { - $oAuthAuthenticator = new $authenticatorClassname($container, $authMethod['scopes']); - } - - $middlewares[] = new TokenAuthentication($this->getTokenAuthenticationOptions([ - 'authenticator' => $oAuthAuthenticator, - 'regex' => '/Bearer\s+(.*)$/i', - 'header' => 'Authorization', - 'parameter' => null, - 'cookie' => null, - 'argument' => null, - ], $userOptions)); - break; - default: - throw new Exception('Unknown authorization schema type'); - } - } - - if (is_callable($getMockStatusCodeCallback)) { - $mockSchemaResponses = array_map(function ($item) { - return json_decode($item['jsonSchema'], true); - }, $operation['responses']); - $middlewares[] = new OpenApiDataMockerRouteMiddleware($dataMocker, $mockSchemaResponses, $responseFactory, $getMockStatusCodeCallback, $mockAfterCallback); - } - - $this->addRoute( + $route = $app->map( [$operation['httpMethod']], "{$operation['basePathWithoutHost']}{$operation['path']}", - $callback, - $middlewares + $callback )->setName($operation['operationId']); + + foreach ($middlewares as $middleware) { + $route->add($middleware); + } } } - - /** - * Merges user defined options with dynamic params - * - * @param array $staticOptions Required static options - * @param array $userOptions User options - * - * @return array Merged array - */ - private function getTokenAuthenticationOptions(array $staticOptions, array $userOptions = null) - { - if (is_array($userOptions) === false) { - return $staticOptions; - } - - return array_merge($userOptions, $staticOptions); - } - - /** - * Returns app setting by name. - * - * @param ContainerInterface|array $settings Either a ContainerInterface or an associative array of app settings - * @param string $settingName Setting name - * @param mixed $default Default setting value. - * - * @return mixed - */ - private function getSetting($settings, $settingName, $default = null) - { - if ($settings instanceof ContainerInterface && $settings->has($settingName)) { - return $settings->get($settingName); - } elseif (is_array($settings) && array_key_exists($settingName, $settings)) { - return $settings[$settingName]; - } - - return $default; - } - - /** - * Add route with multiple methods - * - * @param string[] $methods Numeric array of HTTP method names - * @param string $pattern The route URI pattern - * @param callable|string $callable The route callback routine - * @param array|null $middlewares List of middlewares - * - * @return RouteInterface - * - * @throws InvalidArgumentException If the route pattern isn't a string - */ - public function addRoute(array $methods, string $pattern, $callable, $middlewares = []) - { - $route = $this->slimApp->map($methods, $pattern, $callable); - foreach ($middlewares as $middleware) { - $route->add($middleware); - } - return $route; - } - - /** - * Returns Slim Framework instance - * - * @return App - */ - public function getSlimApp() - { - return $this->slimApp; - } } diff --git a/samples/server/petstore/php-slim4/lib/Auth/AbstractAuthenticator.php b/samples/server/petstore/php-slim4/lib/Auth/AbstractAuthenticator.php index de2e6011364..699fd1fdbc7 100644 --- a/samples/server/petstore/php-slim4/lib/Auth/AbstractAuthenticator.php +++ b/samples/server/petstore/php-slim4/lib/Auth/AbstractAuthenticator.php @@ -22,7 +22,6 @@ */ namespace OpenAPIServer\Auth; -use Psr\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; use Dyorg\TokenAuthentication; use Dyorg\TokenAuthentication\TokenSearch; @@ -37,12 +36,6 @@ use Dyorg\TokenAuthentication\Exceptions\UnauthorizedExceptionInterface; */ abstract class AbstractAuthenticator { - - /** - * @var ContainerInterface|null Slim app container instance - */ - protected $container; - /** * @var string[]|null List of required scopes */ @@ -62,12 +55,10 @@ abstract class AbstractAuthenticator /** * Authenticator constructor * - * @param ContainerInterface|null $container Slim app container instance - * @param string[]|null $requiredScope List of required scopes + * @param string[]|null $requiredScope List of required scopes */ - public function __construct(ContainerInterface $container = null, $requiredScope = null) + public function __construct($requiredScope = null) { - $this->container = $container; $this->requiredScope = $requiredScope; } diff --git a/samples/server/petstore/php-slim4/public/.htaccess b/samples/server/petstore/php-slim4/public/.htaccess index f6a2ceb3952..57c2185f0e4 100644 --- a/samples/server/petstore/php-slim4/public/.htaccess +++ b/samples/server/petstore/php-slim4/public/.htaccess @@ -1,3 +1,7 @@ + + SetEnv APP_ENV 'production' + + RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f diff --git a/samples/server/petstore/php-slim4/public/index.php b/samples/server/petstore/php-slim4/public/index.php index 9993535af75..e336d00f81b 100644 --- a/samples/server/petstore/php-slim4/public/index.php +++ b/samples/server/petstore/php-slim4/public/index.php @@ -22,48 +22,63 @@ require_once __DIR__ . '/../vendor/autoload.php'; -use OpenAPIServer\SlimRouter; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\ResponseInterface; -use OpenAPIServer\Mock\OpenApiDataMocker; +use DI\Bridge\Slim\Bridge; +use DI\ContainerBuilder; +use OpenAPIServer\App\RegisterDependencies; +use OpenAPIServer\App\RegisterRoutes; +use OpenAPIServer\App\RegisterMiddlewares; +use Slim\Factory\ServerRequestCreatorFactory; +use Slim\ResponseEmitter; -// load config file -$config = []; -if (is_array($prodConfig = @include(__DIR__ . '/../config/prod/config.inc.php'))) { - $config = $prodConfig; -} elseif (is_array($devConfig = @include(__DIR__ . '/../config/dev/config.inc.php'))) { - $config = $devConfig; -} else { - throw new InvalidArgumentException('Config file missed or broken.'); +// Instantiate PHP-DI ContainerBuilder +$builder = new ContainerBuilder(); + +// consider prod by default +$env; +switch (strtolower($_SERVER['APP_ENV'] ?? 'prod')) { + case 'development': + case 'dev': + $env = 'dev'; + break; + case 'production': + case 'prod': + default: + $env = 'prod'; } -$router = new SlimRouter($config); -$app = $router->getSlimApp(); +// Main configuration +$builder->addDefinitions(__DIR__ . "/../config/{$env}/default.inc.php"); -// Parse json, form data and xml -$app->addBodyParsingMiddleware(); +// Config file for the environment if exists +$userConfig = __DIR__ . "/../config/{$env}/config.inc.php"; +if (file_exists($userConfig)) { + $builder->addDefinitions($userConfig); +} -/** - * The routing middleware should be added before the ErrorMiddleware - * Otherwise exceptions thrown from it will not be handled - */ -$app->addRoutingMiddleware(); +// Set up dependencies +$dependencies = new RegisterDependencies(); +$dependencies($builder); -/** - * Add Error Handling Middleware - * - * @param bool $displayErrorDetails -> Should be set to false in production - * @param bool $logErrors -> Parameter is passed to the default ErrorHandler - * @param bool $logErrorDetails -> Display error details in error log - * which can be replaced by a callable of your choice. +// Build PHP-DI Container instance +$container = $builder->build(); - * Note: This middleware should be added last. It will not handle any exceptions/errors - * for middleware added after it. - */ -$app->addErrorMiddleware( - $config['slimSettings']['displayErrorDetails'] ?? false, - $config['slimSettings']['logErrors'] ?? true, - $config['slimSettings']['logErrorDetails'] ?? true -); +// Instantiate the app +$app = Bridge::create($container); -$app->run(); +// Register middleware +$middleware = new RegisterMiddlewares(); +$middleware($app); + +// Register routes +// yes, it's anti-pattern you shouldn't get deps from container directly +$routes = $container->get(RegisterRoutes::class); +$routes($app); + +// Create Request object from globals +$serverRequestCreator = ServerRequestCreatorFactory::create(); +$request = $serverRequestCreator->createServerRequestFromGlobals(); + +// Run App & Emit Response +$response = $app->handle($request); +$responseEmitter = new ResponseEmitter(); +$responseEmitter->emit($response);