From 4feb6c9f19b628ea16db60da8f22b24fa205a3c0 Mon Sep 17 00:00:00 2001 From: Ralf Date: Tue, 1 Aug 2023 16:05:14 +0200 Subject: [PATCH 1/2] update php 7 > 8.1+, dependencies, phpunit10, psr12, bugfixing and code cleanup --- backend/class/app.php | 478 ++++----- backend/class/config/environment.php | 89 +- backend/class/config/json/virtualAppstack.php | 78 +- backend/class/context/deployment.php | 90 +- backend/class/context/main.php | 153 +-- backend/class/dbdoc/dbdoc.php | 904 ++++++++---------- backend/class/dbdoc/modeladapter.php | 291 +++--- backend/class/dbdoc/modeladapter/bare.php | 73 +- .../modeladapterGetSqlAdapter.php | 21 + backend/class/dbdoc/modeladapter/sql.php | 116 ++- .../class/dbdoc/modeladapter/sql/mysql.php | 46 +- .../class/dbdoc/modeladapter/sql/sqlite.php | 44 +- backend/class/dbdoc/plugin.php | 266 +++--- backend/class/dbdoc/plugin/collection.php | 22 +- .../class/dbdoc/plugin/connectionPrefix.php | 24 +- backend/class/dbdoc/plugin/database.php | 58 +- backend/class/dbdoc/plugin/field.php | 53 +- backend/class/dbdoc/plugin/fieldlist.php | 22 +- backend/class/dbdoc/plugin/foreign.php | 22 +- backend/class/dbdoc/plugin/fulltext.php | 20 +- backend/class/dbdoc/plugin/index.php | 22 +- backend/class/dbdoc/plugin/initial.php | 7 +- backend/class/dbdoc/plugin/modelPrefix.php | 22 +- backend/class/dbdoc/plugin/permissions.php | 7 +- backend/class/dbdoc/plugin/primary.php | 55 +- backend/class/dbdoc/plugin/schema.php | 22 +- backend/class/dbdoc/plugin/schemaPrefix.php | 22 +- backend/class/dbdoc/plugin/sql/collection.php | 82 +- backend/class/dbdoc/plugin/sql/field.php | 889 ++++++++--------- backend/class/dbdoc/plugin/sql/fieldlist.php | 145 +-- backend/class/dbdoc/plugin/sql/foreign.php | 380 ++++---- backend/class/dbdoc/plugin/sql/fulltext.php | 300 +++--- backend/class/dbdoc/plugin/sql/index.php | 445 ++++----- backend/class/dbdoc/plugin/sql/initial.php | 77 +- .../class/dbdoc/plugin/sql/mysql/field.php | 100 +- .../class/dbdoc/plugin/sql/mysql/foreign.php | 56 +- .../dbdoc/plugin/sql/mysql/permissions.php | 230 ++--- .../class/dbdoc/plugin/sql/mysql/primary.php | 101 +- .../class/dbdoc/plugin/sql/mysql/table.php | 86 +- backend/class/dbdoc/plugin/sql/mysql/user.php | 227 +++-- .../class/dbdoc/plugin/sql/permissions.php | 12 +- backend/class/dbdoc/plugin/sql/primary.php | 172 ++-- backend/class/dbdoc/plugin/sql/schema.php | 111 ++- .../class/dbdoc/plugin/sql/sqlite/field.php | 406 ++++---- .../dbdoc/plugin/sql/sqlite/fieldlist.php | 191 ++-- .../class/dbdoc/plugin/sql/sqlite/foreign.php | 133 +-- .../dbdoc/plugin/sql/sqlite/fulltext.php | 25 +- .../class/dbdoc/plugin/sql/sqlite/index.php | 415 +++----- .../sql/sqlite/partialStatementInterface.php | 13 +- .../class/dbdoc/plugin/sql/sqlite/primary.php | 137 ++- .../class/dbdoc/plugin/sql/sqlite/schema.php | 102 +- .../class/dbdoc/plugin/sql/sqlite/table.php | 519 +++++----- .../class/dbdoc/plugin/sql/sqlite/unique.php | 153 +-- backend/class/dbdoc/plugin/sql/table.php | 211 ++-- backend/class/dbdoc/plugin/sql/unique.php | 239 +++-- backend/class/dbdoc/plugin/sql/user.php | 12 +- backend/class/dbdoc/plugin/table.php | 22 +- backend/class/dbdoc/plugin/unique.php | 22 +- backend/class/dbdoc/plugin/user.php | 90 +- backend/class/dbdoc/structure.php | 9 +- backend/class/dbdoc/task.php | 263 ++--- backend/class/deploy/deployment.php | 398 ++++---- backend/class/deploy/deploymentresult.php | 22 +- backend/class/deploy/task.php | 100 +- backend/class/deploy/task/client.php | 56 +- backend/class/deploy/task/client/cache.php | 71 +- .../class/deploy/task/client/cache/flush.php | 99 +- backend/class/deploy/task/dbdoc.php | 158 ++- backend/class/deploy/task/dummy.php | 27 +- backend/class/deploy/task/model.php | 115 +-- backend/class/deploy/task/model/entry.php | 176 ++-- backend/class/deploy/task/model/filter.php | 148 +-- .../class/deploy/task/model/filter/update.php | 111 +-- backend/class/deploy/task/model/migrate.php | 320 ++++--- backend/class/deploy/task/model/query.php | 34 +- backend/class/deploy/taskresult.php | 17 +- backend/class/deploy/taskresult/text.php | 22 +- backend/class/model/schematic/sql/dynamic.php | 124 +-- backend/class/validator/number/tasktype.php | 60 +- composer.json | 36 +- config/app.json | 78 +- config/environment.json | 256 ++--- frontend/template/basic/template.twig | 16 +- frontend/view/deployment/run.cli.twig | 4 +- frontend/view/header/header_base.cli.twig | 18 +- frontend/view/main/listapps.twig | 12 +- frontend/view/main/listmodels.cli.twig | 12 +- frontend/view/main/listmodels.twig | 57 +- phpcs.xml | 26 +- translation/de_DE/exception.json | 4 +- 90 files changed, 5877 insertions(+), 6102 deletions(-) create mode 100644 backend/class/dbdoc/modeladapter/modeladapterGetSqlAdapter.php diff --git a/backend/class/app.php b/backend/class/app.php index 4eb789b..fcd5100 100644 --- a/backend/class/app.php +++ b/backend/class/app.php @@ -1,273 +1,289 @@ initDebug(); - } - - /** - * @inheritDoc - */ - protected function makeRequest() - { - parent::makeRequest(); - if($this->getResponse() instanceof \codename\core\response\cli) { - $this->getResponse()->setData('templateengine', 'cli'); - } - } - - /** - * [makeForeignAppstack description] - * @param string $vendor [description] - * @param string $app [description] - * @return array [description] - */ - public static function makeForeignAppstack(string $vendor, string $app) : array { - return parent::makeAppstack($vendor, $app); - } - - /** - * Gets the all models/definitions, also inherited - * returns a multidimensional assoc array like: - * models[schema][model] = array( 'fields' => ... ) - * @author Kevin Dargel - * @return array - */ - public static function getModelConfigurations(string $filterByVendor = '', string $filterByApp = '', string $model = '', array $useAppstack = null) : array { - - $result = array(); - - if($useAppstack == null) { - $useAppstack = self::getAppstack(); +class app extends \codename\core\app +{ + /** + * {@inheritDoc} + */ + public function __construct() + { + parent::__construct(); + $this->initDebug(); } - // Traverse Appstack - foreach($useAppstack as $app) { - - if($filterByApp !== '') { - if($filterByApp !== $app['app']) { - continue; - } - } - - if($filterByVendor !== '') { - if($filterByVendor !== $app['vendor']) { - continue; - } - } - - // array of vendor,app - $appdir = app::getHomedir($app['vendor'], $app['app']); - $dir = $appdir . "config/model"; - - // get all model json files, first: - $files = app::getFilesystem()->dirList( $dir ); - - foreach($files as $f) { - $file = $dir . '/' . $f; - - // check for .json extension - $fileInfo = new \SplFileInfo($file); - if($fileInfo->getExtension() === 'json') { - // get the model filename w/o extension - $modelName = $fileInfo->getBasename('.json'); - - // split: schema_model - // maximum: two components (schema, model) - // following _ are treated as part of the model name itself - $comp = explode( '_' , $modelName, 2); - $schema = $comp[0]; - $model = $comp[1]; - - $modelconfig = (new \codename\architect\config\json\virtualAppstack("config/model/" . $fileInfo->getFilename(), true, true, $useAppstack))->get(); - $result[$schema][$model][] = $modelconfig; - } - } + /** + * [makeForeignAppstack description] + * @param string $vendor [description] + * @param string $app [description] + * @return array [description] + * @throws ReflectionException + * @throws exception + */ + public static function makeForeignAppstack(string $vendor, string $app): array + { + return parent::makeAppstack($vendor, $app); } - return $result; - } - - - /** - * returns an array of sibling app names - * if they depend on the core framework - * @return array [description] - */ - public static function getSiblingApps() : array { - - $vendorDirs = app::getFilesystem()->dirList(CORE_VENDORDIR); - $appPaths = []; - - foreach($vendorDirs as $vendorDir) { - // for now, we're relying on our current vendor name for finding siblings - $paths = app::getFilesystem()->dirList(CORE_VENDORDIR . $vendorDir); - foreach($paths as $p) { - if(app::getFilesystem()->isDirectory(CORE_VENDORDIR . $vendorDir . '/' . $p)) { - $appPaths[] = [ $vendorDir, $p ]; + /** + * Gets the all models/definitions, also inherited + * returns a multidimensional assoc array like: + * models[schema][model] = array( 'fields' => ... ) + * @param string $filterByVendor + * @param string $filterByApp + * @param string $model + * @param array|null $useAppstack + * @return array + * @throws ReflectionException + * @throws exception + */ + public static function getModelConfigurations(string $filterByVendor = '', string $filterByApp = '', string $model = '', array $useAppstack = null): array + { + $result = []; + + if ($useAppstack == null) { + $useAppstack = self::getAppstack(); } - } - } - // The base app class, reflected. - $baseReflectionClass = new \ReflectionClass( '\\codename\\core\\app' ); - - $apps = array(); + // Traverse Appstack + foreach ($useAppstack as $app) { + if ($filterByApp !== '') { + if ($filterByApp !== $app['app']) { + continue; + } + } - foreach($appPaths as $pathComponents) { + if ($filterByVendor !== '') { + if ($filterByVendor !== $app['vendor']) { + continue; + } + } - $vendordir = $pathComponents[0]; - $appdir = $pathComponents[1]; + // array of vendor,app + $appdir = app::getHomedir($app['vendor'], $app['app']); + $dir = $appdir . "config/model"; - if(app::getFilesystem()->isDirectory($dir = CORE_VENDORDIR . $vendordir . '/' . $appdir)) { + // get all model json files, first: + $files = app::getFilesystem()->dirList($dir); - // exclude this app and the core framework. - if($appdir != 'architect' && $appdir != 'core') { + foreach ($files as $f) { + $file = $dir . '/' . $f; - $appname = null; - $vendorname = null; - $probeNamespace = null; + // check for .json extension + $fileInfo = new SplFileInfo($file); + if ($fileInfo->getExtension() === 'json') { + // get the model filename w/o extension + $modelName = $fileInfo->getBasename('.json'); - // analyze composer.json, if available - if(file_exists($composerJson = $dir . '/composer.json')) { - $composerData = @json_decode(file_get_contents($composerJson), true); - $probeNamespace = array_keys($composerData['autoload']['psr-4'] ?? [])[0] ?? $probeNamespace; + // split: schema_model + // maximum: two components (schema, model) + // following _ are treated as part of the model name itself + $comp = explode('_', $modelName, 2); + $schema = $comp[0]; + $model = $comp[1]; - // assume vendor/project (composer-style) - // to define the core-app identifier (vendor and app) - if($names = explode('/', $composerData['name'] ?? '')) { - $vendorname = $names[0]; - $appname = $names[1]; - } - } else { - // not a composer-loadable directory - continue; - } - - if($probeNamespace) { - // try to look for app class - $classname = $probeNamespace . 'app'; - } else { - $classname = $vendordir . '\\' . $appdir . '\\app'; - } - - if(class_exists($classname)) { - - // testing for inheritance from $baseReflectionClass - // @see https://stackoverflow.com/questions/782653/checking-if-a-class-is-a-subclass-of-another - $testReflectionClass = new \ReflectionClass($classname); - if($testReflectionClass->isSubclassOf($baseReflectionClass)) { - // compatible sibling app found. - $apps[] = array( - 'vendor' => $vendorname ?? $vendordir, - 'app' => $appname ?? $appdir, - 'homedir' => $vendordir . '/' . $appdir - ); + $modelconfig = (new virtualAppstack("config/model/" . $fileInfo->getFilename(), true, true, $useAppstack))->get(); + $result[$schema][$model][] = $modelconfig; + } } - } } - } - } - return $apps; - } + return $result; + } + /** + * returns an array of sibling app names + * if they depend on the core framework + * @return array [description] + * @throws ReflectionException + * @throws exception + */ + public static function getSiblingApps(): array + { + $vendorDirs = app::getFilesystem()->dirList(CORE_VENDORDIR); + $appPaths = []; + + foreach ($vendorDirs as $vendorDir) { + // for now, we're relying on our current vendor name for finding siblings + $paths = app::getFilesystem()->dirList(CORE_VENDORDIR . $vendorDir); + foreach ($paths as $p) { + if (app::getFilesystem()->isDirectory(CORE_VENDORDIR . $vendorDir . '/' . $p)) { + $appPaths[] = [$vendorDir, $p]; + } + } + } + // The base app class, reflected. + $baseReflectionClass = new ReflectionClass('\\codename\\core\\app'); + + $apps = []; + + foreach ($appPaths as $pathComponents) { + $vendordir = $pathComponents[0]; + $appdir = $pathComponents[1]; + + if (app::getFilesystem()->isDirectory($dir = CORE_VENDORDIR . $vendordir . '/' . $appdir)) { + // exclude this app and the core framework. + if ($appdir != 'architect' && $appdir != 'core') { + $appname = null; + $vendorname = null; + $probeNamespace = null; + + // analyze composer.json, if available + if (file_exists($composerJson = $dir . '/composer.json')) { + $composerData = @json_decode(file_get_contents($composerJson), true); + $probeNamespace = array_keys($composerData['autoload']['psr-4'] ?? [])[0] ?? $probeNamespace; + + // assume vendor/project (composer-style) + // to define the core-app identifier (vendor and app) + if ($names = explode('/', $composerData['name'] ?? '')) { + $vendorname = $names[0]; + $appname = $names[1]; + } + } else { + // not a composer-loadable directory + continue; + } + + if ($probeNamespace) { + // try to look for app class + $classname = $probeNamespace . 'app'; + } else { + $classname = $vendordir . '\\' . $appdir . '\\app'; + } + + if (class_exists($classname)) { + // testing for inheritance from $baseReflectionClass + // @see https://stackoverflow.com/questions/782653/checking-if-a-class-is-a-subclass-of-another + $testReflectionClass = new ReflectionClass($classname); + if ($testReflectionClass->isSubclassOf($baseReflectionClass)) { + // compatible sibling app found. + $apps[] = [ + 'vendor' => $vendorname ?? $vendordir, + 'app' => $appname ?? $appdir, + 'homedir' => $vendordir . '/' . $appdir, + ]; + } + } + } + } + } - /** - * Returns the (maybe cached) client that is stored as "driver" in $identifier (app.json) for the given $type. - * @param string $type - * @param string $identifier - * @return object - * @todo refactor - */ - final public static function getForeignClient(\codename\architect\config\environment $environment, \codename\core\value\text\objecttype $type, \codename\core\value\text\objectidentifier $identifier, bool $store = true) { + return $apps; + } - // $config = self::getData($type, $identifier); - $config = $environment->get("{$type->get()}>{$identifier->get()}"); + /** + * Returns the (maybe cached) client that is stored as "driver" in $identifier (app.json) for the given $type. + * @param environment $environment + * @param objecttype $type + * @param objectidentifier $identifier + * @param bool $store + * @return object + * @throws ReflectionException + * @throws exception + * @todo refactor + */ + final public static function getForeignClient(environment $environment, objecttype $type, objectidentifier $identifier, bool $store = true): object + { + $config = $environment->get("{$type->get()}>{$identifier->get()}"); + + $type = $type->get(); + $identifier = $identifier->get(); + $simplename = $type . $identifier; + + if ($store && array_key_exists($simplename, $_REQUEST['instances'])) { + return $_REQUEST['instances'][$simplename]; + } - $type = $type->get(); - $identifier = $identifier->get(); - $simplename = $type . $identifier; + $app = array_key_exists('app', $config) ? $config['app'] : self::getApp(); + $vendor = self::getVendor(); - if ($store && array_key_exists($simplename, $_REQUEST['instances'])) { - return $_REQUEST['instances'][$simplename]; - } + if (is_array($config['driver'])) { + $config['driver'] = $config['driver'][0]; + } + // we have to traverse the appstack! + $classpath = self::getHomedir($vendor, $app) . '/backend/class/' . $type . '/' . $config['driver'] . '.php'; + $classname = "\\$vendor\\$app\\$type\\" . $config['driver']; - $app = array_key_exists('app', $config) ? $config['app'] : self::getApp(); - $vendor = self::getVendor(); - if(is_array($config['driver'])) { - $config['driver'] = $config['driver'][0]; - } + // if not found in app, traverse appstack + if (!self::getInstance('filesystem_local')->fileAvailable($classpath)) { + $found = false; + foreach (self::getAppstack() as $parentapp) { + $vendor = $parentapp['vendor']; + $app = $parentapp['app']; + $classpath = self::getHomedir($vendor, $app) . '/backend/class/' . $type . '/' . $config['driver'] . '.php'; + $classname = "\\$vendor\\$app\\$type\\" . $config['driver']; - // we have to traverse the appstack! - $classpath = self::getHomedir($vendor, $app) . '/backend/class/' . $type . '/' . $config['driver'] . '.php'; - $classname = "\\{$vendor}\\{$app}\\{$type}\\" . $config['driver']; + if (self::getInstance('filesystem_local')->fileAvailable($classpath)) { + $found = true; + break; + } + } + if ($found !== true) { + throw new exception(self::EXCEPTION_GETCLIENT_NOTFOUND, exception::$ERRORLEVEL_FATAL, [$type, $identifier]); + } + } - // if not found in app, traverse appstack - if(!self::getInstance('filesystem_local')->fileAvailable($classpath)) { - $found = false; - foreach(self::getAppstack() as $parentapp) { - $vendor = $parentapp['vendor']; - $app = $parentapp['app']; - $classpath = self::getHomedir($vendor, $app) . '/backend/class/' . $type . '/' . $config['driver'] . '.php'; - $classname = "\\{$vendor}\\{$app}\\{$type}\\" . $config['driver']; + // instantiate + return $_REQUEST['instances'][$simplename] = new $classname($config); + } - if(self::getInstance('filesystem_local')->fileAvailable($classpath)) { - $found = true; - break; - } + /** + * {@inheritDoc} + */ + protected function makeRequest(): void + { + parent::makeRequest(); + $response = static::getResponse(); + if ($response instanceof cli) { + $response->setData('templateengine', 'cli'); } + } - if($found !== true) { - throw new \codename\core\exception(self::EXCEPTION_GETCLIENT_NOTFOUND, \codename\core\exception::$ERRORLEVEL_FATAL, array($type, $identifier)); - } - } - - // instanciate - return $_REQUEST['instances'][$simplename] = new $classname($config); - } - - /** - * [printNamespaces description] - * only needed for debug purposes. - * may be removed in the future - * @return [type] [description] - */ - protected function printNamespaces() { - $namespaces=array(); - foreach(get_declared_classes() as $name) { - if(preg_match_all("@[^\\\]+(?=\\\)@iU", $name, $matches)) { - $matches = $matches[0]; - $parent =&$namespaces; - while(count($matches)) { - $match = array_shift($matches); - if(!isset($parent[$match]) && count($matches)) - $parent[$match] = array(); - $parent =&$parent[$match]; + /** + * [printNamespaces description] + * only needed for debug purposes. + * may be removed in the future + * @return void [type] [description] + */ + protected function printNamespaces(): void + { + $namespaces = []; + foreach (get_declared_classes() as $name) { + if (preg_match_all("@[^\\\]+(?=\\\)@iU", $name, $matches)) { + $matches = $matches[0]; + $parent =& $namespaces; + while (count($matches)) { + $match = array_shift($matches); + if (!isset($parent[$match]) && count($matches)) { + $parent[$match] = []; + } + $parent =& $parent[$match]; + } } } - } - - echo("
");
-    print_r($namespaces);
-    echo("
"); - } + echo("
");
+        print_r($namespaces);
+        echo("
"); + } } diff --git a/backend/class/config/environment.php b/backend/class/config/environment.php index 9d1316f..af1b80f 100644 --- a/backend/class/config/environment.php +++ b/backend/class/config/environment.php @@ -1,54 +1,57 @@ environmentKey = $environmentKey; - } +class environment extends config +{ + /** + * env key + * @var null|string + */ + protected ?string $environmentKey = null; - /** - * @inheritDoc - */ - public function get(string $key = '', $default = null) - { - if($key == '') { - $key = $this->environmentKey; - } else { - $key = "{$this->environmentKey}>{$key}"; + /** + * {@inheritDoc} + */ + public function __construct(array $data, string $environmentKey = null) + { + parent::__construct($data); + $this->environmentKey = $environmentKey; } - return parent::get($key, $default); - } - /** - * sets the environment key to be used - * @param string $key [description] - */ - public function setEnvironmentKey(string $key) { - $this->environmentKey = $key; - } + /** + * {@inheritDoc} + */ + public function get(string $key = '', mixed $default = null): mixed + { + if ($key == '') { + $key = $this->environmentKey; + } else { + $key = $this->environmentKey . '>' . $key; + } + return parent::get($key, $default); + } - /** - * gets the environment key currently used - * @return string [description] - */ - public function getEnvironmentKey() : string { - return $this->environmentKey; - } + /** + * gets the environment key currently used + * @return string [description] + */ + public function getEnvironmentKey(): string + { + return $this->environmentKey; + } -} \ No newline at end of file + /** + * sets the environment key to be used + * @param string $key [description] + */ + public function setEnvironmentKey(string $key): void + { + $this->environmentKey = $key; + } +} diff --git a/backend/class/config/json/virtualAppstack.php b/backend/class/config/json/virtualAppstack.php index 6bf4b49..fd33d3e 100644 --- a/backend/class/config/json/virtualAppstack.php +++ b/backend/class/config/json/virtualAppstack.php @@ -1,46 +1,50 @@ useAppstack = $useAppstack; - $value = parent::__CONSTRUCT($file, $appstack, $inherit, $useAppstack); - return $value; - } - - /** - * @inheritDoc - */ - protected function getFullpath(string $file, bool $appstack) : string - { - $fullpath = app::getHomedir() . $file; - - if(app::getInstance('filesystem_local')->fileAvailable($fullpath)) { - return $fullpath; - } +class virtualAppstack extends json +{ + /** + * custom appstack for inheritance overriding + * @var null|array + */ + protected ?array $useAppstack = null; - if(!$appstack) { - throw new \codename\core\exception(self::EXCEPTION_GETFULLPATH_FILEMISSING, \codename\core\exception::$ERRORLEVEL_FATAL, array('file' => $fullpath, 'info' => 'use appstack?')); + /** + * {@inheritDoc} + */ + public function __construct( + string $file, + bool $appstack = false, + bool $inherit = false, + array $useAppstack = null + ) { + $this->useAppstack = $useAppstack; + return parent::__construct($file, $appstack, $inherit, $useAppstack); } - return app::getInheritedPath($file, $this->useAppstack); - } -} \ No newline at end of file + /** + * {@inheritDoc} + */ + protected function getFullpath(string $file, bool $appstack): string + { + $fullpath = app::getHomedir() . $file; + + if (app::getInstance('filesystem_local')->fileAvailable($fullpath)) { + return $fullpath; + } + + if (!$appstack) { + throw new exception(self::EXCEPTION_GETFULLPATH_FILEMISSING, exception::$ERRORLEVEL_FATAL, ['file' => $fullpath, 'info' => 'use appstack?']); + } + + return app::getInheritedPath($file, $this->useAppstack); + } +} diff --git a/backend/class/context/deployment.php b/backend/class/context/deployment.php index 0d546ec..aa5a4e3 100644 --- a/backend/class/context/deployment.php +++ b/backend/class/context/deployment.php @@ -1,48 +1,56 @@ getRequest()->getData('vendor'); - $app = $this->getRequest()->getData('app'); - $deploy = $this->getRequest()->getData('deploy'); - - $instance = \codename\architect\deploy\deployment::createFromConfig($vendor, $app, $deploy); - - $result = $instance->run(); - - $this->getResponse()->setData('deploymentresult', $result); - } - - /** - * [view_default description] - * @return void - */ - public function view_default() { - } - +class deployment extends context +{ + /** + * list available app configs for deployment + * @return void + */ + public function view_apps(): void + { + } + + /** + * view available tasks from a given deployment config + * @return void + */ + public function view_tasks(): void + { + } + + /** + * run a given deployment configuration + * @return void + * @throws ReflectionException + * @throws exception + */ + public function view_run(): void + { + $vendor = $this->getRequest()->getData('vendor'); + $app = $this->getRequest()->getData('app'); + $deploy = $this->getRequest()->getData('deploy'); + + $instance = \codename\architect\deploy\deployment::createFromConfig($vendor, $app, $deploy); + + $result = $instance->run(); + + $this->getResponse()->setData('deploymentresult', $result); + } + + /** + * [view_default description] + * @return void + */ + public function view_default(): void + { + } } diff --git a/backend/class/context/main.php b/backend/class/context/main.php index 470861c..a76bb09 100644 --- a/backend/class/context/main.php +++ b/backend/class/context/main.php @@ -1,84 +1,95 @@ view_listapps(); - $this->getResponse()->setData('view', 'listapps'); - } - - /** - * [view_listapps description] - * @return void - */ - public function view_listapps() { - - $apps = app::getSiblingApps(); - $this->getResponse()->setData('apps', $apps); - - $table = new \codename\core\ui\frontend\element\table(array( - 'templateengine' => $this->getResponse()->getData('templateengine') ?? 'default' - ), $apps); - - $this->getResponse()->setData('table', $table->outputString()); - } - - /** - * Displays a list of available models - * for a given vendor and app name - * @return void - */ - public function view_listmodels() { - if($this->getRequest()->getData('filter>vendor') != null && $this->getRequest()->getData('filter>app') != null) { - - $app = $this->getRequest()->getData('filter>app'); - $vendor = $this->getRequest()->getData('filter>vendor'); - - $exec_tasks = $this->getRequest()->getData('exec_tasks') ? array_values($this->getRequest()->getData('exec_tasks')) : array(task::TASK_TYPE_REQUIRED); // by default, only execute required tasks - - $dbdoc = new \codename\architect\dbdoc\dbdoc($app, $vendor); - - $stats = $dbdoc->run( - $this->getRequest()->getData('exec') == '1', - $exec_tasks - ); - - // store dbdoc output - $this->getResponse()->setData('dbdoc_stats', $stats); - - // store models dbdoc found - $this->getResponse()->setData('models', $dbdoc->models); - - // create a table - $table = new \codename\core\ui\frontend\element\table(array( - 'templateengine' => $this->getResponse()->getData('templateengine') ?? 'default', - 'columns' => [ /* 'vendor', 'app', */ 'identifier', 'model', 'schema', 'driver' ] - ), $dbdoc->models); - - $this->getResponse()->setData('table', $table->outputString()); - - } else { - - if($this->getRequest()->getData('filter>vendor') == null) { - throw new catchableException("EXCEPTION_ARCHITECT_CONTEXT_MAIN_MISSING_FILTER_VENDOR", catchableException::$ERRORLEVEL_ERROR); - } - if($this->getRequest()->getData('filter>app') == null) { - throw new catchableException("EXCEPTION_ARCHITECT_CONTEXT_MAIN_MISSING_FILTER_APP", catchableException::$ERRORLEVEL_ERROR); - } +class main extends context +{ + /** + * view "default" + * @throws ReflectionException + * @throws exception + */ + public function view_default(): void + { + $this->view_listapps(); + $this->getResponse()->setData('view', 'listapps'); + } + /** + * [view_listapps description] + * @return void + * @throws ReflectionException + * @throws exception + */ + public function view_listapps(): void + { + $apps = app::getSiblingApps(); + $this->getResponse()->setData('apps', $apps); + + $table = new table([ + 'templateengine' => $this->getResponse()->getData('templateengine') ?? 'default', + ], $apps); + + $this->getResponse()->setData('table', $table->outputString()); } - } -} \ No newline at end of file + /** + * Displays a list of available models + * for a given vendor and app name + * @return void + * @throws ReflectionException + * @throws catchableException + * @throws exception + */ + public function view_listmodels(): void + { + if ($this->getRequest()->getData('filter>vendor') != null && $this->getRequest()->getData('filter>app') != null) { + $app = $this->getRequest()->getData('filter>app'); + $vendor = $this->getRequest()->getData('filter>vendor'); + + $exec_tasks = $this->getRequest()->getData('exec_tasks') ? array_values($this->getRequest()->getData('exec_tasks')) : [task::TASK_TYPE_REQUIRED]; // by default, only execute required tasks + + $dbdoc = new dbdoc($app, $vendor); + + $stats = $dbdoc->run( + $this->getRequest()->getData('exec') == '1', + $exec_tasks + ); + + // store dbdoc output + $this->getResponse()->setData('dbdoc_stats', $stats); + + // store models dbdoc found + $this->getResponse()->setData('models', $dbdoc->models); + + // create a table + $table = new table([ + 'templateengine' => $this->getResponse()->getData('templateengine') ?? 'default', + 'columns' => [ /* 'vendor', 'app', */ 'identifier', 'model', 'schema', 'driver'], + ], $dbdoc->models); + + $this->getResponse()->setData('table', $table->outputString()); + } else { + if ($this->getRequest()->getData('filter>vendor') == null) { + throw new catchableException("EXCEPTION_ARCHITECT_CONTEXT_MAIN_MISSING_FILTER_VENDOR", catchableException::$ERRORLEVEL_ERROR); + } + if ($this->getRequest()->getData('filter>app') == null) { + throw new catchableException("EXCEPTION_ARCHITECT_CONTEXT_MAIN_MISSING_FILTER_APP", catchableException::$ERRORLEVEL_ERROR); + } + } + } +} diff --git a/backend/class/dbdoc/dbdoc.php b/backend/class/dbdoc/dbdoc.php index 6a37f07..0410909 100644 --- a/backend/class/dbdoc/dbdoc.php +++ b/backend/class/dbdoc/dbdoc.php @@ -1,531 +1,459 @@ errorstack = new errorstack('DBDOC'); - $this->app = $app; - $this->vendor = $vendor; - $this->env = $env ?? app::getEnv(); - $this->environment = $envConfig ?? null; - $this->init(); - } - - /** - * [protected description] - * @var string - */ - protected $app; - - /** - * returns the current app - * @return string - */ - public function getApp() : string { - return $this->app; - } - - /** - * [protected description] - * @var string - */ - protected $vendor; - - /** - * returns the current vendor - * @return string - */ - public function getVendor() : string { - return $this->vendor; - } - - /** - * [protected description] - * @var string - */ - protected $env; - - /** - * [getEnv description] - * @return string [description] - */ - public function getEnv() : string { - return $this->env; - } - - /** - * [model configurations loaded] - * @var array - */ - public $models; - - /** - * [protected description] - * @var \codename\core\config - */ - protected $environment; - - /** - * [protected description] - * @var \codename\architect\dbdoc\modeladapter - */ - protected $adapters = array(); - - /** - * [init description] - * @return [type] [description] - */ - public function init() { - - // should init empty model array! - $this->models = array(); - - $foreignAppstack = app::makeForeignAppstack($this->vendor, $this->app); - $modelConfigurations = app::getModelConfigurations($this->vendor, $this->app, '', $foreignAppstack); - $modelList = array(); - - foreach($modelConfigurations as $schema => $models) { - foreach($models as $modelname => $modelConfig) { - $modelList[] = array( - 'identifier' => "{$schema}_{$modelname}", - 'model' => $modelname, - 'vendor' => $this->vendor, - 'app' => $this->app, - 'schema' => $schema, - // 'driver' => 'dummy value', - 'config' => $modelConfig[0] // ?? - ); - } +class dbdoc +{ + /** + * Prefix used for getting the right environment configuration + * suitable for 'architecting' the app stuff. + * + * Information: + * As we'd like to make systems as secure as possible, + * we're providing two types of global system environment configuration sets. + * The first one is simply the real-world-production-state-config. + * + * You'd call it ... *surprise, surprise* ... 'production'. + * While this configuration should only provide _basic_ access to resources (like the database) + * e.g. only SELECT, UPDATE, JOIN, ... , DELETE (if, at all!) + * You have to have another set of credentials to be used for the deployment state + * of the application. + * + * Therefore, you __have__ to provide a second configuration. + * For example, you could either provide the same credentials, if you're using + * ROOT access in production systems for standard DB access. + * + * Nah, you really shouldn't do that. + * + * Instead, you supply those limited configs in production + * and root credentials needed for some structural stuff + * during deployment. + * + * We'd call it + * + * architect_production + * + * To sum it up, modify your environment.json to supply one more prefixed key + * for each env-key used. We assume you're using "dev" and "production". + * Therefore, you need to supply "architect_dev" and "architect_production". + * + * IMPORTANT HINT: + * Simply supply root credentials in architect_-prefixed configs. + * You don't have to create the credentials defined in the un-prefixed configs + * As the architect does this for you. + * + * Enjoy. + * + * @var string + */ + protected const ARCHITECT_ENV_PREFIX = 'architect_'; + /** + * Exception thrown if we're missing a specific env config key (with the deployment-mode prefix) + * @var string + */ + public const EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG = 'EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG'; + /** + * translate env config drivers to namespaced modeladapter + * @var array + */ + protected static array $driverTranslation = [ + 'mysql' => 'sql\\mysql', + 'sqlite' => 'sql\\sqlite', + ]; + /** + * [model configurations loaded] + * @var array + */ + public array $models; + /** + * [protected description] + * @var string + */ + protected string $app; + /** + * [protected description] + * @var string + */ + protected string $vendor; + /** + * [protected description] + * @var string + */ + protected string $env; + /** + * [protected description] + * @var null|config + */ + protected ?config $environment; + /** + * [protected description] + * @var array + */ + protected array $adapters = []; + /** + * [protected description] + * @var null|errorstack + */ + protected ?errorstack $errorstack = null; + + /** + * @param string $app + * @param string $vendor + * @param string|null $env [override environment by name] + * @param config|null $envConfig [override environment by config] + * @throws ReflectionException + * @throws exception + */ + public function __construct(string $app, string $vendor, ?string $env = null, ?config $envConfig = null) + { + $this->errorstack = new errorstack('DBDOC'); + $this->app = $app; + $this->vendor = $vendor; + $this->env = $env ?? app::getEnv(); + $this->environment = $envConfig ?? null; + $this->init(); } - $this->models = $modelList; + /** + * [getEnv description] + * @return string [description] + */ + public function getEnv(): string + { + return $this->env; + } - // Load this file by default - plus inheritance - // 'config/environment.json' - $this->environment = $this->environment ?? new \codename\core\config\json('config/environment.json', true, true, $foreignAppstack);; + /** + * [init description] + * @return void [type] [description] + * @throws ReflectionException + * @throws exception + */ + public function init(): void + { + // should init empty model array! + $this->models = []; + + $foreignAppstack = app::makeForeignAppstack($this->vendor, $this->app); + $modelConfigurations = app::getModelConfigurations($this->vendor, $this->app, '', $foreignAppstack); + $modelList = []; + + foreach ($modelConfigurations as $schema => $models) { + foreach ($models as $modelname => $modelConfig) { + $modelList[] = [ + 'identifier' => $schema . '_' . $modelname, + 'model' => $modelname, + 'vendor' => $this->vendor, + 'app' => $this->app, + 'schema' => $schema, + 'config' => $modelConfig[0], + ]; + } + } - // construct the prefixed environment config (used for deployment) - $prefixedEnvironmentName = $this->getPrefixedEnvironmentName(); + $this->models = $modelList; - // check for existance - if(!$this->environment->exists($prefixedEnvironmentName)) { - // this is needed. - // warn user/admin we're missing an important configuration part. - // throw new exception(self::EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG, exception::$ERRORLEVEL_FATAL, $prefixedEnvironmentName); - } + // Load this file by default - plus inheritance + // 'config/environment.json' + $this->environment = $this->environment ?? new json('config/environment.json', true, true, $foreignAppstack); - // initialize model adapters - foreach($this->models as &$m) { - - // skip models without connection - /* if(empty($m['config']['connection'])) { - continue; - }*/ - /* - $this->adapters[] = new \codename\architect\dbdoc\modeladapter\sql\mysql( - $this, - $m['schema'], - $m['model'], - new \codename\core\config($m['config']), - new \codename\architect\config\environment($this->environment->get(), $prefixedEnvironmentName) // prefixed environment name: e.g. architect_dev, see above - );*/ - - $modelAdapter = $this->getModelAdapter( - $m['schema'], - $m['model'], - $m['config'], - new \codename\architect\config\environment($this->environment->get(), $prefixedEnvironmentName) - ); - - if($modelAdapter != null) { - $this->adapters[] = $modelAdapter; - $m['driver'] = get_class($modelAdapter); - } else { - // error? - } - } + // construct the prefixed environment config (used for deployment) + $prefixedEnvironmentName = $this->getPrefixedEnvironmentName(); - // display errors on need! + // check for existence + if (!$this->environment->exists($prefixedEnvironmentName)) { + // this is needed. + // warn user/admin we're missing an important configuration part. + // throw new exception(self::EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG, exception::$ERRORLEVEL_FATAL, $prefixedEnvironmentName); + } - if(count($errors = $this->errorstack->getErrors()) > 0) { - // print_r($errors); - // die(); - throw new exception('DBDOC_ERRORS', exception::$ERRORLEVEL_FATAL, $errors); - } - } - - /** - * [getPrefixedEnvironmentName description] - * @return string [description] - */ - protected function getPrefixedEnvironmentName() : string { - return self::ARCHITECT_ENV_PREFIX . $this->env; - } - - /** - * translate env config drivers to namespaced modeladapters - * @var [type] - */ - protected static $driverTranslation = array( - 'mysql' => 'sql\\mysql', - 'sqlite' => 'sql\\sqlite' - ); - - /** - * [protected description] - * @var errorstack - */ - protected $errorstack = null; - - /** - * [getModelAdapter description] - * @param string $schema [description] - * @param string $model [description] - * @param array $config [description] - * @param \codename\architect\config\environment $env [description] - * @return \codename\architect\dbdoc\modeladapter [description] - */ - public function getModelAdapter(string $schema, string $model, array $config, \codename\architect\config\environment $env) { - - // validate model configuration - if(count($errors = app::getValidator('structure_config_model')->reset()->validate($config)) > 0) { - $this->errorstack->addErrors($errors); - return null; - } + // initialize model adapters + foreach ($this->models as &$m) { + $modelAdapter = $this->getModelAdapter( + $m['schema'], + $m['model'], + $m['config'], + new environment($this->environment->get(), $prefixedEnvironmentName) + ); + + if ($modelAdapter != null) { + $this->adapters[] = $modelAdapter; + $m['driver'] = get_class($modelAdapter); + } + } - // fallback adapter - $driver = 'bare'; + // display errors on need! - if(!empty($config['connection'])) { - // explicit connection. - // we can identify the driver used - $envDriver = $env->get('database>'.$config['connection'].'>driver'); - $driver = self::$driverTranslation[$envDriver] ?? null; + if (count($errors = $this->errorstack->getErrors()) > 0) { + throw new exception('DBDOC_ERRORS', exception::$ERRORLEVEL_FATAL, $errors); + } } - if($driver == null) { - return null; + /** + * [getPrefixedEnvironmentName description] + * @return string [description] + */ + protected function getPrefixedEnvironmentName(): string + { + return self::ARCHITECT_ENV_PREFIX . $this->env; } - $class = '\\codename\\architect\\dbdoc\\modeladapter\\' . $driver; - - if(class_exists($class)) { - return new $class( - $this, - $schema, - $model, - new \codename\core\config($config), - $env // prefixed environment name: e.g. architect_dev, see above - ); - } else { - // unknown driver - } + /** + * [getModelAdapter description] + * @param string $schema [description] + * @param string $model [description] + * @param array $config [description] + * @param environment $env [description] + * @return null|modeladapter [description] + * @throws ReflectionException + * @throws exception + */ + public function getModelAdapter(string $schema, string $model, array $config, environment $env): ?modeladapter + { + // validate model configuration + if (count($errors = app::getValidator('structure_config_model')->reset()->validate($config)) > 0) { + $this->errorstack->addErrors($errors); + return null; + } - return null; - } - - - /** - * Exception thrown if we're missing a specific env config key (with the deployment-mode prefix) - * @var string - */ - const EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG = 'EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG'; - - /** - * [getAdapter description] - * @param string $schema [description] - * @param string $model [description] - * @param string $app [description] - * @param string $vendor [description] - * @return \codename\architect\dbdoc\modeladapter [description] - */ - public function getAdapter(string $schema, string $model, string $app = '', string $vendor = '') { - $app = ($app == '') ? $this->getApp() : $app; - $vendor = ($vendor == '') ? $this->getVendor() : $vendor; - - if(($this->getApp() != $app) || ($this->getVendor() != $vendor)) { - // get a foreign adapter - // init a new dbdoc instance - $foreignDbDoc = new self($app, $vendor); - return $foreignDbDoc->getAdapter($schema, $model, $app, $vendor); - } - foreach($this->adapters as $adapter) { - if($adapter->schema == $schema && $adapter->model == $model) { - return $adapter; - } - } + // fallback adapter + $driver = 'bare'; - throw new catchableException('DBDOC_GETADAPTER_NOTFOUND', exception::$ERRORLEVEL_ERROR, array($schema, $model, $app, $vendor)); - } - - /** - * stable usort function - * @var [type] - */ - protected static function stable_usort(array &$array, $value_compare_func) - { - $index = 0; - foreach ($array as &$item) { - $item = array($index++, $item); - } - $result = usort($array, function($a, $b) use($value_compare_func) { - $result = call_user_func($value_compare_func, $a[1], $b[1]); - return $result == 0 ? $a[0] - $b[0] : $result; - }); - foreach ($array as &$item) { - $item = $item[1]; - } - return $result; - } - - /** - * [uasort description] - * @param array $array [description] - * @param [type] $value_compare_func [description] - * @return [type] [description] - */ - protected static function stable_uasort(array &$array, $value_compare_func) - { - $index = 0; - foreach ($array as &$item) { - $item = array($index++, $item); - } - $result = uasort($array, function($a, $b) use($value_compare_func) { - $result = call_user_func($value_compare_func, $a[1], $b[1]); - return $result == 0 ? $a[0] - $b[0] : $result; - }); - foreach ($array as &$item) { - $item = $item[1]; - } - return $result; - } - - /** - * [run description] - * @param boolean $exec [execute the tasks] - * @param int[] $exec_tasks [limit execution to specific task types. task::TASK_TYPE_...] - * @return array [some data] - */ - public function run(bool $exec = false, array $exec_tasks = array( )) : array { - - $tasks = array(); - - foreach($this->adapters as $dbdoc_ma) { - - $newTasks = $dbdoc_ma->runDiagnostics(); - $filteredTasks = array(); - - // do some intelligent comparison between existing and to-be-merged tasks to cut out duplicates - foreach($newTasks as $newTask) { - $duplicate = false; - if($newTask->identifier != null) { - foreach($tasks as $existingTask) { - if($newTask->identifier == $existingTask->identifier) { - // mark as duplicate - $duplicate = true; - break; - } - } + if (!empty($config['connection'])) { + // explicit connection. + // we can identify the driver used + $envDriver = $env->get('database>' . $config['connection'] . '>driver'); + $driver = self::$driverTranslation[$envDriver] ?? null; + } + + if ($driver == null) { + return null; } - if(!$duplicate) { - $filteredTasks[] = $newTask; + + $class = '\\codename\\architect\\dbdoc\\modeladapter\\' . $driver; + + if (class_exists($class)) { + return new $class( + $this, + $schema, + $model, + new config($config), + $env // prefixed environment name: e.g. architect_dev, see above + ); } - } - $tasks = array_merge($tasks, $filteredTasks); + return null; } - // priority sorting, based on precededBy value - $sort_success = self::stable_usort($tasks, function(task $taskA, task $taskB) { - - /* - echo("
"); - echo("
{$taskA->name} vs. {$taskB->name}"); - echo("
taskA: {$taskA->identifier}"); - echo("
taskB: {$taskB->identifier}"); - echo("
TaskA.precededBy:\n". print_r($taskA->precededBy,true) . "\nTaskB.precededBy:\n". print_r($taskB->precededBy,true) ."
"); - */ - - if((count($taskA->precededBy) == 0) && (count($taskB->precededBy) == 0)) { - // no precendence defined - // echo("
{$taskA->name} == {$taskB->name}"); - return 0; - } - - - /* - if(in_array($taskB->identifier, $taskA->precededBy)) { - echo("
{$taskA->name} < {$taskB->name}"); - return -1; - } - */ - - /* if(in_array($taskA->identifier, $taskB->precededBy)) { - echo("
{$taskA->name} > {$taskB->name}"); - return 1; - }*/ - - // check if B requires A - foreach($taskB->precededBy as $identifier) { - // echo("
-- comparing {$identifier} ___ AND ___ {$taskA->identifier}"); - if( (strlen($taskA->identifier) >= strlen($identifier)) && strpos($taskA->identifier, $identifier) === 0) { - /* - echo("
"); - echo("
{$taskA->name} vs. {$taskB->name}"); - echo("
taskA: {$taskA->identifier}"); - echo("
taskB: {$taskA->identifier}"); - */ - // echo("
-- {$taskA->name} > {$taskB->name}"); - - return -1; + /** + * [uasort description] + * @param array $array [description] + * @param [type] $value_compare_func [description] + * @return bool [type] [description] + */ + protected static function stable_uasort(array &$array, $value_compare_func): bool + { + $index = 0; + foreach ($array as &$item) { + $item = [$index++, $item]; } - } - - // check if A requires B - foreach($taskA->precededBy as $identifier) { - // echo("
-- comparing {$identifier} ___ AND ___ {$taskB->identifier}"); - if( (strlen($taskB->identifier) >= strlen($identifier)) && strpos($taskB->identifier, $identifier) === 0) { - /* - echo("
"); - echo("
{$taskA->name} vs. {$taskB->name}"); - echo("
taskA: {$taskA->identifier}"); - echo("
taskB: {$taskA->identifier}"); - */ - // echo("
-- {$taskA->name} < {$taskB->name}"); - return +1; + $result = uasort($array, function ($a, $b) use ($value_compare_func) { + $result = call_user_func($value_compare_func, $a[1], $b[1]); + return $result == 0 ? $a[0] - $b[0] : $result; + }); + foreach ($array as &$item) { + $item = $item[1]; } - } - - /* - echo("
"); - echo("
{$taskA->name} vs. {$taskB->name}"); - echo("
taskA: {$taskA->identifier}"); - echo("
taskB: {$taskB->identifier}"); - echo("
TaskA.precededBy:\n". print_r($taskA->precededBy,true) . "\nTaskB.precededBy:\n". print_r($taskB->precededBy,true) ."
"); - */ - - // echo("
-- {$taskA->name} == {$taskB->name}"); - // echo("
{$taskA->name} == {$taskB->name} : " . var_export($taskA->precededBy,true) . var_export($taskB->precededBy,true)); - // echo("
-- equal (no precedence)."); - - return 0; // was 0 // was +1 - }); - - if(!$sort_success) { - echo("Sort unsuccessful!"); - die(); + return $result; } - $availableTasks = array(); - $availableTaskTypes = array(); - $executedTasks = array(); - - foreach($tasks as $t) { - // if($t->type == task::TASK_TYPE_REQUIRED) { - - if($exec) { - // validate the task type itself - if(count($errors = app::getValidator('number_tasktype')->reset()->validate($t->type)) === 0) { - $taskType = task::TASK_TYPES[$t->type]; - - // check if requested - if(in_array($t->type, $exec_tasks)) { - // echo("
executing {$taskType} task ... "); - $executedTasks[] = $t; - - // Run the task! - $t->run(); - } else { - // echo("
skipping {$taskType} task ... "); - $availableTaskTypes[] = $t->type; - $availableTasks[] = $t; - } - } else { - // echo("
invalid {$taskType}, skipping ... "); + /** + * [getAdapter description] + * @param string $schema [description] + * @param string $model [description] + * @param string $app [description] + * @param string $vendor [description] + * @return modeladapter [description] + * @throws ReflectionException + * @throws catchableException + * @throws exception + */ + public function getAdapter(string $schema, string $model, string $app = '', string $vendor = ''): modeladapter + { + $app = ($app == '') ? $this->getApp() : $app; + $vendor = ($vendor == '') ? $this->getVendor() : $vendor; + + if (($this->getApp() != $app) || ($this->getVendor() != $vendor)) { + // get a foreign adapter + // init a new dbdoc instance + $foreignDbDoc = new self($app, $vendor); + return $foreignDbDoc->getAdapter($schema, $model, $app, $vendor); + } + foreach ($this->adapters as $adapter) { + if ($adapter->schema == $schema && $adapter->model == $model) { + return $adapter; + } } - } else { - $availableTaskTypes[] = $t->type; - $availableTasks[] = $t; - } + throw new catchableException('DBDOC_GETADAPTER_NOTFOUND', catchableException::$ERRORLEVEL_ERROR, [$schema, $model, $app, $vendor]); + } + + /** + * returns the current app + * @return string + */ + public function getApp(): string + { + return $this->app; } - $availableTaskTypes = array_unique($availableTaskTypes); - // translate: - $availableTaskTypesAssoc = array(); - foreach($availableTaskTypes as $type) { - $availableTaskTypesAssoc[task::TASK_TYPES[$type]] = $type; + /** + * returns the current vendor + * @return string + */ + public function getVendor(): string + { + return $this->vendor; } - return array( - 'tasks' => $tasks, - 'available_tasks' => $availableTasks, - 'available_task_types' => $availableTaskTypesAssoc, - 'executed_tasks' => $executedTasks, - 'executed_task_types' => $exec_tasks - ); + /** + * [run description] + * @param bool $exec [execute the tasks] + * @param int[] $exec_tasks [limit execution to specific task types. task::TASK_TYPE_...] + * @return array [some data] + * @throws ReflectionException + * @throws exception + */ + public function run(bool $exec = false, array $exec_tasks = []): array + { + $tasks = []; + + foreach ($this->adapters as $dbdoc_ma) { + $newTasks = $dbdoc_ma->runDiagnostics(); + $filteredTasks = []; + + // do some intelligent comparison between existing and to-be-merged tasks to cut out duplicates + foreach ($newTasks as $newTask) { + $duplicate = false; + if ($newTask->identifier != null) { + foreach ($tasks as $existingTask) { + if ($newTask->identifier == $existingTask->identifier) { + // mark as duplicate + $duplicate = true; + break; + } + } + } + if (!$duplicate) { + $filteredTasks[] = $newTask; + } + } + + $tasks = array_merge($tasks, $filteredTasks); + } + + // priority sorting, based on precededBy value + $sort_success = self::stable_usort($tasks, function (task $taskA, task $taskB) { + if ((count($taskA->precededBy) == 0) && (count($taskB->precededBy) == 0)) { + // no precedence defined + return 0; + } + + // check if B requires A + foreach ($taskB->precededBy as $identifier) { + if ((strlen($taskA->identifier) >= strlen($identifier)) && str_starts_with($taskA->identifier, $identifier)) { + return -1; + } + } + + // check if A requires B + foreach ($taskA->precededBy as $identifier) { + if ((strlen($taskB->identifier) >= strlen($identifier)) && str_starts_with($taskB->identifier, $identifier)) { + return +1; + } + } + + return 0; // was 0 // was +1 + }); + + if (!$sort_success) { + echo("Sort unsuccessful!"); + die(); + } + + $availableTasks = []; + $availableTaskTypes = []; + $executedTasks = []; + + foreach ($tasks as $t) { + if ($exec) { + // validate the task type itself + if (count(app::getValidator('number_tasktype')->reset()->validate($t->type)) === 0) { + // check if requested + if (in_array($t->type, $exec_tasks)) { + $executedTasks[] = $t; + + // Run the task! + $t->run(); + } else { + $availableTaskTypes[] = $t->type; + $availableTasks[] = $t; + } + } + } else { + $availableTaskTypes[] = $t->type; + $availableTasks[] = $t; + } + } + + $availableTaskTypes = array_unique($availableTaskTypes); + // translate: + $availableTaskTypesAssoc = []; + foreach ($availableTaskTypes as $type) { + $availableTaskTypesAssoc[task::TASK_TYPES[$type]] = $type; + } - } + return [ + 'tasks' => $tasks, + 'available_tasks' => $availableTasks, + 'available_task_types' => $availableTaskTypesAssoc, + 'executed_tasks' => $executedTasks, + 'executed_task_types' => $exec_tasks, + ]; + } + /** + * stable usort function + * @param array $array + * @param $value_compare_func + * @return bool + */ + protected static function stable_usort(array &$array, $value_compare_func): bool + { + $index = 0; + foreach ($array as &$item) { + $item = [$index++, $item]; + } + $result = usort($array, function ($a, $b) use ($value_compare_func) { + $result = call_user_func($value_compare_func, $a[1], $b[1]); + return $result == 0 ? $a[0] - $b[0] : $result; + }); + foreach ($array as &$item) { + $item = $item[1]; + } + return $result; + } } diff --git a/backend/class/dbdoc/modeladapter.php b/backend/class/dbdoc/modeladapter.php index b9185a0..b0906df 100644 --- a/backend/class/dbdoc/modeladapter.php +++ b/backend/class/dbdoc/modeladapter.php @@ -1,163 +1,162 @@ schema}.{$this->model}"; - } - - /** - * [getPlugins description] - * @return string[] - */ - public function getPlugins() : array { - return array(); - } - - /** - * plugin execution queue - * @var \codename\architect\dbdoc\plugin[] - */ - protected $executionQueue = array(); - - /** - * [addToQueue description] - * @param \codename\architect\dbdoc\plugin $plugin [description] - */ - public function addToQueue(\codename\architect\dbdoc\plugin $plugin, bool $insertAtBeginning = false) { - if($insertAtBeginning) { - array_unshift($this->executionQueue, $plugin); - } else { - $this->executionQueue[] = $plugin; - } - } - - /** - * [getNextQueuedPlugin description] - * @return \codename\architect\dbdoc\plugin [description] - */ - protected function getNextQueuedPlugin() { - return array_shift($this->executionQueue); - } - - /** - * Creates a new structural model for DDL - */ - public function __construct(\codename\architect\dbdoc\dbdoc $dbdocInstance, string $schema, string $model, \codename\core\config $config, \codename\architect\config\environment $environment) - { - $this->dbdoc = $dbdocInstance; - $this->schema = $schema; - $this->model = $model; - $this->config = $config; - $this->environment = $environment; - } - - /** - * [parent dbdoc instance] - * @var dbdoc - */ - public $dbdoc; - - /** - * [getPluginInstance description] - * @param string $pluginIdentifier [description] - * @param array $parameter [description] - * @return \codename\architect\dbdoc\plugin [description] - */ - public function getPluginInstance(string $pluginIdentifier, array $parameter = array(), bool $isVirtual = false) { - foreach($this->getPluginCompat() as $compat) { - $classname = "\\codename\\architect\\dbdoc\\plugin\\" . str_replace('_', '\\', $compat . '_' . $pluginIdentifier); - if(class_exists($classname) && !(new \ReflectionClass($classname))->isAbstract()) { - return new $classname($this, $parameter, $isVirtual); - } - } - return null; - } - - - /** - * [runDiagnostics description] - * @return task[] [description] - */ - public function runDiagnostics() : array{ - - // load plugins - foreach($this->getPlugins() as $pluginIdentifier) { - $plugin = $this->getPluginInstance($pluginIdentifier, array()); - if($plugin != null) { - $this->addToQueue($plugin); - } +abstract class modeladapter +{ + /** + * model configuration + * @var config + */ + public config $config; + /** + * environment configuration + * @var environment + */ + public environment $environment; + /** + * model schema + * @var string + */ + public string $schema; + /** + * model name + * @var string + */ + public string $model; + /** + * [parent dbdoc instance] + * @var dbdoc + */ + public dbdoc $dbdoc; + /** + * plugin execution queue + * @var null|array + */ + protected ?array $executionQueue = []; + + /** + * Creates a new structural model for DDL + */ + public function __construct(dbdoc $dbdocInstance, string $schema, string $model, config $config, environment $environment) + { + $this->dbdoc = $dbdocInstance; + $this->schema = $schema; + $this->model = $model; + $this->config = $config; + $this->environment = $environment; } - $tasks = array(); + /** + * get compatible driver name + * @return string + */ + abstract public function getDriverCompat(): string; + + /** + * at the moment, this just puts out the identifier + * (model) + * @return string [description] + */ + public function getIdentifier(): string + { + return "$this->schema.$this->model"; + } - // loop through unshift - $plugin = $this->getNextQueuedPlugin(); + /** + * [runDiagnostics description] + * @return task[] [description] + */ + public function runDiagnostics(): array + { + // load plugins + foreach ($this->getPlugins() as $pluginIdentifier) { + $plugin = $this->getPluginInstance($pluginIdentifier); + if ($plugin != null) { + $this->addToQueue($plugin); + } + } + + $tasks = []; + + // loop through unshift + $plugin = $this->getNextQueuedPlugin(); + + while ($plugin != null) { + $tasks = array_merge($tasks, $plugin->Compare()); + $plugin = $this->getNextQueuedPlugin(); + } + + return $tasks; + } - while($plugin != null) { - $tasks = array_merge($tasks, $plugin->Compare()); - $plugin = $this->getNextQueuedPlugin(); + /** + * [getPlugins description] + * @return string[] + */ + public function getPlugins(): array + { + return []; } - /* - foreach($tasks as $t) { - $taskType = task::TASK_TYPES[$t->type]; - echo("
Task [{$taskType}] [id:{$t->identifier}] {$t->plugin}::{$t->name} " . var_export($t->data, true)); - }*/ + /** + * [getPluginInstance description] + * @param string $pluginIdentifier [description] + * @param array $parameter [description] + * @param bool $isVirtual + * @return plugin|null [description] + */ + public function getPluginInstance(string $pluginIdentifier, array $parameter = [], bool $isVirtual = false): ?plugin + { + foreach ($this->getPluginCompat() as $compat) { + $classname = "\\codename\\architect\\dbdoc\\plugin\\" . str_replace('_', '\\', $compat . '_' . $pluginIdentifier); + if (class_exists($classname) && !(new ReflectionClass($classname))->isAbstract()) { + return new $classname($this, $parameter, $isVirtual); + } + } + return null; + } - return $tasks; - } + /** + * get compatible plugin namespaces + * @return string[] + */ + abstract public function getPluginCompat(): array; + + /** + * @param plugin $plugin + * @param bool $insertAtBeginning + * @return void + */ + public function addToQueue(plugin $plugin, bool $insertAtBeginning = false): void + { + if ($insertAtBeginning) { + array_unshift($this->executionQueue, $plugin); + } else { + $this->executionQueue[] = $plugin; + } + } - /** - * [getStructure description] - * @return codename\architect\dbdoc\structure [description] - */ - // public function getStructure() : \codename\architect\dbdoc\structure; + /** + * [getNextQueuedPlugin description] + * @return mixed [description] + */ + protected function getNextQueuedPlugin(): mixed + { + return array_shift($this->executionQueue); + } + /** + * [getStructure description] + * @return structure [description] + */ + // public function getStructure() : structure; } diff --git a/backend/class/dbdoc/modeladapter/bare.php b/backend/class/dbdoc/modeladapter/bare.php index 394a89b..1d1ca29 100644 --- a/backend/class/dbdoc/modeladapter/bare.php +++ b/backend/class/dbdoc/modeladapter/bare.php @@ -1,45 +1,48 @@ adapter instanceof sql) { + return $this->adapter; + } + throw new exception('EXCEPTION_GETSQLADAPTER_WRONG_OBJECT', exception::$ERRORLEVEL_FATAL); + } +} diff --git a/backend/class/dbdoc/modeladapter/sql.php b/backend/class/dbdoc/modeladapter/sql.php index d663ed4..69bdfc7 100644 --- a/backend/class/dbdoc/modeladapter/sql.php +++ b/backend/class/dbdoc/modeladapter/sql.php @@ -1,65 +1,75 @@ adapter; - } -} +use codename\architect\app; +use codename\architect\config\environment; +use codename\architect\dbdoc\dbdoc; +use codename\architect\dbdoc\modeladapter; +use codename\core\config; +use codename\core\database; +use codename\core\exception; +use codename\core\value\text\objectidentifier; +use codename\core\value\text\objecttype; +use ReflectionException; /** * sql ddl adapter * @package architect */ -abstract class sql extends \codename\architect\dbdoc\modeladapter { - - /** - * Contains the database connection - * @var \codename\core\database - */ - public $db = null; +abstract class sql extends modeladapter +{ + /** + * Contains the database connection + * @var null|database + */ + public ?database $db = null; - /** - * @inheritDoc - */ - public function __construct(\codename\architect\dbdoc\dbdoc $dbdocInstance, string $schema, string $model, \codename\core\config $config, \codename\architect\config\environment $environment) - { - parent::__construct($dbdocInstance, $schema, $model, $config, $environment); + /** + * {@inheritDoc} + * @param dbdoc $dbdocInstance + * @param string $schema + * @param string $model + * @param config $config + * @param environment $environment + * @throws ReflectionException + * @throws exception + */ + public function __construct(dbdoc $dbdocInstance, string $schema, string $model, config $config, environment $environment) + { + parent::__construct($dbdocInstance, $schema, $model, $config, $environment); - // establish database connection - // we require a special environment configuration - // in the environment - $this->db = $this->getDatabaseConnection($this->config->get('connection')); - } + // establish database connection + // we require a special environment configuration + // in the environment + $this->db = $this->getDatabaseConnection($this->config->get('connection')); + } - /** - * @inheritDoc - */ - public function getPlugins() : array - { - return array( - 'initial' - // 'connection', - /* 'schema', - 'table', - 'fieldlist' */ - ); - } + /** + * [loadDatabaseConnection description] + * @param string $identifier [description] + * @return database [type] [description] + * @throws ReflectionException + * @throws exception + */ + protected function getDatabaseConnection(string $identifier = 'default'): database + { + $dbValueObjecttype = new objecttype('database'); + $dbValueObjectidentifier = new objectidentifier($identifier); + $object = app::getForeignClient($this->environment, $dbValueObjecttype, $dbValueObjectidentifier); + if ($object instanceof database) { + return $object; + } + throw new exception('EXCEPTION_GETDATABASECONNECTION_WRONG_OBJECT', exception::$ERRORLEVEL_FATAL); + } - /** - * [loadDatabaseConnection description] - * @param string $identifier [description] - * @return [type] [description] - */ - protected function getDatabaseConnection(string $identifier = 'default') : \codename\core\database { - $dbValueObjecttype = new \codename\core\value\text\objecttype('database'); - $dbValueObjectidentifier = new \codename\core\value\text\objectidentifier($identifier); - return app::getForeignClient($this->environment, $dbValueObjecttype, $dbValueObjectidentifier); - } - -} \ No newline at end of file + /** + * {@inheritDoc} + */ + public function getPlugins(): array + { + return [ + 'initial', + ]; + } +} diff --git a/backend/class/dbdoc/modeladapter/sql/mysql.php b/backend/class/dbdoc/modeladapter/sql/mysql.php index 172e528..f2ebce2 100644 --- a/backend/class/dbdoc/modeladapter/sql/mysql.php +++ b/backend/class/dbdoc/modeladapter/sql/mysql.php @@ -1,31 +1,31 @@ initEvents(); - $this->virtual = $isVirtual; - $this->parameter = $parameter; - $this->adapter = $adapter; - } - - /** - * gets the model definition data for this plugin - */ - public abstract function getDefinition(); - - /** - * gets the current structure, retrieved via the adapter - */ - public abstract function getStructure(); - - /** - * do the comparison job - * @return task[] - */ - public function Compare() : array{ - return array(); - } - - /** - * Determines the plugin run type - * virtual means, the plugin does not check the state - * via getStructure. - * This is useful for creating a complete run - * without having to rely on re-runs, - * because structures have to exist before - * @return bool [description] - */ - public function isVirtual() : bool { - return $this->virtual; - } - - /** - * virtual mode - * @var bool - */ - protected $virtual = false; - - /** - * [runTask description] - * @param \codename\core\config $taskConfig [description] - * @return [type] [description] - */ - public function runTask(task $task) { - - } - - /** - * [getPluginIdentifier description] - * @return string [description] - */ - public function getPluginIdentifier() : string { - return str_replace('\\', '_', str_replace('codename\\architect\\dbdoc\\plugin\\', '', get_class($this))); - } - - /** - * [createTask description] - * @param int $taskType [task type ] - * @param string $taskName [custom task name] - * @param array $config [configuration] - * @param array $precendence [task identifer prefixes that have to be finished first] - * @return [type] [description] - */ - protected function createTask(int $taskType = task::TASK_TYPE_INFO, string $taskName, array $config = array(), array $precededBy = array()) { - $task = new \codename\architect\dbdoc\task($taskType, $taskName, $this->adapter, $this->getPluginIdentifier(), new \codename\core\config($config)); - $task->precededBy = $precededBy; - $task->identifier = "{$this->getTaskIdentifierPrefix()}_{$taskType}_{$taskName}_". serialize($config); - return $task; - } - - /** - * [getTaskIdentifierPrefix description] - * @return string - */ - protected function getTaskIdentifierPrefix() : string { - return "{$this->adapter->dbdoc->getVendor()}_{$this->adapter->dbdoc->getApp()}_"; - } - - /** - * init events - */ - private function initEvents() { - $this->onSuccess = new \codename\core\event('PLUGIN_COMPARE_ON_SUCCESS'); - $this->onFail = new \codename\core\event('PLUGIN_COMPARE_ON_FAIL'); - $this->onError = new \codename\core\event('PLUGIN_COMPARE_ON_ERROR'); - } - - /** - * event fired, if the comparison was successful - * @var \codename\core\event - */ - public $onSuccess = null; // new \codename\core\event('PLUGIN_COMPARE_ON_SUCCESS'); - - /** - * event fired, if the comparison failed - * @var \codename\core\event - */ - public $onFail = null; // new \codename\core\event('PLUGIN_COMPARE_ON_FAIL'); - - /** - * event fired, if the comparison was interrupted (!) - * @var \codename\core\event - */ - public $onError = null; // new \codename\core\event('PLUGIN_COMPARE_ON_ERROR'); - -} \ No newline at end of file +abstract class plugin +{ + /** + * event fired, if the comparison was successful + * @var null|event + */ + public ?event $onSuccess = null; + /** + * event fired, if the comparison failed + * @var null|event + */ + public ?event $onFail = null; + /** + * event fired, if the comparison was interrupted (!) + * @var null|event + */ + public ?event $onError = null; + /** + * the adapter + * @var modeladapter + */ + protected modeladapter $adapter; + /** + * [protected description] + * @var array + */ + protected array $parameter; + /** + * virtual mode + * @var bool + */ + protected bool $virtual = false; + + /** + * @param modeladapter $adapter + * @param array $parameter + * @param bool $isVirtual + */ + public function __construct(modeladapter $adapter, array $parameter = [], bool $isVirtual = false) + { + $this->initEvents(); + $this->virtual = $isVirtual; + $this->parameter = $parameter; + $this->adapter = $adapter; + } + + /** + * init events + */ + private function initEvents(): void + { + $this->onSuccess = new event('PLUGIN_COMPARE_ON_SUCCESS'); + $this->onFail = new event('PLUGIN_COMPARE_ON_FAIL'); + $this->onError = new event('PLUGIN_COMPARE_ON_ERROR'); + } + + /** + * gets the model definition data for this plugin + */ + abstract public function getDefinition(); + + /** + * gets the current structure, retrieved via the adapter + */ + abstract public function getStructure(); + + /** + * do the comparison job + * @return task[] + */ + public function Compare(): array + { + return []; + } + + /** + * Determines the plugin run type + * virtual means, the plugin does not check the state + * via getStructure. + * This is useful for creating a complete run + * without having to rely on re-runs, + * because structures have to exist before + * @return bool [description] + */ + public function isVirtual(): bool + { + return $this->virtual; + } + + /** + * [runTask description] + * @param task $task + * @return void [type] [description] + */ + public function runTask(task $task): void + { + } + + /** + * [createTask description] + * @param int $taskType [task type ] + * @param string $taskName [custom task name] + * @param array $config [configuration] + * @param array $precededBy + * @return task [type] [description] + */ + protected function createTask(int $taskType, string $taskName, array $config = [], array $precededBy = []): task + { + $task = new task($taskType, $taskName, $this->adapter, $this->getPluginIdentifier(), new config($config)); + $task->precededBy = $precededBy; + $task->identifier = "{$this->getTaskIdentifierPrefix()}_{$taskType}_{$taskName}_" . serialize($config); + return $task; + } + + /** + * [getPluginIdentifier description] + * @return string [description] + */ + public function getPluginIdentifier(): string + { + return str_replace('\\', '_', str_replace('codename\\architect\\dbdoc\\plugin\\', '', get_class($this))); + } + + /** + * [getTaskIdentifierPrefix description] + * @return string + */ + protected function getTaskIdentifierPrefix(): string + { + return "{$this->adapter->dbdoc->getVendor()}_{$this->adapter->dbdoc->getApp()}_"; + } +} diff --git a/backend/class/dbdoc/plugin/collection.php b/backend/class/dbdoc/plugin/collection.php index 1010bf9..e652678 100644 --- a/backend/class/dbdoc/plugin/collection.php +++ b/backend/class/dbdoc/plugin/collection.php @@ -1,18 +1,18 @@ adapter->config->get('collection') ?? array(); - } - -} \ No newline at end of file +abstract class collection extends modelPrefix +{ + /** + * {@inheritDoc} + */ + public function getDefinition(): mixed + { + return $this->adapter->config->get('collection') ?? []; + } +} diff --git a/backend/class/dbdoc/plugin/connectionPrefix.php b/backend/class/dbdoc/plugin/connectionPrefix.php index f79cabb..8cb0630 100644 --- a/backend/class/dbdoc/plugin/connectionPrefix.php +++ b/backend/class/dbdoc/plugin/connectionPrefix.php @@ -1,19 +1,21 @@ adapter->config->get('connection')}_"; - } - -} \ No newline at end of file +abstract class connectionPrefix extends plugin +{ + /** + * {@inheritDoc} + */ + protected function getTaskIdentifierPrefix(): string + { + return parent::getTaskIdentifierPrefix() . "{$this->adapter->config->get('connection')}_"; + } +} diff --git a/backend/class/dbdoc/plugin/database.php b/backend/class/dbdoc/plugin/database.php index d7c6f1f..cc9abd6 100644 --- a/backend/class/dbdoc/plugin/database.php +++ b/backend/class/dbdoc/plugin/database.php @@ -1,35 +1,37 @@ adapter->config->get('connection') ?? 'default'; - $globalEnv = \codename\architect\app::getEnv(); - $environment = $this->adapter->environment; - - // backup env key - $prevEnv = $environment->getEnvironmentKey(); - - // change env key - $environment->setEnvironmentKey($globalEnv); - - // get database name - $databaseName = $environment->get('database>'.$connection.'>database'); - - // revert env key - $environment->setEnvironmentKey($prevEnv); - - return $databaseName; - } - -} \ No newline at end of file +abstract class database extends connectionPrefix +{ + /** + * {@inheritDoc} + */ + public function getDefinition(): mixed + { + // get database specifier from model (connection) + $connection = $this->adapter->config->get('connection') ?? 'default'; + $globalEnv = app::getEnv(); + $environment = $this->adapter->environment; + + // backup env key + $prevEnv = $environment->getEnvironmentKey(); + + // change env key + $environment->setEnvironmentKey($globalEnv); + + // get database name + $databaseName = $environment->get('database>' . $connection . '>database'); + + // revert env key + $environment->setEnvironmentKey($prevEnv); + + return $databaseName; + } +} diff --git a/backend/class/dbdoc/plugin/field.php b/backend/class/dbdoc/plugin/field.php index 97e27af..b8bb550 100644 --- a/backend/class/dbdoc/plugin/field.php +++ b/backend/class/dbdoc/plugin/field.php @@ -1,36 +1,35 @@ parameter['field']; - $def = array( - 'field' => $field, - 'notnull' => in_array($field, $this->adapter->config->get('notnull') ?? []), - // 'default' => $this->adapter->config->get('default>' . $field), - // NOTE: 'primary' => true/false -- should be handled in an extra plugin for EACH TABLE ! this is just to overcome some too field-specific stuff - 'primary' => in_array($field, $this->adapter->config->get('primary') ?? array()), - 'foreign' => is_array($field) ? null : $this->adapter->config->get('foreign>' . $field), - 'datatype' => is_array($field) ? null : $this->adapter->config->get('datatype>' . $field), - 'collection' => is_array($field) ? null : $this->adapter->config->get('collection>' . $field), - 'children' => is_array($field) ? null : $this->adapter->config->get('children>' . $field), - 'options' => is_array($field) ? null : $this->adapter->config->get('options>' . $field) ?? [] - ); +abstract class field extends modelPrefix +{ + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + $field = $this->parameter['field']; + $def = [ + 'field' => $field, + 'notnull' => in_array($field, $this->adapter->config->get('notnull') ?? []), + // 'default' => $this->adapter->config->get('default>' . $field), + // NOTE: 'primary' => true/false -- should be handled in an extra plugin for EACH TABLE ! this is just to overcome some too field-specific stuff + 'primary' => in_array($field, $this->adapter->config->get('primary') ?? []), + 'foreign' => is_array($field) ? null : $this->adapter->config->get('foreign>' . $field), + 'datatype' => is_array($field) ? null : $this->adapter->config->get('datatype>' . $field), + 'collection' => is_array($field) ? null : $this->adapter->config->get('collection>' . $field), + 'children' => is_array($field) ? null : $this->adapter->config->get('children>' . $field), + 'options' => is_array($field) ? null : $this->adapter->config->get('options>' . $field) ?? [], + ]; - if($this->adapter->config->exists('default')) { - $def['default'] = $this->adapter->config->get('default>' . $field); + if ($this->adapter->config->exists('default')) { + $def['default'] = $this->adapter->config->get('default>' . $field); + } + return $def; } - return $def; - } - - -} \ No newline at end of file +} diff --git a/backend/class/dbdoc/plugin/fieldlist.php b/backend/class/dbdoc/plugin/fieldlist.php index 33be276..e56fd30 100644 --- a/backend/class/dbdoc/plugin/fieldlist.php +++ b/backend/class/dbdoc/plugin/fieldlist.php @@ -1,18 +1,18 @@ adapter->config->get('field'); - } - -} \ No newline at end of file +abstract class fieldlist extends modelPrefix +{ + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + return $this->adapter->config->get('field'); + } +} diff --git a/backend/class/dbdoc/plugin/foreign.php b/backend/class/dbdoc/plugin/foreign.php index 59eb884..52cfd58 100644 --- a/backend/class/dbdoc/plugin/foreign.php +++ b/backend/class/dbdoc/plugin/foreign.php @@ -1,18 +1,18 @@ adapter->config->get('foreign') ?? array(); - } - -} \ No newline at end of file +abstract class foreign extends modelPrefix +{ + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + return $this->adapter->config->get('foreign') ?? []; + } +} diff --git a/backend/class/dbdoc/plugin/fulltext.php b/backend/class/dbdoc/plugin/fulltext.php index bce579a..cd7d463 100644 --- a/backend/class/dbdoc/plugin/fulltext.php +++ b/backend/class/dbdoc/plugin/fulltext.php @@ -1,18 +1,18 @@ adapter->config->get('fulltext') ?? array(); - } - +abstract class fulltext extends modelPrefix +{ + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + return $this->adapter->config->get('fulltext') ?? []; + } } diff --git a/backend/class/dbdoc/plugin/index.php b/backend/class/dbdoc/plugin/index.php index 8cb5e09..e2ff428 100644 --- a/backend/class/dbdoc/plugin/index.php +++ b/backend/class/dbdoc/plugin/index.php @@ -1,4 +1,5 @@ adapter->config->get('index') ?? array(); - } - -} \ No newline at end of file +abstract class index extends modelPrefix +{ + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + return $this->adapter->config->get('index') ?? []; + } +} diff --git a/backend/class/dbdoc/plugin/initial.php b/backend/class/dbdoc/plugin/initial.php index 6e0c5e3..1cc79d5 100644 --- a/backend/class/dbdoc/plugin/initial.php +++ b/backend/class/dbdoc/plugin/initial.php @@ -1,10 +1,11 @@ adapter->model}_"; - } - -} \ No newline at end of file +abstract class modelPrefix extends schemaPrefix +{ + /** + * {@inheritDoc} + */ + protected function getTaskIdentifierPrefix(): string + { + return parent::getTaskIdentifierPrefix() . "{$this->adapter->model}_"; + } +} diff --git a/backend/class/dbdoc/plugin/permissions.php b/backend/class/dbdoc/plugin/permissions.php index c9a5318..842ee99 100644 --- a/backend/class/dbdoc/plugin/permissions.php +++ b/backend/class/dbdoc/plugin/permissions.php @@ -1,10 +1,11 @@ adapter->config->get('primary'); - if(count($primary) === 0) { - throw new catchableException(self::EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MISSING, catchableException::$ERRORLEVEL_FATAL, $this->adapter->schema); - } else if(count($primary) > 1) { - throw new catchableException(self::EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MULTIPLE, catchableException::$ERRORLEVEL_FATAL, $this->adapter->schema); + /** + * {@inheritDoc} + * @return mixed + * @throws catchableException + */ + public function getDefinition(): mixed + { + $primary = $this->adapter->config->get('primary'); + if (count($primary) === 0) { + throw new catchableException(self::EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MISSING, catchableException::$ERRORLEVEL_FATAL, $this->adapter->schema); + } elseif (count($primary) > 1) { + throw new catchableException(self::EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MULTIPLE, catchableException::$ERRORLEVEL_FATAL, $this->adapter->schema); + } + return $primary[0]; } - return $primary[0]; - } - -} \ No newline at end of file +} diff --git a/backend/class/dbdoc/plugin/schema.php b/backend/class/dbdoc/plugin/schema.php index 89a52d4..a969cda 100644 --- a/backend/class/dbdoc/plugin/schema.php +++ b/backend/class/dbdoc/plugin/schema.php @@ -1,18 +1,18 @@ adapter->schema; - } - -} \ No newline at end of file +abstract class schema extends connectionPrefix +{ + /** + * {@inheritDoc} + */ + public function getDefinition(): ?string + { + return $this->adapter->schema; + } +} diff --git a/backend/class/dbdoc/plugin/schemaPrefix.php b/backend/class/dbdoc/plugin/schemaPrefix.php index 138a660..820b9c1 100644 --- a/backend/class/dbdoc/plugin/schemaPrefix.php +++ b/backend/class/dbdoc/plugin/schemaPrefix.php @@ -1,4 +1,5 @@ adapter->schema}_"; - } - -} \ No newline at end of file +abstract class schemaPrefix extends connectionPrefix +{ + /** + * {@inheritDoc} + */ + protected function getTaskIdentifierPrefix(): string + { + return parent::getTaskIdentifierPrefix() . "{$this->adapter->schema}_"; + } +} diff --git a/backend/class/dbdoc/plugin/sql/collection.php b/backend/class/dbdoc/plugin/sql/collection.php index 6d579ee..336e7e2 100644 --- a/backend/class/dbdoc/plugin/sql/collection.php +++ b/backend/class/dbdoc/plugin/sql/collection.php @@ -1,68 +1,30 @@ getDefinition(); - - $precededBy = []; - - // - // TODO: Check, if the given collection config is correct - // - - // foreach($collectionDefinitions as $def) { - // - // // $foreignAdapter = $this->adapter->dbdoc->getAdapter($def['schema'], $def['model'], $def['app'] ?? '', $def['vendor'] ?? ''); - // // $plugin = $foreignAdapter->getPluginInstance('table', [], $this->virtual /*, array('field' => $def['key'])*/ ); - // // if($plugin != null) { - // // $precededBy[] = $plugin->getTaskIdentifierPrefix(); - // // } - // - // $aux = $def['aux']; - // $auxAdapter = $this->adapter->dbdoc->getAdapter($aux['schema'], $aux['model'], $aux['app'] ?? '', $aux['vendor'] ?? ''); - // $plugin = $auxAdapter->getPluginInstance('table', [], $this->virtual /*, array('field' => $aux['key'])*/); - // if($plugin != null) { - // $precededBy[] = $plugin->getTaskIdentifierPrefix(); - // } else { - // echo("table plugin is null"); - // } - // - // print_r($precededBy); - // - // $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "DUMMY_COLLECTION_TASK_RUN", - // array( - // // 'field' => $field, - // 'config' => $def, - // ), - // $precededBy - // ); - // } - - return $tasks; - } - +class collection extends \codename\architect\dbdoc\plugin\collection +{ + /** + * {@inheritDoc} + */ + public function getStructure(): array + { + return []; + } + + /** + * {@inheritDoc} + */ + public function Compare(): array + { + // + // TODO: Check, if the given collection config is correct + // + + return []; + } } diff --git a/backend/class/dbdoc/plugin/sql/field.php b/backend/class/dbdoc/plugin/sql/field.php index 2c8b412..87a2ac7 100644 --- a/backend/class/dbdoc/plugin/sql/field.php +++ b/backend/class/dbdoc/plugin/sql/field.php @@ -1,495 +1,508 @@ adapter->getPluginInstance('primary', array(), $this->virtual); - $definition = array_replace($definition, $plugin->getDefinition()); - } +abstract class field extends \codename\architect\dbdoc\plugin\field +{ + use modeladapterGetSqlAdapter; - // - // NOTE: we can only sync column datatypes if it's not a structure (e.g. array) - // - if($definition['foreign'] && $definition['datatype'] != 'structure') { - // we have to get field information from a different model (!) - // , $def['app'] ?? '', $def['vendor'] ?? '' - $foreignAdapter = $this->adapter->dbdoc->getAdapter( - $definition['foreign']['schema'], - $definition['foreign']['model'], - $definition['foreign']['app'] ?? '', - $definition['foreign']['vendor'] ?? '' - ); - $plugin = $foreignAdapter->getPluginInstance('field', array('field' => $definition['foreign']['key'])); - if($plugin != null) { - $foreignDefinition = $plugin->getDefinition(); - - // equalize datatypes - // both the referenced column and this one have to be of the same type - $definition['options']['db_data_type'] = $foreignDefinition['options']['db_data_type']; - $definition['options']['db_column_type'] = $foreignDefinition['options']['db_column_type']; - // TODO: NEW OPTIONS FORMAT/SETTING? - - // TODO: we may warn, if there's a configurational difference! - } - } + /** + * basic conversion table between sql defaults and core framework + * @var string[] + */ + protected $conversionTable = [ + 'text' => ['text', 'mediumtext', 'longtext'], + 'text_timestamp' => ['datetime'], + 'text_date' => ['date'], + 'number' => ['numeric', 'decimal'], // was integer + 'number_natural' => ['integer', 'int', 'bigint'], + 'boolean' => ['boolean'], + 'structure' => ['text', 'mediumtext', 'longtext'], + 'mixed' => ['text'], + // 'virtual' => [ null ] + // 'collection' + ]; - // - // Handle automatic fallback to current_timestamp() for _created fields in models - // except we override it in the model - // - if(!isset($definition['default']) && $definition['field'] == $this->adapter->model.'_created') { - $definition['default'] = 'current_timestamp()'; - } + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws catchableException + * @throws exception + */ + public function Compare(): array + { + $tasks = []; + $definition = $this->getDefinition(); + + // cancel, if field is a collection (virtual field) + if ($definition['collection']) { + return $tasks; + } - return $definition; - } - - /** - * @inheritDoc - */ - public function getStructure() - { - // get some column specifications - $db = $this->getSqlAdapter()->db; - $db->query( - "SELECT column_name, column_type, data_type, is_nullable, column_default - FROM information_schema.columns - WHERE table_schema = '{$this->adapter->schema}' - AND table_name = '{$this->adapter->model}' - AND column_name = '{$this->parameter['field']}';" - ); - $res = $db->getResult(); - if(count($res) === 1) { - return $res[0]; - } - return null; - } - - /** - * @inheritDoc - */ - public function Compare() : array - { - $tasks = array(); - $definition = $this->getDefinition(); - - // cancel, if field is a collection (virtual field) - if($definition['collection']) { - return $tasks; - } + // cancel, if field is a virtual field + if ($definition['datatype'] == 'virtual') { + return $tasks; + } - // cancel, if field is a virtual field - if($definition['datatype'] == 'virtual') { - return $tasks; - } + // override with definition from primary plugin + if ($definition['primary']) { + $plugin = $this->adapter->getPluginInstance('primary', [], $this->virtual); + if ($plugin != null) { + $definition = $plugin->getDefinition(); + } + } - // override with definition from primary plugin - if($definition['primary']) { - $plugin = $this->adapter->getPluginInstance('primary', array(), $this->virtual); - if($plugin != null) { - $definition = $plugin->getDefinition(); - } - } + $structure = $this->virtual ? null : $this->getStructure(); + + if ($structure != null) { + /* + echo("
");
+            print_r($definition);
+            echo("
"); + + echo("
");
+            print_r($structure);
+            echo("
"); + */ + // TODO: check field properties + + // compare db_data_type + // compare db_column_type + + // echo("
{$definition['db_column_type']} <=> {$structure['column_type']}"); + + $checkDataType = true; + + $column_type = trim(preg_replace('/\(.*\)/', '', $structure['column_type'])); + + if ( + $definition['options']['db_column_type'] != null && + !in_array($structure['column_type'], $definition['options']['db_column_type']) && + !in_array($column_type, $definition['options']['db_column_type']) + ) { + /* $definition['options']['db_column_type'] != $structure['column_type'] */ + // check for array-based definition + // different column type! + // echo(" -- unequal?"); + /* echo("
");
+                print_r($structure);
+                print_r($definition);
+                echo("
"); */ + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_COLUMN_TYPE", $definition); + } else { + $checkDataType = false; + } - $structure = $this->virtual ? null : $this->getStructure(); - - if($structure != null) { - /* - echo("
");
-      print_r($definition);
-      echo("
"); - - echo("
");
-      print_r($structure);
-      echo("
"); - */ - // TODO: check field properties - - // compare db_data_type - // compare db_column_type - - // echo("
{$definition['db_column_type']} <=> {$structure['column_type']}"); - - $checkDataType = true; - - $column_type = trim(preg_replace('/\(.*\)/','',$structure['column_type'])); - - if ( - $definition['options']['db_column_type'] != null && - !in_array($structure['column_type'], $definition['options']['db_column_type']) && - !in_array($column_type, $definition['options']['db_column_type']) - ) { - /* $definition['options']['db_column_type'] != $structure['column_type'] */ - // check for array-based definition - // different column type! - // echo(" -- unequal?"); - /* echo("
");
-        print_r($structure);
-        print_r($definition);
-        echo("
"); */ - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_COLUMN_TYPE", $definition); - } else { - $checkDataType = false; - } - - if($checkDataType) { - // echo("
{$definition['db_data_type']} <=> {$structure['data_type']}"); - if($definition['options']['db_data_type'] != null && !in_array($structure['data_type'], $definition['options']['db_data_type'])) { - // different data type! - // echo(" -- unequal?"); - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_DATA_TYPE", $definition); - } - } - - // mysql uses a varchar(3) for storing is_nullable (yes / no) - if($definition['notnull'] && $structure['is_nullable'] == 'YES') { - // make not nullable! - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_NOTNULL", $definition); - } - - - if(isset($definition['default'])) { - // set default column value - - if(is_bool($definition['default'])) { - if($definition['default'] != boolval($structure['column_default'])) { - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_DEFAULT", $definition); - } - } else if(is_int($definition['default'])) { - if($definition['default'] != intval($structure['column_default'])) { - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_DEFAULT", $definition); - } - } else if(is_string($definition['default'])) { - if($definition['default'] != $structure['column_default']) { - $definition['debug'] = $structure; - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_DEFAULT", $definition); - } - } // TODO: DEFAULT ARRAY VALUE - /* else if(is_array($definition['default'])) { - if(json_encode($definition['default']) != $structure['column_default']) { - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_DEFAULT", $definition); - } - }*/ - - } - - - } else { - // some error ! - // print_r($definition); - // print_r($structure); - - // only create, if not primary - // if it is, it is created in the table plugin (at least for mysql) - if(!$definition['primary']) { - // create create-field task - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_COLUMN", array( - 'field' => $definition['field'], - // 'def' => $definition - // 'datatype' => $definition['datatype'], - // 'datatype_override' => $definition['datatype_override'], - // 'db_datatype' => $definition['datatype_override'] ?? $this->convertModelDataTypeToDbType($definition['datatype']) // first item == default? - )); - } - } + if ($checkDataType) { + // echo("
{$definition['db_data_type']} <=> {$structure['data_type']}"); + if ($definition['options']['db_data_type'] != null && !in_array($structure['data_type'], $definition['options']['db_data_type'])) { + // different data type! + // echo(" -- unequal?"); + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_DATA_TYPE", $definition); + } + } - return $tasks; - } + // mysql uses a varchar(3) for storing is_nullable (yes / no) + if ($definition['notnull'] && $structure['is_nullable'] == 'YES') { + // make not nullable! + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_NOTNULL", $definition); + } - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; + if (isset($definition['default'])) { + // set default column value + + if (is_bool($definition['default'])) { + if ($definition['default'] != boolval($structure['column_default'])) { + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_DEFAULT", $definition); + } + } elseif (is_int($definition['default'])) { + if ($definition['default'] != intval($structure['column_default'])) { + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_DEFAULT", $definition); + } + } elseif (is_string($definition['default'])) { + if ($definition['default'] != $structure['column_default']) { + $definition['debug'] = $structure; + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_DEFAULT", $definition); + } + } // TODO: DEFAULT ARRAY VALUE + /* elseif(is_array($definition['default'])) { + if(json_encode($definition['default']) != $structure['column_default']) { + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "MODIFY_DEFAULT", $definition); + } + }*/ + } + } elseif (!$definition['primary']) { + // some error ! + // print_r($definition); + // print_r($structure); + + // only create, if not primary + // is it is, it is created in the table plugin (at least for mysql) + + // create create-field task + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_COLUMN", [ + 'field' => $definition['field'], + // 'def' => $definition + // 'datatype' => $definition['datatype'], + // 'datatype_override' => $definition['datatype_override'], + // 'db_datatype' => $definition['datatype_override'] ?? $this->convertModelDataTypeToDbType($definition['datatype']) // first item == default? + ]); + } - $definition = $this->getDefinition(); + return $tasks; + } - if($task->name == "CREATE_COLUMN") { + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws catchableException + * @throws exception + */ + public function getDefinition(): array + { + $definition = parent::getDefinition(); - $attributes = array(); + // required fields for SQL database adapters: + $definition['options']['db_data_type'] = $definition['options']['db_data_type'] ?? null; + $definition['options']['db_column_type'] = $definition['options']['db_column_type'] ?? null; - if($definition['notnull']) { - $attributes[] = "NOT NULL"; - } + if ($definition['primary']) { + $plugin = $this->adapter->getPluginInstance('primary', [], $this->virtual); + $definition = array_replace($definition, $plugin->getDefinition()); + } - if(isset($definition['default'])) { // - // Special case: field is timestamp && default is CURRENT_TIMESTAMP + // NOTE: we can only sync column datatype if it's not a structure (e.g. array) // - if($definition['datatype'] === 'text_timestamp' && $definition['default'] == 'current_timestamp()') { - $attributes[] = "DEFAULT ".$definition['default']; - } else { - $attributes[] = "DEFAULT ".json_encode($definition['default']); + if ($definition['foreign'] && $definition['datatype'] != 'structure') { + // we have to get field information from a different model (!) + // , $def['app'] ?? '', $def['vendor'] ?? '' + $foreignAdapter = $this->adapter->dbdoc->getAdapter( + $definition['foreign']['schema'], + $definition['foreign']['model'], + $definition['foreign']['app'] ?? '', + $definition['foreign']['vendor'] ?? '' + ); + $plugin = $foreignAdapter->getPluginInstance('field', ['field' => $definition['foreign']['key']]); + if ($plugin != null) { + $foreignDefinition = $plugin->getDefinition(); + + // equalize datatype + // both the referenced column and this one have to be of the same type + $definition['options']['db_data_type'] = $foreignDefinition['options']['db_data_type']; + $definition['options']['db_column_type'] = $foreignDefinition['options']['db_column_type']; + // TODO: NEW OPTIONS FORMAT/SETTING? + + // TODO: we may warn, if there's a configuration difference! + } } - } - - /* - // not allowed on normal fields? some requirements have to be met? - if($definition['auto_increment']) { - $attributes[] = "AUTO_INCREMENT"; - }*/ - - // TODO: add unique - // TODO: add index - $add = implode(' ', $attributes); - - // fallback from specific column types to a more generous type - $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0]; - $db->query( - "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} ADD COLUMN {$definition['field']} {$columnType} {$add};" - ); + // + // Handle automatic fallback to current_timestamp() for _created fields in models + // except we override it in the model + // + if (!isset($definition['default']) && $definition['field'] == $this->adapter->model . '_created') { + $definition['default'] = 'current_timestamp()'; + } + return $definition; } - if($task->name == "MODIFY_COLUMN_TYPE" || $task->name == "MODIFY_DATA_TYPE" || $task->name == "MODIFY_NOTNULL" || $task->name == "MODIFY_DEFAULT") { - // ALTER TABLE tablename MODIFY columnname INTEGER; - $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0]; - $nullable = $definition['notnull'] ? 'NOT NULL' : 'NULL'; - - if(array_key_exists('default', $definition) && $definition['datatype'] === 'text_timestamp' && $definition['default'] == 'current_timestamp()') { - $defaultValue = $definition['default'] ?? null; - } else { - $defaultValue = json_encode($definition['default'] ?? null); - } - - // - // we should update the existing dataset - // if it's NOT nullable - // - if($definition['notnull'] && $definition['default'] != null) { + /** + * {@inheritDoc} + * @return mixed + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): mixed + { + // get some column specifications + $db = $this->getSqlAdapter()->db; $db->query( - "UPDATE {$this->adapter->schema}.{$this->adapter->model} SET {$definition['field']} = {$defaultValue} WHERE {$definition['field']} IS NULL;" + "SELECT column_name, column_type, data_type, is_nullable, column_default + FROM information_schema.columns + WHERE table_schema = '{$this->adapter->schema}' + AND table_name = '{$this->adapter->model}' + AND column_name = '{$this->parameter['field']}';" ); - } + $res = $db->getResult(); + if (count($res) === 1) { + return $res[0]; + } + return null; + } - $default = isset($definition['default']) ? 'DEFAULT ' . $defaultValue.'' : ''; + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws catchableException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; - $db->query( - "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} MODIFY COLUMN {$definition['field']} {$columnType} {$nullable} {$default};" - ); - } + $definition = $this->getDefinition(); + + if ($task->name == "CREATE_COLUMN") { + $attributes = []; - } - - - /** - * basic conversion table between sql defaults and core framework - * @var string[] - */ - protected $conversionTable = array( - 'text' => [ 'text', 'mediumtext', 'longtext' ], - 'text_timestamp' => [ 'datetime' ], - 'text_date' => [ 'date' ], - 'number' => [ 'numeric', 'decimal' ], // was integer - 'number_natural' => [ 'integer', 'int', 'bigint' ], - 'boolean' => [ 'boolean' ], - 'structure' => [ 'text', 'mediumtext', 'longtext' ], - 'mixed' => [ 'text' ], - // 'virtual' => [ null ] - // 'collection' - ); - - /** - * [getDatatypeConversionTable description] - * @return array [description] - */ - public function getDatatypeConversionTable(): array - { - return $this->conversionTable; - } - - /** - * gets all conversion options for converting - * model datatype => db datatype - * @param string $key [datatype / validator] - * @return string[] [description] - */ - protected function getDatatypeConversionOptions(string $key) { - $conversionTable = $this->getDatatypeConversionTable(); - if(array_key_exists($key,$conversionTable)) { - // use defined type - $res = $conversionTable[$key]; - return $res; - } else { - $keyComponents = explode('_', $key); - $keyComponentCount = count($keyComponents); - - // CHANGED/ADDED: add top-down search - // recursively re-combine $t's elements and reduce each loop by 1 - // NOTE: the direct full match is handled above - for ($i=0; $i < $keyComponentCount; $i++) { - $testKey = implode('_', array_slice($keyComponents, 0, $keyComponentCount - $i)); - if(array_key_exists($testKey, $conversionTable)) { - $res = $conversionTable[$testKey]; - return $res; + if ($definition['notnull']) { + $attributes[] = "NOT NULL"; + } + + if (isset($definition['default'])) { + // + // Special case: field is timestamp && default is CURRENT_TIMESTAMP + // + if ($definition['datatype'] === 'text_timestamp' && $definition['default'] == 'current_timestamp()') { + $attributes[] = "DEFAULT " . $definition['default']; + } else { + $attributes[] = "DEFAULT " . json_encode($definition['default']); + } + } + + /* + // not allowed on normal fields? some requirements have to be met? + if($definition['auto_increment']) { + $attributes[] = "AUTO_INCREMENT"; + }*/ + + // TODO: add unique + // TODO: add index + + $add = implode(' ', $attributes); + + // fallback from specific column types to a more generous type + $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0]; + $db->query( + "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} ADD COLUMN {$definition['field']} $columnType $add;" + ); } - } - // throw some error, as it is not in our type definition library - throw new catchableException('EXCEPTION_DBDOC_MODEL_DATATYPE_NOT_IN_DEFINITION_LIBRARY', catchableException::$ERRORLEVEL_ERROR, array($key, $keyComponents[0])); - } - } - - /** - * [convertModelDataTypeToDbDataType description] - * @param [type] $t [description] - * @return string [db data type from conversion table] - */ - public function convertModelDataTypeToDbDataType($t) { - if($t == null) { - throw new exception("EXCEPTION_DBDOC_PLUGIN_SQL_FIELD_MODEL_DATATYPE_NULL", exception::$ERRORLEVEL_ERROR, $this->parameter); - } - $conversionOptions = $this->getDatatypeConversionOptions($t); - return $this->getDatatypeConversionOptions($t); // all results - } - - - /** - * [getDbDataTypeDefaultsTable description] - * @return array [description] - */ - public abstract function getDbDataTypeDefaultsTable(): array; - - /** - * [convertDbDataTypeToDbColumnTypeDefault description] - * @param [type] $t [description] - * @return [type] [description] - */ - public function convertDbDataTypeToDbColumnTypeDefault($t) { - - if($t == null) { - throw new exception("EXCEPTION_DBDOC_PLUGIN_SQL_FIELD_NO_COLUMN_TYPE_TRANSLATION_AVAILABLE", exception::$ERRORLEVEL_ERROR, $this); - } + if ($task->name == "MODIFY_COLUMN_TYPE" || $task->name == "MODIFY_DATA_TYPE" || $task->name == "MODIFY_NOTNULL" || $task->name == "MODIFY_DEFAULT") { + // ALTER TABLE tablename MODIFY columnname INTEGER; + $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0]; + $nullable = $definition['notnull'] ? 'NOT NULL' : 'NULL'; - // check for existing overrides/matching types - $conversionTable = $this->getDbDataTypeDefaultsTable(); - - // make $t an array, if it's not - $checkTypes = !is_array($t) ? [$t] : $t; - - $res = []; - foreach($checkTypes as $checkType) { - if(array_key_exists($checkType,$conversionTable)) { - // use defined type - $res[] = $conversionTable[$checkType]; - } else { - $tArr = explode('_', $checkType); - if(array_key_exists($tArr[0], $conversionTable)) { - // we have a defined underlying db field type - $res[] = $conversionTable[$tArr[0]]; - } else { - // throw some error, as it is not in our type definition library - // throw new \codename\core\exception('EXCEPTION_DBDOC_MODEL_COLUMN_TYPE_NOT_IN_DEFINITION_LIBRARY', catchableException::$ERRORLEVEL_ERROR, array($t, $tArr[0])); - // return null; + if (array_key_exists('default', $definition) && $definition['datatype'] === 'text_timestamp' && $definition['default'] == 'current_timestamp()') { + $defaultValue = $definition['default']; + } else { + $defaultValue = json_encode($definition['default'] ?? null); + } + + // + // we should update the existing dataset + // if it's NOT nullable + // + if ($definition['notnull'] && $definition['default'] != null) { + $db->query( + "UPDATE {$this->adapter->schema}.{$this->adapter->model} SET {$definition['field']} = $defaultValue WHERE {$definition['field']} IS NULL;" + ); + } + + $default = isset($definition['default']) ? 'DEFAULT ' . $defaultValue : ''; + + $db->query( + "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} MODIFY COLUMN {$definition['field']} $columnType $nullable $default;" + ); } - } } - return $res; - } - - /** - * converts a field configuration - * @return [type] [description] - */ - public function convertFieldConfigurationToDbColumnType(array $config = []) { + /** - * check: - * - datatype - * - options - * + db_datatype ? - * + (db_column_type) ? - * + length + * converts a field configuration + * @param array $config + * @return array [type] [description] + * @throws exception */ + public function convertFieldConfigurationToDbColumnType(array $config = []): array + { + /** + * check: + * - datatype + * - options + * + db_datatype ? + * + (db_column_type) ? + * + length + */ + + $dbDataType = $config['options']['db_data_type'] ?? null; + $dbColumnType = $config['options']['db_column_type'] ?? null; + $length = $config['options']['length'] ?? null; + $precision = $config['options']['precision'] ?? null; + + // explicit db_column_type not specified + if ($dbDataType == null) { + $tDbDataType = $this->convertModelDataTypeToDbDataType($config['datatype']); + // $dbDataType = count($tDbDataType) > 0 ? $tDbDataType[0] : null; + $dbDataType = $tDbDataType; + } - $datatype = $config['datatype']; - $dbDataType = $config['options']['db_data_type'] ?? null; - $dbColumnType = $config['options']['db_column_type'] ?? null; - $length = $config['options']['length'] ?? null; - $precision = $config['options']['precision'] ?? null; - - // explicit db_column_type not specified - if($dbDataType == null) { - $tDbDataType = $this->convertModelDataTypeToDbDataType($config['datatype']); - // $dbDataType = count($tDbDataType) > 0 ? $tDbDataType[0] : null; - $dbDataType = $tDbDataType; - } - - if($dbColumnType == null) { + if ($dbColumnType == null) { + $columnTypes = []; + foreach ($dbDataType as $type) { + switch ($type) { + case 'text': + if ($length) { + $columnTypes[] = "varchar($length)"; + } + break; + + case 'numeric': + if ($length && $precision) { + $columnTypes[] = "numeric($length,$precision)"; + } elseif ($length) { + $columnTypes[] = "numeric($length,0)"; + } + break; + + case 'decimal': + if ($length && $precision) { + $columnTypes[] = "decimal($length,$precision)"; + } elseif ($length) { + $columnTypes[] = "decimal($length,0)"; + } + break; + + case 'integer': + case 'int': + if ($length) { + $columnTypes[] = "int($length)"; + } + break; + + default: + # code... + break; + } + } - $columnTypes = []; - foreach($dbDataType as $type) { - switch ($type) { + $dbColumnType = count($columnTypes) > 0 ? $columnTypes : null; + } - case 'text': - if($length) { - $columnTypes[] = "varchar({$length})"; - } - break; + if ($dbColumnType == null) { + // $defaults = $this->convertDbDataTypeToDbColumnTypeDefault($dbDataType); + // $dbColumnType = count($defaults) > 0 ? $defaults[0] : null; // Should we fall back to the first entry? + $dbColumnType = $this->convertDbDataTypeToDbColumnTypeDefault($dbDataType); + } - case 'numeric': - if($length && $precision) { - $columnTypes[] = "numeric({$length},{$precision})"; - } else if($length) { - $columnTypes[] = "numeric({$length},0)"; - } - break; + return [ + 'db_column_type' => $dbColumnType && !is_array($dbColumnType) ? [$dbColumnType] : $dbColumnType, + 'db_data_type' => $dbDataType && !is_array($dbDataType) ? [$dbDataType] : $dbDataType, + ]; + } - case 'decimal': - if($length && $precision) { - $columnTypes[] = "decimal({$length},{$precision})"; - } else if($length) { - $columnTypes[] = "decimal({$length},0)"; - } - break; + /** + * [convertModelDataTypeToDbDataType description] + * @param [type] $t [description] + * @return array|string [db data type from conversion table] + * @throws catchableException + * @throws exception + */ + public function convertModelDataTypeToDbDataType($t): array|string + { + if ($t == null) { + throw new exception("EXCEPTION_DBDOC_PLUGIN_SQL_FIELD_MODEL_DATATYPE_NULL", exception::$ERRORLEVEL_ERROR, $this->parameter); + } + return $this->getDatatypeConversionOptions($t); // all results + } - case 'integer': - case 'int': - if($length) { - $columnTypes[] = "int({$length})"; + /** + * gets all conversion options for converting + * model datatype => db datatype + * @param string $key [datatype / validator] + * @return array|string [description] + * @throws catchableException + */ + protected function getDatatypeConversionOptions(string $key): array|string + { + $conversionTable = $this->getDatatypeConversionTable(); + if (array_key_exists($key, $conversionTable)) { + // use defined type + return $conversionTable[$key]; + } else { + $keyComponents = explode('_', $key); + $keyComponentCount = count($keyComponents); + + // CHANGED/ADDED: add top-down search + // recursively re-combine $t's elements and reduce each loop by 1 + // NOTE: the direct full match is handled above + for ($i = 0; $i < $keyComponentCount; $i++) { + $testKey = implode('_', array_slice($keyComponents, 0, $keyComponentCount - $i)); + if (array_key_exists($testKey, $conversionTable)) { + return $conversionTable[$testKey]; + } } - break; - default: - # code... - break; + // throw some error, as it is not in our type definition library + throw new catchableException('EXCEPTION_DBDOC_MODEL_DATATYPE_NOT_IN_DEFINITION_LIBRARY', catchableException::$ERRORLEVEL_ERROR, [$key, $keyComponents[0]]); } - } + } - $dbColumnType = count($columnTypes) > 0 ? $columnTypes : null; + /** + * [getDatatypeConversionTable description] + * @return array [description] + */ + public function getDatatypeConversionTable(): array + { + return $this->conversionTable; } - if($dbColumnType == null) { - // $defaults = $this->convertDbDataTypeToDbColumnTypeDefault($dbDataType); - // $dbColumnType = count($defaults) > 0 ? $defaults[0] : null; // Should we fallback to the first entry? - $dbColumnType = $this->convertDbDataTypeToDbColumnTypeDefault($dbDataType); + /** + * [convertDbDataTypeToDbColumnTypeDefault description] + * @param [type] $t [description] + * @return array [type] [description] + * @throws exception + */ + public function convertDbDataTypeToDbColumnTypeDefault($t): array + { + if ($t == null) { + throw new exception("EXCEPTION_DBDOC_PLUGIN_SQL_FIELD_NO_COLUMN_TYPE_TRANSLATION_AVAILABLE", exception::$ERRORLEVEL_ERROR, $this); + } + + // check for existing overrides/matching types + $conversionTable = $this->getDbDataTypeDefaultsTable(); + + // make $t an array, if it's not + $checkTypes = !is_array($t) ? [$t] : $t; + + $res = []; + foreach ($checkTypes as $checkType) { + if (array_key_exists($checkType, $conversionTable)) { + // use defined type + $res[] = $conversionTable[$checkType]; + } else { + $tArr = explode('_', $checkType); + if (array_key_exists($tArr[0], $conversionTable)) { + // we have a defined underlying db field type + $res[] = $conversionTable[$tArr[0]]; + } else { + // throw some error, as it is not in our type definition library + // throw new \codename\core\exception('EXCEPTION_DBDOC_MODEL_COLUMN_TYPE_NOT_IN_DEFINITION_LIBRARY', catchableException::$ERRORLEVEL_ERROR, array($t, $tArr[0])); + // return null; + } + } + } + return $res; } - return [ - 'db_column_type' => $dbColumnType && !is_array($dbColumnType) ? [$dbColumnType] : $dbColumnType, - 'db_data_type' => $dbDataType && !is_array($dbDataType) ? [$dbDataType] : $dbDataType - ]; - } + /** + * [getDbDataTypeDefaultsTable description] + * @return array [description] + */ + abstract public function getDbDataTypeDefaultsTable(): array; } diff --git a/backend/class/dbdoc/plugin/sql/fieldlist.php b/backend/class/dbdoc/plugin/sql/fieldlist.php index 7f9aab6..95a1864 100644 --- a/backend/class/dbdoc/plugin/sql/fieldlist.php +++ b/backend/class/dbdoc/plugin/sql/fieldlist.php @@ -1,86 +1,89 @@ getDefinition(); +// $structure = $this->getStructure(); + + // fields contained in model, that are not in the database table +// $missing = array_diff($definition, $structure); + + // columns in the database table, that are simply "too much" (not in the model definition) +// $toomuch = array_diff($structure, $definition); + + // TODO: handle toomuch + // e.g. check for prefix __old_ + // of not, create task to rename column + // otherwise, recommend harddeletion ? + + foreach ($definition as $field) { + $plugin = $this->adapter->getPluginInstance( + 'field', + [ + 'field' => $field, + ], + $this->virtual // virtual on need. + ); + + if ($plugin != null) { + // add this plugin to the first + $this->adapter->addToQueue($plugin, true); + } + } + + return []; + } - /** - * @inheritDoc - */ - public function getDefinition() - { - return $this->adapter->config->get('field'); - } + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + return $this->adapter->config->get('field'); + } - /** - * @inheritDoc - */ - public function getStructure() - { - $db = $this->getSqlAdapter()->db; - $db->query("SELECT column_name + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): array + { + $db = $this->getSqlAdapter()->db; + $db->query( + "SELECT column_name FROM information_schema.columns WHERE table_name = '{$this->adapter->model}' AND table_schema = '{$this->adapter->schema}' - ;"); - $res = $db->getResult(); - - $columns = array(); - foreach($res as $r) { - $columns[] = $r['column_name']; - } - - return $columns; - } - - /** - * @inheritDoc - */ - public function Compare() : array - { - $definition = $this->getDefinition(); - $structure = $this->getStructure(); - - // fields contained in model, that are not in the database table - $missing = array_diff($definition, $structure); + ;" + ); + $res = $db->getResult(); - // columns in the database table, that are simply "too much" (not in the model definition) - $toomuch = array_diff($structure, $definition); - - // TODO: handle toomuch - // e.g. check for prefix __old_ - // of not, create task to rename column - // otherwise, recommend harddeletion ? - - foreach($definition as $field) { - $plugin = $this->adapter->getPluginInstance( - 'field', - array( - 'field' => $field - ), - $this->virtual // virtual on need. - ); - - if($plugin != null) { - // add this plugin to the first - $this->adapter->addToQueue($plugin, true); - } - } - - // do something with it. - if(count($missing) == 0) { - - } else { + $columns = []; + foreach ($res as $r) { + $columns[] = $r['column_name']; + } + return $columns; } - - return array(); - } - -} \ No newline at end of file +} diff --git a/backend/class/dbdoc/plugin/sql/foreign.php b/backend/class/dbdoc/plugin/sql/foreign.php index 58d5cfb..612ac66 100644 --- a/backend/class/dbdoc/plugin/sql/foreign.php +++ b/backend/class/dbdoc/plugin/sql/foreign.php @@ -1,217 +1,211 @@ $config) { - // omit pure structure fields - if($this->adapter->config->get('datatype>'.$field) == 'structure') { - // omit. - } else { - $res[$field] = $config; - } +class foreign extends \codename\architect\dbdoc\plugin\foreign +{ + use modeladapterGetSqlAdapter; + + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws catchableException + * @throws exception + */ + public function Compare(): array + { + $tasks = []; + + $definition = $this->getDefinition(); + + // virtual = assume empty structure + $structure = $this->virtual ? [] : $this->getStructure(); + + $valid = []; + + foreach ($structure as $struc) { + // invalid or simply too much + if (array_key_exists($struc['column_name'], $definition)) { + // struc-def match, check values + $foreignConfig = $definition[$struc['column_name']]; + + if ($foreignConfig['schema'] != $struc['referenced_table_schema'] + || $foreignConfig['model'] != $struc['referenced_table_name'] + || $foreignConfig['key'] != $struc['referenced_column_name'] + ) { + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "MODIFY_FOREIGNKEY_CONSTRAINT", [ + 'constraint_name' => $struc['constraint_name'], + 'field' => $struc['column_name'], + 'config' => $foreignConfig, + ]); + } else { + $valid[$struc['column_name']] = $foreignConfig; + } + } else { + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "REMOVE_FOREIGNKEY_CONSTRAINT", [ + 'constraint_name' => $struc['constraint_name'], + ]); + } + } + + $missing = array_diff_key($definition, $valid); + + foreach ($missing as $field => $def) { + $precededBy = []; + + // let the task be preceded by tasks related to the existence of the foreign field + $foreignAdapter = $this->adapter->dbdoc->getAdapter($def['schema'], $def['model'], $def['app'] ?? '', $def['vendor'] ?? ''); + + // omit multi-component foreignkeys + if (isset($def['optional']) && $def['optional']) { + continue; + } + + $foreignFields = is_array($def['key']) ? array_values($def['key']) : [$def['key']]; + $nullPluginDetected = false; + foreach ($foreignFields as $key) { + $plugin = $foreignAdapter->getPluginInstance('field', ['field' => $key]); + if ($plugin != null) { + $precededBy[] = $plugin->getTaskIdentifierPrefix(); + } else { + // cancel here, as we might reference a model that can't be constructed + // in this case, the field plugin is null + $nullPluginDetected = true; + } + } + if ($nullPluginDetected) { + continue; + } + + // the foreign table + $plugin = $foreignAdapter->getPluginInstance('table'); + if ($plugin != null) { + $precededBy[] = $plugin->getTaskIdentifierPrefix(); + } + + // let the task be preceded by tasks related to the existence the field itself + $plugin = $this->adapter->getPluginInstance('field', ['field' => $field]); + if ($plugin != null) { + $precededBy[] = $plugin->getTaskIdentifierPrefix(); + } + + // the current table + $plugin = $this->adapter->getPluginInstance('table'); + if ($plugin != null) { + $precededBy[] = $plugin->getTaskIdentifierPrefix(); + } + + $tasks[] = $this->createTask( + task::TASK_TYPE_SUGGESTED, + "ADD_FOREIGNKEY_CONSTRAINT", + [ + 'field' => $field, + 'config' => $def, + ], + $precededBy + ); + } + + return $tasks; } - return $res; - } - /** - * @inheritDoc - */ - public function getStructure() - { - $db = $this->getSqlAdapter()->db; - - $db->query( - "SELECT tc.table_schema, tc.table_name, constraint_name, column_name, referenced_table_schema, referenced_table_name, referenced_column_name + + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + $def = parent::getDefinition(); + $res = []; + foreach ($def as $field => $config) { + // omit pure structure fields + if ($this->adapter->config->get('datatype>' . $field) == 'structure') { + // omit. + } else { + $res[$field] = $config; + } + } + return $res; + } + + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): array + { + $db = $this->getSqlAdapter()->db; + + $db->query( + "SELECT tc.table_schema, tc.table_name, constraint_name, column_name, referenced_table_schema, referenced_table_name, referenced_column_name FROM information_schema.table_constraints tc INNER JOIN information_schema.key_column_usage kcu USING (constraint_catalog, constraint_schema, constraint_name) WHERE constraint_type = 'FOREIGN KEY' AND tc.table_schema = '{$this->adapter->schema}' - AND tc.table_name = '{$this->adapter->model}';"); - - $constraints = $db->getResult(); - - return $constraints; - } - - /** - * @inheritDoc - */ - public function Compare() : array - { - $tasks = array(); - - $definition = $this->getDefinition(); - - // virtual = assume empty structure - $structure = $this->virtual ? array() : $this->getStructure(); - - $valid = array(); - $missing = array(); - $toomuch = array(); - - foreach($structure as $struc) { - - // invalid or simply too much - if(array_key_exists($struc['column_name'], $definition)) { - // struc-def match, check values - $foreignConfig = $definition[$struc['column_name']]; - - if($foreignConfig['schema'] != $struc['referenced_table_schema'] - || $foreignConfig['model'] != $struc['referenced_table_name'] - || $foreignConfig['key'] != $struc['referenced_column_name'] - ) { - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "MODIFY_FOREIGNKEY_CONSTRAINT", array( - 'constraint_name' => $struc['constraint_name'], - 'field' => $struc['column_name'], - 'config' => $foreignConfig - )); - } else { - $valid[$struc['column_name']] = $foreignConfig; - } + AND tc.table_name = '{$this->adapter->model}';" + ); - } else { - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "REMOVE_FOREIGNKEY_CONSTRAINT", array( - 'constraint_name' => $struc['constraint_name'] - )); - } + return $db->getResult(); } - $missing = array_diff_key($definition, $valid); - - foreach($missing as $field => $def) { - - $precededBy = array(); - - // let the task be preceded by tasks related to the existance of the foreign field - $foreignAdapter = $this->adapter->dbdoc->getAdapter($def['schema'], $def['model'], $def['app'] ?? '', $def['vendor'] ?? ''); - - // omit multi-component foreignkeys - if(isset($def['optional']) && $def['optional']) { - continue; - } - - $foreignFields = is_array($def['key']) ? array_values($def['key']) : [$def['key']]; - $nullPluginDetected = false; - foreach($foreignFields as $key) { - $plugin = $foreignAdapter->getPluginInstance('field', array('field' => $key)); - if($plugin != null) { - $precededBy[] = $plugin->getTaskIdentifierPrefix(); - } else { - // cancel here, as we might reference a model that can't be constructed - // in this case, the field plugin is null - $nullPluginDetected = true; + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + if ($task->name == "ADD_FOREIGNKEY_CONSTRAINT") { + $field = $task->data->get('field'); + $config = $task->data->get('config'); + + $constraintName = "fkey_" . md5("{$this->adapter->model}_{$config['model']}_{$field}_fkey"); + + if (is_array($config['key'])) { + $fkey = implode(',', array_keys($config['key'])); + $references = implode(',', array_values($config['key'])); + } else { + $fkey = $field; + $references = $config['key']; + } + + $db->query( + "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} + ADD CONSTRAINT $constraintName + FOREIGN KEY ($fkey) + REFERENCES {$config['schema']}.{$config['model']} ($references);" + ); + return; } - } - if($nullPluginDetected) { - continue; - } - - - // the foreign table - $plugin = $foreignAdapter->getPluginInstance('table'); - if($plugin != null) { - $precededBy[] = $plugin->getTaskIdentifierPrefix(); - } - - // let the task be preceded by tasks related to the existance the field itself - $plugin = $this->adapter->getPluginInstance('field', array('field' => $field)); - if($plugin != null) { - $precededBy[] = $plugin->getTaskIdentifierPrefix(); - } - - // the current table - $plugin = $this->adapter->getPluginInstance('table'); - if($plugin != null) { - $precededBy[] = $plugin->getTaskIdentifierPrefix(); - } - - // echo("
{$field} preceded by: \n" . print_r($def,true) . "
"); - // echo("
foreign plugin, preceded by: \n" . print_r($definition,true) . "
"); - - //echo("
{$field} preceded by: \n" . print_r($precededBy,true) . "
"); - - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "ADD_FOREIGNKEY_CONSTRAINT", - array( - 'field' => $field, - 'config' => $def, - ), - $precededBy - ); - } - return $tasks; - } - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; - if($task->name == "ADD_FOREIGNKEY_CONSTRAINT") { - /* - "ALTER TABLE $schema.$table - ADD CONSTRAINT ".$table."_".$ref_table."_".$column."_fkey - FOREIGN KEY ($column) - REFERENCES $ref_schema.$ref_table ($ref_column);" - */ - - $field = $task->data->get('field'); - $config = $task->data->get('config'); - - $constraintName = "fkey_" . md5("{$this->adapter->model}_{$config['model']}_{$field}_fkey"); - - if(is_array($config['key'])) { - $fkey = implode(',', array_keys($config['key'])); - $references = implode(',', array_values($config['key'])); - } else { - $fkey = $field; - $references = $config['key']; - } - - $db->query( - "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} - ADD CONSTRAINT {$constraintName} - FOREIGN KEY ({$fkey}) - REFERENCES {$config['schema']}.{$config['model']} ({$references});" - ); - return; - } + // TODO: Remove / modify foreign key + // may be abstracted to two tasks, first: delete/drop, then (re)create + // - // TODO: Remove / modify foreign key - // may be abstracted to two tasks, first: delete/drop, then (re)create - // + // NOTE: this is not valid for MySQL + // see: https://stackoverflow.com/questions/14122031/how-to-remove-constraints-from-my-mysql-table/14122155 + if ($task->name == "REMOVE_FOREIGNKEY_CONSTRAINT") { + $constraintName = $task->data->get('constraint_name'); - // NOTE: this is not valid for MySQL - // see: https://stackoverflow.com/questions/14122031/how-to-remove-constraints-from-my-mysql-table/14122155 - if($task->name == "REMOVE_FOREIGNKEY_CONSTRAINT") { - - $field = $task->data->get('field'); - $config = $task->data->get('config'); - - $constraintName = $task->data->get('constraint_name'); - - $db->query( - "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} - DROP CONSTRAINT {$constraintName};" - ); - return; + $db->query( + "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} + DROP CONSTRAINT $constraintName;" + ); + } } - } - } diff --git a/backend/class/dbdoc/plugin/sql/fulltext.php b/backend/class/dbdoc/plugin/sql/fulltext.php index 6dbe3be..a6a2785 100644 --- a/backend/class/dbdoc/plugin/sql/fulltext.php +++ b/backend/class/dbdoc/plugin/sql/fulltext.php @@ -1,6 +1,11 @@ "); - - return $definition; - } - - /** - * @inheritDoc - */ - public function Compare() : array { - $tasks = array(); - - // return $tasks; - - $definition = $this->getDefinition(); - - // virtual = assume empty structure - $structure = $this->virtual ? array() : $this->getStructure(); - - $valid = array(); - $missing = array(); - $toomuch = array(); - - foreach($structure as $strucName => $struc) { - - // get ordered (?) column_names - $fulltextColumnNames = array_map( - function($spec) { - return $spec['column_name']; - }, $struc - ); - - // reduce to string, if only one element - $fulltextColumnNames = count($fulltextColumnNames) == 1 ? $fulltextColumnNames[0] : $fulltextColumnNames; - - // compare! - if(in_array($fulltextColumnNames, $definition)) { - // constraint exists and is correct - $valid[] = $fulltextColumnNames; - } else { - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "REMOVE_FULLTEXT", array( - 'fulltext_name' => $strucName - )); - } - } - - // determine missing constraints - array_walk($definition, function($d) use ($valid, &$missing) { - foreach($valid as $v) { - // DEBUG - // echo("-- Compare ".var_export($d,true)." ".gettype($d)." <=> ".var_export($v, true)." ".gettype($v)."
".chr(10)); - if(gettype($v) == gettype($d)) { - if($d == $v) { - // DEBUG - // echo("-- => valid/equal, skipping.
"); - return; - } - } else { - // DEBUG - // echo("-- => unequal types, skipping.
"); - continue; +class fulltext extends \codename\architect\dbdoc\plugin\fulltext +{ + use modeladapterGetSqlAdapter; + + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function Compare(): array + { + $tasks = []; + + $definition = $this->getDefinition(); + + // virtual = assume empty structure + $structure = $this->virtual ? [] : $this->getStructure(); + + $valid = []; + $missing = []; + + foreach ($structure as $strucName => $struc) { + // get ordered (?) column_names + $fulltextColumnNames = array_map( + function ($spec) { + return $spec['column_name']; + }, + $struc + ); + + // reduce to string, if only one element + $fulltextColumnNames = count($fulltextColumnNames) == 1 ? $fulltextColumnNames[0] : $fulltextColumnNames; + + // compare! + if (in_array($fulltextColumnNames, $definition)) { + // constraint exists and is correct + $valid[] = $fulltextColumnNames; + } else { + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "REMOVE_FULLTEXT", [ + 'fulltext_name' => $strucName, + ]); + } } - } - - // DEBUG - // echo("-- => invalid/unequal, add to missing.
"); - $missing[] = $d; - }); - - foreach($missing as $def) { - - if(is_array($def)) { - // multi-column constraint - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "ADD_FULLTEXT", array( - 'fulltext_columns' => $def - )); - } else { - // single-column constraint - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "ADD_FULLTEXT", array( - 'fulltext_columns' => $def - )); - } - } - - return $tasks; - } - /** - * @inheritDoc - */ - public function getStructure() { + // determine missing constraints + array_walk($definition, function ($d) use ($valid, &$missing) { + foreach ($valid as $v) { + if (gettype($v) == gettype($d)) { + if ($d == $v) { + return; + } + } + } + + $missing[] = $d; + }); + + foreach ($missing as $def) { + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "ADD_FULLTEXT", [ + 'fulltext_columns' => $def, + ]); + } - $db = $this->getSqlAdapter()->db; + return $tasks; + } - $db->query( - "SELECT DISTINCT tc.table_schema, tc.table_name, s.index_name, tc.constraint_name, s.column_name, s.seq_in_index + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): array + { + $db = $this->getSqlAdapter()->db; + + $db->query( + "SELECT DISTINCT tc.table_schema, tc.table_name, s.index_name, tc.constraint_name, s.column_name, s.seq_in_index FROM information_schema.statistics s LEFT OUTER JOIN information_schema.table_constraints tc ON tc.table_schema = s.table_schema AND tc.table_name = s.table_name AND s.index_name = tc.constraint_name - WHERE 0 = 0 - AND s.index_name NOT IN ('PRIMARY') + WHERE s.index_name NOT IN ('PRIMARY') AND s.table_schema = '{$this->adapter->schema}' AND s.table_name = '{$this->adapter->model}' AND s.index_type = 'FULLTEXT'" - ); - - $allFulltext = $db->getResult(); - - // echo '
';
-    // print_r($allFulltext);
-    // echo '
'; - - $fulltextGroups = []; + ); + + $allFulltext = $db->getResult(); + + $fulltextGroups = []; + + // perform grouping + foreach ($allFulltext as $fulltext) { + if (array_key_exists($fulltext['index_name'], $fulltextGroups)) { + // match to existing group + foreach ($fulltextGroups as $groupName => $group) { + if ($fulltext['index_name'] == $groupName) { + $fulltextGroups[$groupName][] = $fulltext; + break; + } + } + } else { + // create new group + $fulltextGroups[$fulltext['index_name']][] = $fulltext; + } + } - // perform grouping - foreach($allFulltext as $fulltext) { - if(array_key_exists($fulltext['index_name'], $fulltextGroups)) { - // match to existing group - foreach($fulltextGroups as $groupName => $group) { - if($fulltext['index_name'] == $groupName) { - $fulltextGroups[$groupName][] = $fulltext; - break; - } + $sortedfulltextGroups = []; + // sort! + foreach ($fulltextGroups as $groupName => $group) { + usort($group, function ($left, $right) { + return $left['seq_in_index'] > $right['seq_in_index']; + }); + $sortedfulltextGroups[$groupName] = $group; } - } else { - // create new group - $fulltextGroups[$fulltext['index_name']][] = $fulltext; - } - } - $sortedfulltextGroups = []; - // sort! - foreach($fulltextGroups as $groupName => $group) { - usort($group, function($left, $right) { - return $left['seq_in_index'] > $right['seq_in_index']; - }); - $sortedfulltextGroups[$groupName] = $group; + return $sortedfulltextGroups; } - return $sortedfulltextGroups; - } - - - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; - - if($task->name == "ADD_FULLTEXT") { - - $fulltextColumns = $task->data->get('fulltext_columns'); - $columns = is_array($fulltextColumns) ? implode(',', $fulltextColumns) : $fulltextColumns; - $fulltextName = 'fulltext_' . md5($columns); - $db->query( - "CREATE FULLTEXT INDEX {$fulltextName} ON {$this->adapter->schema}.{$this->adapter->model} ({$columns}) COMMENT '' ALGORITHM DEFAULT LOCK DEFAULT;" - ); - } - - if($task->name == "REMOVE_FULLTEXT") { + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + + if ($task->name == "ADD_FULLTEXT") { + $fulltextColumns = $task->data->get('fulltext_columns'); + $columns = is_array($fulltextColumns) ? implode(',', $fulltextColumns) : $fulltextColumns; + $fulltextName = 'fulltext_' . md5($columns); + + $db->query( + "CREATE FULLTEXT INDEX $fulltextName ON {$this->adapter->schema}.{$this->adapter->model} ($columns) COMMENT '' ALGORITHM DEFAULT LOCK DEFAULT;" + ); + } - // simply drop fulltext by fulltext_name - $fulltextName = $task->data->get('fulltext_name'); + if ($task->name == "REMOVE_FULLTEXT") { + // simply drop fulltext by fulltext_name + $fulltextName = $task->data->get('fulltext_name'); - $db->query( - "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} DROP INDEX {$fulltextName};" - ); + $db->query( + "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} DROP INDEX $fulltextName;" + ); + } } - } - } diff --git a/backend/class/dbdoc/plugin/sql/index.php b/backend/class/dbdoc/plugin/sql/index.php index 54469c7..fd365f8 100644 --- a/backend/class/dbdoc/plugin/sql/index.php +++ b/backend/class/dbdoc/plugin/sql/index.php @@ -1,6 +1,11 @@ adapter->getPluginInstance('foreign', array(), true); - $foreignKeys = $foreignPlugin->getDefinition(); - foreach($foreignKeys as $fkey => $fkeyConfig) { - if(is_array($fkeyConfig['key'])) { - // multi-component foreign key - $fkey is NOT a field name, use 'key'-keys - $definition[] = array_keys($fkeyConfig['key']); - } else { - // just use the foreign key definition name (this is the current table's key to be used) - $definition[] = $fkey; - } - } +class index extends \codename\architect\dbdoc\plugin\index +{ + use modeladapterGetSqlAdapter; + + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function Compare(): array + { + $tasks = []; + + $definition = $this->getDefinition(); + + // virtual = assume empty structure + $structure = $this->virtual ? [] : $this->getStructure(); + + $valid = []; + $missing = []; + + // $fieldsOnly = $this->parameter['fields_only'] ?? null; + foreach ($structure as $strucName => $struc) { + // get ordered (?) column_names + $indexColumnNames = array_map( + function ($spec) { + return $spec['column_name']; + }, + $struc + ); + + // reduce to string, if only one element + $indexColumnNames = count($indexColumnNames) == 1 ? $indexColumnNames[0] : $indexColumnNames; + + // compare! + if (in_array($indexColumnNames, $definition)) { + // constraint exists and is correct + $valid[] = $indexColumnNames; + } else { + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "REMOVE_INDEX", [ + 'index_name' => $strucName, + ]); + } + } - // for mysql/sql, merge in unique keys! - if($this->adapter->getDriverCompat() == 'mysql') { - $uniquePlugin = $this->adapter->getPluginInstance('unique', array(), true); - $uniqueKeys = $uniquePlugin->getDefinition(); - foreach($uniqueKeys as $i => $uniqueKey) { - $definition[] = $uniqueKey; - } - } + // determine missing constraints + array_walk($definition, function ($d) use ($valid, &$missing) { + foreach ($valid as $v) { + if (gettype($v) == gettype($d)) { + if ($d == $v) { + return; + } + } + } + + $missing[] = $d; + }); + + foreach ($missing as $def) { + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "ADD_INDEX", [ + 'index_columns' => $def, + ]); + } - // - // make unique! - // otherwise, we may get duplicates - // NOTE: - // this may cause a problem, when creating a foreign key at the same time? - // - $definition = array_values(array_unique($definition, SORT_REGULAR)); - - // print_r($definition); - // echo("
"); - - return $definition; - } - - /** - * @inheritDoc - */ - public function Compare() : array - { - $tasks = array(); - - $definition = $this->getDefinition(); - - // virtual = assume empty structure - $structure = $this->virtual ? array() : $this->getStructure(); - - $valid = array(); - $missing = array(); - $toomuch = array(); - - // $fieldsOnly = $this->parameter['fields_only'] ?? null; - foreach($structure as $strucName => $struc) { - - // get ordered (?) column_names - $indexColumnNames = array_map( - function($spec) { - return $spec['column_name']; - }, $struc - ); - - // if($fieldsOnly && !in_array($indexColumnNames, $fieldsOnly, true)) { - // echo "Skipping ".var_export($indexColumnNames, true)." because not contained in fieldsOnly ".var_export($fieldsOnly, true)."
"; - // continue; - // } - // reduce to string, if only one element - $indexColumnNames = count($indexColumnNames) == 1 ? $indexColumnNames[0] : $indexColumnNames; - - // compare! - if(in_array($indexColumnNames, $definition)) { - // constraint exists and is correct - $valid[] = $indexColumnNames; - } else { - // $toomuch = $constraintColumnNames; - - // echo("
");
-        // print_r([
-        //   $definition,
-        //   $indexColumnNames
-        // ]);
-        // echo("
"); - - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "REMOVE_INDEX", array( - 'index_name' => $strucName - )); - } + return $tasks; } - // - // NOTE: see note above - // echo("
Foreign Definition for model [{$this->adapter->model}]".chr(10));
-    // print_r([
-    //   'definition' => $definition,
-    //   'valid'      => $valid,
-    //   'structure' => $structure
-    // ]);
-    // echo("
"); - - // determine missing constraints - array_walk($definition, function($d) use ($valid, &$missing) { - foreach($valid as $v) { - // DEBUG - // echo("-- Compare ".var_export($d,true)." ".gettype($d)." <=> ".var_export($v, true)." ".gettype($v)."
".chr(10)); - if(gettype($v) == gettype($d)) { - if($d == $v) { - // DEBUG - // echo("-- => valid/equal, skipping.
"); - return; - } - } else { - // DEBUG - // echo("-- => unequal types, skipping.
"); - continue; + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + // "index" specified in model definition + $definition = parent::getDefinition(); + + // + // NOTE: Bad issue on 2019-02-20: + // Index Plugin wants to remove Indexes created + // for Foreign & Unique Keys, as well as Primary Keys + // after the change in structure retrieval (constraint_name is null) + // therefore, we have to check those keys, too. + // + // + // for mysql/sql, merge in foreign keys! + $foreignPlugin = $this->adapter->getPluginInstance('foreign', [], true); + $foreignKeys = $foreignPlugin->getDefinition(); + foreach ($foreignKeys as $fkey => $fkeyConfig) { + if (is_array($fkeyConfig['key'])) { + // multi-component foreign key - $fkey is NOT a field name, use 'key'-keys + $definition[] = array_keys($fkeyConfig['key']); + } else { + // just use the foreign key definition name (this is the current table's key to be used) + $definition[] = $fkey; + } } - } - - // DEBUG - // echo("-- => invalid/unequal, add to missing.
"); - $missing[] = $d; - }); - - foreach($missing as $def) { - - // if($fieldsOnly && !in_array($def, $fieldsOnly, true)) { - // echo "Skipping ".var_export($def, true)." because not contained in fieldsOnly ".var_export($fieldsOnly, true)."
"; - // continue; - // } - - if(is_array($def)) { - // multi-column constraint - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "ADD_INDEX", array( - 'index_columns' => $def - )); - } else { - // single-column constraint - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "ADD_INDEX", array( - 'index_columns' => $def - )); - } - } - - return $tasks; - } - - /** - * @inheritDoc - */ - public function getStructure() - { - /* - we may use some query like this: - // @see: https://stackoverflow.com/questions/5213339/how-to-see-indexes-for-a-database-or-table - - SELECT (DISTINCT?) s.* - FROM INFORMATION_SCHEMA.STATISTICS s - LEFT OUTER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS t - ON t.TABLE_SCHEMA = s.TABLE_SCHEMA - AND t.TABLE_NAME = s.TABLE_NAME - AND s.INDEX_NAME = t.CONSTRAINT_NAME - WHERE 0 = 0 - AND t.CONSTRAINT_NAME IS NULL - AND s.TABLE_SCHEMA = 'YOUR_SCHEMA_SAMPLE'; + // for mysql/sql, merge in unique keys! + if ($this->adapter->getDriverCompat() == 'mysql') { + $uniquePlugin = $this->adapter->getPluginInstance('unique', [], true); + $uniqueKeys = $uniquePlugin->getDefinition(); + foreach ($uniqueKeys as $uniqueKey) { + $definition[] = $uniqueKey; + } + } - *** removal: - DROP INDEX `indexname` ON `tablename` - + // + // make unique! + // otherwise, we may get duplicates + // NOTE: + // this may cause a problem, when creating a foreign key at the same time? + // + return array_values(array_unique($definition, SORT_REGULAR)); + } + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception */ + public function getStructure(): array + { + $db = $this->getSqlAdapter()->db; - $db = $this->getSqlAdapter()->db; - - $db->query( - "SELECT DISTINCT tc.table_schema, tc.table_name, s.index_name, tc.constraint_name, s.column_name, s.seq_in_index + $db->query( + "SELECT DISTINCT tc.table_schema, tc.table_name, s.index_name, tc.constraint_name, s.column_name, s.seq_in_index FROM information_schema.statistics s LEFT OUTER JOIN information_schema.table_constraints tc ON tc.table_schema = s.table_schema AND tc.table_name = s.table_name AND s.index_name = tc.constraint_name - WHERE 0 = 0 - AND s.index_name NOT IN ('PRIMARY') + WHERE s.index_name NOT IN ('PRIMARY') AND s.table_schema = '{$this->adapter->schema}' AND s.table_name = '{$this->adapter->model}' AND s.index_type != 'FULLTEXT'" - ); - - // - // NOTE: we removed the following WHERE-component: - // AND tc.constraint_name IS NULL - // and replaced it with a check for just != PRIMARY - // because we may have constraints attached (foreign keys!) - // So, this plugin now handles ALL indexes, - // - explicit indexes (via "index" key in model config) - // - implicit indexes (unique & foreign keys) - // - - $allIndices = $db->getResult(); - - $indexGroups = []; - - // perform grouping - foreach($allIndices as $index) { - if(array_key_exists($index['index_name'], $indexGroups)) { - // match to existing group - foreach($indexGroups as $groupName => $group) { - if($index['index_name'] == $groupName) { - $indexGroups[$groupName][] = $index; - break; - } + ); + + // + // NOTE: we removed the following WHERE-component: + // AND tc.constraint_name IS NULL + // and replaced it with a check for just != PRIMARY + // because we may have constraints attached (foreign keys!) + // So, this plugin now handles ALL indexes, + // - explicit indexes (via "index" key in model config) + // - implicit indexes (unique & foreign keys) + // + + $allIndices = $db->getResult(); + + $indexGroups = []; + + // perform grouping + foreach ($allIndices as $index) { + if (array_key_exists($index['index_name'], $indexGroups)) { + // match to existing group + foreach ($indexGroups as $groupName => $group) { + if ($index['index_name'] == $groupName) { + $indexGroups[$groupName][] = $index; + break; + } + } + } else { + // create new group + $indexGroups[$index['index_name']][] = $index; + } } - } else { - // create new group - $indexGroups[$index['index_name']][] = $index; - } - } - - $sortedIndexGroups = []; - // sort! - foreach($indexGroups as $groupName => $group) { - usort($group, function($left, $right) { - return $left['seq_in_index'] > $right['seq_in_index']; - }); - $sortedIndexGroups[$groupName] = $group; - } - - return $sortedIndexGroups; - } - - - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; - if($task->name == "ADD_INDEX") { - /* - CREATE INDEX ON (); - */ - - $indexColumns = $task->data->get('index_columns'); - $columns = is_array($indexColumns) ? implode(',', $indexColumns) : $indexColumns; - $indexName = 'index_' . md5($columns); + $sortedIndexGroups = []; + // sort! + foreach ($indexGroups as $groupName => $group) { + usort($group, function ($left, $right) { + return $left['seq_in_index'] > $right['seq_in_index']; + }); + $sortedIndexGroups[$groupName] = $group; + } - $db->query( - "CREATE INDEX {$indexName} ON {$this->adapter->schema}.{$this->adapter->model} ({$columns});" - ); + return $sortedIndexGroups; } - if($task->name == "REMOVE_INDEX") { - // simply drop index by index_name - $indexName = $task->data->get('index_name'); + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + + if ($task->name == "ADD_INDEX") { + $indexColumns = $task->data->get('index_columns'); + $columns = is_array($indexColumns) ? implode(',', $indexColumns) : $indexColumns; + $indexName = 'index_' . md5($columns); + + $db->query( + "CREATE INDEX $indexName ON {$this->adapter->schema}.{$this->adapter->model} ($columns);" + ); + } - $db->query( - "DROP INDEX IF EXISTS {$indexName} ON {$this->adapter->schema}.{$this->adapter->model};" - ); - } - } + if ($task->name == "REMOVE_INDEX") { + // simply drop index by index_name + $indexName = $task->data->get('index_name'); + $db->query( + "DROP INDEX IF EXISTS $indexName ON {$this->adapter->schema}.{$this->adapter->model};" + ); + } + } } diff --git a/backend/class/dbdoc/plugin/sql/initial.php b/backend/class/dbdoc/plugin/sql/initial.php index 9e58c5b..77b5ce3 100644 --- a/backend/class/dbdoc/plugin/sql/initial.php +++ b/backend/class/dbdoc/plugin/sql/initial.php @@ -1,52 +1,53 @@ adapter->getPluginInstance('user'); + if ($plugin != null) { + // add this plugin to the first + $this->adapter->addToQueue($plugin, true); + } - // check for user existance - $plugin = $this->adapter->getPluginInstance('user'); - if($plugin != null) { - // add this plugin to the first - $this->adapter->addToQueue($plugin, true); - } + // we can simply continue constructing our database and tables + // as the user is only relevant for authentication + // constructing schema, table, fields and constraints + // does not depend on it. + $plugin = $this->adapter->getPluginInstance('schema'); + if ($plugin != null) { + // add this plugin to the first + $this->adapter->addToQueue($plugin, true); + } - // we can simply continue constructing our database and tables - // as the user is only relevant for authentication - // constructing schema, table, fields and constraints - // does not depend on it. - $plugin = $this->adapter->getPluginInstance('schema'); - if($plugin != null) { - // add this plugin to the first - $this->adapter->addToQueue($plugin, true); + return []; } - - return array(); - } - - /** - * @inheritDoc - */ - public function getDefinition() - { - } - /** - * @inheritDoc - */ - public function getStructure() - { - } + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + return []; + } + /** + * {@inheritDoc} + */ + public function getStructure(): array + { + return []; + } } diff --git a/backend/class/dbdoc/plugin/sql/mysql/field.php b/backend/class/dbdoc/plugin/sql/mysql/field.php index ffdd290..9c50b94 100644 --- a/backend/class/dbdoc/plugin/sql/mysql/field.php +++ b/backend/class/dbdoc/plugin/sql/mysql/field.php @@ -1,68 +1,54 @@ adapter->config->get('db_data_type>'.$this->parameter['field']) ?? $this->convertModelDataTypeToDbDataType($this->adapter->config->get('datatype>'.$this->parameter['field'])); +class field extends \codename\architect\dbdoc\plugin\sql\field +{ + /** + * array of default datatype (note the difference to the column type!) + * @var array + */ + protected array $defaultsConversionTable = [ + 'bigint' => 'bigint(20)', + 'integer' => 'int(11)', + 'text' => 'text', + 'date' => 'date', + 'datetime' => 'datetime', + ]; + + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + $definition = parent::getDefinition(); + // TODO: check if this is the correct behaviour + // the base class sql\field may already set db_data_type, e.g. if it's a primary key + + // field is a virtual field (collection) + if ($definition['collection']) { + return $definition; + } + + if ($definition['datatype'] == 'virtual') { + return $definition; + } + + if (!is_array($definition['field'])) { + $definition['options'] = array_replace($definition['options'], $this->convertFieldConfigurationToDbColumnType($definition)); + } + return $definition; } - if(!isset($definition['db_column_type'])) { - $definition['db_column_type'] = $this->adapter->config->get('db_column_type>'.$this->parameter['field']) ?? $this->convertDbDataTypeToDbColumnTypeDefault($definition['db_data_type']); - }*/ - - // $definition['options']['db_column_type'] = $this->convertFieldConfigurationToDbColumnType($definition); - if(!is_array($definition['field'])) { - $definition['options'] = array_replace($definition['options'], $this->convertFieldConfigurationToDbColumnType($definition)); + /** + * {@inheritDoc} + */ + public function getDbDataTypeDefaultsTable(): array + { + return $this->defaultsConversionTable; } - return $definition; - } - - /** - * array of default datatypes (note the difference to the column type!) - * @var [type] - */ - protected $defaultsConversionTable = array( - 'bigint' => 'bigint(20)', - 'integer' => 'int(11)', - 'text' => 'text', - 'date' => 'date', - 'datetime' => 'datetime' - ); - - /** - * @inheritDoc - */ - public function getDbDataTypeDefaultsTable(): array { - return $this->defaultsConversionTable; - } - - - - - } diff --git a/backend/class/dbdoc/plugin/sql/mysql/foreign.php b/backend/class/dbdoc/plugin/sql/mysql/foreign.php index 465fe9b..21af9ef 100644 --- a/backend/class/dbdoc/plugin/sql/mysql/foreign.php +++ b/backend/class/dbdoc/plugin/sql/mysql/foreign.php @@ -1,42 +1,40 @@ getSqlAdapter()->db; - - // NOTE: Special implementation for MySQL - // see: https://stackoverflow.com/questions/14122031/how-to-remove-constraints-from-my-mysql-table/14122155 - if($task->name == "REMOVE_FOREIGNKEY_CONSTRAINT") { - - $field = $task->data->get('field'); - $config = $task->data->get('config'); +class foreign extends \codename\architect\dbdoc\plugin\sql\foreign +{ + /** + * {@inheritDoc} + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; - $constraintName = $task->data->get('constraint_name'); + // NOTE: Special implementation for MySQL + // see: https://stackoverflow.com/questions/14122031/how-to-remove-constraints-from-my-mysql-table/14122155 + if ($task->name == "REMOVE_FOREIGNKEY_CONSTRAINT") { + $constraintName = $task->data->get('constraint_name'); - // drop the foreign key constraint itself - $db->query( - "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} - DROP FOREIGN KEY {$constraintName};" - ); + // drop the foreign key constraint itself + $db->query( + "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} + DROP FOREIGN KEY $constraintName;" + ); - // drop the associated index - $db->query( - "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} - DROP INDEX IF EXISTS {$constraintName};" - ); - return; + // drop the associated index + $db->query( + "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} + DROP INDEX IF EXISTS $constraintName;" + ); + return; + } + parent::runTask($task); } - parent::runTask($task); - } } diff --git a/backend/class/dbdoc/plugin/sql/mysql/permissions.php b/backend/class/dbdoc/plugin/sql/mysql/permissions.php index 4913d97..6dc7183 100644 --- a/backend/class/dbdoc/plugin/sql/mysql/permissions.php +++ b/backend/class/dbdoc/plugin/sql/mysql/permissions.php @@ -1,138 +1,148 @@ getDefinition(); + $structure = $this->virtual ? [] : $this->getStructure(); + + $missing = []; + + foreach ($definition['permissions'] as $permission) { + if (!self::in_arrayi($permission, $structure)) { + $missing[] = $permission; + } + } - /** - * @inheritDoc - */ - public function getStructure() - { - $definition = $this->getDefinition(); - $db = $this->getSqlAdapter()->db; + if (count($missing) > 0) { + $userPlugin = $this->adapter->getPluginInstance('user'); + $tablePlugin = $this->adapter->getPluginInstance('table'); - $permissions = array(); + $precededBy = [ + $userPlugin->getTaskIdentifierPrefix(), // execute user-related plugins first + $tablePlugin->getTaskIdentifierPrefix(), // also table-related ones + ]; - $db->query( - "SELECT table_priv + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "GRANT_PERMISSIONS", [ + 'permissions' => $missing, + ], $precededBy); + } + + return $tasks; + } + + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + // needed DML grants + return [ + 'user' => $this->adapter->getPluginInstance('user')->getDefinition()['user'], + 'permissions' => [ + 'select', + 'insert', + 'update', + 'delete', // TODO: we should NOT include this - instead, mark rows as is_deleted = TRUE + ], + ]; + } + + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): array + { + $definition = $this->getDefinition(); + $db = $this->getSqlAdapter()->db; + + $permissions = []; + + $db->query( + "SELECT table_priv FROM mysql.tables_priv WHERE host = '%' AND user = '{$definition['user']}' AND db = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}';" - ); + ); - $permissionsResult = $db->getResult(); + $permissionsResult = $db->getResult(); - if(count($permissionsResult) === 1) { - // yiss, we have it! - // Format: Select,Update,... - $permissions = explode(',', $permissionsResult[0]['table_priv']); - } + if (count($permissionsResult) === 1) { + // yiss, we have it! + // Format: Select,Update,... + $permissions = explode(',', $permissionsResult[0]['table_priv']); + } - return $permissions; - } - - /** - * @inheritDoc - */ - public function getDefinition() - { - // needed DML grants - return array( - 'user' => $this->adapter->getPluginInstance('user')->getDefinition()['user'], - 'permissions' => array( - 'select', - 'insert', - 'update', - 'delete' // TODO: we should NOT include this - instead, mark rows as is_deleted = TRUE - ) - ); - } - - /** - * @inheritDoc - */ - public function Compare() : array - { - $tasks = array(); - - $definition = $this->getDefinition(); - $structure = $this->virtual ? array() : $this->getStructure(); - - $missing = array(); - - foreach($definition['permissions'] as $permission) { - if(!self::in_arrayi($permission, $structure)) { - $missing[] = $permission; - } + return $permissions; } - if(count($missing) > 0) { - - $userPlugin = $this->adapter->getPluginInstance('user'); - $tablePlugin = $this->adapter->getPluginInstance('table'); - - $precededBy = [ - $userPlugin->getTaskIdentifierPrefix(), // execute user-related plugins first - $tablePlugin->getTaskIdentifierPrefix() // also table-related ones - ]; - - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "GRANT_PERMISSIONS", array( - 'permissions' => $missing - ), $precededBy); + /** + * [in_arrayi description] + * @param [type] $needle [description] + * @param [type] $haystack [description] + * @return bool [description] + */ + protected static function in_arrayi($needle, $haystack): bool + { + return in_array(strtolower($needle), array_map('strtolower', $haystack)); } - return $tasks; - } - - /** - * [in_arrayi description] - * @param [type] $needle [description] - * @param [type] $haystack [description] - * @return bool [description] - */ - protected static function in_arrayi($needle, $haystack) : bool { - return in_array(strtolower($needle), array_map('strtolower', $haystack)); - } - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; - $definition = $this->getDefinition(); - - if($task->name == 'GRANT_PERMISSIONS') { - - if($task->data->get('permissions') == null) { - throw new exception("EXCEPTION_ARCHITECT_DBDOC_PLUGIN_SQL_MYSQL_USER_PERMISSIONS_INVALID", exception::$ERRORLEVEL_FATAL, $task->data->get()); - } - - foreach($task->data->get('permissions') as $permission) { - if(!in_array($permission, $this->getDefinition()['permissions'])) { - throw new exception("EXCEPTION_ARCHITECT_DBDOC_PLUGIN_SQL_MYSQL_USER_PERMISSIONS_SECURITY_ISSUE", exception::$ERRORLEVEL_FATAL, $task->data->get()); - } - } - - $permissions = implode(',', $task->data->get('permissions')); - - $db->query( - "GRANT {$permissions} + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + $definition = $this->getDefinition(); + + if ($task->name == 'GRANT_PERMISSIONS') { + if ($task->data->get('permissions') == null) { + throw new exception("EXCEPTION_ARCHITECT_DBDOC_PLUGIN_SQL_MYSQL_USER_PERMISSIONS_INVALID", exception::$ERRORLEVEL_FATAL, $task->data->get()); + } + + foreach ($task->data->get('permissions') as $permission) { + if (!in_array($permission, $this->getDefinition()['permissions'])) { + throw new exception("EXCEPTION_ARCHITECT_DBDOC_PLUGIN_SQL_MYSQL_USER_PERMISSIONS_SECURITY_ISSUE", exception::$ERRORLEVEL_FATAL, $task->data->get()); + } + } + + $permissions = implode(',', $task->data->get('permissions')); + + $db->query( + "GRANT $permissions ON {$this->adapter->schema}.{$this->adapter->model} TO '{$definition['user']}'@'%';" - ); - + ); + } } - } - - } diff --git a/backend/class/dbdoc/plugin/sql/mysql/primary.php b/backend/class/dbdoc/plugin/sql/mysql/primary.php index acd5676..ab25843 100644 --- a/backend/class/dbdoc/plugin/sql/mysql/primary.php +++ b/backend/class/dbdoc/plugin/sql/mysql/primary.php @@ -1,62 +1,61 @@ adapter->config->get('options>'.$definition['field']) ?? []; - $definition['options']['db_data_type'] = $definition['options']['db_data_type'] ?? [ self::DB_DEFAULT_DATA_TYPE ]; // NOTE: this has to be an array - $definition['options']['db_column_type'] = $definition['options']['db_column_type'] ?? [ self::DB_DEFAULT_COLUMN_TYPE ]; // NOTE: this has to be an array - return $definition; - } - - /** - * @inheritDoc - */ - protected function checkPrimaryKeyAttributes(array $definition, array $structure) : array - { - $tasks = array(); - - if($structure['data_type'] != self::DB_DEFAULT_DATA_TYPE) { - // suggest column data_type modification - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "MODIFY_COLUMN_DATATYPE", array( - 'field' => $structure['column_name'], - 'db_data_type' => self::DB_DEFAULT_DATA_TYPE - )); - } else { - if($structure['column_type'] != self::DB_DEFAULT_COLUMN_TYPE) { - // suggest column column_type modification - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "MODIFY_COLUMN_COLUMNTYPE", array( - 'field' => $structure['column_name'], - 'db_data_type' => self::DB_DEFAULT_DATA_TYPE, - 'db_column_type' => self::DB_DEFAULT_COLUMN_TYPE - )); - } +class primary extends \codename\architect\dbdoc\plugin\sql\primary +{ + /** + * default column data type for primary keys on mysql + * @var string + */ + public const DB_DEFAULT_DATA_TYPE = 'bigint'; + + /** + * default column type for primary keys on mysql + * @var string + */ + public const DB_DEFAULT_COLUMN_TYPE = 'bigint(20)'; + + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + $definition = parent::getDefinition(); + $definition['options'] = $this->adapter->config->get('options>' . $definition['field']) ?? []; + $definition['options']['db_data_type'] = $definition['options']['db_data_type'] ?? [self::DB_DEFAULT_DATA_TYPE]; // NOTE: this has to be an array + $definition['options']['db_column_type'] = $definition['options']['db_column_type'] ?? [self::DB_DEFAULT_COLUMN_TYPE]; // NOTE: this has to be an array + return $definition; } - return $tasks; - } - + /** + * {@inheritDoc} + */ + protected function checkPrimaryKeyAttributes(array $definition, array $structure): array + { + $tasks = []; + + if ($structure['data_type'] != self::DB_DEFAULT_DATA_TYPE) { + // suggest column data_type modification + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "MODIFY_COLUMN_DATATYPE", [ + 'field' => $structure['column_name'], + 'db_data_type' => self::DB_DEFAULT_DATA_TYPE, + ]); + } elseif ($structure['column_type'] != self::DB_DEFAULT_COLUMN_TYPE) { + // suggest column column_type modification + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "MODIFY_COLUMN_COLUMNTYPE", [ + 'field' => $structure['column_name'], + 'db_data_type' => self::DB_DEFAULT_DATA_TYPE, + 'db_column_type' => self::DB_DEFAULT_COLUMN_TYPE, + ]); + } + + return $tasks; + } } diff --git a/backend/class/dbdoc/plugin/sql/mysql/table.php b/backend/class/dbdoc/plugin/sql/mysql/table.php index b1fdad7..a6c256a 100644 --- a/backend/class/dbdoc/plugin/sql/mysql/table.php +++ b/backend/class/dbdoc/plugin/sql/mysql/table.php @@ -1,54 +1,56 @@ getSqlAdapter()->db; - if($task->name == 'CREATE_TABLE') { - - // get pkey creation info - $pkeyPlugin = $this->adapter->getPluginInstance('primary'); - $field = $pkeyPlugin->getDefinition(); - - // $fieldPlugin = $this->adapter->getPluginInstance('field', array('field' => $primarykey)); - // $field = $fieldPlugin->getDefinition(); - - $attributes = array(); - - if($field['notnull']) { - $attributes[] = "NOT NULL"; - } - - if($field['auto_increment']) { - $attributes[] = "AUTO_INCREMENT"; - } - - $add = implode(' ', $attributes); - - // for mysql, we have to create the table with at least ONE COLUMN - $db->query( - "CREATE TABLE {$this->adapter->schema}.{$this->adapter->model} ( - {$field['field']} {$field['options']['db_column_type'][0]} {$add}, +class table extends plugin\sql\table +{ + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + if ($task->name == 'CREATE_TABLE') { + // get pkey creation info + $pkeyPlugin = $this->adapter->getPluginInstance('primary'); + $field = $pkeyPlugin->getDefinition(); + + $attributes = []; + + if ($field['notnull']) { + $attributes[] = "NOT NULL"; + } + + if ($field['auto_increment']) { + $attributes[] = "AUTO_INCREMENT"; + } + + $add = implode(' ', $attributes); + + // for mysql, we have to create the table with at least ONE COLUMN + $db->query( + "CREATE TABLE {$this->adapter->schema}.{$this->adapter->model} ( + {$field['field']} {$field['options']['db_column_type'][0]} $add, PRIMARY KEY({$field['field']}) ) ENGINE=InnoDB CHARACTER SET=utf8 COLLATE utf8_general_ci;" - ); - - } - if($task->name == 'DELETE_COLUMN') { - $db->query( - "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} DROP COLUMN IF EXISTS {$task->data->get('field')};" - ); + ); + } + if ($task->name == 'DELETE_COLUMN') { + $db->query( + "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} DROP COLUMN IF EXISTS {$task->data->get('field')};" + ); + } } - } } diff --git a/backend/class/dbdoc/plugin/sql/mysql/user.php b/backend/class/dbdoc/plugin/sql/mysql/user.php index 9d00bd5..511b913 100644 --- a/backend/class/dbdoc/plugin/sql/mysql/user.php +++ b/backend/class/dbdoc/plugin/sql/mysql/user.php @@ -1,155 +1,146 @@ getDefinition(); - $db = $this->getSqlAdapter()->db; - - $permissions = array(); - - // query user dataset - $db->query( - "SELECT exists( +class user extends \codename\architect\dbdoc\plugin\sql\user +{ + use modeladapterGetSqlAdapter; + + protected const NEEDED_DML_GRANTS = [ + 'select', + 'insert', + 'update', + 'delete', // TODO: we should NOT include this - instead, mark rows as is_deleted = TRUE + ]; + + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function Compare(): array + { + $tasks = []; + $structure = $this->getStructure(); + + if (!$structure['user_exists']) { + // create user + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_USER"); + } elseif (!$structure['password_correct']) { + // change password + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CHANGE_PASSWORD"); + } + + // run permissions plugin. + // virtual, if structure/user does not exist (yet) + $plugin = $this->adapter->getPluginInstance('permissions', [], !$structure['user_exists']); + if ($plugin != null) { + // add this plugin to the first + $this->adapter->addToQueue($plugin, true); + } + + return $tasks; + } + + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): array + { + $definition = $this->getDefinition(); + $db = $this->getSqlAdapter()->db; + + // query user dataset + $db->query( + "SELECT exists( SELECT 1 FROM mysql.user WHERE host = '%' AND user = '{$definition['user']}' ) as result;" - ); - $exists = $db->getResult()[0]['result']; + ); + $exists = $db->getResult()[0]['result']; - if($exists) { - // check password, indirectly - $db->query( - "SELECT exists( + if ($exists) { + // check password, indirectly + $db->query( + "SELECT exists( SELECT 1 FROM mysql.user WHERE host = '%' AND user = '{$definition['user']}' AND password = PASSWORD('{$definition['pass']}') ) as result;" - ); - $passwordCorrect = $db->getResult()[0]['result']; - - /* - $db->query( - "SELECT table_priv - FROM mysql.tables_priv - WHERE host = '%' - AND user = '{$definition['user']}' - AND db = '{$this->adapter->schema}' - AND table_name = '{$this->adapter->model}';" - ); - - $permissionsResult = $db->getResult(); - - if(count($permissionsResult) === 1) { - // yiss, we have it! - // Format: Select,Update,... - $permissions = explode(',', $permissionsResult[0]['table_priv']); - } - */ - } else { - $passwordCorrect = null; - } - - return array( - 'user_exists' => $exists, - 'password_correct' => $passwordCorrect, - //'permissions' => $permissions - ); - } - - /** - * @inheritDoc - */ - public function Compare() : array - { - $tasks = array(); - - $definition = $this->getDefinition(); - $structure = $this->getStructure(); - - if(!$structure['user_exists']) { - // create user - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_USER", array()); - - } else if(!$structure['password_correct']) { - // change password - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CHANGE_PASSWORD", array()); - } + ); + $passwordCorrect = $db->getResult()[0]['result']; + } else { + $passwordCorrect = null; + } - // run permissions plugin. - // virtual, if structure/user does not exist (yet) - $plugin = $this->adapter->getPluginInstance('permissions', array(), !$structure['user_exists']); - if($plugin != null) { - // add this plugin to the first - $this->adapter->addToQueue($plugin, true); + return [ + 'user_exists' => $exists, + 'password_correct' => $passwordCorrect, + ]; } - return $tasks; - } - - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; - $definition = $this->getDefinition(); - - if($task->name == 'CREATE_USER') { - $db->query( - "CREATE USER '{$definition['user']}'@'%' IDENTIFIED BY '{$definition['pass']}';" - ); - } + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + $definition = $this->getDefinition(); + + if ($task->name == 'CREATE_USER') { + $db->query( + "CREATE USER '{$definition['user']}'@'%' IDENTIFIED BY '{$definition['pass']}';" + ); + } - if($task->name == 'CHANGE_PASSWORD') { - $db->query( - "UPDATE mysql.user + if ($task->name == 'CHANGE_PASSWORD') { + $db->query( + "UPDATE mysql.user SET password = PASSWORD({$definition['pass']}) WHERE host = '%' AND user = '{$definition['user']}' ) as result;" - ); - } - - if($task->name == 'GRANT_PERMISSIONS') { + ); + } - if($task->data->get('permissions') == null) { - throw new exception("EXCEPTION_ARCHITECT_DBDOC_PLUGIN_SQL_MYSQL_USER_PERMISSIONS_INVALID", exception::$ERRORLEVEL_FATAL, $task->data->get()); - } + if ($task->name == 'GRANT_PERMISSIONS') { + if ($task->data->get('permissions') == null) { + throw new exception("EXCEPTION_ARCHITECT_DBDOC_PLUGIN_SQL_MYSQL_USER_PERMISSIONS_INVALID", exception::$ERRORLEVEL_FATAL, $task->data->get()); + } - foreach($task->data->get('permissions') as $permission) { - if(!in_array($permission, self::NEEDED_DML_GRANTS)) { - throw new exception("EXCEPTION_ARCHITECT_DBDOC_PLUGIN_SQL_MYSQL_USER_PERMISSIONS_SECURITY_ISSUE", exception::$ERRORLEVEL_FATAL, $task->data->get()); - } - } + foreach ($task->data->get('permissions') as $permission) { + if (!in_array($permission, self::NEEDED_DML_GRANTS)) { + throw new exception("EXCEPTION_ARCHITECT_DBDOC_PLUGIN_SQL_MYSQL_USER_PERMISSIONS_SECURITY_ISSUE", exception::$ERRORLEVEL_FATAL, $task->data->get()); + } + } - $permissions = implode(',', $task->data->get('permissions')); + $permissions = implode(',', $task->data->get('permissions')); - $db->query( - "GRANT {$permissions} + $db->query( + "GRANT $permissions ON {$this->adapter->schema}.{$this->adapter->model} TO '{$definition['user']}'@'%';" - ); - + ); + } } - } - - } diff --git a/backend/class/dbdoc/plugin/sql/permissions.php b/backend/class/dbdoc/plugin/sql/permissions.php index cdb394c..6f79e22 100644 --- a/backend/class/dbdoc/plugin/sql/permissions.php +++ b/backend/class/dbdoc/plugin/sql/permissions.php @@ -1,12 +1,14 @@ $field, - 'auto_increment' => true, - 'notnull' => true, - 'primary' => true, - 'datatype' => $this->adapter->config->get('datatype>' . $field), - // 'db_column_type' => $this->adapter->config->get('datatype_override>' . $field) - ); - } + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws catchableException + * @throws exception + */ + public function Compare(): array + { + $tasks = []; + $definition = $this->getDefinition(); - /** - * @inheritDoc - */ - public function getStructure() - { - // get some column specifications - $db = $this->getSqlAdapter()->db; - $db->query( - "SELECT column_name, column_type, data_type - FROM information_schema.columns - WHERE table_schema = '{$this->adapter->schema}' - AND table_name = '{$this->adapter->model}' - AND column_key = 'PRI';" - ); + // virtual = assume empty structure + $structure = $this->virtual ? null : $this->getStructure(); - $res = $db->getResult(); - if(count($res) === 1) { - return $res[0]; + if ($structure == null) { + // in mysql, we prefer to create the table with the primary key, in the first place. + if (!$this->virtual) { + // set task for PKEY creation + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_PRIMARYKEY", [ + 'field' => $definition, + ]); + } + } elseif ($definition['field'] == $structure['column_name']) { + // we got the right column, compare properties + $this->checkPrimaryKeyAttributes($definition, $structure); + } else { + // primary key set on wrong column/field ! + // task? info? error? modify? + $tasks[] = $this->createTask(task::TASK_TYPE_ERROR, "PRIMARYKEY_WRONG_COLUMN", [ + 'field' => $definition['field'], + 'column' => $structure['column_name'], + ]); + } + return $tasks; } - return null; - } - - /** - * @inheritDoc - */ - public function Compare() : array - { - $tasks = array(); - $definition = $this->getDefinition(); - - // virtual = assume empty structure - $structure = $this->virtual ? null : $this->getStructure(); - - if($structure == null) { - // in mysql, we prefer to create the table with the primary key, in the first place. - if(!$this->virtual) { - // set task for PKEY creation - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_PRIMARYKEY", array( - 'field' => $definition - )); - } - - } else { - - // we just got the primary key of the table. - // check for column equality - if($definition['field'] == $structure['column_name']) { - - // we got the right column, compare properties - $this->checkPrimaryKeyAttributes($definition, $structure); + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + $primarykey = parent::getDefinition(); + $field = $primarykey; + return [ + 'field' => $field, + 'auto_increment' => true, + 'notnull' => true, + 'primary' => true, + 'datatype' => $this->adapter->config->get('datatype>' . $field), + ]; + } - } else { - // primary key set on wrong column/field ! - // task? info? error? modify? - $tasks[] = $this->createTask(task::TASK_TYPE_ERROR, "PRIMARYKEY_WRONG_COLUMN", array( - 'field' => $definition['field'], - 'column' => $structure['column_name'] - )); + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): array + { + // get some column specifications + $db = $this->getSqlAdapter()->db; + $db->query( + "SELECT column_name, column_type, data_type + FROM information_schema.columns + WHERE table_schema = '{$this->adapter->schema}' + AND table_name = '{$this->adapter->model}' + AND column_key = 'PRI';" + ); - } + $res = $db->getResult(); + if (count($res) === 1) { + return $res[0]; + } + return []; } - return $tasks; - } - - /** - * this function checks a given structure information - * for correctness and returns an array of tasks needed for completion - * @param [type] $definition [description] - * @param [type] $structure [description] - * @return task[] [description] - */ - protected abstract function checkPrimaryKeyAttributes(array $definition, array $structure) : array; + /** + * this function checks a given structure information + * for correctness and returns an array of tasks needed for completion + * @param array $definition + * @param array $structure + * @return array [description] + */ + abstract protected function checkPrimaryKeyAttributes(array $definition, array $structure): array; } diff --git a/backend/class/dbdoc/plugin/sql/schema.php b/backend/class/dbdoc/plugin/sql/schema.php index cd2c353..0b6ac84 100644 --- a/backend/class/dbdoc/plugin/sql/schema.php +++ b/backend/class/dbdoc/plugin/sql/schema.php @@ -1,68 +1,81 @@ getSqlAdapter()->db; - $db->query( - "SELECT exists(select 1 FROM information_schema.schemata WHERE schema_name = '{$this->adapter->schema}') as result;" - ); - return $db->getResult()[0]['result']; - } + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function Compare(): array + { + $tasks = []; + $definition = $this->getDefinition(); - /** - * @inheritDoc - */ - public function Compare() : array - { - $tasks = array(); - $definition = $this->getDefinition(); + // virtual = assume empty structure + $structure = $this->virtual ? false : $this->getStructure(); - // virtual = assume empty structure - $structure = $this->virtual ? false : $this->getStructure(); + if (!$structure) { + // schema/database does not exist + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_SCHEMA", [ + 'schema' => $definition, + ]); + } - if(!$structure) { - // schema/database does not exist - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_SCHEMA", array( - 'schema' => $definition - )); - } + // schema/database exists + // start subroutine plugins + $plugin = $this->adapter->getPluginInstance('table', [], !$structure); + if ($plugin != null) { + // add this plugin to the first + $this->adapter->addToQueue($plugin, true); + } - // schema/database exists - // start subroutine plugins - $plugin = $this->adapter->getPluginInstance('table', array(), !$structure); - if($plugin != null) { - // add this plugin to the first - $this->adapter->addToQueue($plugin, true); + return $tasks; } - return $tasks; - } - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; - - if($task->name == 'CREATE_SCHEMA') { - // CREATE SCHEMA - $db->query("CREATE SCHEMA `{$this->adapter->schema}`;"); + /** + * {@inheritDoc} + * @return mixed + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): mixed + { + $db = $this->getSqlAdapter()->db; + $db->query( + "SELECT exists(select 1 FROM information_schema.schemata WHERE schema_name = '{$this->adapter->schema}') as result;" + ); + return $db->getResult()[0]['result']; } - } + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + if ($task->name == 'CREATE_SCHEMA') { + // CREATE SCHEMA + $db->query("CREATE SCHEMA `{$this->adapter->schema}`;"); + } + } } diff --git a/backend/class/dbdoc/plugin/sql/sqlite/field.php b/backend/class/dbdoc/plugin/sql/sqlite/field.php index 1cee900..a128bce 100644 --- a/backend/class/dbdoc/plugin/sql/sqlite/field.php +++ b/backend/class/dbdoc/plugin/sql/sqlite/field.php @@ -1,247 +1,243 @@ convertModelDataTypeToDbDataType($definition['datatype']); - - - // Override regular SQL-style to match SQLite's requirements - // at least for datetime-fields with a default value - if($definition['datatype'] == 'text_timestamp' && (($definition['default'] ?? null) == 'current_timestamp()')) { - $definition['default'] = 'CURRENT_TIMESTAMP'; - } - - return $definition; - } +class field extends \codename\architect\dbdoc\plugin\sql\field implements partialStatementInterface +{ + /** + * array of default datatype (note the difference to the column type!) + * @var array + */ + protected array $defaultsConversionTable = [ + 'bigint' => 'INTEGER', + 'integer' => 'INTEGER', + 'text' => 'TEXT', + 'date' => 'TEXT', + 'datetime' => 'TIMESTAMP', + ]; + /** + * basic conversion table between sql defaults and core framework + * @var string[] + */ + protected $conversionTable = [ + 'text' => ['text', 'mediumtext', 'longtext'], + 'text_timestamp' => ['datetime'], + 'text_date' => ['date'], + 'number' => ['numeric', 'decimal'], + 'number_natural' => ['INTEGER', 'int', 'bigint'], + 'boolean' => ['boolean'], + 'structure' => ['text', 'mediumtext', 'longtext'], + 'mixed' => ['text'], + 'virtual' => [null], + ]; + + /** + * {@inheritDoc} + * @return mixed + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): mixed + { + // get some column specifications + $db = $this->getSqlAdapter()->db; - - /** - * @inheritDoc - */ - public function getStructure() - { - // get some column specifications - $db = $this->getSqlAdapter()->db; - - $db->query( - "SELECT * + $db->query( + "SELECT * FROM pragma_table_info('{$this->adapter->schema}.{$this->adapter->model}') WHERE name = '{$this->parameter['field']}' ;" - ); - - $res = $db->getResult(); - - if(count($res) === 1) { - // Change sqlite pragma's resultset - // and map type to column_type (which is handled in generic field plugin) - $r = $res[0]; - $r['column_type'] = $r['type']; - $r['column_default'] = $r['dflt_value']; - $r['is_nullable'] = $r['notnull'] != 1; // emulate 'is_nullable' key - return $r; - } - return null; - } - - /** - * array of default datatypes (note the difference to the column type!) - * @var [type] - */ - protected $defaultsConversionTable = array( - 'bigint' => 'INTEGER', - 'integer' => 'INTEGER', - 'text' => 'TEXT', - 'date' => 'TEXT', - 'datetime' => 'TIMESTAMP' - ); - - protected $conversionTable = array( - 'text' => [ 'text', 'mediumtext', 'longtext' ], - 'text_timestamp' => [ 'datetime' ], - 'text_date' => [ 'date' ], - 'number' => [ 'numeric', 'decimal' ], // was integer - 'number_natural' => [ 'INTEGER', 'int', 'bigint' ], - 'boolean' => [ 'boolean' ], - 'structure' => [ 'text', 'mediumtext', 'longtext' ], - 'mixed' => [ 'text' ], - 'virtual' => [ null ] - // 'collection' - ); - - /** - * @inheritDoc - */ - public function getDbDataTypeDefaultsTable(): array { - return $this->defaultsConversionTable; - } - - /** - * [getPartialStatement description] - * @return [type] [description] - */ - public function getPartialStatement() { - $definition = $this->getDefinition(); - - if($definition['collection']) { - return null; - } - - // cancel, if field is a virtual field - if($definition['datatype'] == 'virtual') { - return null; - } + ); - $attributes = array(); + $res = $db->getResult(); - if($definition['primary']) { - // support for single-column PKEYs - $attributes[] = 'PRIMARY KEY'; + if (count($res) === 1) { + // Change sqlite pragma result + // and map type to column_type (which is handled in generic field plugin) + $r = $res[0]; + $r['column_type'] = $r['type']; + $r['column_default'] = $r['dflt_value']; + $r['is_nullable'] = $r['notnull'] != 1; // emulate 'is_nullable' key + return $r; + } + return null; } - if($definition['auto_increment'] ?? false) { - // support for single-column PKEYs - $attributes[] = 'AUTOINCREMENT'; + /** + * {@inheritDoc} + */ + public function getDbDataTypeDefaultsTable(): array + { + return $this->defaultsConversionTable; } - if($definition['notnull'] ?? false) { - $attributes[] = "NOT NULL"; - } + /** + * [getPartialStatement description] + * @return string|null [type] [description] + * @throws ReflectionException + * @throws catchableException + * @throws exception + */ + public function getPartialStatement(): ?string + { + $definition = $this->getDefinition(); + + if ($definition['collection']) { + return null; + } - if(isset($definition['default'])) { - // - // Special case: field is timestamp && default is CURRENT_TIMESTAMP - // - if($definition['datatype'] === 'text_timestamp' && $definition['default'] == 'CURRENT_TIMESTAMP') { - $attributes[] = 'DEFAULT CURRENT_TIMESTAMP'; // '(DATETIME(\'now\'))'; // WORKAROUND! // $definition['default']; - } else { - if(is_bool($definition['default'])) { - $attributes[] = "DEFAULT ".(int)$definition['default']; - } else { - $attributes[] = "DEFAULT ".json_encode($definition['default']); + // cancel, if field is a virtual field + if ($definition['datatype'] == 'virtual') { + return null; } - } - } - // TODO: add unique - // TODO: add index + $attributes = []; + + if ($definition['primary']) { + // support for single-column PKEYs + $attributes[] = 'PRIMARY KEY'; + } - $add = implode(' ', $attributes); + if ($definition['auto_increment'] ?? false) { + // support for single-column PKEYs + $attributes[] = 'AUTOINCREMENT'; + } - // fallback from specific column types to a more generous type - $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0]; + if ($definition['notnull'] ?? false) { + $attributes[] = "NOT NULL"; + } - return "{$definition['field']} {$columnType} {$add}"; - } + if (isset($definition['default'])) { + // + // Special case: field is timestamp && default is CURRENT_TIMESTAMP + // + if ($definition['datatype'] === 'text_timestamp' && $definition['default'] == 'CURRENT_TIMESTAMP') { + $attributes[] = 'DEFAULT CURRENT_TIMESTAMP'; + } elseif (is_bool($definition['default'])) { + $attributes[] = "DEFAULT " . (int)$definition['default']; + } else { + $attributes[] = "DEFAULT " . json_encode($definition['default']); + } + } - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; + // TODO: add unique + // TODO: add index - $definition = $this->getDefinition(); + $add = implode(' ', $attributes); - if($task->name == "CREATE_COLUMN") { + // fallback from specific column types to a more generous type + $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0]; - $attributes = array(); + return "{$definition['field']} $columnType $add"; + } - if($definition['notnull'] && isset($definition['default'])) { - $attributes[] = "NOT NULL"; - } + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + $definition = parent::getDefinition(); - if(isset($definition['default'])) { - // - // Special case: field is timestamp && default is CURRENT_TIMESTAMP - // - if($definition['datatype'] === 'text_timestamp' && $definition['default'] == 'CURRENT_TIMESTAMP') { - $attributes[] = 'DEFAULT CURRENT_TIMESTAMP'; // '(DATETIME(\'now\'))'; // WORKAROUND! // $definition['default']; - } else { - $attributes[] = "DEFAULT ".json_encode($definition['default']); + // field is a virtual field (collection) + if ($definition['collection']) { + return $definition; } - } - - /* - // not allowed on normal fields? some requirements have to be met? - if($definition['auto_increment']) { - $attributes[] = "AUTO_INCREMENT"; - }*/ - // TODO: add unique - // TODO: add index + // cancel, if field is a virtual field + if ($definition['datatype'] == 'virtual') { + return $definition; + } - $add = implode(' ', $attributes); + // required fields for SQL database adapters: + // $definition['options']['db_data_type'] = $definition['options']['db_data_type'] ?? null; + // $definition['options']['db_column_type'] = $definition['options']['db_column_type'] ?? null; + // TODO + $definition['options']['db_data_type'] = null; + $definition['options']['db_column_type'] = $this->convertModelDataTypeToDbDataType($definition['datatype']); - // fallback from specific column types to a more generous type - $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0]; - $db->query( - "ALTER TABLE '{$this->adapter->schema}.{$this->adapter->model}' ADD COLUMN {$definition['field']} {$columnType} {$add};" - ); + // Override regular SQL-style to match SQLite requirements + // at least for datetime-fields with a default value + if ($definition['datatype'] == 'text_timestamp' && (($definition['default'] ?? null) == 'current_timestamp()')) { + $definition['default'] = 'CURRENT_TIMESTAMP'; + } + return $definition; } - if($task->name == "MODIFY_COLUMN_TYPE" || $task->name == "MODIFY_DATA_TYPE" || $task->name == "MODIFY_NOTNULL" || $task->name == "MODIFY_DEFAULT") { - // ALTER TABLE tablename MODIFY columnname INTEGER; - $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0]; - $nullable = $definition['notnull'] ? 'NOT NULL' : '' ; // 'NULL'; - - if(array_key_exists('default', $definition) && $definition['datatype'] === 'text_timestamp' && $definition['default'] == 'current_timestamp()') { - $defaultValue = 'DEFAULT CURRENT_TIMESTAMP'; - } else { - $defaultValue = json_encode($definition['default'] ?? null); - } - - // - // we should update the existing dataset - // if it's NOT nullable - // - if($definition['notnull'] && $definition['default'] != null) { - $defaultValue = (is_bool($definition['default']) ? ($definition['default'] ? 1 : 0) : $defaultValue); - $db->query( - "UPDATE '{$this->adapter->schema}.{$this->adapter->model}' SET {$definition['field']} = {$defaultValue} WHERE {$definition['field']} IS NULL;" - ); - } - - $default = isset($definition['default']) ? 'DEFAULT ' . $defaultValue.'' : ''; + /** + * {@inheritDoc} + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + + $definition = $this->getDefinition(); + + if ($task->name == "CREATE_COLUMN") { + $attributes = []; + + if ($definition['notnull'] && isset($definition['default'])) { + $attributes[] = "NOT NULL"; + } + + if (isset($definition['default'])) { + // + // Special case: field is timestamp && default is CURRENT_TIMESTAMP + // + if ($definition['datatype'] === 'text_timestamp' && $definition['default'] == 'CURRENT_TIMESTAMP') { + $attributes[] = 'DEFAULT CURRENT_TIMESTAMP'; + } else { + $attributes[] = "DEFAULT " . json_encode($definition['default']); + } + } + + /* + // not allowed on normal fields? some requirements have to be met? + if($definition['auto_increment']) { + $attributes[] = "AUTO_INCREMENT"; + }*/ + + // TODO: add unique + // TODO: add index + + $add = implode(' ', $attributes); + + // fallback from specific column types to a more generous type + $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0]; + + $db->query( + "ALTER TABLE '{$this->adapter->schema}.{$this->adapter->model}' ADD COLUMN {$definition['field']} $columnType $add;" + ); + } - // NOT SUPPORTED ON SQLITE - // $db->query( - // "ALTER TABLE '{$this->adapter->schema}.{$this->adapter->model}' MODIFY COLUMN {$definition['field']} {$columnType} {$nullable} {$default};" - // ); + if ($task->name == "MODIFY_COLUMN_TYPE" || $task->name == "MODIFY_DATA_TYPE" || $task->name == "MODIFY_NOTNULL" || $task->name == "MODIFY_DEFAULT") { + if (array_key_exists('default', $definition) && $definition['datatype'] === 'text_timestamp' && $definition['default'] == 'current_timestamp()') { + $defaultValue = 'DEFAULT CURRENT_TIMESTAMP'; + } else { + $defaultValue = json_encode($definition['default'] ?? null); + } + + // + // we should update the existing dataset + // if it's NOT nullable + // + if ($definition['notnull'] && $definition['default'] != null) { + $defaultValue = (is_bool($definition['default']) ? (int)$definition['default'] : $defaultValue); + $db->query( + "UPDATE '{$this->adapter->schema}.{$this->adapter->model}' SET {$definition['field']} = $defaultValue WHERE {$definition['field']} IS NULL;" + ); + } + } } - - } - } diff --git a/backend/class/dbdoc/plugin/sql/sqlite/fieldlist.php b/backend/class/dbdoc/plugin/sql/sqlite/fieldlist.php index 0d79344..86d986a 100644 --- a/backend/class/dbdoc/plugin/sql/sqlite/fieldlist.php +++ b/backend/class/dbdoc/plugin/sql/sqlite/fieldlist.php @@ -1,117 +1,110 @@ getDefinition(); - $structure = $this->getStructure(); - - // fields contained in model, that are not in the database table - $missing = array_diff($definition, $structure); - - // columns in the database table, that are simply "too much" (not in the model definition) - $toomuch = array_diff($structure, $definition); +class fieldlist extends plugin\sql\fieldlist implements partialStatementInterface +{ + /** + * {@inheritDoc} + */ + public function Compare(): array + { + $definition = $this->getDefinition(); +// $structure = $this->getStructure(); + + // fields contained in model, that are not in the database table +// $missing = array_diff($definition, $structure); + + // columns in the database table, that are simply "too much" (not in the model definition) +// $toomuch = array_diff($structure, $definition); + + // TODO: handle toomuch + // e.g. check for prefix __old_ + // of not, create task to rename column + // otherwise, recommend harddeletion ? + + $modificationTasks = []; + + foreach ($definition as $field) { + $plugin = $this->adapter->getPluginInstance( + 'field', + [ + 'field' => $field, + ], + $this->virtual // virtual on need. + ); + + if ($plugin != null) { + // add this plugin to the first + // $this->adapter->addToQueue($plugin, true); + if (count($compareTasks = $plugin->Compare()) > 0) { + $modificationTasks = array_merge($modificationTasks, $compareTasks); + } + } + } - // TODO: handle toomuch - // e.g. check for prefix __old_ - // of not, create task to rename column - // otherwise, recommend harddeletion ? + return $modificationTasks; + } - $modificationTasks = []; + /** + * {@inheritDoc} + */ + public function getStructure(): array + { + $db = $this->getSqlAdapter()->db; + $db->query("PRAGMA table_info('{$this->adapter->schema}.{$this->adapter->model}');"); - foreach($definition as $field) { - $plugin = $this->adapter->getPluginInstance( - 'field', - array( - 'field' => $field - ), - $this->virtual // virtual on need. - ); + $res = $db->getResult(); - if($plugin != null) { - // add this plugin to the first - // $this->adapter->addToQueue($plugin, true); - if(count($compareTasks = $plugin->Compare()) > 0) { - $modificationTasks = array_merge($modificationTasks, $compareTasks); + $columns = []; + foreach ($res as $r) { + $columns[] = $r['name']; } - } - } - - // do something with it. - if(count($missing) == 0) { - - } else { + return $columns; } - return $modificationTasks; - } - - /** - * [getPartialStatement description] - * @return [type] [description] - */ - public function getPartialStatement() { - $definition = $this->getDefinition(); - $partialStatements = []; - foreach($definition as $field) { - $plugin = $this->adapter->getPluginInstance( - 'field', - array( - 'field' => $field - ), - $this->virtual // virtual on need. - ); - if($plugin instanceof \codename\architect\dbdoc\plugin\sql\sqlite\field) { - $partialStatement = $plugin->getPartialStatement(); - if($partialStatement) { - // - // getPartialStatement() might return a NULL value - // (e.g. if virtual/collection field) - // only add, if there's a value - // - $partialStatements[] = $partialStatement; + /** + * [getPartialStatement description] + * @return array [type] [description] + * @throws ReflectionException + * @throws catchableException + * @throws exception + */ + public function getPartialStatement(): array + { + $definition = $this->getDefinition(); + $partialStatements = []; + foreach ($definition as $field) { + $plugin = $this->adapter->getPluginInstance( + 'field', + [ + 'field' => $field, + ], + $this->virtual // virtual on need. + ); + if ($plugin instanceof field) { + $partialStatement = $plugin->getPartialStatement(); + if ($partialStatement) { + // + // getPartialStatement() might return a NULL value + // (e.g. if virtual/collection field) + // only add, if there's a value + // + $partialStatements[] = $partialStatement; + } + } } - } + return $partialStatements; } - return $partialStatements; - } - - /** - * @inheritDoc - */ - public function getStructure() - { - $db = $this->getSqlAdapter()->db; - // $db->query("SELECT column_name - // FROM information_schema.columns - // WHERE table_name = '{$this->adapter->model}' - // AND table_schema = '{$this->adapter->schema}' - // ;"); - $db->query("PRAGMA table_info('{$this->adapter->schema}.{$this->adapter->model}');"); - - $res = $db->getResult(); - - // echo("
");print_r($res);echo("
"); - - $columns = array(); - foreach($res as $r) { - $columns[] = $r['name']; - } - - return $columns; - } - } diff --git a/backend/class/dbdoc/plugin/sql/sqlite/foreign.php b/backend/class/dbdoc/plugin/sql/sqlite/foreign.php index 228de4a..3240d40 100644 --- a/backend/class/dbdoc/plugin/sql/sqlite/foreign.php +++ b/backend/class/dbdoc/plugin/sql/sqlite/foreign.php @@ -1,100 +1,61 @@ getSqlAdapter()->db; - - // $db->query( - // "SELECT tc.table_schema, tc.table_name, constraint_name, column_name, referenced_table_schema, referenced_table_name, referenced_column_name - // FROM information_schema.table_constraints tc - // INNER JOIN information_schema.key_column_usage kcu - // USING (constraint_catalog, constraint_schema, constraint_name) - // WHERE constraint_type = 'FOREIGN KEY' - // AND tc.table_schema = '{$this->adapter->schema}' - // AND tc.table_name = '{$this->adapter->model}';"); - - $db->query("PRAGMA foreign_key_list('{$this->adapter->schema}.{$this->adapter->model}')"); - - $constraints = $db->getResult(); - - return $constraints; - } - - /** - * @inheritDoc - */ - public function Compare(): array - { - return []; - } - - /** - * @inheritDoc - */ - public function getPartialStatement() - { - $definition = $this->getDefinition(); - - $foreignStatements = []; - foreach($definition as $fkeyName => $def) { - // Omit multi-component Fkeys - if($def['optional'] ?? false) { - continue; - } - - $constraintName = "fkey_" . md5("{$this->adapter->model}_{$def['model']}_{$fkeyName}_fkey"); - $foreignStatements[] = "CONSTRAINT {$constraintName} FOREIGN KEY ({$fkeyName}) REFERENCES `{$def['schema']}.{$def['model']}` ({$def['key']})"; +class foreign extends \codename\architect\dbdoc\plugin\sql\foreign implements partialStatementInterface +{ + /** + * {@inheritDoc} + */ + public function getStructure(): array + { + $db = $this->getSqlAdapter()->db; + + $db->query("PRAGMA foreign_key_list('{$this->adapter->schema}.{$this->adapter->model}')"); + + return $db->getResult(); } - return $foreignStatements; - } - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - // Disabled, as Sqlite's FKEY implementation is quite different. - return; - - $db = $this->getSqlAdapter()->db; - - // NOTE: Special implementation for MySQL - // see: https://stackoverflow.com/questions/14122031/how-to-remove-constraints-from-my-mysql-table/14122155 - if($task->name == "REMOVE_FOREIGNKEY_CONSTRAINT") { - - $field = $task->data->get('field'); - $config = $task->data->get('config'); - - $constraintName = $task->data->get('constraint_name'); - - // drop the foreign key constraint itself - $db->query( - "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} - DROP FOREIGN KEY {$constraintName};" - ); + /** + * {@inheritDoc} + */ + public function Compare(): array + { + return []; + } - // drop the associated index - $db->query( - "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} - DROP INDEX IF EXISTS {$constraintName};" - ); - return; + /** + * {@inheritDoc} + */ + public function getPartialStatement(): array|string|null + { + $definition = $this->getDefinition(); + + $foreignStatements = []; + foreach ($definition as $fkeyName => $def) { + // Omit multi-component Fkeys + if ($def['optional'] ?? false) { + continue; + } + + $constraintName = "fkey_" . md5("{$this->adapter->model}_{$def['model']}_{$fkeyName}_fkey"); + $foreignStatements[] = "CONSTRAINT $constraintName FOREIGN KEY ($fkeyName) REFERENCES `{$def['schema']}.{$def['model']}` ({$def['key']})"; + } + + return $foreignStatements; } - parent::runTask($task); - } + /** + * {@inheritDoc} + */ + public function runTask(task $task): void + { + // Disabled, as Sqlite's FKEY implementation is quite different. + } } diff --git a/backend/class/dbdoc/plugin/sql/sqlite/fulltext.php b/backend/class/dbdoc/plugin/sql/sqlite/fulltext.php index 6be592d..f56d81f 100644 --- a/backend/class/dbdoc/plugin/sql/sqlite/fulltext.php +++ b/backend/class/dbdoc/plugin/sql/sqlite/fulltext.php @@ -1,19 +1,22 @@ getDefinition(); - - // virtual = assume empty structure - $structure = $this->virtual ? array() : $this->getStructure(); - - $valid = array(); - $missing = array(); - $toomuch = array(); - - // $fieldsOnly = $this->parameter['fields_only'] ?? null; - foreach($structure as $strucName => $struc) { - - // get ordered (?) column_names - $indexColumnNames = array_map( - function($spec) { - return $spec['column_name']; - }, $struc - ); - - // $tasks[] = $this->createTask(task::TASK_TYPE_INFO, "FOUND_INDEXES", array( - // 'structure' => $structure - // )); - - // if($fieldsOnly && !in_array($indexColumnNames, $fieldsOnly, true)) { - // echo "Skipping ".var_export($indexColumnNames, true)." because not contained in fieldsOnly ".var_export($fieldsOnly, true)."
"; - // continue; - // } - // reduce to string, if only one element - $indexColumnNames = count($indexColumnNames) == 1 ? $indexColumnNames[0] : $indexColumnNames; - // - // if($indexColumnNames === 'productpricing_product_id') { - // print_r($definition); - // print_r($struc); - // die(); - // } - - // if($strucName == 'index_5cda4449ac0c1f3b3455772af51d07ac' || $indexColumnNames == 'productpricing_product_id') { - // print_r($indexColumnNames); - // print_r($definition); - // die(); - // } - - // compare! - if(in_array($indexColumnNames, $definition)) { - // constraint exists and is correct - $valid[] = $indexColumnNames; - } else { - // $toomuch = $constraintColumnNames; - - // echo("
");
-        // print_r([
-        //   $definition,
-        //   $indexColumnNames
-        // ]);
-        // echo("
"); - - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "REMOVE_INDEX", array( - 'index_name' => $strucName, - )); - } - } +class index extends \codename\architect\dbdoc\plugin\sql\index +{ + use modeladapterGetSqlAdapter; - // - // NOTE: see note above - // echo("
Foreign Definition for model [{$this->adapter->model}]".chr(10));
-    // print_r([
-    //   'definition' => $definition,
-    //   'valid'      => $valid,
-    //   'structure' => $structure
-    // ]);
-    // echo("
"); - - // determine missing constraints - array_walk($definition, function($d) use ($valid, &$missing) { - foreach($valid as $v) { - // DEBUG - // echo("-- Compare ".var_export($d,true)." ".gettype($d)." <=> ".var_export($v, true)." ".gettype($v)."
".chr(10)); - if(gettype($v) == gettype($d)) { - if($d == $v) { - // DEBUG - // echo("-- => valid/equal, skipping.
"); - return; - } - } else { - // DEBUG - // echo("-- => unequal types, skipping.
"); - continue; + /** + * {@inheritDoc} + */ + public function Compare(): array + { + $tasks = []; + + $definition = $this->getDefinition(); + + // virtual = assume empty structure + $structure = $this->virtual ? [] : $this->getStructure(); + + $valid = []; + $missing = []; + + foreach ($structure as $strucName => $struc) { + // get ordered (?) column_names + $indexColumnNames = array_map( + function ($spec) { + return $spec['column_name']; + }, + $struc + ); + + // reduce to string, if only one element + $indexColumnNames = count($indexColumnNames) == 1 ? $indexColumnNames[0] : $indexColumnNames; + + // compare! + if (in_array($indexColumnNames, $definition)) { + // constraint exists and is correct + $valid[] = $indexColumnNames; + } else { + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "REMOVE_INDEX", [ + 'index_name' => $strucName, + ]); + } } - } - - // DEBUG - // echo("-- => invalid/unequal, add to missing.
"); - // print_r($d); - $missing[] = $d; - }); - - foreach($missing as $def) { - - // if($fieldsOnly && !in_array($def, $fieldsOnly, true)) { - // echo "Skipping ".var_export($def, true)." because not contained in fieldsOnly ".var_export($fieldsOnly, true)."
"; - // continue; - // } - - $columns = is_array($def) ? implode(',', $def) : $def; - $indexName = 'index_' . md5("{$this->adapter->schema}.{$this->adapter->model}-".$columns); // prepend schema+model - - // if($indexName == 'index_5cda4449ac0c1f3b3455772af51d07ac') { - // print_r($def); - // print_r($columns); - // print_r($definition); - // die(); - // } - - if(is_array($def)) { - // multi-column constraint - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "ADD_INDEX", array( - 'index_name' => $indexName, - 'index_columns' => $def, - // 'raw_columns' =>$columns - )); - } else { - // single-column constraint - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "ADD_INDEX", array( - 'index_name' => $indexName, - 'index_columns' => $def, - // 'raw_columns' =>$columns - )); - } - } - - return $tasks; - } - - /** - * @inheritDoc - */ - public function getStructure() - { - /* - we may use some query like this: - // @see: https://stackoverflow.com/questions/5213339/how-to-see-indexes-for-a-database-or-table - SELECT (DISTINCT?) s.* - FROM INFORMATION_SCHEMA.STATISTICS s - LEFT OUTER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS t - ON t.TABLE_SCHEMA = s.TABLE_SCHEMA - AND t.TABLE_NAME = s.TABLE_NAME - AND s.INDEX_NAME = t.CONSTRAINT_NAME - WHERE 0 = 0 - AND t.CONSTRAINT_NAME IS NULL - AND s.TABLE_SCHEMA = 'YOUR_SCHEMA_SAMPLE'; - - - *** removal: - DROP INDEX `indexname` ON `tablename` + // determine missing constraints + array_walk($definition, function ($d) use ($valid, &$missing) { + foreach ($valid as $v) { + if (gettype($v) == gettype($d)) { + if ($d == $v) { + return; + } + } + } + + $missing[] = $d; + }); + + foreach ($missing as $def) { + $columns = is_array($def) ? implode(',', $def) : $def; + $indexName = 'index_' . md5("{$this->adapter->schema}.{$this->adapter->model}-" . $columns); // prepend schema+model + + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "ADD_INDEX", [ + 'index_name' => $indexName, + 'index_columns' => $def, + ]); + } + return $tasks; + } + /** + * {@inheritDoc} */ + public function getStructure(): array + { + $db = $this->getSqlAdapter()->db; + + $db->query("PRAGMA index_list('{$this->adapter->schema}.{$this->adapter->model}')"); + + // + // NOTE: we removed the following WHERE-component: + // AND tc.constraint_name IS NULL + // and replaced it with a check for just != PRIMARY + // because we may have constraints attached (foreign keys!) + // So, this plugin now handles ALL indexes, + // - explicit indexes (via "index" key in model config) + // - implicit indexes (unique & foreign keys) + // + + $allIndices = $db->getResult(); + + $indexGroups = []; + + // perform grouping + foreach ($allIndices as $index) { + // Compat mapping to generic index plugin + $index['index_name'] = $index['name']; + + $db->query("PRAGMA index_info('{$index['index_name']}')"); + $indexInfoRes = $db->getResult(); + + foreach ($indexInfoRes as $indexColumn) { + $indexGroups[$index['index_name']][] = array_merge( + $index, + $indexColumn, + ['column_name' => $indexColumn['name']] + ); + } + } - $db = $this->getSqlAdapter()->db; - - // $db->query( - // "SELECT DISTINCT tc.table_schema, tc.table_name, s.index_name, tc.constraint_name, s.column_name, s.seq_in_index - // FROM information_schema.statistics s - // LEFT OUTER JOIN information_schema.table_constraints tc - // ON tc.table_schema = s.table_schema - // AND tc.table_name = s.table_name - // AND s.index_name = tc.constraint_name - // WHERE 0 = 0 - // AND s.index_name NOT IN ('PRIMARY') - // AND s.table_schema = '{$this->adapter->schema}' - // AND s.table_name = '{$this->adapter->model}' - // AND s.index_type != 'FULLTEXT'" - // ); - $db->query("PRAGMA index_list('{$this->adapter->schema}.{$this->adapter->model}')"); - - // - // NOTE: we removed the following WHERE-component: - // AND tc.constraint_name IS NULL - // and replaced it with a check for just != PRIMARY - // because we may have constraints attached (foreign keys!) - // So, this plugin now handles ALL indexes, - // - explicit indexes (via "index" key in model config) - // - implicit indexes (unique & foreign keys) - // - - $allIndices = $db->getResult(); - - $indexGroups = []; - - // perform grouping - foreach($allIndices as $index) { - // Compat mapping to generic index plugin - $index['index_name'] = $index['name']; - - // if($index['name'] == 'index_e7760f32be0d18cb84d382d6b705b5b1') { - // print_r($index); - // } - - $db->query("PRAGMA index_info('{$index['index_name']}')"); - $indexInfoRes = $db->getResult(); - - // $indexInfo = $indexInfoRes[0]; - // print_r($index); - // print_r($indexInfo); - // die(); - // if($index['name'] == 'index_e7760f32be0d18cb84d382d6b705b5b1') { - // print_r($indexInfoRes); - // } - - foreach($indexInfoRes as $indexColumn) { - $indexGroups[$index['index_name']][] = array_merge( - $index, - $indexColumn, - [ 'column_name' => $indexColumn['name'] ] - ); - } - - // print_r($indexInfo); - - // if(array_key_exists($index['index_name'], $indexGroups)) { - // // match to existing group - // foreach($indexGroups as $groupName => $group) { - // if($index['index_name'] == $groupName) { - // $indexGroups[$groupName][] = $index; - // break; - // } - // } - // } else { - // // create new group - // $indexGroups[$index['index_name']][] = $index; - // } - } - - // print_r($indexGroups); - // die(); + $sortedIndexGroups = []; + // sort! + foreach ($indexGroups as $groupName => $group) { + usort($group, function ($left, $right) { + return $left['seqno'] > $right['seqno']; + }); + $sortedIndexGroups[$groupName] = $group; + } - $sortedIndexGroups = []; - // sort! - foreach($indexGroups as $groupName => $group) { - usort($group, function($left, $right) { - return $left['seqno'] > $right['seqno']; - }); - $sortedIndexGroups[$groupName] = $group; + return $sortedIndexGroups; } - return $sortedIndexGroups; - } - - - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; - - if($task->name == "ADD_INDEX") { - /* - CREATE INDEX ON
(); - */ - - $indexColumns = $task->data->get('index_columns'); - $columns = is_array($indexColumns) ? implode(',', $indexColumns) : $indexColumns; - $indexName = 'index_' . md5("{$this->adapter->schema}.{$this->adapter->model}-".$columns);// prepend schema+model - $db->query( - "CREATE INDEX {$indexName} ON '{$this->adapter->schema}.{$this->adapter->model}' ({$columns});" - ); - } - - if($task->name == "REMOVE_INDEX") { + /** + * {@inheritDoc} + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + + if ($task->name == "ADD_INDEX") { + $indexColumns = $task->data->get('index_columns'); + $columns = is_array($indexColumns) ? implode(',', $indexColumns) : $indexColumns; + $indexName = 'index_' . md5("{$this->adapter->schema}.{$this->adapter->model}-" . $columns);// prepend schema+model + + $db->query( + "CREATE INDEX $indexName ON '{$this->adapter->schema}.{$this->adapter->model}' ($columns);" + ); + } - // simply drop index by index_name - $indexName = $task->data->get('index_name'); + if ($task->name == "REMOVE_INDEX") { + // simply drop index by index_name + $indexName = $task->data->get('index_name'); - $db->query( - "DROP INDEX IF EXISTS {$indexName};" // ON '{$this->adapter->schema}.{$this->adapter->model}' - ); + $db->query( + "DROP INDEX IF EXISTS $indexName;" // ON '{$this->adapter->schema}.{$this->adapter->model}' + ); + } } - } - } diff --git a/backend/class/dbdoc/plugin/sql/sqlite/partialStatementInterface.php b/backend/class/dbdoc/plugin/sql/sqlite/partialStatementInterface.php index 6509631..68487ff 100644 --- a/backend/class/dbdoc/plugin/sql/sqlite/partialStatementInterface.php +++ b/backend/class/dbdoc/plugin/sql/sqlite/partialStatementInterface.php @@ -1,12 +1,13 @@ getSqlAdapter()->db; - // $db->query( - // "SELECT column_name, column_type, data_type - // FROM information_schema.columns - // WHERE table_schema = '{$this->adapter->schema}' - // AND table_name = '{$this->adapter->model}' - // AND column_key = 'PRI';" - // ); + /** + * {@inheritDoc} + */ + public function getStructure(): array + { + // get some column specifications + $db = $this->getSqlAdapter()->db; - // $db->query( - // "PRAGMA table_info('{$this->adapter->schema}.{$this->adapter->model}');" - // ); + // $db->query( + // "PRAGMA table_info('{$this->adapter->schema}.{$this->adapter->model}');" + // ); - $db->query( - "SELECT name AS column_name, type AS column_type, type AS data_type + $db->query( + "SELECT name AS column_name, type AS column_type, type AS data_type FROM pragma_table_info('{$this->adapter->schema}.{$this->adapter->model}') WHERE pk = 1;" - ); + ); - $res = $db->getResult(); - if(count($res) === 1) { - return $res[0]; + $res = $db->getResult(); + if (count($res) === 1) { + return $res[0]; + } + return []; } - return null; - } - - /** - * default column data type for primary keys on mysql - * @var string - */ - public const DB_DEFAULT_DATA_TYPE = 'INTEGER'; - /** - * default column type for primary keys on mysql - * @var string - */ - public const DB_DEFAULT_COLUMN_TYPE = 'INTEGER'; + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + $definition = parent::getDefinition(); + $definition['options'] = $this->adapter->config->get('options>' . $definition['field']) ?? []; + $definition['options']['db_data_type'] = $definition['options']['db_data_type'] ?? [self::DB_DEFAULT_DATA_TYPE]; // NOTE: this has to be an array + $definition['options']['db_column_type'] = $definition['options']['db_column_type'] ?? [self::DB_DEFAULT_COLUMN_TYPE]; // NOTE: this has to be an array + return $definition; + } - /** - * @inheritDoc - */ - public function getDefinition() - { - $definition = parent::getDefinition(); - $definition['options'] = $this->adapter->config->get('options>'.$definition['field']) ?? []; - $definition['options']['db_data_type'] = $definition['options']['db_data_type'] ?? [ self::DB_DEFAULT_DATA_TYPE ]; // NOTE: this has to be an array - $definition['options']['db_column_type'] = $definition['options']['db_column_type'] ?? [ self::DB_DEFAULT_COLUMN_TYPE ]; // NOTE: this has to be an array - return $definition; - } + /** + * {@inheritDoc} + */ + protected function checkPrimaryKeyAttributes(array $definition, array $structure): array + { + $tasks = []; - /** - * @inheritDoc - */ - protected function checkPrimaryKeyAttributes(array $definition, array $structure) : array - { - $tasks = array(); + if ($structure['data_type'] != self::DB_DEFAULT_DATA_TYPE) { + // suggest column data_type modification + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "MODIFY_COLUMN_DATATYPE", [ + 'field' => $structure['column_name'], + 'db_data_type' => self::DB_DEFAULT_DATA_TYPE, + ]); + } elseif ($structure['column_type'] != self::DB_DEFAULT_COLUMN_TYPE) { + // suggest column column_type modification + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "MODIFY_COLUMN_COLUMNTYPE", [ + 'field' => $structure['column_name'], + 'db_data_type' => self::DB_DEFAULT_DATA_TYPE, + 'db_column_type' => self::DB_DEFAULT_COLUMN_TYPE, + ]); + } - if($structure['data_type'] != self::DB_DEFAULT_DATA_TYPE) { - // suggest column data_type modification - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "MODIFY_COLUMN_DATATYPE", array( - 'field' => $structure['column_name'], - 'db_data_type' => self::DB_DEFAULT_DATA_TYPE - )); - } else { - if($structure['column_type'] != self::DB_DEFAULT_COLUMN_TYPE) { - // suggest column column_type modification - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "MODIFY_COLUMN_COLUMNTYPE", array( - 'field' => $structure['column_name'], - 'db_data_type' => self::DB_DEFAULT_DATA_TYPE, - 'db_column_type' => self::DB_DEFAULT_COLUMN_TYPE - )); - } + return $tasks; } - - return $tasks; - } - } diff --git a/backend/class/dbdoc/plugin/sql/sqlite/schema.php b/backend/class/dbdoc/plugin/sql/sqlite/schema.php index de1ca3d..a9f8a53 100644 --- a/backend/class/dbdoc/plugin/sql/sqlite/schema.php +++ b/backend/class/dbdoc/plugin/sql/sqlite/schema.php @@ -1,69 +1,71 @@ getSqlAdapter()->db; - return true; - // $db->query( - // "SELECT exists(select 1 FROM information_schema.schemata WHERE schema_name = '{$this->adapter->schema}') as result;" - // ); - // return $db->getResult()[0]['result']; - } + /** + * {@inheritDoc} + */ + public function Compare(): array + { + $tasks = []; + $definition = $this->getDefinition(); - /** - * @inheritDoc - */ - public function Compare() : array - { - $tasks = array(); - $definition = $this->getDefinition(); + // virtual = assume empty structure + $structure = !$this->virtual && $this->getStructure(); - // virtual = assume empty structure - $structure = $this->virtual ? false : $this->getStructure(); + if (!$structure) { + // schema/database does not exist + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_SCHEMA", [ + 'schema' => $definition, + ]); + } - if(!$structure) { - // schema/database does not exist - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_SCHEMA", array( - 'schema' => $definition - )); - } + // schema/database exists + // start subroutine plugins + $plugin = $this->adapter->getPluginInstance('table', [], !$structure); + if ($plugin != null) { + // add this plugin to the first + $this->adapter->addToQueue($plugin, true); + } - // schema/database exists - // start subroutine plugins - $plugin = $this->adapter->getPluginInstance('table', array(), !$structure); - if($plugin != null) { - // add this plugin to the first - $this->adapter->addToQueue($plugin, true); + return $tasks; } - return $tasks; - } - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; - - if($task->name == 'CREATE_SCHEMA') { - // CREATE SCHEMA - $db->query("CREATE SCHEMA `{$this->adapter->schema}`;"); + /** + * {@inheritDoc} + */ + public function getStructure(): bool + { + return true; } - } + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + if ($task->name == 'CREATE_SCHEMA') { + // CREATE SCHEMA + $db->query("CREATE SCHEMA `{$this->adapter->schema}`;"); + } + } } diff --git a/backend/class/dbdoc/plugin/sql/sqlite/table.php b/backend/class/dbdoc/plugin/sql/sqlite/table.php index ec9f233..d4f5132 100644 --- a/backend/class/dbdoc/plugin/sql/sqlite/table.php +++ b/backend/class/dbdoc/plugin/sql/sqlite/table.php @@ -1,312 +1,293 @@ getDefinition(); - - // if virtual, simulate nonexisting structure - $structure = $this->virtual ? false : $this->getStructure(); - - // structure doesn't exist - if(!$structure) { - // table does not exist - // create table - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_TABLE", array( - 'table' => $definition - )); - } else { - // Modify existing table - // Needs to be done as full re-create - - // NOTE: Only for deleting obsolete fields... - // $tasks = $this->getCheckStructure($tasks); - - // either run sub-plugins virtually or the 'hard' way - - $modificationTasks = []; - $regularTasks = []; - - // execute plugin for fulltext - $plugin = $this->adapter->getPluginInstance('fulltext', array(), $this->virtual); - if($plugin != null) { - if(count($compareTasks = $plugin->Compare()) > 0) { - // change(s) detected - $modificationTasks = array_merge($modificationTasks, $compareTasks); - } - } - - // execute plugin for unique constraints - $plugin = $this->adapter->getPluginInstance('unique', array(), $this->virtual); - if($plugin != null) { - if(count($compareTasks = $plugin->Compare()) > 0) { - // change(s) detected - // CHANGED 2021-04-29: unique constraints now handled during table creation - $modificationTasks = array_merge($modificationTasks, $compareTasks); - } - } - - // collection key plugin - $plugin = $this->adapter->getPluginInstance('collection', array(), $this->virtual); - if($plugin != null) { - if(count($compareTasks = $plugin->Compare()) > 0) { - // change(s) detected - $modificationTasks = array_merge($modificationTasks, $compareTasks); - } - } - - // foreign key plugin - $plugin = $this->adapter->getPluginInstance('foreign', array(), $this->virtual); - if($plugin != null) { - if(count($compareTasks = $plugin->Compare()) > 0) { - // change(s) detected - $modificationTasks = array_merge($modificationTasks, $compareTasks); - } - } - - // fieldlist - $plugin = $this->adapter->getPluginInstance('fieldlist', array(), $this->virtual); - if($plugin != null) { - if(count($compareTasks = $plugin->Compare()) > 0) { - // change(s) detected - $modificationTasks = array_merge($modificationTasks, $compareTasks); - } - } - - // pkey first - $plugin = $this->adapter->getPluginInstance('primary', array(), $this->virtual); - if($plugin != null) { - if(count($compareTasks = $plugin->Compare()) > 0) { - // change(s) detected - $modificationTasks = array_merge($modificationTasks, $compareTasks); - } - } +class table extends plugin\sql\table +{ + /** + * {@inheritDoc} + */ + public function Compare(): array + { + $tasks = []; + $definition = $this->getDefinition(); + + // if virtual, simulate nonexisting structure + $structure = $this->virtual ? false : $this->getStructure(); + + // structure doesn't exist + if (!$structure) { + // table does not exist + // create table + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_TABLE", [ + 'table' => $definition, + ]); + } else { + // Modify existing table + // Needs to be done as full re-create + + // NOTE: Only for deleting obsolete fields... + // $tasks = $this->getCheckStructure($tasks); + + // either run sub-plugins virtually or the 'hard' way + + $modificationTasks = []; + $regularTasks = []; + + // execute plugin for fulltext + $plugin = $this->adapter->getPluginInstance('fulltext', [], $this->virtual); + if ($plugin != null) { + if (count($compareTasks = $plugin->Compare()) > 0) { + // change(s) detected + $modificationTasks = array_merge($modificationTasks, $compareTasks); + } + } - if(count($modificationTasks) > 0) { + // execute plugin for unique constraints + $plugin = $this->adapter->getPluginInstance('unique', [], $this->virtual); + if ($plugin != null) { + if (count($compareTasks = $plugin->Compare()) > 0) { + // change(s) detected + // CHANGED 2021-04-29: unique constraints now handled during table creation + $modificationTasks = array_merge($modificationTasks, $compareTasks); + } + } + // collection key plugin + $plugin = $this->adapter->getPluginInstance('collection', [], $this->virtual); + if ($plugin != null) { + if (count($compareTasks = $plugin->Compare()) > 0) { + // change(s) detected + $modificationTasks = array_merge($modificationTasks, $compareTasks); + } + } - $recreateTableTask = $this->createTask(task::TASK_TYPE_INFO, "RECREATE_TABLE", array( - 'table' => $definition, - )); + // foreign key plugin + $plugin = $this->adapter->getPluginInstance('foreign', [], $this->virtual); + if ($plugin != null) { + if (count($compareTasks = $plugin->Compare()) > 0) { + // change(s) detected + $modificationTasks = array_merge($modificationTasks, $compareTasks); + } + } - $tasks[] = $recreateTableTask; + // fieldlist + $plugin = $this->adapter->getPluginInstance('fieldlist', [], $this->virtual); + if ($plugin != null) { + if (count($compareTasks = $plugin->Compare()) > 0) { + // change(s) detected + $modificationTasks = array_merge($modificationTasks, $compareTasks); + } + } - $infoTasks = []; + // pkey first + $plugin = $this->adapter->getPluginInstance('primary', [], $this->virtual); + if ($plugin != null) { + if (count($compareTasks = $plugin->Compare()) > 0) { + // change(s) detected + $modificationTasks = array_merge($modificationTasks, $compareTasks); + } + } - $minimumTaskType = null; + if (count($modificationTasks) > 0) { + $recreateTableTask = $this->createTask(task::TASK_TYPE_INFO, "RECREATE_TABLE", [ + 'table' => $definition, + ]); - foreach ($modificationTasks as $mTask) { - if($mTask instanceof task) { + $tasks[] = $recreateTableTask; - if($mTask->type === task::TASK_TYPE_ERROR) { - // error overrides all - $minimumTaskType = $mTask->type; - } else { - // required is the next largest one - if($minimumTaskType !== task::TASK_TYPE_REQUIRED && $mTask->type === task::TASK_TYPE_REQUIRED) { - $minimumTaskType = $mTask->type; - } else if($mTask->type === task::TASK_TYPE_SUGGESTED) { - $minimumTaskType = $mTask->type; - } - } + $infoTasks = []; - $infoTask = $this->createTask(task::TASK_TYPE_INFO, 'RELAYED_'.$mTask->name, $mTask->data->get()); - // $infoTask->precededBy = $recreateTableTask->identifier; + $minimumTaskType = null; - $infoTasks[] = $infoTask; - } - } + foreach ($modificationTasks as $mTask) { + if ($mTask instanceof task) { + if ($mTask->type === task::TASK_TYPE_ERROR) { + // error overrides all + $minimumTaskType = $mTask->type; + } elseif ($minimumTaskType !== task::TASK_TYPE_REQUIRED && $mTask->type === task::TASK_TYPE_REQUIRED) { + $minimumTaskType = $mTask->type; + } elseif ($mTask->type === task::TASK_TYPE_SUGGESTED) { + $minimumTaskType = $mTask->type; + } - $recreateTableTask->type = $minimumTaskType ?? $recreateTableTask->type; + $infoTask = $this->createTask(task::TASK_TYPE_INFO, 'RELAYED_' . $mTask->name, $mTask->data->get()); - $tasks = array_merge($tasks, $infoTasks); - } + $infoTasks[] = $infoTask; + } + } - // Merge-in regular tasks - $tasks = array_merge($tasks, $regularTasks); + $recreateTableTask->type = $minimumTaskType ?? $recreateTableTask->type; - } + $tasks = array_merge($tasks, $infoTasks); + } - // execute plugin for indices - $plugin = $this->adapter->getPluginInstance('index', array(), $this->virtual); - if($plugin != null) { - $this->adapter->addToQueue($plugin); - } + // Merge-in regular tasks + $tasks = array_merge($tasks, $regularTasks); + } - return $tasks; - } - - /** - * @inheritDoc - */ - public function getStructure() - { - $db = $this->getSqlAdapter()->db; - // $db->query( - // "SELECT exists(select 1 FROM information_schema.tables WHERE table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}') as result;" - // ); - $db->query( - "SELECT exists(select 1 FROM sqlite_master WHERE type = 'table' AND tbl_name = '{$this->adapter->schema}.{$this->adapter->model}') as result;" - ); - return $db->getResult()[0]['result']; - } - - /** - * @inheritDoc - */ - protected function getCheckStructure($tasks) { - $db = $this->getSqlAdapter()->db; - // $db->query( - // "SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}';" - // ); - // $db->query( - // "PRAGMA table_info('{$this->adapter->schema}.{$this->adapter->model}');" - // ); - $db->query( - "SELECT name as COLUMN_NAME FROM pragma_table_info('{$this->adapter->schema}.{$this->adapter->model}');" - ); - - ; - $columns = $db->getResult(); - - if ($columns ?? false) { - $fields = $this->adapter->config->get()['field'] ?? []; - - foreach($columns as $column) { - if (!in_array($column['COLUMN_NAME'], $fields)) { - $tasks[] = $this->createTask(task::TASK_TYPE_OPTIONAL, "DELETE_COLUMN", array( - 'table' => $this->adapter->model, - 'field' => $column['COLUMN_NAME'] - )); + // execute plugin for indices + $plugin = $this->adapter->getPluginInstance('index', [], $this->virtual); + if ($plugin != null) { + $this->adapter->addToQueue($plugin); } - } + + return $tasks; } - return $tasks; - } - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; - - // If recreating a table due to required changes - // this is variable being set during the process - $preliminaryTable = null; - - if($task->name == 'RECREATE_TABLE') { - - // for recreating a table, we - // - create a preliminary table first (with the desired structure) - // - then copy all existing data to it (via mutual fields) - // - drop the existing table - // - rename the preliminary table to the desired name - // (to minimize downtime and keep the system from failing) - // - $preliminaryTable = "__prelim__{$this->adapter->schema}.{$this->adapter->model}"; - - // TODO: we also might have multiple 'prelims' - // as an earlier creation process might have failed - // to improve redundancy and keep old data for debugging - // we might use an increment value, - // when an existing table is detected - // e.g. __prelim_2_tablename - - // test for existing prelim/backup table - $db->query("SELECT exists(select 1 FROM sqlite_master WHERE type = 'table' AND tbl_name = '{$preliminaryTable}') as result;"); - if($db->getResult()[0]['result']) { - // Drop existing preliminary table - $db->query("DROP TABLE `{$preliminaryTable}`"); - } + + /** + * {@inheritDoc} + */ + public function getStructure(): mixed + { + $db = $this->getSqlAdapter()->db; + $db->query( + "SELECT exists(select 1 FROM sqlite_master WHERE type = 'table' AND tbl_name = '{$this->adapter->schema}.{$this->adapter->model}') as result;" + ); + return $db->getResult()[0]['result']; } + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + + // If recreating a table due to required changes + // this is variable being set during the process + $preliminaryTable = null; + + if ($task->name == 'RECREATE_TABLE') { + // for recreating a table, we + // - create a preliminary table first (with the desired structure) + // - then copy all existing data to it (via mutual fields) + // - drop the existing table + // - rename the preliminary table to the desired name + // (to minimize downtime and keep the system from failing) + // + $preliminaryTable = "__prelim__{$this->adapter->schema}.{$this->adapter->model}"; + + // TODO: we also might have multiple 'prelims' + // as an earlier creation process might have failed + // to improve redundancy and keep old data for debugging + // we might use an increment value, + // when an existing table is detected + // e.g. __prelim_2_tablename + + // test for existing prelim/backup table + $db->query("SELECT exists(select 1 FROM sqlite_master WHERE type = 'table' AND tbl_name = '$preliminaryTable') as result;"); + if ($db->getResult()[0]['result']) { + // Drop existing preliminary table + $db->query("DROP TABLE `$preliminaryTable`"); + } + } + - if($task->name == 'CREATE_TABLE' || $task->name == 'RECREATE_TABLE') { - // - // SQLite full create - // Aggregate every field statement of this table - // and execute all at once - // - $fieldStatements = []; - $fieldlistPlugin = $this->adapter->getPluginInstance('fieldlist'); - if($fieldlistPlugin instanceof partialStatementInterface) { - $fieldStatements = $fieldlistPlugin->getPartialStatement(); - } + if ($task->name == 'CREATE_TABLE' || $task->name == 'RECREATE_TABLE') { + // + // SQLite full create + // Aggregate every field statement of this table + // and execute all at once + // + $fieldStatements = []; + $fieldlistPlugin = $this->adapter->getPluginInstance('fieldlist'); + if ($fieldlistPlugin instanceof partialStatementInterface) { + $fieldStatements = $fieldlistPlugin->getPartialStatement(); + } - // foreign key statements - $foreignPlugin = $this->adapter->getPluginInstance('foreign'); - if($foreignPlugin instanceof partialStatementInterface) { - $foreignStatements = $foreignPlugin->getPartialStatement(); - $fieldStatements = array_merge($fieldStatements, $foreignStatements); - } + // foreign key statements + $foreignPlugin = $this->adapter->getPluginInstance('foreign'); + if ($foreignPlugin instanceof partialStatementInterface) { + $foreignStatements = $foreignPlugin->getPartialStatement(); + $fieldStatements = array_merge($fieldStatements, $foreignStatements); + } - // unique key statements - $uniquePlugin = $this->adapter->getPluginInstance('unique'); - if($uniquePlugin instanceof partialStatementInterface) { - $uniqueStatements = $uniquePlugin->getPartialStatement(); - $fieldStatements = array_merge($fieldStatements, $uniqueStatements); - } + // unique key statements + $uniquePlugin = $this->adapter->getPluginInstance('unique'); + if ($uniquePlugin instanceof partialStatementInterface) { + $uniqueStatements = $uniquePlugin->getPartialStatement(); + $fieldStatements = array_merge($fieldStatements, $uniqueStatements); + } - $fieldSql = implode(",\n", $fieldStatements); + $fieldSql = implode(",\n", $fieldStatements); - $tableSpecifier = $preliminaryTable ?? "{$this->adapter->schema}.{$this->adapter->model}"; + $tableSpecifier = $preliminaryTable ?? "{$this->adapter->schema}.{$this->adapter->model}"; - // DEBUG - // print_r($fieldSql); + // DEBUG + // print_r($fieldSql); - $db->query( - "CREATE TABLE `{$tableSpecifier}` ( + $db->query( + "CREATE TABLE `$tableSpecifier` ( $fieldSql );" - ); + ); + } + if ($task->name == 'RECREATE_TABLE') { + // copy data from existing to preliminary table + if ($preliminaryTable) { + // determine mutual fields + $db->query("PRAGMA table_info(`$preliminaryTable`)"); + $preliminaryTableFields = array_column($db->getResult(), 'name'); + $db->query("PRAGMA table_info(`{$this->adapter->schema}.{$this->adapter->model}`)"); + $existingTableFields = array_column($db->getResult(), 'name'); + $mutualFields = array_intersect($preliminaryTableFields, $existingTableFields); + $mutualFieldsSql = implode(',', $mutualFields); + + // copy old data to new table + $db->query( + "INSERT INTO + `$preliminaryTable` ($mutualFieldsSql) + SELECT $mutualFieldsSql FROM `{$this->adapter->schema}.{$this->adapter->model}` + " + ); + + // drop existing table + $db->query("DROP TABLE `{$this->adapter->schema}.{$this->adapter->model}`"); + + // rename preliminary table + $db->query("ALTER TABLE `$preliminaryTable` RENAME TO `{$this->adapter->schema}.{$this->adapter->model}`"); + } + } } - if($task->name == 'RECREATE_TABLE') { - - // copy data from existing to preliminary table - if($preliminaryTable) { - - // determine mutual fields - $db->query("PRAGMA table_info(`{$preliminaryTable}`)"); - $preliminaryTableFields = array_column($db->getResult(), 'name'); - $db->query("PRAGMA table_info(`{$this->adapter->schema}.{$this->adapter->model}`)"); - $existingTableFields = array_column($db->getResult(), 'name'); - $mutualFields = array_intersect($preliminaryTableFields, $existingTableFields); - $mutualFieldsSql = implode(',', $mutualFields); - - // copy old data to new table - $db->query("INSERT INTO - `{$preliminaryTable}` ({$mutualFieldsSql}) - SELECT {$mutualFieldsSql} FROM `{$this->adapter->schema}.{$this->adapter->model}` - "); - - // drop existing table - $db->query("DROP TABLE `{$this->adapter->schema}.{$this->adapter->model}`"); - - // rename preliminary table - $db->query("ALTER TABLE `{$preliminaryTable}` RENAME TO `{$this->adapter->schema}.{$this->adapter->model}`"); - } + /** + * {@inheritDoc} + */ + protected function getCheckStructure($tasks): array + { + $db = $this->getSqlAdapter()->db; + $db->query( + "SELECT name as COLUMN_NAME FROM pragma_table_info('{$this->adapter->schema}.{$this->adapter->model}');" + ); + $columns = $db->getResult(); + + if ($columns ?? false) { + $fields = $this->adapter->config->get()['field'] ?? []; + + foreach ($columns as $column) { + if (!in_array($column['COLUMN_NAME'], $fields)) { + $tasks[] = $this->createTask(task::TASK_TYPE_OPTIONAL, "DELETE_COLUMN", [ + 'table' => $this->adapter->model, + 'field' => $column['COLUMN_NAME'], + ]); + } + } + } + return $tasks; } - - // if($task->name == 'DELETE_COLUMN') { - // $db->query( - // "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} DROP COLUMN IF EXISTS {$task->data->get('field')};" - // ); - // } - } } diff --git a/backend/class/dbdoc/plugin/sql/sqlite/unique.php b/backend/class/dbdoc/plugin/sql/sqlite/unique.php index f3d6fca..3514ee8 100644 --- a/backend/class/dbdoc/plugin/sql/sqlite/unique.php +++ b/backend/class/dbdoc/plugin/sql/sqlite/unique.php @@ -1,115 +1,66 @@ getSqlAdapter()->db; - - // $db->query( - // "SELECT tc.table_schema, tc.table_name, constraint_name, column_name, referenced_table_schema, referenced_table_name, referenced_column_name - // FROM information_schema.table_constraints tc - // INNER JOIN information_schema.key_column_usage kcu - // USING (constraint_catalog, constraint_schema, constraint_name) - // WHERE constraint_type = 'FOREIGN KEY' - // AND tc.table_schema = '{$this->adapter->schema}' - // AND tc.table_name = '{$this->adapter->model}';"); - - $db->query($q = "SELECT name AS constraint_name, `unique` FROM pragma_index_list('{$this->adapter->schema}.{$this->adapter->model}')"); - $res = $db->getResult(); - - // if(count($res) > 0) { - // echo("
");
-    //   print_r($q);
-    //   print_r($res);
-    //   echo("
"); - // } - - $constraints = []; - foreach($res as $c) { - if($c['unique']) { - $db->query($q = "SELECT name as column_name FROM pragma_index_info('{$c['constraint_name']}')"); - // echo($q); - // $res2 = $db->getResult(); - $constraint = $c; - $constraintColumns = $db->getResult(); // array_column($res2, 'column_name'); - $constraint['constraint_columns'] = $constraintColumns; - $constraints[] = $constraint; - } +class unique extends \codename\architect\dbdoc\plugin\sql\unique implements partialStatementInterface +{ + /** + * {@inheritDoc} + */ + public function getStructure(): array + { + $db = $this->getSqlAdapter()->db; + + $db->query("SELECT name AS constraint_name, `unique` FROM pragma_index_list('{$this->adapter->schema}.{$this->adapter->model}')"); + $res = $db->getResult(); + + $constraints = []; + foreach ($res as $c) { + if ($c['unique']) { + $db->query("SELECT name as column_name FROM pragma_index_info('{$c['constraint_name']}')"); + $constraint = $c; + $constraintColumns = $db->getResult(); + $constraint['constraint_columns'] = $constraintColumns; + $constraints[] = $constraint; + } + } + + return $constraints; } - // if(count($res) > 0) { - // echo("
");
-    //   print_r($res);
-    //   print_r($constraints);
-    //   echo("
"); - // } - return $constraints; - } - - /** - * @inheritDoc - */ - public function getPartialStatement() - { - $definition = $this->getDefinition(); - - $uniqueStatements = []; - foreach($definition as $def) { - $constraintColumns = $def; - $columns = is_array($constraintColumns) ? implode(',', $constraintColumns) : $constraintColumns; - $constraintName = "unique_" . md5("{$this->adapter->schema}_{$this->adapter->model}_{$columns}"); - $uniqueStatements[] = "CONSTRAINT {$constraintName} UNIQUE ({$columns})"; + /** + * {@inheritDoc} + */ + public function getPartialStatement(): array|null|string + { + $definition = $this->getDefinition(); + + $uniqueStatements = []; + foreach ($definition as $def) { + $constraintColumns = $def; + $columns = is_array($constraintColumns) ? implode(',', $constraintColumns) : $constraintColumns; + $constraintName = "unique_" . md5("{$this->adapter->schema}_{$this->adapter->model}_$columns"); + $uniqueStatements[] = "CONSTRAINT $constraintName UNIQUE ($columns)"; + } + + return $uniqueStatements; } - return $uniqueStatements; - } - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - // Disabled, NOTE: - // SQLite's unique constraint handling is faulty - // those have to be created during CREATE TABLE - // as CREATE UNIQUE INDEX ... afterwards does not produce - // a working constraint - return; - - $db = $this->getSqlAdapter()->db; - if($task->name == "ADD_UNIQUE_CONSTRAINT") { - /* - ALTER TABLE
- ADD UNIQUE (); - */ - - $constraintColumns = $task->data->get('constraint_columns'); - $columns = is_array($constraintColumns) ? implode(',', $constraintColumns) : $constraintColumns; - - $constraintName = "unique_" . md5("{$this->adapter->schema}_{$this->adapter->model}_{$columns}"); - - $db->query( - "CREATE UNIQUE INDEX {$constraintName} - ON '{$this->adapter->schema}.{$this->adapter->model}' ({$columns});" - ); - } else if($task->name == "REMOVE_UNIQUE_CONSTRAINT") { - $constraintName = $task->data->get('constraint_name'); - - $db->query( - "DROP INDEX {$constraintName} ON '{$this->adapter->schema}.{$this->adapter->model}';" - ); + /** + * {@inheritDoc} + */ + public function runTask(task $task): void + { + // Disabled, NOTE: + // SQLite unique constraint handling is faulty + // those have to be created during CREATE TABLE + // as CREATE UNIQUE INDEX ... afterwards does not produce + // a working constraint } - } } diff --git a/backend/class/dbdoc/plugin/sql/table.php b/backend/class/dbdoc/plugin/sql/table.php index e7d4d1f..17bdda1 100644 --- a/backend/class/dbdoc/plugin/sql/table.php +++ b/backend/class/dbdoc/plugin/sql/table.php @@ -1,119 +1,134 @@ getSqlAdapter()->db; - $db->query( - "SELECT exists(select 1 FROM information_schema.tables WHERE table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}') as result;" - ); - return $db->getResult()[0]['result']; - } - - /** - * @inheritDoc - */ - protected function getCheckStructure($tasks) { - $db = $this->getSqlAdapter()->db; - $db->query( - "SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}';" - ); - $columns = $db->getResult(); - - if ($columns ?? false) { - $fields = $this->adapter->config->get()['field'] ?? []; - - foreach($columns as $column) { - if (!in_array($column['COLUMN_NAME'], $fields)) { - $tasks[] = $this->createTask(task::TASK_TYPE_OPTIONAL, "DELETE_COLUMN", array( - 'table' => $this->adapter->model, - 'field' => $column['COLUMN_NAME'] - )); +class table extends plugin\table +{ + use modeladapterGetSqlAdapter; + + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function Compare(): array + { + $tasks = []; + $definition = $this->getDefinition(); + + // if virtual, simulate nonexisting structure + $structure = $this->virtual ? false : $this->getStructure(); + + // structure doesn't exist + if (!$structure) { + // table does not exist + // create table + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_TABLE", [ + 'table' => $definition, + ]); } - } - } - return $tasks; - } - - /** - * @inheritDoc - */ - public function Compare() : array - { - $tasks = array(); - $definition = $this->getDefinition(); - - // if virtual, simulate nonexisting structure - $structure = $this->virtual ? false : $this->getStructure(); - - // structure doesn't exist - if(!$structure) { - // table does not exist - // create table - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_TABLE", array( - 'table' => $definition - )); - } - $tasks = $this->getCheckStructure($tasks); + $tasks = $this->getCheckStructure($tasks); - // either run sub-plugins virtually or the 'hard' way + // either run sub-plugins virtually or the 'hard' way - // execute plugin for indices - $plugin = $this->adapter->getPluginInstance('index', array(), $this->virtual); - if($plugin != null) { - $this->adapter->addToQueue($plugin, true); - } + // execute plugin for indices + $plugin = $this->adapter->getPluginInstance('index', [], $this->virtual); + if ($plugin != null) { + $this->adapter->addToQueue($plugin, true); + } - // execute plugin for fulltext - $plugin = $this->adapter->getPluginInstance('fulltext', array(), $this->virtual); - if($plugin != null) { - $this->adapter->addToQueue($plugin, true); - } + // execute plugin for fulltext + $plugin = $this->adapter->getPluginInstance('fulltext', [], $this->virtual); + if ($plugin != null) { + $this->adapter->addToQueue($plugin, true); + } - // execute plugin for unique constraints - $plugin = $this->adapter->getPluginInstance('unique', array(), $this->virtual); - if($plugin != null) { - $this->adapter->addToQueue($plugin, true); - } + // execute plugin for unique constraints + $plugin = $this->adapter->getPluginInstance('unique', [], $this->virtual); + if ($plugin != null) { + $this->adapter->addToQueue($plugin, true); + } - // collection key plugin - $plugin = $this->adapter->getPluginInstance('collection', array(), $this->virtual); - if($plugin != null) { - $this->adapter->addToQueue($plugin, true); - } + // collection key plugin + $plugin = $this->adapter->getPluginInstance('collection', [], $this->virtual); + if ($plugin != null) { + $this->adapter->addToQueue($plugin, true); + } - // foreign key plugin - $plugin = $this->adapter->getPluginInstance('foreign', array(), $this->virtual); - if($plugin != null) { - $this->adapter->addToQueue($plugin, true); - } + // foreign key plugin + $plugin = $this->adapter->getPluginInstance('foreign', [], $this->virtual); + if ($plugin != null) { + $this->adapter->addToQueue($plugin, true); + } - //N fieldlist - $plugin = $this->adapter->getPluginInstance('fieldlist', array(), $this->virtual); - if($plugin != null) { - $this->adapter->addToQueue($plugin, true); - } + //N fieldlist + $plugin = $this->adapter->getPluginInstance('fieldlist', [], $this->virtual); + if ($plugin != null) { + $this->adapter->addToQueue($plugin, true); + } - // pkey first - $plugin = $this->adapter->getPluginInstance('primary', array(), $this->virtual); - if($plugin != null) { - $this->adapter->addToQueue($plugin, true); + // pkey first + $plugin = $this->adapter->getPluginInstance('primary', [], $this->virtual); + if ($plugin != null) { + $this->adapter->addToQueue($plugin, true); + } + + return $tasks; } - return $tasks; - } + /** + * {@inheritDoc} + * @return mixed + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): mixed + { + $db = $this->getSqlAdapter()->db; + $db->query( + "SELECT exists(select 1 FROM information_schema.tables WHERE table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}') as result;" + ); + return $db->getResult()[0]['result']; + } -} \ No newline at end of file + /** + * @param $tasks + * @return array + * @throws ReflectionException + * @throws exception + */ + protected function getCheckStructure($tasks): array + { + $db = $this->getSqlAdapter()->db; + $db->query( + "SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}';" + ); + $columns = $db->getResult(); + + if (count($columns)) { + $fields = $this->adapter->config->get()['field'] ?? []; + + foreach ($columns as $column) { + if (!in_array($column['COLUMN_NAME'], $fields)) { + $tasks[] = $this->createTask(task::TASK_TYPE_OPTIONAL, "DELETE_COLUMN", [ + 'table' => $this->adapter->model, + 'field' => $column['COLUMN_NAME'], + ]); + } + } + } + return $tasks; + } +} diff --git a/backend/class/dbdoc/plugin/sql/unique.php b/backend/class/dbdoc/plugin/sql/unique.php index c286a4c..9f1c13d 100644 --- a/backend/class/dbdoc/plugin/sql/unique.php +++ b/backend/class/dbdoc/plugin/sql/unique.php @@ -1,144 +1,141 @@ getSqlAdapter()->db; - $db->query( - "SELECT table_schema, table_name, constraint_name +class unique extends \codename\architect\dbdoc\plugin\unique +{ + use modeladapterGetSqlAdapter; + + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function Compare(): array + { + $tasks = []; + + $definition = $this->getDefinition(); + + // virtual = assume empty structure + $structure = $this->virtual ? [] : $this->getStructure(); + + $valid = []; + $missing = []; + + foreach ($structure as $struc) { + // get ordered (?) column_names + $constraintColumnNames = array_map( + function ($spec) { + return $spec['column_name']; + }, + $struc['constraint_columns'] + ); + + // reduce to string, if only one element + $constraintColumnNames = count($constraintColumnNames) == 1 ? $constraintColumnNames[0] : $constraintColumnNames; + + // compare! + if (in_array($constraintColumnNames, $definition)) { + // constraint exists and is correct + $valid[] = $constraintColumnNames; + } else { + $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "REMOVE_UNIQUE_CONSTRAINT", [ + 'constraint_name' => $struc['constraint_name'], + ]); + } + } + + // determine missing constraints + array_walk($definition, function ($d) use ($valid, &$missing) { + foreach ($valid as $v) { + if (gettype($v) == gettype($d)) { + if ($d == $v) { + return; + } + } + } + $missing[] = $d; + }); + + foreach ($missing as $def) { + $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "ADD_UNIQUE_CONSTRAINT", [ + 'constraint_columns' => $def, + ]); + } + + return $tasks; + } + + /** + * {@inheritDoc} + * @return array + * @throws ReflectionException + * @throws exception + */ + public function getStructure(): array + { + $db = $this->getSqlAdapter()->db; + $db->query( + "SELECT table_schema, table_name, constraint_name FROM information_schema.table_constraints WHERE constraint_type='UNIQUE' AND table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}';" - ); - $constraints = $db->getResult(); + ); + $constraints = $db->getResult(); - foreach($constraints as &$constraint) { - $db->query( - "SELECT table_schema, table_name, constraint_name, column_name + foreach ($constraints as &$constraint) { + $db->query( + "SELECT table_schema, table_name, constraint_name, column_name FROM information_schema.key_column_usage WHERE constraint_name = '{$constraint['constraint_name']}' AND table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}' ORDER BY constraint_name;" - ); - $constraintColumns = $db->getResult(); - $constraint['constraint_columns'] = $constraintColumns; - } + ); + $constraintColumns = $db->getResult(); + $constraint['constraint_columns'] = $constraintColumns; + } - return $constraints; - } - - /** - * @inheritDoc - */ - public function Compare() : array - { - $tasks = array(); - - $definition = $this->getDefinition(); - - // virtual = assume empty structure - $structure = $this->virtual ? array() : $this->getStructure(); - - $valid = array(); - $missing = array(); - $toomuch = array(); - - foreach($structure as $struc) { - - // get ordered (?) column_names - $constraintColumnNames = array_map( - function($spec) { - return $spec['column_name']; - }, $struc['constraint_columns'] - ); - - // reduce to string, if only one element - $constraintColumnNames = count($constraintColumnNames) == 1 ? $constraintColumnNames[0] : $constraintColumnNames; - - // compare! - if(in_array($constraintColumnNames, $definition)) { - // constraint exists and is correct - $valid[] = $constraintColumnNames; - } else { - // $toomuch = $constraintColumnNames; - $tasks[] = $this->createTask(task::TASK_TYPE_SUGGESTED, "REMOVE_UNIQUE_CONSTRAINT", array( - 'constraint_name' => $struc['constraint_name'] - )); - } + return $constraints; } - // determine missing constraints - array_walk($definition, function($d) use ($valid, &$missing) { - foreach($valid as $v) { - if(gettype($v) == gettype($d)) { - if($d == $v) { - return; - } - } else { - continue; + /** + * {@inheritDoc} + * @param task $task + * @throws ReflectionException + * @throws exception + */ + public function runTask(task $task): void + { + $db = $this->getSqlAdapter()->db; + if ($task->name == "ADD_UNIQUE_CONSTRAINT") { + $constraintColumns = $task->data->get('constraint_columns'); + $columns = is_array($constraintColumns) ? implode(',', $constraintColumns) : $constraintColumns; + + $constraintName = "unique_" . md5("{$this->adapter->schema}_{$this->adapter->model}_$columns"); + + $db->query( + "CREATE UNIQUE INDEX $constraintName + ON {$this->adapter->schema}.{$this->adapter->model} ($columns);" + ); + } elseif ($task->name == "REMOVE_UNIQUE_CONSTRAINT") { + $constraintName = $task->data->get('constraint_name'); + + $db->query( + "DROP INDEX $constraintName ON {$this->adapter->schema}.{$this->adapter->model};" + ); } - } - $missing[] = $d; - }); - - foreach($missing as $def) { - if(is_array($def)) { - // multi-column constraint - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "ADD_UNIQUE_CONSTRAINT", array( - 'constraint_columns' => $def - )); - } else { - // single-column constraint - $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "ADD_UNIQUE_CONSTRAINT", array( - 'constraint_columns' => $def - )); - } } - - return $tasks; - } - - /** - * @inheritDoc - */ - public function runTask(\codename\architect\dbdoc\task $task) - { - $db = $this->getSqlAdapter()->db; - if($task->name == "ADD_UNIQUE_CONSTRAINT") { - /* - ALTER TABLE
- ADD UNIQUE (); - */ - - $constraintColumns = $task->data->get('constraint_columns'); - $columns = is_array($constraintColumns) ? implode(',', $constraintColumns) : $constraintColumns; - - $constraintName = "unique_" . md5("{$this->adapter->schema}_{$this->adapter->model}_{$columns}"); - - $db->query( - "CREATE UNIQUE INDEX {$constraintName} - ON {$this->adapter->schema}.{$this->adapter->model} ({$columns});" - ); - } else if($task->name == "REMOVE_UNIQUE_CONSTRAINT") { - $constraintName = $task->data->get('constraint_name'); - - $db->query( - "DROP INDEX {$constraintName} ON {$this->adapter->schema}.{$this->adapter->model};" - ); - } - } - -} \ No newline at end of file +} diff --git a/backend/class/dbdoc/plugin/sql/user.php b/backend/class/dbdoc/plugin/sql/user.php index e6aa87a..80e6ce7 100644 --- a/backend/class/dbdoc/plugin/sql/user.php +++ b/backend/class/dbdoc/plugin/sql/user.php @@ -1,12 +1,14 @@ adapter->model; - } - -} \ No newline at end of file +abstract class table extends modelPrefix +{ + /** + * {@inheritDoc} + */ + public function getDefinition(): ?string + { + return $this->adapter->model; + } +} diff --git a/backend/class/dbdoc/plugin/unique.php b/backend/class/dbdoc/plugin/unique.php index 637dd74..3c65ff0 100644 --- a/backend/class/dbdoc/plugin/unique.php +++ b/backend/class/dbdoc/plugin/unique.php @@ -1,18 +1,18 @@ adapter->config->get('unique') ?? array(); - } - -} \ No newline at end of file +abstract class unique extends modelPrefix +{ + /** + * {@inheritDoc} + */ + public function getDefinition(): array + { + return $this->adapter->config->get('unique') ?? []; + } +} diff --git a/backend/class/dbdoc/plugin/user.php b/backend/class/dbdoc/plugin/user.php index 6488919..71b01f7 100644 --- a/backend/class/dbdoc/plugin/user.php +++ b/backend/class/dbdoc/plugin/user.php @@ -1,53 +1,57 @@ adapter->config->get('connection') ?? 'default'; - $globalEnv = \codename\architect\app::getEnv(); - $environment = $this->adapter->environment; - - // backup env key - $prevEnv = $environment->getEnvironmentKey(); - - // change env key - $environment->setEnvironmentKey($globalEnv); - - // get database name - $config = $environment->get('database>'.$connection); - - // username defined in config or as an ENV-key - $user = isset($config['env_user']) ? getenv($config['env_user']) : $config['user']; - - // password defined as text or as ENV-key - // should throw an exception if neither is defined - $pass = isset($config['env_pass']) ? getenv($config['env_pass']) : $config['pass']; - - // revert env key - $environment->setEnvironmentKey($prevEnv); - - if(empty($user) || empty($pass)) { - throw new exception("EXCEPTION_ARCHITECT_DBDOC_PLUGIN_USER_INVALID_CONFIGURATION", exception::$ERRORLEVEL_FATAL, $config); +abstract class user extends connectionPrefix +{ + /** + * {@inheritDoc} + * @return array + * @throws exception + */ + public function getDefinition(): array + { + // get some database definitions for the real-world (non-architect) environment + // especially: username, password - for DB access! + + // get database specifier from model (connection) + $connection = $this->adapter->config->get('connection') ?? 'default'; + $globalEnv = app::getEnv(); + $environment = $this->adapter->environment; + + // backup env key + $prevEnv = $environment->getEnvironmentKey(); + + // change env key + $environment->setEnvironmentKey($globalEnv); + + // get database name + $config = $environment->get('database>' . $connection); + + // username defined in config or as an ENV-key + $user = isset($config['env_user']) ? getenv($config['env_user']) : $config['user']; + + // password defined as text or as ENV-key + // should throw an exception if neither is defined + $pass = isset($config['env_pass']) ? getenv($config['env_pass']) : $config['pass']; + + // revert env key + $environment->setEnvironmentKey($prevEnv); + + if (empty($user) || empty($pass)) { + throw new exception("EXCEPTION_ARCHITECT_DBDOC_PLUGIN_USER_INVALID_CONFIGURATION", exception::$ERRORLEVEL_FATAL, $config); + } + + return [ + 'user' => $user, + 'pass' => $pass, + ]; } - - return array( - 'user' => $user, - 'pass' => $pass - ); - } - } diff --git a/backend/class/dbdoc/structure.php b/backend/class/dbdoc/structure.php index b68def5..25b16bc 100644 --- a/backend/class/dbdoc/structure.php +++ b/backend/class/dbdoc/structure.php @@ -1,12 +1,11 @@ 'TASK_TYPE_ERROR', - self::TASK_TYPE_INFO => 'TASK_TYPE_INFO', - self::TASK_TYPE_REQUIRED => 'TASK_TYPE_REQUIRED', - self::TASK_TYPE_SUGGESTED => 'TASK_TYPE_SUGGESTED', - self::TASK_TYPE_OPTIONAL => 'TASK_TYPE_OPTIONAL', - ); - - /** - * the adapter - * @var string - */ - public $plugin; - - /** - * [protected description] - * @var \codename\core\config - */ - public $data; - - /** - * [public description] - * @var int - */ - public $type; - - /** - * [public description] - * @var string - */ - public $name; - - /** - * [public description] - * @var string - */ - public $identifier = null; - - /** - * task identifier prefixes - * that should precede this task - * @var string[] - */ - public $precededBy = array(); - - /** - * - */ - public function __construct(int $taskType, string $taskName, modeladapter $adapter, string $plugin, \codename\core\config $data) - { - $this->type = $taskType; - $this->name = $taskName; - $this->adapter = $adapter; - $this->plugin = $plugin; - $this->data = $data; - } - - /** - * [run description] - * @return [type] [description] - */ - public function run() { - $plugin = $this->getPlugin($this->data->get() ?? array()); - if($plugin != null) { - $plugin->runTask($this); - } else { - throw new exception(self::EXCEPTION_DBDOC_TASK_RUN_PLUGIN_NOT_FOUND, exception::$ERRORLEVEL_FATAL, $this); +class task +{ + /** + * [TASK_TYPE_ERROR description] + * @var int + */ + public const TASK_TYPE_ERROR = -1; + + /** + * [TASK_TYPE_INFO description] + * @var int + */ + public const TASK_TYPE_INFO = 0; + + /** + * [TASK_TYPE_REQUIRED description] + * @var int + */ + public const TASK_TYPE_REQUIRED = 1; + + /** + * [TASK_TYPE_SUGGESTED description] + * @var int + */ + public const TASK_TYPE_SUGGESTED = 2; + + /** + * [TASK_TYPE_OPTIONAL description] + * @var int + */ + public const TASK_TYPE_OPTIONAL = 3; + + /** + * [public description] + * @var array + */ + public const TASK_TYPES = [ + self::TASK_TYPE_ERROR => 'TASK_TYPE_ERROR', + self::TASK_TYPE_INFO => 'TASK_TYPE_INFO', + self::TASK_TYPE_REQUIRED => 'TASK_TYPE_REQUIRED', + self::TASK_TYPE_SUGGESTED => 'TASK_TYPE_SUGGESTED', + self::TASK_TYPE_OPTIONAL => 'TASK_TYPE_OPTIONAL', + ]; + /** + * [public description] + * @var string + */ + public const EXCEPTION_DBDOC_TASK_RUN_PLUGIN_NOT_FOUND = "EXCEPTION_DBDOC_TASK_RUN_PLUGIN_NOT_FOUND"; + /** + * the adapter + * @var string + */ + public string $plugin; + /** + * [protected description] + * @var config + */ + public config $data; + /** + * [public description] + * @var int + */ + public int $type; + /** + * [public description] + * @var string + */ + public string $name; + /** + * [public description] + * @var null|string + */ + public ?string $identifier = null; + /** + * task identifier prefixes + * that should precede this task + * @var array + */ + public array $precededBy = []; + /** + * @var modeladapter + */ + private modeladapter $adapter; + + /** + * + */ + public function __construct(int $taskType, string $taskName, modeladapter $adapter, string $plugin, config $data) + { + $this->type = $taskType; + $this->name = $taskName; + $this->adapter = $adapter; + $this->plugin = $plugin; + $this->data = $data; } - } - - /** - * [public description] - * @var string - */ - public const EXCEPTION_DBDOC_TASK_RUN_PLUGIN_NOT_FOUND = "EXCEPTION_DBDOC_TASK_RUN_PLUGIN_NOT_FOUND"; - /** - * [getPlugin description] - * @return plugin [description] - */ - protected function getPlugin(array $data = array()) { - $classname = "\\codename\\architect\\dbdoc\\plugin\\" . str_replace('_', '\\', $this->plugin); - if(class_exists($classname) && !(new \ReflectionClass($classname))->isAbstract()) { - $plugin = new $classname($this->adapter, $data); - return $plugin; + /** + * [run description] + * @return void [type] [description] + * @throws exception + */ + public function run(): void + { + $plugin = $this->getPlugin($this->data->get() ?? []); + if ($plugin != null) { + $plugin->runTask($this); + } else { + throw new exception(self::EXCEPTION_DBDOC_TASK_RUN_PLUGIN_NOT_FOUND, exception::$ERRORLEVEL_FATAL, $this); + } } - return null; - } - /** - * [getTaskTypeName description] - * @return string [description] - */ - public function getTaskTypeName() : string { - return self::TASK_TYPES[$this->type]; - } + /** + * [getPlugin description] + * @param array $data + * @return plugin|null [description] + */ + protected function getPlugin(array $data = []): ?plugin + { + $classname = "\\codename\\architect\\dbdoc\\plugin\\" . str_replace('_', '\\', $this->plugin); + if (class_exists($classname) && !(new ReflectionClass($classname))->isAbstract()) { + return new $classname($this->adapter, $data); + } + return null; + } -} \ No newline at end of file + /** + * [getTaskTypeName description] + * @return string [description] + */ + public function getTaskTypeName(): string + { + return self::TASK_TYPES[$this->type]; + } +} diff --git a/backend/class/deploy/deployment.php b/backend/class/deploy/deployment.php index 992455c..7cf4464 100644 --- a/backend/class/deploy/deployment.php +++ b/backend/class/deploy/deployment.php @@ -1,214 +1,232 @@ vendor = $vendor; - $this->app = $app; - $this->name = $name; - $this->config = $config; - $this->createDeploymentTasks(); - } - - /** - * current app - * @return string [description] - */ - public function getApp() : string { - return $this->app; - } - - /** - * current vendor - * @return string [description] - */ - public function getVendor() : string { - return $this->vendor; - } - - /** - * appstack of the foreign app - * @var array - */ - protected $foreignAppstack = null; - - /** - * get app's appstack - * @return array - */ - public function getAppstack() : array { - if(!$this->foreignAppstack) { - $this->foreignAppstack = app::makeForeignAppstack($this->getVendor(), $this->getApp()); - } - return $this->foreignAppstack; - } - - /** - * environment config of foreign app - * @var \codename\core\config - */ - protected $environment = null; - - /** - * returns the complete environment config - * of the app being deployed - * @return \codename\core\config [description] - */ - public function getEnvironment() : \codename\core\config { - if(!$this->environment) { - // TODO/CHECK: should we really inherit? Yes, we should. - $this->environment = new \codename\core\config\json('config/environment.json', true, true, $this->getAppstack()); +class deployment +{ + /** + * name of this deployment (e.g. file basename) + * @var null|string + */ + protected ?string $name = null; + + /** + * vendor name of app + * @var null|string + */ + protected ?string $vendor = null; + + /** + * app's name + * @var null|string + */ + protected ?string $app = null; + + /** + * This instance's deployment configuration + * @var null|config + */ + protected ?config $config = null; + /** + * appstack of the foreign app + * @var null|array + */ + protected ?array $foreignAppstack = null; + /** + * environment config of foreign app + * @var null|config + */ + protected ?config $environment = null; + /** + * virtualized environment config of foreign app + * @var null|environment + */ + protected ?environment $virtualEnvironment = null; + /** + * task instances + * @var task[] + */ + protected array $tasks = []; + + /** + * Initialize a new deployment instance + * @param string $vendor [description] + * @param string $app [description] + * @param string $name [description] + * @param config $config [description] + * @throws ReflectionException + * @throws exception + */ + public function __construct(string $vendor, string $app, string $name, config $config) + { + $this->vendor = $vendor; + $this->app = $app; + $this->name = $name; + $this->config = $config; + $this->createDeploymentTasks(); } - return $this->environment; - } - - /** - * virtualized environment config of foreign app - * @var \codename\architect\config\environment - */ - protected $virtualEnvironment = null; - - /** - * returns the current virtual environment config - * of the app being deployed - * using the ENV the architect is running in - * @return \codename\architect\config\environment [description] - */ - public function getVirtualEnvironment() : \codename\architect\config\environment { - if(!$this->virtualEnvironment) { - $this->virtualEnvironment = new \codename\architect\config\environment($this->getEnvironment()->get(), \codename\core\app::getEnv()); - } - return $this->virtualEnvironment; - } - - /** - * task instances - * @var task[] - */ - protected $tasks = []; - - - /** - * runs the deployment process - * @return deploymentresult [description] - */ - public function run() : deploymentresult { - $deploymentResultData = [ - 'date' => new \DateTime('now'), - 'taskresult' => [] - ]; - - foreach($this->tasks as $taskName => $task) { - $result = $task->run(); - $deploymentResultData['taskresult'][$taskName] = $result; + + /** + * creates the list of task instances for the deployment + * @return void [type] [description] + * @throws ReflectionException + * @throws exception + */ + protected function createDeploymentTasks(): void + { + if (!$this->config->exists('tasks')) { + throw new exception('DEPLOYMENT_CONFIGURATION_TASKS_KEY_MISSING', exception::$ERRORLEVEL_ERROR, [ + 'app' => $this->app, + 'vendor' => $this->vendor, + 'name' => $this->name, + ]); + } + foreach ($this->config->get('tasks') as $taskName => $taskConfig) { + $this->tasks[$taskName] = $this->createTaskInstance($taskName, $taskConfig); + } } - return new deploymentresult($deploymentResultData); - } - - /** - * creates the list of task instances for the deployment - * @return [type] [description] - */ - protected function createDeploymentTasks() { - if(!$this->config->exists('tasks')) { - throw new exception('DEPLOYMENT_CONFIGURATION_TASKS_KEY_MISSING', exception::$ERRORLEVEL_ERROR, [ - 'app' => $this->app, - 'vendor' => $this->vendor, - 'name' => $this->name - ]); + /** + * creates a task instance using a given name & config + * @param string $name [description] + * @param array $task [description] + * @return task [description] + * @throws ReflectionException + * @throws exception + */ + protected function createTaskInstance(string $name, array $task): task + { + $class = app::getInheritedClass('deploy_task_' . $task['type']); + $config = new config($task['config']); + return new $class($this, $name, $config); } - foreach($this->config->get('tasks') as $taskName => $taskConfig) { - $this->tasks[$taskName] = $this->createTaskInstance($taskName, $taskConfig); + + /** + * Creates a new deployment instance from a given config file + * @param string $vendor + * @param string $app + * @param string $deploymentName + * @return deployment + * @throws ReflectionException + * @throws exception + */ + public static function createFromConfig(string $vendor, string $app, string $deploymentName): deployment + { + // get app's homedir + $appdir = app::getHomedir($vendor, $app); + $fs = app::getFilesystem(); + + $dir = $appdir . 'config/deployment'; + + // + // Stop, if deployment config directory doesn't exist + // + if (!$fs->dirAvailable($dir)) { + throw new exception('DEPLOYMENT_CONFIG_DIRECTORY_DOES_NOT_EXIST', exception::$ERRORLEVEL_FATAL, $dir); + } + + $file = $dir . '/' . $deploymentName . '.json'; + + if (!$fs->fileAvailable($file)) { + throw new exception('DEPLOYMENT_CONFIG_FILE_DOES_NOT_EXIST', exception::$ERRORLEVEL_FATAL, $file); + } + + $config = new json($file); + return new self($vendor, $app, $deploymentName, $config); } - } - - /** - * creates a task instance using a given name & config - * @param string $name [description] - * @param array $task [description] - * @return task [description] - */ - protected function createTaskInstance(string $name, array $task) : task { - $class = app::getInheritedClass('deploy_task_'.$task['type']); - $config = new \codename\core\config($task['config']); - return new $class($this, $name, $config); - } - - /** - * Creates a new deployment instance from a given config file - * @param string $vendor - * @param string $app - * @param string $deploymentName - * @return deployment - */ - public static function createFromConfig(string $vendor, string $app, string $deploymentName) : deployment { - - // get app's homedir - $appdir = app::getHomedir($vendor, $app); - $fs = app::getFilesystem(); - - $dir = $appdir . 'config/deployment'; - - // - // Stop, if deployment config directory doesn't exist - // - if(!$fs->dirAvailable($dir)) { - throw new exception('DEPLOYMENT_CONFIG_DIRECTORY_DOES_NOT_EXIST', exception::$ERRORLEVEL_FATAL, $dir); + + /** + * returns the current virtual environment config + * of the app being deployed + * using the ENV the architect is running in + * @return environment [description] + * @throws ReflectionException + * @throws exception + */ + public function getVirtualEnvironment(): environment + { + if (!$this->virtualEnvironment) { + $this->virtualEnvironment = new environment($this->getEnvironment()->get(), \codename\core\app::getEnv()); + } + return $this->virtualEnvironment; } - $file = $dir.'/'.$deploymentName.'.json'; + /** + * returns the complete environment config + * of the app being deployed + * @return config [description] + * @throws ReflectionException + * @throws exception + */ + public function getEnvironment(): config + { + if (!$this->environment) { + // TODO/CHECK: should we really inherit? Yes, we should. + $this->environment = new json('config/environment.json', true, true, $this->getAppstack()); + } + return $this->environment; + } - if(!$fs->fileAvailable($file)) { - throw new exception('DEPLOYMENT_CONFIG_FILE_DOES_NOT_EXIST', exception::$ERRORLEVEL_FATAL, $file); + /** + * get app's appstack + * @return array + * @throws ReflectionException + * @throws exception + */ + public function getAppstack(): array + { + if (!$this->foreignAppstack) { + $this->foreignAppstack = app::makeForeignAppstack($this->getVendor(), $this->getApp()); + } + return $this->foreignAppstack; } - $config = new \codename\core\config\json($file); - return new self($vendor, $app, $deploymentName, $config); - } + /** + * current vendor + * @return string [description] + */ + public function getVendor(): string + { + return $this->vendor; + } + /** + * current app + * @return string [description] + */ + public function getApp(): string + { + return $this->app; + } + /** + * runs the deployment process + * @return deploymentresult [description] + */ + public function run(): deploymentresult + { + $deploymentResultData = [ + 'date' => new DateTime('now'), + 'taskresult' => [], + ]; + + foreach ($this->tasks as $taskName => $task) { + $result = $task->run(); + $deploymentResultData['taskresult'][$taskName] = $result; + } + + return new deploymentresult($deploymentResultData); + } } diff --git a/backend/class/deploy/deploymentresult.php b/backend/class/deploy/deploymentresult.php index 4ee94ae..0a730fd 100644 --- a/backend/class/deploy/deploymentresult.php +++ b/backend/class/deploy/deploymentresult.php @@ -1,16 +1,20 @@ get('taskresult'); - } +class deploymentresult extends config +{ + /** + * returns taskresult + * @return taskresult[] + */ + public function getTaskResults(): array + { + return $this->get('taskresult'); + } } diff --git a/backend/class/deploy/task.php b/backend/class/deploy/task.php index 36b8684..271ed45 100644 --- a/backend/class/deploy/task.php +++ b/backend/class/deploy/task.php @@ -1,4 +1,5 @@ deploymentInstance = $deploymentInstance; - $this->config = $config; - $this->handleConfig(); - } - - /** - * [handleConfig description] - * @return [type] [description] - */ - protected function handleConfig() { - // Do stuff with me. Override me. - } - - /** - * returns the current deployment instance - * @return deployment [description] - */ - public function getDeploymentInstance() : deployment { - return $this->deploymentInstance; - } - - /** - * [run description] - * @return taskresult [description] - */ - public abstract function run() : taskresult; - +abstract class task +{ + /** + * the task's configuration + * @var null|config + */ + protected ?config $config = null; + + /** + * the current calling deployment instance + * @var null|deployment + */ + protected ?deployment $deploymentInstance = null; + + /** + * Initialize a new, pre-configured task object + * @param deployment $deploymentInstance [current deployment instance (parent)] + * @param string $name [tasks's real configuration] + * @param config $config [tasks configuration ('config' key)] + */ + public function __construct(deployment $deploymentInstance, string $name, config $config) + { + $this->deploymentInstance = $deploymentInstance; + $this->config = $config; + $this->handleConfig(); + } + + /** + * [handleConfig description] + * @return void [type] [description] + */ + protected function handleConfig(): void + { + // Do stuff with me. Override me. + } + + /** + * returns the current deployment instance + * @return deployment [description] + */ + public function getDeploymentInstance(): deployment + { + return $this->deploymentInstance; + } + + /** + * [run description] + * @return taskresult [description] + */ + abstract public function run(): taskresult; } diff --git a/backend/class/deploy/task/client.php b/backend/class/deploy/task/client.php index a579c20..90335c8 100644 --- a/backend/class/deploy/task/client.php +++ b/backend/class/deploy/task/client.php @@ -1,35 +1,41 @@ getClientObjectTypeName()); - $dbValueObjectidentifier = new \codename\core\value\text\objectidentifier($clientName); - return app::getForeignClient( - $this->getDeploymentInstance()->getVirtualEnvironment(), - $dbValueObjecttype, - $dbValueObjectidentifier, - false - ); - } +abstract class client extends task +{ + /** + * [getClientInstance description] + * @param string $clientName [description] + * @return object + * @throws ReflectionException + * @throws exception + */ + protected function getClientInstance(string $clientName): object + { + $dbValueObjecttype = new objecttype($this->getClientObjectTypeName()); + $dbValueObjectidentifier = new objectidentifier($clientName); + return app::getForeignClient( + $this->getDeploymentInstance()->getVirtualEnvironment(), + $dbValueObjecttype, + $dbValueObjectidentifier, + false + ); + } + /** + * returns a name of an object type for getting the client instance + * @return string + */ + abstract protected function getClientObjectTypeName(): string; } diff --git a/backend/class/deploy/task/client/cache.php b/backend/class/deploy/task/client/cache.php index 0a86532..d33fe1f 100644 --- a/backend/class/deploy/task/client/cache.php +++ b/backend/class/deploy/task/client/cache.php @@ -1,40 +1,51 @@ cacheIdentifier = $this->config->get('identifier'); - } +abstract class cache extends client +{ + /** + * cache identifier + * @var string + */ + protected string $cacheIdentifier; - /** - * returns the cache instance - * @return \codename\core\cache [description] - */ - protected function getCache() : \codename\core\cache { - return $this->getClientInstance($this->cacheIdentifier); - } + /** + * {@inheritDoc} + */ + protected function handleConfig(): void + { + parent::handleConfig(); + $this->cacheIdentifier = $this->config->get('identifier'); + } - /** - * @inheritDoc - */ - protected function getClientObjectTypeName(): string - { - return 'cache'; - } + /** + * returns the cache instance + * @return \codename\core\cache [description] + * @throws ReflectionException + * @throws exception + */ + protected function getCache(): \codename\core\cache + { + $object = $this->getClientInstance($this->cacheIdentifier); + if ($object instanceof \codename\core\cache) { + return $object; + } + throw new exception('EXCEPTION_GETCACHE_WRONG_OBJECT', exception::$ERRORLEVEL_FATAL); + } + /** + * {@inheritDoc} + */ + protected function getClientObjectTypeName(): string + { + return 'cache'; + } } diff --git a/backend/class/deploy/task/client/cache/flush.php b/backend/class/deploy/task/client/cache/flush.php index c14d589..f7e764d 100644 --- a/backend/class/deploy/task/client/cache/flush.php +++ b/backend/class/deploy/task/client/cache/flush.php @@ -1,58 +1,65 @@ flushConfig = $this->config->get('flush'); - } - - /** - * flush configuration - * @var [type] - */ - protected $flushConfig = []; - - /** - * @inheritDoc - */ - public function run() : taskresult - { - $cacheInstance = $this->getCache(); - - $results = []; - - foreach($this->flushConfig as $flush) { - if($flush['all'] ?? false) { - $success = $cacheInstance->flush(); - $results[] = chr(9)."[".($success ? 'SUCCESS' : 'FAIL')."]" . " Flush all cache items."; - } else { - $cacheGroup = $flush['cache_group']; - $cacheKey = $flush['cache_key'] ?? null; - if($cacheKey) { - $success = $cacheInstance->clearKey($cacheGroup, $cacheKey); - $results[] = chr(9)."[".($success ? 'SUCCESS' : 'FAIL')."]" . " '{$cacheGroup}_{$cacheKey}' (specific key)"; - } else { - $success = $cacheInstance->clearGroup($cacheGroup); - $results[] = chr(9)."[".($success ? 'SUCCESS' : 'FAIL')."]" . " '{$cacheGroup}_*' (whole group)"; +class flush extends cache +{ + /** + * flush configuration + * @var array + */ + protected array $flushConfig = []; + + /** + * {@inheritDoc} + * @return taskresult + * @throws ReflectionException + * @throws exception + */ + public function run(): taskresult + { + $cacheInstance = $this->getCache(); + + $results = []; + + foreach ($this->flushConfig as $flush) { + if ($flush['all'] ?? false) { + $success = $cacheInstance->flush(); + $results[] = chr(9) . "[" . ($success ? 'SUCCESS' : 'FAIL') . "]" . " Flush all cache items."; + } else { + $cacheGroup = $flush['cache_group']; + $cacheKey = $flush['cache_key'] ?? null; + if ($cacheKey) { + $success = $cacheInstance->clearKey($cacheGroup, $cacheKey); + $results[] = chr(9) . "[" . ($success ? 'SUCCESS' : 'FAIL') . "]" . " '{$cacheGroup}_$cacheKey' (specific key)"; + } else { + $success = $cacheInstance->clearGroup($cacheGroup); + $results[] = chr(9) . "[" . ($success ? 'SUCCESS' : 'FAIL') . "]" . " '{$cacheGroup}_*' (whole group)"; + } + } } - } - } - $resultsAsText = implode(chr(10), $results); + $resultsAsText = implode(chr(10), $results); + + return new taskresult\text([ + 'text' => "Flushed cache '$this->cacheIdentifier':" . chr(10) . $resultsAsText, + ]); + } - return new taskresult\text([ - 'text' => "Flushed cache '{$this->cacheIdentifier}':".chr(10).$resultsAsText - ]); - } + /** + * {@inheritDoc} + */ + protected function handleConfig(): void + { + parent::handleConfig(); + $this->flushConfig = $this->config->get('flush'); + } } diff --git a/backend/class/deploy/task/dbdoc.php b/backend/class/deploy/task/dbdoc.php index 8634d82..50c6a65 100644 --- a/backend/class/deploy/task/dbdoc.php +++ b/backend/class/deploy/task/dbdoc.php @@ -1,95 +1,91 @@ not a dryrun, applies changes - * false => dryrun, more or less a test - * @var bool - */ - protected $executionFlag = false; - - /** - * to-be-executed task types as int ids - * @see \codename\architect\dbdoc\task::TASK_TYPES - * @var int[] - */ - protected $executeTaskTypes = []; - - /** - * @inheritDoc - */ - protected function handleConfig() - { - parent::handleConfig(); - $this->executionFlag = $this->config->get('executionFlag') ?? false; - - // map string task types to int codes - $executeTaskTypes = []; - foreach($this->config->get('executeTaskTypes') as $typeName) { - foreach(\codename\architect\dbdoc\task::TASK_TYPES as $taskTypeInt => $taskTypeName) { - if($typeName === $taskTypeName) { - $executeTaskTypes[] = $taskTypeInt; +class dbdoc extends \codename\architect\deploy\task +{ + /** + * whether DBDoc executes tasks + * true => not a dryrun, applies changes + * false => dryrun, more or less a test + * @var bool + */ + protected bool $executionFlag = false; + + /** + * to-be-executed task types as int ids + * @see task::TASK_TYPES + * @var int[] + */ + protected array $executeTaskTypes = []; + + /** + * {@inheritDoc} + */ + public function run(): taskresult + { + $executionFlagString = var_export($this->executionFlag, true); + $executeTaskTypesString = implode(', ', $this->executeTaskTypes); + $executeTaskTypeNamesString = implode(', ', $this->config->get('executeTaskTypes')); + + try { + // build a new dbdoc instance + $dbdoc = new \codename\architect\dbdoc\dbdoc( + $this->getDeploymentInstance()->getApp(), + $this->getDeploymentInstance()->getVendor() + ); + + // run it with params + $res = $dbdoc->run($this->executionFlag, $this->executeTaskTypes); + + // handle result + $textResult = "Dbdoc execution success with executionFlag: $executionFlagString and task types: [ $executeTaskTypesString ] ([ $executeTaskTypeNamesString ])"; + + // if verbose, additionally export the dbdoc results + if ($this->config->get('verbose')) { + $textResult .= print_r($res, true); + } + } catch (\Exception $e) { + // prepare error output + $textResult = "Dbdoc exception: " . $e->getCode() . ' ' . $e->getMessage() . " using executionFlag: $executionFlagString and task types: [ $executeTaskTypesString ] ([ $executeTaskTypeNamesString ])"; + if ($e instanceof exception) { + $textResult .= print_r($e->info, true); + } + + if ($this->config->get('verbose')) { + $textResult .= print_r($e->getTrace(), true); + } } - } - } - $this->executeTaskTypes = $executeTaskTypes; - } - - /** - * @inheritDoc - */ - public function run(): \codename\architect\deploy\taskresult - { - $executionFlagString = var_export($this->executionFlag, true); - $executeTaskTypesString = implode(', ', $this->executeTaskTypes); - $executeTaskTypeNamesString = implode(', ', $this->config->get('executeTaskTypes')); - - try { - - // build a new dbdoc instance - $dbdoc = new \codename\architect\dbdoc\dbdoc( - $this->getDeploymentInstance()->getApp(), - $this->getDeploymentInstance()->getVendor() - ); - - // run it with params - $res = $dbdoc->run($this->executionFlag, $this->executeTaskTypes); - - // handle result - $textResult = "Dbdoc execution success with executionFlag: {$executionFlagString} and task types: [ {$executeTaskTypesString} ] ([ {$executeTaskTypeNamesString} ])"; - - // if verbose, additionally export the dbdoc results - if($this->config->get('verbose')) { - $textResult .= print_r($res, true); - } - - } catch (\Exception $e) { - - // prepare error output - $textResult = "Dbdoc exception: " . $e->getCode() . ' ' . $e->getMessage() . " using executionFlag: {$executionFlagString} and task types: [ {$executeTaskTypesString} ] ([ {$executeTaskTypeNamesString} ])"; - if($e instanceof exception) { - $textResult .= print_r($e->info, true); - } - - if($this->config->get('verbose')) { - $textResult .= print_r($e->getTrace(), true); - } + return new taskresult\text([ + 'text' => $textResult, + ]); } - return new taskresult\text([ - 'text' => $textResult - ]); - } - + /** + * {@inheritDoc} + */ + protected function handleConfig(): void + { + parent::handleConfig(); + $this->executionFlag = $this->config->get('executionFlag') ?? false; + + // map string task types to int codes + $executeTaskTypes = []; + foreach ($this->config->get('executeTaskTypes') as $typeName) { + foreach (task::TASK_TYPES as $taskTypeInt => $taskTypeName) { + if ($typeName === $taskTypeName) { + $executeTaskTypes[] = $taskTypeInt; + } + } + } + $this->executeTaskTypes = $executeTaskTypes; + } } diff --git a/backend/class/deploy/task/dummy.php b/backend/class/deploy/task/dummy.php index 7f3e61b..f8416e7 100644 --- a/backend/class/deploy/task/dummy.php +++ b/backend/class/deploy/task/dummy.php @@ -1,23 +1,22 @@ 'Success!' - ]); - } - - +class dummy extends task +{ + /** + * {@inheritDoc} + */ + public function run(): taskresult + { + return new taskresult\text([ + 'text' => 'Success!', + ]); + } } diff --git a/backend/class/deploy/task/model.php b/backend/class/deploy/task/model.php index 7a2acad..12a387b 100644 --- a/backend/class/deploy/task/model.php +++ b/backend/class/deploy/task/model.php @@ -1,68 +1,73 @@ model = $this->config->get('model'); - $this->schema = $this->config->get('schema'); - } - - /** - * [getModelInstance description] - * @return \codename\core\model [description] - */ - /** - * [getModelInstance description] - * @param string|null $schemaName [description] - * @param string|null $modelName [description] - * @return \codename\core\model [description] - */ - protected function getModelInstance(string $schemaName = null, string $modelName = null) : \codename\core\model { - if(!$schemaName) { - $schemaName = $this->schema; - } - if(!$modelName) { - $modelName = $this->model; + /** + * {@inheritDoc} + */ + protected function handleConfig(): void + { + parent::handleConfig(); + $this->model = $this->config->get('model'); + $this->schema = $this->config->get('schema'); } - $useAppstack = $this->getDeploymentInstance()->getAppstack(); - $modelconfig = (new \codename\architect\config\json\virtualAppstack("config/model/" . $schemaName . '_' . $modelName . '.json', true, true, $useAppstack))->get(); - $modelconfig['appstack'] = $useAppstack; - $model = new \codename\architect\model\schematic\sql\dynamic($modelconfig, function(string $connection, bool $storeConnection = false) { - $dbValueObjecttype = new \codename\core\value\text\objecttype('database'); - $dbValueObjectidentifier = new \codename\core\value\text\objectidentifier($connection); - return app::getForeignClient( - $this->getDeploymentInstance()->getVirtualEnvironment(), - $dbValueObjecttype, - $dbValueObjectidentifier, - $storeConnection); - }); - $model->setConfig(null, $schemaName, $modelName); - return $model; - } + /** + * [getModelInstance description] + * @param string|null $schemaName [description] + * @param string|null $modelName [description] + * @return \codename\core\model [description] + * @throws ReflectionException + * @throws exception + */ + protected function getModelInstance(string $schemaName = null, string $modelName = null): \codename\core\model + { + if (!$schemaName) { + $schemaName = $this->schema; + } + if (!$modelName) { + $modelName = $this->model; + } + $useAppstack = $this->getDeploymentInstance()->getAppstack(); + $modelconfig = (new virtualAppstack("config/model/" . $schemaName . '_' . $modelName . '.json', true, true, $useAppstack))->get(); + $modelconfig['appstack'] = $useAppstack; + $model = new dynamic($modelconfig, function (string $connection, bool $storeConnection = false) { + $dbValueObjecttype = new objecttype('database'); + $dbValueObjectidentifier = new objectidentifier($connection); + return app::getForeignClient( + $this->getDeploymentInstance()->getVirtualEnvironment(), + $dbValueObjecttype, + $dbValueObjectidentifier, + $storeConnection + ); + }); + $model->setConfig(null, $schemaName, $modelName); + return $model; + } } diff --git a/backend/class/deploy/task/model/entry.php b/backend/class/deploy/task/model/entry.php index 58503e0..b7130fe 100644 --- a/backend/class/deploy/task/model/entry.php +++ b/backend/class/deploy/task/model/entry.php @@ -1,112 +1,112 @@ data = $this->config->get('data'); - } - /** - * @inheritDoc - */ - public function run(): \codename\architect\deploy\taskresult - { - $model = $this->getModelInstance(); - - $normalizedData = $model->normalizeData($this->data); +class entry extends model +{ + /** + * updatable data + * @var null|array + */ + protected ?array $data = null; - $model->validate($normalizedData); + /** + * {@inheritDoc} + * @return taskresult + * @throws ReflectionException + * @throws exception + */ + public function run(): taskresult + { + $model = $this->getModelInstance(); - $text = ''; + $normalizedData = $model->normalizeData($this->data); - if(count($errors = $model->getErrors()) > 0) { - $text = "Model '{$model->getIdentifier()}' data validation error: " . print_r($errors, true); - } else { + $model->validate($normalizedData); - // check for PKEY value or uniques - if($normalizedData[$model->getPrimarykey()] ?? false) { - - // we have a primary key - update the whole dataset - $model->save($normalizedData); - $text = "Model '{$model->getIdentifier()}' saved via PKEY"; - - } else { - if($model->getConfig()->get('unique')) { - $filtersAdded = false; - foreach($model->getConfig()->get('unique') as $uniqueKey) { - if(is_array($uniqueKey)) { - // multiple keys, combined unique key - $filters = []; - foreach($uniqueKey as $key) { - if($normalizedData[$key] ?? false) { - $filters[] = [ 'field' => $key, 'operator' => '=', 'value' => $normalizedData[$key]]; + if (count($errors = $model->getErrors()) > 0) { + $text = "Model '{$model->getIdentifier()}' data validation error: " . print_r($errors, true); + } elseif ($normalizedData[$model->getPrimaryKey()] ?? false) { + // we have a primary key - update the whole dataset + $model->save($normalizedData); + $text = "Model '{$model->getIdentifier()}' saved via PKEY"; + } elseif ($model->getConfig()->get('unique')) { + $filtersAdded = false; + foreach ($model->getConfig()->get('unique') as $uniqueKey) { + if (is_array($uniqueKey)) { + // multiple keys, combined unique key + $filters = []; + foreach ($uniqueKey as $key) { + if ($normalizedData[$key] ?? false) { + $filters[] = ['field' => $key, 'operator' => '=', 'value' => $normalizedData[$key]]; + } else { + // irrelevant unique key, one value is null + $filters = []; + break; + } + } + if (count($filters) > 0) { + $filtersAdded = true; + $model->addFilterCollection($filters); + } } else { - // irrelevant unique key, one value is null - $filters = []; - break; + // single unique key field + $filtersAdded = true; + $model->addFilter($uniqueKey, $normalizedData[$uniqueKey]); } - } - if(count($filters) > 0) { - $filtersAdded = true; - $model->addFilterCollection($filters, 'AND'); - } - } else { - // single unique key field - $filtersAdded = true; - $model->addFilter($uniqueKey, $normalizedData[$uniqueKey]); } - } - if($filtersAdded) { - $res = $model->search()->getResult(); - if(count($res) === 1) { - // update using found PKEY - $normalizedData[$model->getPrimarykey()] = $res[0][$model->getPrimarykey()]; - $model->save($normalizedData); - $text = "Model '{$model->getIdentifier()}' saved via filter, updated"; - } else if(count($res) === 0) { - // - // NOTE/WARNING: - // if you have a PKEY or UNIQUE key constraint values differing - // (two datasets which could match the filter) - // we're trying to save here - // which will cause an error. - // - // insert - $model->save($normalizedData); - $text = "Model '{$model->getIdentifier()}' saved via filter, inserted/created."; + if ($filtersAdded) { + $res = $model->search()->getResult(); + if (count($res) === 1) { + // update using found PKEY + $normalizedData[$model->getPrimaryKey()] = $res[0][$model->getPrimaryKey()]; + $model->save($normalizedData); + $text = "Model '{$model->getIdentifier()}' saved via filter, updated"; + } elseif (count($res) === 0) { + // + // NOTE/WARNING: + // if you have a PKEY or UNIQUE key constraint values differing + // (two datasets which could match the filter) + // we're trying to save here + // which will cause an error. + // + // insert + $model->save($normalizedData); + $text = "Model '{$model->getIdentifier()}' saved via filter, inserted/created."; + } else { + // error - multiple results + throw new exception('EXCEPTION_TASK_MODEL_ENTRY_MULTIPLE_UNIQUE_KEY_RESULTS', exception::$ERRORLEVEL_ERROR, $res); + } } else { - // error - multiple results - throw new exception('EXCEPTION_TASK_MODEL_ENTRY_MULTIPLE_UNIQUE_KEY_RESULTS', exception::$ERRORLEVEL_ERROR, $res); + // no filterable fields as a base + throw new exception('EXCEPTION_TASK_MODEL_ENTRY_NO_FILTERABLE_KEYS', exception::$ERRORLEVEL_ERROR); } - } else { - // no filterable fields as a base - throw new exception('EXCEPTION_TASK_MODEL_ENTRY_NO_FILTERABLE_KEYS', exception::$ERRORLEVEL_ERROR, $res); - } - } else { - // error, not handleable: - // no PKEY and no unique-combination given - throw new exception('EXCEPTION_TASK_MODEL_ENTRY_NO_UNIQUE_OR_PRIMARY_KEYS_GIVEN_OR_AVAILABLE', exception::$ERRORLEVEL_ERROR); + // error, not handleable: + // no PKEY and no unique-combination given + throw new exception('EXCEPTION_TASK_MODEL_ENTRY_NO_UNIQUE_OR_PRIMARY_KEYS_GIVEN_OR_AVAILABLE', exception::$ERRORLEVEL_ERROR); } - } - } - return new taskresult\text([ - 'text' => $text - ]); - } + return new taskresult\text([ + 'text' => $text, + ]); + } + /** + * {@inheritDoc} + */ + protected function handleConfig(): void + { + parent::handleConfig(); + $this->data = $this->config->get('data'); + } } diff --git a/backend/class/deploy/task/model/filter.php b/backend/class/deploy/task/model/filter.php index 160f449..2f2347e 100644 --- a/backend/class/deploy/task/model/filter.php +++ b/backend/class/deploy/task/model/filter.php @@ -1,93 +1,95 @@ filters = $this->config->get('filter') ?? null; - $this->filtercollections = $this->config->get('filtercollection') ?? null; - } + /** + * {@inheritDoc} + */ + protected function handleConfig(): void + { + parent::handleConfig(); + $this->filters = $this->config->get('filter') ?? null; + $this->filtercollections = $this->config->get('filtercollection') ?? null; + } - /** - * returns a prepared model instance (with filters and stuff) - * @return \codename\core\model [description] - */ - protected function getPreparedModel() : \codename\core\model { - $model = $this->getModelInstance(); + /** + * returns a prepared model instance (with filters and stuff) + * @return model [description] + * @throws ReflectionException + * @throws exception + */ + protected function getPreparedModel(): model + { + $model = $this->getModelInstance(); - $filtersApplied = false; - if($this->filters) { - $filtersApplied = true; - foreach($this->filters as $filter) { - $filterValue = $filter['value']; - if($filter['eval'] ?? false) { - if($filter['value']['function'] ?? false) { - if(is_callable($filter['value']['function'])) { - $filterValue = call_user_func($filter['value']['function']); // TODO: parameters? - } else { - throw new exception('EXCEPTION_TASK_MODEL_FILTER_VALUE_EVAL_INVALID', exception::$ERRORLEVEL_ERROR, $filter['value']['function']); + $filtersApplied = false; + if ($this->filters) { + $filtersApplied = true; + foreach ($this->filters as $filter) { + $filterValue = $filter['value']; + if ($filter['eval'] ?? false) { + if ($filter['value']['function'] ?? false) { + if (is_callable($filter['value']['function'])) { + $filterValue = call_user_func($filter['value']['function']); // TODO: parameters? + } else { + throw new exception('EXCEPTION_TASK_MODEL_FILTER_VALUE_EVAL_INVALID', exception::$ERRORLEVEL_ERROR, $filter['value']['function']); + } + } else { + throw new exception('EXCEPTION_TASK_MODEL_FILTER_VALUE_FUNCTION_NOT_SET', exception::$ERRORLEVEL_ERROR, $filter['value']); + } + } + $model->addDefaultFilter($filter['field'], $filterValue, $filter['operator'], $filter['conjunction'] ?? null); } - } else { - throw new exception('EXCEPTION_TASK_MODEL_FILTER_VALUE_FUNCTION_NOT_SET', exception::$ERRORLEVEL_ERROR, $filter['value']); - } } - $model->addDefaultfilter($filter['field'], $filterValue, $filter['operator'], $filter['conjunction'] ?? null); - } - } - if($this->filtercollections) { - $filtersApplied = true; - foreach($this->filtercollections as $filtercollection) { + if ($this->filtercollections) { + $filtersApplied = true; + foreach ($this->filtercollections as $filtercollection) { + // evaluate filters + $filters = []; + foreach ($filtercollection['filters'] as $filter) { + $filterValue = $filter['value']; + if ($filter['eval'] ?? false) { + if ($filter['value']['function'] ?? false) { + if (is_callable($filter['value']['function'])) { + $filterValue = call_user_func($filter['value']['function']); // TODO: parameters? + } else { + throw new exception('EXCEPTION_TASK_MODEL_FILTERCOLLECTION_FILTER_VALUE_EVAL_INVALID', exception::$ERRORLEVEL_ERROR, $filter['value']['function']); + } + } else { + throw new exception('EXCEPTION_TASK_MODEL_FILTERCOLLECTION_FILTER_VALUE_FUNCTION_NOT_SET', exception::$ERRORLEVEL_ERROR, $filter['value']); + } + } + $filters[] = ['field' => $filter['field'], 'operator' => $filter['operator'], 'value' => $filterValue]; + } - // evaluate filters - $filters = []; - foreach($filtercollection['filters'] as $filter) { - $filterValue = $filter['value']; - if($filter['eval'] ?? false) { - if($filter['value']['function'] ?? false) { - if(is_callable($filter['value']['function'])) { - $filterValue = call_user_func($filter['value']['function']); // TODO: parameters? - } else { - throw new exception('EXCEPTION_TASK_MODEL_FILTERCOLLECTION_FILTER_VALUE_EVAL_INVALID', exception::$ERRORLEVEL_ERROR, $filter['value']['function']); - } - } else { - throw new exception('EXCEPTION_TASK_MODEL_FILTERCOLLECTION_FILTER_VALUE_FUNCTION_NOT_SET', exception::$ERRORLEVEL_ERROR, $filter['value']); + $model->addDefaultFilterCollection($filters, $filtercollection['group_operator'] ?? 'AND', $filtercollection['group_name'] ?? 'default', $filtercollection['conjunction'] ?? 'AND'); } - } - $filters[] = [ 'field' => $filter['field'], 'operator' => $filter['operator'], 'value' => $filterValue ]; } - - $model->addDefaultFilterCollection($filters, $filtercollection['group_operator'] ?? 'AND', $filtercollection['group_name'] ?? 'default', $filtercollection['conjunction'] ?? 'AND'); - } - } - if(!$filtersApplied) { - throw new exception('EXCEPTION_TASK_MODEL_FILTER_INVALID', exception::$ERRORLEVEL_ERROR); + if (!$filtersApplied) { + throw new exception('EXCEPTION_TASK_MODEL_FILTER_INVALID', exception::$ERRORLEVEL_ERROR); + } + return $model; } - return $model; - } - } diff --git a/backend/class/deploy/task/model/filter/update.php b/backend/class/deploy/task/model/filter/update.php index 796072c..b50d441 100644 --- a/backend/class/deploy/task/model/filter/update.php +++ b/backend/class/deploy/task/model/filter/update.php @@ -1,68 +1,69 @@ data = $this->config->get('data'); - } - - /** - * @inheritDoc - */ - public function run(): \codename\architect\deploy\taskresult - { - $model = $this->getPreparedModel(); - - $normalizedData = $model->normalizeData($this->data); - - // - // TODO: we might make sure there's no PKEY or unique key value inside the dataset - // - - if($this->config->get('validate') ?? true) { - $model->validate($normalizedData); +class update extends filter +{ + /** + * updatable data + * @var null|array + */ + protected ?array $data = null; + + /** + * {@inheritDoc} + * @return taskresult + * @throws ReflectionException + * @throws exception + */ + public function run(): taskresult + { + $model = $this->getPreparedModel(); + + $normalizedData = $model->normalizeData($this->data); + + // + // TODO: we might make sure there's no PKEY or unique key value inside the dataset + // + + if ($this->config->get('validate') ?? true) { + $model->validate($normalizedData); + } + + if (count($errors = $model->getErrors()) > 0) { + $text = "Model '{$model->getIdentifier()}' data validation error: " . print_r($errors, true); + } else { + $filterQueryComponents = $model->getFilterQueryComponents(); + + // perform the update + $model->update($normalizedData); + + $text = "Model '{$model->getIdentifier()}' mass dataset update using: " . print_r($normalizedData, true); + + if ($this->config->get('verbose')) { + $text .= "and filters: " . print_r($filterQueryComponents, true); + } + } + + return new taskresult\text([ + 'text' => $text, + ]); } - $text = ''; - - if(count($errors = $model->getErrors()) > 0) { - $text = "Model '{$model->getIdentifier()}' data validation error: " . print_r($errors, true); - } else { - - $filterQueryComponents = $model->getFilterQueryComponents(); - - // perform the update - $model->update($normalizedData); - - $text = "Model '{$model->getIdentifier()}' mass dataset update using: " . print_r($normalizedData, true); - - if($this->config->get('verbose')) { - $text .= "and filters: " . print_r($filterQueryComponents, true); - } + /** + * {@inheritDoc} + */ + protected function handleConfig(): void + { + parent::handleConfig(); + $this->data = $this->config->get('data'); } - - return new taskresult\text([ - 'text' => $text - ]); - - } } diff --git a/backend/class/deploy/task/model/migrate.php b/backend/class/deploy/task/model/migrate.php index 2ff0926..a7a80b4 100644 --- a/backend/class/deploy/task/model/migrate.php +++ b/backend/class/deploy/task/model/migrate.php @@ -1,184 +1,190 @@ filters = $this->config->get('filter') ?? null; - $this->filtercollections = $this->config->get('filtercollection') ?? null; - $this->targetModel = $this->config->get('target>model'); - $this->targetSchema = $this->config->get('target>schema'); - $this->map = $this->config->get('map'); - $this->updateForeign = $this->config->get('update_foreign'); - } - - /** - * target model name - * @var string - */ - protected $targetModel = null; - - /** - * target schema - * @var string - */ - protected $targetSchema = null; - - /** - * list of $sourceModelField => $targetModelField maps - * @var array - */ - protected $map = []; - - /** - * foreign key update config in source model - * list of foreign key names - * @var array - */ - protected $updateForeign = null; - - /** - * @inheritDoc - */ - public function run() : \codename\architect\deploy\taskresult - { - $sourceModel = $this->getModelInstance($this->schema, $this->model); - $targetModel = $this->getModelInstance($this->targetSchema, $this->targetModel); - - - // hide all fields not necessary. - $sourceModel->hideAllFields(); - - // add pkey anyways - $sourceModel->addField($sourceModel->getPrimarykey()); - - foreach($this->map as $sourceModelField => $targetModelField) { - $sourceModel->addField($sourceModelField); +use codename\core\transaction; +use ReflectionException; + +class migrate extends model +{ + /** + * target model name + * @var null|string + */ + protected ?string $targetModel = null; + /** + * target schema + * @var null|string + */ + protected ?string $targetSchema = null; + /** + * list of $sourceModelField => $targetModelField maps + * @var array + */ + protected array $map = []; + /** + * foreign key update config in source model + * list of foreign key names + * @var null|array + */ + protected ?array $updateForeign = null; + /** + * @var array|mixed|null + */ + private mixed $filters; + /** + * @var array|mixed|null + */ + private mixed $filtercollections; + + /** + * {@inheritDoc} + */ + public function handleConfig(): void + { + parent::handleConfig(); + $this->filters = $this->config->get('filter') ?? null; + $this->filtercollections = $this->config->get('filtercollection') ?? null; + $this->targetModel = $this->config->get('target>model'); + $this->targetSchema = $this->config->get('target>schema'); + $this->map = $this->config->get('map'); + $this->updateForeign = $this->config->get('update_foreign'); } - $backMap = []; + /** + * {@inheritDoc} + * @return taskresult + * @throws ReflectionException + * @throws exception + */ + public function run(): taskresult + { + $sourceModel = $this->getModelInstance($this->schema, $this->model); + $targetModel = $this->getModelInstance($this->targetSchema, $this->targetModel); - // prepare foreign key maps - if($this->updateForeign) { - foreach($this->updateForeign as $foreignKey) { - $foreignKeyConfig = $sourceModel->getConfig()->get('foreign>'.$foreignKey); - if(!$foreignKeyConfig) { - throw new exception('EXCEPTION_TASK_MODEL_MIGRATE_FOREIGNKEY_INVALID', exception::$ERRORLEVEL_ERROR, $foreignKey); - } - if(($foreignKeyConfig['schema'] != $this->targetSchema) || ($foreignKeyConfig['model'] != $this->targetModel)) { - throw new exception('EXCEPTION_TASK_MODEL_MIGRATE_INVALID_BACKREFERENCE', exception::$ERRORLEVEL_ERROR, [ - 'foreign_config' => $foreignKeyConfig, - 'target_schema' => $this->targetSchema, - 'target_model' => $this->targetModel - ]); - } - $backMap[$foreignKey] = $foreignKeyConfig['key']; - } - } - if(count($backMap) === 0) { - $backMap = null; - } + // hide all fields not necessary. + $sourceModel->hideAllFields(); + // add pkey anyway + $sourceModel->addField($sourceModel->getPrimaryKey()); - // - // Apply filters - // - $filtersApplied = false; - if($this->filters) { - $filtersApplied = true; - foreach($this->filters as $filter) { - $filterValue = $filter['value']; - if($filter['eval'] ?? false) { - if($filter['value']['function'] ?? false) { - if(is_callable($filter['value']['function'])) { - $filterValue = call_user_func($filter['value']['function']); // TODO: parameters? - } else { - throw new exception('EXCEPTION_TASK_MODEL_FILTER_VALUE_EVAL_INVALID', exception::$ERRORLEVEL_ERROR, $filter['value']['function']); - } - } else { - throw new exception('EXCEPTION_TASK_MODEL_FILTER_VALUE_FUNCTION_NOT_SET', exception::$ERRORLEVEL_ERROR, $filter['value']); - } + foreach ($this->map as $sourceModelField => $targetModelField) { + $sourceModel->addField($sourceModelField); } - $sourceModel->addDefaultfilter($filter['field'], $filterValue, $filter['operator'], $filter['conjunction'] ?? null); - } - } - if($this->filtercollections) { - $filtersApplied = true; - foreach($this->filtercollections as $filtercollection) { - $sourceModel->addDefaultFilterCollection($filtercollection['filters'], $filtercollection['group_operator'] ?? 'AND', $filtercollection['group_name'] ?? 'default', $filtercollection['conjunction'] ?? 'AND'); - } - } - - $transaction = new \codename\core\transaction('migrate', [ $sourceModel, $targetModel ]); - - $runBatch = true; - $migratedCount = 0; - - while($runBatch === true) { - - $start = microtime(true); - if($this->config->get('batch_size')) { - echo("Batch Size: " . ($this->config->get('batch_size')) . ''.chr(10)); - $sourceModel->setLimit(intval($this->config->get('batch_size'))); - } - $result = $sourceModel->search()->getResult(); - $end = microtime(true); - echo("Query completed in " . ($end-$start) . ' ms'.chr(10)); - echo("Migrating...".chr(10)); + $backMap = []; + + // prepare foreign key maps + if ($this->updateForeign) { + foreach ($this->updateForeign as $foreignKey) { + $foreignKeyConfig = $sourceModel->getConfig()->get('foreign>' . $foreignKey); + if (!$foreignKeyConfig) { + throw new exception('EXCEPTION_TASK_MODEL_MIGRATE_FOREIGNKEY_INVALID', exception::$ERRORLEVEL_ERROR, $foreignKey); + } + if (($foreignKeyConfig['schema'] != $this->targetSchema) || ($foreignKeyConfig['model'] != $this->targetModel)) { + throw new exception('EXCEPTION_TASK_MODEL_MIGRATE_INVALID_BACKREFERENCE', exception::$ERRORLEVEL_ERROR, [ + 'foreign_config' => $foreignKeyConfig, + 'target_schema' => $this->targetSchema, + 'target_model' => $this->targetModel, + ]); + } + $backMap[$foreignKey] = $foreignKeyConfig['key']; + } + } - if(count($result) === 0) { - echo("No more migration candidates, breaking".chr(10)); - $runBatch = false; - break; - } + if (count($backMap) === 0) { + $backMap = null; + } - $transaction->start(); - foreach($result as $sourceDataset) { - $targetDataset = []; - foreach($this->map as $sourceModelField => $targetModelField) { - $targetDataset[$targetModelField] = $sourceDataset[$sourceModelField]; + // + // Apply filters + // + if ($this->filters) { + foreach ($this->filters as $filter) { + $filterValue = $filter['value']; + if ($filter['eval'] ?? false) { + if ($filter['value']['function'] ?? false) { + if (is_callable($filter['value']['function'])) { + $filterValue = call_user_func($filter['value']['function']); // TODO: parameters? + } else { + throw new exception('EXCEPTION_TASK_MODEL_FILTER_VALUE_EVAL_INVALID', exception::$ERRORLEVEL_ERROR, $filter['value']['function']); + } + } else { + throw new exception('EXCEPTION_TASK_MODEL_FILTER_VALUE_FUNCTION_NOT_SET', exception::$ERRORLEVEL_ERROR, $filter['value']); + } + } + $sourceModel->addDefaultFilter($filter['field'], $filterValue, $filter['operator'], $filter['conjunction'] ?? null); + } + } + if ($this->filtercollections) { + foreach ($this->filtercollections as $filtercollection) { + $sourceModel->addDefaultFilterCollection($filtercollection['filters'], $filtercollection['group_operator'] ?? 'AND', $filtercollection['group_name'] ?? 'default', $filtercollection['conjunction'] ?? 'AND'); + } } - $targetModel->save($targetDataset); - $lastInsertId = $targetModel->lastInsertId(); + $transaction = new transaction('migrate', [$sourceModel, $targetModel]); - if($backMap) { - $updateSourceDataset = [ - $sourceModel->getPrimarykey() => $sourceDataset[$sourceModel->getPrimarykey()] - ]; + $migratedCount = 0; - foreach($backMap as $sourceModelField => $targetModelField) { - if($targetModelField === $targetModel->getPrimarykey()) { - $updateSourceDataset[$sourceModelField] = $lastInsertId; - } else { - $updateSourceDataset[$sourceModelField] = $targetDataset[$targetModelField]; + while (true) { + $start = microtime(true); + if ($this->config->get('batch_size')) { + echo("Batch Size: " . ($this->config->get('batch_size')) . chr(10)); + $sourceModel->setLimit(intval($this->config->get('batch_size'))); } - } + $result = $sourceModel->search()->getResult(); + $end = microtime(true); - $sourceModel->save($updateSourceDataset); - } + echo("Query completed in " . ($end - $start) . ' ms' . chr(10)); + echo("Migrating..." . chr(10)); - // echo("Migrated [{$sourceDataset[$sourceModel->getPrimaryKey()]} => {$lastInsertId}]".chr(10)); - } + if (count($result) === 0) { + echo("No more migration candidates, breaking" . chr(10)); + break; + } - $migratedCount += count($result); + $transaction->start(); + + foreach ($result as $sourceDataset) { + $targetDataset = []; + foreach ($this->map as $sourceModelField => $targetModelField) { + $targetDataset[$targetModelField] = $sourceDataset[$sourceModelField]; + } + + $targetModel->save($targetDataset); + $lastInsertId = $targetModel->lastInsertId(); + + if ($backMap) { + $updateSourceDataset = [ + $sourceModel->getPrimaryKey() => $sourceDataset[$sourceModel->getPrimaryKey()], + ]; + + foreach ($backMap as $sourceModelField => $targetModelField) { + if ($targetModelField === $targetModel->getPrimaryKey()) { + $updateSourceDataset[$sourceModelField] = $lastInsertId; + } else { + $updateSourceDataset[$sourceModelField] = $targetDataset[$targetModelField]; + } + } + + $sourceModel->save($updateSourceDataset); + } + // echo("Migrated [{$sourceDataset[$sourceModel->getPrimaryKey()]} => {$lastInsertId}]".chr(10)); + } - $transaction->end(); - } + $migratedCount += count($result); - return new \codename\architect\deploy\taskresult\text([ - 'text' => "migrated count: ".$migratedCount - ]); - } + $transaction->end(); + } + return new text([ + 'text' => "migrated count: " . $migratedCount, + ]); + } } diff --git a/backend/class/deploy/task/model/query.php b/backend/class/deploy/task/model/query.php index c02b648..05962f7 100644 --- a/backend/class/deploy/task/model/query.php +++ b/backend/class/deploy/task/model/query.php @@ -1,22 +1,30 @@ getModelInstance()->setLimit(1)->search()->getResult(); - - return new \codename\architect\deploy\taskresult\text([ - 'text' => print_r($res, true) - ]); - } +class query extends model +{ + /** + * {@inheritDoc} + * @return taskresult + * @throws ReflectionException + * @throws exception + */ + public function run(): taskresult + { + $res = $this->getModelInstance()->setLimit(1)->search()->getResult(); + return new text([ + 'text' => print_r($res, true), + ]); + } } diff --git a/backend/class/deploy/taskresult.php b/backend/class/deploy/taskresult.php index 196d174..4480f30 100644 --- a/backend/class/deploy/taskresult.php +++ b/backend/class/deploy/taskresult.php @@ -1,14 +1,17 @@ get('text'); - } - +class text extends taskresult +{ + /** + * {@inheritDoc} + */ + public function formatAsString(): string + { + return $this->get('text'); + } } diff --git a/backend/class/model/schematic/sql/dynamic.php b/backend/class/model/schematic/sql/dynamic.php index da458bd..e3ac226 100644 --- a/backend/class/model/schematic/sql/dynamic.php +++ b/backend/class/model/schematic/sql/dynamic.php @@ -1,70 +1,86 @@ getDbCallback = $getDbCallback; - parent::__CONSTRUCT($modeldata); - } +class dynamic extends sql +{ + /** + * workaround to get db from another appstack + * @var callable + */ + protected $getDbCallback = null; - /** - * loads a new config file (uncached) - * @return \codename\core\config - */ - protected function loadConfig() : \codename\core\config { - if($this->modeldata->exists('appstack')) { - return new \codename\core\config\json('config/model/' . $this->schema . '_' . $this->table . '.json', true, false, $this->modeldata->get('appstack')); - } else { - return new \codename\core\config\json('config/model/' . $this->schema . '_' . $this->table . '.json', true); + /** + * @param array $modeldata + * @param callable $getDbCallback + */ + public function __construct(array $modeldata, callable $getDbCallback) + { + $this->getDbCallback = $getDbCallback; + parent::__construct($modeldata); } - } - - /** - * @inheritDoc - */ - public function setConfig(string $connection = null, string $schema, string $table) : \codename\core\model { - - $this->schema = $schema; - $this->table = $table; - if(!$this->config) { - $this->config = $this->loadConfig(); - } + /** + * @param string|null $connection + * @param string $schema + * @param string $table + * @return model + * @throws ReflectionException + * @throws exception + */ + public function setConfig(?string $connection, string $schema, string $table): model + { + $this->schema = $schema; + $this->table = $table; - // Connection now defined in model .json - if($this->config->exists("connection")) { - $connection = $this->config->get("connection"); - } else { - $connection = 'default'; - } + if (!$this->config) { + $this->config = $this->loadConfig(); + } - $getDbCallback = $this->getDbCallback; - $this->db = $getDbCallback($connection, $this->storeConnection); + // Connection now defined in model .json + if ($this->config->exists("connection")) { + $connection = $this->config->get("connection"); + } else { + $connection = 'default'; + } - return $this; - } + $getDbCallback = $this->getDbCallback; + $this->db = $getDbCallback($connection, $this->storeConnection); - /** - * workaround to get db from another appstack - * @var callable - */ - protected $getDbCallback = null; + return $this; + } - /** - * @inheritDoc - */ - protected function getType() : string - { - // TODO: make dynamic, based on ENV setting! - return 'mysql'; - } + /** + * loads a new config file (uncached) + * @return config + * @throws ReflectionException + * @throws exception + */ + protected function loadConfig(): config + { + if ($this->modeldata->exists('appstack')) { + return new json('config/model/' . $this->schema . '_' . $this->table . '.json', true, false, $this->modeldata->get('appstack')); + } else { + return new json('config/model/' . $this->schema . '_' . $this->table . '.json', true); + } + } + /** + * {@inheritDoc} + */ + protected function getType(): string + { + // TODO: make dynamic, based on ENV setting! + return 'mysql'; + } } diff --git a/backend/class/validator/number/tasktype.php b/backend/class/validator/number/tasktype.php index 9f5f032..7c2addf 100644 --- a/backend/class/validator/number/tasktype.php +++ b/backend/class/validator/number/tasktype.php @@ -1,31 +1,45 @@ errorstack->getErrors(); + } - if(!array_key_exists($value, task::TASK_TYPES)) { - $this->errorstack->addError('VALUE', 'TASK_TYPE_UNKNOWN', $value); - } + if (!array_key_exists($value, task::TASK_TYPES)) { + $this->errorstack->addError('VALUE', 'TASK_TYPE_UNKNOWN', $value); + } - return $this->errorstack->getErrors(); - } -} \ No newline at end of file + return $this->errorstack->getErrors(); + } +} diff --git a/composer.json b/composer.json index ec985c2..86e8097 100644 --- a/composer.json +++ b/composer.json @@ -1,17 +1,25 @@ { - "name": "codename/architect", - "description": "This is going to be the management application for database and application management.", - "type": "package", - "keywords": ["architect", "manager", "codename", "core"], - "authors": [ - { - "name": "Kevin Dargel", - "role": "Software Developer" - } - ], - "autoload": { - "psr-4": { - "codename\\architect\\": "backend/class/" - } + "name": "codename/architect", + "description": "This is going to be the management application for database and application management.", + "type": "package", + "keywords": [ + "architect", + "manager", + "codename", + "core" + ], + "authors": [ + { + "name": "Kevin Dargel", + "role": "Software Developer" } + ], + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "codename\\architect\\": "backend/class/" + } + } } diff --git a/config/app.json b/config/app.json index 5b4236c..08f33d0 100644 --- a/config/app.json +++ b/config/app.json @@ -1,42 +1,42 @@ { - "defaultcontext": "main", - "defaulttemplateengine" : "default", - "defaulttemplate": "blank", - "context": { - "main": { - "template" : "basic", - "defaultview": "default", - "templateengine": "default", - "view": { - "default": { - "public": true - }, - "listapps": { - "public": true - }, - "listmodels": { - "public": true - } - } - }, - "deployment": { - "template" : "basic", - "defaultview": "default", - "templateengine": "default", - "view": { - "default" : { - "public": true - }, - "apps": { - "public": true - }, - "tasks": { - "public": true - }, - "run": { - "public": true - } - } - } + "defaultcontext": "main", + "defaulttemplateengine": "default", + "defaulttemplate": "blank", + "context": { + "main": { + "template": "basic", + "defaultview": "default", + "templateengine": "default", + "view": { + "default": { + "public": true + }, + "listapps": { + "public": true + }, + "listmodels": { + "public": true + } + } + }, + "deployment": { + "template": "basic", + "defaultview": "default", + "templateengine": "default", + "view": { + "default": { + "public": true + }, + "apps": { + "public": true + }, + "tasks": { + "public": true + }, + "run": { + "public": true + } + } + } } } diff --git a/config/environment.json b/config/environment.json index 6e11d7d..8a7b386 100644 --- a/config/environment.json +++ b/config/environment.json @@ -1,168 +1,168 @@ { - "production" : { - "templateengine" : { - "default" : { - "__comment" : "twig config used for default output", - "driver" : "twig" + "production": { + "templateengine": { + "default": { + "__comment": "twig config used for default output", + "driver": "twig" }, - "cli" : { - "__comment" : "twig config used for cli-only output. forced in app class.", - "driver" : "twig", - "template_file_extension" : ".cli.twig" + "cli": { + "__comment": "twig config used for cli-only output. forced in app class.", + "driver": "twig", + "template_file_extension": ".cli.twig" } }, "filesystem": { - "local": { - "driver": "local" - } - }, - "cache" : { - "default" : { - "driver" : "devnull" + "local": { + "driver": "local" } }, - "session" : { - "default" : { - "driver" : "dummy" - } - }, - "log" : { + "cache": { + "default": { + "driver": "devnull" + } + }, + "session": { + "default": { + "driver": "dummy" + } + }, + "log": { "errormessage": { - "driver": "file", - "data": { - "name": "architect_error", - "minlevel" : -3 - } - }, + "driver": "file", + "data": { + "name": "architect_error", + "minlevel": -3 + } + }, "debug": { - "driver": "dummy", - "data": { - "name": "architect_debug", - "minlevel" : -2 - } - }, + "driver": "dummy", + "data": { + "name": "architect_debug", + "minlevel": -2 + } + }, "access": { - "driver": "dummy", - "data": { - "name": "architect_access", - "minlevel" : -5 - } - } + "driver": "dummy", + "data": { + "name": "architect_access", + "minlevel": -5 + } + } } }, "dev": { - "templateengine" : { - "default" : { - "__comment" : "twig config used for default output", - "driver" : "twig" + "templateengine": { + "default": { + "__comment": "twig config used for default output", + "driver": "twig" }, - "cli" : { - "__comment" : "twig config used for cli-only output. forced in app class.", - "driver" : "twig", - "template_file_extension" : ".cli.twig" + "cli": { + "__comment": "twig config used for cli-only output. forced in app class.", + "driver": "twig", + "template_file_extension": ".cli.twig" } }, "filesystem": { - "local": { - "driver": "local" - } - }, - "cache" : { - "default" : { - "driver" : "devnull" + "local": { + "driver": "local" + } + }, + "cache": { + "default": { + "driver": "devnull" } }, - "session" : { - "default" : { - "driver" : "dummy" - } - }, - "log" : { + "session": { + "default": { + "driver": "dummy" + } + }, + "log": { "errormessage": { - "driver": "file", - "data": { - "name": "architect_error", - "minlevel" : -3 - } - }, + "driver": "file", + "data": { + "name": "architect_error", + "minlevel": -3 + } + }, "debug": { - "driver": "file", - "data": { - "name": "architect_debug", - "minlevel" : -2 - } - }, + "driver": "file", + "data": { + "name": "architect_debug", + "minlevel": -2 + } + }, "access": { - "driver": "file", - "data": { - "name": "architect_access", - "minlevel" : -5 - } - }, - "query" : { "driver": "file", - "data": { - "name": "query", - "minlevel" : 0 - } + "data": { + "name": "architect_access", + "minlevel": -5 + } + }, + "query": { + "driver": "file", + "data": { + "name": "query", + "minlevel": 0 + } } } }, "dev_test": { - "templateengine" : { - "default" : { - "__comment" : "twig config used for default output", - "driver" : "twig" + "templateengine": { + "default": { + "__comment": "twig config used for default output", + "driver": "twig" }, - "cli" : { - "__comment" : "twig config used for cli-only output. forced in app class.", - "driver" : "twig", - "template_file_extension" : ".cli.twig" + "cli": { + "__comment": "twig config used for cli-only output. forced in app class.", + "driver": "twig", + "template_file_extension": ".cli.twig" } }, "filesystem": { - "local": { - "driver": "local" - } - }, - "cache" : { - "default" : { - "driver" : "devnull" + "local": { + "driver": "local" + } + }, + "cache": { + "default": { + "driver": "devnull" + } + }, + "session": { + "default": { + "driver": "dummy" } }, - "session" : { - "default" : { - "driver" : "dummy" - } - }, - "log" : { + "log": { "errormessage": { - "driver": "file", - "data": { - "name": "architect_error", - "minlevel" : -3 - } - }, + "driver": "file", + "data": { + "name": "architect_error", + "minlevel": -3 + } + }, "debug": { - "driver": "file", - "data": { - "name": "architect_debug", - "minlevel" : -2 - } - }, + "driver": "file", + "data": { + "name": "architect_debug", + "minlevel": -2 + } + }, "access": { - "driver": "file", - "data": { - "name": "architect_access", - "minlevel" : -5 - } - }, - "query" : { "driver": "file", - "data": { - "name": "query", - "minlevel" : 0 - } + "data": { + "name": "architect_access", + "minlevel": -5 + } + }, + "query": { + "driver": "file", + "data": { + "name": "query", + "minlevel": 0 + } } } } diff --git a/frontend/template/basic/template.twig b/frontend/template/basic/template.twig index 867b588..c8f0bca 100644 --- a/frontend/template/basic/template.twig +++ b/frontend/template/basic/template.twig @@ -1,10 +1,10 @@ - - - {{ title }} - - - {% include 'view/header/header' %} - {{ response.getData('content')|raw }} - + + + {{ title }} + + + {% include 'view/header/header' %} + {{ response.getData('content')|raw }} + diff --git a/frontend/view/deployment/run.cli.twig b/frontend/view/deployment/run.cli.twig index 88e764f..aaeca4c 100644 --- a/frontend/view/deployment/run.cli.twig +++ b/frontend/view/deployment/run.cli.twig @@ -2,7 +2,7 @@ DeploymentResult ---------------------------------- {% for taskName, taskResult in response.getData('deploymentresult').getTaskResults() %} -[TASK] {{ taskName }}: - {{ taskResult.formatAsString() | raw }} + [TASK] {{ taskName }}: + {{ taskResult.formatAsString() | raw }} {% endfor %} diff --git a/frontend/view/header/header_base.cli.twig b/frontend/view/header/header_base.cli.twig index 62a6711..13cf51a 100644 --- a/frontend/view/header/header_base.cli.twig +++ b/frontend/view/header/header_base.cli.twig @@ -1,12 +1,12 @@ - _ _ _ _ - /\ | | (_) | | | - / \ _ __ ___| |__ _| |_ ___ ___| |_ - / /\ \ | '__/ __| '_ \| | __/ _ \/ __| __| - / ____ \| | | (__| | | | | || __/ (__| |_ - /_/ \_\_| \___|_| |_|_|\__\___|\___|\__| +_ _ _ _ +/\ | | (_) | | | +/ \ _ __ ___| |__ _| |_ ___ ___| |_ +/ /\ \ | '__/ __| '_ \| | __/ _ \/ __| __| +/ ____ \| | | (__| | | | | || __/ (__| |_ +/_/ \_\_| \___|_| |_|_|\__\___|\___|\__| - Automatic deployment, the easy way. +Automatic deployment, the easy way. - for usage with - Core Framework-based Apps +for usage with +Core Framework-based Apps diff --git a/frontend/view/main/listapps.twig b/frontend/view/main/listapps.twig index 5f64f7c..6a6bd30 100644 --- a/frontend/view/main/listapps.twig +++ b/frontend/view/main/listapps.twig @@ -8,11 +8,13 @@ {% for app in response.getData('apps') %} - - - - - + + + + + {% endfor %}
{{ app.vendor }}{{ app.app }}Details
{{ app.vendor }}{{ app.app }} + Details +
\ No newline at end of file diff --git a/frontend/view/main/listmodels.cli.twig b/frontend/view/main/listmodels.cli.twig index ddf98c3..6727248 100644 --- a/frontend/view/main/listmodels.cli.twig +++ b/frontend/view/main/listmodels.cli.twig @@ -1,15 +1,15 @@ -Models in {{ request.getData('filter')['vendor']}}\{{ request.getData('filter')['app']}} +Models in {{ request.getData('filter')['vendor'] }}\{{ request.getData('filter')['app'] }} *** Build Tasks - Vendor: {{ request.getData('filter>vendor') }} - App: {{ request.getData('filter>app') }} +Vendor: {{ request.getData('filter>vendor') }} +App: {{ request.getData('filter>app') }} *** Actions: {% for tasktype_name, tasktype_value in response.getData('dbdoc_stats>available_task_types') %} -{{tasktype_name}}: {% for key,value in request.getData('filter') %}--filter[{{key}}]={{value}}{% endfor %}{# - #} --exec=1 --exec_tasks[]={{tasktype_value}} + {{ tasktype_name }}: {% for key,value in request.getData('filter') %}--filter[{{ key }}]={{ value }}{% endfor %}{# +#} --exec=1 --exec_tasks[]={{ tasktype_value }} {% endfor %} ---------- @@ -25,7 +25,7 @@ Models in {{ request.getData('filter')['vendor']}}\{{ request.getData('filter')[ *** Models -{{ response.getData('table')|raw}} +{{ response.getData('table')|raw }} ---------- diff --git a/frontend/view/main/listmodels.twig b/frontend/view/main/listmodels.twig index 22c38fa..8a0e15a 100644 --- a/frontend/view/main/listmodels.twig +++ b/frontend/view/main/listmodels.twig @@ -10,25 +10,30 @@ Back to App List
  • - Refresh + Refresh +

  • {% for tasktype_name, tasktype_value in response.getData('dbdoc_stats>available_task_types') %} -
  • - Execute {{tasktype_name}} -
  • +
  • + Execute {{ tasktype_name }} +
  • {% endfor %} @@ -37,23 +42,29 @@

    Available Tasks


    Models

    -{{ response.getData('table')|raw}} +{{ response.getData('table')|raw }}

    Statistics

    diff --git a/phpcs.xml b/phpcs.xml index 73b533d..8644693 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,21 +1,21 @@ - The default CoreFramework coding standard + The default CoreFramework coding standard - - - + + + - - + + - - + + \ No newline at end of file diff --git a/translation/de_DE/exception.json b/translation/de_DE/exception.json index dfa94dd..58e8f2f 100644 --- a/translation/de_DE/exception.json +++ b/translation/de_DE/exception.json @@ -1,4 +1,4 @@ { - "EXCEPTION_ARCHITECT_CONTEXT_MAIN_MISSING_FILTER_VENDOR" : "Kein Vendor definiert", - "EXCEPTION_ARCHITECT_CONTEXT_MAIN_MISSING_FILTER_APP" : "Keine App definiert" + "EXCEPTION_ARCHITECT_CONTEXT_MAIN_MISSING_FILTER_VENDOR": "Kein Vendor definiert", + "EXCEPTION_ARCHITECT_CONTEXT_MAIN_MISSING_FILTER_APP": "Keine App definiert" } \ No newline at end of file From b75d346fc8f568c1d53610e7da87fadd4cfdb348 Mon Sep 17 00:00:00 2001 From: Ralf Date: Fri, 1 Nov 2024 10:33:38 +0100 Subject: [PATCH 2/2] update php 8.1 > 8.3, dependencies, bugfixing and code cleanup --- README.md | 2 +- backend/class/app.php | 11 +++-- backend/class/config/json/virtualAppstack.php | 10 ++--- backend/class/context/deployment.php | 2 + backend/class/context/main.php | 6 +-- backend/class/dbdoc/dbdoc.php | 41 +++++++++---------- backend/class/dbdoc/plugin.php | 6 +-- backend/class/dbdoc/plugin/database.php | 2 +- backend/class/dbdoc/plugin/index.php | 2 +- backend/class/dbdoc/plugin/modelPrefix.php | 2 +- backend/class/dbdoc/plugin/primary.php | 6 +-- backend/class/dbdoc/plugin/sql/field.php | 38 ++++++++--------- backend/class/dbdoc/plugin/sql/fieldlist.php | 16 ++++---- backend/class/dbdoc/plugin/sql/foreign.php | 22 +++++----- backend/class/dbdoc/plugin/sql/fulltext.php | 22 +++++----- backend/class/dbdoc/plugin/sql/index.php | 24 +++++------ .../class/dbdoc/plugin/sql/mysql/field.php | 4 +- .../class/dbdoc/plugin/sql/mysql/foreign.php | 4 +- .../dbdoc/plugin/sql/mysql/permissions.php | 6 +-- .../class/dbdoc/plugin/sql/mysql/primary.php | 4 +- .../class/dbdoc/plugin/sql/mysql/table.php | 4 +- backend/class/dbdoc/plugin/sql/mysql/user.php | 16 ++++---- .../class/dbdoc/plugin/sql/permissions.php | 2 +- backend/class/dbdoc/plugin/sql/primary.php | 6 +-- backend/class/dbdoc/plugin/sql/schema.php | 2 +- .../class/dbdoc/plugin/sql/sqlite/field.php | 12 +++--- .../dbdoc/plugin/sql/sqlite/fieldlist.php | 30 +++++++------- .../class/dbdoc/plugin/sql/sqlite/index.php | 22 +++++----- .../class/dbdoc/plugin/sql/sqlite/primary.php | 6 +-- .../class/dbdoc/plugin/sql/sqlite/table.php | 16 ++++---- backend/class/dbdoc/plugin/sql/table.php | 4 +- backend/class/dbdoc/plugin/sql/unique.php | 16 ++++---- backend/class/dbdoc/plugin/sql/user.php | 2 +- backend/class/dbdoc/plugin/user.php | 2 +- backend/class/dbdoc/task.php | 14 +++---- backend/class/deploy/deployment.php | 4 +- backend/class/deploy/task.php | 6 +++ backend/class/deploy/task/client.php | 8 ++-- backend/class/deploy/task/dbdoc.php | 4 +- backend/class/deploy/task/model.php | 8 ++-- backend/class/deploy/task/model/entry.php | 2 + .../class/deploy/task/model/filter/update.php | 6 +++ backend/class/deploy/task/model/migrate.php | 4 +- backend/class/deploy/task/model/query.php | 2 + backend/class/model/schematic/sql/dynamic.php | 2 +- backend/class/validator/number/tasktype.php | 6 +-- composer.json | 2 +- 47 files changed, 228 insertions(+), 210 deletions(-) diff --git a/README.md b/README.md index 39af52c..6324d10 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This README would normally document whatever steps are necessary to get your app ### How do I get set up? ### -* Summary of set up +* Summary of setup * Configuration * Dependencies * Database configuration diff --git a/backend/class/app.php b/backend/class/app.php index fcd5100..5423594 100644 --- a/backend/class/app.php +++ b/backend/class/app.php @@ -47,13 +47,12 @@ public static function makeForeignAppstack(string $vendor, string $app): array * models[schema][model] = array( 'fields' => ... ) * @param string $filterByVendor * @param string $filterByApp - * @param string $model * @param array|null $useAppstack * @return array * @throws ReflectionException * @throws exception */ - public static function getModelConfigurations(string $filterByVendor = '', string $filterByApp = '', string $model = '', array $useAppstack = null): array + public static function getModelConfigurations(string $filterByVendor = '', string $filterByApp = '', array $useAppstack = null): array { $result = []; @@ -75,11 +74,11 @@ public static function getModelConfigurations(string $filterByVendor = '', strin } } - // array of vendor,app + // array of vendor, app $appdir = app::getHomedir($app['vendor'], $app['app']); $dir = $appdir . "config/model"; - // get all model json files, first: + // get all model JSON files, first: $files = app::getFilesystem()->dirList($dir); foreach ($files as $f) { @@ -88,7 +87,7 @@ public static function getModelConfigurations(string $filterByVendor = '', strin // check for .json extension $fileInfo = new SplFileInfo($file); if ($fileInfo->getExtension() === 'json') { - // get the model filename w/o extension + // get the model filename w/o an extension $modelName = $fileInfo->getBasename('.json'); // split: schema_model @@ -189,7 +188,7 @@ public static function getSiblingApps(): array } /** - * Returns the (maybe cached) client that is stored as "driver" in $identifier (app.json) for the given $type. + * Returns the (maybe cached) client stored as "driver" in $identifier (app.json) for the given $type. * @param environment $environment * @param objecttype $type * @param objectidentifier $identifier diff --git a/backend/class/config/json/virtualAppstack.php b/backend/class/config/json/virtualAppstack.php index fd33d3e..bb03236 100644 --- a/backend/class/config/json/virtualAppstack.php +++ b/backend/class/config/json/virtualAppstack.php @@ -7,7 +7,7 @@ use codename\core\exception; /** - * json config reader using a virtual or custom appstack + * JSON config reader using a virtual or custom appstack */ class virtualAppstack extends json { @@ -21,10 +21,10 @@ class virtualAppstack extends json * {@inheritDoc} */ public function __construct( - string $file, - bool $appstack = false, - bool $inherit = false, - array $useAppstack = null + string $file, + bool $appstack = false, + bool $inherit = false, + array $useAppstack = null ) { $this->useAppstack = $useAppstack; return parent::__construct($file, $appstack, $inherit, $useAppstack); diff --git a/backend/class/context/deployment.php b/backend/class/context/deployment.php index aa5a4e3..c35f865 100644 --- a/backend/class/context/deployment.php +++ b/backend/class/context/deployment.php @@ -4,6 +4,7 @@ use codename\core\context; use codename\core\exception; +use DateMalformedStringException; use ReflectionException; /** @@ -31,6 +32,7 @@ public function view_tasks(): void * run a given deployment configuration * @return void * @throws ReflectionException + * @throws DateMalformedStringException * @throws exception */ public function view_run(): void diff --git a/backend/class/context/main.php b/backend/class/context/main.php index a76bb09..2d8fc89 100644 --- a/backend/class/context/main.php +++ b/backend/class/context/main.php @@ -5,9 +5,9 @@ use codename\architect\app; use codename\architect\dbdoc\dbdoc; use codename\architect\dbdoc\task; -use codename\core\catchableException; use codename\core\context; use codename\core\exception; +use codename\core\exception\catchableException; use codename\core\ui\frontend\element\table; use ReflectionException; @@ -66,8 +66,8 @@ public function view_listmodels(): void $dbdoc = new dbdoc($app, $vendor); $stats = $dbdoc->run( - $this->getRequest()->getData('exec') == '1', - $exec_tasks + $this->getRequest()->getData('exec') == '1', + $exec_tasks ); // store dbdoc output diff --git a/backend/class/dbdoc/dbdoc.php b/backend/class/dbdoc/dbdoc.php index 0410909..5b3fd91 100644 --- a/backend/class/dbdoc/dbdoc.php +++ b/backend/class/dbdoc/dbdoc.php @@ -4,11 +4,11 @@ use codename\architect\app; use codename\architect\config\environment; -use codename\core\catchableException; use codename\core\config; use codename\core\config\json; use codename\core\errorstack; use codename\core\exception; +use codename\core\exception\catchableException; use ReflectionException; /** @@ -28,12 +28,12 @@ class dbdoc * * You'd call it ... *surprise, surprise* ... 'production'. * While this configuration should only provide _basic_ access to resources (like the database) - * e.g. only SELECT, UPDATE, JOIN, ... , DELETE (if, at all!) + * e.g., only SELECT, UPDATE, JOIN, ..., DELETE (if, at all!) * You have to have another set of credentials to be used for the deployment state * of the application. * * Therefore, you __have__ to provide a second configuration. - * For example, you could either provide the same credentials, if you're using + * For example, you could too provide the same credentials if you're using * ROOT access in production systems for standard DB access. * * Nah, you really shouldn't do that. @@ -43,28 +43,27 @@ class dbdoc * during deployment. * * We'd call it - * - * architect_production + * "architect_production" * * To sum it up, modify your environment.json to supply one more prefixed key * for each env-key used. We assume you're using "dev" and "production". * Therefore, you need to supply "architect_dev" and "architect_production". * * IMPORTANT HINT: - * Simply supply root credentials in architect_-prefixed configs. - * You don't have to create the credentials defined in the un-prefixed configs - * As the architect does this for you. + * Supply root credentials in architect_-prefixed configs. + * You don't have to create the credentials defined in the prefixed configs + * as the architect does this for you. * * Enjoy. * * @var string */ - protected const ARCHITECT_ENV_PREFIX = 'architect_'; + protected const string ARCHITECT_ENV_PREFIX = 'architect_'; /** * Exception thrown if we're missing a specific env config key (with the deployment-mode prefix) * @var string */ - public const EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG = 'EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG'; + public const string EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG = 'EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG'; /** * translate env config drivers to namespaced modeladapter * @var array @@ -148,7 +147,7 @@ public function init(): void $this->models = []; $foreignAppstack = app::makeForeignAppstack($this->vendor, $this->app); - $modelConfigurations = app::getModelConfigurations($this->vendor, $this->app, '', $foreignAppstack); + $modelConfigurations = app::getModelConfigurations($this->vendor, $this->app, $foreignAppstack); $modelList = []; foreach ($modelConfigurations as $schema => $models) { @@ -175,7 +174,7 @@ public function init(): void // check for existence if (!$this->environment->exists($prefixedEnvironmentName)) { - // this is needed. + // this is necessary. // warn user/admin we're missing an important configuration part. // throw new exception(self::EXCEPTION_ARCHITECT_MISSING_PREFIXED_ENVIRONMENT_CONFIG, exception::$ERRORLEVEL_FATAL, $prefixedEnvironmentName); } @@ -183,10 +182,10 @@ public function init(): void // initialize model adapters foreach ($this->models as &$m) { $modelAdapter = $this->getModelAdapter( - $m['schema'], - $m['model'], - $m['config'], - new environment($this->environment->get(), $prefixedEnvironmentName) + $m['schema'], + $m['model'], + $m['config'], + new environment($this->environment->get(), $prefixedEnvironmentName) ); if ($modelAdapter != null) { @@ -247,11 +246,11 @@ public function getModelAdapter(string $schema, string $model, array $config, en if (class_exists($class)) { return new $class( - $this, - $schema, - $model, - new config($config), - $env // prefixed environment name: e.g. architect_dev, see above + $this, + $schema, + $model, + new config($config), + $env // prefixed environment name: e.g., architect_dev, see above ); } diff --git a/backend/class/dbdoc/plugin.php b/backend/class/dbdoc/plugin.php index 0f6cb97..f46f865 100644 --- a/backend/class/dbdoc/plugin.php +++ b/backend/class/dbdoc/plugin.php @@ -12,17 +12,17 @@ abstract class plugin { /** - * event fired, if the comparison was successful + * event fired if the comparison was successful * @var null|event */ public ?event $onSuccess = null; /** - * event fired, if the comparison failed + * event fired if the comparison failed * @var null|event */ public ?event $onFail = null; /** - * event fired, if the comparison was interrupted (!) + * event fired if the comparison was interrupted (!) * @var null|event */ public ?event $onError = null; diff --git a/backend/class/dbdoc/plugin/database.php b/backend/class/dbdoc/plugin/database.php index cc9abd6..ee3d541 100644 --- a/backend/class/dbdoc/plugin/database.php +++ b/backend/class/dbdoc/plugin/database.php @@ -23,7 +23,7 @@ public function getDefinition(): mixed // backup env key $prevEnv = $environment->getEnvironmentKey(); - // change env key + // change an env key $environment->setEnvironmentKey($globalEnv); // get database name diff --git a/backend/class/dbdoc/plugin/index.php b/backend/class/dbdoc/plugin/index.php index e2ff428..2871fc3 100644 --- a/backend/class/dbdoc/plugin/index.php +++ b/backend/class/dbdoc/plugin/index.php @@ -3,7 +3,7 @@ namespace codename\architect\dbdoc\plugin; /** - * we may add some kind of loading prevention, if some classes are not loaded/undefined + * we may add some kind of loading prevention if some classes are not loaded/undefined * as we're using a filename that is the same as standard php scripts loaded for directories * if none is given */ diff --git a/backend/class/dbdoc/plugin/modelPrefix.php b/backend/class/dbdoc/plugin/modelPrefix.php index 0b49e77..5827aa6 100644 --- a/backend/class/dbdoc/plugin/modelPrefix.php +++ b/backend/class/dbdoc/plugin/modelPrefix.php @@ -3,7 +3,7 @@ namespace codename\architect\dbdoc\plugin; /** - * plugin for model + * plugin for a model * mostly just for correct task prefixing * @package architect */ diff --git a/backend/class/dbdoc/plugin/primary.php b/backend/class/dbdoc/plugin/primary.php index e7fa964..1c674bc 100644 --- a/backend/class/dbdoc/plugin/primary.php +++ b/backend/class/dbdoc/plugin/primary.php @@ -2,7 +2,7 @@ namespace codename\architect\dbdoc\plugin; -use codename\core\catchableException; +use codename\core\exception\catchableException; /** * plugin for providing and comparing model primary key config @@ -14,13 +14,13 @@ abstract class primary extends modelPrefix * [EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MISSING description] * @var string */ - public const EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MISSING = "EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MISSING"; + public const string EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MISSING = "EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MISSING"; /** * [EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MULTIPLE description] * @var string */ - public const EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MULTIPLE = "EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MULTIPLE"; + public const string EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MULTIPLE = "EXCEPTION_DBDOC_PLUGIN_PRIMARY_GETDEFINITION_MULTIPLE"; /** * {@inheritDoc} diff --git a/backend/class/dbdoc/plugin/sql/field.php b/backend/class/dbdoc/plugin/sql/field.php index 87a2ac7..2db49aa 100644 --- a/backend/class/dbdoc/plugin/sql/field.php +++ b/backend/class/dbdoc/plugin/sql/field.php @@ -4,8 +4,8 @@ use codename\architect\dbdoc\modeladapter\modeladapterGetSqlAdapter; use codename\architect\dbdoc\task; -use codename\core\catchableException; use codename\core\exception; +use codename\core\exception\catchableException; use ReflectionException; /** @@ -17,7 +17,7 @@ abstract class field extends \codename\architect\dbdoc\plugin\field use modeladapterGetSqlAdapter; /** - * basic conversion table between sql defaults and core framework + * basic conversion table between SQL defaults and core framework * @var string[] */ protected $conversionTable = [ @@ -87,12 +87,12 @@ public function Compare(): array $column_type = trim(preg_replace('/\(.*\)/', '', $structure['column_type'])); if ( - $definition['options']['db_column_type'] != null && - !in_array($structure['column_type'], $definition['options']['db_column_type']) && - !in_array($column_type, $definition['options']['db_column_type']) + $definition['options']['db_column_type'] != null && + !in_array($structure['column_type'], $definition['options']['db_column_type']) && + !in_array($column_type, $definition['options']['db_column_type']) ) { /* $definition['options']['db_column_type'] != $structure['column_type'] */ - // check for array-based definition + // check for an array-based definition // different column type! // echo(" -- unequal?"); /* echo("
    ");
    @@ -151,7 +151,7 @@ public function Compare(): array
                 // only create, if not primary
                 // is it is, it is created in the table plugin (at least for mysql)
     
    -            // create create-field task
    +            // create a create-field task
                 $tasks[] = $this->createTask(task::TASK_TYPE_REQUIRED, "CREATE_COLUMN", [
                   'field' => $definition['field'],
                     // 'def' => $definition
    @@ -185,23 +185,23 @@ public function getDefinition(): array
             }
     
             //
    -        // NOTE: we can only sync column datatype if it's not a structure (e.g. array)
    +        // NOTE: we can only sync column datatype if it's not a structure (e.g., array)
             //
             if ($definition['foreign'] && $definition['datatype'] != 'structure') {
                 // we have to get field information from a different model (!)
                 // , $def['app'] ?? '', $def['vendor'] ?? ''
                 $foreignAdapter = $this->adapter->dbdoc->getAdapter(
    -                $definition['foreign']['schema'],
    -                $definition['foreign']['model'],
    -                $definition['foreign']['app'] ?? '',
    -                $definition['foreign']['vendor'] ?? ''
    +              $definition['foreign']['schema'],
    +              $definition['foreign']['model'],
    +              $definition['foreign']['app'] ?? '',
    +              $definition['foreign']['vendor'] ?? ''
                 );
                 $plugin = $foreignAdapter->getPluginInstance('field', ['field' => $definition['foreign']['key']]);
                 if ($plugin != null) {
                     $foreignDefinition = $plugin->getDefinition();
     
                     // equalize datatype
    -                // both the referenced column and this one have to be of the same type
    +                // both the referenced column, and this one has to be of the same type
                     $definition['options']['db_data_type'] = $foreignDefinition['options']['db_data_type'];
                     $definition['options']['db_column_type'] = $foreignDefinition['options']['db_column_type'];
                     // TODO: NEW OPTIONS FORMAT/SETTING?
    @@ -232,7 +232,7 @@ public function getStructure(): mixed
             // get some column specifications
             $db = $this->getSqlAdapter()->db;
             $db->query(
    -            "SELECT column_name, column_type, data_type, is_nullable, column_default
    +          "SELECT column_name, column_type, data_type, is_nullable, column_default
           FROM information_schema.columns
           WHERE table_schema = '{$this->adapter->schema}'
           AND table_name = '{$this->adapter->model}'
    @@ -290,7 +290,7 @@ public function runTask(task $task): void
                 // fallback from specific column types to a more generous type
                 $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0];
                 $db->query(
    -                "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} ADD COLUMN {$definition['field']} $columnType $add;"
    +              "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} ADD COLUMN {$definition['field']} $columnType $add;"
                 );
             }
     
    @@ -311,14 +311,14 @@ public function runTask(task $task): void
                 //
                 if ($definition['notnull'] && $definition['default'] != null) {
                     $db->query(
    -                    "UPDATE {$this->adapter->schema}.{$this->adapter->model} SET {$definition['field']} = $defaultValue WHERE {$definition['field']} IS NULL;"
    +                  "UPDATE {$this->adapter->schema}.{$this->adapter->model} SET {$definition['field']} = $defaultValue WHERE {$definition['field']} IS NULL;"
                     );
                 }
     
                 $default = isset($definition['default']) ? 'DEFAULT ' . $defaultValue : '';
     
                 $db->query(
    -                "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} MODIFY COLUMN {$definition['field']} $columnType $nullable $default;"
    +              "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} MODIFY COLUMN {$definition['field']} $columnType $nullable $default;"
                 );
             }
         }
    @@ -432,7 +432,7 @@ protected function getDatatypeConversionOptions(string $key): array|string
         {
             $conversionTable = $this->getDatatypeConversionTable();
             if (array_key_exists($key, $conversionTable)) {
    -            // use defined type
    +            // use a defined type
                 return $conversionTable[$key];
             } else {
                 $keyComponents = explode('_', $key);
    @@ -483,7 +483,7 @@ public function convertDbDataTypeToDbColumnTypeDefault($t): array
             $res = [];
             foreach ($checkTypes as $checkType) {
                 if (array_key_exists($checkType, $conversionTable)) {
    -                // use defined type
    +                // use a defined type
                     $res[] = $conversionTable[$checkType];
                 } else {
                     $tArr = explode('_', $checkType);
    diff --git a/backend/class/dbdoc/plugin/sql/fieldlist.php b/backend/class/dbdoc/plugin/sql/fieldlist.php
    index 95a1864..4a47695 100644
    --- a/backend/class/dbdoc/plugin/sql/fieldlist.php
    +++ b/backend/class/dbdoc/plugin/sql/fieldlist.php
    @@ -24,24 +24,24 @@ public function Compare(): array
             $definition = $this->getDefinition();
     //        $structure = $this->getStructure();
     
    -        // fields contained in model, that are not in the database table
    +        // fields contained in a model, that are not in the database table
     //        $missing = array_diff($definition, $structure);
     
             // columns in the database table, that are simply "too much" (not in the model definition)
     //        $toomuch = array_diff($structure, $definition);
     
             // TODO: handle toomuch
    -        // e.g. check for prefix __old_
    +        // e.g., check for prefix __old_
             // of not, create task to rename column
             // otherwise, recommend harddeletion ?
     
             foreach ($definition as $field) {
                 $plugin = $this->adapter->getPluginInstance(
    -                'field',
    -                [
    -                  'field' => $field,
    -                ],
    -                $this->virtual // virtual on need.
    +              'field',
    +              [
    +                'field' => $field,
    +              ],
    +              $this->virtual // virtual on a need.
                 );
     
                 if ($plugin != null) {
    @@ -71,7 +71,7 @@ public function getStructure(): array
         {
             $db = $this->getSqlAdapter()->db;
             $db->query(
    -            "SELECT column_name
    +          "SELECT column_name
           FROM information_schema.columns
           WHERE table_name = '{$this->adapter->model}'
           AND table_schema = '{$this->adapter->schema}'
    diff --git a/backend/class/dbdoc/plugin/sql/foreign.php b/backend/class/dbdoc/plugin/sql/foreign.php
    index 612ac66..f42d759 100644
    --- a/backend/class/dbdoc/plugin/sql/foreign.php
    +++ b/backend/class/dbdoc/plugin/sql/foreign.php
    @@ -4,8 +4,8 @@
     
     use codename\architect\dbdoc\modeladapter\modeladapterGetSqlAdapter;
     use codename\architect\dbdoc\task;
    -use codename\core\catchableException;
     use codename\core\exception;
    +use codename\core\exception\catchableException;
     use ReflectionException;
     
     /**
    @@ -107,13 +107,13 @@ public function Compare(): array
                 }
     
                 $tasks[] = $this->createTask(
    -                task::TASK_TYPE_SUGGESTED,
    -                "ADD_FOREIGNKEY_CONSTRAINT",
    -                [
    -                  'field' => $field,
    -                  'config' => $def,
    -                ],
    -                $precededBy
    +              task::TASK_TYPE_SUGGESTED,
    +              "ADD_FOREIGNKEY_CONSTRAINT",
    +              [
    +                'field' => $field,
    +                'config' => $def,
    +              ],
    +              $precededBy
                 );
             }
     
    @@ -149,7 +149,7 @@ public function getStructure(): array
             $db = $this->getSqlAdapter()->db;
     
             $db->query(
    -            "SELECT tc.table_schema, tc.table_name, constraint_name, column_name, referenced_table_schema, referenced_table_name, referenced_column_name
    +          "SELECT tc.table_schema, tc.table_name, constraint_name, column_name, referenced_table_schema, referenced_table_name, referenced_column_name
             FROM information_schema.table_constraints tc
             INNER JOIN information_schema.key_column_usage kcu
             USING (constraint_catalog, constraint_schema, constraint_name)
    @@ -185,7 +185,7 @@ public function runTask(task $task): void
                 }
     
                 $db->query(
    -                "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model}
    +              "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model}
             ADD CONSTRAINT $constraintName
             FOREIGN KEY ($fkey)
             REFERENCES {$config['schema']}.{$config['model']} ($references);"
    @@ -203,7 +203,7 @@ public function runTask(task $task): void
                 $constraintName = $task->data->get('constraint_name');
     
                 $db->query(
    -                "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model}
    +              "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model}
             DROP CONSTRAINT $constraintName;"
                 );
             }
    diff --git a/backend/class/dbdoc/plugin/sql/fulltext.php b/backend/class/dbdoc/plugin/sql/fulltext.php
    index a6a2785..c5db1d7 100644
    --- a/backend/class/dbdoc/plugin/sql/fulltext.php
    +++ b/backend/class/dbdoc/plugin/sql/fulltext.php
    @@ -8,7 +8,7 @@
     use ReflectionException;
     
     /**
    - * we may add some kind of loading prevention, if some classes are not loaded/undefined
    + * we may add some kind of loading prevention if some classes are not loaded/undefined
      * as we're using a filename that is the same as standard php scripts loaded for directories
      * if none is given
      */
    @@ -42,10 +42,10 @@ public function Compare(): array
             foreach ($structure as $strucName => $struc) {
                 // get ordered (?) column_names
                 $fulltextColumnNames = array_map(
    -                function ($spec) {
    -                    return $spec['column_name'];
    -                },
    -                $struc
    +              function ($spec) {
    +                  return $spec['column_name'];
    +              },
    +              $struc
                 );
     
                 // reduce to string, if only one element
    @@ -95,7 +95,7 @@ public function getStructure(): array
             $db = $this->getSqlAdapter()->db;
     
             $db->query(
    -            "SELECT DISTINCT tc.table_schema, tc.table_name, s.index_name, tc.constraint_name, s.column_name, s.seq_in_index
    +          "SELECT DISTINCT tc.table_schema, tc.table_name, s.index_name, tc.constraint_name, s.column_name, s.seq_in_index
           FROM information_schema.statistics s
           LEFT OUTER JOIN information_schema.table_constraints tc
               ON tc.table_schema = s.table_schema
    @@ -114,7 +114,7 @@ public function getStructure(): array
             // perform grouping
             foreach ($allFulltext as $fulltext) {
                 if (array_key_exists($fulltext['index_name'], $fulltextGroups)) {
    -                // match to existing group
    +                // match to an existing group
                     foreach ($fulltextGroups as $groupName => $group) {
                         if ($fulltext['index_name'] == $groupName) {
                             $fulltextGroups[$groupName][] = $fulltext;
    @@ -122,7 +122,7 @@ public function getStructure(): array
                         }
                     }
                 } else {
    -                // create new group
    +                // create a new group
                     $fulltextGroups[$fulltext['index_name']][] = $fulltext;
                 }
             }
    @@ -156,16 +156,16 @@ public function runTask(task $task): void
                 $fulltextName = 'fulltext_' . md5($columns);
     
                 $db->query(
    -                "CREATE FULLTEXT INDEX $fulltextName ON {$this->adapter->schema}.{$this->adapter->model} ($columns) COMMENT '' ALGORITHM DEFAULT LOCK DEFAULT;"
    +              "CREATE FULLTEXT INDEX $fulltextName ON {$this->adapter->schema}.{$this->adapter->model} ($columns) COMMENT '' ALGORITHM DEFAULT LOCK DEFAULT;"
                 );
             }
     
             if ($task->name == "REMOVE_FULLTEXT") {
    -            // simply drop fulltext by fulltext_name
    +            // drop fulltext by fulltext_name
                 $fulltextName = $task->data->get('fulltext_name');
     
                 $db->query(
    -                "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} DROP INDEX $fulltextName;"
    +              "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} DROP INDEX $fulltextName;"
                 );
             }
         }
    diff --git a/backend/class/dbdoc/plugin/sql/index.php b/backend/class/dbdoc/plugin/sql/index.php
    index fd365f8..e0ed5d6 100644
    --- a/backend/class/dbdoc/plugin/sql/index.php
    +++ b/backend/class/dbdoc/plugin/sql/index.php
    @@ -8,7 +8,7 @@
     use ReflectionException;
     
     /**
    - * we may add some kind of loading prevention, if some classes are not loaded/undefined
    + * we may add some kind of loading prevention if some classes are not loaded/undefined
      * as we're using a filename that is the same as standard php scripts loaded for directories
      * if none is given
      */
    @@ -43,10 +43,10 @@ public function Compare(): array
             foreach ($structure as $strucName => $struc) {
                 // get ordered (?) column_names
                 $indexColumnNames = array_map(
    -                function ($spec) {
    -                    return $spec['column_name'];
    -                },
    -                $struc
    +              function ($spec) {
    +                  return $spec['column_name'];
    +              },
    +              $struc
                 );
     
                 // reduce to string, if only one element
    @@ -109,7 +109,7 @@ public function getDefinition(): array
                     // multi-component foreign key - $fkey is NOT a field name, use 'key'-keys
                     $definition[] = array_keys($fkeyConfig['key']);
                 } else {
    -                // just use the foreign key definition name (this is the current table's key to be used)
    +                // use the foreign key definition name (this is the current table's key to be used)
                     $definition[] = $fkey;
                 }
             }
    @@ -143,7 +143,7 @@ public function getStructure(): array
             $db = $this->getSqlAdapter()->db;
     
             $db->query(
    -            "SELECT DISTINCT tc.table_schema, tc.table_name, s.index_name, tc.constraint_name, s.column_name, s.seq_in_index
    +          "SELECT DISTINCT tc.table_schema, tc.table_name, s.index_name, tc.constraint_name, s.column_name, s.seq_in_index
           FROM information_schema.statistics s
           LEFT OUTER JOIN information_schema.table_constraints tc
               ON tc.table_schema = s.table_schema
    @@ -172,7 +172,7 @@ public function getStructure(): array
             // perform grouping
             foreach ($allIndices as $index) {
                 if (array_key_exists($index['index_name'], $indexGroups)) {
    -                // match to existing group
    +                // match to an existing group
                     foreach ($indexGroups as $groupName => $group) {
                         if ($index['index_name'] == $groupName) {
                             $indexGroups[$groupName][] = $index;
    @@ -180,7 +180,7 @@ public function getStructure(): array
                         }
                     }
                 } else {
    -                // create new group
    +                // create a new group
                     $indexGroups[$index['index_name']][] = $index;
                 }
             }
    @@ -214,16 +214,16 @@ public function runTask(task $task): void
                 $indexName = 'index_' . md5($columns);
     
                 $db->query(
    -                "CREATE INDEX $indexName ON {$this->adapter->schema}.{$this->adapter->model} ($columns);"
    +              "CREATE INDEX $indexName ON {$this->adapter->schema}.{$this->adapter->model} ($columns);"
                 );
             }
     
             if ($task->name == "REMOVE_INDEX") {
    -            // simply drop index by index_name
    +            // drop index by index_name
                 $indexName = $task->data->get('index_name');
     
                 $db->query(
    -                "DROP INDEX IF EXISTS $indexName ON {$this->adapter->schema}.{$this->adapter->model};"
    +              "DROP INDEX IF EXISTS $indexName ON {$this->adapter->schema}.{$this->adapter->model};"
                 );
             }
         }
    diff --git a/backend/class/dbdoc/plugin/sql/mysql/field.php b/backend/class/dbdoc/plugin/sql/mysql/field.php
    index 9c50b94..0a12883 100644
    --- a/backend/class/dbdoc/plugin/sql/mysql/field.php
    +++ b/backend/class/dbdoc/plugin/sql/mysql/field.php
    @@ -26,8 +26,8 @@ class field extends \codename\architect\dbdoc\plugin\sql\field
         public function getDefinition(): array
         {
             $definition = parent::getDefinition();
    -        // TODO: check if this is the correct behaviour
    -        // the base class sql\field may already set db_data_type, e.g. if it's a primary key
    +        // TODO: check if this is the correct behavior
    +        // the base class sql\field may already set db_data_type, e.g., if it's a primary key
     
             // field is a virtual field (collection)
             if ($definition['collection']) {
    diff --git a/backend/class/dbdoc/plugin/sql/mysql/foreign.php b/backend/class/dbdoc/plugin/sql/mysql/foreign.php
    index 21af9ef..6c45741 100644
    --- a/backend/class/dbdoc/plugin/sql/mysql/foreign.php
    +++ b/backend/class/dbdoc/plugin/sql/mysql/foreign.php
    @@ -24,13 +24,13 @@ public function runTask(task $task): void
     
                 // drop the foreign key constraint itself
                 $db->query(
    -                "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model}
    +              "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model}
             DROP FOREIGN KEY $constraintName;"
                 );
     
                 // drop the associated index
                 $db->query(
    -                "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model}
    +              "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model}
             DROP INDEX IF EXISTS $constraintName;"
                 );
                 return;
    diff --git a/backend/class/dbdoc/plugin/sql/mysql/permissions.php b/backend/class/dbdoc/plugin/sql/mysql/permissions.php
    index 6dc7183..29e600d 100644
    --- a/backend/class/dbdoc/plugin/sql/mysql/permissions.php
    +++ b/backend/class/dbdoc/plugin/sql/mysql/permissions.php
    @@ -8,7 +8,7 @@
     use ReflectionException;
     
     /**
    - * plugin for providing and comparing user config in database
    + * plugin for providing and comparing user config in a database
      * @package architect
      */
     class permissions extends \codename\architect\dbdoc\plugin\sql\permissions
    @@ -84,7 +84,7 @@ public function getStructure(): array
             $permissions = [];
     
             $db->query(
    -            "SELECT table_priv
    +          "SELECT table_priv
             FROM mysql.tables_priv
             WHERE host = '%'
             AND user = '{$definition['user']}'
    @@ -139,7 +139,7 @@ public function runTask(task $task): void
                 $permissions = implode(',', $task->data->get('permissions'));
     
                 $db->query(
    -                "GRANT $permissions
    +              "GRANT $permissions
             ON {$this->adapter->schema}.{$this->adapter->model}
             TO '{$definition['user']}'@'%';"
                 );
    diff --git a/backend/class/dbdoc/plugin/sql/mysql/primary.php b/backend/class/dbdoc/plugin/sql/mysql/primary.php
    index ab25843..56986ba 100644
    --- a/backend/class/dbdoc/plugin/sql/mysql/primary.php
    +++ b/backend/class/dbdoc/plugin/sql/mysql/primary.php
    @@ -14,13 +14,13 @@ class primary extends \codename\architect\dbdoc\plugin\sql\primary
          * default column data type for primary keys on mysql
          * @var string
          */
    -    public const DB_DEFAULT_DATA_TYPE = 'bigint';
    +    public const string DB_DEFAULT_DATA_TYPE = 'bigint';
     
         /**
          * default column type for primary keys on mysql
          * @var string
          */
    -    public const DB_DEFAULT_COLUMN_TYPE = 'bigint(20)';
    +    public const string DB_DEFAULT_COLUMN_TYPE = 'bigint(20)';
     
         /**
          * {@inheritDoc}
    diff --git a/backend/class/dbdoc/plugin/sql/mysql/table.php b/backend/class/dbdoc/plugin/sql/mysql/table.php
    index a6c256a..a940736 100644
    --- a/backend/class/dbdoc/plugin/sql/mysql/table.php
    +++ b/backend/class/dbdoc/plugin/sql/mysql/table.php
    @@ -41,7 +41,7 @@ public function runTask(task $task): void
     
                 // for mysql, we have to create the table with at least ONE COLUMN
                 $db->query(
    -                "CREATE TABLE {$this->adapter->schema}.{$this->adapter->model} (
    +              "CREATE TABLE {$this->adapter->schema}.{$this->adapter->model} (
               {$field['field']} {$field['options']['db_column_type'][0]} $add,
               PRIMARY KEY({$field['field']})
             ) ENGINE=InnoDB CHARACTER SET=utf8 COLLATE utf8_general_ci;"
    @@ -49,7 +49,7 @@ public function runTask(task $task): void
             }
             if ($task->name == 'DELETE_COLUMN') {
                 $db->query(
    -                "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} DROP COLUMN IF EXISTS {$task->data->get('field')};"
    +              "ALTER TABLE {$this->adapter->schema}.{$this->adapter->model} DROP COLUMN IF EXISTS {$task->data->get('field')};"
                 );
             }
         }
    diff --git a/backend/class/dbdoc/plugin/sql/mysql/user.php b/backend/class/dbdoc/plugin/sql/mysql/user.php
    index 511b913..4a51f57 100644
    --- a/backend/class/dbdoc/plugin/sql/mysql/user.php
    +++ b/backend/class/dbdoc/plugin/sql/mysql/user.php
    @@ -8,14 +8,14 @@
     use ReflectionException;
     
     /**
    - * plugin for providing and comparing user config in database
    + * plugin for providing and comparing user config in a database
      * @package architect
      */
     class user extends \codename\architect\dbdoc\plugin\sql\user
     {
         use modeladapterGetSqlAdapter;
     
    -    protected const NEEDED_DML_GRANTS = [
    +    protected const array NEEDED_DML_GRANTS = [
           'select',
           'insert',
           'update',
    @@ -65,7 +65,7 @@ public function getStructure(): array
     
             // query user dataset
             $db->query(
    -            "SELECT exists(
    +          "SELECT exists(
             SELECT 1
             FROM mysql.user
             WHERE host = '%'
    @@ -75,9 +75,9 @@ public function getStructure(): array
             $exists = $db->getResult()[0]['result'];
     
             if ($exists) {
    -            // check password, indirectly
    +            // check the password indirectly
                 $db->query(
    -                "SELECT exists(
    +              "SELECT exists(
               SELECT 1
               FROM mysql.user
               WHERE host = '%'
    @@ -109,13 +109,13 @@ public function runTask(task $task): void
     
             if ($task->name == 'CREATE_USER') {
                 $db->query(
    -                "CREATE USER '{$definition['user']}'@'%' IDENTIFIED BY '{$definition['pass']}';"
    +              "CREATE USER '{$definition['user']}'@'%' IDENTIFIED BY '{$definition['pass']}';"
                 );
             }
     
             if ($task->name == 'CHANGE_PASSWORD') {
                 $db->query(
    -                "UPDATE mysql.user
    +              "UPDATE mysql.user
             SET password = PASSWORD({$definition['pass']})
             WHERE host = '%'
             AND user = '{$definition['user']}'
    @@ -137,7 +137,7 @@ public function runTask(task $task): void
                 $permissions = implode(',', $task->data->get('permissions'));
     
                 $db->query(
    -                "GRANT $permissions
    +              "GRANT $permissions
             ON {$this->adapter->schema}.{$this->adapter->model}
             TO '{$definition['user']}'@'%';"
                 );
    diff --git a/backend/class/dbdoc/plugin/sql/permissions.php b/backend/class/dbdoc/plugin/sql/permissions.php
    index 6f79e22..00f3410 100644
    --- a/backend/class/dbdoc/plugin/sql/permissions.php
    +++ b/backend/class/dbdoc/plugin/sql/permissions.php
    @@ -5,7 +5,7 @@
     use codename\architect\dbdoc\modeladapter\modeladapterGetSqlAdapter;
     
     /**
    - * plugin for providing and comparing user config in database
    + * plugin for providing and comparing user config in a database
      * @package architect
      */
     abstract class permissions extends \codename\architect\dbdoc\plugin\permissions
    diff --git a/backend/class/dbdoc/plugin/sql/primary.php b/backend/class/dbdoc/plugin/sql/primary.php
    index 5b629e1..410648f 100644
    --- a/backend/class/dbdoc/plugin/sql/primary.php
    +++ b/backend/class/dbdoc/plugin/sql/primary.php
    @@ -4,8 +4,8 @@
     
     use codename\architect\dbdoc\modeladapter\modeladapterGetSqlAdapter;
     use codename\architect\dbdoc\task;
    -use codename\core\catchableException;
     use codename\core\exception;
    +use codename\core\exception\catchableException;
     use ReflectionException;
     
     /**
    @@ -80,7 +80,7 @@ public function getStructure(): array
             // get some column specifications
             $db = $this->getSqlAdapter()->db;
             $db->query(
    -            "SELECT column_name, column_type, data_type
    +          "SELECT column_name, column_type, data_type
           FROM information_schema.columns
           WHERE table_schema = '{$this->adapter->schema}'
           AND table_name = '{$this->adapter->model}'
    @@ -95,7 +95,7 @@ public function getStructure(): array
         }
     
         /**
    -     * this function checks a given structure information
    +     * this function checks given structure information
          * for correctness and returns an array of tasks needed for completion
          * @param array $definition
          * @param array $structure
    diff --git a/backend/class/dbdoc/plugin/sql/schema.php b/backend/class/dbdoc/plugin/sql/schema.php
    index 0b6ac84..0297db0 100644
    --- a/backend/class/dbdoc/plugin/sql/schema.php
    +++ b/backend/class/dbdoc/plugin/sql/schema.php
    @@ -58,7 +58,7 @@ public function getStructure(): mixed
         {
             $db = $this->getSqlAdapter()->db;
             $db->query(
    -            "SELECT exists(select 1 FROM information_schema.schemata WHERE schema_name = '{$this->adapter->schema}') as result;"
    +          "SELECT exists(select 1 FROM information_schema.schemata WHERE schema_name = '{$this->adapter->schema}') as result;"
             );
             return $db->getResult()[0]['result'];
         }
    diff --git a/backend/class/dbdoc/plugin/sql/sqlite/field.php b/backend/class/dbdoc/plugin/sql/sqlite/field.php
    index a128bce..6762db5 100644
    --- a/backend/class/dbdoc/plugin/sql/sqlite/field.php
    +++ b/backend/class/dbdoc/plugin/sql/sqlite/field.php
    @@ -3,8 +3,8 @@
     namespace codename\architect\dbdoc\plugin\sql\sqlite;
     
     use codename\architect\dbdoc\task;
    -use codename\core\catchableException;
     use codename\core\exception;
    +use codename\core\exception\catchableException;
     use ReflectionException;
     
     /**
    @@ -25,7 +25,7 @@ class field extends \codename\architect\dbdoc\plugin\sql\field implements partia
           'datetime' => 'TIMESTAMP',
         ];
         /**
    -     * basic conversion table between sql defaults and core framework
    +     * basic conversion table between SQL defaults and core framework
          * @var string[]
          */
         protected $conversionTable = [
    @@ -52,7 +52,7 @@ public function getStructure(): mixed
             $db = $this->getSqlAdapter()->db;
     
             $db->query(
    -            "SELECT *
    +          "SELECT *
             FROM pragma_table_info('{$this->adapter->schema}.{$this->adapter->model}')
             WHERE
             name = '{$this->parameter['field']}'
    @@ -166,7 +166,7 @@ public function getDefinition(): array
             $definition['options']['db_column_type'] = $this->convertModelDataTypeToDbDataType($definition['datatype']);
     
     
    -        // Override regular SQL-style to match SQLite requirements
    +        // Override regular SQL style to match SQLite requirements
             // at least for datetime-fields with a default value
             if ($definition['datatype'] == 'text_timestamp' && (($definition['default'] ?? null) == 'current_timestamp()')) {
                 $definition['default'] = 'CURRENT_TIMESTAMP';
    @@ -217,7 +217,7 @@ public function runTask(task $task): void
                 $columnType = $definition['options']['db_column_type'][0] ?? $definition['options']['db_data_type'][0];
     
                 $db->query(
    -                "ALTER TABLE '{$this->adapter->schema}.{$this->adapter->model}' ADD COLUMN {$definition['field']} $columnType $add;"
    +              "ALTER TABLE '{$this->adapter->schema}.{$this->adapter->model}' ADD COLUMN {$definition['field']} $columnType $add;"
                 );
             }
     
    @@ -235,7 +235,7 @@ public function runTask(task $task): void
                 if ($definition['notnull'] && $definition['default'] != null) {
                     $defaultValue = (is_bool($definition['default']) ? (int)$definition['default'] : $defaultValue);
                     $db->query(
    -                    "UPDATE '{$this->adapter->schema}.{$this->adapter->model}' SET {$definition['field']} = $defaultValue WHERE {$definition['field']} IS NULL;"
    +                  "UPDATE '{$this->adapter->schema}.{$this->adapter->model}' SET {$definition['field']} = $defaultValue WHERE {$definition['field']} IS NULL;"
                     );
                 }
             }
    diff --git a/backend/class/dbdoc/plugin/sql/sqlite/fieldlist.php b/backend/class/dbdoc/plugin/sql/sqlite/fieldlist.php
    index 86d986a..2b3a81a 100644
    --- a/backend/class/dbdoc/plugin/sql/sqlite/fieldlist.php
    +++ b/backend/class/dbdoc/plugin/sql/sqlite/fieldlist.php
    @@ -3,8 +3,8 @@
     namespace codename\architect\dbdoc\plugin\sql\sqlite;
     
     use codename\architect\dbdoc\plugin;
    -use codename\core\catchableException;
     use codename\core\exception;
    +use codename\core\exception\catchableException;
     use ReflectionException;
     
     /**
    @@ -22,14 +22,14 @@ public function Compare(): array
             $definition = $this->getDefinition();
     //        $structure = $this->getStructure();
     
    -        // fields contained in model, that are not in the database table
    +        // fields contained in a model, that are not in the database table
     //        $missing = array_diff($definition, $structure);
     
             // columns in the database table, that are simply "too much" (not in the model definition)
     //        $toomuch = array_diff($structure, $definition);
     
             // TODO: handle toomuch
    -        // e.g. check for prefix __old_
    +        // e.g., check for prefix __old_
             // of not, create task to rename column
             // otherwise, recommend harddeletion ?
     
    @@ -37,11 +37,11 @@ public function Compare(): array
     
             foreach ($definition as $field) {
                 $plugin = $this->adapter->getPluginInstance(
    -                'field',
    -                [
    -                  'field' => $field,
    -                ],
    -                $this->virtual // virtual on need.
    +              'field',
    +              [
    +                'field' => $field,
    +              ],
    +              $this->virtual // virtual on a need.
                 );
     
                 if ($plugin != null) {
    @@ -87,19 +87,19 @@ public function getPartialStatement(): array
             $partialStatements = [];
             foreach ($definition as $field) {
                 $plugin = $this->adapter->getPluginInstance(
    -                'field',
    -                [
    -                  'field' => $field,
    -                ],
    -                $this->virtual // virtual on need.
    +              'field',
    +              [
    +                'field' => $field,
    +              ],
    +              $this->virtual // virtual on a need.
                 );
                 if ($plugin instanceof field) {
                     $partialStatement = $plugin->getPartialStatement();
                     if ($partialStatement) {
                         //
                         // getPartialStatement() might return a NULL value
    -                    // (e.g. if virtual/collection field)
    -                    // only add, if there's a value
    +                    // (e.g., if virtual/collection field)
    +                    // only add if there's a value
                         //
                         $partialStatements[] = $partialStatement;
                     }
    diff --git a/backend/class/dbdoc/plugin/sql/sqlite/index.php b/backend/class/dbdoc/plugin/sql/sqlite/index.php
    index e1547d5..543ff32 100644
    --- a/backend/class/dbdoc/plugin/sql/sqlite/index.php
    +++ b/backend/class/dbdoc/plugin/sql/sqlite/index.php
    @@ -6,7 +6,7 @@
     use codename\architect\dbdoc\task;
     
     /**
    - * we may add some kind of loading prevention, if some classes are not loaded/undefined
    + * we may add some kind of loading prevention if some classes are not loaded/undefined
      * as we're using a filename that is the same as standard php scripts loaded for directories
      * if none is given
      */
    @@ -37,10 +37,10 @@ public function Compare(): array
             foreach ($structure as $strucName => $struc) {
                 // get ordered (?) column_names
                 $indexColumnNames = array_map(
    -                function ($spec) {
    -                    return $spec['column_name'];
    -                },
    -                $struc
    +              function ($spec) {
    +                  return $spec['column_name'];
    +              },
    +              $struc
                 );
     
                 // reduce to string, if only one element
    @@ -116,9 +116,9 @@ public function getStructure(): array
     
                 foreach ($indexInfoRes as $indexColumn) {
                     $indexGroups[$index['index_name']][] = array_merge(
    -                    $index,
    -                    $indexColumn,
    -                    ['column_name' => $indexColumn['name']]
    +                  $index,
    +                  $indexColumn,
    +                  ['column_name' => $indexColumn['name']]
                     );
                 }
             }
    @@ -149,17 +149,17 @@ public function runTask(task $task): void
                 $indexName = 'index_' . md5("{$this->adapter->schema}.{$this->adapter->model}-" . $columns);// prepend schema+model
     
                 $db->query(
    -                "CREATE INDEX $indexName ON '{$this->adapter->schema}.{$this->adapter->model}' ($columns);"
    +              "CREATE INDEX $indexName ON '{$this->adapter->schema}.{$this->adapter->model}' ($columns);"
                 );
             }
     
             if ($task->name == "REMOVE_INDEX") {
    -            // simply drop index by index_name
    +            // drop index by index_name
                 $indexName = $task->data->get('index_name');
     
     
                 $db->query(
    -                "DROP INDEX IF EXISTS $indexName;" // ON '{$this->adapter->schema}.{$this->adapter->model}'
    +              "DROP INDEX IF EXISTS $indexName;" // ON '{$this->adapter->schema}.{$this->adapter->model}'
                 );
             }
         }
    diff --git a/backend/class/dbdoc/plugin/sql/sqlite/primary.php b/backend/class/dbdoc/plugin/sql/sqlite/primary.php
    index eddb9ef..75cec3f 100644
    --- a/backend/class/dbdoc/plugin/sql/sqlite/primary.php
    +++ b/backend/class/dbdoc/plugin/sql/sqlite/primary.php
    @@ -14,12 +14,12 @@ class primary extends \codename\architect\dbdoc\plugin\sql\primary
          * default column data type for primary keys on mysql
          * @var string
          */
    -    public const DB_DEFAULT_DATA_TYPE = 'INTEGER';
    +    public const string DB_DEFAULT_DATA_TYPE = 'INTEGER';
         /**
          * default column type for primary keys on mysql
          * @var string
          */
    -    public const DB_DEFAULT_COLUMN_TYPE = 'INTEGER';
    +    public const string DB_DEFAULT_COLUMN_TYPE = 'INTEGER';
     
         /**
          * {@inheritDoc}
    @@ -34,7 +34,7 @@ public function getStructure(): array
             // );
     
             $db->query(
    -            "SELECT name AS column_name, type AS column_type, type AS data_type
    +          "SELECT name AS column_name, type AS column_type, type AS data_type
           FROM pragma_table_info('{$this->adapter->schema}.{$this->adapter->model}')
           WHERE
           pk = 1;"
    diff --git a/backend/class/dbdoc/plugin/sql/sqlite/table.php b/backend/class/dbdoc/plugin/sql/sqlite/table.php
    index d4f5132..779bc88 100644
    --- a/backend/class/dbdoc/plugin/sql/sqlite/table.php
    +++ b/backend/class/dbdoc/plugin/sql/sqlite/table.php
    @@ -151,7 +151,7 @@ public function getStructure(): mixed
         {
             $db = $this->getSqlAdapter()->db;
             $db->query(
    -            "SELECT exists(select 1 FROM sqlite_master WHERE type = 'table' AND tbl_name = '{$this->adapter->schema}.{$this->adapter->model}') as result;"
    +          "SELECT exists(select 1 FROM sqlite_master WHERE type = 'table' AND tbl_name = '{$this->adapter->schema}.{$this->adapter->model}') as result;"
             );
             return $db->getResult()[0]['result'];
         }
    @@ -166,12 +166,12 @@ public function runTask(task $task): void
         {
             $db = $this->getSqlAdapter()->db;
     
    -        // If recreating a table due to required changes
    +        // If recreating a table due to required changes,
             // this is variable being set during the process
             $preliminaryTable = null;
     
             if ($task->name == 'RECREATE_TABLE') {
    -            // for recreating a table, we
    +            // for recreating a table, we:
                 // - create a preliminary table first (with the desired structure)
                 // - then copy all existing data to it (via mutual fields)
                 // - drop the existing table
    @@ -185,7 +185,7 @@ public function runTask(task $task): void
                 // to improve redundancy and keep old data for debugging
                 // we might use an increment value,
                 // when an existing table is detected
    -            // e.g. __prelim_2_tablename
    +            // e.g., __prelim_2_tablename
     
                 // test for existing prelim/backup table
                 $db->query("SELECT exists(select 1 FROM sqlite_master WHERE type = 'table' AND tbl_name = '$preliminaryTable') as result;");
    @@ -231,7 +231,7 @@ public function runTask(task $task): void
                 // print_r($fieldSql);
     
                 $db->query(
    -                "CREATE TABLE `$tableSpecifier` (
    +              "CREATE TABLE `$tableSpecifier` (
               $fieldSql
             );"
                 );
    @@ -250,7 +250,7 @@ public function runTask(task $task): void
     
                     // copy old data to new table
                     $db->query(
    -                    "INSERT INTO
    +                  "INSERT INTO
               `$preliminaryTable` ($mutualFieldsSql)
               SELECT $mutualFieldsSql FROM `{$this->adapter->schema}.{$this->adapter->model}`
             "
    @@ -272,11 +272,11 @@ protected function getCheckStructure($tasks): array
         {
             $db = $this->getSqlAdapter()->db;
             $db->query(
    -            "SELECT name as COLUMN_NAME FROM pragma_table_info('{$this->adapter->schema}.{$this->adapter->model}');"
    +          "SELECT name as COLUMN_NAME FROM pragma_table_info('{$this->adapter->schema}.{$this->adapter->model}');"
             );
             $columns = $db->getResult();
     
    -        if ($columns ?? false) {
    +        if (count($columns)) {
                 $fields = $this->adapter->config->get()['field'] ?? [];
     
                 foreach ($columns as $column) {
    diff --git a/backend/class/dbdoc/plugin/sql/table.php b/backend/class/dbdoc/plugin/sql/table.php
    index 17bdda1..b30d402 100644
    --- a/backend/class/dbdoc/plugin/sql/table.php
    +++ b/backend/class/dbdoc/plugin/sql/table.php
    @@ -98,7 +98,7 @@ public function getStructure(): mixed
         {
             $db = $this->getSqlAdapter()->db;
             $db->query(
    -            "SELECT exists(select 1 FROM information_schema.tables WHERE table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}') as result;"
    +          "SELECT exists(select 1 FROM information_schema.tables WHERE table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}') as result;"
             );
             return $db->getResult()[0]['result'];
         }
    @@ -113,7 +113,7 @@ protected function getCheckStructure($tasks): array
         {
             $db = $this->getSqlAdapter()->db;
             $db->query(
    -            "SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}';"
    +          "SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = '{$this->adapter->schema}' AND table_name = '{$this->adapter->model}';"
             );
             $columns = $db->getResult();
     
    diff --git a/backend/class/dbdoc/plugin/sql/unique.php b/backend/class/dbdoc/plugin/sql/unique.php
    index 9f1c13d..d7593a8 100644
    --- a/backend/class/dbdoc/plugin/sql/unique.php
    +++ b/backend/class/dbdoc/plugin/sql/unique.php
    @@ -36,10 +36,10 @@ public function Compare(): array
             foreach ($structure as $struc) {
                 // get ordered (?) column_names
                 $constraintColumnNames = array_map(
    -                function ($spec) {
    -                    return $spec['column_name'];
    -                },
    -                $struc['constraint_columns']
    +              function ($spec) {
    +                  return $spec['column_name'];
    +              },
    +              $struc['constraint_columns']
                 );
     
                 // reduce to string, if only one element
    @@ -87,7 +87,7 @@ public function getStructure(): array
         {
             $db = $this->getSqlAdapter()->db;
             $db->query(
    -            "SELECT table_schema, table_name, constraint_name
    +          "SELECT table_schema, table_name, constraint_name
           FROM information_schema.table_constraints
           WHERE constraint_type='UNIQUE'
           AND table_schema = '{$this->adapter->schema}'
    @@ -97,7 +97,7 @@ public function getStructure(): array
     
             foreach ($constraints as &$constraint) {
                 $db->query(
    -                "SELECT table_schema, table_name, constraint_name, column_name
    +              "SELECT table_schema, table_name, constraint_name, column_name
             FROM information_schema.key_column_usage
             WHERE constraint_name = '{$constraint['constraint_name']}'
             AND table_schema = '{$this->adapter->schema}'
    @@ -127,14 +127,14 @@ public function runTask(task $task): void
                 $constraintName = "unique_" . md5("{$this->adapter->schema}_{$this->adapter->model}_$columns");
     
                 $db->query(
    -                "CREATE UNIQUE INDEX $constraintName
    +              "CREATE UNIQUE INDEX $constraintName
              ON {$this->adapter->schema}.{$this->adapter->model} ($columns);"
                 );
             } elseif ($task->name == "REMOVE_UNIQUE_CONSTRAINT") {
                 $constraintName = $task->data->get('constraint_name');
     
                 $db->query(
    -                "DROP INDEX $constraintName ON {$this->adapter->schema}.{$this->adapter->model};"
    +              "DROP INDEX $constraintName ON {$this->adapter->schema}.{$this->adapter->model};"
                 );
             }
         }
    diff --git a/backend/class/dbdoc/plugin/sql/user.php b/backend/class/dbdoc/plugin/sql/user.php
    index 80e6ce7..4e23f31 100644
    --- a/backend/class/dbdoc/plugin/sql/user.php
    +++ b/backend/class/dbdoc/plugin/sql/user.php
    @@ -5,7 +5,7 @@
     use codename\architect\dbdoc\modeladapter\modeladapterGetSqlAdapter;
     
     /**
    - * plugin for providing and comparing user config in database
    + * plugin for providing and comparing user config in a database
      * @package architect
      */
     abstract class user extends \codename\architect\dbdoc\plugin\user
    diff --git a/backend/class/dbdoc/plugin/user.php b/backend/class/dbdoc/plugin/user.php
    index 71b01f7..ea24713 100644
    --- a/backend/class/dbdoc/plugin/user.php
    +++ b/backend/class/dbdoc/plugin/user.php
    @@ -29,7 +29,7 @@ public function getDefinition(): array
             // backup env key
             $prevEnv = $environment->getEnvironmentKey();
     
    -        // change env key
    +        // change an env key
             $environment->setEnvironmentKey($globalEnv);
     
             // get database name
    diff --git a/backend/class/dbdoc/task.php b/backend/class/dbdoc/task.php
    index 1bd9333..10dc6fb 100644
    --- a/backend/class/dbdoc/task.php
    +++ b/backend/class/dbdoc/task.php
    @@ -16,37 +16,37 @@ class task
          * [TASK_TYPE_ERROR description]
          * @var int
          */
    -    public const TASK_TYPE_ERROR = -1;
    +    public const int TASK_TYPE_ERROR = -1;
     
         /**
          * [TASK_TYPE_INFO description]
          * @var int
          */
    -    public const TASK_TYPE_INFO = 0;
    +    public const int TASK_TYPE_INFO = 0;
     
         /**
          * [TASK_TYPE_REQUIRED description]
          * @var int
          */
    -    public const TASK_TYPE_REQUIRED = 1;
    +    public const int TASK_TYPE_REQUIRED = 1;
     
         /**
          * [TASK_TYPE_SUGGESTED description]
          * @var int
          */
    -    public const TASK_TYPE_SUGGESTED = 2;
    +    public const int TASK_TYPE_SUGGESTED = 2;
     
         /**
          * [TASK_TYPE_OPTIONAL description]
          * @var int
          */
    -    public const TASK_TYPE_OPTIONAL = 3;
    +    public const int TASK_TYPE_OPTIONAL = 3;
     
         /**
          * [public description]
          * @var array
          */
    -    public const TASK_TYPES = [
    +    public const array TASK_TYPES = [
           self::TASK_TYPE_ERROR => 'TASK_TYPE_ERROR',
           self::TASK_TYPE_INFO => 'TASK_TYPE_INFO',
           self::TASK_TYPE_REQUIRED => 'TASK_TYPE_REQUIRED',
    @@ -57,7 +57,7 @@ class task
          * [public description]
          * @var string
          */
    -    public const EXCEPTION_DBDOC_TASK_RUN_PLUGIN_NOT_FOUND = "EXCEPTION_DBDOC_TASK_RUN_PLUGIN_NOT_FOUND";
    +    public const string EXCEPTION_DBDOC_TASK_RUN_PLUGIN_NOT_FOUND = "EXCEPTION_DBDOC_TASK_RUN_PLUGIN_NOT_FOUND";
         /**
          * the adapter
          * @var string
    diff --git a/backend/class/deploy/deployment.php b/backend/class/deploy/deployment.php
    index 7cf4464..8d7fc3e 100644
    --- a/backend/class/deploy/deployment.php
    +++ b/backend/class/deploy/deployment.php
    @@ -17,7 +17,7 @@
     class deployment
     {
         /**
    -     * name of this deployment (e.g. file basename)
    +     * name of this deployment (e.g., file basename)
          * @var null|string
          */
         protected ?string $name = null;
    @@ -99,7 +99,7 @@ protected function createDeploymentTasks(): void
         }
     
         /**
    -     * creates a task instance using a given name & config
    +     * creates a task instance using a given name and config
          * @param string $name [description]
          * @param array $task [description]
          * @return task           [description]
    diff --git a/backend/class/deploy/task.php b/backend/class/deploy/task.php
    index 271ed45..54e79ba 100644
    --- a/backend/class/deploy/task.php
    +++ b/backend/class/deploy/task.php
    @@ -9,6 +9,11 @@
      */
     abstract class task
     {
    +    /**
    +     * @var string
    +     */
    +    protected string $name;
    +
         /**
          * the task's configuration
          * @var null|config
    @@ -30,6 +35,7 @@ abstract class task
         public function __construct(deployment $deploymentInstance, string $name, config $config)
         {
             $this->deploymentInstance = $deploymentInstance;
    +        $this->name = $name;
             $this->config = $config;
             $this->handleConfig();
         }
    diff --git a/backend/class/deploy/task/client.php b/backend/class/deploy/task/client.php
    index 90335c8..d49e1bd 100644
    --- a/backend/class/deploy/task/client.php
    +++ b/backend/class/deploy/task/client.php
    @@ -26,10 +26,10 @@ protected function getClientInstance(string $clientName): object
             $dbValueObjecttype = new objecttype($this->getClientObjectTypeName());
             $dbValueObjectidentifier = new objectidentifier($clientName);
             return app::getForeignClient(
    -            $this->getDeploymentInstance()->getVirtualEnvironment(),
    -            $dbValueObjecttype,
    -            $dbValueObjectidentifier,
    -            false
    +          $this->getDeploymentInstance()->getVirtualEnvironment(),
    +          $dbValueObjecttype,
    +          $dbValueObjectidentifier,
    +          false
             );
         }
     
    diff --git a/backend/class/deploy/task/dbdoc.php b/backend/class/deploy/task/dbdoc.php
    index 50c6a65..23cc50b 100644
    --- a/backend/class/deploy/task/dbdoc.php
    +++ b/backend/class/deploy/task/dbdoc.php
    @@ -38,8 +38,8 @@ public function run(): taskresult
             try {
                 // build a new dbdoc instance
                 $dbdoc = new \codename\architect\dbdoc\dbdoc(
    -                $this->getDeploymentInstance()->getApp(),
    -                $this->getDeploymentInstance()->getVendor()
    +              $this->getDeploymentInstance()->getApp(),
    +              $this->getDeploymentInstance()->getVendor()
                 );
     
                 // run it with params
    diff --git a/backend/class/deploy/task/model.php b/backend/class/deploy/task/model.php
    index 12a387b..d1c7140 100644
    --- a/backend/class/deploy/task/model.php
    +++ b/backend/class/deploy/task/model.php
    @@ -61,10 +61,10 @@ protected function getModelInstance(string $schemaName = null, string $modelName
                 $dbValueObjecttype = new objecttype('database');
                 $dbValueObjectidentifier = new objectidentifier($connection);
                 return app::getForeignClient(
    -                $this->getDeploymentInstance()->getVirtualEnvironment(),
    -                $dbValueObjecttype,
    -                $dbValueObjectidentifier,
    -                $storeConnection
    +              $this->getDeploymentInstance()->getVirtualEnvironment(),
    +              $dbValueObjecttype,
    +              $dbValueObjectidentifier,
    +              $storeConnection
                 );
             });
             $model->setConfig(null, $schemaName, $modelName);
    diff --git a/backend/class/deploy/task/model/entry.php b/backend/class/deploy/task/model/entry.php
    index b7130fe..cdfb60e 100644
    --- a/backend/class/deploy/task/model/entry.php
    +++ b/backend/class/deploy/task/model/entry.php
    @@ -5,6 +5,7 @@
     use codename\architect\deploy\task\model;
     use codename\architect\deploy\taskresult;
     use codename\core\exception;
    +use DateMalformedStringException;
     use ReflectionException;
     
     /**
    @@ -22,6 +23,7 @@ class entry extends model
          * {@inheritDoc}
          * @return taskresult
          * @throws ReflectionException
    +     * @throws DateMalformedStringException
          * @throws exception
          */
         public function run(): taskresult
    diff --git a/backend/class/deploy/task/model/filter/update.php b/backend/class/deploy/task/model/filter/update.php
    index b50d441..06751c8 100644
    --- a/backend/class/deploy/task/model/filter/update.php
    +++ b/backend/class/deploy/task/model/filter/update.php
    @@ -5,6 +5,8 @@
     use codename\architect\deploy\task\model\filter;
     use codename\architect\deploy\taskresult;
     use codename\core\exception;
    +use codename\core\model\schematic\sql;
    +use DateMalformedStringException;
     use ReflectionException;
     
     /**
    @@ -22,6 +24,7 @@ class update extends filter
          * {@inheritDoc}
          * @return taskresult
          * @throws ReflectionException
    +     * @throws DateMalformedStringException
          * @throws exception
          */
         public function run(): taskresult
    @@ -41,6 +44,9 @@ public function run(): taskresult
             if (count($errors = $model->getErrors()) > 0) {
                 $text = "Model '{$model->getIdentifier()}' data validation error: " . print_r($errors, true);
             } else {
    +            if (!($model instanceof sql)) {
    +                throw new exception('EXCEPTION_DEPLOY_TASK_MODEL_MODEL_WRONG_TYPE', exception::$ERRORLEVEL_FATAL);
    +            }
                 $filterQueryComponents = $model->getFilterQueryComponents();
     
                 // perform the update
    diff --git a/backend/class/deploy/task/model/migrate.php b/backend/class/deploy/task/model/migrate.php
    index a7a80b4..efc9931 100644
    --- a/backend/class/deploy/task/model/migrate.php
    +++ b/backend/class/deploy/task/model/migrate.php
    @@ -7,6 +7,7 @@
     use codename\architect\deploy\taskresult\text;
     use codename\core\exception;
     use codename\core\transaction;
    +use DateMalformedStringException;
     use ReflectionException;
     
     class migrate extends model
    @@ -27,7 +28,7 @@ class migrate extends model
          */
         protected array $map = [];
         /**
    -     * foreign key update config in source model
    +     * foreign key update config in a source model
          * list of foreign key names
          * @var null|array
          */
    @@ -59,6 +60,7 @@ public function handleConfig(): void
          * {@inheritDoc}
          * @return taskresult
          * @throws ReflectionException
    +     * @throws DateMalformedStringException
          * @throws exception
          */
         public function run(): taskresult
    diff --git a/backend/class/deploy/task/model/query.php b/backend/class/deploy/task/model/query.php
    index 05962f7..bbdd474 100644
    --- a/backend/class/deploy/task/model/query.php
    +++ b/backend/class/deploy/task/model/query.php
    @@ -6,6 +6,7 @@
     use codename\architect\deploy\taskresult;
     use codename\architect\deploy\taskresult\text;
     use codename\core\exception;
    +use DateMalformedStringException;
     use ReflectionException;
     
     /**
    @@ -17,6 +18,7 @@ class query extends model
          * {@inheritDoc}
          * @return taskresult
          * @throws ReflectionException
    +     * @throws DateMalformedStringException
          * @throws exception
          */
         public function run(): taskresult
    diff --git a/backend/class/model/schematic/sql/dynamic.php b/backend/class/model/schematic/sql/dynamic.php
    index e3ac226..5042e7c 100644
    --- a/backend/class/model/schematic/sql/dynamic.php
    +++ b/backend/class/model/schematic/sql/dynamic.php
    @@ -47,7 +47,7 @@ public function setConfig(?string $connection, string $schema, string $table): m
                 $this->config = $this->loadConfig();
             }
     
    -        // Connection now defined in model .json
    +        // Connection now defined in model.json
             if ($this->config->exists("connection")) {
                 $connection = $this->config->get("connection");
             } else {
    diff --git a/backend/class/validator/number/tasktype.php b/backend/class/validator/number/tasktype.php
    index 7c2addf..0d6ae69 100644
    --- a/backend/class/validator/number/tasktype.php
    +++ b/backend/class/validator/number/tasktype.php
    @@ -20,9 +20,9 @@ public function __construct(bool $nullAllowed = false)
             }
     
             parent::__construct(
    -            false,
    -            min(array_keys(task::TASK_TYPES)),
    -            max(array_keys(task::TASK_TYPES))
    +          false,
    +          min(array_keys(task::TASK_TYPES)),
    +          max(array_keys(task::TASK_TYPES))
             );
         }
     
    diff --git a/composer.json b/composer.json
    index 86e8097..78b2e62 100644
    --- a/composer.json
    +++ b/composer.json
    @@ -15,7 +15,7 @@
         }
       ],
       "require": {
    -    "php": "^8.1"
    +    "php": "^8.3"
       },
       "autoload": {
         "psr-4": {