PageRenderTime 27ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/fuel/core/classes/migrate.php

https://bitbucket.org/sriedel/iccrm-wip
PHP | 619 lines | 348 code | 70 blank | 201 comment | 25 complexity | 4a317b4c66c9273f7d8ba19fd2f82989 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.0
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2012 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. /**
  14. * Migrate Class
  15. *
  16. * @package Fuel
  17. * @category Migrations
  18. * @link http://docs.fuelphp.com/classes/migrate.html
  19. */
  20. class Migrate
  21. {
  22. /**
  23. * @var array current migrations registered in the database
  24. */
  25. protected static $migrations = array();
  26. /**
  27. * @var string migration classes namespace prefix
  28. */
  29. protected static $prefix = 'Fuel\\Migrations\\';
  30. /**
  31. * @var string name of the migration table
  32. */
  33. protected static $table = 'migration';
  34. /**
  35. * @var array migration table schema
  36. */
  37. protected static $table_definition = array(
  38. 'type' => array('type' => 'varchar', 'constraint' => 25),
  39. 'name' => array('type' => 'varchar', 'constraint' => 50),
  40. 'migration' => array('type' => 'varchar', 'constraint' => 100, 'null' => false, 'default' => ''),
  41. );
  42. /**
  43. * loads in the migrations config file, checks to see if the migrations
  44. * table is set in the database (if not, create it), and reads in all of
  45. * the versions from the DB.
  46. *
  47. * @return void
  48. */
  49. public static function _init()
  50. {
  51. logger(\Fuel::L_DEBUG, 'Migrate class initialized');
  52. // load the migrations config
  53. \Config::load('migrations', true);
  54. // set the name of the table containing the installed migrations
  55. static::$table = \Config::get('migrations.table', static::$table);
  56. // installs or upgrades the migration table to the current schema
  57. static::table_version_check();
  58. //get all installed migrations from db
  59. $migrations = \DB::select()
  60. ->from(static::$table)
  61. ->order_by('type', 'ASC')
  62. ->order_by('name', 'ASC')
  63. ->order_by('migration', 'ASC')
  64. ->execute()
  65. ->as_array();
  66. // convert the db migrations to match the config file structure
  67. foreach($migrations as $migration)
  68. {
  69. isset(static::$migrations[$migration['type']]) or static::$migrations[$migration['type']] = array();
  70. static::$migrations[$migration['type']][$migration['name']][] = $migration['migration'];
  71. }
  72. }
  73. /**
  74. * migrate to a specific version, range of versions, or all
  75. *
  76. * @param mixed version to migrate to (up or down!)
  77. * @param string name of the package, module or app
  78. * @param string type of migration (package, module or app)
  79. * @param bool if true, also run out-of-sequence migrations
  80. *
  81. * @throws UnexpectedValueException
  82. * @return array
  83. */
  84. public static function version($version = null, $name = 'default', $type = 'app', $all = false)
  85. {
  86. // get the current version from the config
  87. $all or $current = \Config::get('migrations.version.'.$type.'.'.$name);
  88. // any migrations defined?
  89. if ( ! empty($current))
  90. {
  91. // get the timestamp of the last installed migration
  92. if (preg_match('/^(.*?)_(.*)$/', end($current), $match))
  93. {
  94. // determine the direction
  95. $direction = (is_null($version) or $match[1] < $version) ? 'up' : 'down';
  96. // fetch the migrations
  97. if ($direction == 'up')
  98. {
  99. $migrations = static::find_migrations($name, $type, $match[1], $version);
  100. }
  101. else
  102. {
  103. $migrations = static::find_migrations($name, $type, $version, $match[1], $direction);
  104. // we're going down, so reverse the order of mygrations
  105. $migrations = array_reverse($migrations, true);
  106. }
  107. // run migrations from current version to given version
  108. return static::run($migrations, $name, $type, $direction);
  109. }
  110. else
  111. {
  112. throw new \UnexpectedValueException('Could not determine a valid version from '.$current.'.');
  113. }
  114. }
  115. // run migrations from the beginning to given version
  116. return static::run(static::find_migrations($name, $type, null, $version), $name, $type, 'up');
  117. }
  118. /**
  119. * migrate to a latest version
  120. *
  121. * @param string name of the package, module or app
  122. * @param string type of migration (package, module or app)
  123. * @param bool if true, also run out-of-sequence migrations
  124. *
  125. * @return array
  126. */
  127. public static function latest($name = 'default', $type = 'app', $all = false)
  128. {
  129. // equivalent to from current version (or all) to latest
  130. return static::version(null, $name, $type, $all);
  131. }
  132. /**
  133. * migrate to the version defined in the config file
  134. *
  135. * @param string name of the package, module or app
  136. * @param string type of migration (package, module or app)
  137. *
  138. * @return array
  139. */
  140. public static function current($name = 'default', $type = 'app')
  141. {
  142. // get the current version from the config
  143. $current = \Config::get('migrations.version.'.$type.'.'.$name);
  144. // any migrations defined?
  145. if ( ! empty($current))
  146. {
  147. // get the timestamp of the last installed migration
  148. if (preg_match('/^(.*?)_(.*)$/', end($current), $match))
  149. {
  150. // run migrations from start to current version
  151. return static::run(static::find_migrations($name, $type, null, $match[1]), $name, $type, 'up');
  152. }
  153. }
  154. // nothing to migrate
  155. return array();
  156. }
  157. /**
  158. * migrate up to the next version
  159. *
  160. * @param mixed version to migrate up to
  161. * @param string name of the package, module or app
  162. * @param string type of migration (package, module or app)
  163. *
  164. * @return array
  165. */
  166. public static function up($version = null, $name = 'default', $type = 'app')
  167. {
  168. // get the current version info from the config
  169. $current = \Config::get('migrations.version.'.$type.'.'.$name);
  170. // get the last migration installed
  171. $current = empty($current) ? null : end($current);
  172. // get the available migrations after the current one
  173. $migrations = static::find_migrations($name, $type, $current, $version);
  174. // found any?
  175. if ( ! empty($migrations))
  176. {
  177. // if no version was given, only install the next migration
  178. is_null($version) and $migrations = array(reset($migrations));
  179. // install migrations found
  180. return static::run($migrations, $name, $type, 'up');
  181. }
  182. // nothing to migrate
  183. return array();
  184. }
  185. /**
  186. * migrate down to the previous version
  187. *
  188. * @param mixed version to migrate down to
  189. * @param string name of the package, module or app
  190. * @param string type of migration (package, module or app)
  191. *
  192. * @return array
  193. */
  194. public static function down($version = null, $name = 'default', $type = 'app')
  195. {
  196. // get the current version info from the config
  197. $current = \Config::get('migrations.version.'.$type.'.'.$name);
  198. // any migrations defined?
  199. if ( ! empty($current))
  200. {
  201. // get the last entry
  202. $current = end($current);
  203. // get the available migrations before the last current one
  204. $migrations = static::find_migrations($name, $type, $version, $current, 'down');
  205. // found any?
  206. if ( ! empty($migrations))
  207. {
  208. // we're going down, so reverse the order of mygrations
  209. $migrations = array_reverse($migrations, true);
  210. // if no version was given, only revert the last migration
  211. is_null($version) and $migrations = array(reset($migrations));
  212. // revert the installed migrations
  213. return static::run($migrations, $name, $type, 'down');
  214. }
  215. }
  216. // nothing to migrate
  217. return array();
  218. }
  219. /**
  220. * run the action migrations found
  221. *
  222. * @param array list of files to migrate
  223. * @param string name of the package, module or app
  224. * @param string type of migration (package, module or app)
  225. * @param string method to call on the migration
  226. *
  227. * @return array
  228. */
  229. protected static function run($migrations, $name, $type, $method = 'up')
  230. {
  231. // storage for installed migrations
  232. $done = array();
  233. // Loop through the runnable migrations and run them
  234. foreach ($migrations as $ver => $migration)
  235. {
  236. logger(Fuel::L_INFO, 'Migrating to version: '.$ver);
  237. $result = call_user_func(array(new $migration['class'], $method));
  238. if ($result === false)
  239. {
  240. logger(Fuel::L_INFO, 'Skipped migration to '.$ver.'.');
  241. return $done;
  242. }
  243. $file = basename($migration['path'], '.php');
  244. $method == 'up' ? static::write_install($name, $type, $file) : static::write_revert($name, $type, $file);
  245. $done[] = $file;
  246. }
  247. empty($done) or logger(Fuel::L_INFO, 'Migrated to '.$ver.' successfully.');
  248. return $done;
  249. }
  250. /**
  251. * add an installed migration to the database
  252. *
  253. * @param string name of the package, module or app
  254. * @param string type of migration (package, module or app)
  255. * @param string name of the migration file just run
  256. *
  257. * @return void
  258. */
  259. protected static function write_install($name, $type, $file)
  260. {
  261. // add the migration just run
  262. \DB::insert(static::$table)->set(array(
  263. 'name' => $name,
  264. 'type' => $type,
  265. 'migration' => $file,
  266. ))->execute();
  267. // add the file to the list of run migrations
  268. static::$migrations[$type][$name][] = $file;
  269. // make sure the migrations are in the correct order
  270. sort(static::$migrations[$type][$name]);
  271. // and save the update to the environment config file
  272. \Config::set('migrations.version.'.$type.'.'.$name, static::$migrations[$type][$name]);
  273. \Config::save(\Fuel::$env.DS.'migrations', 'migrations');
  274. }
  275. /**
  276. * remove a reverted migration from the database
  277. *
  278. * @param string name of the package, module or app
  279. * @param string type of migration (package, module or app)
  280. * @param string name of the migration file just run
  281. *
  282. * @return void
  283. */
  284. protected static function write_revert($name, $type, $file)
  285. {
  286. // remove the migration just run
  287. \DB::delete(static::$table)
  288. ->where('name', $name)
  289. ->where('type', $type)
  290. ->where('migration', $file)
  291. ->execute();
  292. // remove the file from the list of run migrations
  293. if (($key = array_search($file, static::$migrations[$type][$name])) !== false)
  294. {
  295. unset(static::$migrations[$type][$name][$key]);
  296. }
  297. // make sure the migrations are in the correct order
  298. sort(static::$migrations[$type][$name]);
  299. // and save the update to the config file
  300. \Config::set('migrations.version.'.$type.'.'.$name, static::$migrations[$type][$name]);
  301. \Config::save(\Fuel::$env.DS.'migrations', 'migrations');
  302. }
  303. /**
  304. * migrate down to the previous version
  305. *
  306. * @param string name of the package, module or app
  307. * @param string type of migration (package, module or app)
  308. * @param mixed version to start migrations from, or null to start at the beginning
  309. * @param mixed version to end migrations by, or null to migrate to the end
  310. *
  311. * @return array
  312. */
  313. protected static function find_migrations($name, $type, $start = null, $end = null, $direction = 'up')
  314. {
  315. // Load all *_*.php files in the migrations path
  316. $method = '_find_'.$type;
  317. if ( ! $files = static::$method($name))
  318. {
  319. return array();
  320. }
  321. // get the currently installed migrations from the DB
  322. $current = \Arr::get(static::$migrations, $type.'.'.$name, array());
  323. // storage for the result
  324. $migrations = array();
  325. // normalize start and end values
  326. if ( ! is_null($start))
  327. {
  328. // if we have a prefix, use that
  329. ($pos = strpos($start, '_')) === false or $start = ltrim(substr($start, 0, $pos), '0');
  330. is_numeric($start) and $start = (int) $start;
  331. }
  332. if ( ! is_null($end))
  333. {
  334. // if we have a prefix, use that
  335. ($pos = strpos($end, '_')) === false or $end = ltrim(substr($end, 0, $pos), '0');
  336. is_numeric($end) and $end = (int) $end;
  337. }
  338. // filter the migrations out of bounds
  339. foreach ($files as $file)
  340. {
  341. // get the version for this migration and normalize it
  342. $migration = basename($file);
  343. ($pos = strpos($migration, '_')) === false or $migration = ltrim(substr($migration, 0, $pos), '0');
  344. is_numeric($migration) and $migration = (int) $migration;
  345. // add the file to the migrations list if it's in between version bounds
  346. if ((is_null($start) or $migration > $start) and (is_null($end) or $migration <= $end))
  347. {
  348. // see if it is already installed
  349. if ( in_array(basename($file, '.php'), $current))
  350. {
  351. // already installed. store it only if we're going down
  352. $direction == 'down' and $migrations[$migration] = array('path' => $file);
  353. }
  354. else
  355. {
  356. // not installed yet. store it only if we're going up
  357. $direction == 'up' and $migrations[$migration] = array('path' => $file);
  358. }
  359. }
  360. }
  361. // We now prepare to actually DO the migrations
  362. // But first let's make sure that everything is the way it should be
  363. foreach ($migrations as $ver => $migration)
  364. {
  365. // get the migration filename from the path
  366. $migration['file'] = basename($migration['path']);
  367. // make sure the migration filename has a valid format
  368. if (preg_match('/^.*?_(.*).php$/', $migration['file'], $match))
  369. {
  370. // determine the classname for this migration
  371. $class_name = ucfirst(strtolower($match[1]));
  372. // load the file and determiine the classname
  373. include $migration['path'];
  374. $class = static::$prefix.$class_name;
  375. // make sure it exists in the migration file loaded
  376. if ( ! class_exists($class, false))
  377. {
  378. throw new FuelException(sprintf('Migration "%s" does not contain expected class "%s"', $migration['path'], $class));
  379. }
  380. // and that it contains an "up" and "down" method
  381. if ( ! is_callable(array($class, 'up')) or ! is_callable(array($class, 'down')))
  382. {
  383. throw new FuelException(sprintf('Migration class "%s" must include public methods "up" and "down"', $name));
  384. }
  385. $migrations[$ver]['class'] = $class;
  386. }
  387. else
  388. {
  389. throw new FuelException(sprintf('Invalid Migration filename "%s"', $migration['path']));
  390. }
  391. }
  392. // make sure the result is sorted properly with all version types
  393. uksort($migrations, 'strnatcasecmp');
  394. return $migrations;
  395. }
  396. /**
  397. * finds migrations for the given app
  398. *
  399. * @param string name of the app (not used at the moment)
  400. *
  401. * @return array
  402. */
  403. protected static function _find_app($name = null)
  404. {
  405. return glob(APPPATH.\Config::get('migrations.folder').'*_*.php');
  406. }
  407. /**
  408. * finds migrations for the given module (or all if name is not given)
  409. *
  410. * @param string name of the module
  411. *
  412. * @return array
  413. */
  414. protected static function _find_module($name = null)
  415. {
  416. $files = array();
  417. if ($name)
  418. {
  419. // find a module
  420. foreach (\Config::get('module_paths') as $m)
  421. {
  422. $files = glob($m .$name.'/'.\Config::get('migrations.folder').'*_*.php');
  423. if (count($files))
  424. {
  425. break;
  426. }
  427. }
  428. }
  429. else
  430. {
  431. // find all modules
  432. foreach (\Config::get('module_paths') as $m)
  433. {
  434. $files = array_merge($files, glob($m.'*/'.\Config::get('migrations.folder').'*_*.php'));
  435. }
  436. }
  437. return $files;
  438. }
  439. /**
  440. * finds migrations for the given package (or all if name is not given)
  441. *
  442. * @param string name of the package
  443. *
  444. * @return array
  445. */
  446. protected static function _find_package($name = null)
  447. {
  448. $files = array();
  449. if ($name)
  450. {
  451. // find a package
  452. foreach (\Config::get('package_paths', array(PKGPATH)) as $p)
  453. {
  454. $files = glob($p .$name.'/'.\Config::get('migrations.folder').'*_*.php');
  455. if (count($files))
  456. {
  457. break;
  458. }
  459. }
  460. }
  461. else
  462. {
  463. // find all packages
  464. foreach (\Config::get('package_paths', array(PKGPATH)) as $p)
  465. {
  466. $files = array_merge($files, glob($p.'*/'.\Config::get('migrations.folder').'*_*.php'));
  467. }
  468. }
  469. return $files;
  470. }
  471. /**
  472. * installs or upgrades the migration table to the current schema
  473. *
  474. * @return void
  475. *
  476. * @deprecated Remove upgrade check in 1.4
  477. */
  478. protected static function table_version_check()
  479. {
  480. // if table does not exist
  481. if ( ! \DBUtil::table_exists(static::$table))
  482. {
  483. // create table
  484. \DBUtil::create_table(static::$table, static::$table_definition);
  485. }
  486. // check if a table upgrade is needed
  487. elseif ( ! \DBUtil::field_exists(static::$table, array('migration')))
  488. {
  489. // get the current migration status
  490. $current = \DB::select()->from(static::$table)->order_by('type', 'ASC')->order_by('name', 'ASC')->execute()->as_array();
  491. // drop the existing table, and recreate it in the new layout
  492. \DBUtil::drop_table(static::$table);
  493. \DBUtil::create_table(static::$table, static::$table_definition);
  494. // check if we had a current migration status
  495. if ( ! empty($current))
  496. {
  497. // do we need to migrate from a v1.0 migration environment?
  498. if (isset($current[0]['current']))
  499. {
  500. // convert the current result into a v1.1. migration environment structure
  501. $current = array(0 => array('name' => 'default', 'type' => 'app', 'version' => $current[0]['current']));
  502. }
  503. // build a new config structure
  504. $configs = array();
  505. // convert the v1.1 structure to the v1.2 structure
  506. foreach ($current as $migration)
  507. {
  508. // find the migrations for this entry
  509. $migrations = static::find_migrations($migration['name'], $migration['type'], null, $migration['version']);
  510. // array to keep track of the migrations already run
  511. $config = array();
  512. // add the individual migrations found
  513. foreach ($migrations as $file)
  514. {
  515. $file = pathinfo($file['path']);
  516. // add this migration to the table
  517. \DB::insert(static::$table)->set(array(
  518. 'name' => $migration['name'],
  519. 'type' => $migration['type'],
  520. 'migration' => $file['filename'],
  521. ))->execute();
  522. // and to the config
  523. $config[] = $file['filename'];
  524. }
  525. // create a config entry for this name and type if needed
  526. isset($configs[$migration['type']]) or $configs[$migration['type']] = array();
  527. $configs[$migration['type']][$migration['name']] = $config;
  528. }
  529. // write the updated migrations config back
  530. \Config::set('migrations.version', $configs);
  531. \Config::save(\Fuel::$env.DS.'migrations', 'migrations');
  532. }
  533. }
  534. // delete any old migration config file that may exist
  535. file_exists(APPPATH.'config'.DS.'migrations.php') and unlink(APPPATH.'config'.DS.'migrations.php');
  536. }
  537. }