1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
23: App::import('Core', 'File', false);
24: App::import('Model', 'CakeSchema', false);
25:
26: 27: 28: 29: 30: 31: 32:
33: class SchemaShell extends Shell {
34:
35: 36: 37: 38: 39: 40:
41: var $__dry = null;
42:
43: 44: 45: 46: 47:
48: function initialize() {
49: $this->_welcome();
50: $this->out('Cake Schema Shell');
51: $this->hr();
52: }
53:
54: 55: 56: 57: 58:
59: function startup() {
60: $name = $file = $path = $connection = $plugin = null;
61: if (!empty($this->params['name'])) {
62: $name = $this->params['name'];
63: } elseif (!empty($this->args[0])) {
64: $name = $this->params['name'] = $this->args[0];
65: }
66:
67: if (strpos($name, '.')) {
68: list($this->params['plugin'], $splitName) = pluginSplit($name);
69: $name = $this->params['name'] = $splitName;
70: }
71:
72: if ($name) {
73: $this->params['file'] = Inflector::underscore($name);
74: }
75:
76: if (empty($this->params['file'])) {
77: $this->params['file'] = 'schema.php';
78: }
79: if (strpos($this->params['file'], '.php') === false) {
80: $this->params['file'] .= '.php';
81: }
82: $file = $this->params['file'];
83:
84: if (!empty($this->params['path'])) {
85: $path = $this->params['path'];
86: }
87:
88: if (!empty($this->params['connection'])) {
89: $connection = $this->params['connection'];
90: }
91: if (!empty($this->params['plugin'])) {
92: $plugin = $this->params['plugin'];
93: if (empty($name)) {
94: $name = $plugin;
95: }
96: }
97: $this->Schema =& new CakeSchema(compact('name', 'path', 'file', 'connection', 'plugin'));
98: }
99:
100: 101: 102: 103: 104:
105: function main() {
106: $this->help();
107: }
108:
109: 110: 111: 112: 113: 114:
115: function view() {
116: $File = new File($this->Schema->path . DS . $this->params['file']);
117: if ($File->exists()) {
118: $this->out($File->read());
119: $this->_stop();
120: } else {
121: $file = $this->Schema->path . DS . $this->params['file'];
122: $this->err(sprintf(__('Schema file (%s) could not be found.', true), $file));
123: $this->_stop();
124: }
125: }
126:
127: 128: 129: 130: 131: 132:
133: function generate() {
134: $this->out(__('Generating Schema...', true));
135: $options = array();
136: if (isset($this->params['f'])) {
137: $options = array('models' => false);
138: }
139:
140: $snapshot = false;
141: if (isset($this->args[0]) && $this->args[0] === 'snapshot') {
142: $snapshot = true;
143: }
144:
145: if (!$snapshot && file_exists($this->Schema->path . DS . $this->params['file'])) {
146: $snapshot = true;
147: $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'));
148: if ($result === 'q') {
149: return $this->_stop();
150: }
151: if ($result === 'o') {
152: $snapshot = false;
153: }
154: }
155:
156: $cacheDisable = Configure::read('Cache.disable');
157: Configure::write('Cache.disable', true);
158:
159: $content = $this->Schema->read($options);
160: $content['file'] = $this->params['file'];
161:
162: Configure::write('Cache.disable', $cacheDisable);
163:
164: if ($snapshot === true) {
165: $Folder =& new Folder($this->Schema->path);
166: $result = $Folder->read();
167:
168: $numToUse = false;
169: if (isset($this->params['s'])) {
170: $numToUse = $this->params['s'];
171: }
172:
173: $count = 1;
174: if (!empty($result[1])) {
175: foreach ($result[1] as $file) {
176: if (preg_match('/schema(?:[_\d]*)?\.php$/', $file)) {
177: $count++;
178: }
179: }
180: }
181:
182: if ($numToUse !== false) {
183: if ($numToUse > $count) {
184: $count = $numToUse;
185: }
186: }
187:
188: $fileName = rtrim($this->params['file'], '.php');
189: $content['file'] = $fileName . '_' . $count . '.php';
190: }
191:
192: if ($this->Schema->write($content)) {
193: $this->out(sprintf(__('Schema file: %s generated', true), $content['file']));
194: $this->_stop();
195: } else {
196: $this->err(__('Schema file: %s generated', true));
197: $this->_stop();
198: }
199: }
200:
201: 202: 203: 204: 205: 206: 207: 208: 209:
210: function dump() {
211: $write = false;
212: $Schema = $this->Schema->load();
213: if (!$Schema) {
214: $this->err(__('Schema could not be loaded', true));
215: $this->_stop();
216: }
217: if (isset($this->params['write'])) {
218: if ($this->params['write'] == 1) {
219: $write = Inflector::underscore($this->Schema->name);
220: } else {
221: $write = $this->params['write'];
222: }
223: }
224: $db =& ConnectionManager::getDataSource($this->Schema->connection);
225: $contents = "#" . $Schema->name . " sql generated on: " . date('Y-m-d H:i:s') . " : " . time() . "\n\n";
226: $contents .= $db->dropSchema($Schema) . "\n\n". $db->createSchema($Schema);
227:
228: if ($write) {
229: if (strpos($write, '.sql') === false) {
230: $write .= '.sql';
231: }
232: if (strpos($write, DS) !== false) {
233: $File =& new File($write, true);
234: } else {
235: $File =& new File($this->Schema->path . DS . $write, true);
236: }
237:
238: if ($File->write($contents)) {
239: $this->out(sprintf(__('SQL dump file created in %s', true), $File->pwd()));
240: $this->_stop();
241: } else {
242: $this->err(__('SQL dump could not be created', true));
243: $this->_stop();
244: }
245: }
246: $this->out($contents);
247: return $contents;
248: }
249:
250: 251: 252: 253: 254:
255: function create() {
256: list($Schema, $table) = $this->_loadSchema();
257: $this->__create($Schema, $table);
258: }
259:
260: 261: 262: 263: 264:
265: function update() {
266: list($Schema, $table) = $this->_loadSchema();
267: $this->__update($Schema, $table);
268: }
269:
270: 271: 272: 273: 274:
275: function _loadSchema() {
276: $name = $plugin = null;
277: if (isset($this->params['name'])) {
278: $name = $this->params['name'];
279: }
280: if (isset($this->params['plugin'])) {
281: $plugin = $this->params['plugin'];
282: }
283:
284: if (isset($this->params['dry'])) {
285: $this->__dry = true;
286: $this->out(__('Performing a dry run.', true));
287: }
288:
289: $options = array('name' => $name, 'plugin' => $plugin);
290: if (isset($this->params['s'])) {
291: $fileName = rtrim($this->Schema->file, '.php');
292: $options['file'] = $fileName . '_' . $this->params['s'] . '.php';
293: }
294:
295: $Schema =& $this->Schema->load($options);
296:
297: if (!$Schema) {
298: $this->err(sprintf(__('%s could not be loaded', true), $this->Schema->path . DS . $this->Schema->file));
299: $this->_stop();
300: }
301: $table = null;
302: if (isset($this->args[1])) {
303: $table = $this->args[1];
304: }
305: return array(&$Schema, $table);
306: }
307:
308: 309: 310: 311: 312: 313:
314: function __create(&$Schema, $table = null) {
315: $db =& ConnectionManager::getDataSource($this->Schema->connection);
316:
317: $drop = $create = array();
318:
319: if (!$table) {
320: foreach ($Schema->tables as $table => $fields) {
321: $drop[$table] = $db->dropSchema($Schema, $table);
322: $create[$table] = $db->createSchema($Schema, $table);
323: }
324: } elseif (isset($Schema->tables[$table])) {
325: $drop[$table] = $db->dropSchema($Schema, $table);
326: $create[$table] = $db->createSchema($Schema, $table);
327: }
328: if (empty($drop) || empty($create)) {
329: $this->out(__('Schema is up to date.', true));
330: $this->_stop();
331: }
332:
333: $this->out("\n" . __('The following table(s) will be dropped.', true));
334: $this->out(array_keys($drop));
335:
336: if ('y' == $this->in(__('Are you sure you want to drop the table(s)?', true), array('y', 'n'), 'n')) {
337: $this->out(__('Dropping table(s).', true));
338: $this->__run($drop, 'drop', $Schema);
339: }
340:
341: $this->out("\n" . __('The following table(s) will be created.', true));
342: $this->out(array_keys($create));
343:
344: if ('y' == $this->in(__('Are you sure you want to create the table(s)?', true), array('y', 'n'), 'y')) {
345: $this->out(__('Creating table(s).', true));
346: $this->__run($create, 'create', $Schema);
347: }
348: $this->out(__('End create.', true));
349: }
350:
351: 352: 353: 354: 355: 356:
357: function __update(&$Schema, $table = null) {
358: $db =& ConnectionManager::getDataSource($this->Schema->connection);
359:
360: $this->out(__('Comparing Database to Schema...', true));
361: $options = array();
362: if (isset($this->params['f'])) {
363: $options['models'] = false;
364: }
365: $Old = $this->Schema->read($options);
366: $compare = $this->Schema->compare($Old, $Schema);
367:
368: $contents = array();
369:
370: if (empty($table)) {
371: foreach ($compare as $table => $changes) {
372: $contents[$table] = $db->alterSchema(array($table => $changes), $table);
373: }
374: } elseif (isset($compare[$table])) {
375: $contents[$table] = $db->alterSchema(array($table => $compare[$table]), $table);
376: }
377:
378: if (empty($contents)) {
379: $this->out(__('Schema is up to date.', true));
380: $this->_stop();
381: }
382:
383: $this->out("\n" . __('The following statements will run.', true));
384: $this->out(array_map('trim', $contents));
385: if ('y' == $this->in(__('Are you sure you want to alter the tables?', true), array('y', 'n'), 'n')) {
386: $this->out();
387: $this->out(__('Updating Database...', true));
388: $this->__run($contents, 'update', $Schema);
389: }
390:
391: $this->out(__('End update.', true));
392: }
393:
394: 395: 396: 397: 398:
399: function __run($contents, $event, &$Schema) {
400: if (empty($contents)) {
401: $this->err(__('Sql could not be run', true));
402: return;
403: }
404: Configure::write('debug', 2);
405: $db =& ConnectionManager::getDataSource($this->Schema->connection);
406:
407: foreach ($contents as $table => $sql) {
408: if (empty($sql)) {
409: $this->out(sprintf(__('%s is up to date.', true), $table));
410: } else {
411: if ($this->__dry === true) {
412: $this->out(sprintf(__('Dry run for %s :', true), $table));
413: $this->out($sql);
414: } else {
415: if (!$Schema->before(array($event => $table))) {
416: return false;
417: }
418: $error = null;
419: if (!$db->execute($sql)) {
420: $error = $table . ': ' . $db->lastError();
421: }
422:
423: $Schema->after(array($event => $table, 'errors' => $error));
424:
425: if (!empty($error)) {
426: $this->out($error);
427: } else {
428: $this->out(sprintf(__('%s updated.', true), $table));
429: }
430: }
431: }
432: }
433: }
434:
435: 436: 437: 438: 439:
440: function help() {
441: $help = <<<TEXT
442: The Schema Shell generates a schema object from
443: the database and updates the database from the schema.
444: ---------------------------------------------------------------
445: Usage: cake schema <command> <arg1> <arg2>...
446: ---------------------------------------------------------------
447: Params:
448: -connection <config>
449: set db config <config>. uses 'default' if none is specified
450:
451: -path <dir>
452: path <dir> to read and write schema.php.
453: default path: {$this->Schema->path}
454:
455: -name <name>
456: Classname to use. If <name> is Plugin.className, it will
457: set the plugin and name params.
458:
459: -file <name>
460: file <name> to read and write.
461: default file: {$this->Schema->file}
462:
463: -s <number>
464: snapshot <number> to use for run.
465:
466: -dry
467: Perform a dry run on create + update commands.
468: Queries will be output to window instead of executed.
469:
470: -f
471: force 'generate' to create a new schema.
472:
473: -plugin
474: Indicate the plugin to use.
475:
476: Commands:
477:
478: schema help
479: shows this help message.
480:
481: schema view <name>
482: read and output contents of schema file.
483:
484: schema generate
485: reads from 'connection' writes to 'path'
486: To force generation of all tables into the schema, use the -f param.
487: Use 'schema generate snapshot <number>' to generate snapshots
488: which you can use with the -s parameter in the other operations.
489:
490: schema dump <name>
491: Dump database sql based on schema file to stdout.
492: If you use the `-write` param is used a .sql will be generated.
493: If `-write` is a filename, then that file name will be generate.
494: If `-write` is a full path, the schema will be written there.
495:
496: schema create <name> <table>
497: Drop and create tables based on schema file
498: optional <table> argument can be used to create only a single
499: table in the schema. Pass the -s param with a number to use a snapshot.
500: Use the `-dry` param to preview the changes.
501:
502: schema update <name> <table>
503: Alter the tables based on schema file. Optional <table>
504: parameter will only update one table.
505: To use a snapshot pass the `-s` param with the snapshot number.
506: To preview the changes that will be done use `-dry`.
507: To force update of all tables into the schema, use the -f param.
508: TEXT;
509: $this->out($help);
510: $this->_stop();
511: }
512: }
513: