PageRenderTime 103ms CodeModel.GetById 39ms RepoModel.GetById 0ms app.codeStats 0ms

/cake/console/libs/schema.php

https://github.com/raulpa/cakephp
PHP | 503 lines | 332 code | 64 blank | 107 comment | 65 complexity | fefd38e9f88026866350c2e18dcd18df MD5 | raw file
  1. <?php
  2. /**
  3. * Command-line database management utility to automate programmer chores.
  4. *
  5. * Schema is CakePHP's database management utility. This helps you maintain versions of
  6. * of your database.
  7. *
  8. * PHP versions 4 and 5
  9. *
  10. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  11. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  12. *
  13. * Licensed under The MIT License
  14. * Redistributions of files must retain the above copyright notice.
  15. *
  16. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  17. * @link http://cakephp.org CakePHP(tm) Project
  18. * @package cake
  19. * @subpackage cake.cake.console.libs
  20. * @since CakePHP(tm) v 1.2.0.5550
  21. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  22. */
  23. App::import('Core', 'File', false);
  24. App::import('Model', 'CakeSchema', false);
  25. /**
  26. * Schema is a command-line database management utility for automating programmer chores.
  27. *
  28. * @package cake
  29. * @subpackage cake.cake.console.libs
  30. * @link http://book.cakephp.org/view/1523/Schema-management-and-migrations
  31. */
  32. class SchemaShell extends Shell {
  33. /**
  34. * is this a dry run?
  35. *
  36. * @var boolean
  37. * @access private
  38. */
  39. var $__dry = null;
  40. /**
  41. * Override initialize
  42. *
  43. * @access public
  44. */
  45. function initialize() {
  46. $this->_welcome();
  47. $this->out('Cake Schema Shell');
  48. $this->hr();
  49. }
  50. /**
  51. * Override startup
  52. *
  53. * @access public
  54. */
  55. function startup() {
  56. $name = $file = $path = $connection = $plugin = null;
  57. if (!empty($this->params['name'])) {
  58. $name = $this->params['name'];
  59. } elseif (!empty($this->args[0])) {
  60. $name = $this->params['name'] = $this->args[0];
  61. }
  62. if (strpos($name, '.')) {
  63. list($this->params['plugin'], $splitName) = pluginSplit($name);
  64. $name = $this->params['name'] = $splitName;
  65. }
  66. if ($name) {
  67. $this->params['file'] = Inflector::underscore($name);
  68. }
  69. if (empty($this->params['file'])) {
  70. $this->params['file'] = 'schema.php';
  71. }
  72. if (strpos($this->params['file'], '.php') === false) {
  73. $this->params['file'] .= '.php';
  74. }
  75. $file = $this->params['file'];
  76. if (!empty($this->params['path'])) {
  77. $path = $this->params['path'];
  78. }
  79. if (!empty($this->params['connection'])) {
  80. $connection = $this->params['connection'];
  81. }
  82. if (!empty($this->params['plugin'])) {
  83. $plugin = $this->params['plugin'];
  84. }
  85. $this->Schema =& new CakeSchema(compact('name', 'path', 'file', 'connection', 'plugin'));
  86. }
  87. /**
  88. * Override main
  89. *
  90. * @access public
  91. */
  92. function main() {
  93. $this->help();
  94. }
  95. /**
  96. * Read and output contents of schema object
  97. * path to read as second arg
  98. *
  99. * @access public
  100. */
  101. function view() {
  102. $File = new File($this->Schema->path . DS . $this->params['file']);
  103. if ($File->exists()) {
  104. $this->out($File->read());
  105. $this->_stop();
  106. } else {
  107. $file = $this->Schema->path . DS . $this->params['file'];
  108. $this->err(sprintf(__('Schema file (%s) could not be found.', true), $file));
  109. $this->_stop();
  110. }
  111. }
  112. /**
  113. * Read database and Write schema object
  114. * accepts a connection as first arg or path to save as second arg
  115. *
  116. * @access public
  117. */
  118. function generate() {
  119. $this->out(__('Generating Schema...', true));
  120. $options = array();
  121. if (isset($this->params['f'])) {
  122. $options = array('models' => false);
  123. }
  124. $snapshot = false;
  125. if (isset($this->args[0]) && $this->args[0] === 'snapshot') {
  126. $snapshot = true;
  127. }
  128. if (!$snapshot && file_exists($this->Schema->path . DS . $this->params['file'])) {
  129. $snapshot = true;
  130. $result = strtolower($this->in("Schema file exists.\n [O]verwrite\n [S]napshot\n [Q]uit\nWould you like to do?", array('o', 's', 'q'), 's'));
  131. if ($result === 'q') {
  132. return $this->_stop();
  133. }
  134. if ($result === 'o') {
  135. $snapshot = false;
  136. }
  137. }
  138. $content = $this->Schema->read($options);
  139. $content['file'] = $this->params['file'];
  140. if ($snapshot === true) {
  141. $Folder =& new Folder($this->Schema->path);
  142. $result = $Folder->read();
  143. $numToUse = false;
  144. if (isset($this->params['s'])) {
  145. $numToUse = $this->params['s'];
  146. }
  147. $count = 1;
  148. if (!empty($result[1])) {
  149. foreach ($result[1] as $file) {
  150. if (preg_match('/schema(?:[_\d]*)?\.php$/', $file)) {
  151. $count++;
  152. }
  153. }
  154. }
  155. if ($numToUse !== false) {
  156. if ($numToUse > $count) {
  157. $count = $numToUse;
  158. }
  159. }
  160. $fileName = rtrim($this->params['file'], '.php');
  161. $content['file'] = $fileName . '_' . $count . '.php';
  162. }
  163. if ($this->Schema->write($content)) {
  164. $this->out(sprintf(__('Schema file: %s generated', true), $content['file']));
  165. $this->_stop();
  166. } else {
  167. $this->err(__('Schema file: %s generated', true));
  168. $this->_stop();
  169. }
  170. }
  171. /**
  172. * Dump Schema object to sql file
  173. * Use the `write` param to enable and control SQL file output location.
  174. * Simply using -write will write the sql file to the same dir as the schema file.
  175. * If -write contains a full path name the file will be saved there. If -write only
  176. * contains no DS, that will be used as the file name, in the same dir as the schema file.
  177. *
  178. * @access public
  179. */
  180. function dump() {
  181. $write = false;
  182. $Schema = $this->Schema->load();
  183. if (!$Schema) {
  184. $this->err(__('Schema could not be loaded', true));
  185. $this->_stop();
  186. }
  187. if (isset($this->params['write'])) {
  188. if ($this->params['write'] == 1) {
  189. $write = Inflector::underscore($this->Schema->name);
  190. } else {
  191. $write = $this->params['write'];
  192. }
  193. }
  194. $db =& ConnectionManager::getDataSource($this->Schema->connection);
  195. $contents = "#" . $Schema->name . " sql generated on: " . date('Y-m-d H:i:s') . " : " . time() . "\n\n";
  196. $contents .= $db->dropSchema($Schema) . "\n\n". $db->createSchema($Schema);
  197. if ($write) {
  198. if (strpos($write, '.sql') === false) {
  199. $write .= '.sql';
  200. }
  201. if (strpos($write, DS) !== false) {
  202. $File =& new File($write, true);
  203. } else {
  204. $File =& new File($this->Schema->path . DS . $write, true);
  205. }
  206. if ($File->write($contents)) {
  207. $this->out(sprintf(__('SQL dump file created in %s', true), $File->pwd()));
  208. $this->_stop();
  209. } else {
  210. $this->err(__('SQL dump could not be created', true));
  211. $this->_stop();
  212. }
  213. }
  214. $this->out($contents);
  215. return $contents;
  216. }
  217. /**
  218. * Run database create commands. Alias for run create.
  219. *
  220. * @return void
  221. */
  222. function create() {
  223. list($Schema, $table) = $this->_loadSchema();
  224. $this->__create($Schema, $table);
  225. }
  226. /**
  227. * Run database create commands. Alias for run create.
  228. *
  229. * @return void
  230. */
  231. function update() {
  232. list($Schema, $table) = $this->_loadSchema();
  233. $this->__update($Schema, $table);
  234. }
  235. /**
  236. * Prepares the Schema objects for database operations.
  237. *
  238. * @return void
  239. */
  240. function _loadSchema() {
  241. $name = $plugin = null;
  242. if (isset($this->params['name'])) {
  243. $name = $this->params['name'];
  244. }
  245. if (isset($this->params['plugin'])) {
  246. $plugin = $this->params['plugin'];
  247. }
  248. if (isset($this->params['dry'])) {
  249. $this->__dry = true;
  250. $this->out(__('Performing a dry run.', true));
  251. }
  252. $options = array('name' => $name, 'plugin' => $plugin);
  253. if (isset($this->params['s'])) {
  254. $fileName = rtrim($this->Schema->file, '.php');
  255. $options['file'] = $fileName . '_' . $this->params['s'] . '.php';
  256. }
  257. $Schema =& $this->Schema->load($options);
  258. if (!$Schema) {
  259. $this->err(sprintf(__('%s could not be loaded', true), $this->Schema->path . DS . $this->Schema->file));
  260. $this->_stop();
  261. }
  262. $table = null;
  263. if (isset($this->args[1])) {
  264. $table = $this->args[1];
  265. }
  266. return array(&$Schema, $table);
  267. }
  268. /**
  269. * Create database from Schema object
  270. * Should be called via the run method
  271. *
  272. * @access private
  273. */
  274. function __create(&$Schema, $table = null) {
  275. $db =& ConnectionManager::getDataSource($this->Schema->connection);
  276. $drop = $create = array();
  277. if (!$table) {
  278. foreach ($Schema->tables as $table => $fields) {
  279. $drop[$table] = $db->dropSchema($Schema, $table);
  280. $create[$table] = $db->createSchema($Schema, $table);
  281. }
  282. } elseif (isset($Schema->tables[$table])) {
  283. $drop[$table] = $db->dropSchema($Schema, $table);
  284. $create[$table] = $db->createSchema($Schema, $table);
  285. }
  286. if (empty($drop) || empty($create)) {
  287. $this->out(__('Schema is up to date.', true));
  288. $this->_stop();
  289. }
  290. $this->out("\n" . __('The following table(s) will be dropped.', true));
  291. $this->out(array_keys($drop));
  292. if ('y' == $this->in(__('Are you sure you want to drop the table(s)?', true), array('y', 'n'), 'n')) {
  293. $this->out(__('Dropping table(s).', true));
  294. $this->__run($drop, 'drop', $Schema);
  295. }
  296. $this->out("\n" . __('The following table(s) will be created.', true));
  297. $this->out(array_keys($create));
  298. if ('y' == $this->in(__('Are you sure you want to create the table(s)?', true), array('y', 'n'), 'y')) {
  299. $this->out(__('Creating table(s).', true));
  300. $this->__run($create, 'create', $Schema);
  301. }
  302. $this->out(__('End create.', true));
  303. }
  304. /**
  305. * Update database with Schema object
  306. * Should be called via the run method
  307. *
  308. * @access private
  309. */
  310. function __update(&$Schema, $table = null) {
  311. $db =& ConnectionManager::getDataSource($this->Schema->connection);
  312. $this->out(__('Comparing Database to Schema...', true));
  313. $options = array();
  314. if (isset($this->params['f'])) {
  315. $options['models'] = false;
  316. }
  317. $Old = $this->Schema->read($options);
  318. $compare = $this->Schema->compare($Old, $Schema);
  319. $contents = array();
  320. if (empty($table)) {
  321. foreach ($compare as $table => $changes) {
  322. $contents[$table] = $db->alterSchema(array($table => $changes), $table);
  323. }
  324. } elseif (isset($compare[$table])) {
  325. $contents[$table] = $db->alterSchema(array($table => $compare[$table]), $table);
  326. }
  327. if (empty($contents)) {
  328. $this->out(__('Schema is up to date.', true));
  329. $this->_stop();
  330. }
  331. $this->out("\n" . __('The following statements will run.', true));
  332. $this->out(array_map('trim', $contents));
  333. if ('y' == $this->in(__('Are you sure you want to alter the tables?', true), array('y', 'n'), 'n')) {
  334. $this->out();
  335. $this->out(__('Updating Database...', true));
  336. $this->__run($contents, 'update', $Schema);
  337. }
  338. $this->out(__('End update.', true));
  339. }
  340. /**
  341. * Runs sql from __create() or __update()
  342. *
  343. * @access private
  344. */
  345. function __run($contents, $event, &$Schema) {
  346. if (empty($contents)) {
  347. $this->err(__('Sql could not be run', true));
  348. return;
  349. }
  350. Configure::write('debug', 2);
  351. $db =& ConnectionManager::getDataSource($this->Schema->connection);
  352. foreach ($contents as $table => $sql) {
  353. if (empty($sql)) {
  354. $this->out(sprintf(__('%s is up to date.', true), $table));
  355. } else {
  356. if ($this->__dry === true) {
  357. $this->out(sprintf(__('Dry run for %s :', true), $table));
  358. $this->out($sql);
  359. } else {
  360. if (!$Schema->before(array($event => $table))) {
  361. return false;
  362. }
  363. $error = null;
  364. if (!$db->execute($sql)) {
  365. $error = $table . ': ' . $db->lastError();
  366. }
  367. $Schema->after(array($event => $table, 'errors' => $error));
  368. if (!empty($error)) {
  369. $this->out($error);
  370. } else {
  371. $this->out(sprintf(__('%s updated.', true), $table));
  372. }
  373. }
  374. }
  375. }
  376. }
  377. /**
  378. * Displays help contents
  379. *
  380. * @access public
  381. */
  382. function help() {
  383. $help = <<<TEXT
  384. The Schema Shell generates a schema object from
  385. the database and updates the database from the schema.
  386. ---------------------------------------------------------------
  387. Usage: cake schema <command> <arg1> <arg2>...
  388. ---------------------------------------------------------------
  389. Params:
  390. -connection <config>
  391. set db config <config>. uses 'default' if none is specified
  392. -path <dir>
  393. path <dir> to read and write schema.php.
  394. default path: {$this->Schema->path}
  395. -name <name>
  396. Classname to use. If <name> is Plugin.className, it will
  397. set the plugin and name params.
  398. -file <name>
  399. file <name> to read and write.
  400. default file: {$this->Schema->file}
  401. -s <number>
  402. snapshot <number> to use for run.
  403. -dry
  404. Perform a dry run on create + update commands.
  405. Queries will be output to window instead of executed.
  406. -f
  407. force 'generate' to create a new schema.
  408. -plugin
  409. Indicate the plugin to use.
  410. Commands:
  411. schema help
  412. shows this help message.
  413. schema view <name>
  414. read and output contents of schema file.
  415. schema generate
  416. reads from 'connection' writes to 'path'
  417. To force generation of all tables into the schema, use the -f param.
  418. Use 'schema generate snapshot <number>' to generate snapshots
  419. which you can use with the -s parameter in the other operations.
  420. schema dump <name>
  421. Dump database sql based on schema file to stdout.
  422. If you use the `-write` param is used a .sql will be generated.
  423. If `-write` is a filename, then that file name will be generate.
  424. If `-write` is a full path, the schema will be written there.
  425. schema create <name> <table>
  426. Drop and create tables based on schema file
  427. optional <table> argument can be used to create only a single
  428. table in the schema. Pass the -s param with a number to use a snapshot.
  429. Use the `-dry` param to preview the changes.
  430. schema update <name> <table>
  431. Alter the tables based on schema file. Optional <table>
  432. parameter will only update one table.
  433. To use a snapshot pass the `-s` param with the snapshot number.
  434. To preview the changes that will be done use `-dry`.
  435. TEXT;
  436. $this->out($help);
  437. $this->_stop();
  438. }
  439. }