From 6b6410cf0ccce3e3feee2b27daa786b95a5a7ade Mon Sep 17 00:00:00 2001
From: Yuriy Belenko <yura-bely@mail.ru>
Date: Tue, 3 Mar 2020 18:53:57 +0300
Subject: [PATCH] [Slim4] Add Data Mocker middleware (#4978)

* [Slim4] Store response schemas

* [Slim4] Add Data Mocker middleware

* [Slim4] Enhance Slim router

* [Slim4] Enhance config

* [Slim4] Fix data format key in object mocking

* [Slim4] Add tests for Data Mocker middleware

* [Slim4] Add Mock feature documentation

* [Slim4] Refresh samples
---
 .../languages/PhpSlim4ServerCodegen.java      |   6 +
 .../php-slim4-server/README.mustache          |   4 +
 .../php-slim4-server/SlimRouter.mustache      |  48 +-
 .../resources/php-slim4-server/index.mustache |  32 +
 .../php-slim4-server/mock_server.mustache     | 135 ++++
 .../openapi_data_mocker_middleware.mustache   | 195 +++++
 ...enapi_data_mocker_middleware_test.mustache | 282 +++++++
 samples/server/petstore/php-slim4/README.md   |   4 +
 .../petstore/php-slim4/docs/MockServer.md     | 135 ++++
 samples/server/petstore/php-slim4/index.php   |  32 +
 .../lib/Mock/OpenApiDataMockerMiddleware.php  | 186 +++++
 .../petstore/php-slim4/lib/SlimRouter.php     | 738 +++++++++++++++++-
 .../Mock/OpenApiDataMockerMiddlewareTest.php  | 273 +++++++
 13 files changed, 2058 insertions(+), 12 deletions(-)
 create mode 100644 modules/openapi-generator/src/main/resources/php-slim4-server/mock_server.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/php-slim4-server/openapi_data_mocker_middleware.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/php-slim4-server/openapi_data_mocker_middleware_test.mustache
 create mode 100644 samples/server/petstore/php-slim4/docs/MockServer.md
 create mode 100644 samples/server/petstore/php-slim4/lib/Mock/OpenApiDataMockerMiddleware.php
 create mode 100644 samples/server/petstore/php-slim4/test/Mock/OpenApiDataMockerMiddlewareTest.php

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 2aa867d6aa0d..2eefb7de347d 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
@@ -113,6 +113,9 @@ public void processOpts() {
         additionalProperties.put("interfacesSrcPath", "./" + toSrcPath(interfacesPackage, srcBasePath));
         additionalProperties.put("interfacesTestPath", "./" + toSrcPath(interfacesPackage, testBasePath));
 
+        // external docs folder
+        additionalProperties.put("docsBasePath", "./" + docsBasePath);
+
         if (additionalProperties.containsKey(PSR7_IMPLEMENTATION)) {
             this.setPsr7Implementation((String) additionalProperties.get(PSR7_IMPLEMENTATION));
         }
@@ -150,6 +153,9 @@ public void processOpts() {
         supportingFiles.add(new SupportingFile("openapi_data_mocker_interface.mustache", toSrcPath(mockPackage, srcBasePath), toInterfaceName("OpenApiDataMocker") + ".php"));
         supportingFiles.add(new SupportingFile("openapi_data_mocker.mustache", toSrcPath(mockPackage, srcBasePath), "OpenApiDataMocker.php"));
         supportingFiles.add(new SupportingFile("openapi_data_mocker_test.mustache", toSrcPath(mockPackage, testBasePath), "OpenApiDataMockerTest.php"));
+        supportingFiles.add(new SupportingFile("openapi_data_mocker_middleware.mustache", toSrcPath(mockPackage, srcBasePath), "OpenApiDataMockerMiddleware.php"));
+        supportingFiles.add(new SupportingFile("openapi_data_mocker_middleware_test.mustache", toSrcPath(mockPackage, testBasePath), "OpenApiDataMockerMiddlewareTest.php"));
+        supportingFiles.add(new SupportingFile("mock_server.mustache", docsBasePath, "MockServer.md"));
 
         // traits of ported utils
         supportingFiles.add(new SupportingFile("string_utils_trait.mustache", toSrcPath(utilsPackage, srcBasePath), toTraitName("StringUtils") + ".php"));
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 8788d0d4676e..99317cce997c 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
@@ -57,6 +57,8 @@ Command | Target
 `$ composer test` | All tests
 `$ composer test-apis` | Apis tests
 `$ composer test-models` | Models tests
+`$ composer test-mock` | Mock feature tests
+`$ composer test-utils` | Utils tests
 
 #### Config
 
@@ -110,6 +112,8 @@ Switch on option in `./index.php`:
 +++ $app->addErrorMiddleware(true, true, true);
 ```
 
+## [Mock Server Documentation]({{docsBasePath}}/MockServer.md)
+
 {{#generateApiDocs}}
 ## API Endpoints
 
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
index eda33894db3a..d67b154d06d8 100644
--- a/modules/openapi-generator/src/main/resources/php-slim4-server/SlimRouter.mustache
+++ b/modules/openapi-generator/src/main/resources/php-slim4-server/SlimRouter.mustache
@@ -41,6 +41,8 @@ use Dyorg\TokenAuthentication;
 use Dyorg\TokenAuthentication\TokenSearch;
 use Psr\Http\Message\ServerRequestInterface;
 use {{invokerPackage}}\Middleware\JsonBodyParserMiddleware;
+use {{mockPackage}}\OpenApiDataMocker;
+use {{mockPackage}}\OpenApiDataMockerMiddleware;
 use Exception;
 
 /**
@@ -69,6 +71,15 @@ class SlimRouter
             'classname' => '{{classname}}',
             'userClassname' => '{{userClassname}}',
             'operationId' => '{{operationId}}',
+            'responses' => [
+            {{#responses}}
+                '{{#isDefault}}default{{/isDefault}}{{^isDefault}}{{code}}{{/isDefault}}' => [
+                    'code' => {{code}},
+                    'message' => '{{message}}',
+                    'jsonSchema' => '{{{jsonSchema}}}',
+                ],
+            {{/responses}}
+            ],
             'authMethods' => [
                 {{#hasAuthMethods}}
                 {{#authMethods}}
@@ -161,12 +172,13 @@ class SlimRouter
         };
         {{/hasAuthMethods}}
 
-        $userOptions = null;
-        if ($settings instanceof ContainerInterface && $settings->has('tokenAuthenticationOptions')) {
-            $userOptions = $settings->get('tokenAuthenticationOptions');
-        } elseif (is_array($settings) && isset($settings['tokenAuthenticationOptions'])) {
-            $userOptions = $settings['tokenAuthenticationOptions'];
-        }
+        $userOptions = $this->getSetting($settings, 'tokenAuthenticationOptions', null);
+
+        // mocker options
+        $mockerOptions = $this->getSetting($settings, 'mockerOptions', null);
+        $dataMocker = $mockerOptions['dataMocker'] ?? new OpenApiDataMocker();
+        $getMockResponseCallback = $mockerOptions['getMockResponseCallback'] ?? null;
+        $mockAfterCallback = $mockerOptions['afterCallback'] ?? null;
 
         foreach ($this->operations as $operation) {
             $callback = function ($request, $response, $arguments) use ($operation) {
@@ -235,6 +247,10 @@ class SlimRouter
             }
             {{/hasAuthMethods}}
 
+            if (is_callable($getMockResponseCallback)) {
+                $middlewares[] = new OpenApiDataMockerMiddleware($dataMocker, $operation['responses'], $getMockResponseCallback, $mockAfterCallback);
+            }
+
             $this->addRoute(
                 [$operation['httpMethod']],
                 "{$operation['basePathWithoutHost']}{$operation['path']}",
@@ -261,6 +277,26 @@ class SlimRouter
         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
      *
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 8735f6d93cd9..cb98beb8f30c 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
@@ -14,6 +14,9 @@
 require_once __DIR__ . '/vendor/autoload.php';
 
 use {{invokerPackage}}\SlimRouter;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use {{mockPackage}}\OpenApiDataMocker;
 {{/apiInfo}}
 
 $config = [];
@@ -51,6 +54,35 @@ $config['tokenAuthenticationOptions'] = [
     // 'error' => null,
 ];
 
+/**
+ * Mocker Middleware options.
+ */
+$config['mockerOptions'] = [
+    // 'dataMocker' => new OpenApiDataMocker(),
+
+    // 'getMockResponseCallback' => function (ServerRequestInterface $request, array $responses) {
+    //     // check if client clearly asks for mocked response
+    //     if (
+    //         $request->hasHeader('X-{{invokerPackage}}-Mock')
+    //         && $request->getHeader('X-{{invokerPackage}}-Mock')[0] === 'ping'
+    //     ) {
+    //         if (array_key_exists('default', $responses)) {
+    //             return $responses['default'];
+    //         }
+
+    //         // return first response
+    //         return $responses[array_key_first($responses)];
+    //     }
+
+    //     return false;
+    // },
+
+    // 'afterCallback' => function ($request, $response) {
+    //     // mark mocked response to distinguish real and fake responses
+    //     return $response->withHeader('X-{{invokerPackage}}-Mock', 'pong');
+    // },
+];
+
 $router = new SlimRouter($config);
 $app = $router->getSlimApp();
 
diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/mock_server.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/mock_server.mustache
new file mode 100644
index 000000000000..15d8c2dcadff
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/php-slim4-server/mock_server.mustache
@@ -0,0 +1,135 @@
+# {{packageName}} - PHP Slim 4 Server library for {{appName}}
+
+## Mock Server Documentation
+
+### Mocker Options
+To enable mock server uncomment these lines in `index.php` config file:
+
+```php
+/**
+ * Mocker Middleware options.
+ */
+$config['mockerOptions'] = [
+    'dataMocker' => new OpenApiDataMocker(),
+
+    'getMockResponseCallback' => function (ServerRequestInterface $request, array $responses) {
+        // check if client clearly asks for mocked response
+        if (
+            $request->hasHeader('X-{{invokerPackage}}-Mock')
+            && $request->getHeader('X-{{invokerPackage}}-Mock')[0] === 'ping'
+        ) {
+            if (array_key_exists('default', $responses)) {
+                return $responses['default'];
+            }
+
+            // return first response
+            return $responses[array_key_first($responses)];
+        }
+
+        return false;
+    },
+
+    'afterCallback' => function ($request, $response) {
+        // mark mocked response to distinguish real and fake responses
+        return $response->withHeader('X-{{invokerPackage}}-Mock', 'pong');
+    },
+];
+```
+
+* `dataMocker` is mocker class instance. To create custom data mocker extend `{{mockPackage}}\{{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}}`.
+* `getMockResponseCallback` is callback before mock data generation. Above example shows how to enable mock feature for only requests with `{{X-{{invokerPackage}}}}-mock: ping` HTTP header. Adjust requests filtering to fit your project requirements. This function must return single response schema from `$responses` array parameter. **Mock feature is disabled when callback returns anything beside array.**
+* `afterCallback` is callback executed after mock data generation. Most obvious use case is append specific HTTP headers to distinguish real and fake responses. **This function must always return response instance.**
+
+### Supported features
+
+All data types supported except specific string formats: `email`, `uuid`, `password` which are poorly implemented.
+
+#### Data Types Support
+
+| Data Type | Data Format |      Supported     |
+|:---------:|:-----------:|:------------------:|
+| `integer` | `int32`     | :white_check_mark: |
+| `integer` | `int64`     | :white_check_mark: |
+| `number`  | `float`     | :white_check_mark: |
+| `number`  | `double`    |                    |
+| `string`  | `byte`      | :white_check_mark: |
+| `string`  | `binary`    | :white_check_mark: |
+| `boolean` |             | :white_check_mark: |
+| `string`  | `date`      | :white_check_mark: |
+| `string`  | `date-time` | :white_check_mark: |
+| `string`  | `password`  | :white_check_mark: |
+| `string`  | `email`     | :white_check_mark: |
+| `string`  | `uuid`      | :white_check_mark: |
+
+#### Data Options Support
+
+| Data Type   |         Option         |      Supported     |
+|:-----------:|:----------------------:|:------------------:|
+| `string`    | `minLength`            | :white_check_mark: |
+| `string`    | `maxLength`            | :white_check_mark: |
+| `string`    | `enum`                 | :white_check_mark: |
+| `string`    | `pattern`              |                    |
+| `integer`   | `minimum`              | :white_check_mark: |
+| `integer`   | `maximum`              | :white_check_mark: |
+| `integer`   | `exclusiveMinimum`     | :white_check_mark: |
+| `integer`   | `exclusiveMaximum`     | :white_check_mark: |
+| `number`    | `minimum`              | :white_check_mark: |
+| `number`    | `maximum`              | :white_check_mark: |
+| `number`    | `exclusiveMinimum`     | :white_check_mark: |
+| `number`    | `exclusiveMaximum`     | :white_check_mark: |
+| `array`     | `items`                | :white_check_mark: |
+| `array`     | `additionalItems`      |                    |
+| `array`     | `minItems`             | :white_check_mark: |
+| `array`     | `maxItems`             | :white_check_mark: |
+| `array`     | `uniqueItems`          |                    |
+| `object`    | `properties`           | :white_check_mark: |
+| `object`    | `maxProperties`        |                    |
+| `object`    | `minProperties`        |                    |
+| `object`    | `patternProperties`    |                    |
+| `object`    | `additionalProperties` |                    |
+| `object`    | `required`             |                    |
+| `*`         | `$ref`                 | :white_check_mark: |
+| `*`         | `allOf`                |                    |
+| `*`         | `anyOf`                |                    |
+| `*`         | `oneOf`                |                    |
+| `*`         | `not`                  |                    |
+
+### Known Limitations
+
+Avoid circular refs in your schema. Schema below can cause infinite loop and `Out of Memory` PHP error:
+```yml
+# ModelA has reference to ModelB while ModelB has reference to ModelA.
+# Mock server will produce huge nested JSON example and ended with `Out of Memory` error.
+definitions:
+  ModelA:
+    type: object
+    properties:
+      model_b:
+        $ref: '#/definitions/ModelB'
+  ModelB:
+    type: array
+    items:
+      $ref: '#/definitions/ModelA'
+```
+
+Don't ref scalar types, because generator will not produce models which mock server can find. So schema below will cause error:
+```yml
+# generated build contains only `OuterComposite` model class which referenced to not existed `OuterNumber`, `OuterString`, `OuterBoolean` classes
+# mock server cannot mock `OuterComposite` model and throws exception
+definitions:
+  OuterComposite:
+    type: object
+    properties:
+      my_number:
+        $ref: '#/definitions/OuterNumber'
+      my_string:
+        $ref: '#/definitions/OuterString'
+      my_boolean:
+        $ref: '#/definitions/OuterBoolean'
+  OuterNumber:
+    type: number
+  OuterString:
+    type: string
+  OuterBoolean:
+    type: boolean
+```
diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/openapi_data_mocker_middleware.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/openapi_data_mocker_middleware.mustache
new file mode 100644
index 000000000000..c7c6192f7685
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/php-slim4-server/openapi_data_mocker_middleware.mustache
@@ -0,0 +1,195 @@
+<?php
+
+/**
+ * OpenApiDataMockerMiddleware
+ *
+ * PHP version 7.1
+ *
+ * @package {{invokerPackage}}
+ * @author  OpenAPI Generator team
+ * @link    https://github.com/openapitools/openapi-generator
+ */
+
+/**{{#apiInfo}}{{#appName}}
+ * {{{appName}}}
+ *
+ {{/appName}}
+ {{#appDescription}}
+ * {{{appDescription}}}
+ {{/appDescription}}
+ {{#version}}
+ * The version of the OpenAPI document: {{{version}}}
+ {{/version}}
+ {{#infoEmail}}
+ * Contact: {{{infoEmail}}}
+ {{/infoEmail}}
+ * Generated by: https://github.com/openapitools/openapi-generator.git
+ */
+
+/**
+ * NOTE: This class is auto generated by the openapi generator program.
+ * https://github.com/openapitools/openapi-generator
+ * Do not edit the class manually.
+ */
+namespace {{mockPackage}};
+
+use Slim\Factory\AppFactory;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use {{mockPackage}}\{{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}};
+use InvalidArgumentException;
+
+/**
+ * OpenApiDataMockerMiddleware Class Doc Comment
+ *
+ * @package {{mockPackage}}
+ * @author  OpenAPI Generator team
+ * @link    https://github.com/openapitools/openapi-generator
+ */
+final class OpenApiDataMockerMiddleware implements MiddlewareInterface
+{
+    /**
+     * @var {{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}} DataMocker.
+     */
+    private $mocker;
+
+    /**
+     * @var array Array of responses schemas.
+     */
+    private $responses;
+
+    /**
+     * @var callable|null Custom callback to select mocked response.
+     */
+    private $getMockResponseCallback;
+
+    /**
+     * @var callable|null Custom after callback.
+     */
+    private $afterCallback;
+
+    /**
+     * Class constructor.
+     *
+     * @param {{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}} $mocker                  DataMocker.
+     * @param array                      $responses               Array of responses schemas.
+     * @param callable|null              $getMockResponseCallback Custom callback to select mocked response.
+     * Mock feature is disabled when this argument is null.
+     * @example $getMockResponseCallback = function (ServerRequestInterface $request, array $responses) {
+     *     // check if client clearly asks for mocked response
+     *     if (
+     *         $request->hasHeader('X-{{invokerPackage}}-Mock')
+     *         && $request->header('X-{{invokerPackage}}-Mock')[0] === 'ping'
+     *     ) {
+     *         return $responses[array_key_first($responses)];
+     *     }
+     *     return false;
+     * };
+     * @param callable|null              $afterCallback           After callback.
+     * Function must return response instance.
+     * @example $afterCallback = function (ServerRequestInterface $request, ResponseInterface $response) {
+     *     // mark mocked response to distinguish real and fake responses
+     *     return $response->withHeader('X-{{invokerPackage}}-Mock', 'pong');
+     * };
+     */
+    public function __construct(
+        {{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}} $mocker,
+        array $responses,
+        $getMockResponseCallback = null,
+        $afterCallback = null
+    ) {
+        $this->mocker = $mocker;
+        $this->responses = $responses;
+        if (is_callable($getMockResponseCallback)) {
+            $this->getMockResponseCallback = $getMockResponseCallback;
+        } elseif ($getMockResponseCallback !== null) {
+            // wrong argument type
+            throw new InvalidArgumentException('\$getMockResponseCallback must be closure or null');
+        }
+
+        if (is_callable($afterCallback)) {
+            $this->afterCallback = $afterCallback;
+        } elseif ($afterCallback !== null) {
+            // wrong argument type
+            throw new InvalidArgumentException('\$afterCallback must be closure or null');
+        }
+    }
+
+    /**
+     * Parse incoming JSON input into a native PHP format
+     *
+     * @param ServerRequestInterface  $request HTTP request
+     * @param RequestHandlerInterface $handler Request handler
+     *
+     * @return ResponseInterface HTTP response
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $customCallback = $this->getMockResponseCallback;
+        $customAfterCallback = $this->afterCallback;
+        $mockedResponse = (is_callable($customCallback)) ? $customCallback($request, $this->responses) : null;
+        if (
+            is_array($mockedResponse)
+            && array_key_exists('code', $mockedResponse)
+            && array_key_exists('jsonSchema', $mockedResponse)
+        ) {
+            // response schema succesfully selected, we can mock it now
+            $statusCode = ($mockedResponse['code'] === 0) ? 200 : $mockedResponse['code'];
+            $contentType = '*/*';
+            $response = AppFactory::determineResponseFactory()->createResponse($statusCode);
+            $responseSchema = json_decode($mockedResponse['jsonSchema'], true);
+
+            if (is_array($responseSchema) && array_key_exists('headers', $responseSchema)) {
+                // response schema contains headers definitions, apply them one by one
+                foreach ($responseSchema['headers'] as $headerName => $headerDefinition) {
+                    $response = $response->withHeader($headerName, $this->mocker->mockFromSchema($headerDefinition['schema']));
+                }
+            }
+
+            if (
+                is_array($responseSchema)
+                && array_key_exists('content', $responseSchema)
+                && !empty($responseSchema['content'])
+            ) {
+                // response schema contains body definition
+                $responseContentSchema = null;
+                foreach ($responseSchema['content'] as $schemaContentType => $schemaDefinition) {
+                    // we can respond in JSON format when any(*/*) content-type allowed
+                    // or JSON(application/json) content-type specifically defined
+                    if (
+                        $schemaContentType === '*/*'
+                        || strtolower(substr($schemaContentType, 0, 16)) === 'application/json'
+                    ) {
+                        $contentType = 'application/json';
+                        $responseContentSchema = $schemaDefinition['schema'];
+                    }
+                }
+
+                if ($contentType === 'application/json') {
+                    $responseBody = $this->mocker->mockFromSchema($responseContentSchema);
+                    $response->getBody()->write(json_encode($responseBody));
+                } else {
+                    // notify developer that only application/json response supported so far
+                    $response->getBody()->write('Mock feature supports only "application/json" content-type!');
+                }
+            }
+
+            // after callback applied only when mocked response schema has been selected
+            if (is_callable($customAfterCallback)) {
+                $response = $customAfterCallback($request, $response);
+            }
+
+            // no reason to execute following middlewares (auth, validation etc.)
+            // return mocked response and end connection
+            return $response
+                ->withHeader('Content-Type', $contentType);
+        }
+
+        // no response selected, mock feature disabled
+        // execute following middlewares
+        return $handler->handle($request);
+    }
+}
+{{/apiInfo}}
diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/openapi_data_mocker_middleware_test.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/openapi_data_mocker_middleware_test.mustache
new file mode 100644
index 000000000000..2ea22728930a
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/php-slim4-server/openapi_data_mocker_middleware_test.mustache
@@ -0,0 +1,282 @@
+<?php
+
+/**
+ * OpenApiDataMockerMiddlewareTest
+ *
+ * PHP version 7.1
+ *
+ * @package {{invokerPackage}}
+ * @author  OpenAPI Generator team
+ * @link    https://github.com/openapitools/openapi-generator
+ */
+
+/**{{#apiInfo}}{{#invokerPackage}}
+ * {{{invokerPackage}}}
+ *
+ {{/invokerPackage}}
+ {{#appDescription}}
+ * {{{appDescription}}}
+ {{/appDescription}}
+ {{#version}}
+ * The version of the OpenAPI document: {{{version}}}
+ {{/version}}
+ {{#infoEmail}}
+ * Contact: {{{infoEmail}}}
+ {{/infoEmail}}
+ * Generated by: https://github.com/openapitools/openapi-generator.git
+ */
+
+/**
+ * NOTE: This class is auto generated by the openapi generator program.
+ * https://github.com/openapitools/openapi-generator
+ * Do not edit the class manually.
+ */
+namespace {{mockPackage}};
+
+use {{mockPackage}}\OpenApiDataMockerMiddleware;
+use {{mockPackage}}\OpenApiDataMocker;
+use Slim\Factory\AppFactory;
+use Slim\Factory\ServerRequestCreatorFactory;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use PHPUnit\Framework\TestCase;
+use StdClass;
+
+/**
+ * OpenApiDataMockerMiddlewareTest Class Doc Comment
+ *
+ * @package {{mockPackage}}
+ * @author  OpenAPI Generator team
+ * @link    https://github.com/openapitools/openapi-generator
+ * @coversDefaultClass \{{mockPackage}}\OpenApiDataMockerMiddleware
+ */
+class OpenApiDataMockerMiddlewareTest extends TestCase
+{
+    /**
+     * @covers ::__construct
+     * @dataProvider provideConstructCorrectArguments
+     */
+    public function testConstructor(
+        $mocker,
+        $responses,
+        $getMockResponseCallback,
+        $afterCallback
+    ) {
+        $middleware = new OpenApiDataMockerMiddleware($mocker, $responses, $getMockResponseCallback, $afterCallback);
+        $this->assertInstanceOf(OpenApiDataMockerMiddleware::class, $middleware);
+        $this->assertNotNull($middleware);
+    }
+
+    public function provideConstructCorrectArguments()
+    {
+        $getMockResponseCallback = function () {
+            return false;
+        };
+        $afterCallback = function () {
+            return false;
+        };
+        return [
+            [new OpenApiDataMocker(), [], null, null],
+            [new OpenApiDataMocker(), [], $getMockResponseCallback, $afterCallback],
+        ];
+    }
+
+    /**
+     * @covers ::__construct
+     * @dataProvider provideConstructInvalidArguments
+     * @expectedException \InvalidArgumentException
+     * @expectedException \TypeError
+     */
+    public function testConstructorWithInvalidArguments(
+        $mocker,
+        $responses,
+        $getMockResponseCallback,
+        $afterCallback
+    ) {
+        $middleware = new OpenApiDataMockerMiddleware($mocker, $responses, $getMockResponseCallback, $afterCallback);
+    }
+
+    public function provideConstructInvalidArguments()
+    {
+        return [
+            'getMockResponseCallback not callable' => [
+                new OpenApiDataMocker(), [], 'foobar', null,
+            ],
+            'afterCallback not callable' => [
+                new OpenApiDataMocker(), [], null, 'foobar',
+            ],
+        ];
+    }
+
+    /**
+     * @covers ::process
+     * @dataProvider provideProcessArguments
+     */
+    public function testProcess(
+        $mocker,
+        $responses,
+        $getMockResponseCallback,
+        $afterCallback,
+        $request,
+        $expectedStatusCode,
+        $expectedHeaders,
+        $notExpectedHeaders,
+        $expectedBody
+    ) {
+
+        // Create a stub for the RequestHandlerInterface interface.
+        $handler = $this->createMock(RequestHandlerInterface::class);
+        $handler->method('handle')
+             ->willReturn(AppFactory::determineResponseFactory()->createResponse());
+
+        $middleware = new OpenApiDataMockerMiddleware(
+            $mocker,
+            $responses,
+            $getMockResponseCallback,
+            $afterCallback
+        );
+        $response = $middleware->process($request, $handler);
+
+        // check status code
+        $this->assertSame($expectedStatusCode, $response->getStatusCode());
+
+        // check http headers in request
+        foreach ($expectedHeaders as $expectedHeader => $expectedHeaderValue) {
+            $this->assertTrue($response->hasHeader($expectedHeader));
+            if ($expectedHeaderValue !== '*') {
+                $this->assertSame($expectedHeaderValue, $response->getHeader($expectedHeader)[0]);
+            }
+        }
+        foreach ($notExpectedHeaders as $notExpectedHeader) {
+            $this->assertFalse($response->hasHeader($notExpectedHeader));
+        }
+
+        // check body
+        if (is_array($expectedBody)) {
+            // random values, check keys only
+            foreach ($expectedBody as $attribute => $value) {
+                $this->assertObjectHasAttribute($attribute, json_decode((string) $response->getBody(), false));
+            }
+        } else {
+            $this->assertEquals($expectedBody, (string) $response->getBody());
+        }
+    }
+
+    public function provideProcessArguments()
+    {
+        $mocker = new OpenApiDataMocker();
+        $isMockResponseRequired = function (ServerRequestInterface $request) {
+            $mockHttpHeader = 'X-{{invokerPackage}}-Mock';
+            return $request->hasHeader($mockHttpHeader)
+                && $request->getHeader($mockHttpHeader)[0] === 'ping';
+        };
+
+        $getMockResponseCallback = function (ServerRequestInterface $request, array $responses) use ($isMockResponseRequired) {
+            if ($isMockResponseRequired($request)) {
+                if (array_key_exists('default', $responses)) {
+                    return $responses['default'];
+                }
+
+                // return first response
+                return $responses[array_key_first($responses)];
+            }
+
+            return false;
+        };
+
+        $afterCallback = function ($request, $response) use ($isMockResponseRequired) {
+            if ($isMockResponseRequired($request)) {
+                $response = $response->withHeader('X-{{invokerPackage}}-Mock', 'pong');
+            }
+
+            return $response;
+        };
+
+        $responses = [
+            '400' => [
+                'code' => 400,
+                'jsonSchema' => json_encode([
+                    'description' => 'Bad Request Response',
+                    'content' => new StdClass(),
+                ]),
+            ],
+            'default' => [
+                'code' => 201,
+                'jsonSchema' => json_encode([
+                    'description' => 'Success Response',
+                    'headers' => [
+                        'X-Location' => ['schema' => ['type' => 'string']],
+                        'X-Created-Id' => ['schema' => ['type' => 'integer']],
+                    ],
+                    'content' => [
+                        'application/json;encoding=utf-8' => ['schema' => ['type' => 'object', 'properties' => ['id' => ['type' => 'integer'], 'className' => ['type' => 'string'], 'declawed' => ['type' => 'boolean']]]],
+                    ],
+                ]),
+            ],
+        ];
+
+        $responsesXmlOnly = [
+            'default' => [
+                'code' => 201,
+                'jsonSchema' => json_encode([
+                    'description' => 'Success Response',
+                    'content' => [
+                        'application/xml' => [
+                            'schema' => [
+                                'type' => 'string',
+                            ],
+                        ],
+                    ],
+                ]),
+            ],
+        ];
+
+        $requestFactory = ServerRequestCreatorFactory::create();
+
+        return [
+            'callbacks null' => [
+                $mocker,
+                $responses,
+                null,
+                null,
+                $requestFactory->createServerRequestFromGlobals(),
+                200,
+                [],
+                ['X-{{invokerPackage}}-Mock', 'x-location', 'x-created-id'],
+                '',
+            ],
+            'xml not supported' => [
+                $mocker,
+                $responsesXmlOnly,
+                $getMockResponseCallback,
+                $afterCallback,
+                $requestFactory
+                    ->createServerRequestFromGlobals()
+                    ->withHeader('X-{{invokerPackage}}-Mock', 'ping'),
+                201,
+                ['X-{{invokerPackage}}-Mock' => 'pong', 'content-type' => '*/*'],
+                ['x-location', 'x-created-id'],
+                'Mock feature supports only "application/json" content-type!',
+            ],
+            'mock response default schema' => [
+                $mocker,
+                $responses,
+                $getMockResponseCallback,
+                $afterCallback,
+                $requestFactory
+                    ->createServerRequestFromGlobals()
+                    ->withHeader('X-{{invokerPackage}}-Mock', 'ping'),
+                201,
+                ['X-{{invokerPackage}}-Mock' => 'pong', 'content-type' => 'application/json', 'x-location' => '*', 'x-created-id' => '*'],
+                [],
+                [
+                    'id' => 1,
+                    'className' => 'cat',
+                    'declawed' => false,
+                ],
+            ],
+        ];
+    }
+}
+{{/apiInfo}}
diff --git a/samples/server/petstore/php-slim4/README.md b/samples/server/petstore/php-slim4/README.md
index b0efeb9dc660..325111aa6243 100644
--- a/samples/server/petstore/php-slim4/README.md
+++ b/samples/server/petstore/php-slim4/README.md
@@ -46,6 +46,8 @@ Command | Target
 `$ composer test` | All tests
 `$ composer test-apis` | Apis tests
 `$ composer test-models` | Models tests
+`$ composer test-mock` | Mock feature tests
+`$ composer test-utils` | Utils tests
 
 #### Config
 
@@ -99,6 +101,8 @@ Switch on option in `./index.php`:
 +++ $app->addErrorMiddleware(true, true, true);
 ```
 
+## [Mock Server Documentation](./docs/MockServer.md)
+
 ## API Endpoints
 
 All URIs are relative to *http://petstore.swagger.io:80/v2*
diff --git a/samples/server/petstore/php-slim4/docs/MockServer.md b/samples/server/petstore/php-slim4/docs/MockServer.md
new file mode 100644
index 000000000000..76f5668ad51d
--- /dev/null
+++ b/samples/server/petstore/php-slim4/docs/MockServer.md
@@ -0,0 +1,135 @@
+# php-base - PHP Slim 4 Server library for OpenAPI Petstore
+
+## Mock Server Documentation
+
+### Mocker Options
+To enable mock server uncomment these lines in `index.php` config file:
+
+```php
+/**
+ * Mocker Middleware options.
+ */
+$config['mockerOptions'] = [
+    'dataMocker' => new OpenApiDataMocker(),
+
+    'getMockResponseCallback' => function (ServerRequestInterface $request, array $responses) {
+        // check if client clearly asks for mocked response
+        if (
+            $request->hasHeader('X-OpenAPIServer-Mock')
+            && $request->getHeader('X-OpenAPIServer-Mock')[0] === 'ping'
+        ) {
+            if (array_key_exists('default', $responses)) {
+                return $responses['default'];
+            }
+
+            // return first response
+            return $responses[array_key_first($responses)];
+        }
+
+        return false;
+    },
+
+    'afterCallback' => function ($request, $response) {
+        // mark mocked response to distinguish real and fake responses
+        return $response->withHeader('X-OpenAPIServer-Mock', 'pong');
+    },
+];
+```
+
+* `dataMocker` is mocker class instance. To create custom data mocker extend `OpenAPIServer\Mock\OpenApiDataMockerInterface`.
+* `getMockResponseCallback` is callback before mock data generation. Above example shows how to enable mock feature for only requests with `{{X-OpenAPIServer}}-mock: ping` HTTP header. Adjust requests filtering to fit your project requirements. This function must return single response schema from `$responses` array parameter. **Mock feature is disabled when callback returns anything beside array.**
+* `afterCallback` is callback executed after mock data generation. Most obvious use case is append specific HTTP headers to distinguish real and fake responses. **This function must always return response instance.**
+
+### Supported features
+
+All data types supported except specific string formats: `email`, `uuid`, `password` which are poorly implemented.
+
+#### Data Types Support
+
+| Data Type | Data Format |      Supported     |
+|:---------:|:-----------:|:------------------:|
+| `integer` | `int32`     | :white_check_mark: |
+| `integer` | `int64`     | :white_check_mark: |
+| `number`  | `float`     | :white_check_mark: |
+| `number`  | `double`    |                    |
+| `string`  | `byte`      | :white_check_mark: |
+| `string`  | `binary`    | :white_check_mark: |
+| `boolean` |             | :white_check_mark: |
+| `string`  | `date`      | :white_check_mark: |
+| `string`  | `date-time` | :white_check_mark: |
+| `string`  | `password`  | :white_check_mark: |
+| `string`  | `email`     | :white_check_mark: |
+| `string`  | `uuid`      | :white_check_mark: |
+
+#### Data Options Support
+
+| Data Type   |         Option         |      Supported     |
+|:-----------:|:----------------------:|:------------------:|
+| `string`    | `minLength`            | :white_check_mark: |
+| `string`    | `maxLength`            | :white_check_mark: |
+| `string`    | `enum`                 | :white_check_mark: |
+| `string`    | `pattern`              |                    |
+| `integer`   | `minimum`              | :white_check_mark: |
+| `integer`   | `maximum`              | :white_check_mark: |
+| `integer`   | `exclusiveMinimum`     | :white_check_mark: |
+| `integer`   | `exclusiveMaximum`     | :white_check_mark: |
+| `number`    | `minimum`              | :white_check_mark: |
+| `number`    | `maximum`              | :white_check_mark: |
+| `number`    | `exclusiveMinimum`     | :white_check_mark: |
+| `number`    | `exclusiveMaximum`     | :white_check_mark: |
+| `array`     | `items`                | :white_check_mark: |
+| `array`     | `additionalItems`      |                    |
+| `array`     | `minItems`             | :white_check_mark: |
+| `array`     | `maxItems`             | :white_check_mark: |
+| `array`     | `uniqueItems`          |                    |
+| `object`    | `properties`           | :white_check_mark: |
+| `object`    | `maxProperties`        |                    |
+| `object`    | `minProperties`        |                    |
+| `object`    | `patternProperties`    |                    |
+| `object`    | `additionalProperties` |                    |
+| `object`    | `required`             |                    |
+| `*`         | `$ref`                 | :white_check_mark: |
+| `*`         | `allOf`                |                    |
+| `*`         | `anyOf`                |                    |
+| `*`         | `oneOf`                |                    |
+| `*`         | `not`                  |                    |
+
+### Known Limitations
+
+Avoid circular refs in your schema. Schema below can cause infinite loop and `Out of Memory` PHP error:
+```yml
+# ModelA has reference to ModelB while ModelB has reference to ModelA.
+# Mock server will produce huge nested JSON example and ended with `Out of Memory` error.
+definitions:
+  ModelA:
+    type: object
+    properties:
+      model_b:
+        $ref: '#/definitions/ModelB'
+  ModelB:
+    type: array
+    items:
+      $ref: '#/definitions/ModelA'
+```
+
+Don't ref scalar types, because generator will not produce models which mock server can find. So schema below will cause error:
+```yml
+# generated build contains only `OuterComposite` model class which referenced to not existed `OuterNumber`, `OuterString`, `OuterBoolean` classes
+# mock server cannot mock `OuterComposite` model and throws exception
+definitions:
+  OuterComposite:
+    type: object
+    properties:
+      my_number:
+        $ref: '#/definitions/OuterNumber'
+      my_string:
+        $ref: '#/definitions/OuterString'
+      my_boolean:
+        $ref: '#/definitions/OuterBoolean'
+  OuterNumber:
+    type: number
+  OuterString:
+    type: string
+  OuterBoolean:
+    type: boolean
+```
diff --git a/samples/server/petstore/php-slim4/index.php b/samples/server/petstore/php-slim4/index.php
index 70cb6dede1a4..8baabc9b1398 100644
--- a/samples/server/petstore/php-slim4/index.php
+++ b/samples/server/petstore/php-slim4/index.php
@@ -14,6 +14,9 @@
 require_once __DIR__ . '/vendor/autoload.php';
 
 use OpenAPIServer\SlimRouter;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use OpenAPIServer\Mock\OpenApiDataMocker;
 
 $config = [];
 
@@ -50,6 +53,35 @@
     // 'error' => null,
 ];
 
+/**
+ * Mocker Middleware options.
+ */
+$config['mockerOptions'] = [
+    // 'dataMocker' => new OpenApiDataMocker(),
+
+    // 'getMockResponseCallback' => function (ServerRequestInterface $request, array $responses) {
+    //     // check if client clearly asks for mocked response
+    //     if (
+    //         $request->hasHeader('X-OpenAPIServer-Mock')
+    //         && $request->getHeader('X-OpenAPIServer-Mock')[0] === 'ping'
+    //     ) {
+    //         if (array_key_exists('default', $responses)) {
+    //             return $responses['default'];
+    //         }
+
+    //         // return first response
+    //         return $responses[array_key_first($responses)];
+    //     }
+
+    //     return false;
+    // },
+
+    // 'afterCallback' => function ($request, $response) {
+    //     // mark mocked response to distinguish real and fake responses
+    //     return $response->withHeader('X-OpenAPIServer-Mock', 'pong');
+    // },
+];
+
 $router = new SlimRouter($config);
 $app = $router->getSlimApp();
 
diff --git a/samples/server/petstore/php-slim4/lib/Mock/OpenApiDataMockerMiddleware.php b/samples/server/petstore/php-slim4/lib/Mock/OpenApiDataMockerMiddleware.php
new file mode 100644
index 000000000000..60ba929a403a
--- /dev/null
+++ b/samples/server/petstore/php-slim4/lib/Mock/OpenApiDataMockerMiddleware.php
@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * OpenApiDataMockerMiddleware
+ *
+ * PHP version 7.1
+ *
+ * @package OpenAPIServer
+ * @author  OpenAPI Generator team
+ * @link    https://github.com/openapitools/openapi-generator
+ */
+
+/**
+ * OpenAPI Petstore
+ *
+ * This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\
+ * The version of the OpenAPI document: 1.0.0
+ * Generated by: https://github.com/openapitools/openapi-generator.git
+ */
+
+/**
+ * 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\Mock;
+
+use Slim\Factory\AppFactory;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use OpenAPIServer\Mock\OpenApiDataMockerInterface;
+use InvalidArgumentException;
+
+/**
+ * OpenApiDataMockerMiddleware Class Doc Comment
+ *
+ * @package OpenAPIServer\Mock
+ * @author  OpenAPI Generator team
+ * @link    https://github.com/openapitools/openapi-generator
+ */
+final class OpenApiDataMockerMiddleware implements MiddlewareInterface
+{
+    /**
+     * @var OpenApiDataMockerInterface DataMocker.
+     */
+    private $mocker;
+
+    /**
+     * @var array Array of responses schemas.
+     */
+    private $responses;
+
+    /**
+     * @var callable|null Custom callback to select mocked response.
+     */
+    private $getMockResponseCallback;
+
+    /**
+     * @var callable|null Custom after callback.
+     */
+    private $afterCallback;
+
+    /**
+     * Class constructor.
+     *
+     * @param OpenApiDataMockerInterface $mocker                  DataMocker.
+     * @param array                      $responses               Array of responses schemas.
+     * @param callable|null              $getMockResponseCallback Custom callback to select mocked response.
+     * Mock feature is disabled when this argument is null.
+     * @example $getMockResponseCallback = function (ServerRequestInterface $request, array $responses) {
+     *     // check if client clearly asks for mocked response
+     *     if (
+     *         $request->hasHeader('X-OpenAPIServer-Mock')
+     *         && $request->header('X-OpenAPIServer-Mock')[0] === 'ping'
+     *     ) {
+     *         return $responses[array_key_first($responses)];
+     *     }
+     *     return false;
+     * };
+     * @param callable|null              $afterCallback           After callback.
+     * Function must return response instance.
+     * @example $afterCallback = function (ServerRequestInterface $request, ResponseInterface $response) {
+     *     // mark mocked response to distinguish real and fake responses
+     *     return $response->withHeader('X-OpenAPIServer-Mock', 'pong');
+     * };
+     */
+    public function __construct(
+        OpenApiDataMockerInterface $mocker,
+        array $responses,
+        $getMockResponseCallback = null,
+        $afterCallback = null
+    ) {
+        $this->mocker = $mocker;
+        $this->responses = $responses;
+        if (is_callable($getMockResponseCallback)) {
+            $this->getMockResponseCallback = $getMockResponseCallback;
+        } elseif ($getMockResponseCallback !== null) {
+            // wrong argument type
+            throw new InvalidArgumentException('\$getMockResponseCallback must be closure or null');
+        }
+
+        if (is_callable($afterCallback)) {
+            $this->afterCallback = $afterCallback;
+        } elseif ($afterCallback !== null) {
+            // wrong argument type
+            throw new InvalidArgumentException('\$afterCallback must be closure or null');
+        }
+    }
+
+    /**
+     * Parse incoming JSON input into a native PHP format
+     *
+     * @param ServerRequestInterface  $request HTTP request
+     * @param RequestHandlerInterface $handler Request handler
+     *
+     * @return ResponseInterface HTTP response
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $customCallback = $this->getMockResponseCallback;
+        $customAfterCallback = $this->afterCallback;
+        $mockedResponse = (is_callable($customCallback)) ? $customCallback($request, $this->responses) : null;
+        if (
+            is_array($mockedResponse)
+            && array_key_exists('code', $mockedResponse)
+            && array_key_exists('jsonSchema', $mockedResponse)
+        ) {
+            // response schema succesfully selected, we can mock it now
+            $statusCode = ($mockedResponse['code'] === 0) ? 200 : $mockedResponse['code'];
+            $contentType = '*/*';
+            $response = AppFactory::determineResponseFactory()->createResponse($statusCode);
+            $responseSchema = json_decode($mockedResponse['jsonSchema'], true);
+
+            if (is_array($responseSchema) && array_key_exists('headers', $responseSchema)) {
+                // response schema contains headers definitions, apply them one by one
+                foreach ($responseSchema['headers'] as $headerName => $headerDefinition) {
+                    $response = $response->withHeader($headerName, $this->mocker->mockFromSchema($headerDefinition['schema']));
+                }
+            }
+
+            if (
+                is_array($responseSchema)
+                && array_key_exists('content', $responseSchema)
+                && !empty($responseSchema['content'])
+            ) {
+                // response schema contains body definition
+                $responseContentSchema = null;
+                foreach ($responseSchema['content'] as $schemaContentType => $schemaDefinition) {
+                    // we can respond in JSON format when any(*/*) content-type allowed
+                    // or JSON(application/json) content-type specifically defined
+                    if (
+                        $schemaContentType === '*/*'
+                        || strtolower(substr($schemaContentType, 0, 16)) === 'application/json'
+                    ) {
+                        $contentType = 'application/json';
+                        $responseContentSchema = $schemaDefinition['schema'];
+                    }
+                }
+
+                if ($contentType === 'application/json') {
+                    $responseBody = $this->mocker->mockFromSchema($responseContentSchema);
+                    $response->getBody()->write(json_encode($responseBody));
+                } else {
+                    // notify developer that only application/json response supported so far
+                    $response->getBody()->write('Mock feature supports only "application/json" content-type!');
+                }
+            }
+
+            // after callback applied only when mocked response schema has been selected
+            if (is_callable($customAfterCallback)) {
+                $response = $customAfterCallback($request, $response);
+            }
+
+            // no reason to execute following middlewares (auth, validation etc.)
+            // return mocked response and end connection
+            return $response
+                ->withHeader('Content-Type', $contentType);
+        }
+
+        // no response selected, mock feature disabled
+        // execute following middlewares
+        return $handler->handle($request);
+    }
+}
diff --git a/samples/server/petstore/php-slim4/lib/SlimRouter.php b/samples/server/petstore/php-slim4/lib/SlimRouter.php
index ac60e425307b..440f59aa6ecb 100644
--- a/samples/server/petstore/php-slim4/lib/SlimRouter.php
+++ b/samples/server/petstore/php-slim4/lib/SlimRouter.php
@@ -33,6 +33,8 @@
 use Dyorg\TokenAuthentication\TokenSearch;
 use Psr\Http\Message\ServerRequestInterface;
 use OpenAPIServer\Middleware\JsonBodyParserMiddleware;
