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 4eb789b..5423594 100644
--- a/backend/class/app.php
+++ b/backend/class/app.php
@@ -1,273 +1,288 @@
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 array|null $useAppstack
+ * @return array
+ * @throws ReflectionException
+ * @throws exception
+ */
+ public static function getModelConfigurations(string $filterByVendor = '', string $filterByApp = '', 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 an 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 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..bb03236 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..c35f865 100644
--- a/backend/class/context/deployment.php
+++ b/backend/class/context/deployment.php
@@ -1,48 +1,58 @@
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 DateMalformedStringException
+ * @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..2d8fc89 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..5b3fd91 100644
--- a/backend/class/dbdoc/dbdoc.php
+++ b/backend/class/dbdoc/dbdoc.php
@@ -1,531 +1,458 @@
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 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.
+ *
+ * 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:
+ * 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 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 string 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 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);
+ }
- 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(!$duplicate) {
- $filteredTasks[] = $newTask;
+
+ if ($driver == null) {
+ return null;
}
- }
- $tasks = array_merge($tasks, $filteredTasks);
+ $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
+ );
+ }
+
+ 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]);
}
- $availableTaskTypes = array_unique($availableTaskTypes);
- // translate:
- $availableTaskTypesAssoc = array();
- foreach($availableTaskTypes as $type) {
- $availableTaskTypesAssoc[task::TASK_TYPES[$type]] = $type;
+ /**
+ * returns the current app
+ * @return string
+ */
+ public function getApp(): string
+ {
+ return $this->app;
}
- return array(
- 'tasks' => $tasks,
- 'available_tasks' => $availableTasks,
- 'available_task_types' => $availableTaskTypesAssoc,
- 'executed_tasks' => $executedTasks,
- 'executed_task_types' => $exec_tasks
- );
+ /**
+ * returns the current vendor
+ * @return string
+ */
+ public function getVendor(): string
+ {
+ return $this->vendor;
+ }
+
+ /**
+ * [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..ee3d541 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 an 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..2871fc3 100644
--- a/backend/class/dbdoc/plugin/index.php
+++ b/backend/class/dbdoc/plugin/index.php
@@ -1,8 +1,9 @@
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..2db49aa 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 an 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 a 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 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?
+
+ // 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 = [];
+
+ 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']);
+ }
+ }
- }
-
-
- /**
- * 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;
+ /*
+ // 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 a 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 a 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..4a47695 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 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_
+ // 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 a 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..f42d759 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..c5db1d7 100644
--- a/backend/class/dbdoc/plugin/sql/fulltext.php
+++ b/backend/class/dbdoc/plugin/sql/fulltext.php
@@ -1,9 +1,14 @@
");
-
- 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 an existing group
+ foreach ($fulltextGroups as $groupName => $group) {
+ if ($fulltext['index_name'] == $groupName) {
+ $fulltextGroups[$groupName][] = $fulltext;
+ break;
+ }
+ }
+ } else {
+ // create a 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") {
+ // 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..e0ed5d6 100644
--- a/backend/class/dbdoc/plugin/sql/index.php
+++ b/backend/class/dbdoc/plugin/sql/index.php
@@ -1,9 +1,14 @@
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 {
+ // 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 an existing group
+ foreach ($indexGroups as $groupName => $group) {
+ if ($index['index_name'] == $groupName) {
+ $indexGroups[$groupName][] = $index;
+ break;
+ }
+ }
+ } else {
+ // create a 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") {
+ // 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..0a12883 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 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']) {
+ 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..6c45741 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..29e600d 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..56986ba 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 string DB_DEFAULT_DATA_TYPE = 'bigint';
+
+ /**
+ * default column type for primary keys on mysql
+ * @var string
+ */
+ public const string 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..a940736 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..4a51f57 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 array 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 the 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..00f3410 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 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..0297db0 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..6762db5 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..2b3a81a 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 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_
+ // 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 a 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 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
+ //
+ $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") {
+ // 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..779bc88 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 (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;
}
-
- // 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..b30d402 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..d7593a8 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..4e23f31 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..ea24713 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 an 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 int TASK_TYPE_ERROR = -1;
+
+ /**
+ * [TASK_TYPE_INFO description]
+ * @var int
+ */
+ public const int TASK_TYPE_INFO = 0;
+
+ /**
+ * [TASK_TYPE_REQUIRED description]
+ * @var int
+ */
+ public const int TASK_TYPE_REQUIRED = 1;
+
+ /**
+ * [TASK_TYPE_SUGGESTED description]
+ * @var int
+ */
+ public const int TASK_TYPE_SUGGESTED = 2;
+
+ /**
+ * [TASK_TYPE_OPTIONAL description]
+ * @var int
+ */
+ public const int TASK_TYPE_OPTIONAL = 3;
+
+ /**
+ * [public description]
+ * @var array
+ */
+ 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',
+ self::TASK_TYPE_SUGGESTED => 'TASK_TYPE_SUGGESTED',
+ self::TASK_TYPE_OPTIONAL => 'TASK_TYPE_OPTIONAL',
+ ];
+ /**
+ * [public description]
+ * @var string
+ */
+ public const string 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..8d7fc3e 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 and 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..54e79ba 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
+{
+ /**
+ * @var string
+ */
+ protected string $name;
+
+ /**
+ * 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->name = $name;
+ $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..d49e1bd 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..23cc50b 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..d1c7140 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..cdfb60e 100644
--- a/backend/class/deploy/task/model/entry.php
+++ b/backend/class/deploy/task/model/entry.php
@@ -1,112 +1,114 @@
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 DateMalformedStringException
+ * @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..06751c8 100644
--- a/backend/class/deploy/task/model/filter/update.php
+++ b/backend/class/deploy/task/model/filter/update.php
@@ -1,68 +1,75 @@
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 DateMalformedStringException
+ * @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 {
+ if (!($model instanceof sql)) {
+ throw new exception('EXCEPTION_DEPLOY_TASK_MODEL_MODEL_WRONG_TYPE', exception::$ERRORLEVEL_FATAL);
+ }
+ $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..efc9931 100644
--- a/backend/class/deploy/task/model/migrate.php
+++ b/backend/class/deploy/task/model/migrate.php
@@ -1,184 +1,192 @@
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 DateMalformedStringException;
+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 a 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 DateMalformedStringException
+ * @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..bbdd474 100644
--- a/backend/class/deploy/task/model/query.php
+++ b/backend/class/deploy/task/model/query.php
@@ -1,22 +1,32 @@
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 DateMalformedStringException
+ * @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..5042e7c 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..0d6ae69 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..78b2e62 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.3"
+ },
+ "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') %}
-
- | {{ app.vendor }} |
- {{ app.app }} |
- Details |
-
+
+ | {{ app.vendor }} |
+ {{ app.app }} |
+
+ Details
+ |
+
{% endfor %}
\ 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
{% for task in response.getData('dbdoc_stats>available_tasks') %}
- -
- Task [{{ task.getTaskTypeName() }}] [{{ task.adapter.getIdentifier() }}] {{ task.plugin }}::{{ task.name }} {{ print_r(task.data.get()) }}
-
+ -
+ Task [{{ task.getTaskTypeName() }}] [{{ task.adapter.getIdentifier() }}]
+ {{ task.plugin }}
+ ::
+ {{ task.name }} {{ print_r(task.data.get()) }}
+
{% endfor %}
Models
-{{ response.getData('table')|raw}}
+{{ response.getData('table')|raw }}
Statistics
{% for task in response.getData('dbdoc_stats>executed_tasks') %}
- -
- Task [{{ task.getTaskTypeName() }}] [{{ task.adapter.getIdentifier() }}] {{ task.plugin }}::{{ task.name }} {{ print_r(task.data.get()) }}
-
+ -
+ Task [{{ task.getTaskTypeName() }}] [{{ task.adapter.getIdentifier() }}]
+ {{ task.plugin }}
+ ::
+ {{ task.name }} {{ print_r(task.data.get()) }}
+
{% endfor %}
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