<?php
namespace Tlf;
/**
* A minimal class for handling sql migration. Create a migrations dir. Then create dirs like `v1`, `v2` & create files `up.sql`, `down.sql` in each versioned dir. Migrating from 1 to 2 will execute `v2/up.sql`. From 3 down to 1 will execute `v2/down.sql` and `v1/down.sql`. You may also make files like `v1/up-1.sql`, `v1/up-2.sql` to execute multiple files in order.
*
* You can create `.config/ldb.json` file with the relative `'dir'` and `'db'` settings
*
* @tagline Easy to use SQL Migrations from versioned directories
*/
class LilMigrations {
/**
* a pdo instance
*/
public \PDO $pdo;
/**
* the dir for migrations scripts.
*/
public string $dir;
/**
* array<string, mixed> of variables to `extract` and make available to `up.php` and `down.php` migration files.
*/
public array $migration_vars = [];
/**
* In $dir, there should be directories named 'v1', 'v2', 'v3', and so on.
* In the v1/v2/v3 dirs, there should be up.sql & down.sql files with valid SQL statements for whatever database you're using
*
* @param $pdo a pdo instance
* @param $dir a directory path
*/
public function __construct(\PDO $pdo, string $dir){
$this->pdo = $pdo;
$this->dir = $dir;
}
/**
* Convenience method to initialize sqlite db in memory
* @return Tlf\LilDb
*/
static public function sqlite(string $dbName = ':memory:'){
$pdo = new \PDO('sqlite:'.$dbName);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$ldb = new static($pdo);
return $ldb;
}
/**
*
* Migrate from old version to new
*
* @param $old the current version of the database
* @param $new the new version of the database to go to
*/
public function migrate(int $old, int $new){
if ($old < $new){
for ($i=$old+1; $i <= $new; $i++){
$this->run_migration_version($i, 'up');
}
} else if ($old > $new) {
for ($i=$old-1; $i >= $new; $i--){
$this->run_migration_version($i, 'down');
}
} else {
$this->run_migration_version($i, 'up');
}
}
public function run_migration_version($version, $up_or_down){
$i = $version;
$file = $up_or_down;
$v_dir = "v$i/";
$migrate_dir = $this->dir.'/'.$v_dir;
$files = is_dir($migrate_dir) ? scandir($migrate_dir) : false;
if ($files==false){
echo "\nMigrations dir $v_dir does not exist. Continuing.";
return;
}
$files = array_filter($files,
function($v) use ($file){
if (substr($v,0,strlen($file))==$file)return true;
return false;
});
//@todo test the file sorting
// // for testing
// rsort($files);
//
// $files = ['up-9.sql', 'up-2.sql', 'up-13.sql', 'up-0.sql', 'up.sql', 'up-1.sql'];
usort($files,
function($v1, $v2){
$pos1 = strpos($v1,'-');
if ($pos1==false)$index1 = -1;
else $index1 = (int)substr($v1,$pos1+1,-4);
$pos2 = strpos($v2,'-');
if ($pos2==false)$index2 = -1;
else $index2 = (int)substr($v2,$pos2+1,-4);
return $index1-$index2;
}
);
foreach ($files as $f){
$rel = $v_dir.$f;
$exec_file = $this->dir.'/'.$rel;
// $exec_file = $this->dir.'/'.$rel;
$ext = pathinfo($exec_file, PATHINFO_EXTENSION);
if ($ext=='php'){
$migrate_vars = $this->migration_vars;
extract($migrate_vars);
$pdo = $this->pdo;
require($exec_file);
return;
}
if ($ext!='sql'){
echo "\nFile '$rel' is not an sql file.";
return;
}
if (!file_exists($exec_file)){
echo "\nFile '$rel' was not found for migrations.";
continue;
}
echo "\nExecute '$rel'";
$sql = file_get_contents($exec_file);
$this->pdo->exec($sql);
if ($this->pdo->errorCode()!='00000'){
echo "\nSQL Error in '$rel':\n";
print_r($this->pdo->errorInfo());
return;
}
}
}
}