+use OpenAPIServer\Mock\OpenApiDataMocker;
+use OpenAPIServer\Mock\OpenApiDataMockerMiddleware;
 use Exception;
 
 /**
@@ -58,6 +60,22 @@ class SlimRouter
             'classname' => 'AbstractAnotherFakeApi',
             'userClassname' => 'AnotherFakeApi',
             'operationId' => 'call123TestSpecialTags',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/Client"
+      }
+    }
+  }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -69,6 +87,16 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'createXmlItem',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -80,6 +108,22 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'fakeOuterBooleanSerialize',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'Output boolean',
+                    'jsonSchema' => '{
+  "description" : "Output boolean",
+  "content" : {
+    "*/*" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/OuterBoolean"
+      }
+    }
+  }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -91,6 +135,22 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'fakeOuterCompositeSerialize',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'Output composite',
+                    'jsonSchema' => '{
+  "description" : "Output composite",
+  "content" : {
+    "*/*" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/OuterComposite"
+      }
+    }
+  }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -102,6 +162,22 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'fakeOuterNumberSerialize',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'Output number',
+                    'jsonSchema' => '{
+  "description" : "Output number",
+  "content" : {
+    "*/*" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/OuterNumber"
+      }
+    }
+  }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -113,6 +189,22 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'fakeOuterStringSerialize',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'Output string',
+                    'jsonSchema' => '{
+  "description" : "Output string",
+  "content" : {
+    "*/*" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/OuterString"
+      }
+    }
+  }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -124,6 +216,16 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'testBodyWithFileSchema',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'Success',
+                    'jsonSchema' => '{
+  "description" : "Success",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -135,6 +237,16 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'testBodyWithQueryParams',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'Success',
+                    'jsonSchema' => '{
+  "description" : "Success",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -146,6 +258,22 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'testClientModel',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/Client"
+      }
+    }
+  }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -157,6 +285,24 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'testEndpointParameters',
+            'responses' => [
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid username supplied',
+                    'jsonSchema' => '{
+  "description" : "Invalid username supplied",
+  "content" : { }
+}',
+                ],
+                '404' => [
+                    'code' => 404,
+                    'message' => 'User not found',
+                    'jsonSchema' => '{
+  "description" : "User not found",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
                 // http security schema named 'http_basic_test'
                 [
@@ -176,6 +322,24 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'testEnumParameters',
+            'responses' => [
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid request',
+                    'jsonSchema' => '{
+  "description" : "Invalid request",
+  "content" : { }
+}',
+                ],
+                '404' => [
+                    'code' => 404,
+                    'message' => 'Not found',
+                    'jsonSchema' => '{
+  "description" : "Not found",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -187,6 +351,16 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'testGroupParameters',
+            'responses' => [
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Someting wrong',
+                    'jsonSchema' => '{
+  "description" : "Someting wrong",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -198,6 +372,16 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'testInlineAdditionalProperties',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -209,6 +393,16 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'testJsonFormData',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -220,6 +414,16 @@ class SlimRouter
             'classname' => 'AbstractFakeApi',
             'userClassname' => 'FakeApi',
             'operationId' => 'testQueryParameterCollectionFormat',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'Success',
+                    'jsonSchema' => '{
+  "description" : "Success",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -231,6 +435,22 @@ class SlimRouter
             'classname' => 'AbstractFakeClassnameTags123Api',
             'userClassname' => 'FakeClassnameTags123Api',
             'operationId' => 'testClassname',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/Client"
+      }
+    }
+  }
+}',
+                ],
+            ],
             'authMethods' => [
                 // apiKey security schema named 'api_key_query'
                 [
@@ -254,6 +474,24 @@ class SlimRouter
             'classname' => 'AbstractPetApi',
             'userClassname' => 'PetApi',
             'operationId' => 'addPet',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : { }
+}',
+                ],
+                '405' => [
+                    'code' => 405,
+                    'message' => 'Invalid input',
+                    'jsonSchema' => '{
+  "description" : "Invalid input",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
                 // oauth2 security schema named 'petstore_auth'
                 [
@@ -277,6 +515,41 @@ class SlimRouter
             'classname' => 'AbstractPetApi',
             'userClassname' => 'PetApi',
             'operationId' => 'findPetsByStatus',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/xml" : {
+      "schema" : {
+        "type" : "array",
+        "items" : {
+          "$ref" : "#/components/schemas/Pet"
+        }
+      }
+    },
+    "application/json" : {
+      "schema" : {
+        "type" : "array",
+        "items" : {
+          "$ref" : "#/components/schemas/Pet"
+        }
+      }
+    }
+  }
+}',
+                ],
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid status value',
+                    'jsonSchema' => '{
+  "description" : "Invalid status value",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
                 // oauth2 security schema named 'petstore_auth'
                 [
@@ -300,6 +573,41 @@ class SlimRouter
             'classname' => 'AbstractPetApi',
             'userClassname' => 'PetApi',
             'operationId' => 'findPetsByTags',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/xml" : {
+      "schema" : {
+        "type" : "array",
+        "items" : {
+          "$ref" : "#/components/schemas/Pet"
+        }
+      }
+    },
+    "application/json" : {
+      "schema" : {
+        "type" : "array",
+        "items" : {
+          "$ref" : "#/components/schemas/Pet"
+        }
+      }
+    }
+  }
+}',
+                ],
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid tag value',
+                    'jsonSchema' => '{
+  "description" : "Invalid tag value",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
                 // oauth2 security schema named 'petstore_auth'
                 [
@@ -323,6 +631,40 @@ class SlimRouter
             'classname' => 'AbstractPetApi',
             'userClassname' => 'PetApi',
             'operationId' => 'updatePet',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : { }
+}',
+                ],
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid ID supplied',
+                    'jsonSchema' => '{
+  "description" : "Invalid ID supplied",
+  "content" : { }
+}',
+                ],
+                '404' => [
+                    'code' => 404,
+                    'message' => 'Pet not found',
+                    'jsonSchema' => '{
+  "description" : "Pet not found",
+  "content" : { }
+}',
+                ],
+                '405' => [
+                    'code' => 405,
+                    'message' => 'Validation exception',
+                    'jsonSchema' => '{
+  "description" : "Validation exception",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
                 // oauth2 security schema named 'petstore_auth'
                 [
@@ -346,6 +688,24 @@ class SlimRouter
             'classname' => 'AbstractPetApi',
             'userClassname' => 'PetApi',
             'operationId' => 'deletePet',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : { }
+}',
+                ],
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid pet value',
+                    'jsonSchema' => '{
+  "description" : "Invalid pet value",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
                 // oauth2 security schema named 'petstore_auth'
                 [
@@ -369,6 +729,43 @@ class SlimRouter
             'classname' => 'AbstractPetApi',
             'userClassname' => 'PetApi',
             'operationId' => 'getPetById',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/xml" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/Pet"
+      }
+    },
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/Pet"
+      }
+    }
+  }
+}',
+                ],
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid ID supplied',
+                    'jsonSchema' => '{
+  "description" : "Invalid ID supplied",
+  "content" : { }
+}',
+                ],
+                '404' => [
+                    'code' => 404,
+                    'message' => 'Pet not found',
+                    'jsonSchema' => '{
+  "description" : "Pet not found",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
                 // apiKey security schema named 'api_key'
                 [
@@ -392,6 +789,16 @@ class SlimRouter
             'classname' => 'AbstractPetApi',
             'userClassname' => 'PetApi',
             'operationId' => 'updatePetWithForm',
+            'responses' => [
+                '405' => [
+                    'code' => 405,
+                    'message' => 'Invalid input',
+                    'jsonSchema' => '{
+  "description" : "Invalid input",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
                 // oauth2 security schema named 'petstore_auth'
                 [
@@ -415,6 +822,22 @@ class SlimRouter
             'classname' => 'AbstractPetApi',
             'userClassname' => 'PetApi',
             'operationId' => 'uploadFile',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/ApiResponse"
+      }
+    }
+  }
+}',
+                ],
+            ],
             'authMethods' => [
                 // oauth2 security schema named 'petstore_auth'
                 [
@@ -438,6 +861,22 @@ class SlimRouter
             'classname' => 'AbstractPetApi',
             'userClassname' => 'PetApi',
             'operationId' => 'uploadFileWithRequiredFile',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/ApiResponse"
+      }
+    }
+  }
+}',
+                ],
+            ],
             'authMethods' => [
                 // oauth2 security schema named 'petstore_auth'
                 [
@@ -461,6 +900,26 @@ class SlimRouter
             'classname' => 'AbstractStoreApi',
             'userClassname' => 'StoreApi',
             'operationId' => 'getInventory',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/json" : {
+      "schema" : {
+        "type" : "object",
+        "additionalProperties" : {
+          "type" : "integer",
+          "format" : "int32"
+        }
+      }
+    }
+  }
+}',
+                ],
+            ],
             'authMethods' => [
                 // apiKey security schema named 'api_key'
                 [
@@ -484,6 +943,35 @@ class SlimRouter
             'classname' => 'AbstractStoreApi',
             'userClassname' => 'StoreApi',
             'operationId' => 'placeOrder',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/xml" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/Order"
+      }
+    },
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/Order"
+      }
+    }
+  }
+}',
+                ],
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid Order',
+                    'jsonSchema' => '{
+  "description" : "Invalid Order",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -495,6 +983,24 @@ class SlimRouter
             'classname' => 'AbstractStoreApi',
             'userClassname' => 'StoreApi',
             'operationId' => 'deleteOrder',
