PageRenderTime 36ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/package/app/app/symfony/addon/propel/sfPropelDatabaseSchema.class.php

https://bitbucket.org/pandaos/kaltura
PHP | 719 lines | 575 code | 80 blank | 64 comment | 101 complexity | f315c6d45212e000929e73d3c3e7d599 MD5 | raw file
Possible License(s): AGPL-3.0, GPL-3.0, BSD-3-Clause, LGPL-2.1, GPL-2.0, LGPL-3.0, JSON, MPL-2.0-no-copyleft-exception, Apache-2.0
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. *
  11. * @package symfony
  12. * @subpackage addon
  13. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  14. * @author Fran??ois Zaninotto <francois.zaninotto@symfony-project.com>
  15. * @version SVN: $Id$
  16. */
  17. class sfPropelDatabaseSchema
  18. {
  19. protected $connection_name = '';
  20. protected $database = array();
  21. public function asArray()
  22. {
  23. return array($this->connection_name => $this->database);
  24. }
  25. public function loadYAML($file)
  26. {
  27. $schema = sfYaml::load($file);
  28. if (count($schema) > 1)
  29. {
  30. throw new sfException('A schema.yml must only contain 1 database entry.');
  31. }
  32. $tmp = array_keys($schema);
  33. $this->connection_name = array_shift($tmp);
  34. if ($this->connection_name)
  35. {
  36. $this->database = $schema[$this->connection_name];
  37. $this->fixYAMLDatabase();
  38. $this->fixYAMLI18n();
  39. $this->fixYAMLColumns();
  40. }
  41. }
  42. public function asXML()
  43. {
  44. $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
  45. $xml .= "<database name=\"$this->connection_name\"".$this->getAttributesFor($this->database).">\n";
  46. // tables
  47. foreach ($this->getChildren($this->database) as $tb_name => $table)
  48. {
  49. $xml .= "\n <table name=\"$tb_name\"".$this->getAttributesFor($table).">\n";
  50. // columns
  51. foreach ($this->getChildren($table) as $col_name => $column)
  52. {
  53. $xml .= " <column name=\"$col_name\"".$this->getAttributesForColumn($tb_name, $col_name, $column);
  54. }
  55. // indexes
  56. if (isset($table['_indexes']))
  57. {
  58. foreach ($table['_indexes'] as $index_name => $index)
  59. {
  60. $xml .= " <index name=\"$index_name\">\n";
  61. foreach ($index as $index_column)
  62. {
  63. $xml .= " <index-column name=\"$index_column\" />\n";
  64. }
  65. $xml .= " </index>\n";
  66. }
  67. }
  68. // uniques
  69. if (isset($table['_uniques']))
  70. {
  71. foreach ($table['_uniques'] as $unique_name => $index)
  72. {
  73. $xml .= " <unique name=\"$unique_name\">\n";
  74. foreach ($index as $unique_column)
  75. {
  76. $xml .= " <unique-column name=\"$unique_column\" />\n";
  77. }
  78. $xml .= " </unique>\n";
  79. }
  80. }
  81. // foreign-keys
  82. if (isset($table['_foreignKeys']))
  83. {
  84. foreach ($table['_foreignKeys'] as $fkey_name => $fkey)
  85. {
  86. $xml .= " <foreign-key foreignTable=\"$fkey[foreignTable]\"";
  87. // foreign key name
  88. if (!is_numeric($fkey_name))
  89. {
  90. $xml .= " name=\"$fkey_name\"";
  91. }
  92. // onDelete
  93. if (isset($fkey['onDelete']))
  94. {
  95. $xml .= " onDelete=\"$fkey[onDelete]\"";
  96. }
  97. // onUpdate
  98. if (isset($fkey['onUpdate']))
  99. {
  100. $xml .= " onUpdate=\"$fkey[onUpdate]\"";
  101. }
  102. $xml .= ">\n";
  103. // references
  104. if (isset($fkey['references']))
  105. {
  106. foreach ($fkey['references'] as $reference)
  107. {
  108. $xml .= " <reference local=\"$reference[local]\" foreign=\"$reference[foreign]\" />\n";
  109. }
  110. }
  111. $xml .= " </foreign-key>\n";
  112. }
  113. }
  114. $xml .= " </table>\n";
  115. }
  116. $xml .= "\n</database>\n";
  117. return $xml;
  118. }
  119. protected function fixYAMLDatabase()
  120. {
  121. if (!isset($this->database['_attributes']))
  122. {
  123. $this->database['_attributes'] = array();
  124. }
  125. // conventions for database attributes
  126. $this->setIfNotSet($this->database['_attributes'], 'defaultIdMethod', 'native');
  127. $this->setIfNotSet($this->database['_attributes'], 'noXsd', true);
  128. $this->setIfNotSet($this->database['_attributes'], 'package', 'lib.model');
  129. }
  130. protected function fixYAMLI18n()
  131. {
  132. foreach ($this->getTables() as $i18n_table => $columns)
  133. {
  134. $pos = strpos($i18n_table, '_i18n');
  135. $has_primary_key = false;
  136. foreach ($columns as $column => $attributes)
  137. {
  138. if (is_array($attributes) && array_key_exists('primaryKey', $attributes))
  139. {
  140. $has_primary_key = true;
  141. }
  142. }
  143. if ($pos > 0 && $pos == strlen($i18n_table) - 5 && !$has_primary_key)
  144. {
  145. // i18n table without primary key
  146. $main_table = $this->findTable(substr($i18n_table, 0, $pos));
  147. if ($main_table)
  148. {
  149. // set i18n attributes for main table
  150. $this->setIfNotSet($this->database[$main_table]['_attributes'], 'isI18N', 1);
  151. $this->setIfNotSet($this->database[$main_table]['_attributes'], 'i18nTable', $i18n_table);
  152. // set id and culture columns for i18n table
  153. $this->setIfNotSet($this->database[$i18n_table], 'id', array(
  154. 'type' => 'integer',
  155. 'required' => true,
  156. 'primaryKey' => true,
  157. 'foreignTable' => $main_table,
  158. 'foreignReference' => 'id',
  159. 'onDelete' => 'cascade'
  160. ));
  161. $this->setIfNotSet($this->database[$i18n_table], 'culture', array(
  162. 'isCulture' => true,
  163. 'type' => 'varchar',
  164. 'size' => '7',
  165. 'required' => true,
  166. 'primaryKey' => true
  167. ));
  168. }
  169. else
  170. {
  171. throw new sfException(sprintf('Missing main table for internationalized table "%s".', $i18n_table));
  172. }
  173. }
  174. }
  175. }
  176. protected function fixYAMLColumns()
  177. {
  178. foreach ($this->getTables() as $table => $columns)
  179. {
  180. $has_primary_key = false;
  181. foreach ($columns as $column => $attributes)
  182. {
  183. if ($attributes == null)
  184. {
  185. // conventions for null attributes
  186. if ($column == 'created_at' || $column == 'updated_at')
  187. {
  188. // timestamp convention
  189. $this->database[$table][$column]['type']= 'timestamp';
  190. }
  191. if ($column == 'id')
  192. {
  193. // primary key convention
  194. $this->database[$table]['id'] = array(
  195. 'type' => 'integer',
  196. 'required' => true,
  197. 'primaryKey' => true,
  198. 'autoincrement' => true
  199. );
  200. $has_primary_key = true;
  201. }
  202. $pos = strpos($column, '_id');
  203. if ($pos > 0 && $pos == strlen($column) - 3)
  204. {
  205. // foreign key convention
  206. $foreign_table = $this->findTable(substr($column, 0, $pos));
  207. if ($foreign_table)
  208. {
  209. $this->database[$table][$column] = array(
  210. 'type' => 'integer',
  211. 'foreignTable' => $foreign_table,
  212. 'foreignReference' => 'id'
  213. );
  214. }
  215. else
  216. {
  217. throw new sfException(sprintf('Unable to resolve foreign table for column "%s"', $column));
  218. }
  219. }
  220. }
  221. else
  222. {
  223. if (!is_array($attributes))
  224. {
  225. // compact type given as single attribute
  226. $this->database[$table][$column] = $this->getAttributesFromCompactType($attributes);
  227. }
  228. else
  229. {
  230. if (isset($attributes['type']))
  231. {
  232. // compact type given as value of the type attribute
  233. $this->database[$table][$column] = array_merge($this->database[$table][$column], $this->getAttributesFromCompactType($attributes['type']));
  234. }
  235. if (isset($attributes['primaryKey']))
  236. {
  237. $has_primary_key = true;
  238. }
  239. }
  240. }
  241. }
  242. if (!$has_primary_key)
  243. {
  244. // convention for tables without primary key
  245. $this->database[$table]['id'] = array(
  246. 'type' => 'integer',
  247. 'required' => true,
  248. 'primaryKey' => true,
  249. 'autoincrement' => true
  250. );
  251. }
  252. }
  253. }
  254. protected function getAttributesFromCompactType($type)
  255. {
  256. preg_match('/varchar\(([\d]+)\)/', $type, $matches);
  257. if (isset($matches[1]))
  258. {
  259. return array('type' => 'varchar', 'size' => $matches[1]);
  260. }
  261. else
  262. {
  263. return array('type' => $type);
  264. }
  265. }
  266. protected function setIfNotSet(&$entry, $key, $value)
  267. {
  268. if (!isset($entry[$key]))
  269. {
  270. $entry[$key] = $value;
  271. }
  272. }
  273. protected function findTable($table_name)
  274. {
  275. // find a table from a phpName or a name
  276. $table_match = false;
  277. foreach ($this->getTables() as $tb_name => $table)
  278. {
  279. if ((isset($table['_attributes']['phpName']) && $table['_attributes']['phpName'] == sfInflector::camelize($table_name)) || ($tb_name == $table_name))
  280. {
  281. $table_match = $tb_name;
  282. }
  283. }
  284. return $table_match;
  285. }
  286. protected function getAttributesForColumn($tb_name, $col_name, $column)
  287. {
  288. $attributes_string = '';
  289. if (is_array($column))
  290. {
  291. foreach ($column as $key => $value)
  292. {
  293. if (!in_array($key, array('foreignTable', 'foreignReference', 'onDelete', 'onUpdate', 'index', 'unique')))
  294. {
  295. $attributes_string .= " $key=\"".htmlspecialchars($this->getCorrectValueFor($key, $value))."\"";
  296. }
  297. }
  298. $attributes_string .= " />\n";
  299. }
  300. else
  301. {
  302. throw new sfException('Incorrect settings for column '.$col_name);
  303. }
  304. // conventions for foreign key attributes
  305. if (is_array($column) && isset($column['foreignTable']))
  306. {
  307. $attributes_string .= " <foreign-key foreignTable=\"$column[foreignTable]\"";
  308. if (isset($column['onDelete']))
  309. {
  310. $attributes_string .= " onDelete=\"$column[onDelete]\"";
  311. }
  312. if (isset($column['onUpdate']))
  313. {
  314. $attributes_string .= " onUpdate=\"$column[onUpdate]\"";
  315. }
  316. $attributes_string .= ">\n";
  317. $attributes_string .= " <reference local=\"$col_name\" foreign=\"$column[foreignReference]\" />\n";
  318. $attributes_string .= " </foreign-key>\n";
  319. }
  320. // conventions for index and unique index attributes
  321. if (is_array($column) && isset($column['index']))
  322. {
  323. if ($column['index'] === 'unique')
  324. {
  325. $attributes_string .= " <unique name=\"${tb_name}_${col_name}_unique\">\n";
  326. $attributes_string .= " <unique-column name=\"$col_name\" />\n";
  327. $attributes_string .= " </unique>\n";
  328. }
  329. else
  330. {
  331. $attributes_string .= " <index name=\"${tb_name}_${col_name}_index\">\n";
  332. $attributes_string .= " <index-column name=\"$col_name\" />\n";
  333. $attributes_string .= " </index>\n";
  334. }
  335. }
  336. // conventions for sequence name attributes
  337. // required for databases using sequences for auto-increment columns (e.g. PostgreSQL or Oracle)
  338. if (is_array($column) && isset($column['sequence']))
  339. {
  340. $attributes_string .= " <id-method-parameter value=\"$column[sequence]\" />\n";
  341. }
  342. return $attributes_string;
  343. }
  344. protected function getAttributesFor($tag)
  345. {
  346. if (!isset($tag['_attributes']))
  347. {
  348. return '';
  349. }
  350. $attributes = $tag['_attributes'];
  351. $attributes_string = '';
  352. foreach ($attributes as $key => $value)
  353. {
  354. $attributes_string .= ' '.$key.'="'.htmlspecialchars($this->getCorrectValueFor($key, $value)).'"';
  355. }
  356. return $attributes_string;
  357. }
  358. protected function getCorrectValueFor($key, $value)
  359. {
  360. $booleans = array('required', 'primaryKey', 'autoincrement', 'autoIncrement', 'noXsd', 'isI18N', 'isCulture');
  361. if (in_array($key, $booleans))
  362. {
  363. return $value == 1 ? 'true' : 'false';
  364. }
  365. else
  366. {
  367. return is_null($value) ? 'null' : $value;
  368. }
  369. }
  370. public function getTables()
  371. {
  372. return $this->getChildren($this->database);
  373. }
  374. public function getChildren($hash)
  375. {
  376. foreach ($hash as $key => $value)
  377. {
  378. // ignore special children (starting with _)
  379. if ($key[0] == '_')
  380. {
  381. unset($hash[$key]);
  382. }
  383. }
  384. return $hash;
  385. }
  386. public function loadXML($file)
  387. {
  388. $schema = simplexml_load_file($file);
  389. $database = array();
  390. // database
  391. list($database_name, $database_attributes) = $this->getNameAndAttributes($schema->attributes());
  392. if ($database_name)
  393. {
  394. $this->connection_name = $database_name;
  395. }
  396. else
  397. {
  398. throw new sfException('The database tag misses a name attribute');
  399. }
  400. if ($database_attributes)
  401. {
  402. $database['_attributes'] = $database_attributes;
  403. }
  404. // tables
  405. foreach ($schema as $table)
  406. {
  407. list($table_name, $table_attributes) = $this->getNameAndAttributes($table->attributes());
  408. if ($table_name)
  409. {
  410. $database[$table_name] = array();
  411. }
  412. else
  413. {
  414. throw new sfException('A table tag misses the name attribute');
  415. }
  416. if ($table_attributes)
  417. {
  418. $database[$table_name]['_attributes'] = $table_attributes;
  419. }
  420. // columns
  421. foreach ($table->xpath('column') as $column)
  422. {
  423. list($column_name, $column_attributes) = $this->getNameAndAttributes($column->attributes());
  424. if ($column_name)
  425. {
  426. $database[$table_name][$column_name] = $column_attributes;
  427. }
  428. else
  429. {
  430. throw new sfException('A column tag misses the name attribute');
  431. }
  432. }
  433. // foreign-keys
  434. $database[$table_name]['_foreign_keys'] = array();
  435. foreach ($table->xpath('foreign-key') as $foreign_key)
  436. {
  437. $foreign_key_table = array();
  438. // foreign key attributes
  439. if (isset($foreign_key['foreignTable']))
  440. {
  441. $foreign_key_table['foreign_table'] = (string) $foreign_key['foreignTable'];
  442. }
  443. else
  444. {
  445. throw new sfException('A foreign key misses the foreignTable attribute');
  446. }
  447. if (isset($foreign_key['onDelete']))
  448. {
  449. $foreign_key_table['on_delete'] = (string) $foreign_key['onDelete'];
  450. }
  451. if (isset($foreign_key['onUpdate']))
  452. {
  453. $foreign_key_table['on_update'] = (string) $foreign_key['onUpdate'];
  454. }
  455. // foreign key references
  456. $foreign_key_table['references'] = array();
  457. foreach ($foreign_key->xpath('reference') as $reference)
  458. {
  459. $reference_attributes = array();
  460. foreach ($reference->attributes() as $reference_attribute_name => $reference_attribute_value)
  461. {
  462. $reference_attributes[$reference_attribute_name] = strval($reference_attribute_value);
  463. }
  464. $foreign_key_table['references'][] = $reference_attributes;
  465. }
  466. if (isset($foreign_key['name']))
  467. {
  468. $database[$table_name]['_foreign_keys'][(string)$foreign_key['name']] = $foreign_key_table;
  469. }
  470. else
  471. {
  472. $database[$table_name]['_foreign_keys'][] = $foreign_key_table;
  473. }
  474. }
  475. $this->removeEmptyKey($database[$table_name], '_foreign_keys');
  476. // indexes
  477. $database[$table_name]['_indexes'] = array();
  478. foreach ($table->xpath('index') as $index)
  479. {
  480. $index_keys = array();
  481. foreach ($index->xpath('index-column') as $index_key)
  482. {
  483. $index_keys[] = strval($index_key['name']);
  484. }
  485. $database[$table_name]['_indexes'][strval($index['name'])] = $index_keys;
  486. }
  487. $this->removeEmptyKey($database[$table_name], '_indexes');
  488. // unique indexes
  489. $database[$table_name]['_uniques'] = array();
  490. foreach ($table->xpath('unique') as $index)
  491. {
  492. $unique_keys = array();
  493. foreach ($index->xpath('unique-column') as $unique_key)
  494. {
  495. $unique_keys[] = strval($unique_key['name']);
  496. }
  497. $database[$table_name]['_uniques'][strval($index['name'])] = $unique_keys;
  498. }
  499. $this->removeEmptyKey($database[$table_name], '_uniques');
  500. }
  501. $this->database = $database;
  502. $this->fixXML();
  503. }
  504. public function fixXML()
  505. {
  506. $this->fixXMLForeignKeys();
  507. $this->fixXMLIndexes();
  508. // $this->fixXMLColumns();
  509. }
  510. protected function fixXMLForeignKeys()
  511. {
  512. foreach ($this->getTables() as $table => $columns)
  513. {
  514. if (isset($this->database[$table]['_foreign_keys']))
  515. {
  516. $foreign_keys = $this->database[$table]['_foreign_keys'];
  517. foreach ($foreign_keys as $foreign_key_name => $foreign_key_attributes)
  518. {
  519. // Only single foreign keys can be simplified
  520. if (count($foreign_key_attributes['references']) == 1)
  521. {
  522. $reference = $foreign_key_attributes['references'][0];
  523. // set simple foreign key
  524. $this->database[$table][$reference['local']]['foreignTable'] = $foreign_key_attributes['foreign_table'];
  525. $this->database[$table][$reference['local']]['foreignReference'] = $reference['foreign'];
  526. if (isset($foreign_key_attributes['on_delete']))
  527. {
  528. $this->database[$table][$reference['local']]['onDelete'] = $foreign_key_attributes['on_delete'];
  529. }
  530. if (isset($foreign_key_attributes['on_update']))
  531. {
  532. $this->database[$table][$reference['local']]['onUpdate'] = $foreign_key_attributes['on_update'];
  533. }
  534. // remove complex foreign key
  535. unset($this->database[$table]['_foreign_keys'][$foreign_key_name]);
  536. }
  537. $this->removeEmptyKey($this->database[$table], '_foreign_keys');
  538. }
  539. }
  540. }
  541. }
  542. protected function fixXMLIndexes()
  543. {
  544. foreach ($this->getTables() as $table => $columns)
  545. {
  546. if (isset($this->database[$table]['_indexes']))
  547. {
  548. $indexes = $this->database[$table]['_indexes'];
  549. foreach ($indexes as $index => $references)
  550. {
  551. // Only single indexes can be simplified
  552. if (count($references) == 1 && array_key_exists(substr($index, 0, strlen($index) - 6), $columns))
  553. {
  554. $reference = $references[0];
  555. // set simple index
  556. $this->database[$table][$reference]['index'] = 'true';
  557. // remove complex index
  558. unset($this->database[$table]['_indexes'][$index]);
  559. }
  560. $this->removeEmptyKey($this->database[$table], '_indexes');
  561. }
  562. }
  563. if (isset($this->database[$table]['_uniques']))
  564. {
  565. $uniques = $this->database[$table]['_uniques'];
  566. foreach ($uniques as $index => $references)
  567. {
  568. // Only single unique indexes can be simplified
  569. if (count($references) == 1 && array_key_exists(substr($index, 0, strlen($index) - 7), $columns))
  570. {
  571. $reference = $references[0];
  572. // set simple index
  573. $this->database[$table][$reference]['index'] = 'unique';
  574. // remove complex unique index
  575. unset($this->database[$table]['_uniques'][$index]);
  576. }
  577. $this->removeEmptyKey($this->database[$table], '_uniques');
  578. }
  579. }
  580. }
  581. }
  582. protected function fixXMLColumns()
  583. {
  584. foreach ($this->getTables() as $table => $columns)
  585. {
  586. foreach ($columns as $column => $attributes)
  587. {
  588. if ($column == 'id' && !array_diff($attributes, array('type' => 'integer', 'required' => 'true', 'primaryKey' => 'true', 'autoincrement' => 'true')))
  589. {
  590. // simplify primary keys
  591. $this->database[$table]['id'] = null;
  592. }
  593. if (($column == 'created_at') || ($column == 'updated_at') && !array_diff($attributes, array('type' => 'timestamp')))
  594. {
  595. // simplify timestamps
  596. $this->database[$table][$column] = null;
  597. }
  598. $pos = strpos($column, '_id');
  599. $has_fk_name = $pos > 0 && $pos == strlen($column) - 3;
  600. $is_foreign_key = isset($attributes['type']) && $attributes['type'] == 'integer' && isset($attributes['foreignReference']) && $attributes['foreignReference'] == 'id';
  601. $has_foreign_table = isset($attributes['foreignTable']) && array_key_exists($attributes['foreignTable'], $this->getTables());
  602. $has_other_attribute = isset($attributes['onDelete']);
  603. if ($has_fk_name && $has_foreign_table && $is_foreign_key && !$has_other_attribute)
  604. {
  605. // simplify foreign key
  606. $this->database[$table][$column] = null;
  607. }
  608. }
  609. }
  610. }
  611. public function asYAML()
  612. {
  613. return sfYaml::dump(array($this->connection_name => $this->database));
  614. }
  615. protected function getNameAndAttributes($hash, $name_attribute = 'name')
  616. {
  617. // tag name
  618. $name = '';
  619. if (isset($hash[$name_attribute]))
  620. {
  621. $name = strval($hash[$name_attribute]);
  622. unset($hash[$name_attribute]);
  623. }
  624. // tag attributes
  625. $attributes = array();
  626. foreach ($hash as $attribute => $value)
  627. {
  628. $attributes[$attribute] = strval($value);
  629. }
  630. return array($name, $attributes);
  631. }
  632. protected function removeEmptyKey(&$hash, $key)
  633. {
  634. if (isset($hash[$key]) && !$hash[$key])
  635. {
  636. unset($hash[$key]);
  637. }
  638. }
  639. }