migration.abstract.class.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. <?php
  2. namespace Juju\PDO;
  3. require_once(realpath(dirname(__DIR__).'/pdo.class.php'));
  4. use Juju\{PDO, PDO\Transaction, Settings};
  5. abstract class Migration {
  6. public abstract static function up(Transaction $pdo);
  7. public abstract static function down(Transaction $pdo);
  8. public abstract static function change(Transaction $pdo, string $direction);
  9. const MIGRATE_UP = 'up';
  10. const MIGRATE_DOWN = 'down';
  11. private static $pdo;
  12. private static $last_date;
  13. private static $initial_date;
  14. private static $current_version;
  15. private static $initial_version;
  16. final public static function version(){
  17. $name = get_called_class();
  18. return substr($name, strrpos($name, '\\') + 1);
  19. }
  20. final public static function version_table(){
  21. if(!class_exists("Juju\\Settings")){
  22. throw new \Exception("Settings not loaded");
  23. }
  24. return Settings::get('db.versions');
  25. }
  26. final public static function get_current_version(bool $force = false){
  27. if(is_null(self::$current_version) || $force){
  28. if(is_null(self::$pdo)){
  29. throw new \Exception("Migration is not bound to a data source");
  30. }
  31. $table_name = self::version_table();
  32. $query = self::$pdo->query("select max(version) version from `{$table_name}`");
  33. self::$current_version = $query->fetch()['version'];
  34. $query->closeCursor();
  35. }
  36. return self::$current_version;
  37. }
  38. final public static function get_initial_version(bool $force = false){
  39. if(is_null(self::$initial_version) || $force){
  40. if(is_null(self::$pdo)){
  41. throw new \Exception("Migration is not bound to a data source");
  42. }
  43. $table_name = self::version_table();
  44. $query = self::$pdo->query("select min(version) version from `{$table_name}`");
  45. self::$initial_version = $query->fetch()['version'];
  46. $query->closeCursor();
  47. }
  48. return self::$initial_version;
  49. }
  50. final public static function get_last_install_date(bool $force = false){
  51. if(is_null(self::$last_date) || $force){
  52. if(is_null(self::$pdo)){
  53. throw new \Exception("Migration is not bound to a data source");
  54. }
  55. $table_name = self::version_table();
  56. $query = self::$pdo->query("select max(date) date from `{$table_name}`");
  57. self::$last_date = $query->fetch()['date'];
  58. $query->closeCursor();
  59. }
  60. return self::$last_date;
  61. }
  62. final public static function get_initial_install_date(bool $force = false){
  63. if(is_null(self::$initial_date) || $force){
  64. if(is_null(self::$pdo)){
  65. throw new \Exception("Migration is not bound to a data source");
  66. }
  67. $table_name = self::version_table();
  68. $query = self::$pdo->query("select min(date) date from `{$table_name}`");
  69. self::$initial_date = $query->fetch()['date'];
  70. $query->closeCursor();
  71. }
  72. return self::$initial_date;
  73. }
  74. final public static function installed(){
  75. $pdo = self::$pdo;
  76. $count = $pdo->exec(
  77. "select 1 ".
  78. "from `".self::version_table()."` ".
  79. "where version = {$pdo->quote(static::version())}"
  80. );
  81. if($count === false){
  82. throw $pdo->getError();
  83. }
  84. return $count;
  85. }
  86. final public static function migrations(){
  87. $migrations = array_filter(get_declared_classes(), function($class){
  88. return 0 === strpos($class, "Migration\\");
  89. });
  90. sort($migrations);
  91. return $migrations;
  92. }
  93. final public static function migrate(string $direction, int $amount = 1){
  94. if($amount < 1){
  95. throw new \Exception("Migration amount must be a positive integer");
  96. }
  97. $pdo = self::$pdo;
  98. $table = self::$pdo->table(self::version_table());
  99. $table
  100. ->column('version', 'varchar(100)')
  101. ->column('date', 'datetime', DefaultValue::named('current_timestamp'))
  102. ->primaryKey('version')
  103. ->index('date_idx', ['date'])
  104. ->commit();
  105. switch($direction){
  106. case self::MIGRATE_UP:
  107. foreach(self::migrations() as $migration){
  108. if(!$migration::installed()){
  109. $pdo->transaction(function($pdo) use($migration, $table){
  110. $migration::up($pdo);
  111. $migration::change($pdo, self::MIGRATE_UP);
  112. });
  113. $table->insert(['version'=>$migration::version()]);
  114. if(!$migration::installed()){
  115. throw new \Exception("Migration {$migration::version()} {$direction} failed");
  116. }
  117. if(--$amount == 0){
  118. break;
  119. }
  120. }
  121. }
  122. break;
  123. case self::MIGRATE_DOWN:
  124. foreach(array_reverse(self::migrations()) as $migration){
  125. if($migration::installed()){
  126. $pdo->transaction(function($pdo) use($migration, $table){
  127. $migration::change($pdo, self::MIGRATE_DOWN);
  128. $migration::down($pdo);
  129. });
  130. $table->delete(['version'=>$migration::version()]);
  131. if($migration::installed()){
  132. throw new \Exception("Migration {$migration::version()} {$direction} failed");
  133. }
  134. if(--$amount == 0){
  135. break;
  136. }
  137. }
  138. }
  139. break;
  140. default:
  141. throw new \Exception("Invalid migration direction '{$direction}'");
  142. }
  143. self::get_initial_version(true);
  144. self::get_initial_install_date(true);
  145. self::get_current_version(true);
  146. self::get_last_install_date(true);
  147. }
  148. final public static function migrate_all(string $direction){
  149. self::migrate($direction, count(self::migrations()));
  150. }
  151. final public static function bind(string $dsn){
  152. self::$pdo = PDO::from($dsn);
  153. }
  154. final public static function release(){
  155. self::$pdo = null;
  156. }
  157. }
  158. ?>