Compare commits
12 Commits
ae4f73ecb6
...
v1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 2dbaa2bf77 | |||
| 80e889946b | |||
| cf59109a47 | |||
| ae59ffaa97 | |||
| 616dc01c21 | |||
| 877ca38c0b | |||
| 8a3dfbe9f0 | |||
| d055bd1132 | |||
| c2773db2dd | |||
| 0642d7a8ca | |||
| 55e67bd8c2 | |||
| 136e4905d6 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/vendor
|
||||||
|
/composer.lock
|
||||||
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";
|
||||||
|
|
||||||
|
?>
|
||||||
|
```
|
||||||
|
|||||||
224
bin/dbmodel-files
Normal file
224
bin/dbmodel-files
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
include $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
echo "ℹ️ DBmodel files\n\n";
|
||||||
|
|
||||||
|
$project_root = findProjectRoot(__DIR__);
|
||||||
|
|
||||||
|
if (!$project_root) {
|
||||||
|
echo "⛔ Project root not found\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$composer_arr = json_decode(file_get_contents($project_root . '/composer.json'), true);
|
||||||
|
|
||||||
|
echo "Project root: {$project_root}\n";
|
||||||
|
$source_dirs = array_values($composer_arr['autoload']['psr-4']);
|
||||||
|
foreach ($source_dirs as $index => $source_dir) {
|
||||||
|
echo "➡️ [$index]: $project_root/$source_dir\n";
|
||||||
|
}
|
||||||
|
$selected = readline("❔ Select source dir [default: 0]: ");
|
||||||
|
$selected = empty($selected) ? 0 : $selected;
|
||||||
|
if (!isset($source_dirs[$selected])) {
|
||||||
|
echo "⛔ Invalid selection\n";
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
$source_dir = $project_root . '/' . $source_dirs[$selected];
|
||||||
|
$source_dir = rtrim($source_dir, '/') . '/';
|
||||||
|
$namespaces = array_keys($composer_arr['autoload']['psr-4']);
|
||||||
|
$namespace = $namespaces[$selected];
|
||||||
|
echo "✔️ Source dir: {$source_dir} with namespace: {$namespace}\n";
|
||||||
|
|
||||||
|
// Copy files
|
||||||
|
echo "ℹ️ Copy files\n";
|
||||||
|
|
||||||
|
copyFile($project_root . '/config/Configuration.php', '<' . '?php
|
||||||
|
|
||||||
|
$others_config = scandir(__DIR__);
|
||||||
|
$loaded = false;
|
||||||
|
foreach ($others_config as $file) {
|
||||||
|
if ($file == basename(__FILE__)) continue;
|
||||||
|
if (substr($file, -4) == \'.php\') {
|
||||||
|
require_once __DIR__ . \'/\' . $file;
|
||||||
|
$loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$loaded) {
|
||||||
|
class Configuration
|
||||||
|
{
|
||||||
|
public const DB_TYPE = \'sqlite\';
|
||||||
|
// MySQL
|
||||||
|
public const DB_HOST = \'localhost\';
|
||||||
|
public const DB_USER = \'username\';
|
||||||
|
public const DB_PASS = \'****************\';
|
||||||
|
public const DB_NAME = \'databasename\';
|
||||||
|
// SQLite
|
||||||
|
public const DB_FILEPATH = __DIR__ . \'/../../data/database.db\';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$protocol = (!empty($_SERVER[\'HTTPS\']) && $_SERVER[\'HTTPS\'] !== \'off\') ? "https://" : "http://";
|
||||||
|
$host = $_SERVER[\'HTTP_HOST\'];
|
||||||
|
$uri = $_SERVER[\'REQUEST_URI\']; // obsahuje aj query string
|
||||||
|
|
||||||
|
define(\'URL_PREFIX\', $protocol.$host.str_replace(basename($_SERVER[\'SCRIPT_NAME\']), \'\', $_SERVER[\'SCRIPT_NAME\']));
|
||||||
|
');
|
||||||
|
|
||||||
|
copyFile($source_dir . 'Maintenance.php', '<' . '?php
|
||||||
|
|
||||||
|
namespace ' . $namespace . ';
|
||||||
|
|
||||||
|
class Maintenance extends \TPsoft\DBmodel\Maintenance
|
||||||
|
{
|
||||||
|
|
||||||
|
public function database()
|
||||||
|
{
|
||||||
|
if (!$this->existsTable(\'options\')) {
|
||||||
|
$this->checkDBTable(\'options\', \'
|
||||||
|
`key` VARCHAR(255) NOT NULL PRIMARY KEY,
|
||||||
|
`value` VARCHAR(255) NOT NULL
|
||||||
|
\');
|
||||||
|
$this->dbver(1);
|
||||||
|
}
|
||||||
|
$dbver = $this->dbver();
|
||||||
|
if ($dbver == 1) {
|
||||||
|
// create new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function settings(string $key, ?string $value = null): string|false
|
||||||
|
{
|
||||||
|
if (is_null($value)) {
|
||||||
|
return $this->dbh->getOne(sprintf(\'SELECT `value` FROM `options` WHERE `key` = %s\', $this->dbh->quote($key)));
|
||||||
|
} else {
|
||||||
|
$db_type = $this->dbh->getDBtype();
|
||||||
|
switch ($db_type) {
|
||||||
|
case \'mysql\':
|
||||||
|
return $this->dbh->query(sprintf(
|
||||||
|
\'INSERT INTO `options` (`key`, `value`) VALUES (%s, %s) ON DUPLICATE KEY UPDATE `value` = %s\',
|
||||||
|
$this->dbh->quote($key),
|
||||||
|
$this->dbh->quote($value),
|
||||||
|
$this->dbh->quote($value)
|
||||||
|
)) !== false;
|
||||||
|
break;
|
||||||
|
case \'sqlite\':
|
||||||
|
return $this->dbh->query(sprintf(
|
||||||
|
\'INSERT INTO `options` (`key`, `value`) VALUES (%s, %s) ON CONFLICT(`key`) DO UPDATE SET `value` = %s\',
|
||||||
|
$this->dbh->quote($key),
|
||||||
|
$this->dbh->quote($value),
|
||||||
|
$this->dbh->quote($value)
|
||||||
|
)) !== false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
new \Exception(\'Unknown DB type: \' . $db_type);
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function dbver(?string $ver = null)
|
||||||
|
{
|
||||||
|
return $this->settings(\'version\', $ver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
');
|
||||||
|
|
||||||
|
copyFile($source_dir . 'Init.php', '<' . '?php
|
||||||
|
|
||||||
|
require __DIR__ . \'/../vendor/autoload.php\';
|
||||||
|
|
||||||
|
use \Exception;
|
||||||
|
use \TPsoft\DBmodel\DBmodel;
|
||||||
|
use \\' . $namespace . 'Maintenance;
|
||||||
|
|
||||||
|
global $dbh;
|
||||||
|
|
||||||
|
if (Configuration::DB_TYPE == \'mysql\') {
|
||||||
|
$dbh = new DBmodel(sprintf(\'mysql:host=%s;dbname=%s;charset=utf8mb4\', Configuration::DB_HOST, Configuration::DB_NAME), Configuration::DB_USER, Configuration::DB_PASS);
|
||||||
|
} else if (Configuration::DB_TYPE == \'sqlite\') {
|
||||||
|
$dbh = new DBmodel(sprintf(\'sqlite:%s\', Configuration::DB_FILEPATH));
|
||||||
|
} else {
|
||||||
|
throw new Exception(\'Unknown database type\');
|
||||||
|
}
|
||||||
|
|
||||||
|
$maintenance = new Maintenance($dbh);
|
||||||
|
$maintenance->database();
|
||||||
|
');
|
||||||
|
|
||||||
|
copyFile($project_root . '/scripts/createModel.php', '<' . '?php
|
||||||
|
|
||||||
|
require_once __DIR__ . \'/../src/Init.php\';
|
||||||
|
use \TPsoft\DBmodel\Creator;
|
||||||
|
|
||||||
|
global $dbh;
|
||||||
|
$creator = new Creator($dbh);
|
||||||
|
$creator->rootDir(realpath(__DIR__.\'/../src/\'));
|
||||||
|
$creator->interact();
|
||||||
|
');
|
||||||
|
|
||||||
|
// Change composer.json
|
||||||
|
if (!isset($composer_arr['autoload']['files'])) {
|
||||||
|
$composer_arr['autoload']['files'] = [];
|
||||||
|
}
|
||||||
|
$composer_arr['autoload']['files'][] = 'config/Configuration.php';
|
||||||
|
if (!isset($composer_arr['scripts'])) {
|
||||||
|
$composer_arr['scripts'] = [];
|
||||||
|
}
|
||||||
|
if (!isset($composer_arr['scripts']['model'])) {
|
||||||
|
$composer_arr['scripts']['model'] = 'php scripts/createModel.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
$composer_json = json_encode($composer_arr, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
$composer_json = preg_replace_callback(
|
||||||
|
'/^( +)/m',
|
||||||
|
fn($m) => str_repeat("\t", intdiv(strlen($m[1]), 4)),
|
||||||
|
$composer_json
|
||||||
|
);
|
||||||
|
$suc = file_put_contents($project_root . '/composer.json', $composer_json);
|
||||||
|
if ($suc) {
|
||||||
|
echo "✅ File changed: $project_root/composer.json\n";
|
||||||
|
} else {
|
||||||
|
echo "🔴 Failed to change file: $project_root/composer.json\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions
|
||||||
|
*/
|
||||||
|
function findProjectRoot(string $startDir): ?string
|
||||||
|
{
|
||||||
|
$dir = realpath($startDir);
|
||||||
|
|
||||||
|
while ($dir && $dir !== dirname($dir)) {
|
||||||
|
if (file_exists($dir . '/composer.json')) {
|
||||||
|
return $dir;
|
||||||
|
}
|
||||||
|
$dir = dirname($dir);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFile(string $destination, string $content): bool
|
||||||
|
{
|
||||||
|
$dir = dirname($destination);
|
||||||
|
if (!file_exists($dir)) {
|
||||||
|
mkdir($dir, 0777, true);
|
||||||
|
}
|
||||||
|
if (file_exists($destination)) {
|
||||||
|
echo "⚠️ File already exists: $destination\n";
|
||||||
|
$confirm = readline("❔ Overwrite? (y - yes, other - no): ");
|
||||||
|
if ($confirm != 'y') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$suc = file_put_contents($destination, $content) !== false;
|
||||||
|
if ($suc) {
|
||||||
|
echo "✅ File created: $destination\n";
|
||||||
|
} else {
|
||||||
|
echo "🔴 Failed to create file: $destination\n";
|
||||||
|
}
|
||||||
|
return $suc;
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
"description": "This library extends the builtin PDO object by several useful features. ",
|
"description": "This library extends the builtin PDO object by several useful features. ",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"keywords": ["db", "model", "pdo"],
|
"keywords": ["db", "model", "pdo", "mysql", "sqlite"],
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Igor Mino",
|
"name": "Igor Mino",
|
||||||
@ -27,6 +27,9 @@
|
|||||||
"psr-4": {
|
"psr-4": {
|
||||||
"TPsoft\\DBmodel\\": "src/"
|
"TPsoft\\DBmodel\\": "src/"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"bin": [
|
||||||
|
"bin/dbmodel-files"
|
||||||
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,20 +4,31 @@ namespace TPsoft\DBmodel;
|
|||||||
|
|
||||||
class Creator {
|
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;
|
$this->dbh = $dbh;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function interact() {
|
public function interact() {
|
||||||
$name = $this->readline('New model name: ');
|
$name = $this->readline('New model name: ');
|
||||||
$table = $this->readline('Table name: ');
|
$table = strtolower($name);
|
||||||
$entity = $this->readline('Entity 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) {
|
if (strlen($entity) <= 0) {
|
||||||
$entity = $table;
|
$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') {
|
if ($this->readline("Create MODEL class '$model_filepath'? (y - yes, other - no) ") != 'y') {
|
||||||
echo "Creating of MODEL class is skipped\n";
|
echo "Creating of MODEL class is skipped\n";
|
||||||
return false;
|
return false;
|
||||||
@ -38,11 +49,11 @@ class Creator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$table_columns = $this->dbh->getTableColumns($table);
|
$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';
|
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) {
|
if (is_array($pks) && count($pks) > 1) {
|
||||||
$primary_key_name = "array('".implode("', '", $pks)."')";
|
$primary_key_name = "array('".implode("', '", $pks)."')";
|
||||||
$columns = array_merge($pks, $columns);
|
$columns = array_merge($pks, $columns);
|
||||||
@ -84,6 +95,7 @@ class Creator {
|
|||||||
$data[\'changed_dt\'] = date(\'Y-m-d H:i:s\');
|
$data[\'changed_dt\'] = date(\'Y-m-d H:i:s\');
|
||||||
}'
|
}'
|
||||||
: '';
|
: '';
|
||||||
|
$namespace_str = strlen($namespace) > 0 ? "\nnamespace $namespace;\n" : '';
|
||||||
$content = '<?'."php
|
$content = '<?'."php
|
||||||
/*
|
/*
|
||||||
TPsoft.org 2000-".date('Y')."
|
TPsoft.org 2000-".date('Y')."
|
||||||
@ -92,12 +104,12 @@ class Creator {
|
|||||||
Milestones:
|
Milestones:
|
||||||
".date('Y-m-d H:i')." Created
|
".date('Y-m-d H:i')." Created
|
||||||
*/
|
*/
|
||||||
|
".$namespace_str."
|
||||||
class ".$name." extends \TPsoft\DBmodel\DBmodel {
|
class ".$name." extends \TPsoft\DBmodel\DBmodel {
|
||||||
|
|
||||||
public $"."tables = array(
|
public $"."tables = array(
|
||||||
'".$entity."' => array(
|
'".$entity."' => array(
|
||||||
'name' => '".$entity."',
|
'name' => '".$tablename."',
|
||||||
'primary_key_name' => ".$primary_key_name.",
|
'primary_key_name' => ".$primary_key_name.",
|
||||||
'allow_attributes' => array(
|
'allow_attributes' => array(
|
||||||
".implode(",\n\t\t\t\t", $allowed_attributes)."
|
".implode(",\n\t\t\t\t", $allowed_attributes)."
|
||||||
@ -140,6 +152,19 @@ class ".$name." extends \TPsoft\DBmodel\DBmodel {
|
|||||||
->toArray();
|
->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) {
|
public function ".$entity_one."Combo($"."col_key, $"."col_value, $"."add_empty = false) {
|
||||||
return $"."this->search('".$entity."')
|
return $"."this->search('".$entity."')
|
||||||
->toCombo($"."col_key, $"."col_value, $"."add_empty);
|
->toCombo($"."col_key, $"."col_value, $"."add_empty);
|
||||||
@ -166,11 +191,14 @@ class ".$name." extends \TPsoft\DBmodel\DBmodel {
|
|||||||
return $line;
|
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__));
|
return dirname(dirname(__FILE__));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@ -27,7 +27,7 @@ class DBmodel
|
|||||||
public $log = null;
|
public $log = null;
|
||||||
public $tables = array();
|
public $tables = array();
|
||||||
|
|
||||||
public function __construct(string $dsn = null, ?string $username = null, #[\SensitiveParameter] ?string $password = null, ?array $options = null)
|
public function __construct(?string $dsn = null, ?string $username = null, #[\SensitiveParameter] ?string $password = null, ?array $options = null)
|
||||||
{
|
{
|
||||||
if (is_null($dsn)) {
|
if (is_null($dsn)) {
|
||||||
if (DBmodel::$instance) {
|
if (DBmodel::$instance) {
|
||||||
@ -154,6 +154,11 @@ class DBmodel
|
|||||||
return $where;
|
return $where;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDBtype()
|
||||||
|
{
|
||||||
|
return $this->dbh->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
public function existTable($table_id)
|
public function existTable($table_id)
|
||||||
{
|
{
|
||||||
$query = sprintf(
|
$query = sprintf(
|
||||||
@ -269,11 +274,14 @@ class DBmodel
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (count($primary_key_names) > 1) return true;
|
if (count($primary_key_names) > 1) return true;
|
||||||
|
$last_id = $this->getLastInsertID();
|
||||||
|
/*
|
||||||
if (method_exists($this->dbh, 'getLastInsertID')) {
|
if (method_exists($this->dbh, 'getLastInsertID')) {
|
||||||
$last_id = $this->getLastInsertID();
|
$last_id = $this->getLastInsertID();
|
||||||
} else {
|
} else {
|
||||||
$last_id = $this->getOne('SELECT last_insert_id()');
|
$last_id = $this->getOne('SELECT last_insert_id()');
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
return $last_id;
|
return $last_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,12 +312,14 @@ class DBmodel
|
|||||||
$query = sprintf(
|
$query = sprintf(
|
||||||
'UPDATE %s'
|
'UPDATE %s'
|
||||||
. ' SET %s'
|
. ' SET %s'
|
||||||
. ' WHERE %s'
|
. ' WHERE %s',
|
||||||
. ' LIMIT 1',
|
|
||||||
$table,
|
$table,
|
||||||
$set,
|
$set,
|
||||||
$this->buildWherePrimaryKey($table_id, $primary_key)
|
$this->buildWherePrimaryKey($table_id, $primary_key)
|
||||||
);
|
);
|
||||||
|
if ($this->getDBtype() == 'mysql') {
|
||||||
|
$query .= ' LIMIT 1';
|
||||||
|
}
|
||||||
$this->_debug('[DBmodel][record][UPDATE]: ' . $query);
|
$this->_debug('[DBmodel][record][UPDATE]: ' . $query);
|
||||||
$ret = $this->query($query);
|
$ret = $this->query($query);
|
||||||
if ($ret === false) {
|
if ($ret === false) {
|
||||||
@ -331,11 +341,13 @@ class DBmodel
|
|||||||
}
|
}
|
||||||
$query = sprintf(
|
$query = sprintf(
|
||||||
'DELETE FROM %s'
|
'DELETE FROM %s'
|
||||||
. ' WHERE %s'
|
. ' WHERE %s',
|
||||||
. ' LIMIT 1',
|
|
||||||
$table,
|
$table,
|
||||||
$this->buildWherePrimaryKey($table_id, $primary_key)
|
$this->buildWherePrimaryKey($table_id, $primary_key)
|
||||||
);
|
);
|
||||||
|
if ($this->getDBtype() == 'mysql') {
|
||||||
|
$query .= ' LIMIT 1';
|
||||||
|
}
|
||||||
$this->_debug('[DBmodel][record][DELETE]: ' . $query);
|
$this->_debug('[DBmodel][record][DELETE]: ' . $query);
|
||||||
$ret = $this->query($query);
|
$ret = $this->query($query);
|
||||||
if ($ret === false) {
|
if ($ret === false) {
|
||||||
@ -548,6 +560,15 @@ class DBmodel
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getTableColumns($table_name) {
|
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->getAll(sprintf('DESC %s', $table_name));
|
||||||
$desc = $this->arrayKeysToLowerCase($desc);
|
$desc = $this->arrayKeysToLowerCase($desc);
|
||||||
$pks = array();
|
$pks = array();
|
||||||
@ -567,6 +588,26 @@ class DBmodel
|
|||||||
return array('pks' => $pks, 'columns' => $columns, 'types' => $types);
|
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
|
* HELPER METHODS
|
||||||
*/
|
*/
|
||||||
@ -674,12 +715,12 @@ class DBmodel
|
|||||||
foreach ($data[$column] as $key => $val) {
|
foreach ($data[$column] as $key => $val) {
|
||||||
$prefix = $this->extractPrefix($val, $where_prefixes);
|
$prefix = $this->extractPrefix($val, $where_prefixes);
|
||||||
$n_val = str_replace($prefix, '', $val);
|
$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 {
|
} else {
|
||||||
$prefix = $this->extractPrefix($data[$column], $where_prefixes);
|
$prefix = $this->extractPrefix($data[$column], $where_prefixes);
|
||||||
$n_val = str_replace($prefix, '', $data[$column]);
|
$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;
|
break;
|
||||||
}
|
}
|
||||||
@ -851,7 +892,7 @@ class DBmodel
|
|||||||
return $this->allowNextError();
|
return $this->allowNextError();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function import($objModel)
|
public function import(DBmodel $objModel)
|
||||||
{
|
{
|
||||||
if (is_null($objModel)) return false;
|
if (is_null($objModel)) return false;
|
||||||
if (
|
if (
|
||||||
|
|||||||
256
src/Maintenance.php
Normal file
256
src/Maintenance.php
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright (c) TPsoft.org 2000-2025
|
||||||
|
Author: Ing. Igor Mino <mino@tpsoft.org>
|
||||||
|
License: GNU GPL-3.0 or later
|
||||||
|
|
||||||
|
Description: Tool for automatic maintenance of database in your project.
|
||||||
|
Version: 1.0.0
|
||||||
|
|
||||||
|
Milestones:
|
||||||
|
2025-05-29 07:59 Igor - Forked from Beteha to standalone public library
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TPsoft\DBmodel;
|
||||||
|
|
||||||
|
class Maintenance
|
||||||
|
{
|
||||||
|
public const ABNORMAL = 0;
|
||||||
|
public const OK = 1;
|
||||||
|
public const FIXED = 2;
|
||||||
|
public const FIX_FAILED = 3;
|
||||||
|
public const UPDATED = 4;
|
||||||
|
public const UPDATE_FAIL = 5;
|
||||||
|
|
||||||
|
public string $lastMessage = '';
|
||||||
|
|
||||||
|
protected DBmodel $dbh;
|
||||||
|
|
||||||
|
public function __construct(?DBmodel $dbh)
|
||||||
|
{
|
||||||
|
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 testDB($test, $test_out)
|
||||||
|
{
|
||||||
|
if (is_array($test_out)) {
|
||||||
|
$test_out = array_change_key_case($test_out, CASE_LOWER);
|
||||||
|
$row = $this->dbh->getRow($test);
|
||||||
|
if (!is_array($row)) return false;
|
||||||
|
$row = array_change_key_case($row, CASE_LOWER);
|
||||||
|
foreach ($test_out as $col => $val) {
|
||||||
|
if (strlen($row[$col]) != strlen($val)) {
|
||||||
|
$max_len = min(strlen($row[$col]), strlen($val));
|
||||||
|
if (substr(strtolower($row[$col]), 0, $max_len) != substr(strtolower($val), 0, $max_len)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (strtolower($row[$col]) != strtolower($val)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return $this->dbh->getOne($test) == $test_out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkDB($test, $test_out, $fix)
|
||||||
|
{
|
||||||
|
if ($this->testDB($test, $test_out)) {
|
||||||
|
return Maintenance::OK;
|
||||||
|
} else {
|
||||||
|
$this->dbh->query($fix);
|
||||||
|
if ($this->testDB($test, $test_out)) {
|
||||||
|
return Maintenance::FIXED;
|
||||||
|
} else {
|
||||||
|
return Maintenance::FIX_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
$check_query,
|
||||||
|
// TEST output
|
||||||
|
$table_name,
|
||||||
|
// FIX query
|
||||||
|
'CREATE TABLE `' . $table_name . '` (' . $definition . ') ' . $after_definition
|
||||||
|
);
|
||||||
|
$this->lastMessage = 'Table ' . strtoupper($table_name) . ' (' . $table_name . ')';
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function existsColumn($table_name, $column)
|
||||||
|
{
|
||||||
|
return $this->testDB(
|
||||||
|
'DESCRIBE `' . $table_name . '` `' . $column . '`',
|
||||||
|
$column
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkDBAdd($table_name, $column, $definition)
|
||||||
|
{
|
||||||
|
$status = $this->checkDB(
|
||||||
|
// TEST query
|
||||||
|
'DESCRIBE `' . $table_name . '` `' . $column . '`',
|
||||||
|
// TEST output
|
||||||
|
$column,
|
||||||
|
// FIX query
|
||||||
|
'ALTER TABLE `' . $table_name . '` ADD `' . $column . '` ' . $definition
|
||||||
|
);
|
||||||
|
$this->lastMessage = "Added column $column to table $table_name";
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkDBRename($table_name, $column_old, $column_new, $definition, $test = null)
|
||||||
|
{
|
||||||
|
if (!$this->existsColumn($table_name, $column_old)) {
|
||||||
|
return Maintenance::ABNORMAL;
|
||||||
|
}
|
||||||
|
$status = $this->checkDB(
|
||||||
|
// TEST query
|
||||||
|
'DESCRIBE `' . $table_name . '` `' . $column_old . '`',
|
||||||
|
// TEST output
|
||||||
|
$test,
|
||||||
|
// FIX query
|
||||||
|
'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_old . '` `' . $column_new . '` ' . $definition
|
||||||
|
);
|
||||||
|
$this->lastMessage = "Renamed column $column_old to $column_new for $table_name";
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkDBRetype($table_name, $column, $new_type, $definition)
|
||||||
|
{
|
||||||
|
if (!$this->existsColumn($table_name, $column)) {
|
||||||
|
return Maintenance::ABNORMAL;
|
||||||
|
}
|
||||||
|
$status = $this->checkDB(
|
||||||
|
// TEST query
|
||||||
|
'DESCRIBE `' . $table_name . '` `' . $column . '`',
|
||||||
|
// TEST output
|
||||||
|
array('type' => strtolower($new_type)),
|
||||||
|
// FIX query
|
||||||
|
'ALTER TABLE `' . $table_name . '` CHANGE `' . $column . '` `' . $column . '` ' . $definition
|
||||||
|
);
|
||||||
|
$this->lastMessage = "Changed type of column $column for table " . strtoupper($table_name) . " to tyoe " . strtoupper($new_type);
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkDBRemove($table_name, $column)
|
||||||
|
{
|
||||||
|
$status = $this->checkDB(
|
||||||
|
// TEST query
|
||||||
|
'DESCRIBE `' . $table_name . '` `' . $column . '`',
|
||||||
|
// TEST output
|
||||||
|
NULL,
|
||||||
|
// FIX query
|
||||||
|
'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column . '`'
|
||||||
|
);
|
||||||
|
$this->lastMessage = "Removed column $column for " . strtoupper($table_name);
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addKey($table_name, $key)
|
||||||
|
{
|
||||||
|
$test = 'SHOW INDEX FROM `' . $table_name . '` WHERE column_name = "' . $key . '"';
|
||||||
|
$test_out = array('table' => $table_name);
|
||||||
|
if ($this->testDB($test, $test_out)) {
|
||||||
|
$status = Maintenance::OK;
|
||||||
|
} else {
|
||||||
|
$fix = 'CREATE INDEX `' . $key . '` ON `' . $table_name . '` (`' . $key . '`)';
|
||||||
|
$this->dbh->query($fix);
|
||||||
|
if ($this->testDB($test, $test_out)) {
|
||||||
|
$status = Maintenance::FIXED;
|
||||||
|
} else {
|
||||||
|
$status = Maintenance::FIX_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->lastMessage = 'Added KEY ' . $key . ' for ' . strtoupper($table_name);
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dropKey($table_name, $key)
|
||||||
|
{
|
||||||
|
$test = 'SHOW INDEX FROM `' . $table_name . '` WHERE column_name = "' . $key . '"';
|
||||||
|
$test_out = null;
|
||||||
|
if ($this->testDB($test, $test_out)) {
|
||||||
|
$status = Maintenance::OK;
|
||||||
|
} else {
|
||||||
|
$index_info = $this->dbh->getRow('SHOW INDEX FROM `' . $table_name . '` WHERE column_name = "' . $key . '"');
|
||||||
|
$index_info = array_change_key_case($index_info, CASE_LOWER);
|
||||||
|
$index_name = $index_info['key_name'];
|
||||||
|
$fix = 'ALTER TABLE `' . $table_name . '` DROP INDEX `' . $index_name . '`';
|
||||||
|
$this->dbh->query($fix);
|
||||||
|
if ($this->testDB($test, $test_out)) {
|
||||||
|
$status = Maintenance::FIXED;
|
||||||
|
} else {
|
||||||
|
$status = Maintenance::FIX_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->lastMessage = 'Deleted KEY ' . $key . ' for ' . strtoupper($table_name);
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateDB($test, $test_out, $update)
|
||||||
|
{
|
||||||
|
if (!$this->testDB($test, $test_out)) {
|
||||||
|
return Maintenance::OK;
|
||||||
|
} else {
|
||||||
|
$suc = $this->dbh->query($update);
|
||||||
|
if ($suc !== false) {
|
||||||
|
return Maintenance::UPDATED;
|
||||||
|
} else {
|
||||||
|
return Maintenance::UPDATE_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once __DIR__.'/../src/DBmodel.php';
|
require_once __DIR__.'/../src/DBmodel.php';
|
||||||
require_once __DIR__.'/../src/creator.php';
|
require_once __DIR__.'/../src/Creator.php';
|
||||||
|
|
||||||
$db = new \TPsoft\DBmodel\DBmodel('mysql:host=127.0.0.1;dbname=test;charset=utf8mb4', 'test', 'test');
|
$db = new \TPsoft\DBmodel\DBmodel('mysql:host=127.0.0.1;dbname=test;charset=utf8mb4', 'test', 'test');
|
||||||
|
|
||||||
|
|||||||
49
test/test4.php
Normal file
49
test/test4.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?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";
|
||||||
|
|
||||||
|
?>
|
||||||
Reference in New Issue
Block a user