PageRenderTime 56ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/cake/libs/model/schema.php

https://github.com/Sigiwara/candeo
PHP | 493 lines | 324 code | 36 blank | 133 comment | 85 complexity | dbd0df1e593139c016b6bf3c6538cb6b MD5 | raw file
  1. <?php
  2. /* SVN FILE: $Id: schema.php 7690 2008-10-02 04:56:53Z nate $ */
  3. /**
  4. * Schema database management for CakePHP.
  5. *
  6. * PHP versions 4 and 5
  7. *
  8. * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
  9. * Copyright 2005-2008, Cake Software Foundation, Inc.
  10. * 1785 E. Sahara Avenue, Suite 490-204
  11. * Las Vegas, Nevada 89104
  12. *
  13. * Licensed under The MIT License
  14. * Redistributions of files must retain the above copyright notice.
  15. *
  16. * @filesource
  17. * @copyright Copyright 2005-2008, Cake Software Foundation, Inc.
  18. * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
  19. * @package cake
  20. * @subpackage cake.cake.libs.model
  21. * @since CakePHP(tm) v 1.2.0.5550
  22. * @version $Revision: 7690 $
  23. * @modifiedby $LastChangedBy: nate $
  24. * @lastmodified $Date: 2008-10-02 00:56:53 -0400 (Thu, 02 Oct 2008) $
  25. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  26. */
  27. App::import('Model', 'ConnectionManager');
  28. /**
  29. * Base Class for Schema management
  30. *
  31. * @package cake
  32. * @subpackage cake.cake.libs.model
  33. */
  34. class CakeSchema extends Object {
  35. /**
  36. * Name of the App Schema
  37. *
  38. * @var string
  39. * @access public
  40. */
  41. var $name = null;
  42. /**
  43. * Path to write location
  44. *
  45. * @var string
  46. * @access public
  47. */
  48. var $path = null;
  49. /**
  50. * File to write
  51. *
  52. * @var string
  53. * @access public
  54. */
  55. var $file = 'schema.php';
  56. /**
  57. * Connection used for read
  58. *
  59. * @var string
  60. * @access public
  61. */
  62. var $connection = 'default';
  63. /**
  64. * Set of tables
  65. *
  66. * @var array
  67. * @access public
  68. */
  69. var $tables = array();
  70. /**
  71. * Constructor
  72. *
  73. * @param array $options optional load object properties
  74. */
  75. function __construct($options = array()) {
  76. parent::__construct();
  77. if (empty($options['name'])) {
  78. $this->name = preg_replace('/schema$/i', '', get_class($this));
  79. }
  80. if (strtolower($this->name) === 'cake') {
  81. $this->name = Inflector::camelize(Inflector::slug(Configure::read('App.dir')));
  82. }
  83. if (empty($options['path'])) {
  84. $this->path = CONFIGS . 'sql';
  85. }
  86. $options = array_merge(get_object_vars($this), $options);
  87. $this->_build($options);
  88. }
  89. /**
  90. * Builds schema object properties
  91. *
  92. * @param array $data loaded object properties
  93. * @return void
  94. * @access protected
  95. */
  96. function _build($data) {
  97. $file = null;
  98. foreach ($data as $key => $val) {
  99. if (!empty($val)) {
  100. if (!in_array($key, array('name', 'path', 'file', 'connection', 'tables', '_log'))) {
  101. $this->tables[$key] = $val;
  102. unset($this->{$key});
  103. } elseif ($key !== 'tables') {
  104. if ($key === 'name' && $val !== $this->name && !isset($data['file'])) {
  105. $file = Inflector::underscore($val) . '.php';
  106. }
  107. $this->{$key} = $val;
  108. }
  109. }
  110. }
  111. if (file_exists($this->path . DS . $file) && is_file($this->path . DS . $file)) {
  112. $this->file = $file;
  113. }
  114. }
  115. /**
  116. * Before callback to be implemented in subclasses
  117. *
  118. * @param array $events schema object properties
  119. * @return boolean Should process continue
  120. * @access public
  121. */
  122. function before($event = array()) {
  123. return true;
  124. }
  125. /**
  126. * After callback to be implemented in subclasses
  127. *
  128. * @param array $events schema object properties
  129. * @access public
  130. */
  131. function after($event = array()) {
  132. }
  133. /**
  134. * Reads database and creates schema tables
  135. *
  136. * @param array $options schema object properties
  137. * @return array Set of name and tables
  138. * @access public
  139. */
  140. function load($options = array()) {
  141. if (is_string($options)) {
  142. $options = array('path' => $options);
  143. }
  144. $this->_build($options);
  145. extract(get_object_vars($this));
  146. $class = $name .'Schema';
  147. if (!class_exists($class)) {
  148. if (file_exists($path . DS . $file) && is_file($path . DS . $file)) {
  149. require_once($path . DS . $file);
  150. } elseif (file_exists($path . DS . 'schema.php') && is_file($path . DS . 'schema.php')) {
  151. require_once($path . DS . 'schema.php');
  152. }
  153. }
  154. if (class_exists($class)) {
  155. $Schema =& new $class($options);
  156. return $Schema;
  157. }
  158. return false;
  159. }
  160. /**
  161. * Reads database and creates schema tables
  162. *
  163. * @param array $options schema object properties
  164. * 'connection' - the db connection to use
  165. * 'name' - name of the schema
  166. * 'models' - a list of models to use, or false to ignore models
  167. * @return array Array indexed by name and tables
  168. * @access public
  169. */
  170. function read($options = array()) {
  171. extract(array_merge(
  172. array(
  173. 'connection' => $this->connection,
  174. 'name' => $this->name,
  175. 'models' => true,
  176. ),
  177. $options
  178. ));
  179. $db =& ConnectionManager::getDataSource($connection);
  180. App::import('Model', 'AppModel');
  181. $tables = array();
  182. $currentTables = $db->listSources();
  183. $prefix = null;
  184. if (isset($db->config['prefix'])) {
  185. $prefix = $db->config['prefix'];
  186. }
  187. if (!is_array($models) && $models !== false) {
  188. $models = Configure::listObjects('model');
  189. }
  190. if (is_array($models)) {
  191. foreach ($models as $model) {
  192. if (PHP5) {
  193. $Object = ClassRegistry::init(array('class' => $model, 'ds' => $connection));
  194. } else {
  195. $Object =& ClassRegistry::init(array('class' => $model, 'ds' => $connection));
  196. }
  197. if (is_object($Object) && $Object->useTable !== false) {
  198. $Object->setDataSource($connection);
  199. $table = $db->fullTableName($Object, false);
  200. if (in_array($table, $currentTables)) {
  201. $key = array_search($table, $currentTables);
  202. if (empty($tables[$Object->table])) {
  203. $tables[$Object->table] = $this->__columns($Object);
  204. $tables[$Object->table]['indexes'] = $db->index($Object);
  205. unset($currentTables[$key]);
  206. }
  207. if (!empty($Object->hasAndBelongsToMany)) {
  208. foreach($Object->hasAndBelongsToMany as $Assoc => $assocData) {
  209. if (isset($assocData['with'])) {
  210. $class = $assocData['with'];
  211. } elseif ($assocData['_with']) {
  212. $class = $assocData['_with'];
  213. }
  214. if (is_object($Object->$class)) {
  215. $table = $db->fullTableName($Object->$class, false);
  216. if (in_array($table, $currentTables)) {
  217. $key = array_search($table, $currentTables);
  218. $tables[$Object->$class->table] = $this->__columns($Object->$class);
  219. $tables[$Object->$class->table]['indexes'] = $db->index($Object->$class);
  220. unset($currentTables[$key]);
  221. }
  222. }
  223. }
  224. }
  225. }
  226. }
  227. }
  228. }
  229. if (!empty($currentTables)) {
  230. foreach($currentTables as $table) {
  231. if ($prefix) {
  232. if (strpos($table, $prefix) !== 0) {
  233. continue;
  234. }
  235. $table = str_replace($prefix, '', $table);
  236. }
  237. $Object = new AppModel(array(
  238. 'name' => Inflector::classify($table), 'table' => $table, 'ds' => $connection
  239. ));
  240. $systemTables = array(
  241. 'aros', 'acos', 'aros_acos', Configure::read('Session.table'), 'i18n'
  242. );
  243. if (in_array($table, $systemTables)) {
  244. $tables[$Object->table] = $this->__columns($Object);
  245. $tables[$Object->table]['indexes'] = $db->index($Object);
  246. } elseif ($models === false) {
  247. $tables[$table] = $this->__columns($Object);
  248. $tables[$table]['indexes'] = $db->index($Object);
  249. } else {
  250. $tables['missing'][$table] = $this->__columns($Object);
  251. $tables['missing'][$table]['indexes'] = $db->index($Object);
  252. }
  253. }
  254. }
  255. ksort($tables);
  256. return compact('name', 'tables');
  257. }
  258. /**
  259. * Writes schema file from object or options
  260. *
  261. * @param mixed $object schema object or options array
  262. * @param array $options schema object properties to override object
  263. * @return mixed false or string written to file
  264. * @access public
  265. */
  266. function write($object, $options = array()) {
  267. if (is_object($object)) {
  268. $object = get_object_vars($object);
  269. $this->_build($object);
  270. }
  271. if (is_array($object)) {
  272. $options = $object;
  273. unset($object);
  274. }
  275. extract(array_merge(
  276. get_object_vars($this), $options
  277. ));
  278. $out = "class {$name}Schema extends CakeSchema {\n";
  279. $out .= "\tvar \$name = '{$name}';\n\n";
  280. if ($path !== $this->path) {
  281. $out .= "\tvar \$path = '{$path}';\n\n";
  282. }
  283. if ($file !== $this->file) {
  284. $out .= "\tvar \$file = '{$file}';\n\n";
  285. }
  286. if ($connection !== 'default') {
  287. $out .= "\tvar \$connection = '{$connection}';\n\n";
  288. }
  289. $out .= "\tfunction before(\$event = array()) {\n\t\treturn true;\n\t}\n\n\tfunction after(\$event = array()) {\n\t}\n\n";
  290. if (empty($tables)) {
  291. $this->read();
  292. }
  293. foreach ($tables as $table => $fields) {
  294. if (!is_numeric($table) && $table !== 'missing') {
  295. $out .= "\tvar \${$table} = array(\n";
  296. if (is_array($fields)) {
  297. $cols = array();
  298. foreach ($fields as $field => $value) {
  299. if ($field != 'indexes') {
  300. if (is_string($value)) {
  301. $type = $value;
  302. $value = array('type'=> $type);
  303. }
  304. $col = "\t\t\t'{$field}' => array('type' => '" . $value['type'] . "', ";
  305. unset($value['type']);
  306. $col .= join(', ', $this->__values($value));
  307. } else {
  308. $col = "\t\t\t'indexes' => array(";
  309. $props = array();
  310. foreach ((array)$value as $key => $index) {
  311. $props[] = "'{$key}' => array(".join(', ', $this->__values($index)).")";
  312. }
  313. $col .= join(', ', $props);
  314. }
  315. $col .= ")";
  316. $cols[] = $col;
  317. }
  318. $out .= join(",\n", $cols);
  319. }
  320. $out .= "\n\t\t);\n";
  321. }
  322. }
  323. $out .="}\n";
  324. $File =& new File($path . DS . $file, true);
  325. $header = '$Id';
  326. $content = "<?php \n/* SVN FILE: $header$ */\n/* ". $name ." schema generated on: " . date('Y-m-d H:m:s') . " : ". time() . "*/\n{$out}?>";
  327. $content = $File->prepare($content);
  328. if ($File->write($content)) {
  329. return $content;
  330. }
  331. return false;
  332. }
  333. /**
  334. * Compares two sets of schemas
  335. *
  336. * @param mixed $old Schema object or array
  337. * @param mixed $new Schema object or array
  338. * @return array Tables (that are added, dropped, or changed)
  339. * @access public
  340. */
  341. function compare($old, $new = null) {
  342. if (empty($new)) {
  343. $new = $this;
  344. }
  345. if (is_array($new)) {
  346. if (isset($new['tables'])) {
  347. $new = $new['tables'];
  348. }
  349. } else {
  350. $new = $new->tables;
  351. }
  352. if (is_array($old)) {
  353. if (isset($old['tables'])) {
  354. $old = $old['tables'];
  355. }
  356. } else {
  357. $old = $old->tables;
  358. }
  359. $tables = array();
  360. foreach ($new as $table => $fields) {
  361. if ($table == 'missing') {
  362. break;
  363. }
  364. if (!array_key_exists($table, $old)) {
  365. $tables[$table]['add'] = $fields;
  366. } else {
  367. $diff = array_diff_assoc($fields, $old[$table]);
  368. if (!empty($diff)) {
  369. $tables[$table]['add'] = $diff;
  370. }
  371. $diff = array_diff_assoc($old[$table], $fields);
  372. if (!empty($diff)) {
  373. $tables[$table]['drop'] = $diff;
  374. }
  375. }
  376. foreach ($fields as $field => $value) {
  377. if (isset($old[$table][$field])) {
  378. $diff = array_diff_assoc($value, $old[$table][$field]);
  379. if (!empty($diff)) {
  380. $tables[$table]['change'][$field] = array_merge($old[$table][$field], $diff);
  381. }
  382. }
  383. if (isset($add[$table][$field])) {
  384. $wrapper = array_keys($fields);
  385. if ($column = array_search($field, $wrapper)) {
  386. if (isset($wrapper[$column - 1])) {
  387. $tables[$table]['add'][$field]['after'] = $wrapper[$column - 1];
  388. }
  389. }
  390. }
  391. }
  392. }
  393. return $tables;
  394. }
  395. /**
  396. * Formats Schema columns from Model Object
  397. *
  398. * @param array $values options keys(type, null, default, key, length, extra)
  399. * @return array Formatted values
  400. * @access public
  401. */
  402. function __values($values) {
  403. $vals = array();
  404. if (is_array($values)) {
  405. foreach ($values as $key => $val) {
  406. if (is_array($val)) {
  407. $vals[] = "'{$key}' => array('".join("', '", $val)."')";
  408. } else if (!is_numeric($key)) {
  409. $val = var_export($val, true);
  410. $vals[] = "'{$key}' => {$val}";
  411. }
  412. }
  413. }
  414. return $vals;
  415. }
  416. /**
  417. * Formats Schema columns from Model Object
  418. *
  419. * @param array $Obj model object
  420. * @return array Formatted columns
  421. * @access public
  422. */
  423. function __columns(&$Obj) {
  424. $db =& ConnectionManager::getDataSource($Obj->useDbConfig);
  425. $fields = $Obj->schema(true);
  426. $columns = $props = array();
  427. foreach ($fields as $name => $value) {
  428. if ($Obj->primaryKey == $name) {
  429. $value['key'] = 'primary';
  430. }
  431. if (!isset($db->columns[$value['type']])) {
  432. trigger_error('Schema generation error: invalid column type ' . $value['type'] . ' does not exist in DBO', E_USER_NOTICE);
  433. continue;
  434. } else {
  435. $defaultCol = $db->columns[$value['type']];
  436. if (isset($defaultCol['limit']) && $defaultCol['limit'] == $value['length']) {
  437. unset($value['length']);
  438. } elseif (isset($defaultCol['length']) && $defaultCol['length'] == $value['length']) {
  439. unset($value['length']);
  440. }
  441. unset($value['limit']);
  442. }
  443. if (isset($value['default']) && ($value['default'] === '' || $value['default'] === false)) {
  444. unset($value['default']);
  445. }
  446. if (empty($value['length'])) {
  447. unset($value['length']);
  448. }
  449. if (empty($value['key'])) {
  450. unset($value['key']);
  451. }
  452. $columns[$name] = $value;
  453. }
  454. return $columns;
  455. }
  456. }
  457. ?>