diff --git a/.gitignore b/.gitignore index fcd813f..0d175d3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,6 @@ vendor/ # Maven target/ -dist/ # JetBrains IDE .idea/ diff --git a/composer.json b/composer.json index 5a4232d..8f8b5f3 100644 --- a/composer.json +++ b/composer.json @@ -1,22 +1,30 @@ { - "name": "codename/core-test", - "description": "This is core framework unittest support package", - "type": "library", - "keywords": ["codename", "core", "framework", "unittest", "test"], - "authors": [ - { - "name": "Kevin Dargel", - "role": "Software Developer" - } - ], - "require" : { - "codename/core": "*", - "codename/architect" : "*", - "phpunit/phpunit" : "^9.0" - }, - "autoload": { - "psr-4": { - "codename\\core\\test\\": "src/" - } + "name": "codename/core-test", + "description": "This is core framework unittest support package", + "type": "library", + "keywords": [ + "codename", + "core", + "framework", + "unittest", + "test" + ], + "authors": [ + { + "name": "Kevin Dargel", + "role": "Software Developer" } + ], + "require": { + "codename/core": "*", + "codename/architect": "*", + "friendsofphp/php-cs-fixer": "^3.15", + "phpunit/phpunit": "^10.0", + "vimeo/psalm": "^5.0" + }, + "autoload": { + "psr-4": { + "codename\\core\\test\\": "src/" + } + } } diff --git a/src/base.php b/src/base.php index 1c2369a..28e5c92 100644 --- a/src/base.php +++ b/src/base.php @@ -1,202 +1,223 @@ $schema, - 'model' => $model, - 'config' => $config, - 'initFunction' => $initFunction, - ]; - } - - /** - * [getModel description] - * @param string $model [description] - * @return \codename\core\model - */ - protected static function getModelStatic(string $model): \codename\core\model { - $modelData = static::$models[$model]; - if($modelData['initFunction'] ?? false) { - return $modelData['initFunction']($modelData['schema'], $modelData['model'], $modelData['config']); - } else { - return new sqlModel($modelData['schema'], $modelData['model'], $modelData['config']); +abstract class base extends TestCase +{ + /** + * models in this environment/test case + * @var array + */ + protected static array $models = []; + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + // overrideableApp now allows resetting all stuff + // with one method call + overrideableApp::reset(); + + // Reset instances to cleanup possible clients + // e.g. database connections + // $_REQUEST['instances'] = []; + } + + /** + * allows setting the current environment config + * @param array $config [description] + */ + protected static function setEnvironmentConfig(array $config): void + { + $configInstance = new config($config); + overrideableApp::__overrideEnvironmentConfig($configInstance); } - } - - /** - * [getModel description] - * @param string $model [description] - * @return \codename\core\model [description] - */ - protected function getModel(string $model): \codename\core\model { - return static::getModelStatic($model); - } - - - /** - * Executes architect steps (building models/data structures) - * @param string $app [description] - * @param string $vendor [description] - * @param string $envName [description] - * @return void - */ - protected static function architect(string $app, string $vendor, string $envName) { - $dbDoc = new overrideableDbDoc($app, $vendor); - $architectEnv = new \codename\architect\config\environment(app::getEnvironment()->get(), $envName); - - $modeladapters = []; - foreach(static::$models as $model) { - $modeladapters[] = $dbDoc->getModelAdapter($model['schema'], $model['model'], $model['config'], $architectEnv); + + /** + * creates a pseudo app instance + * @return overrideableApp + */ + protected static function createApp(): overrideableApp + { + return new overrideableApp(); + } + + /** + * creates a model and builds it + * @param string $schema [description] + * @param string $model [description] + * @param array $config [description] + * @param callable|null $initFunction + * @return void + */ + protected static function createModel(string $schema, string $model, array $config, ?callable $initFunction = null): void + { + static::$models[$model] = [ + 'schema' => $schema, + 'model' => $model, + 'config' => $config, + 'initFunction' => $initFunction, + ]; + } + + /** + * Executes architect steps (building models/data structures) + * @param string $app [description] + * @param string $vendor [description] + * @param string $envName [description] + * @return void + * @throws ReflectionException + * @throws exception + */ + protected static function architect(string $app, string $vendor, string $envName): void + { + $dbDoc = new overrideableDbDoc($app, $vendor); + $architectEnv = new environment(app::getEnvironment()->get(), $envName); + + $modeladapters = []; + foreach (static::$models as $model) { + $modeladapters[] = $dbDoc->getModelAdapter($model['schema'], $model['model'], $model['config'], $architectEnv); + } + + // NOTE: if dbDoc fails due to misconfigured models, + // this will fail here, too + + $dbDoc->setModelAdapters($modeladapters); + + $dbDoc->run(true, [task::TASK_TYPE_REQUIRED]); + $dbDoc->run(true, [task::TASK_TYPE_SUGGESTED]); } - // NOTE: if dbDoc fails due to misconfigured models, - // this will fail here, too - - $dbDoc->setModelAdapters($modeladapters); - - $dbDoc->run(true, [ \codename\architect\dbdoc\task::TASK_TYPE_REQUIRED ]); - $dbDoc->run(true, [ \codename\architect\dbdoc\task::TASK_TYPE_SUGGESTED ]); - } - - /** - * [emulateRequest description] - * @param string $method [description] - * @param string $host [description] - * @param string $uri [description] - * @param [type] $payload [description] - * @param array $headers [description] - * @param bool $defaultHeaders [description] - */ - protected static function emulateRequest(string $method, string $host, string $uri, array $get = [], $payload = null, array $headers = [], bool $defaultHeaders = true): void { - - if(!function_exists('getallheaders')) { - /** - * getallheaders polyfill - * for CLI environment, via $_SERVER['HTTP...'] vars - * @return array - */ - function getallheaders() { - // - // From https://github.com/ralouphie/getallheaders/blob/develop/src/getallheaders.php - // - $headers = array(); - - $copy_server = array( - 'CONTENT_TYPE' => 'Content-Type', - 'CONTENT_LENGTH' => 'Content-Length', - 'CONTENT_MD5' => 'Content-Md5', - ); - - foreach ($_SERVER as $key => $value) { - if (substr($key, 0, 5) === 'HTTP_') { - $key = substr($key, 5); - if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { - $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); - $headers[$key] = $value; + /** + * [emulateRequest description] + * @param string $method [description] + * @param string $host [description] + * @param string $uri [description] + * @param array $get + * @param null $payload + * @param array $headers [description] + * @param bool $defaultHeaders [description] + * @throws \Exception + */ + protected static function emulateRequest(string $method, string $host, string $uri, array $get = [], $payload = null, array $headers = [], bool $defaultHeaders = true): void + { + if (!function_exists('getallheaders')) { + /** + * getallheaders polyfill + * for CLI environment, via $_SERVER['HTTP...'] vars + * @return array + */ + function getallheaders(): array + { + // + // From https://github.com/ralouphie/getallheaders/blob/develop/src/getallheaders.php + // + $headers = []; + + $copy_server = [ + 'CONTENT_TYPE' => 'Content-Type', + 'CONTENT_LENGTH' => 'Content-Length', + 'CONTENT_MD5' => 'Content-Md5', + ]; + + foreach ($_SERVER as $key => $value) { + if (str_starts_with($key, 'HTTP_')) { + $key = substr($key, 5); + if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { + $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); + $headers[$key] = $value; + } + } elseif (isset($copy_server[$key])) { + $headers[$copy_server[$key]] = $value; + } + } + + if (!isset($headers['Authorization'])) { + if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { + $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($_SERVER['PHP_AUTH_USER'])) { + $basic_pass = $_SERVER['PHP_AUTH_PW'] ?? ''; + $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); + } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; + } + } + + return $headers; } - } elseif (isset($copy_server[$key])) { - $headers[$copy_server[$key]] = $value; - } } - if (!isset($headers['Authorization'])) { - if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { - $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; - } elseif (isset($_SERVER['PHP_AUTH_USER'])) { - $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; - $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); - } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { - $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; - } + // TODO: restore previous state? + // or done via PHPUnit automatically? + + if (in_array($method, ['HEAD', 'OPTIONS', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'])) { + $_SERVER['REQUEST_METHOD'] = $method; + } else { + throw new \Exception("Invalid method for emulateRequest: '$method'"); } + $_SERVER['SERVER_NAME'] = $host; + $_SERVER['REQUEST_URI'] = $uri; - return $headers; - } - } + if ($defaultHeaders) { + // add default headers + $headers = array_replace([], $headers); + } - // TODO: restore previous state? - // or done via PHPUnit automatically? + // header emulation by $_SERVER['HTTP_...'] ? + foreach ($headers as $key => $value) { + $headerName = strtoupper(str_replace('-', '_', $key)); + $_SERVER['HTTP_' . $headerName] = $value; + } - if(in_array($method, [ 'HEAD', 'OPTIONS', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE' ])) { - $_SERVER['REQUEST_METHOD'] = $method; - } else { - throw new \Exception("Invalid method for emulateRequest: '$method'"); - } - $_SERVER['SERVER_NAME'] = $host; - $_SERVER['REQUEST_URI'] = $uri; + foreach ($get as $key => $value) { + $_GET[$key] = $value; + } - if($defaultHeaders) { - // add default headers - $headers = array_replace([ ], $headers); + // TODO: payload injection? GET? POST? REQUEST array? + // php://input writing? } - // header emulation by $_SERVER['HTTP_...'] ? - foreach($headers as $key => $value) { - $headerName = strtoupper(str_replace('-', '_', $key)); - $_SERVER['HTTP_'.$headerName] = $value; + /** + * [getModel description] + * @param string $model [description] + * @return model [description] + * @throws ReflectionException + * @throws exception + */ + protected function getModel(string $model): model + { + return static::getModelStatic($model); } - foreach($get as $key => $value) { - $_GET[$key] = $value; + /** + * [getModel description] + * @param string $model [description] + * @return model + * @throws ReflectionException + * @throws exception + */ + protected static function getModelStatic(string $model): model + { + $modelData = static::$models[$model]; + if ($modelData['initFunction'] ?? false) { + return $modelData['initFunction']($modelData['schema'], $modelData['model'], $modelData['config']); + } else { + return new sqlModel($modelData['schema'], $modelData['model'], $modelData['config']); + } } - - // TODO: payload injection? GET? POST? REQUEST array? - // php://input writing? - } - - /** - * models in this environment/test case - * @var array - */ - protected static $models = []; - } diff --git a/src/cliExitPreventResponse.php b/src/cliExitPreventResponse.php index e1081ed..ea54263 100644 --- a/src/cliExitPreventResponse.php +++ b/src/cliExitPreventResponse.php @@ -1,15 +1,18 @@ 0)] - * @return bool [whether waiting was successful] - */ - public static function waitForIt(string $host, int $port, int $connectionTimeout, int $waitBetweenRetries, int $tryCount): bool { - if($tryCount < 1) { - throw new \Exception('Invalid tryCount'); - } - for ($i=0; $i < $tryCount; $i++) { - try { - $ret = (@fsockopen ($host, $port, $error_code, $error_message, $connectionTimeout) !== false); - if($ret) { - return true; +class helper +{ + /** + * synchronously waits for a host to be available + * + * @param string $host [host to connect to] + * @param int $port [port to connect on] + * @param int $connectionTimeout [the connection timeout per try] + * @param int $waitBetweenRetries [wait time in seconds after a try failed] + * @param int $tryCount [overall count of tries to connect (>0)] + * @return bool [whether waiting was successful] + * @throws Exception + */ + public static function waitForIt(string $host, int $port, int $connectionTimeout, int $waitBetweenRetries, int $tryCount): bool + { + if ($tryCount < 1) { + throw new Exception('Invalid tryCount'); + } + for ($i = 0; $i < $tryCount; $i++) { + try { + $ret = (@fsockopen($host, $port, $error_code, $error_message, $connectionTimeout) !== false); + if ($ret) { + return true; + } + } catch (Exception) { + // NOTE: simply swallow exception + } + sleep($waitBetweenRetries); } - } catch (\Exception $e) { - // NOTE: simply swallow exception - } - sleep($waitBetweenRetries); + return false; } - return false; - } - } diff --git a/src/overrideableApp.php b/src/overrideableApp.php index 5a83550..7c638e6 100644 --- a/src/overrideableApp.php +++ b/src/overrideableApp.php @@ -1,225 +1,259 @@ registerShutdownHandler = false; - - parent::__CONSTRUCT(); - - // TODO - $this->injectApp([ - 'vendor' => 'codename', - 'app' => 'architect', - 'namespace' => '\\codename\\architect' - ]); - - // prevent real exit - static::$exitCode = null; - static::__setShouldThrowException(); // by default: true - } - - /** - * [__setShouldThrowException description] - * @param bool|null $state [description] - */ - public static function __setShouldThrowException(?bool $state = true): void { - static::$__shouldThrowExceptionState = $state; - } - - /** - * state override for ::shouldThrowException - * @var bool|null - */ - protected static $__shouldThrowExceptionState = null; - - /** - * @inheritDoc - */ - protected static function shouldThrowException(): bool - { - if(static::$__shouldThrowExceptionState === null) { - return parent::shouldThrowException(); - } else { - return static::$__shouldThrowExceptionState; - } - } - - /** - * resets the app instance - */ - public static function reset(): void { - static::$config = null; // reset (app) config - static::$environment = null; // reset (env) config - // static::$hook // We do not reset this to keep unittest-related global hooks alive - static::$app = null; - static::$vendor = null; - static::$namespace = null; - static::$homedir = null; - // static::$instances = []; - static::$instance = null; - static::$appstack = null; - static::$validatorCacheArray = []; - $_REQUEST['instances'] = []; - } - - /** - * [resetRequest description] - */ - public static function resetRequest(): void { - unset(static::$instances['request']); - } - - /** - * [resetResponse description] - */ - public static function resetResponse(): void { - unset(static::$instances['response']); - } - - /** - * Overrides the current app's name - * must stick to text_methodname - * @param string $app [description] - */ - public static function __setApp(string $app) { - static::$app = new \codename\core\value\text\methodname($app); - } - - /** - * Overrides the current app's vendor - * must stick to text_methodname - * @param string $vendor [description] - */ - public static function __setVendor(string $vendor) { - static::$vendor = new \codename\core\value\text\methodname($vendor); - } - - /** - * Overrides the current app's default namespace. - * Can also be used for resetting (NULL) - * @param string|null $namespace [description] - */ - public static function __setNamespace(?string $namespace) { - static::$namespace = $namespace; - } - - /** - * [__setHomedir description] - * @param string|null $homedir [description] - */ - public static function __setHomedir(?string $homedir) { - static::$homedir = $homedir; - } - - /** - * [__injectApp description] - * @param array $injectApp - * @param int|null $injectionMode - * @return void - */ - public static function __injectApp(array $injectApp, ?int $injectionMode = null): void { - if($injectionMode === null) { - static::injectApp($injectApp); - } else { - static::injectApp($injectApp, $injectionMode); - } - } - - /** - * [__modifyAppstackEntry description] - * @param string $vendor [app's vendor to look for] - * @param string $app [app name to modify] - * @param array|null $newData [null to delete, otherwise: new data to be used] - * @param bool $replace [whether to replace the full dataset or merge with newData] - * @return void - */ - public static function __modifyAppstackEntry(string $vendor, string $app, ?array $newData, bool $replace = false): void { - $stack = static::$appstack->get(); - $newStack = []; - foreach($stack as $appstackEntry) { - if(($appstackEntry['vendor'] == $vendor) && ($appstackEntry['app'] == $app)) { - if($newData) { - if($replace) { - $newStack[] = $newData; - } else { - $newStack[] = array_merge($appstackEntry, $newData); - } +class overrideableApp extends app +{ + /** + * state override for ::shouldThrowException + * @var bool|null + */ + protected static ?bool $__shouldThrowExceptionState = null; + + /** + * @inheritDoc + */ + public function __construct() + { + // Prevent custom shutdown handler registration + // as it causes bugs when using process isolation using PHPUnit + $this->registerShutdownHandler = false; + + parent::__construct(); + + // TODO + static::injectApp([ + 'vendor' => 'codename', + 'app' => 'architect', + 'namespace' => '\\codename\\architect', + ]); + + // prevent real exit + static::$exitCode = null; + static::__setShouldThrowException(); // by default: true + } + + /** + * [__setShouldThrowException description] + * @param bool|null $state [description] + */ + public static function __setShouldThrowException(?bool $state = true): void + { + static::$__shouldThrowExceptionState = $state; + } + + /** + * resets the app instance + */ + public static function reset(): void + { + static::$config = null; // reset (app) config + static::$environment = null; // reset (env) config + // static::$hook // We do not reset this to keep unittest-related global hooks alive + static::$app = null; + static::$vendor = null; + static::$namespace = null; + static::$homedir = null; + // static::$instances = []; + static::$instance = null; + static::$appstack = null; + static::$validatorCacheArray = []; + $_REQUEST['instances'] = []; + } + + /** + * [resetRequest description] + */ + public static function resetRequest(): void + { + unset(static::$instances['request']); + } + + /** + * [resetResponse description] + */ + public static function resetResponse(): void + { + unset(static::$instances['response']); + } + + /** + * Overrides the current app's name + * must stick to text_methodname + * @param string $app [description] + * @throws ReflectionException + * @throws exception + */ + public static function __setApp(string $app): void + { + static::$app = new methodname($app); + } + + /** + * Overrides the current app's vendor + * must stick to text_methodname + * @param string $vendor [description] + * @throws ReflectionException + * @throws exception + */ + public static function __setVendor(string $vendor): void + { + static::$vendor = new methodname($vendor); + } + + /** + * Overrides the current app's default namespace. + * Can also be used for resetting (NULL) + * @param string|null $namespace [description] + */ + public static function __setNamespace(?string $namespace): void + { + static::$namespace = $namespace; + } + + /** + * [__setHomedir description] + * @param string|null $homedir [description] + */ + public static function __setHomedir(?string $homedir): void + { + static::$homedir = $homedir; + } + + /** + * [__injectApp description] + * @param array $injectApp + * @param int|null $injectionMode + * @return void + * @throws exception + */ + public static function __injectApp(array $injectApp, ?int $injectionMode = null): void + { + if ($injectionMode === null) { + static::injectApp($injectApp); + } else { + static::injectApp($injectApp, $injectionMode); + } + } + + /** + * [__modifyAppstackEntry description] + * @param string $vendor [app's vendor to look for] + * @param string $app [app name to modify] + * @param array|null $newData [null to delete, otherwise: new data to be used] + * @param bool $replace [whether to replace the full dataset or merge with newData] + * @return void + * @throws ReflectionException + * @throws exception + */ + public static function __modifyAppstackEntry(string $vendor, string $app, ?array $newData, bool $replace = false): void + { + $stack = static::$appstack->get(); + $newStack = []; + foreach ($stack as $appstackEntry) { + if (($appstackEntry['vendor'] == $vendor) && ($appstackEntry['app'] == $app)) { + if ($newData) { + if ($replace) { + $newStack[] = $newData; + } else { + $newStack[] = array_merge($appstackEntry, $newData); + } + } else { + // omit. + } + } else { + $newStack[] = $appstackEntry; + } + } + // replace stack + self::$appstack = new appstack($newStack); + } + + /** + * [__injectClientInstance description] + * @param string $type [description] + * @param string $identifier [description] + * @param mixed $clientInstance [description] + * @return void [type] [description] + */ + public static function __injectClientInstance(string $type, string $identifier, mixed $clientInstance): void + { + $simplename = $type . $identifier; + $_REQUEST['instances'][$simplename] = $clientInstance; + } + + /** + * [__setInstance description] + * @param string $name [description] + * @param [type] $instance [description] + */ + public static function __setInstance(string $name, $instance): void + { + static::$instances[$name] = $instance; + } + + /** + * Injects a given instance into the available instances + * @param string $contextName + * @param context $contextInstance + * @throws ReflectionException + * @throws exception + */ + public static function __injectContextInstance(string $contextName, context $contextInstance): void + { + $simplename = self::getApp() . "_$contextName"; + $_REQUEST['instances'][$simplename] = $contextInstance; + } + + /** + * Overrides/provides an environment config + * for usage with custom test cases + * @param config $config [description] + */ + public static function __overrideEnvironmentConfig(config $config): void + { + static::$environment = $config; + } + + /** + * Returns the current, full-fledged environment config + * @return config + */ + public static function __getEnvironmentConfig(): config + { + return static::$environment; + } + + /** + * [__overrideJsonConfigPath description] + * @param string $path [description] + */ + public static function __overrideJsonConfigPath(string $path): void + { + static::$json_config = $path; + } + + /** + * @inheritDoc + */ + protected static function shouldThrowException(): bool + { + if (static::$__shouldThrowExceptionState === null) { + return parent::shouldThrowException(); } else { - // omit. + return static::$__shouldThrowExceptionState; } - } else { - $newStack[] = $appstackEntry; - } - } - // replace stack - self::$appstack = new \codename\core\value\structure\appstack($newStack); - } - - /** - * [__injectClientInstance description] - * @param string $type [description] - * @param string $identifier [description] - * @param mixed $clientInstance [description] - * @return [type] [description] - */ - public static function __injectClientInstance(string $type, string $identifier, $clientInstance) { - $simplename = $type . $identifier; - $_REQUEST['instances'][$simplename] = $clientInstance; - } - - /** - * [__setInstance description] - * @param string $name [description] - * @param [type] $instance [description] - */ - public static function __setInstance(string $name, $instance) { - static::$instances[$name] = $instance; - } - - /** - * Injects a given instance into the available instances - * @param string $contextName - * @param \codename\core\context $contextInstance - */ - public static function __injectContextInstance(string $contextName, \codename\core\context $contextInstance) { - $simplename = self::getApp()."_{$contextName}"; - $_REQUEST['instances'][$simplename] = $contextInstance; - } - - /** - * Overrides/provides an environment config - * for usage with custom test cases - * @param \codename\core\config $config [description] - */ - public static function __overrideEnvironmentConfig(\codename\core\config $config) { - static::$environment = $config; - } - - /** - * Returns the current, full-fledged environment config - * @return \codename\core\config - */ - public static function __getEnvironmentConfig(): \codename\core\config { - return static::$environment; - } - - /** - * [__overrideJsonConfigPath description] - * @param string $path [description] - */ - public static function __overrideJsonConfigPath(string $path) { - static::$json_config = $path; - } + } } diff --git a/src/overrideableDbDoc.php b/src/overrideableDbDoc.php index 98ac909..f67f0e8 100644 --- a/src/overrideableDbDoc.php +++ b/src/overrideableDbDoc.php @@ -1,17 +1,21 @@ adapters = $modeladapters; - } +class overrideableDbDoc extends dbdoc +{ + /** + * [setModelAdapters description] + * @param array $modeladapters [description] + */ + public function setModelAdapters(array $modeladapters): void + { + $this->adapters = $modeladapters; + } } diff --git a/src/sqlModel.php b/src/sqlModel.php index ebe07ef..2505919 100644 --- a/src/sqlModel.php +++ b/src/sqlModel.php @@ -1,19 +1,30 @@ config = new \codename\core\config($config); - $this->setConfig(null, $schema, $model); - } +class sqlModel extends sql +{ + /** + * @inheritDoc + * @param string $schema + * @param string $model + * @param array $config + * @throws ReflectionException + * @throws exception + */ + public function __construct(string $schema, string $model, array $config) + { + parent::__construct(); + $this->config = new config($config); + $this->setConfig(null, $schema, $model); + } }