source: Dev/branches/cakephp/cake/console/libs/schema.php @ 126

Last change on this file since 126 was 126, checked in by fpvanagthoven, 14 years ago

Cakephp branch.

File size: 13.4 KB
Line 
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-2011, 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-2011, 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 */
23App::import('Core', 'File', false);
24App::import('Model', 'CakeSchema', false);
25
26/**
27 * Schema is a command-line database management utility for automating programmer chores.
28 *
29 * @package       cake
30 * @subpackage    cake.cake.console.libs
31 * @link          http://book.cakephp.org/view/1523/Schema-management-and-migrations
32 */
33class SchemaShell extends Shell {
34
35/**
36 * is this a dry run?
37 *
38 * @var boolean
39 * @access private
40 */
41        var $__dry = null;
42
43/**
44 * Override initialize
45 *
46 * @access public
47 */
48        function initialize() {
49                $this->_welcome();
50                $this->out('Cake Schema Shell');
51                $this->hr();
52        }
53
54/**
55 * Override startup
56 *
57 * @access public
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 * Override main
102 *
103 * @access public
104 */
105        function main() {
106                $this->help();
107        }
108
109/**
110 * Read and output contents of schema object
111 * path to read as second arg
112 *
113 * @access public
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 * Read database and Write schema object
129 * accepts a connection as first arg or path to save as second arg
130 *
131 * @access public
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 * Dump Schema object to sql file
203 * Use the `write` param to enable and control SQL file output location.
204 * Simply using -write will write the sql file to the same dir as the schema file.
205 * If -write contains a full path name the file will be saved there. If -write only
206 * contains no DS, that will be used as the file name, in the same dir as the schema file.
207 *
208 * @access public
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 * Run database create commands.  Alias for run create.
252 *
253 * @return void
254 */
255        function create() {
256                list($Schema, $table) = $this->_loadSchema();
257                $this->__create($Schema, $table);
258        }
259
260/**
261 * Run database create commands.  Alias for run create.
262 *
263 * @return void
264 */
265        function update() {
266                list($Schema, $table) = $this->_loadSchema();
267                $this->__update($Schema, $table);
268        }
269
270/**
271 * Prepares the Schema objects for database operations.
272 *
273 * @return void
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 * Create database from Schema object
310 * Should be called via the run method
311 *
312 * @access private
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 * Update database with Schema object
353 * Should be called via the run method
354 *
355 * @access private
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 * Runs sql from __create() or __update()
396 *
397 * @access private
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 * Displays help contents
437 *
438 * @access public
439 */
440        function help() {
441                $help = <<<TEXT
442The Schema Shell generates a schema object from
443the database and updates the database from the schema.
444---------------------------------------------------------------
445Usage: cake schema <command> <arg1> <arg2>...
446---------------------------------------------------------------
447Params:
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
476Commands:
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.
508TEXT;
509                $this->out($help);
510                $this->_stop();
511        }
512}
Note: See TracBrowser for help on using the repository browser.