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

/2012/sample-tonic/src/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php

https://bitbucket.org/alessandro-aglietti/itis-leonardo-da-vinci
PHP | 415 lines | 339 code | 17 blank | 59 comment | 4 complexity | 7d3cb71548d04f5e8174a19bec612925 MD5 | raw file
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the MIT license. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\ORM\Mapping\Driver;
  20. use Doctrine\DBAL\Schema\AbstractSchemaManager,
  21. Doctrine\DBAL\Schema\SchemaException,
  22. Doctrine\Common\Persistence\Mapping\Driver\MappingDriver,
  23. Doctrine\Common\Persistence\Mapping\ClassMetadata,
  24. Doctrine\ORM\Mapping\ClassMetadataInfo,
  25. Doctrine\Common\Util\Inflector,
  26. Doctrine\ORM\Mapping\MappingException;
  27. /**
  28. * The DatabaseDriver reverse engineers the mapping metadata from a database.
  29. *
  30. *
  31. * @link www.doctrine-project.org
  32. * @since 2.0
  33. * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
  34. * @author Jonathan Wage <jonwage@gmail.com>
  35. * @author Benjamin Eberlei <kontakt@beberlei.de>
  36. */
  37. class DatabaseDriver implements MappingDriver
  38. {
  39. /**
  40. * @var AbstractSchemaManager
  41. */
  42. private $_sm;
  43. /**
  44. * @var array
  45. */
  46. private $tables = null;
  47. private $classToTableNames = array();
  48. /**
  49. * @var array
  50. */
  51. private $manyToManyTables = array();
  52. /**
  53. * @var array
  54. */
  55. private $classNamesForTables = array();
  56. /**
  57. * @var array
  58. */
  59. private $fieldNamesForColumns = array();
  60. /**
  61. * The namespace for the generated entities.
  62. *
  63. * @var string
  64. */
  65. private $namespace;
  66. /**
  67. *
  68. * @param AbstractSchemaManager $schemaManager
  69. */
  70. public function __construct(AbstractSchemaManager $schemaManager)
  71. {
  72. $this->_sm = $schemaManager;
  73. }
  74. /**
  75. * Set tables manually instead of relying on the reverse engeneering capabilities of SchemaManager.
  76. *
  77. * @param array $entityTables
  78. * @param array $manyToManyTables
  79. * @return void
  80. */
  81. public function setTables($entityTables, $manyToManyTables)
  82. {
  83. $this->tables = $this->manyToManyTables = $this->classToTableNames = array();
  84. foreach ($entityTables as $table) {
  85. $className = $this->getClassNameForTable($table->getName());
  86. $this->classToTableNames[$className] = $table->getName();
  87. $this->tables[$table->getName()] = $table;
  88. }
  89. foreach ($manyToManyTables as $table) {
  90. $this->manyToManyTables[$table->getName()] = $table;
  91. }
  92. }
  93. private function reverseEngineerMappingFromDatabase()
  94. {
  95. if ($this->tables !== null) {
  96. return;
  97. }
  98. $tables = array();
  99. foreach ($this->_sm->listTableNames() as $tableName) {
  100. $tables[$tableName] = $this->_sm->listTableDetails($tableName);
  101. }
  102. $this->tables = $this->manyToManyTables = $this->classToTableNames = array();
  103. foreach ($tables as $tableName => $table) {
  104. /* @var $table \Doctrine\DBAL\Schema\Table */
  105. if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
  106. $foreignKeys = $table->getForeignKeys();
  107. } else {
  108. $foreignKeys = array();
  109. }
  110. $allForeignKeyColumns = array();
  111. foreach ($foreignKeys as $foreignKey) {
  112. $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
  113. }
  114. if ( ! $table->hasPrimaryKey()) {
  115. throw new MappingException(
  116. "Table " . $table->getName() . " has no primary key. Doctrine does not ".
  117. "support reverse engineering from tables that don't have a primary key."
  118. );
  119. }
  120. $pkColumns = $table->getPrimaryKey()->getColumns();
  121. sort($pkColumns);
  122. sort($allForeignKeyColumns);
  123. if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) {
  124. $this->manyToManyTables[$tableName] = $table;
  125. } else {
  126. // lower-casing is necessary because of Oracle Uppercase Tablenames,
  127. // assumption is lower-case + underscore separated.
  128. $className = $this->getClassNameForTable($tableName);
  129. $this->tables[$tableName] = $table;
  130. $this->classToTableNames[$className] = $tableName;
  131. }
  132. }
  133. }
  134. /**
  135. * {@inheritDoc}
  136. */
  137. public function loadMetadataForClass($className, ClassMetadata $metadata)
  138. {
  139. $this->reverseEngineerMappingFromDatabase();
  140. if (!isset($this->classToTableNames[$className])) {
  141. throw new \InvalidArgumentException("Unknown class " . $className);
  142. }
  143. $tableName = $this->classToTableNames[$className];
  144. $metadata->name = $className;
  145. $metadata->table['name'] = $tableName;
  146. $columns = $this->tables[$tableName]->getColumns();
  147. $indexes = $this->tables[$tableName]->getIndexes();
  148. try {
  149. $primaryKeyColumns = $this->tables[$tableName]->getPrimaryKey()->getColumns();
  150. } catch(SchemaException $e) {
  151. $primaryKeyColumns = array();
  152. }
  153. if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
  154. $foreignKeys = $this->tables[$tableName]->getForeignKeys();
  155. } else {
  156. $foreignKeys = array();
  157. }
  158. $allForeignKeyColumns = array();
  159. foreach ($foreignKeys as $foreignKey) {
  160. $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
  161. }
  162. $ids = array();
  163. $fieldMappings = array();
  164. foreach ($columns as $column) {
  165. $fieldMapping = array();
  166. if (in_array($column->getName(), $allForeignKeyColumns)) {
  167. continue;
  168. } else if ($primaryKeyColumns && in_array($column->getName(), $primaryKeyColumns)) {
  169. $fieldMapping['id'] = true;
  170. }
  171. $fieldMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $column->getName(), false);
  172. $fieldMapping['columnName'] = $column->getName();
  173. $fieldMapping['type'] = strtolower((string) $column->getType());
  174. if ($column->getType() instanceof \Doctrine\DBAL\Types\StringType) {
  175. $fieldMapping['length'] = $column->getLength();
  176. $fieldMapping['fixed'] = $column->getFixed();
  177. } else if ($column->getType() instanceof \Doctrine\DBAL\Types\IntegerType) {
  178. $fieldMapping['unsigned'] = $column->getUnsigned();
  179. }
  180. $fieldMapping['nullable'] = $column->getNotNull() ? false : true;
  181. if (isset($fieldMapping['id'])) {
  182. $ids[] = $fieldMapping;
  183. } else {
  184. $fieldMappings[] = $fieldMapping;
  185. }
  186. }
  187. if ($ids) {
  188. if (count($ids) == 1) {
  189. $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
  190. }
  191. foreach ($ids as $id) {
  192. $metadata->mapField($id);
  193. }
  194. }
  195. foreach ($fieldMappings as $fieldMapping) {
  196. $metadata->mapField($fieldMapping);
  197. }
  198. foreach ($this->manyToManyTables as $manyTable) {
  199. foreach ($manyTable->getForeignKeys() as $foreignKey) {
  200. // foreign key maps to the table of the current entity, many to many association probably exists
  201. if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) {
  202. $myFk = $foreignKey;
  203. $otherFk = null;
  204. foreach ($manyTable->getForeignKeys() as $foreignKey) {
  205. if ($foreignKey != $myFk) {
  206. $otherFk = $foreignKey;
  207. break;
  208. }
  209. }
  210. if (!$otherFk) {
  211. // the definition of this many to many table does not contain
  212. // enough foreign key information to continue reverse engeneering.
  213. continue;
  214. }
  215. $localColumn = current($myFk->getColumns());
  216. $associationMapping = array();
  217. $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true);
  218. $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
  219. if (current($manyTable->getColumns())->getName() == $localColumn) {
  220. $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
  221. $associationMapping['joinTable'] = array(
  222. 'name' => strtolower($manyTable->getName()),
  223. 'joinColumns' => array(),
  224. 'inverseJoinColumns' => array(),
  225. );
  226. $fkCols = $myFk->getForeignColumns();
  227. $cols = $myFk->getColumns();
  228. for ($i = 0; $i < count($cols); $i++) {
  229. $associationMapping['joinTable']['joinColumns'][] = array(
  230. 'name' => $cols[$i],
  231. 'referencedColumnName' => $fkCols[$i],
  232. );
  233. }
  234. $fkCols = $otherFk->getForeignColumns();
  235. $cols = $otherFk->getColumns();
  236. for ($i = 0; $i < count($cols); $i++) {
  237. $associationMapping['joinTable']['inverseJoinColumns'][] = array(
  238. 'name' => $cols[$i],
  239. 'referencedColumnName' => $fkCols[$i],
  240. );
  241. }
  242. } else {
  243. $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
  244. }
  245. $metadata->mapManyToMany($associationMapping);
  246. break;
  247. }
  248. }
  249. }
  250. foreach ($foreignKeys as $foreignKey) {
  251. $foreignTable = $foreignKey->getForeignTableName();
  252. $cols = $foreignKey->getColumns();
  253. $fkCols = $foreignKey->getForeignColumns();
  254. $localColumn = current($cols);
  255. $associationMapping = array();
  256. $associationMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $localColumn, true);
  257. $associationMapping['targetEntity'] = $this->getClassNameForTable($foreignTable);
  258. if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) {
  259. $associationMapping['fieldName'] = $associationMapping['fieldName'] . "2";
  260. }
  261. if ($primaryKeyColumns && in_array($localColumn, $primaryKeyColumns)) {
  262. $associationMapping['id'] = true;
  263. }
  264. for ($i = 0; $i < count($cols); $i++) {
  265. $associationMapping['joinColumns'][] = array(
  266. 'name' => $cols[$i],
  267. 'referencedColumnName' => $fkCols[$i],
  268. );
  269. }
  270. //Here we need to check if $cols are the same as $primaryKeyColums
  271. if (!array_diff($cols,$primaryKeyColumns)) {
  272. $metadata->mapOneToOne($associationMapping);
  273. } else {
  274. $metadata->mapManyToOne($associationMapping);
  275. }
  276. }
  277. }
  278. /**
  279. * {@inheritDoc}
  280. */
  281. public function isTransient($className)
  282. {
  283. return true;
  284. }
  285. /**
  286. * {@inheritDoc}
  287. */
  288. public function getAllClassNames()
  289. {
  290. $this->reverseEngineerMappingFromDatabase();
  291. return array_keys($this->classToTableNames);
  292. }
  293. /**
  294. * Set class name for a table.
  295. *
  296. * @param string $tableName
  297. * @param string $className
  298. * @return void
  299. */
  300. public function setClassNameForTable($tableName, $className)
  301. {
  302. $this->classNamesForTables[$tableName] = $className;
  303. }
  304. /**
  305. * Set field name for a column on a specific table.
  306. *
  307. * @param string $tableName
  308. * @param string $columnName
  309. * @param string $fieldName
  310. * @return void
  311. */
  312. public function setFieldNameForColumn($tableName, $columnName, $fieldName)
  313. {
  314. $this->fieldNamesForColumns[$tableName][$columnName] = $fieldName;
  315. }
  316. /**
  317. * Return the mapped class name for a table if it exists. Otherwise return "classified" version.
  318. *
  319. * @param string $tableName
  320. * @return string
  321. */
  322. private function getClassNameForTable($tableName)
  323. {
  324. if (isset($this->classNamesForTables[$tableName])) {
  325. return $this->namespace . $this->classNamesForTables[$tableName];
  326. }
  327. return $this->namespace . Inflector::classify(strtolower($tableName));
  328. }
  329. /**
  330. * Return the mapped field name for a column, if it exists. Otherwise return camelized version.
  331. *
  332. * @param string $tableName
  333. * @param string $columnName
  334. * @param boolean $fk Whether the column is a foreignkey or not.
  335. * @return string
  336. */
  337. private function getFieldNameForColumn($tableName, $columnName, $fk = false)
  338. {
  339. if (isset($this->fieldNamesForColumns[$tableName]) && isset($this->fieldNamesForColumns[$tableName][$columnName])) {
  340. return $this->fieldNamesForColumns[$tableName][$columnName];
  341. }
  342. $columnName = strtolower($columnName);
  343. // Replace _id if it is a foreignkey column
  344. if ($fk) {
  345. $columnName = str_replace('_id', '', $columnName);
  346. }
  347. return Inflector::camelize($columnName);
  348. }
  349. /**
  350. * Set the namespace for the generated entities.
  351. *
  352. * @param string $namespace
  353. * @return void
  354. */
  355. public function setNamespace($namespace)
  356. {
  357. $this->namespace = $namespace;
  358. }
  359. }