PageRenderTime 61ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/code/ryzom/tools/server/www/webtt/cake/libs/model/cake_schema.php

https://bitbucket.org/mattraykowski/ryzomcore_demoshard
PHP | 698 lines | 462 code | 65 blank | 171 comment | 133 complexity | d609942ace5d72008a22593d9dc7236e MD5 | raw file
Possible License(s): AGPL-3.0, GPL-3.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Schema database management for CakePHP.
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package cake
  16. * @subpackage cake.cake.libs.model
  17. * @since CakePHP(tm) v 1.2.0.5550
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. */
  20. App::import('Core', array('Model', 'ConnectionManager'));
  21. /**
  22. * Base Class for Schema management
  23. *
  24. * @package cake
  25. * @subpackage cake.cake.libs.model
  26. */
  27. class CakeSchema extends Object {
  28. /**
  29. * Name of the App Schema
  30. *
  31. * @var string
  32. * @access public
  33. */
  34. var $name = null;
  35. /**
  36. * Path to write location
  37. *
  38. * @var string
  39. * @access public
  40. */
  41. var $path = null;
  42. /**
  43. * File to write
  44. *
  45. * @var string
  46. * @access public
  47. */
  48. var $file = 'schema.php';
  49. /**
  50. * Connection used for read
  51. *
  52. * @var string
  53. * @access public
  54. */
  55. var $connection = 'default';
  56. /**
  57. * plugin name.
  58. *
  59. * @var string
  60. */
  61. var $plugin = null;
  62. /**
  63. * Set of tables
  64. *
  65. * @var array
  66. * @access public
  67. */
  68. var $tables = array();
  69. /**
  70. * Constructor
  71. *
  72. * @param array $options optional load object properties
  73. */
  74. function __construct($options = array()) {
  75. parent::__construct();
  76. if (empty($options['name'])) {
  77. $this->name = preg_replace('/schema$/i', '', get_class($this));
  78. }
  79. if (!empty($options['plugin'])) {
  80. $this->plugin = $options['plugin'];
  81. }
  82. if (strtolower($this->name) === 'cake') {
  83. $this->name = Inflector::camelize(Inflector::slug(Configure::read('App.dir')));
  84. }
  85. if (empty($options['path'])) {
  86. if (is_dir(CONFIGS . 'schema')) {
  87. $this->path = CONFIGS . 'schema';
  88. } else {
  89. $this->path = CONFIGS . 'sql';
  90. }
  91. }
  92. $options = array_merge(get_object_vars($this), $options);
  93. $this->_build($options);
  94. }
  95. /**
  96. * Builds schema object properties
  97. *
  98. * @param array $data loaded object properties
  99. * @return void
  100. * @access protected
  101. */
  102. function _build($data) {
  103. $file = null;
  104. foreach ($data as $key => $val) {
  105. if (!empty($val)) {
  106. if (!in_array($key, array('plugin', 'name', 'path', 'file', 'connection', 'tables', '_log'))) {
  107. $this->tables[$key] = $val;
  108. unset($this->{$key});
  109. } elseif ($key !== 'tables') {
  110. if ($key === 'name' && $val !== $this->name && !isset($data['file'])) {
  111. $file = Inflector::underscore($val) . '.php';
  112. }
  113. $this->{$key} = $val;
  114. }
  115. }
  116. }
  117. if (file_exists($this->path . DS . $file) && is_file($this->path . DS . $file)) {
  118. $this->file = $file;
  119. } elseif (!empty($this->plugin)) {
  120. $this->path = App::pluginPath($this->plugin) . 'config' . DS . 'schema';
  121. }
  122. }
  123. /**
  124. * Before callback to be implemented in subclasses
  125. *
  126. * @param array $events schema object properties
  127. * @return boolean Should process continue
  128. * @access public
  129. */
  130. function before($event = array()) {
  131. return true;
  132. }
  133. /**
  134. * After callback to be implemented in subclasses
  135. *
  136. * @param array $events schema object properties
  137. * @access public
  138. */
  139. function after($event = array()) {
  140. }
  141. /**
  142. * Reads database and creates schema tables
  143. *
  144. * @param array $options schema object properties
  145. * @return array Set of name and tables
  146. * @access public
  147. */
  148. function &load($options = array()) {
  149. if (is_string($options)) {
  150. $options = array('path' => $options);
  151. }
  152. $this->_build($options);
  153. extract(get_object_vars($this));
  154. $class = $name .'Schema';
  155. if (!class_exists($class)) {
  156. if (file_exists($path . DS . $file) && is_file($path . DS . $file)) {
  157. require_once($path . DS . $file);
  158. } elseif (file_exists($path . DS . 'schema.php') && is_file($path . DS . 'schema.php')) {
  159. require_once($path . DS . 'schema.php');
  160. }
  161. }
  162. if (class_exists($class)) {
  163. $Schema =& new $class($options);
  164. return $Schema;
  165. }
  166. $false = false;
  167. return $false;
  168. }
  169. /**
  170. * Reads database and creates schema tables
  171. *
  172. * Options
  173. *
  174. * - 'connection' - the db connection to use
  175. * - 'name' - name of the schema
  176. * - 'models' - a list of models to use, or false to ignore models
  177. *
  178. * @param array $options schema object properties
  179. * @return array Array indexed by name and tables
  180. * @access public
  181. */
  182. function read($options = array()) {
  183. extract(array_merge(
  184. array(
  185. 'connection' => $this->connection,
  186. 'name' => $this->name,
  187. 'models' => true,
  188. ),
  189. $options
  190. ));
  191. $db =& ConnectionManager::getDataSource($connection);
  192. App::import('Model', 'AppModel');
  193. if (isset($this->plugin)) {
  194. App::import('Model', Inflector::camelize($this->plugin) . 'AppModel');
  195. }
  196. $tables = array();
  197. $currentTables = $db->listSources();
  198. $prefix = null;
  199. if (isset($db->config['prefix'])) {
  200. $prefix = $db->config['prefix'];
  201. }
  202. if (!is_array($models) && $models !== false) {
  203. if (isset($this->plugin)) {
  204. $models = App::objects('model', App::pluginPath($this->plugin) . 'models' . DS, false);
  205. } else {
  206. $models = App::objects('model');
  207. }
  208. }
  209. if (is_array($models)) {
  210. foreach ($models as $model) {
  211. $importModel = $model;
  212. if (isset($this->plugin)) {
  213. $importModel = $this->plugin . '.' . $model;
  214. }
  215. if (!App::import('Model', $importModel)) {
  216. continue;
  217. }
  218. $vars = get_class_vars($model);
  219. if (empty($vars['useDbConfig']) || $vars['useDbConfig'] != $connection) {
  220. continue;
  221. }
  222. if (PHP5) {
  223. $Object = ClassRegistry::init(array('class' => $model, 'ds' => $connection));
  224. } else {
  225. $Object =& ClassRegistry::init(array('class' => $model, 'ds' => $connection));
  226. }
  227. if (is_object($Object) && $Object->useTable !== false) {
  228. $fulltable = $table = $db->fullTableName($Object, false);
  229. if ($prefix && strpos($table, $prefix) !== 0) {
  230. continue;
  231. }
  232. $table = str_replace($prefix, '', $table);
  233. if (in_array($fulltable, $currentTables)) {
  234. $key = array_search($fulltable, $currentTables);
  235. if (empty($tables[$table])) {
  236. $tables[$table] = $this->__columns($Object);
  237. $tables[$table]['indexes'] = $db->index($Object);
  238. $tables[$table]['tableParameters'] = $db->readTableParameters($fulltable);
  239. unset($currentTables[$key]);
  240. }
  241. if (!empty($Object->hasAndBelongsToMany)) {
  242. foreach ($Object->hasAndBelongsToMany as $Assoc => $assocData) {
  243. if (isset($assocData['with'])) {
  244. $class = $assocData['with'];
  245. }
  246. if (is_object($Object->$class)) {
  247. $withTable = $db->fullTableName($Object->$class, false);
  248. if ($prefix && strpos($withTable, $prefix) !== 0) {
  249. continue;
  250. }
  251. if (in_array($withTable, $currentTables)) {
  252. $key = array_search($withTable, $currentTables);
  253. $noPrefixWith = str_replace($prefix, '', $withTable);
  254. $tables[$noPrefixWith] = $this->__columns($Object->$class);
  255. $tables[$noPrefixWith]['indexes'] = $db->index($Object->$class);
  256. $tables[$noPrefixWith]['tableParameters'] = $db->readTableParameters($withTable);
  257. unset($currentTables[$key]);
  258. }
  259. }
  260. }
  261. }
  262. }
  263. }
  264. }
  265. }
  266. if (!empty($currentTables)) {
  267. foreach ($currentTables as $table) {
  268. if ($prefix) {
  269. if (strpos($table, $prefix) !== 0) {
  270. continue;
  271. }
  272. $table = str_replace($prefix, '', $table);
  273. }
  274. $Object = new AppModel(array(
  275. 'name' => Inflector::classify($table), 'table' => $table, 'ds' => $connection
  276. ));
  277. $systemTables = array(
  278. 'aros', 'acos', 'aros_acos', Configure::read('Session.table'), 'i18n'
  279. );
  280. if (in_array($table, $systemTables)) {
  281. $tables[$Object->table] = $this->__columns($Object);
  282. $tables[$Object->table]['indexes'] = $db->index($Object);
  283. $tables[$Object->table]['tableParameters'] = $db->readTableParameters($table);
  284. } elseif ($models === false) {
  285. $tables[$table] = $this->__columns($Object);
  286. $tables[$table]['indexes'] = $db->index($Object);
  287. $tables[$table]['tableParameters'] = $db->readTableParameters($table);
  288. } else {
  289. $tables['missing'][$table] = $this->__columns($Object);
  290. $tables['missing'][$table]['indexes'] = $db->index($Object);
  291. $tables['missing'][$table]['tableParameters'] = $db->readTableParameters($table);
  292. }
  293. }
  294. }
  295. ksort($tables);
  296. return compact('name', 'tables');
  297. }
  298. /**
  299. * Writes schema file from object or options
  300. *
  301. * @param mixed $object schema object or options array
  302. * @param array $options schema object properties to override object
  303. * @return mixed false or string written to file
  304. * @access public
  305. */
  306. function write($object, $options = array()) {
  307. if (is_object($object)) {
  308. $object = get_object_vars($object);
  309. $this->_build($object);
  310. }
  311. if (is_array($object)) {
  312. $options = $object;
  313. unset($object);
  314. }
  315. extract(array_merge(
  316. get_object_vars($this), $options
  317. ));
  318. $out = "class {$name}Schema extends CakeSchema {\n";
  319. $out .= "\tvar \$name = '{$name}';\n\n";
  320. if ($path !== $this->path) {
  321. $out .= "\tvar \$path = '{$path}';\n\n";
  322. }
  323. if ($file !== $this->file) {
  324. $out .= "\tvar \$file = '{$file}';\n\n";
  325. }
  326. if ($connection !== 'default') {
  327. $out .= "\tvar \$connection = '{$connection}';\n\n";
  328. }
  329. $out .= "\tfunction before(\$event = array()) {\n\t\treturn true;\n\t}\n\n\tfunction after(\$event = array()) {\n\t}\n\n";
  330. if (empty($tables)) {
  331. $this->read();
  332. }
  333. foreach ($tables as $table => $fields) {
  334. if (!is_numeric($table) && $table !== 'missing') {
  335. $out .= $this->generateTable($table, $fields);
  336. }
  337. }
  338. $out .= "}\n";
  339. $File =& new File($path . DS . $file, true);
  340. $header = '$Id';
  341. $content = "<?php \n/* {$name} schema generated on: " . date('Y-m-d H:i:s') . " : ". time() . "*/\n{$out}?>";
  342. $content = $File->prepare($content);
  343. if ($File->write($content)) {
  344. return $content;
  345. }
  346. return false;
  347. }
  348. /**
  349. * Generate the code for a table. Takes a table name and $fields array
  350. * Returns a completed variable declaration to be used in schema classes
  351. *
  352. * @param string $table Table name you want returned.
  353. * @param array $fields Array of field information to generate the table with.
  354. * @return string Variable declaration for a schema class
  355. */
  356. function generateTable($table, $fields) {
  357. $out = "\tvar \${$table} = array(\n";
  358. if (is_array($fields)) {
  359. $cols = array();
  360. foreach ($fields as $field => $value) {
  361. if ($field != 'indexes' && $field != 'tableParameters') {
  362. if (is_string($value)) {
  363. $type = $value;
  364. $value = array('type'=> $type);
  365. }
  366. $col = "\t\t'{$field}' => array('type' => '" . $value['type'] . "', ";
  367. unset($value['type']);
  368. $col .= join(', ', $this->__values($value));
  369. } elseif ($field == 'indexes') {
  370. $col = "\t\t'indexes' => array(";
  371. $props = array();
  372. foreach ((array)$value as $key => $index) {
  373. $props[] = "'{$key}' => array(" . join(', ', $this->__values($index)) . ")";
  374. }
  375. $col .= join(', ', $props);
  376. } elseif ($field == 'tableParameters') {
  377. //@todo add charset, collate and engine here
  378. $col = "\t\t'tableParameters' => array(";
  379. $props = array();
  380. foreach ((array)$value as $key => $param) {
  381. $props[] = "'{$key}' => '$param'";
  382. }
  383. $col .= join(', ', $props);
  384. }
  385. $col .= ")";
  386. $cols[] = $col;
  387. }
  388. $out .= join(",\n", $cols);
  389. }
  390. $out .= "\n\t);\n";
  391. return $out;
  392. }
  393. /**
  394. * Compares two sets of schemas
  395. *
  396. * @param mixed $old Schema object or array
  397. * @param mixed $new Schema object or array
  398. * @return array Tables (that are added, dropped, or changed)
  399. * @access public
  400. */
  401. function compare($old, $new = null) {
  402. if (empty($new)) {
  403. $new =& $this;
  404. }
  405. if (is_array($new)) {
  406. if (isset($new['tables'])) {
  407. $new = $new['tables'];
  408. }
  409. } else {
  410. $new = $new->tables;
  411. }
  412. if (is_array($old)) {
  413. if (isset($old['tables'])) {
  414. $old = $old['tables'];
  415. }
  416. } else {
  417. $old = $old->tables;
  418. }
  419. $tables = array();
  420. foreach ($new as $table => $fields) {
  421. if ($table == 'missing') {
  422. continue;
  423. }
  424. if (!array_key_exists($table, $old)) {
  425. $tables[$table]['add'] = $fields;
  426. } else {
  427. $diff = $this->_arrayDiffAssoc($fields, $old[$table]);
  428. if (!empty($diff)) {
  429. $tables[$table]['add'] = $diff;
  430. }
  431. $diff = $this->_arrayDiffAssoc($old[$table], $fields);
  432. if (!empty($diff)) {
  433. $tables[$table]['drop'] = $diff;
  434. }
  435. }
  436. foreach ($fields as $field => $value) {
  437. if (isset($old[$table][$field])) {
  438. $diff = $this->_arrayDiffAssoc($value, $old[$table][$field]);
  439. if (!empty($diff) && $field !== 'indexes' && $field !== 'tableParameters') {
  440. $tables[$table]['change'][$field] = array_merge($old[$table][$field], $diff);
  441. }
  442. }
  443. if (isset($add[$table][$field])) {
  444. $wrapper = array_keys($fields);
  445. if ($column = array_search($field, $wrapper)) {
  446. if (isset($wrapper[$column - 1])) {
  447. $tables[$table]['add'][$field]['after'] = $wrapper[$column - 1];
  448. }
  449. }
  450. }
  451. }
  452. if (isset($old[$table]['indexes']) && isset($new[$table]['indexes'])) {
  453. $diff = $this->_compareIndexes($new[$table]['indexes'], $old[$table]['indexes']);
  454. if ($diff) {
  455. if (!isset($tables[$table])) {
  456. $tables[$table] = array();
  457. }
  458. if (isset($diff['drop'])) {
  459. $tables[$table]['drop']['indexes'] = $diff['drop'];
  460. }
  461. if ($diff && isset($diff['add'])) {
  462. $tables[$table]['add']['indexes'] = $diff['add'];
  463. }
  464. }
  465. }
  466. if (isset($old[$table]['tableParameters']) && isset($new[$table]['tableParameters'])) {
  467. $diff = $this->_compareTableParameters($new[$table]['tableParameters'], $old[$table]['tableParameters']);
  468. if ($diff) {
  469. $tables[$table]['change']['tableParameters'] = $diff;
  470. }
  471. }
  472. }
  473. return $tables;
  474. }
  475. /**
  476. * Extended array_diff_assoc noticing change from/to NULL values
  477. *
  478. * It behaves almost the same way as array_diff_assoc except for NULL values: if
  479. * one of the values is not NULL - change is detected. It is useful in situation
  480. * where one value is strval('') ant other is strval(null) - in string comparing
  481. * methods this results as EQUAL, while it is not.
  482. *
  483. * @param array $array1 Base array
  484. * @param array $array2 Corresponding array checked for equality
  485. * @return array Difference as array with array(keys => values) from input array
  486. * where match was not found.
  487. * @access protected
  488. */
  489. function _arrayDiffAssoc($array1, $array2) {
  490. $difference = array();
  491. foreach ($array1 as $key => $value) {
  492. if (!array_key_exists($key, $array2)) {
  493. $difference[$key] = $value;
  494. continue;
  495. }
  496. $correspondingValue = $array2[$key];
  497. if (is_null($value) !== is_null($correspondingValue)) {
  498. $difference[$key] = $value;
  499. continue;
  500. }
  501. if (is_bool($value) !== is_bool($correspondingValue)) {
  502. $difference[$key] = $value;
  503. continue;
  504. }
  505. $compare = strval($value);
  506. $correspondingValue = strval($correspondingValue);
  507. if ($compare === $correspondingValue) {
  508. continue;
  509. }
  510. $difference[$key] = $value;
  511. }
  512. return $difference;
  513. }
  514. /**
  515. * Formats Schema columns from Model Object
  516. *
  517. * @param array $values options keys(type, null, default, key, length, extra)
  518. * @return array Formatted values
  519. * @access public
  520. */
  521. function __values($values) {
  522. $vals = array();
  523. if (is_array($values)) {
  524. foreach ($values as $key => $val) {
  525. if (is_array($val)) {
  526. $vals[] = "'{$key}' => array('" . implode("', '", $val) . "')";
  527. } else if (!is_numeric($key)) {
  528. $val = var_export($val, true);
  529. $vals[] = "'{$key}' => {$val}";
  530. }
  531. }
  532. }
  533. return $vals;
  534. }
  535. /**
  536. * Formats Schema columns from Model Object
  537. *
  538. * @param array $Obj model object
  539. * @return array Formatted columns
  540. * @access public
  541. */
  542. function __columns(&$Obj) {
  543. $db =& ConnectionManager::getDataSource($Obj->useDbConfig);
  544. $fields = $Obj->schema(true);
  545. $columns = $props = array();
  546. foreach ($fields as $name => $value) {
  547. if ($Obj->primaryKey == $name) {
  548. $value['key'] = 'primary';
  549. }
  550. if (!isset($db->columns[$value['type']])) {
  551. trigger_error(sprintf(__('Schema generation error: invalid column type %s does not exist in DBO', true), $value['type']), E_USER_NOTICE);
  552. continue;
  553. } else {
  554. $defaultCol = $db->columns[$value['type']];
  555. if (isset($defaultCol['limit']) && $defaultCol['limit'] == $value['length']) {
  556. unset($value['length']);
  557. } elseif (isset($defaultCol['length']) && $defaultCol['length'] == $value['length']) {
  558. unset($value['length']);
  559. }
  560. unset($value['limit']);
  561. }
  562. if (isset($value['default']) && ($value['default'] === '' || $value['default'] === false)) {
  563. unset($value['default']);
  564. }
  565. if (empty($value['length'])) {
  566. unset($value['length']);
  567. }
  568. if (empty($value['key'])) {
  569. unset($value['key']);
  570. }
  571. $columns[$name] = $value;
  572. }
  573. return $columns;
  574. }
  575. /**
  576. * Compare two schema files table Parameters
  577. *
  578. * @param array $new New indexes
  579. * @param array $old Old indexes
  580. * @return mixed False on failure, or an array of parameters to add & drop.
  581. */
  582. function _compareTableParameters($new, $old) {
  583. if (!is_array($new) || !is_array($old)) {
  584. return false;
  585. }
  586. $change = $this->_arrayDiffAssoc($new, $old);
  587. return $change;
  588. }
  589. /**
  590. * Compare two schema indexes
  591. *
  592. * @param array $new New indexes
  593. * @param array $old Old indexes
  594. * @return mixed false on failure or array of indexes to add and drop
  595. */
  596. function _compareIndexes($new, $old) {
  597. if (!is_array($new) || !is_array($old)) {
  598. return false;
  599. }
  600. $add = $drop = array();
  601. $diff = $this->_arrayDiffAssoc($new, $old);
  602. if (!empty($diff)) {
  603. $add = $diff;
  604. }
  605. $diff = $this->_arrayDiffAssoc($old, $new);
  606. if (!empty($diff)) {
  607. $drop = $diff;
  608. }
  609. foreach ($new as $name => $value) {
  610. if (isset($old[$name])) {
  611. $newUnique = isset($value['unique']) ? $value['unique'] : 0;
  612. $oldUnique = isset($old[$name]['unique']) ? $old[$name]['unique'] : 0;
  613. $newColumn = $value['column'];
  614. $oldColumn = $old[$name]['column'];
  615. $diff = false;
  616. if ($newUnique != $oldUnique) {
  617. $diff = true;
  618. } elseif (is_array($newColumn) && is_array($oldColumn)) {
  619. $diff = ($newColumn !== $oldColumn);
  620. } elseif (is_string($newColumn) && is_string($oldColumn)) {
  621. $diff = ($newColumn != $oldColumn);
  622. } else {
  623. $diff = true;
  624. }
  625. if ($diff) {
  626. $drop[$name] = null;
  627. $add[$name] = $value;
  628. }
  629. }
  630. }
  631. return array_filter(compact('add', 'drop'));
  632. }
  633. }