1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
24: App::import('Model', 'ConnectionManager');
25: 26: 27: 28: 29: 30:
31: class CakeSchema extends Object {
32: 33: 34: 35: 36: 37:
38: var $name = null;
39: 40: 41: 42: 43: 44:
45: var $path = null;
46: 47: 48: 49: 50: 51:
52: var $file = 'schema.php';
53: 54: 55: 56: 57: 58:
59: var $connection = 'default';
60: 61: 62: 63: 64: 65:
66: var $tables = array();
67: 68: 69: 70: 71:
72: function __construct($options = array()) {
73: parent::__construct();
74:
75: if (empty($options['name'])) {
76: $this->name = preg_replace('/schema$/i', '', get_class($this));
77: }
78:
79: if (strtolower($this->name) === 'cake') {
80: $this->name = Inflector::camelize(Inflector::slug(Configure::read('App.dir')));
81: }
82:
83: if (empty($options['path'])) {
84: $this->path = CONFIGS . 'sql';
85: }
86:
87: $options = array_merge(get_object_vars($this), $options);
88: $this->_build($options);
89: }
90: 91: 92: 93: 94: 95: 96:
97: function _build($data) {
98: $file = null;
99: foreach ($data as $key => $val) {
100: if (!empty($val)) {
101: if (!in_array($key, array('name', 'path', 'file', 'connection', 'tables', '_log'))) {
102: $this->tables[$key] = $val;
103: unset($this->{$key});
104: } elseif ($key !== 'tables') {
105: if ($key === 'name' && $val !== $this->name && !isset($data['file'])) {
106: $file = Inflector::underscore($val) . '.php';
107: }
108: $this->{$key} = $val;
109: }
110: }
111: }
112:
113: if (file_exists($this->path . DS . $file) && is_file($this->path . DS . $file)) {
114: $this->file = $file;
115: }
116: }
117: 118: 119: 120: 121: 122: 123:
124: function before($event = array()) {
125: return true;
126: }
127: 128: 129: 130: 131: 132:
133: function after($event = array()) {
134: }
135: 136: 137: 138: 139: 140: 141:
142: function load($options = array()) {
143: if (is_string($options)) {
144: $options = array('path' => $options);
145: }
146:
147: $this->_build($options);
148: extract(get_object_vars($this));
149:
150: $class = $name .'Schema';
151: if (!class_exists($class)) {
152: if (file_exists($path . DS . $file) && is_file($path . DS . $file)) {
153: require_once($path . DS . $file);
154: } elseif (file_exists($path . DS . 'schema.php') && is_file($path . DS . 'schema.php')) {
155: require_once($path . DS . 'schema.php');
156: }
157: }
158:
159: if (class_exists($class)) {
160: $Schema =& new $class($options);
161: return $Schema;
162: }
163:
164: return false;
165: }
166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178:
179: function read($options = array()) {
180: extract(array_merge(
181: array(
182: 'connection' => $this->connection,
183: 'name' => $this->name,
184: 'models' => true,
185: ),
186: $options
187: ));
188: $db =& ConnectionManager::getDataSource($connection);
189:
190: App::import('Model', 'AppModel');
191:
192: $tables = array();
193: $currentTables = $db->listSources();
194:
195: $prefix = null;
196: if (isset($db->config['prefix'])) {
197: $prefix = $db->config['prefix'];
198: }
199:
200: if (!is_array($models) && $models !== false) {
201: $models = Configure::listObjects('model');
202: }
203:
204: if (is_array($models)) {
205: foreach ($models as $model) {
206: if (PHP5) {
207: $Object = ClassRegistry::init(array('class' => $model, 'ds' => $connection));
208: } else {
209: $Object =& ClassRegistry::init(array('class' => $model, 'ds' => $connection));
210: }
211:
212: if (is_object($Object) && $Object->useTable !== false) {
213: $Object->setDataSource($connection);
214: $table = $db->fullTableName($Object, false);
215:
216: if (in_array($table, $currentTables)) {
217: $key = array_search($table, $currentTables);
218: if (empty($tables[$Object->table])) {
219: $tables[$Object->table] = $this->__columns($Object);
220: $tables[$Object->table]['indexes'] = $db->index($Object);
221: unset($currentTables[$key]);
222: }
223: if (!empty($Object->hasAndBelongsToMany)) {
224: foreach ($Object->hasAndBelongsToMany as $Assoc => $assocData) {
225: if (isset($assocData['with'])) {
226: $class = $assocData['with'];
227: } elseif ($assocData['_with']) {
228: $class = $assocData['_with'];
229: }
230: if (is_object($Object->$class)) {
231: $table = $db->fullTableName($Object->$class, false);
232: if (in_array($table, $currentTables)) {
233: $key = array_search($table, $currentTables);
234: $tables[$Object->$class->table] = $this->__columns($Object->$class);
235: $tables[$Object->$class->table]['indexes'] = $db->index($Object->$class);
236: unset($currentTables[$key]);
237: }
238: }
239: }
240: }
241: }
242: }
243: }
244: }
245:
246: if (!empty($currentTables)) {
247: foreach ($currentTables as $table) {
248: if ($prefix) {
249: if (strpos($table, $prefix) !== 0) {
250: continue;
251: }
252: $table = str_replace($prefix, '', $table);
253: }
254: $Object = new AppModel(array(
255: 'name' => Inflector::classify($table), 'table' => $table, 'ds' => $connection
256: ));
257:
258: $systemTables = array(
259: 'aros', 'acos', 'aros_acos', Configure::read('Session.table'), 'i18n'
260: );
261:
262: if (in_array($table, $systemTables)) {
263: $tables[$Object->table] = $this->__columns($Object);
264: $tables[$Object->table]['indexes'] = $db->index($Object);
265: } elseif ($models === false) {
266: $tables[$table] = $this->__columns($Object);
267: $tables[$table]['indexes'] = $db->index($Object);
268: } else {
269: $tables['missing'][$table] = $this->__columns($Object);
270: $tables['missing'][$table]['indexes'] = $db->index($Object);
271: }
272: }
273: }
274:
275: ksort($tables);
276: return compact('name', 'tables');
277: }
278: 279: 280: 281: 282: 283: 284: 285:
286: function write($object, $options = array()) {
287: if (is_object($object)) {
288: $object = get_object_vars($object);
289: $this->_build($object);
290: }
291:
292: if (is_array($object)) {
293: $options = $object;
294: unset($object);
295: }
296:
297: extract(array_merge(
298: get_object_vars($this), $options
299: ));
300:
301: $out = "class {$name}Schema extends CakeSchema {\n";
302:
303: $out .= "\tvar \$name = '{$name}';\n\n";
304:
305: if ($path !== $this->path) {
306: $out .= "\tvar \$path = '{$path}';\n\n";
307: }
308:
309: if ($file !== $this->file) {
310: $out .= "\tvar \$file = '{$file}';\n\n";
311: }
312:
313: if ($connection !== 'default') {
314: $out .= "\tvar \$connection = '{$connection}';\n\n";
315: }
316:
317: $out .= "\tfunction before(\$event = array()) {\n\t\treturn true;\n\t}\n\n\tfunction after(\$event = array()) {\n\t}\n\n";
318:
319: if (empty($tables)) {
320: $this->read();
321: }
322:
323: foreach ($tables as $table => $fields) {
324: if (!is_numeric($table) && $table !== 'missing') {
325: $out .= "\tvar \${$table} = array(\n";
326: if (is_array($fields)) {
327: $cols = array();
328: foreach ($fields as $field => $value) {
329: if ($field != 'indexes') {
330: if (is_string($value)) {
331: $type = $value;
332: $value = array('type'=> $type);
333: }
334: $col = "\t\t'{$field}' => array('type' => '" . $value['type'] . "', ";
335: unset($value['type']);
336: $col .= implode(', ', $this->__values($value));
337: } else {
338: $col = "\t\t'indexes' => array(";
339: $props = array();
340: foreach ((array)$value as $key => $index) {
341: $props[] = "'{$key}' => array(" . implode(', ', $this->__values($index)) . ")";
342: }
343: $col .= implode(', ', $props);
344: }
345: $col .= ")";
346: $cols[] = $col;
347: }
348: $out .= implode(",\n", $cols);
349: }
350: $out .= "\n\t);\n";
351: }
352: }
353: $out .="}\n";
354:
355:
356: $File =& new File($path . DS . $file, true);
357: $header = '$Id';
358: $content = "<?php \n/* SVN FILE: {$header}$ */\n/* {$name} schema generated on: " . date('Y-m-d H:m:s') . " : ". time() . "*/\n{$out}?>";
359: $content = $File->prepare($content);
360: if ($File->write($content)) {
361: return $content;
362: }
363: return false;
364: }
365: 366: 367: 368: 369: 370: 371: 372:
373: function compare($old, $new = null) {
374: if (empty($new)) {
375: $new =& $this;
376: }
377: if (is_array($new)) {
378: if (isset($new['tables'])) {
379: $new = $new['tables'];
380: }
381: } else {
382: $new = $new->tables;
383: }
384:
385: if (is_array($old)) {
386: if (isset($old['tables'])) {
387: $old = $old['tables'];
388: }
389: } else {
390: $old = $old->tables;
391: }
392: $tables = array();
393: foreach ($new as $table => $fields) {
394: if ($table == 'missing') {
395: continue;
396: }
397: if (!array_key_exists($table, $old)) {
398: $tables[$table]['add'] = $fields;
399: } else {
400: $diff = array_diff_assoc($fields, $old[$table]);
401: if (!empty($diff)) {
402: $tables[$table]['add'] = $diff;
403: }
404: $diff = array_diff_assoc($old[$table], $fields);
405: if (!empty($diff)) {
406: $tables[$table]['drop'] = $diff;
407: }
408: }
409:
410: foreach ($fields as $field => $value) {
411: if (isset($old[$table][$field])) {
412: $diff = array_diff_assoc($value, $old[$table][$field]);
413: if (!empty($diff) && $field !== 'indexes') {
414: $tables[$table]['change'][$field] = array_merge($old[$table][$field], $diff);
415: }
416: }
417:
418: if (isset($add[$table][$field])) {
419: $wrapper = array_keys($fields);
420: if ($column = array_search($field, $wrapper)) {
421: if (isset($wrapper[$column - 1])) {
422: $tables[$table]['add'][$field]['after'] = $wrapper[$column - 1];
423: }
424: }
425: }
426: }
427:
428: if (isset($old[$table]['indexes']) && isset($new[$table]['indexes'])) {
429: $diff = $this->_compareIndexes($new[$table]['indexes'], $old[$table]['indexes']);
430: if ($diff) {
431: if (!isset($tables[$table])) {
432: $tables[$table] = array();
433: }
434: if (isset($diff['drop'])) {
435: $tables[$table]['drop']['indexes'] = $diff['drop'];
436: }
437: if ($diff && isset($diff['add'])) {
438: $tables[$table]['add']['indexes'] = $diff['add'];
439: }
440: }
441: }
442: }
443: return $tables;
444: }
445: 446: 447: 448: 449: 450: 451:
452: function __values($values) {
453: $vals = array();
454: if (is_array($values)) {
455: foreach ($values as $key => $val) {
456: if (is_array($val)) {
457: $vals[] = "'{$key}' => array('" . implode("', '", $val) . "')";
458: } else if (!is_numeric($key)) {
459: $val = var_export($val, true);
460: $vals[] = "'{$key}' => {$val}";
461: }
462: }
463: }
464: return $vals;
465: }
466: 467: 468: 469: 470: 471: 472:
473: function __columns(&$Obj) {
474: $db =& ConnectionManager::getDataSource($Obj->useDbConfig);
475: $fields = $Obj->schema(true);
476: $columns = $props = array();
477: foreach ($fields as $name => $value) {
478: if ($Obj->primaryKey == $name) {
479: $value['key'] = 'primary';
480: }
481: if (!isset($db->columns[$value['type']])) {
482: trigger_error('Schema generation error: invalid column type ' . $value['type'] . ' does not exist in DBO', E_USER_NOTICE);
483: continue;
484: } else {
485: $defaultCol = $db->columns[$value['type']];
486: if (isset($defaultCol['limit']) && $defaultCol['limit'] == $value['length']) {
487: unset($value['length']);
488: } elseif (isset($defaultCol['length']) && $defaultCol['length'] == $value['length']) {
489: unset($value['length']);
490: }
491: unset($value['limit']);
492: }
493:
494: if (isset($value['default']) && ($value['default'] === '' || $value['default'] === false)) {
495: unset($value['default']);
496: }
497: if (empty($value['length'])) {
498: unset($value['length']);
499: }
500: if (empty($value['key'])) {
501: unset($value['key']);
502: }
503: $columns[$name] = $value;
504: }
505:
506: return $columns;
507: }
508: 509: 510: 511: 512: 513: 514:
515: function _compareIndexes($new, $old) {
516: if (!is_array($new) || !is_array($old)) {
517: return false;
518: }
519:
520: $add = $drop = array();
521:
522: $diff = array_diff_assoc($new, $old);
523: if (!empty($diff)) {
524: $add = $diff;
525: }
526:
527: $diff = array_diff_assoc($old, $new);
528: if (!empty($diff)) {
529: $drop = $diff;
530: }
531:
532: foreach ($new as $name => $value) {
533: if (isset($old[$name])) {
534: $newUnique = isset($value['unique']) ? $value['unique'] : 0;
535: $oldUnique = isset($old[$name]['unique']) ? $old[$name]['unique'] : 0;
536: $newColumn = $value['column'];
537: $oldColumn = $old[$name]['column'];
538:
539: $diff = false;
540:
541: if ($newUnique != $oldUnique) {
542: $diff = true;
543: } elseif (is_array($newColumn) && is_array($oldColumn)) {
544: $diff = ($newColumn !== $oldColumn);
545: } elseif (is_string($newColumn) && is_string($oldColumn)) {
546: $diff = ($newColumn != $oldColumn);
547: } else {
548: $diff = true;
549: }
550: if ($diff) {
551: $drop[$name] = null;
552: $add[$name] = $value;
553: }
554: }
555: }
556: return array_filter(compact('add', 'drop'));
557: }
558: }
559: ?>