/** * Bind value to key * @return mixed * @param $key string * @param $val mixed */ function set($key, $val) { $fields = $this->fieldConf; unset($this->fieldsCache[$key]); // pre-process if field config available if (!empty($fields) && isset($fields[$key]) && is_array($fields[$key])) { // handle relations if (isset($fields[$key]['belongs-to-one'])) { // one-to-many, one-to-one if (is_null($val)) { $val = NULL; } elseif (is_object($val) && !($this->dbsType == 'mongo' && $val instanceof \MongoId)) { // fetch fkey from mapper if (!$val instanceof Cortex || $val->dry()) { trigger_error(self::E_INVALID_RELATION_OBJECT); } else { $relConf = $fields[$key]['belongs-to-one']; $rel_field = is_array($relConf) ? $relConf[1] : '_id'; $val = $val->get($rel_field, true); } } elseif ($this->dbsType == 'mongo' && !$val instanceof \MongoId) { $val = new \MongoId($val); } } elseif (isset($fields[$key]['has-one'])) { $relConf = $fields[$key]['has-one']; if (is_null($val)) { $val = $this->get($key); $val->set($relConf[1], NULL); } else { if (!$val instanceof Cortex) { $rel = $this->getRelInstance($relConf[0], null, $key); $rel->load(array('_id = ?', $val)); $val = $rel; } $val->set($relConf[1], $this->_id); } $this->saveCsd[$key] = $val; return $val; } elseif (isset($fields[$key]['belongs-to-many'])) { // many-to-many, unidirectional $fields[$key]['type'] = self::DT_JSON; $relConf = $fields[$key]['belongs-to-many']; $rel_field = is_array($relConf) ? $relConf[1] : '_id'; $val = $this->getForeignKeysArray($val, $rel_field, $key); } elseif (isset($fields[$key]['has-many'])) { // many-to-many, bidirectional $relConf = $fields[$key]['has-many']; if ($relConf['hasRel'] == 'has-many') { // custom setter $val = $this->emit('set_' . $key, $val); $val = $this->getForeignKeysArray($val, '_id', $key); $this->saveCsd[$key] = $val; // array of keys return $val; } elseif ($relConf['hasRel'] == 'belongs-to-one') { // TODO: many-to-one, bidirectional, inverse way trigger_error("not implemented"); } } // convert array content if (is_array($val) && $this->dbsType == 'sql') { if ($fields[$key]['type'] == self::DT_SERIALIZED) { $val = serialize($val); } elseif ($fields[$key]['type'] == self::DT_JSON) { $val = json_encode($val); } else { trigger_error(sprintf(self::E_ARRAY_DATATYPE, $key)); } } // add nullable polyfill if ($val === NULL && ($this->dbsType == 'jig' || $this->dbsType == 'mongo') && array_key_exists('nullable', $fields[$key]) && $fields[$key]['nullable'] === false) { trigger_error(sprintf(self::E_NULLABLE_COLLISION, $key)); } // MongoId shorthand if ($this->dbsType == 'mongo' && !$val instanceof \MongoId) { if ($key == '_id') { $val = new \MongoId($val); } elseif (preg_match('/INT/i', $fields[$key]['type']) && !isset($fields[$key]['relType'])) { $val = (int) $val; } } if (preg_match('/BOOL/i', $fields[$key]['type'])) { $val = !$val || $val === 'false' ? false : (bool) $val; if ($this->dbsType == 'sql') { $val = (int) $val; } } } // fluid SQL if ($this->fluid && $this->dbsType == 'sql') { $schema = new Schema($this->db); $table = $schema->alterTable($this->table); // add missing field if (!in_array($key, $table->getCols())) { // determine data type if (isset($this->fieldConf[$key]) && isset($this->fieldConf[$key]['type'])) { $type = $this->fieldConf[$key]['type']; } elseif (is_int($val)) { $type = $schema::DT_INT; } elseif (is_double($val)) { $type = $schema::DT_DOUBLE; } elseif (is_float($val)) { $type = $schema::DT_FLOAT; } elseif (is_bool($val)) { $type = $schema::DT_BOOLEAN; } elseif (date('Y-m-d H:i:s', strtotime($val)) == $val) { $type = $schema::DT_DATETIME; } elseif (date('Y-m-d', strtotime($val)) == $val) { $type = $schema::DT_DATE; } elseif (\UTF::instance()->strlen($val) <= 255) { $type = $schema::DT_VARCHAR256; } else { $type = $schema::DT_TEXT; } $table->addColumn($key)->type($type); $table->build(); // update mapper fields $newField = $table->getCols(true); $newField = $newField[$key]; $refl = new \ReflectionObject($this->mapper); $prop = $refl->getProperty('fields'); $prop->setAccessible(true); $fields = $prop->getValue($this->mapper); $fields[$key] = $newField + array('value' => NULL, 'changed' => NULL); $prop->setValue($this->mapper, $fields); } } // custom setter $val = $this->emit('set_' . $key, $val); return $this->mapper->set($key, $val); }
/** * get database connection information * @param $f3 * @param bool|false $exec * @return array */ protected function checkDatabase($f3, $exec = false) { foreach ($this->databases as $dbKey => $dbData) { $dbLabel = ''; $dbName = ''; $dbUser = ''; $dbConfig = []; // DB connection status $dbConnected = false; // DB type (e.g. MySql,..) $dbDriver = 'unknown'; // enable database ::setup() function in UI $dbSetupEnable = false; // check of everything is OK (connection, tables, columns, indexes,..) $dbStatusCheckCount = 0; // db queries for column fixes (types, indexes, unique) $dbColumnQueries = []; // tables that should exist in this DB $requiredTables = []; // check DB for valid connection $db = DB\Database::instance()->getDB($dbKey); switch ($dbKey) { case 'PF': $dbLabel = 'Pathfinder'; $dbName = Controller::getEnvironmentData('DB_NAME'); $dbUser = Controller::getEnvironmentData('DB_USER'); // enable (table) setup for this DB $dbSetupEnable = true; // get table data from model foreach ($dbData['models'] as $model) { $tableConfig = call_user_func($model . '::resolveConfiguration'); $requiredTables[$tableConfig['table']] = ['model' => $model, 'name' => $tableConfig['table'], 'fieldConf' => $tableConfig['fieldConf'], 'exists' => false, 'empty' => true, 'foreignKeys' => []]; } break; case 'CCP': $dbLabel = 'EVE-Online [SDE]'; $dbName = Controller::getEnvironmentData('DB_CCP_NAME'); $dbUser = Controller::getEnvironmentData('DB_CCP_USER'); // get table model from static table array foreach ($dbData['tables'] as $tableName) { $requiredTables[$tableName] = ['exists' => false, 'empty' => true]; } break; } if ($db) { // db connect was successful $dbConnected = true; $dbDriver = $db->driver(); $dbConfig = $this->checkDBConfig($f3, $db); // get tables $schema = new SQL\Schema($db); $currentTables = $schema->getTables(); // check each table for changes foreach ($requiredTables as $requiredTableName => $data) { $tableExists = false; $tableEmpty = true; // Check if table status is OK (no errors/warnings,..) $tableStatusCheckCount = 0; $currentColumns = []; if (in_array($requiredTableName, $currentTables)) { // Table exists $tableExists = true; // get existing table columns and column related constraints (if exists) $tableModifierTemp = new MySQL\TableModifier($requiredTableName, $schema); $currentColumns = $tableModifierTemp->getCols(true); // get row count $countRes = $db->exec("SELECT COUNT(*) `num` FROM " . $db->quotekey($requiredTableName)); $tableEmpty = $countRes[0]['num'] > 0 ? false : true; } else { // table missing $dbStatusCheckCount++; $tableStatusCheckCount++; } foreach ((array) $data['fieldConf'] as $columnName => $fieldConf) { $columnStatusCheck = true; $foreignKeyStatusCheck = true; $requiredTables[$requiredTableName]['fieldConf'][$columnName]['requiredType'] = $fieldConf['type']; $requiredTables[$requiredTableName]['fieldConf'][$columnName]['requiredIndex'] = $fieldConf['index'] ? '1' : '0'; $requiredTables[$requiredTableName]['fieldConf'][$columnName]['requiredUnique'] = $fieldConf['unique'] ? '1' : '0'; if (array_key_exists($columnName, $currentColumns)) { // column exists // get tableModifier -> possible column update $tableModifier = new MySQL\TableModifier($requiredTableName, $schema); // get new column and copy Schema from existing column $col = new MySQL\Column($columnName, $tableModifier); $col->copyfrom($currentColumns[$columnName]); $currentColType = $currentColumns[$columnName]['type']; $currentColIndexData = call_user_func($data['model'] . '::indexExists', [$columnName]); $currentColIndex = is_array($currentColIndexData); $hasIndex = $currentColIndex ? '1' : '0'; $hasUnique = $currentColIndexData['unique'] ? '1' : '0'; $changedType = false; $changedUnique = false; $changedIndex = false; // set (new) column information ------------------------------------------------------- $requiredTables[$requiredTableName]['fieldConf'][$columnName]['exists'] = true; $requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentType'] = $currentColType; $requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentIndex'] = $hasIndex; $requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentUnique'] = $hasUnique; // check constraint ------------------------------------------------------------------- if (isset($fieldConf['constraint'])) { // add or update constraints foreach ((array) $fieldConf['constraint'] as $constraintData) { $constraint = $col->newConstraint($constraintData); $foreignKeyExists = $col->constraintExists($constraint); $requiredTables[$requiredTableName]['foreignKeys'][] = ['exists' => $foreignKeyExists, 'keyName' => $constraint->getConstraintName()]; $col->addConstraint($constraint); if (!$foreignKeyExists) { $tableStatusCheckCount++; $foreignKeyStatusCheck = false; } } } // check type changed ----------------------------------------------------------------- if ($fieldConf['type'] !== 'JSON' && !$schema->isCompatible($fieldConf['type'], $currentColType)) { // column type has changed $changedType = true; $columnStatusCheck = false; $tableStatusCheckCount++; } // check if column unique changed ----------------------------------------------------- $indexUpdate = false; $indexKey = (bool) $hasIndex; $indexUnique = (bool) $hasUnique; if ($currentColIndexData['unique'] != $fieldConf['unique']) { $changedUnique = true; $columnStatusCheck = false; $tableStatusCheckCount++; $indexUpdate = true; $indexUnique = (bool) $fieldConf['unique']; } // check if column index changed ------------------------------------------------------ if ($currentColIndex != $fieldConf['index']) { $changedIndex = true; $columnStatusCheck = false; $tableStatusCheckCount++; $indexUpdate = true; $indexKey = (bool) $fieldConf['index']; } // build table with changed columns --------------------------------------------------- if (!$columnStatusCheck || !$foreignKeyStatusCheck) { if (!$columnStatusCheck) { // IMPORTANT: setType is always required! Even if type has not changed $col->type($fieldConf['type']); // update/change/delete index/unique keys if ($indexUpdate) { if ($hasIndex) { $tableModifier->dropIndex($columnName); } if ($indexKey) { $tableModifier->addIndex($columnName, $indexUnique); } } $tableModifier->updateColumn($columnName, $col); } $buildStatus = $tableModifier->build($exec); if (is_array($buildStatus) || is_string($buildStatus)) { // query strings for change available $dbColumnQueries = array_merge($dbColumnQueries, (array) $buildStatus); } } // set (new) column information ------------------------------------------------------- $requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedType'] = $changedType; $requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedUnique'] = $changedUnique; $requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedIndex'] = $changedIndex; } elseif (!isset($fieldConf['has-manny']) && isset($fieldConf['type'])) { // column not exists but it is required! // columns that do not match this criteria ("mas-manny") are "virtual" fields // and can be ignored $requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentType'] = ''; $columnStatusCheck = false; $tableStatusCheckCount++; } $requiredTables[$requiredTableName]['fieldConf'][$columnName]['statusCheck'] = $columnStatusCheck; } $dbStatusCheckCount += $tableStatusCheckCount; $requiredTables[$requiredTableName]['empty'] = $tableEmpty; $requiredTables[$requiredTableName]['exists'] = $tableExists; $requiredTables[$requiredTableName]['statusCheckCount'] = $tableStatusCheckCount; } } else { // DB connection failed $dbStatusCheckCount++; } if ($exec) { $f3->reroute('@setup'); } $this->databases[$dbKey]['info'] = ['db' => $db, 'label' => $dbLabel, 'driver' => $dbDriver, 'name' => $dbName, 'user' => $dbUser, 'dbConfig' => $dbConfig, 'setupEnable' => $dbSetupEnable, 'connected' => $dbConnected, 'statusCheckCount' => $dbStatusCheckCount, 'columnQueries' => $dbColumnQueries, 'tableData' => $requiredTables]; } return $this->databases; }
/** * get tableModifier class for this table * @return bool|DB\SQL\TableModifier */ public static function getTableModifier() { $df = parent::resolveConfiguration(); $schema = new Schema($df['db']); $tableModifier = $schema->alterTable($df['table']); return $tableModifier; }
/** * get all table names from a DB * @param string $database * @return array|bool */ public function getTables($database = 'PF') { $schema = new SQL\Schema($this->getDB($database)); return $schema->getTables(); }