+            'responses' => [
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid ID supplied',
+                    'jsonSchema' => '{
+  "description" : "Invalid ID supplied",
+  "content" : { }
+}',
+                ],
+                '404' => [
+                    'code' => 404,
+                    'message' => 'Order not found',
+                    'jsonSchema' => '{
+  "description" : "Order not found",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -506,6 +1012,43 @@ class SlimRouter
             'classname' => 'AbstractStoreApi',
             'userClassname' => 'StoreApi',
             'operationId' => 'getOrderById',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/xml" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/Order"
+      }
+    },
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/Order"
+      }
+    }
+  }
+}',
+                ],
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid ID supplied',
+                    'jsonSchema' => '{
+  "description" : "Invalid ID supplied",
+  "content" : { }
+}',
+                ],
+                '404' => [
+                    'code' => 404,
+                    'message' => 'Order not found',
+                    'jsonSchema' => '{
+  "description" : "Order not found",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -517,6 +1060,16 @@ class SlimRouter
             'classname' => 'AbstractUserApi',
             'userClassname' => 'UserApi',
             'operationId' => 'createUser',
+            'responses' => [
+                'default' => [
+                    'code' => 0,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -528,6 +1081,16 @@ class SlimRouter
             'classname' => 'AbstractUserApi',
             'userClassname' => 'UserApi',
             'operationId' => 'createUsersWithArrayInput',
+            'responses' => [
+                'default' => [
+                    'code' => 0,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -539,6 +1102,16 @@ class SlimRouter
             'classname' => 'AbstractUserApi',
             'userClassname' => 'UserApi',
             'operationId' => 'createUsersWithListInput',
+            'responses' => [
+                'default' => [
+                    'code' => 0,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -550,6 +1123,51 @@ class SlimRouter
             'classname' => 'AbstractUserApi',
             'userClassname' => 'UserApi',
             'operationId' => 'loginUser',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "headers" : {
+    "X-Rate-Limit" : {
+      "description" : "calls per hour allowed by the user",
+      "schema" : {
+        "type" : "integer",
+        "format" : "int32"
+      }
+    },
+    "X-Expires-After" : {
+      "description" : "date in UTC when token expires",
+      "schema" : {
+        "type" : "string",
+        "format" : "date-time"
+      }
+    }
+  },
+  "content" : {
+    "application/xml" : {
+      "schema" : {
+        "type" : "string"
+      }
+    },
+    "application/json" : {
+      "schema" : {
+        "type" : "string"
+      }
+    }
+  }
+}',
+                ],
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid username/password supplied',
+                    'jsonSchema' => '{
+  "description" : "Invalid username/password supplied",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -561,6 +1179,16 @@ class SlimRouter
             'classname' => 'AbstractUserApi',
             'userClassname' => 'UserApi',
             'operationId' => 'logoutUser',
+            'responses' => [
+                'default' => [
+                    'code' => 0,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -572,6 +1200,24 @@ class SlimRouter
             'classname' => 'AbstractUserApi',
             'userClassname' => 'UserApi',
             'operationId' => 'deleteUser',
+            'responses' => [
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid username supplied',
+                    'jsonSchema' => '{
+  "description" : "Invalid username supplied",
+  "content" : { }
+}',
+                ],
+                '404' => [
+                    'code' => 404,
+                    'message' => 'User not found',
+                    'jsonSchema' => '{
+  "description" : "User not found",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -583,6 +1229,43 @@ class SlimRouter
             'classname' => 'AbstractUserApi',
             'userClassname' => 'UserApi',
             'operationId' => 'getUserByName',
+            'responses' => [
+                'default' => [
+                    'code' => 200,
+                    'message' => 'successful operation',
+                    'jsonSchema' => '{
+  "description" : "successful operation",
+  "content" : {
+    "application/xml" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/User"
+      }
+    },
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/User"
+      }
+    }
+  }
+}',
+                ],
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid username supplied',
+                    'jsonSchema' => '{
+  "description" : "Invalid username supplied",
+  "content" : { }
+}',
+                ],
+                '404' => [
+                    'code' => 404,
+                    'message' => 'User not found',
+                    'jsonSchema' => '{
+  "description" : "User not found",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -594,6 +1277,24 @@ class SlimRouter
             'classname' => 'AbstractUserApi',
             'userClassname' => 'UserApi',
             'operationId' => 'updateUser',
+            'responses' => [
+                '400' => [
+                    'code' => 400,
+                    'message' => 'Invalid user supplied',
+                    'jsonSchema' => '{
+  "description" : "Invalid user supplied",
+  "content" : { }
+}',
+                ],
+                '404' => [
+                    'code' => 404,
+                    'message' => 'User not found',
+                    'jsonSchema' => '{
+  "description" : "User not found",
+  "content" : { }
+}',
+                ],
+            ],
             'authMethods' => [
             ],
         ],
