533 lines
12 KiB
PHP
533 lines
12 KiB
PHP
<?php namespace ZN\Database;
|
|
/**
|
|
* ZN PHP Web Framework
|
|
*
|
|
* "Simplicity is the ultimate sophistication." ~ Da Vinci
|
|
*
|
|
* @package ZN
|
|
* @license MIT [http://opensource.org/licenses/MIT]
|
|
* @author Ozan UYKUN [ozan@znframework.com]
|
|
*/
|
|
|
|
use ZN\Base;
|
|
use ZN\Config;
|
|
use ZN\Singleton;
|
|
use ZN\Filesystem;
|
|
|
|
class Migration implements MigrationInterface
|
|
{
|
|
/**
|
|
* Migrations path Models/Migrations/
|
|
*
|
|
* @var string
|
|
*/
|
|
private $path = MODELS_DIR . 'Migrations/';
|
|
|
|
/**
|
|
* Keeps database config
|
|
*
|
|
* @var array
|
|
*/
|
|
private $config;
|
|
|
|
/**
|
|
* Keeps class fix
|
|
*
|
|
* @var string
|
|
*/
|
|
private $classFix = INTERNAL_ACCESS . 'Migrate';
|
|
|
|
/**
|
|
* Keeps migrate table name
|
|
*
|
|
* @var string
|
|
*/
|
|
private $migrateTableName;
|
|
|
|
/**
|
|
* Keeps version directory path
|
|
*
|
|
* @var string
|
|
*/
|
|
private $versionDir = 'Version/';
|
|
|
|
/**
|
|
* Keeps database classeses
|
|
*
|
|
* @var object
|
|
*/
|
|
private $db, $forge;
|
|
|
|
/**
|
|
* Magic constructor
|
|
*
|
|
* @param void
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->config = Config::get('Database');
|
|
|
|
if( ! is_dir($this->path) )
|
|
{
|
|
mkdir($this->path, 0755);
|
|
}
|
|
|
|
$this->db = Singleton::class('ZN\Database\DB');
|
|
$this->forge = Singleton::class('ZN\Database\DBForge');
|
|
$this->migrateTableName = defined('static::table') ? static::table : '';
|
|
|
|
$this->createMigrationTableIfNotExists();
|
|
}
|
|
|
|
/**
|
|
* Up all migrations
|
|
*
|
|
* @param string ...$migrations
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function upAll(string ...$migrations) : bool
|
|
{
|
|
$this->runMigrateAll('up', $migrations);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Down all migrations
|
|
*
|
|
* @param string ...$migrations
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function downAll(string ...$migrations) : bool
|
|
{
|
|
$this->runMigrateAll('down', $migrations);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Create table
|
|
*
|
|
* @param array $data
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function createTable(array $data) : bool
|
|
{
|
|
$this->forge->createTable($this->getTableName(), $data);
|
|
|
|
return $this->saveActionQuery(__FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* Drop table
|
|
*
|
|
* @param void
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function dropTable() : bool
|
|
{
|
|
$this->forge->dropTable($this->getTableName());
|
|
|
|
return $this->saveActionQuery(__FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* Add column
|
|
*
|
|
* @param array $column
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function addColumn(array $column) : bool
|
|
{
|
|
$this->forge->addColumn($this->getTableName(), $column);
|
|
|
|
return $this->saveActionQuery(__FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* Drop column
|
|
*
|
|
* @param mixed $column
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function dropColumn($column) : bool
|
|
{
|
|
$this->forge->dropColumn($this->getTableName(), $column);
|
|
|
|
return $this->saveActionQuery(__FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* Modify column
|
|
*
|
|
* @param array $column
|
|
*
|
|
* @param bool
|
|
*/
|
|
public function modifyColumn(array $column) : bool
|
|
{
|
|
$this->forge->modifyColumn($this->getTableName(), $column);
|
|
|
|
return $this->saveActionQuery(__FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* Rename column
|
|
*
|
|
* @param array $column
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function renameColumn(array $column) : bool
|
|
{
|
|
$this->forge->renameColumn($this->getTableName(), $column);
|
|
|
|
return $this->saveActionQuery(__FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* Truncate table
|
|
*
|
|
* @param void
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function truncate() : bool
|
|
{
|
|
$this->forge->truncate($this->getTableName());
|
|
|
|
return $this->saveActionQuery(__FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* Sets migration path
|
|
*
|
|
* @param string $path = NULL
|
|
*
|
|
* @return Migration
|
|
*/
|
|
public function path(?string $path = NULL) : Migration
|
|
{
|
|
$this->path = Base::suffix($path);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Create migration
|
|
*
|
|
* @param string $name
|
|
* @param int $version = 0
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function create(string $name, int $ver = 0) : bool
|
|
{
|
|
if( $version = $this->getValidVersionNumber($ver) )
|
|
{
|
|
$this->createVersionDirectoryIfNotExists($name);
|
|
|
|
$file = $this->getVersionFile($name, $version);
|
|
|
|
$name .= $version;
|
|
}
|
|
else
|
|
{
|
|
$file = $this->getWithoutVersionFile($name);
|
|
}
|
|
|
|
return $this->generateMigrateFileIfNotExists($name, $file);
|
|
}
|
|
|
|
/**
|
|
* Delete migration
|
|
*
|
|
* @param string $name
|
|
* @param int $version = 0
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function delete(string $name, int $ver = 0) : bool
|
|
{
|
|
if( $version = $this->getValidVersionNumber($ver) )
|
|
{
|
|
$file = $this->getVersionFile($name, $version);
|
|
|
|
$this->deleteAllVersionDirectoryIfExists($name, $ver);
|
|
}
|
|
else
|
|
{
|
|
$file = $this->getWithoutVersionFile($name);
|
|
}
|
|
|
|
return $this->deleteMigrateFileIfExists($file);
|
|
}
|
|
|
|
/**
|
|
* Delete all migrations
|
|
*
|
|
* @param void
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function deleteAll() : bool
|
|
{
|
|
if( is_dir($this->path) )
|
|
{
|
|
return Filesystem::deleteFolder($this->path);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Selects migration version
|
|
*
|
|
* @param int $version = 0
|
|
*
|
|
* @return object
|
|
*/
|
|
public function version(int $version = 0)
|
|
{
|
|
if( empty($this->migrateTableName) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// @codeCoverageIgnoreStart
|
|
$name = $this->classFix.$this->getTableName();
|
|
|
|
if( $version <= 0 )
|
|
{
|
|
return Singleton::class($name);
|
|
}
|
|
|
|
$name .= $this->getValidVersionNumber($version);
|
|
|
|
return Singleton::class($name);
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
/**
|
|
* Protected delete migrate file if exists
|
|
*/
|
|
protected function deleteMigrateFileIfExists($file)
|
|
{
|
|
if( is_file($file) )
|
|
{
|
|
return unlink($file);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Protected generate migrate file if not exists
|
|
*/
|
|
protected function generateMigrateFileIfNotExists($name, $file)
|
|
{
|
|
if( ! is_file($file) )
|
|
{
|
|
return $this->createMigrateFile($name, $file);
|
|
}
|
|
|
|
return false; // @codeCoverageIgnore
|
|
}
|
|
|
|
/**
|
|
* Protected delete all version directory if exists
|
|
*/
|
|
protected function deleteAllVersionDirectoryIfExists($name, $version)
|
|
{
|
|
$getVersionDirectory = $this->getVersionDirectory($name);
|
|
|
|
if( $version === 'all' && is_dir($getVersionDirectory) )
|
|
{
|
|
Filesystem::deleteFolder($getVersionDirectory); // @codeCoverageIgnore
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Protected create version directory if not exists
|
|
*/
|
|
protected function createVersionDirectoryIfNotExists($name)
|
|
{
|
|
if( ! is_dir($getVersionDirectory = $this->getVersionDirectory($name)) )
|
|
{
|
|
mkdir($getVersionDirectory);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Protected get file without version
|
|
*/
|
|
protected function getWithoutVersionFile($name)
|
|
{
|
|
return $this->path . Base::suffix($name, '.php');
|
|
}
|
|
|
|
/**
|
|
* Protected get version file
|
|
*/
|
|
protected function getVersionFile($name, $version)
|
|
{
|
|
return $this->getVersionDirectory($name) . Base::suffix($version, '.php');
|
|
}
|
|
|
|
/**
|
|
* Protected create migrate
|
|
*/
|
|
protected function getVersionDirectory($name)
|
|
{
|
|
return $this->path . $name . $this->versionDir;
|
|
}
|
|
|
|
/**
|
|
* Protected save action query
|
|
*/
|
|
protected function saveActionQuery($type)
|
|
{
|
|
if( ! $this->forge->error() )
|
|
{
|
|
return $this->db->insert($this->config['migration']['table'],
|
|
[
|
|
'name' => $this->getTableName(),
|
|
'type' => $type ?: 'noAction',
|
|
'version' => $this->getVersionNumberFromTableName(),
|
|
'date' => date('Ymdhis')
|
|
]);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* protected create
|
|
*
|
|
* @param void
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function createMigrationTableIfNotExists()
|
|
{
|
|
$table = $this->config['database']['prefix'] . $this->config['migration']['table'];
|
|
|
|
$this->forge->createTable('IF NOT EXISTS '.$table, array
|
|
(
|
|
'name' => [$this->db->varchar(512), $this->db->notNull()],
|
|
'type' => [$this->db->varchar(256), $this->db->notNull()],
|
|
'version' => [$this->db->varchar(3), $this->db->notNull()],
|
|
'date' => [$this->db->varchar(15), $this->db->notNull()]
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Get table name
|
|
*
|
|
* @param void
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function getTableName()
|
|
{
|
|
$table = preg_replace('/[0-9][0-9][0-9]/', '', $this->migrateTableName);
|
|
|
|
return str_replace($this->classFix, '', $table);
|
|
}
|
|
|
|
/**
|
|
* Protected get version number from table name
|
|
*/
|
|
protected function getVersionNumberFromTableName()
|
|
{
|
|
preg_match('(\w+([0-9][0-9][0-9]))', $this->migrateTableName, $match);
|
|
|
|
return $match[1] ?? '000';
|
|
}
|
|
|
|
/**
|
|
* Protected get valid version number
|
|
*/
|
|
protected function getValidVersionNumber($numeric)
|
|
{
|
|
$length = strlen((string) $numeric);
|
|
|
|
if( (int) $numeric > 999 || (int) $numeric < 0 )
|
|
{
|
|
return false; // @codeCoverageIgnore
|
|
}
|
|
|
|
switch( $length )
|
|
{
|
|
case 1 : $numeric = '00'.$numeric; break;
|
|
case 2 : $numeric = '0' .$numeric; break; // @codeCoverageIgnore
|
|
}
|
|
|
|
if( $numeric === '000' )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return $numeric;
|
|
}
|
|
|
|
/**
|
|
* Protected run migrate all
|
|
*/
|
|
protected function runMigrateAll($type, $migrations)
|
|
{
|
|
foreach( $migrations as $migration )
|
|
{
|
|
$migration = Base::prefix($migration, 'Migrate');
|
|
|
|
$migration::$type();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* protected create migrate file
|
|
*
|
|
* @param string $name
|
|
* @param string $file
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function createMigrateFile(string $name, string $file) : bool
|
|
{
|
|
$eol = EOL;
|
|
$str = '<?php'.$eol;
|
|
$str .= 'class '.$this->classFix.$name.' extends '.__CLASS__.$eol;
|
|
$str .= '{'.$eol;
|
|
$str .= "\t".'# Class/Table Name'.$eol;
|
|
$str .= "\t".'const table = __CLASS__;'.$eol.$eol;
|
|
$str .= "\t".'# Up'.$eol;
|
|
$str .= "\t".'public function up()'.$eol;
|
|
$str .= "\t".'{'.$eol;
|
|
$str .= "\t\t".'# Default Query'.$eol;
|
|
$str .= "\t\t".'return $this->createTable' . $eol;
|
|
$str .= "\t\t".'(['.$eol;
|
|
$str .= "\t\t\t".'\'id\' => [DB::int(11), DB::primaryKey(), DB::autoIncrement()]' . $eol;
|
|
$str .= "\t\t".']);'.$eol;
|
|
$str .= "\t".'}'.$eol.$eol;
|
|
$str .= "\t".'# Down'.$eol;
|
|
$str .= "\t".'public function down()'.$eol;
|
|
$str .= "\t".'{'.$eol;
|
|
$str .= "\t\t".'# Default Query'.$eol;
|
|
$str .= "\t\t".'return $this->dropTable();'.$eol;
|
|
$str .= "\t".'}'.$eol;
|
|
$str .= '}';
|
|
|
|
return file_put_contents($file, $str);
|
|
}
|
|
}
|