Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80e889946b | |||
| cf59109a47 | |||
| ae59ffaa97 | |||
| 616dc01c21 | |||
| 877ca38c0b | |||
| 8a3dfbe9f0 | |||
| d055bd1132 |
56
README.md
56
README.md
@ -195,3 +195,59 @@ print_r($result);
|
||||
|
||||
?>
|
||||
```
|
||||
|
||||
## Maintenance of database schema
|
||||
|
||||
When developing a project, a situation arises where the DB schema needs to be modified and distributed to all installations. To ease these problems, the Maintenance class was created, which contains several useful methods. These first check if the schema contains the requested change and if it doesn't, they apply a FIX. Here is a short demonstration of what we can fix automatically in the project when changing the DB schema.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/../src/DBmodel.php';
|
||||
require_once __DIR__.'/../src/Maintenance.php';
|
||||
|
||||
$db = new \TPsoft\DBmodel\DBmodel('mysql:host=127.0.0.1;dbname=test;charset=utf8mb4', 'test', 'test');
|
||||
|
||||
$maintenance = new \TPsoft\DBmodel\Maintenance($db);
|
||||
|
||||
// Check if existing table, if not create it by definition
|
||||
$suc = $maintenance->checkDBTable('users', '
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`email` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
');
|
||||
echo "\nCheckDBTable: "; var_dump($suc);
|
||||
|
||||
// Check if existing column, if not add it by definition
|
||||
$suc = $maintenance->checkDBAdd('users', 'password', 'varchar(255) DEFAULT NULL');
|
||||
echo "\nCheckDBAdd: "; var_dump($suc);
|
||||
|
||||
// Check if existing column
|
||||
$suc = $maintenance->existsColumn('users', 'email');
|
||||
echo "\nExistsColumn: "; var_dump($suc);
|
||||
|
||||
// Check if existing column in new name, if not rename it
|
||||
$suc = $maintenance->checkDBRename('users', 'email', 'email_new', 'varchar(255) DEFAULT NULL');
|
||||
echo "\nCheckDBRename: "; var_dump($suc);
|
||||
|
||||
// Chceck type of column, if not matched change it
|
||||
$suc = $maintenance->checkDBRetype('users', 'email_new', 'varchar(128)', 'varchar(128) DEFAULT NULL AFTER `password`');
|
||||
echo "\nCheckDBRetype: "; var_dump($suc);
|
||||
|
||||
// Remove column from table
|
||||
$suc = $maintenance->checkDBRemove('users', 'email_new');
|
||||
echo "\nCheckDBRemove: "; var_dump($suc);
|
||||
|
||||
// Add index for column
|
||||
$suc = $maintenance->addKey('users', 'name');
|
||||
echo "\nAddKey: "; var_dump($suc);
|
||||
|
||||
// Drop index by column name
|
||||
$suc = $maintenance->dropKey('users', 'name');
|
||||
echo "\nDropKey: "; var_dump($suc);
|
||||
|
||||
echo "\nDone\n\n";
|
||||
|
||||
?>
|
||||
```
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"description": "This library extends the builtin PDO object by several useful features. ",
|
||||
"type": "library",
|
||||
"readme": "README.md",
|
||||
"keywords": ["db", "model", "pdo"],
|
||||
"keywords": ["db", "model", "pdo", "mysql", "sqlite"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Igor Mino",
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
TPsoft.org 2000-2025
|
||||
file for controlers/*.php
|
||||
|
||||
Molestones:
|
||||
2025-05-27 21:36 Created
|
||||
*/
|
||||
|
||||
class Test extends \TPsoft\DBmodel\DBmodel {
|
||||
|
||||
public $tables = array(
|
||||
'test' => array(
|
||||
'name' => 'test',
|
||||
'primary_key_name' => 'id',
|
||||
'allow_attributes' => array(
|
||||
'name' => 'varchar(255)',
|
||||
'created' => 'datetime'
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
public function exist($primary_key = null) {
|
||||
return $this->existRecord('test', $primary_key);
|
||||
}
|
||||
|
||||
public function test($primary_key = null, $data = array()) {
|
||||
return $this->record('test', $primary_key, $data);
|
||||
}
|
||||
|
||||
public function testBy($colname, $colvalue) {
|
||||
return $this->recordBy('test', $colname, $colvalue);
|
||||
}
|
||||
|
||||
public function testSave($data = array()) {
|
||||
return $this->test($this->exist($data) ? $data : null, $data);
|
||||
}
|
||||
|
||||
public function testEmpty() {
|
||||
return $this->recordEmpty('test');
|
||||
}
|
||||
|
||||
public function testAttributes() {
|
||||
return $this->typesAttributes('test');
|
||||
}
|
||||
|
||||
public function testCount() {
|
||||
return $this->count('test');
|
||||
}
|
||||
|
||||
public function getList($search = array(), $reverse = false, $concat_or = false) {
|
||||
return $this->search('test')
|
||||
->where($search, $concat_or)
|
||||
->order(array('id' => $reverse ? 'DESC' : 'ASC'))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function testCombo($col_key, $col_value, $add_empty = false) {
|
||||
return $this->search('test')
|
||||
->toCombo($col_key, $col_value, $add_empty);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
@ -4,20 +4,31 @@ namespace TPsoft\DBmodel;
|
||||
|
||||
class Creator {
|
||||
|
||||
private $dbh;
|
||||
private DBmodel $dbh;
|
||||
private string $rootDir = '';
|
||||
|
||||
public function __construct($dbh) {
|
||||
public function __construct(?DBmodel $dbh = null) {
|
||||
if (is_null($dbh)) {
|
||||
if (!is_null(DBmodel::$instance)) {
|
||||
$this->dbh = DBmodel::$instance;
|
||||
} else {
|
||||
throw new \Exception('DB handler is null');
|
||||
}
|
||||
} else {
|
||||
$this->dbh = $dbh;
|
||||
}
|
||||
}
|
||||
|
||||
public function interact() {
|
||||
$name = $this->readline('New model name: ');
|
||||
$table = $this->readline('Table name: ');
|
||||
$entity = $this->readline('Entity name: ');
|
||||
$table = strtolower($name);
|
||||
$table = $this->readline('Table name ['.$table.']: ', $table);
|
||||
$entity = $this->readline('Entity name ['.$table.']: ', $table);
|
||||
$namespace = $this->readline('Namespace [App\Models]: ', 'App\Models');
|
||||
if (strlen($entity) <= 0) {
|
||||
$entity = $table;
|
||||
}
|
||||
$model_filepath = $this->rootDir().'/models/'.$name.'.php';
|
||||
$model_filepath = $this->rootDir().'/Models/'.$name.'.php';
|
||||
if ($this->readline("Create MODEL class '$model_filepath'? (y - yes, other - no) ") != 'y') {
|
||||
echo "Creating of MODEL class is skipped\n";
|
||||
return false;
|
||||
@ -38,11 +49,11 @@ class Creator {
|
||||
}
|
||||
}
|
||||
$table_columns = $this->dbh->getTableColumns($table);
|
||||
$suc = $this->modelSave($model_filepath, $name, $table, $entity, $table_columns['pks'], $table_columns['columns'], $table_columns['types']);
|
||||
$suc = $this->modelSave($model_filepath, $name, $table, $entity, $table_columns['pks'], $table_columns['columns'], $table_columns['types'], $namespace);
|
||||
echo $suc ? "MODEL class created" : 'Error: MODEL class not created';
|
||||
}
|
||||
|
||||
private function modelSave($filepath, $name, $tablename, $entity, $pks, $columns, $types) {
|
||||
private function modelSave($filepath, $name, $tablename, $entity, $pks, $columns, $types, $namespace) {
|
||||
if (is_array($pks) && count($pks) > 1) {
|
||||
$primary_key_name = "array('".implode("', '", $pks)."')";
|
||||
$columns = array_merge($pks, $columns);
|
||||
@ -84,6 +95,7 @@ class Creator {
|
||||
$data[\'changed_dt\'] = date(\'Y-m-d H:i:s\');
|
||||
}'
|
||||
: '';
|
||||
$namespace_str = strlen($namespace) > 0 ? "\nnamespace $namespace;\n" : '';
|
||||
$content = '<?'."php
|
||||
/*
|
||||
TPsoft.org 2000-".date('Y')."
|
||||
@ -92,12 +104,12 @@ class Creator {
|
||||
Milestones:
|
||||
".date('Y-m-d H:i')." Created
|
||||
*/
|
||||
|
||||
".$namespace_str."
|
||||
class ".$name." extends \TPsoft\DBmodel\DBmodel {
|
||||
|
||||
public $"."tables = array(
|
||||
'".$entity."' => array(
|
||||
'name' => '".$entity."',
|
||||
'name' => '".$tablename."',
|
||||
'primary_key_name' => ".$primary_key_name.",
|
||||
'allow_attributes' => array(
|
||||
".implode(",\n\t\t\t\t", $allowed_attributes)."
|
||||
@ -140,6 +152,19 @@ class ".$name." extends \TPsoft\DBmodel\DBmodel {
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getListOrganize($"."cola_name, $"."search = array(), $"."reverse = false, $"."concat_or = false) {
|
||||
$"."all = $"."this->getList($"."search, $"."reverse, $"."concat_or);
|
||||
$"."ret = array();
|
||||
if (is_array($"."all)) foreach ($"."all as $"."key => $"."row) {
|
||||
$"."ret[$"."row[$"."cola_name]] = $"."row;
|
||||
}
|
||||
return $"."ret;
|
||||
}
|
||||
|
||||
public function getListByID($"."search = array(), $"."reverse = false, $"."concat_or = false) {
|
||||
return $"."this->getListOrganize($primary_key_name, $"."search, $"."reverse, $"."concat_or);
|
||||
}
|
||||
|
||||
public function ".$entity_one."Combo($"."col_key, $"."col_value, $"."add_empty = false) {
|
||||
return $"."this->search('".$entity."')
|
||||
->toCombo($"."col_key, $"."col_value, $"."add_empty);
|
||||
@ -166,11 +191,14 @@ class ".$name." extends \TPsoft\DBmodel\DBmodel {
|
||||
return $line;
|
||||
}
|
||||
|
||||
public function rootDir() {
|
||||
public function rootDir(?string $dir = null) {
|
||||
if (!is_null($dir)) {
|
||||
$this->rootDir = $dir;
|
||||
}
|
||||
if (strlen($this->rootDir) > 0) return $this->rootDir;
|
||||
return dirname(dirname(__FILE__));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
|
||||
@ -154,6 +154,11 @@ class DBmodel
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function getDBtype()
|
||||
{
|
||||
return $this->dbh->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||
}
|
||||
|
||||
public function existTable($table_id)
|
||||
{
|
||||
$query = sprintf(
|
||||
@ -269,11 +274,14 @@ class DBmodel
|
||||
return false;
|
||||
}
|
||||
if (count($primary_key_names) > 1) return true;
|
||||
$last_id = $this->getLastInsertID();
|
||||
/*
|
||||
if (method_exists($this->dbh, 'getLastInsertID')) {
|
||||
$last_id = $this->getLastInsertID();
|
||||
} else {
|
||||
$last_id = $this->getOne('SELECT last_insert_id()');
|
||||
}
|
||||
*/
|
||||
return $last_id;
|
||||
}
|
||||
|
||||
@ -304,12 +312,14 @@ class DBmodel
|
||||
$query = sprintf(
|
||||
'UPDATE %s'
|
||||
. ' SET %s'
|
||||
. ' WHERE %s'
|
||||
. ' LIMIT 1',
|
||||
. ' WHERE %s',
|
||||
$table,
|
||||
$set,
|
||||
$this->buildWherePrimaryKey($table_id, $primary_key)
|
||||
);
|
||||
if ($this->getDBtype() == 'mysql') {
|
||||
$query .= ' LIMIT 1';
|
||||
}
|
||||
$this->_debug('[DBmodel][record][UPDATE]: ' . $query);
|
||||
$ret = $this->query($query);
|
||||
if ($ret === false) {
|
||||
@ -331,11 +341,13 @@ class DBmodel
|
||||
}
|
||||
$query = sprintf(
|
||||
'DELETE FROM %s'
|
||||
. ' WHERE %s'
|
||||
. ' LIMIT 1',
|
||||
. ' WHERE %s',
|
||||
$table,
|
||||
$this->buildWherePrimaryKey($table_id, $primary_key)
|
||||
);
|
||||
if ($this->getDBtype() == 'mysql') {
|
||||
$query .= ' LIMIT 1';
|
||||
}
|
||||
$this->_debug('[DBmodel][record][DELETE]: ' . $query);
|
||||
$ret = $this->query($query);
|
||||
if ($ret === false) {
|
||||
@ -548,6 +560,15 @@ class DBmodel
|
||||
}
|
||||
|
||||
public function getTableColumns($table_name) {
|
||||
$db_type = $this->getDBtype();
|
||||
switch ($db_type) {
|
||||
case 'mysql': return $this->getTableColumnsMYSQL($table_name);
|
||||
case 'sqlite': return $this->getTableColumnsSQLITE($table_name);
|
||||
default: new \Exception('Unknown DB type: ' . $db_type);
|
||||
}
|
||||
}
|
||||
|
||||
public function getTableColumnsMYSQL($table_name) {
|
||||
$desc = $this->getAll(sprintf('DESC %s', $table_name));
|
||||
$desc = $this->arrayKeysToLowerCase($desc);
|
||||
$pks = array();
|
||||
@ -567,6 +588,26 @@ class DBmodel
|
||||
return array('pks' => $pks, 'columns' => $columns, 'types' => $types);
|
||||
}
|
||||
|
||||
public function getTableColumnsSQLITE($table_name) {
|
||||
$desc = $this->getAll(sprintf('PRAGMA table_info(%s)', $table_name));
|
||||
$desc = $this->arrayKeysToLowerCase($desc);
|
||||
$pks = array();
|
||||
$columns = array();
|
||||
foreach ($desc as $d) {
|
||||
$colname = $d['name'];
|
||||
$types[$colname] = $d['type'];
|
||||
if ($d['pk'] == 1) {
|
||||
$pks[] = $colname;
|
||||
if (strtoupper($d['type']) != 'INTEGER') {
|
||||
$columns[] = $colname;
|
||||
}
|
||||
} else {
|
||||
$columns[] = $colname;
|
||||
}
|
||||
}
|
||||
return array('pks' => $pks, 'columns' => $columns, 'types' => $types);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* HELPER METHODS
|
||||
*/
|
||||
@ -674,12 +715,12 @@ class DBmodel
|
||||
foreach ($data[$column] as $key => $val) {
|
||||
$prefix = $this->extractPrefix($val, $where_prefixes);
|
||||
$n_val = str_replace($prefix, '', $val);
|
||||
$data[$column] = $prefix . date('Y-m-d H:i:s', strtotime($n_val));
|
||||
$data[$column] = $prefix . date('Y-m-d H:i:s', is_numeric($n_val) ? $n_val : strtotime($n_val));
|
||||
}
|
||||
} else {
|
||||
$prefix = $this->extractPrefix($data[$column], $where_prefixes);
|
||||
$n_val = str_replace($prefix, '', $data[$column]);
|
||||
$data[$column] = $prefix . date('Y-m-d H:i:s', strtotime($n_val));
|
||||
$data[$column] = $prefix . date('Y-m-d H:i:s', is_numeric($n_val) ? $n_val : strtotime($n_val));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -851,7 +892,7 @@ class DBmodel
|
||||
return $this->allowNextError();
|
||||
}
|
||||
|
||||
public function import($objModel)
|
||||
public function import(DBmodel $objModel)
|
||||
{
|
||||
if (is_null($objModel)) return false;
|
||||
if (
|
||||
|
||||
@ -24,7 +24,7 @@ class Maintenance
|
||||
|
||||
public string $lastMessage = '';
|
||||
|
||||
private DBmodel $dbh;
|
||||
protected DBmodel $dbh;
|
||||
|
||||
public function __construct(?DBmodel $dbh)
|
||||
{
|
||||
@ -79,19 +79,50 @@ class Maintenance
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function checkDBTable($table_name, $definition)
|
||||
public function existsTable($table_name)
|
||||
{
|
||||
$db_type = $this->dbh->getDBtype();
|
||||
switch ($db_type) {
|
||||
case 'mysql':
|
||||
return $this->testDB(
|
||||
'SHOW TABLES LIKE "' . $table_name . '"',
|
||||
$table_name
|
||||
);
|
||||
case 'sqlite':
|
||||
return $this->testDB(
|
||||
'SELECT name FROM sqlite_master WHERE TYPE = "table" AND name LIKE "' . $table_name . '"',
|
||||
$table_name
|
||||
);
|
||||
default:
|
||||
new \Exception('Unknown DB type: ' . $db_type);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkDBTable($table_name, $definition, $after_definition = '')
|
||||
{
|
||||
if (strlen($table_name) <= 0) {
|
||||
$this->lastMessage = 'Table ' . strtoupper($table_name) . ' - invalid index';
|
||||
return Maintenance::ABNORMAL;
|
||||
}
|
||||
$check_query = '';
|
||||
$db_type = $this->dbh->getDBtype();
|
||||
switch ($db_type) {
|
||||
case 'mysql':
|
||||
$check_query = 'SHOW TABLES LIKE "' . $table_name . '"';
|
||||
break;
|
||||
case 'sqlite':
|
||||
$check_query = 'SELECT name FROM sqlite_master WHERE TYPE = "table" AND name LIKE "' . $table_name . '"';
|
||||
break;
|
||||
default:
|
||||
new \Exception('Unknown DB type: ' . $db_type);
|
||||
}
|
||||
$status = $this->checkDB(
|
||||
// TEST query
|
||||
'SHOW TABLES LIKE "' . $table_name . '"',
|
||||
$check_query,
|
||||
// TEST output
|
||||
$table_name,
|
||||
// FIX query
|
||||
'CREATE TABLE `' . $table_name . '` (' . $definition . ')'
|
||||
'CREATE TABLE `' . $table_name . '` (' . $definition . ') ' . $after_definition
|
||||
);
|
||||
$this->lastMessage = 'Table ' . strtoupper($table_name) . ' (' . $table_name . ')';
|
||||
return $status;
|
||||
@ -138,7 +169,7 @@ class Maintenance
|
||||
|
||||
public function checkDBRetype($table_name, $column, $new_type, $definition)
|
||||
{
|
||||
if (!$this->existsColumn($table_name, $column_old)) {
|
||||
if (!$this->existsColumn($table_name, $column)) {
|
||||
return Maintenance::ABNORMAL;
|
||||
}
|
||||
$status = $this->checkDB(
|
||||
|
||||
@ -7,6 +7,7 @@ $db = new \TPsoft\DBmodel\DBmodel('mysql:host=127.0.0.1;dbname=test;charset=utf8
|
||||
|
||||
$maintenance = new \TPsoft\DBmodel\Maintenance($db);
|
||||
|
||||
// Check if existing table, if not create it by definition
|
||||
$suc = $maintenance->checkDBTable('users', '
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
@ -15,24 +16,31 @@ $suc = $maintenance->checkDBTable('users', '
|
||||
');
|
||||
echo "\nCheckDBTable: "; var_dump($suc);
|
||||
|
||||
// Check if existing column, if not add it by definition
|
||||
$suc = $maintenance->checkDBAdd('users', 'password', 'varchar(255) DEFAULT NULL');
|
||||
echo "\nCheckDBAdd: "; var_dump($suc);
|
||||
|
||||
// Check if existing column
|
||||
$suc = $maintenance->existsColumn('users', 'email');
|
||||
echo "\nExistsColumn: "; var_dump($suc);
|
||||
|
||||
// Check if existing column in new name, if not rename it
|
||||
$suc = $maintenance->checkDBRename('users', 'email', 'email_new', 'varchar(255) DEFAULT NULL');
|
||||
echo "\nCheckDBRename: "; var_dump($suc);
|
||||
|
||||
// Chceck type of column, if not matched change it
|
||||
$suc = $maintenance->checkDBRetype('users', 'email_new', 'varchar(128)', 'varchar(128) DEFAULT NULL AFTER `password`');
|
||||
echo "\nCheckDBRetype: "; var_dump($suc);
|
||||
|
||||
// Remove column from table
|
||||
$suc = $maintenance->checkDBRemove('users', 'email_new');
|
||||
echo "\nCheckDBRemove: "; var_dump($suc);
|
||||
|
||||
// Add index for column
|
||||
$suc = $maintenance->addKey('users', 'name');
|
||||
echo "\nAddKey: "; var_dump($suc);
|
||||
|
||||
// Drop index by column name
|
||||
$suc = $maintenance->dropKey('users', 'name');
|
||||
echo "\nDropKey: "; var_dump($suc);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user