@@ -631,12 +1332,13 @@ public function __construct($settings = [])
             throw new Exception($message);
         };
 
-        $userOptions = null;
-        if ($settings instanceof ContainerInterface && $settings->has('tokenAuthenticationOptions')) {
-            $userOptions = $settings->get('tokenAuthenticationOptions');
-        } elseif (is_array($settings) && isset($settings['tokenAuthenticationOptions'])) {
-            $userOptions = $settings['tokenAuthenticationOptions'];
-        }
+        $userOptions = $this->getSetting($settings, 'tokenAuthenticationOptions', null);
+
+        // mocker options
+        $mockerOptions = $this->getSetting($settings, 'mockerOptions', null);
+        $dataMocker = $mockerOptions['dataMocker'] ?? new OpenApiDataMocker();
+        $getMockResponseCallback = $mockerOptions['getMockResponseCallback'] ?? null;
+        $mockAfterCallback = $mockerOptions['afterCallback'] ?? null;
 
         foreach ($this->operations as $operation) {
             $callback = function ($request, $response, $arguments) use ($operation) {
@@ -703,6 +1405,10 @@ public function __construct($settings = [])
                 }
             }
 
+            if (is_callable($getMockResponseCallback)) {
+                $middlewares[] = new OpenApiDataMockerMiddleware($dataMocker, $operation['responses'], $getMockResponseCallback, $mockAfterCallback);
+            }
+
             $this->addRoute(
                 [$operation['httpMethod']],
                 "{$operation['basePathWithoutHost']}{$operation['path']}",
@@ -729,6 +1435,26 @@ private function getTokenAuthenticationOptions(array $staticOptions, array $user
         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
      *
diff --git a/samples/server/petstore/php-slim4/test/Mock/OpenApiDataMockerMiddlewareTest.php b/samples/server/petstore/php-slim4/test/Mock/OpenApiDataMockerMiddlewareTest.php
new file mode 100644
index 000000000000..67dbc36fd327
--- /dev/null
+++ b/samples/server/petstore/php-slim4/test/Mock/OpenApiDataMockerMiddlewareTest.php
@@ -0,0 +1,273 @@
+<?php
+
+/**
+ * OpenApiDataMockerMiddlewareTest
+ *
+ * PHP version 7.1
+ *
+ * @package OpenAPIServer
+ * @author  OpenAPI Generator team
+ * @link    https://github.com/openapitools/openapi-generator
+ */
+
+/**
+ * OpenAPIServer
+ *
+ * This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\
+ * The version of the OpenAPI document: 1.0.0
+ * Generated by: https://github.com/openapitools/openapi-generator.git
+ */
+
+/**
+ * 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\Mock;
+
+use OpenAPIServer\Mock\OpenApiDataMockerMiddleware;
+use OpenAPIServer\Mock\OpenApiDataMocker;
+use Slim\Factory\AppFactory;
+use Slim\Factory\ServerRequestCreatorFactory;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use PHPUnit\Framework\TestCase;
+use StdClass;
+
+/**
+ * OpenApiDataMockerMiddlewareTest Class Doc Comment
+ *
+ * @package OpenAPIServer\Mock
+ * @author  OpenAPI Generator team
+ * @link    https://github.com/openapitools/openapi-generator
+ * @coversDefaultClass \OpenAPIServer\Mock\OpenApiDataMockerMiddleware
+ */
+class OpenApiDataMockerMiddlewareTest extends TestCase
+{
+    /**
+     * @covers ::__construct
+     * @dataProvider provideConstructCorrectArguments
+     */
+    public function testConstructor(
+        $mocker,
+        $responses,
+        $getMockResponseCallback,
+        $afterCallback
+    ) {
+        $middleware = new OpenApiDataMockerMiddleware($mocker, $responses, $getMockResponseCallback, $afterCallback);
+        $this->assertInstanceOf(OpenApiDataMockerMiddleware::class, $middleware);
+        $this->assertNotNull($middleware);
+    }
+
+    public function provideConstructCorrectArguments()
+    {
+        $getMockResponseCallback = function () {
+            return false;
+        };
+        $afterCallback = function () {
+            return false;
+        };
+        return [
+            [new OpenApiDataMocker(), [], null, null],
+            [new OpenApiDataMocker(), [], $getMockResponseCallback, $afterCallback],
+        ];
+    }
+
+    /**
+     * @covers ::__construct
+     * @dataProvider provideConstructInvalidArguments
+     * @expectedException \InvalidArgumentException
+     * @expectedException \TypeError
+     */
+    public function testConstructorWithInvalidArguments(
+        $mocker,
+        $responses,
+        $getMockResponseCallback,
+        $afterCallback
+    ) {
+        $middleware = new OpenApiDataMockerMiddleware($mocker, $responses, $getMockResponseCallback, $afterCallback);
+    }
+
+    public function provideConstructInvalidArguments()
+    {
+        return [
+            'getMockResponseCallback not callable' => [
+                new OpenApiDataMocker(), [], 'foobar', null,
+            ],
+            'afterCallback not callable' => [
+                new OpenApiDataMocker(), [], null, 'foobar',
+            ],
+        ];
+    }
+
+    /**
+     * @covers ::process
+     * @dataProvider provideProcessArguments
+     */
+    public function testProcess(
+        $mocker,
+        $responses,
+        $getMockResponseCallback,
+        $afterCallback,
+        $request,
+        $expectedStatusCode,
+        $expectedHeaders,
+        $notExpectedHeaders,
+        $expectedBody
+    ) {
+
+        // Create a stub for the RequestHandlerInterface interface.
+        $handler = $this->createMock(RequestHandlerInterface::class);
+        $handler->method('handle')
+             ->willReturn(AppFactory::determineResponseFactory()->createResponse());
+
+        $middleware = new OpenApiDataMockerMiddleware(
+            $mocker,
+            $responses,
+            $getMockResponseCallback,
+            $afterCallback
+        );
+        $response = $middleware->process($request, $handler);
+
+        // check status code
+        $this->assertSame($expectedStatusCode, $response->getStatusCode());
+
+        // check http headers in request
+        foreach ($expectedHeaders as $expectedHeader => $expectedHeaderValue) {
+            $this->assertTrue($response->hasHeader($expectedHeader));
+            if ($expectedHeaderValue !== '*') {
+                $this->assertSame($expectedHeaderValue, $response->getHeader($expectedHeader)[0]);
+            }
+        }
+        foreach ($notExpectedHeaders as $notExpectedHeader) {
+            $this->assertFalse($response->hasHeader($notExpectedHeader));
+        }
+
+        // check body
+        if (is_array($expectedBody)) {
+            // random values, check keys only
+            foreach ($expectedBody as $attribute => $value) {
+                $this->assertObjectHasAttribute($attribute, json_decode((string) $response->getBody(), false));
+            }
+        } else {
+            $this->assertEquals($expectedBody, (string) $response->getBody());
+        }
+    }
+
+    public function provideProcessArguments()
+    {
+        $mocker = new OpenApiDataMocker();
+        $isMockResponseRequired = function (ServerRequestInterface $request) {
+            $mockHttpHeader = 'X-OpenAPIServer-Mock';
+            return $request->hasHeader($mockHttpHeader)
+                && $request->getHeader($mockHttpHeader)[0] === 'ping';
+        };
+
+        $getMockResponseCallback = function (ServerRequestInterface $request, array $responses) use ($isMockResponseRequired) {
+            if ($isMockResponseRequired($request)) {
+                if (array_key_exists('default', $responses)) {
+                    return $responses['default'];
+                }
+
+                // return first response
+                return $responses[array_key_first($responses)];
+            }
+
+            return false;
+        };
+
+        $afterCallback = function ($request, $response) use ($isMockResponseRequired) {
+            if ($isMockResponseRequired($request)) {
+                $response = $response->withHeader('X-OpenAPIServer-Mock', 'pong');
+            }
+
+            return $response;
+        };
+
+        $responses = [
+            '400' => [
+                'code' => 400,
+                'jsonSchema' => json_encode([
+                    'description' => 'Bad Request Response',
+                    'content' => new StdClass(),
+                ]),
+            ],
+            'default' => [
+                'code' => 201,
+                'jsonSchema' => json_encode([
+                    'description' => 'Success Response',
+                    'headers' => [
+                        'X-Location' => ['schema' => ['type' => 'string']],
+                        'X-Created-Id' => ['schema' => ['type' => 'integer']],
+                    ],
+                    'content' => [
+                        'application/json;encoding=utf-8' => ['schema' => ['type' => 'object', 'properties' => ['id' => ['type' => 'integer'], 'className' => ['type' => 'string'], 'declawed' => ['type' => 'boolean']]]],
+                    ],
+                ]),
+            ],
+        ];
+
+        $responsesXmlOnly = [
+            'default' => [
+                'code' => 201,
+                'jsonSchema' => json_encode([
+                    'description' => 'Success Response',
+                    'content' => [
+                        'application/xml' => [
+                            'schema' => [
+                                'type' => 'string',
+                            ],
+                        ],
+                    ],
+                ]),
+            ],
+        ];
+
+        $requestFactory = ServerRequestCreatorFactory::create();
+
+        return [
+            'callbacks null' => [
+                $mocker,
+                $responses,
+                null,
+                null,
+                $requestFactory->createServerRequestFromGlobals(),
+                200,
+                [],
+                ['X-OpenAPIServer-Mock', 'x-location', 'x-created-id'],
+                '',
+            ],
+            'xml not supported' => [
+                $mocker,
+                $responsesXmlOnly,
+                $getMockResponseCallback,
+                $afterCallback,
+                $requestFactory
+                    ->createServerRequestFromGlobals()
+                    ->withHeader('X-OpenAPIServer-Mock', 'ping'),
+                201,
+                ['X-OpenAPIServer-Mock' => 'pong', 'content-type' => '*/*'],
+                ['x-location', 'x-created-id'],
+                'Mock feature supports only "application/json" content-type!',
+            ],
+            'mock response default schema' => [
+                $mocker,
+                $responses,
+                $getMockResponseCallback,
+                $afterCallback,
+                $requestFactory
+                    ->createServerRequestFromGlobals()
+                    ->withHeader('X-OpenAPIServer-Mock', 'ping'),
+                201,
+                ['X-OpenAPIServer-Mock' => 'pong', 'content-type' => 'application/json', 'x-location' => '*', 'x-created-id' => '*'],
+                [],
+                [
+                    'id' => 1,
+                    'className' => 'cat',
+                    'declawed' => false,
+                ],
+            ],
+        ];
+    }
+}