PageRenderTime 49ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Ruckusing/FrameworkRunner.php

https://bitbucket.org/salimane/ruckusing-migrations
PHP | 490 lines | 265 code | 54 blank | 171 comment | 45 complexity | ac57ae8bfe8217321e3490b3c78cc1f1 MD5 | raw file
  1. <?php
  2. /**
  3. * Ruckusing
  4. *
  5. * @category Ruckusing
  6. * @package Ruckusing
  7. * @author Cody Caughlan <codycaughlan % gmail . com>
  8. * @link https://github.com/ruckus/ruckusing-migrations
  9. */
  10. require_once RUCKUSING_BASE . '/lib/Ruckusing/Task/Manager.php';
  11. require_once RUCKUSING_BASE . '/lib/Ruckusing/Exception.php';
  12. /**
  13. * Ruckusing_FrameworkRunner
  14. *
  15. * Primary work-horse class. This class bootstraps the framework by loading
  16. * all adapters and tasks.
  17. *
  18. * @category Ruckusing
  19. * @package Ruckusing
  20. * @author Cody Caughlan <codycaughlan % gmail . com>
  21. * @link https://github.com/ruckus/ruckusing-migrations
  22. */
  23. class Ruckusing_FrameworkRunner
  24. {
  25. /**
  26. * reference to our DB connection
  27. *
  28. * @var array
  29. */
  30. private $_db = null;
  31. /**
  32. * The currently active config
  33. *
  34. * @var array
  35. */
  36. private $_active_db_config;
  37. /**
  38. * Available DB config (e.g. test,development, production)
  39. *
  40. * @var array
  41. */
  42. private $_config = array();
  43. /**
  44. * Task manager
  45. *
  46. * @var Ruckusing_Task_Manager
  47. */
  48. private $_task_mgr = null;
  49. /**
  50. * adapter
  51. *
  52. * @var Ruckusing_Adapter_Base
  53. */
  54. private $_adapter = null;
  55. /**
  56. * current task name
  57. *
  58. * @var string
  59. */
  60. private $_cur_task_name = "";
  61. /**
  62. * task options
  63. *
  64. * @var string
  65. */
  66. private $_task_options = "";
  67. /**
  68. * Environment
  69. * default is development
  70. * but can also be one 'test', 'production', etc...
  71. *
  72. * @var string
  73. */
  74. private $_env = "development";
  75. /**
  76. * set up some defaults
  77. *
  78. * @var array
  79. */
  80. private $_opt_map = array(
  81. 'env' => 'development'
  82. );
  83. /**
  84. * Flag to display help of task
  85. * @see Ruckusing_FrameworkRunner::parse_args
  86. *
  87. * @var boolean
  88. */
  89. private $_showhelp = false;
  90. /**
  91. * Creates an instance of Ruckusing_Adapters_Base
  92. *
  93. * @param array $config The current config
  94. * @param array $argv the supplied command line arguments
  95. *
  96. * @return Ruckusing_FrameworkRunner
  97. */
  98. public function __construct($config, $argv)
  99. {
  100. set_error_handler(array('Ruckusing_Exception', 'errorHandler'), E_ALL);
  101. set_exception_handler(array('Ruckusing_Exception', 'exceptionHandler'));
  102. //parse arguments
  103. $this->parse_args($argv);
  104. //set config variables
  105. $this->_config = $config;
  106. //verify config array
  107. $this->verify_db_config();
  108. //initialize logger
  109. $this->initialize_logger();
  110. //include all adapters
  111. $this->load_all_adapters(RUCKUSING_BASE . '/lib/Ruckusing/Adapter');
  112. //initialize logger
  113. $this->initialize_db();
  114. //initialize tasks
  115. $this->init_tasks();
  116. }
  117. /**
  118. * Execute the current task
  119. */
  120. public function execute()
  121. {
  122. if (empty($this->_cur_task_name)) {
  123. if (isset($_SERVER["argv"][1]) && stripos($_SERVER["argv"][1], '=') === false) {
  124. echo sprintf("\n\tWrong Task format: %s\n", $_SERVER["argv"][1]);
  125. }
  126. echo $this->help();
  127. } else {
  128. if ($this->_task_mgr->has_task($this->_cur_task_name)) {
  129. if ($this->_showhelp) {
  130. echo $this->_task_mgr->help($this->_cur_task_name);
  131. } else {
  132. $output = $this->_task_mgr->execute($this, $this->_cur_task_name, $this->_task_options);
  133. }
  134. } else {
  135. echo sprintf("\n\tTask not found: %s\n", $this->_cur_task_name);
  136. echo $this->help();
  137. }
  138. }
  139. if ($this->logger) {
  140. $this->logger->close();
  141. }
  142. }
  143. /**
  144. * Get the current adapter
  145. *
  146. * @return object
  147. */
  148. public function get_adapter()
  149. {
  150. return $this->_adapter;
  151. }
  152. /**
  153. * Initialize the task
  154. */
  155. public function init_tasks()
  156. {
  157. $this->_task_mgr = new Ruckusing_Task_Manager($this->_adapter);
  158. }
  159. /**
  160. * Get the current migration dir
  161. *
  162. * @return string
  163. */
  164. public function migrations_directory()
  165. {
  166. return $this->_config['migrations_dir'] . DIRECTORY_SEPARATOR . $this->_config['db'][$this->_env]['database'];
  167. }
  168. /**
  169. * Get the current db schema dir
  170. *
  171. * @return string
  172. */
  173. public function db_directory()
  174. {
  175. return $this->_config['db_dir'] . DIRECTORY_SEPARATOR . $this->_config['db'][$this->_env]['database'];
  176. }
  177. /**
  178. * Initialize the db
  179. */
  180. public function initialize_db()
  181. {
  182. $db = $this->_config['db'][$this->_env];
  183. $adapter = $this->get_adapter_class($db['type']);
  184. if (empty($adapter)) {
  185. throw new Ruckusing_Exception(
  186. sprintf("No adapter available for DB type: %s", $db['type']),
  187. Ruckusing_Exception::INVALID_ADAPTER
  188. );
  189. }
  190. //construct our adapter
  191. $this->_adapter = new $adapter($db, $this->logger);
  192. }
  193. /**
  194. * Initialize the logger
  195. */
  196. public function initialize_logger()
  197. {
  198. if (is_dir($this->_config['log_dir']) && !is_writable($this->_config['log_dir'])) {
  199. throw new Ruckusing_Exception(
  200. "\n\nCannot write to log directory: " . $this->_config['log_dir'] . "\n\nCheck permissions.\n\n",
  201. Ruckusing_Exception::INVALID_LOG
  202. );
  203. } elseif (!is_dir($this->_config['log_dir'])) {
  204. //try and create the log directory
  205. mkdir($this->_config['log_dir'], 0755, true);
  206. }
  207. $log_name = sprintf("%s.log", $this->_env);
  208. $this->logger = Ruckusing_Util_Logger::instance($this->_config['log_dir'] . "/" . $log_name);
  209. }
  210. /**
  211. * $argv is our complete command line argument set.
  212. * PHP gives us:
  213. * [0] = the actual file name we're executing
  214. * [1..N] = all other arguments
  215. *
  216. * Our task name should be at slot [1]
  217. * Anything else are additional parameters that we can pass
  218. * to our task and they can deal with them as they see fit.
  219. *
  220. * @param array $argv the current command line arguments
  221. */
  222. private function parse_args($argv)
  223. {
  224. $num_args = count($argv);
  225. $options = array();
  226. for ($i = 0; $i < $num_args; $i++) {
  227. $arg = $argv[$i];
  228. if (stripos($arg, ':') !== false) {
  229. $this->_cur_task_name = $arg;
  230. } elseif ($arg == 'help') {
  231. $this->_showhelp = true;
  232. continue;
  233. } elseif (stripos($arg, '=') !== false) {
  234. list($key, $value) = explode('=', $arg);
  235. $key = strtolower($key); // Allow both upper and lower case parameters
  236. $options[$key] = $value;
  237. if ($key == 'env') {
  238. $this->_env = $value;
  239. }
  240. }
  241. }
  242. $this->_task_options = $options;
  243. }
  244. /**
  245. * Update the local schema to handle multiple records versus the prior architecture
  246. * of storing a single version. In addition take all existing migration files
  247. * and register them in our new table, as they have already been executed.
  248. */
  249. public function update_schema_for_timestamps()
  250. {
  251. //only create the table if it doesnt already exist
  252. $this->_adapter->create_schema_version_table();
  253. //insert all existing records into our new table
  254. $migrator_util = new Ruckusing_Util_Migrator($this->_adapter);
  255. $files = $migrator_util->get_migration_files($this->migrations_directory(), 'up');
  256. foreach ($files as $file) {
  257. if ((int) $file['version'] >= PHP_INT_MAX) {
  258. //its new style like '20081010170207' so its not a candidate
  259. continue;
  260. }
  261. //query old table, if it less than or equal to our max version, then its a candidate for insertion
  262. $query_sql = sprintf("SELECT version FROM %s WHERE version >= %d", RUCKUSING_SCHEMA_TBL_NAME, $file['version']);
  263. $existing_version_old_style = $this->_adapter->select_one($query_sql);
  264. if (count($existing_version_old_style) > 0) {
  265. //make sure it doesnt exist in our new table, who knows how it got inserted?
  266. $new_vers_sql = sprintf("SELECT version FROM %s WHERE version = %d", RUCKUSING_TS_SCHEMA_TBL_NAME, $file['version']);
  267. $existing_version_new_style = $this->_adapter->select_one($new_vers_sql);
  268. if (empty($existing_version_new_style)) {
  269. // use printf & %d to force it to be stripped of any leading zeros, we *know* this represents an old version style
  270. // so we dont have to worry about PHP and integer overflow
  271. $insert_sql = sprintf("INSERT INTO %s (version) VALUES (%d)", RUCKUSING_TS_SCHEMA_TBL_NAME, $file['version']);
  272. $this->_adapter->query($insert_sql);
  273. }
  274. }
  275. }
  276. }
  277. /**
  278. * Set an option
  279. *
  280. * @param string $key the key to set
  281. * @param string $value the value to set
  282. */
  283. private function set_opt($key, $value)
  284. {
  285. if (!$key) {
  286. return;
  287. }
  288. $this->_opt_map[$key] = $value;
  289. }
  290. /**
  291. * Verify db config
  292. */
  293. private function verify_db_config()
  294. {
  295. if ( !array_key_exists($this->_env, $this->_config['db'])) {
  296. throw new Ruckusing_Exception(
  297. sprintf("Error: '%s' DB is not configured", $this->_env),
  298. Ruckusing_Exception::INVALID_CONFIG
  299. );
  300. }
  301. $env = $this->_env;
  302. $this->_active_db_config = $this->_config['db'][$this->_env];
  303. if (!array_key_exists("type",$this->_active_db_config)) {
  304. throw new Ruckusing_Exception(
  305. sprintf("Error: 'type' is not set for '%s' DB", $this->_env),
  306. Ruckusing_Exception::INVALID_CONFIG
  307. );
  308. }
  309. if (!array_key_exists("host",$this->_active_db_config)) {
  310. throw new Ruckusing_Exception(
  311. sprintf("Error: 'host' is not set for '%s' DB", $this->_env),
  312. Ruckusing_Exception::INVALID_CONFIG
  313. );
  314. }
  315. if (!array_key_exists("database",$this->_active_db_config)) {
  316. throw new Ruckusing_Exception(
  317. sprintf("Error: 'database' is not set for '%s' DB", $this->_env),
  318. Ruckusing_Exception::INVALID_CONFIG
  319. );
  320. }
  321. if (!array_key_exists("user",$this->_active_db_config)) {
  322. throw new Ruckusing_Exception(
  323. sprintf("Error: 'user' is not set for '%s' DB", $this->_env),
  324. Ruckusing_Exception::INVALID_CONFIG
  325. );
  326. }
  327. if (!array_key_exists("password",$this->_active_db_config)) {
  328. throw new Ruckusing_Exception(
  329. sprintf("Error: 'password' is not set for '%s' DB", $this->_env),
  330. Ruckusing_Exception::INVALID_CONFIG
  331. );
  332. }
  333. if (empty($this->_config['migrations_dir'])) {
  334. throw new Ruckusing_Exception(
  335. "Error: 'migrations_dir' is not set in config.",
  336. Ruckusing_Exception::INVALID_CONFIG
  337. );
  338. }
  339. if (empty($this->_config['db_dir'])) {
  340. throw new Ruckusing_Exception(
  341. "Error: 'db_dir' is not set in config.",
  342. Ruckusing_Exception::INVALID_CONFIG
  343. );
  344. }
  345. if (empty($this->_config['log_dir'])) {
  346. throw new Ruckusing_Exception(
  347. "Error: 'log_dir' is not set in config.",
  348. Ruckusing_Exception::INVALID_CONFIG
  349. );
  350. }
  351. }
  352. /**
  353. * Get the adapter class
  354. *
  355. * @param string $db_type the database type
  356. *
  357. * @return string
  358. */
  359. private function get_adapter_class($db_type)
  360. {
  361. $adapter_class = null;
  362. switch ($db_type) {
  363. case 'mysql':
  364. $adapter_class = "Ruckusing_Adapter_MySQL_Base";
  365. break;
  366. case 'mssql':
  367. $adapter_class = "Ruckusing_Adapter_MSSQL_Base";
  368. break;
  369. case 'pgsql':
  370. $adapter_class = "Ruckusing_Adapter_PgSQL_Base";
  371. break;
  372. }
  373. return $adapter_class;
  374. }
  375. /**
  376. * DB adapters are classes in lib/Ruckusing/Adapter
  377. * and they follow the file name syntax of "<DB Name>/Base.php".
  378. *
  379. * See the function "get_adapter_class" in this class for examples.
  380. *
  381. * @param string $adapter_dir the adapter dir
  382. */
  383. private function load_all_adapters($adapter_dir)
  384. {
  385. if (!is_dir($adapter_dir)) {
  386. throw new Ruckusing_Exception(
  387. sprintf("Adapter dir: %s does not exist", $adapter_dir),
  388. Ruckusing_Exception::INVALID_ADAPTER
  389. );
  390. return false;
  391. }
  392. $files = scandir($adapter_dir);
  393. foreach ($files as $f) {
  394. //skip over invalid files
  395. if ($f == '.' || $f == ".." || !is_dir($adapter_dir . '/' . $f)) {
  396. continue;
  397. }
  398. require_once $adapter_dir . '/' . $f . '/Base.php';
  399. }
  400. }
  401. /**
  402. * Return the usage of the task
  403. *
  404. * @return string
  405. */
  406. public function help()
  407. {
  408. // TODO: dynamically list all available tasks
  409. $output =<<<USAGE
  410. \tUsage: php {$_SERVER['argv'][0]} <task> [help] [task parameters] [env=environment]
  411. \thelp: Display this message
  412. \tenv: The env command line parameter can be used to specify a different
  413. \tdatabase to run against, as specific in the configuration file
  414. \t(config/database.inc.php).
  415. \tBy default, env is "development"
  416. \ttask: In a nutshell, task names are pseudo-namespaced. The tasks that come
  417. \twith the framework are namespaced to "db" (e.g. the tasks are "db:migrate",
  418. \t"db:setup", etc).
  419. \tAll tasks available actually :
  420. \t- db:setup : A basic task to initialize your DB for migrations is
  421. \tavailable. One should always run this task when first starting out.
  422. \t- db:generate : A generic task which acts as a Generator for migrations.
  423. \t- db:migrate : The primary purpose of the framework is to run migrations,
  424. \tand the execution of migrations is all handled by just a regular ol' task.
  425. \t- db:version : It is always possible to ask the framework (really the DB)
  426. \twhat version it is currently at.
  427. \t- db:status : With this taks you'll get an overview of the already
  428. \texecuted migrations and which will be executed when running db:migrate
  429. \t- db:schema : It can be beneficial to get a dump of the DB in raw SQL
  430. \tformat which represents the current version.
  431. USAGE;
  432. return $output;
  433. }
  434. }