1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20: App::import('Core', array('Model', 'ConnectionManager'));
21:
22: 23: 24: 25: 26: 27:
28: class CakeSchema extends Object {
29:
30: 31: 32: 33: 34: 35:
36: var $name = null;
37:
38: 39: 40: 41: 42: 43:
44: var $path = null;
45:
46: 47: 48: 49: 50: 51:
52: var $file = 'schema.php';
53:
54: 55: 56: 57: 58: 59:
60: var $connection = 'default';
61:
62: 63: 64: 65: 66:
67: var $plugin = null;
68:
69: 70: 71: 72: 73: 74:
75: var $tables = array();
76:
77: 78: 79: 80: 81:
82: function __construct($options = array()) {
83: parent::__construct();
84:
85: if (empty($options['name'])) {
86: $this->name = preg_replace('/schema$/i', '', get_class($this));
87: }
88: if (!empty($options['plugin'])) {
89: $this->plugin = $options['plugin'];
90: }
91:
92: if (strtolower($this->name) === 'cake') {
93: $this->name = Inflector::camelize(Inflector::slug(Configure::read('App.dir')));
94: }
95:
96: if (empty($options['path'])) {
97: if (is_dir(CONFIGS . 'schema')) {
98: $this->path = CONFIGS . 'schema';
99: } else {
100: $this->path = CONFIGS . 'sql';
101: }
102: }
103:
104: $options = array_merge(get_object_vars($this), $options);
105: $this->_build($options);
106: }
107:
108: 109: 110: 111: 112: 113: 114:
115: function _build($data) {
116: $file = null;
117: foreach ($data as $key => $val) {
118: if (!empty($val)) {
119: if (!in_array($key, array('plugin', 'name', 'path', 'file', 'connection', 'tables', '_log'))) {
120: $this->tables[$key] = $val;
121: unset($this->{$key});
122: } elseif ($key !== 'tables') {
123: if ($key === 'name' && $val !== $this->name && !isset($data['file'])) {
124: $file = Inflector::underscore($val) . '.php';
125: }
126: $this->{$key} = $val;
127: }
128: }
129: }
130: if (file_exists($this->path . DS . $file) && is_file($this->path . DS . $file)) {
131: $this->file = $file;
132: } elseif (!empty($this->plugin)) {
133: $this->path = App::pluginPath($this->plugin) . 'config' . DS . 'schema';
134: }
135: }
136:
137: 138: 139: 140: 141: 142: 143:
144: function before($event = array()) {
145: return true;
146: }
147:
148: 149: 150: 151: 152: 153:
154: function after($event = array()) {
155: }
156:
157: 158: 159: 160: 161: 162: 163:
164: function &load($options = array()) {
165: if (is_string($options)) {
166: $options = array('path' => $options);
167: }
168:
169: $this->_build($options);
170: extract(get_object_vars($this));
171:
172: $class = $name .'Schema';
173:
174: if (!class_exists($class)) {
175: if (file_exists($path . DS . $file) && is_file($path . DS . $file)) {
176: require_once($path . DS . $file);
177: } elseif (file_exists($path . DS . 'schema.php') && is_file($path . DS . 'schema.php')) {
178: require_once($path . DS . 'schema.php');
179: }
180: }
181:
182: if (class_exists($class)) {
183: $Schema =& new $class($options);
184: return $Schema;
185: }
186: $false = false;
187: return $false;
188: }
189:
190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202:
203: function read($options = array()) {
204: extract(array_merge(
205: array(
206: 'connection' => $this->connection,
207: 'name' => $this->name,
208: 'models' => true,
209: ),
210: $options
211: ));
212: $db =& ConnectionManager::getDataSource($connection);
213:
214: App::import('Model', 'AppModel');
215: if (isset($this->plugin)) {
216: App::import('Model', Inflector::camelize($this->plugin) . 'AppModel');
217: }
218:
219: $tables = array();
220: $currentTables = $db->listSources();
221:
222: $prefix = null;
223: if (isset($db->config['prefix'])) {
224: $prefix = $db->config['prefix'];
225: }
226:
227: if (!is_array($models) && $models !== false) {
228: if (isset($this->plugin)) {
229: $models = App::objects('model', App::pluginPath($this->plugin) . 'models' . DS, false);
230: } else {
231: $models = App::objects('model');
232: }
233: }
234:
235: if (is_array($models)) {
236: foreach ($models as $model) {
237: $importModel = $model;
238: if (isset($this->plugin)) {
239: $importModel = $this->plugin . '.' . $model;
240: }
241: if (!App::import('Model', $importModel)) {
242: continue;
243: }
244: $vars = get_class_vars($model);
245: if (empty($vars['useDbConfig']) || $vars['useDbConfig'] != $connection) {
246: continue;
247: }
248:
249: if (PHP5) {
250: $Object = ClassRegistry::init(array('class' => $model, 'ds' => $connection));
251: } else {
252: $Object =& ClassRegistry::init(array('class' => $model, 'ds' => $connection));
253: }
254:
255: if (is_object($Object) && $Object->useTable !== false) {
256: $fulltable = $table = $db->fullTableName($Object, false);
257: if ($prefix && strpos($table, $prefix) !== 0) {
258: continue;
259: }
260: $table = $this->_noPrefixTable($prefix, $table);
261:
262: if (in_array($fulltable, $currentTables)) {
263: $key = array_search($fulltable, $currentTables);
264: if (empty($tables[$table])) {
265: $tables[$table] = $this->__columns($Object);
266: $tables[$table]['indexes'] = $db->index($Object);
267: $tables[$table]['tableParameters'] = $db->readTableParameters($fulltable);
268: unset($currentTables[$key]);
269: }
270: if (!empty($Object->hasAndBelongsToMany)) {
271: foreach ($Object->hasAndBelongsToMany as $Assoc => $assocData) {
272: if (isset($assocData['with'])) {
273: $class = $assocData['with'];
274: }
275: if (is_object($Object->$class)) {
276: $withTable = $db->fullTableName($Object->$class, false);
277: if ($prefix && strpos($withTable, $prefix) !== 0) {
278: continue;
279: }
280: if (in_array($withTable, $currentTables)) {
281: $key = array_search($withTable, $currentTables);
282: $noPrefixWith = $this->_noPrefixTable($prefix, $withTable);
283:
284: $tables[$noPrefixWith] = $this->__columns($Object->$class);
285: $tables[$noPrefixWith]['indexes'] = $db->index($Object->$class);
286: $tables[$noPrefixWith]['tableParameters'] = $db->readTableParameters($withTable);
287: unset($currentTables[$key]);
288: }
289: }
290: }
291: }
292: }
293: }
294: }
295: }
296:
297: if (!empty($currentTables)) {
298: foreach ($currentTables as $table) {
299: if ($prefix) {
300: if (strpos($table, $prefix) !== 0) {
301: continue;
302: }
303: $table = $this->_noPrefixTable($prefix, $table);
304: }
305: $Object = new AppModel(array(
306: 'name' => Inflector::classify($table), 'table' => $table, 'ds' => $connection
307: ));
308:
309: $systemTables = array(
310: 'aros', 'acos', 'aros_acos', Configure::read('Session.table'), 'i18n'
311: );
312:
313: $fulltable = $db->fullTableName($Object, false);
314:
315: if (in_array($table, $systemTables)) {
316: $tables[$Object->table] = $this->__columns($Object);
317: $tables[$Object->table]['indexes'] = $db->index($Object);
318: $tables[$Object->table]['tableParameters'] = $db->readTableParameters($fulltable);
319: } elseif ($models === false) {
320: $tables[$table] = $this->__columns($Object);
321: $tables[$table]['indexes'] = $db->index($Object);
322: $tables[$table]['tableParameters'] = $db->readTableParameters($fulltable);
323: } else {
324: $tables['missing'][$table] = $this->__columns($Object);
325: $tables['missing'][$table]['indexes'] = $db->index($Object);
326: $tables['missing'][$table]['tableParameters'] = $db->readTableParameters($fulltable);
327: }
328: }
329: }
330:
331: ksort($tables);
332: return compact('name', 'tables');
333: }
334:
335: 336: 337: 338: 339: 340: 341: 342:
343: function write($object, $options = array()) {
344: if (is_object($object)) {
345: $object = get_object_vars($object);
346: $this->_build($object);
347: }
348:
349: if (is_array($object)) {
350: $options = $object;
351: unset($object);
352: }
353:
354: extract(array_merge(
355: get_object_vars($this), $options
356: ));
357:
358: $out = "class {$name}Schema extends CakeSchema {\n";
359:
360: $out .= "\tvar \$name = '{$name}';\n\n";
361:
362: if ($path !== $this->path) {
363: $out .= "\tvar \$path = '{$path}';\n\n";
364: }
365:
366: if ($file !== $this->file) {
367: $out .= "\tvar \$file = '{$file}';\n\n";
368: }
369:
370: if ($connection !== 'default') {
371: $out .= "\tvar \$connection = '{$connection}';\n\n";
372: }
373:
374: $out .= "\tfunction before(\$event = array()) {\n\t\treturn true;\n\t}\n\n\tfunction after(\$event = array()) {\n\t}\n\n";
375:
376: if (empty($tables)) {
377: $this->read();
378: }
379:
380: foreach ($tables as $table => $fields) {
381: if (!is_numeric($table) && $table !== 'missing') {
382: $out .= $this->generateTable($table, $fields);
383: }
384: }
385: $out .= "}\n";
386:
387: $File =& new File($path . DS . $file, true);
388: $header = '$Id';
389: $content = "<?php \n/* {$name} schema generated on: " . date('Y-m-d H:i:s') . " : ". time() . "*/\n{$out}?>";
390: $content = $File->prepare($content);
391: if ($File->write($content)) {
392: return $content;
393: }
394: return false;
395: }
396:
397: 398: 399: 400: 401: 402: 403: 404:
405: function generateTable($table, $fields) {
406: $out = "\tvar \${$table} = array(\n";
407: if (is_array($fields)) {
408: $cols = array();
409: foreach ($fields as $field => $value) {
410: if ($field != 'indexes' && $field != 'tableParameters') {
411: if (is_string($value)) {
412: $type = $value;
413: $value = array('type'=> $type);
414: }
415: $col = "\t\t'{$field}' => array('type' => '" . $value['type'] . "', ";
416: unset($value['type']);
417: $col .= join(', ', $this->__values($value));
418: } elseif ($field == 'indexes') {
419: $col = "\t\t'indexes' => array(";
420: $props = array();
421: foreach ((array)$value as $key => $index) {
422: $props[] = "'{$key}' => array(" . join(', ', $this->__values($index)) . ")";
423: }
424: $col .= join(', ', $props);
425: } elseif ($field == 'tableParameters') {
426:
427: $col = "\t\t'tableParameters' => array(";
428: $props = array();
429: foreach ((array)$value as $key => $param) {
430: $props[] = "'{$key}' => '$param'";
431: }
432: $col .= join(', ', $props);
433: }
434: $col .= ")";
435: $cols[] = $col;
436: }
437: $out .= join(",\n", $cols);
438: }
439: $out .= "\n\t);\n";
440: return $out;
441: }
442:
443: 444: 445: 446: 447: 448: 449: 450:
451: function compare($old, $new = null) {
452: if (empty($new)) {
453: $new =& $this;
454: }
455: if (is_array($new)) {
456: if (isset($new['tables'])) {
457: $new = $new['tables'];
458: }
459: } else {
460: $new = $new->tables;
461: }
462:
463: if (is_array($old)) {
464: if (isset($old['tables'])) {
465: $old = $old['tables'];
466: }
467: } else {
468: $old = $old->tables;
469: }
470: $tables = array();
471: foreach ($new as $table => $fields) {
472: if ($table == 'missing') {
473: continue;
474: }
475: if (!array_key_exists($table, $old)) {
476: $tables[$table]['add'] = $fields;
477: } else {
478: $diff = $this->_arrayDiffAssoc($fields, $old[$table]);
479: if (!empty($diff)) {
480: $tables[$table]['add'] = $diff;
481: }
482: $diff = $this->_arrayDiffAssoc($old[$table], $fields);
483: if (!empty($diff)) {
484: $tables[$table]['drop'] = $diff;
485: }
486: }
487:
488: foreach ($fields as $field => $value) {
489: if (isset($old[$table][$field])) {
490: $diff = $this->_arrayDiffAssoc($value, $old[$table][$field]);
491: if (!empty($diff) && $field !== 'indexes' && $field !== 'tableParameters') {
492: $tables[$table]['change'][$field] = array_merge($old[$table][$field], $diff);
493: }
494: }
495:
496: if (isset($tables[$table]['add'][$field]) && $field !== 'indexes' && $field !== 'tableParameters') {
497: $wrapper = array_keys($fields);
498: if ($column = array_search($field, $wrapper)) {
499: if (isset($wrapper[$column - 1])) {
500: $tables[$table]['add'][$field]['after'] = $wrapper[$column - 1];
501: }
502: }
503: }
504: }
505:
506: if (isset($old[$table]['indexes']) && isset($new[$table]['indexes'])) {
507: $diff = $this->_compareIndexes($new[$table]['indexes'], $old[$table]['indexes']);
508: if ($diff) {
509: if (!isset($tables[$table])) {
510: $tables[$table] = array();
511: }
512: if (isset($diff['drop'])) {
513: $tables[$table]['drop']['indexes'] = $diff['drop'];
514: }
515: if ($diff && isset($diff['add'])) {
516: $tables[$table]['add']['indexes'] = $diff['add'];
517: }
518: }
519: }
520: if (isset($old[$table]['tableParameters']) && isset($new[$table]['tableParameters'])) {
521: $diff = $this->_compareTableParameters($new[$table]['tableParameters'], $old[$table]['tableParameters']);
522: if ($diff) {
523: $tables[$table]['change']['tableParameters'] = $diff;
524: }
525: }
526: }
527: return $tables;
528: }
529:
530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543:
544: function _arrayDiffAssoc($array1, $array2) {
545: $difference = array();
546: foreach ($array1 as $key => $value) {
547: if (!array_key_exists($key, $array2)) {
548: $difference[$key] = $value;
549: continue;
550: }
551: $correspondingValue = $array2[$key];
552: if (is_null($value) !== is_null($correspondingValue)) {
553: $difference[$key] = $value;
554: continue;
555: }
556: if (is_bool($value) !== is_bool($correspondingValue)) {
557: $difference[$key] = $value;
558: continue;
559: }
560: if (is_array($value) && is_array($correspondingValue)) {
561: continue;
562: }
563: $compare = strval($value);
564: $correspondingValue = strval($correspondingValue);
565: if ($compare === $correspondingValue) {
566: continue;
567: }
568: $difference[$key] = $value;
569: }
570: return $difference;
571: }
572:
573: 574: 575: 576: 577: 578: 579:
580: function __values($values) {
581: $vals = array();
582: if (is_array($values)) {
583: foreach ($values as $key => $val) {
584: if (is_array($val)) {
585: $vals[] = "'{$key}' => array('" . implode("', '", $val) . "')";
586: } else if (!is_numeric($key)) {
587: $val = var_export($val, true);
588: $vals[] = "'{$key}' => {$val}";
589: }
590: }
591: }
592: return $vals;
593: }
594:
595: 596: 597: 598: 599: 600: 601:
602: function __columns(&$Obj) {
603: $db =& ConnectionManager::getDataSource($Obj->useDbConfig);
604: $fields = $Obj->schema(true);
605: $columns = $props = array();
606: foreach ($fields as $name => $value) {
607: if ($Obj->primaryKey == $name) {
608: $value['key'] = 'primary';
609: }
610: if (!isset($db->columns[$value['type']])) {
611: trigger_error(sprintf(__('Schema generation error: invalid column type %s does not exist in DBO', true), $value['type']), E_USER_NOTICE);
612: continue;
613: } else {
614: $defaultCol = $db->columns[$value['type']];
615: if (isset($defaultCol['limit']) && $defaultCol['limit'] == $value['length']) {
616: unset($value['length']);
617: } elseif (isset($defaultCol['length']) && $defaultCol['length'] == $value['length']) {
618: unset($value['length']);
619: }
620: unset($value['limit']);
621: }
622:
623: if (isset($value['default']) && ($value['default'] === '' || $value['default'] === false)) {
624: unset($value['default']);
625: }
626: if (empty($value['length'])) {
627: unset($value['length']);
628: }
629: if (empty($value['key'])) {
630: unset($value['key']);
631: }
632: $columns[$name] = $value;
633: }
634:
635: return $columns;
636: }
637:
638: 639: 640: 641: 642: 643: 644:
645: function _compareTableParameters($new, $old) {
646: if (!is_array($new) || !is_array($old)) {
647: return false;
648: }
649: $change = $this->_arrayDiffAssoc($new, $old);
650: return $change;
651: }
652:
653: 654: 655: 656: 657: 658: 659:
660: function _compareIndexes($new, $old) {
661: if (!is_array($new) || !is_array($old)) {
662: return false;
663: }
664:
665: $add = $drop = array();
666:
667: $diff = $this->_arrayDiffAssoc($new, $old);
668: if (!empty($diff)) {
669: $add = $diff;
670: }
671:
672: $diff = $this->_arrayDiffAssoc($old, $new);
673: if (!empty($diff)) {
674: $drop = $diff;
675: }
676:
677: foreach ($new as $name => $value) {
678: if (isset($old[$name])) {
679: $newUnique = isset($value['unique']) ? $value['unique'] : 0;
680: $oldUnique = isset($old[$name]['unique']) ? $old[$name]['unique'] : 0;
681: $newColumn = $value['column'];
682: $oldColumn = $old[$name]['column'];
683:
684: $diff = false;
685:
686: if ($newUnique != $oldUnique) {
687: $diff = true;
688: } elseif (is_array($newColumn) && is_array($oldColumn)) {
689: $diff = ($newColumn !== $oldColumn);
690: } elseif (is_string($newColumn) && is_string($oldColumn)) {
691: $diff = ($newColumn != $oldColumn);
692: } else {
693: $diff = true;
694: }
695: if ($diff) {
696: $drop[$name] = null;
697: $add[$name] = $value;
698: }
699: }
700: }
701: return array_filter(compact('add', 'drop'));
702: }
703:
704: 705: 706: 707: 708: 709: 710:
711: function _noPrefixTable($prefix, $table) {
712: return preg_replace('/^' . preg_quote($prefix) . '/', '', $table);
713: }
714: }
715: