ResultSetMappingBuilder.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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\Query;
  20. use Doctrine\ORM\EntityManager;
  21. use Doctrine\ORM\Mapping\ClassMetadataInfo;
  22. /**
  23. * A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields.
  24. *
  25. * @author Michael Ridgway <mcridgway@gmail.com>
  26. * @since 2.1
  27. */
  28. class ResultSetMappingBuilder extends ResultSetMapping
  29. {
  30. /**
  31. * Picking this rename mode will register entity columns as is,
  32. * as they are in the database. This can cause clashes when multiple
  33. * entities are fetched that have columns with the same name.
  34. *
  35. * @var int
  36. */
  37. const COLUMN_RENAMING_NONE = 1;
  38. /**
  39. * Picking custom renaming allows the user to define the renaming
  40. * of specific columns with a rename array that contains column names as
  41. * keys and result alias as values.
  42. *
  43. * @var int
  44. */
  45. const COLUMN_RENAMING_CUSTOM = 2;
  46. /**
  47. * Incremental renaming uses a result set mapping internal counter to add a
  48. * number to each column result, leading to uniqueness. This only works if
  49. * you use {@see generateSelectClause()} to generate the SELECT clause for
  50. * you.
  51. *
  52. * @var int
  53. */
  54. const COLUMN_RENAMING_INCREMENT = 3;
  55. /**
  56. * @var int
  57. */
  58. private $sqlCounter = 0;
  59. /**
  60. * @var EntityManager
  61. */
  62. private $em;
  63. /**
  64. * Default column renaming mode.
  65. *
  66. * @var int
  67. */
  68. private $defaultRenameMode;
  69. /**
  70. * @param EntityManager $em
  71. * @param integer $defaultRenameMode
  72. */
  73. public function __construct(EntityManager $em, $defaultRenameMode = self::COLUMN_RENAMING_NONE)
  74. {
  75. $this->em = $em;
  76. $this->defaultRenameMode = $defaultRenameMode;
  77. }
  78. /**
  79. * Adds a root entity and all of its fields to the result set.
  80. *
  81. * @param string $class The class name of the root entity.
  82. * @param string $alias The unique alias to use for the root entity.
  83. * @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
  84. * @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM).
  85. *
  86. * @return void
  87. */
  88. public function addRootEntityFromClassMetadata($class, $alias, $renamedColumns = array(), $renameMode = null)
  89. {
  90. $renameMode = $renameMode ?: $this->defaultRenameMode;
  91. $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns);
  92. $this->addEntityResult($class, $alias);
  93. $this->addAllClassFields($class, $alias, $columnAliasMap);
  94. }
  95. /**
  96. * Adds a joined entity and all of its fields to the result set.
  97. *
  98. * @param string $class The class name of the joined entity.
  99. * @param string $alias The unique alias to use for the joined entity.
  100. * @param string $parentAlias The alias of the entity result that is the parent of this joined result.
  101. * @param object $relation The association field that connects the parent entity result
  102. * with the joined entity result.
  103. * @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
  104. * @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM).
  105. *
  106. * @return void
  107. */
  108. public function addJoinedEntityFromClassMetadata($class, $alias, $parentAlias, $relation, $renamedColumns = array(), $renameMode = null)
  109. {
  110. $renameMode = $renameMode ?: $this->defaultRenameMode;
  111. $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns);
  112. $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation);
  113. $this->addAllClassFields($class, $alias, $columnAliasMap);
  114. }
  115. /**
  116. * Adds all fields of the given class to the result set mapping (columns and meta fields).
  117. *
  118. * @param string $class
  119. * @param string $alias
  120. * @param array $columnAliasMap
  121. *
  122. * @return void
  123. *
  124. * @throws \InvalidArgumentException
  125. */
  126. protected function addAllClassFields($class, $alias, $columnAliasMap = array())
  127. {
  128. $classMetadata = $this->em->getClassMetadata($class);
  129. $platform = $this->em->getConnection()->getDatabasePlatform();
  130. if ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()) {
  131. throw new \InvalidArgumentException('ResultSetMapping builder does not currently support inheritance.');
  132. }
  133. foreach ($classMetadata->getColumnNames() as $columnName) {
  134. $propertyName = $classMetadata->getFieldName($columnName);
  135. $columnAlias = $platform->getSQLResultCasing($columnAliasMap[$columnName]);
  136. if (isset($this->fieldMappings[$columnAlias])) {
  137. throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper.");
  138. }
  139. $this->addFieldResult($alias, $columnAlias, $propertyName);
  140. }
  141. foreach ($classMetadata->associationMappings as $associationMapping) {
  142. if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
  143. foreach ($associationMapping['joinColumns'] as $joinColumn) {
  144. $columnName = $joinColumn['name'];
  145. $columnAlias = $platform->getSQLResultCasing($columnAliasMap[$columnName]);
  146. if (isset($this->metaMappings[$columnAlias])) {
  147. throw new \InvalidArgumentException("The column '$columnAlias' conflicts with another column in the mapper.");
  148. }
  149. $this->addMetaResult(
  150. $alias,
  151. $columnAlias,
  152. $columnName,
  153. (isset($associationMapping['id']) && $associationMapping['id'] === true)
  154. );
  155. }
  156. }
  157. }
  158. }
  159. /**
  160. * Gets column alias for a given column.
  161. *
  162. * @param string $columnName
  163. * @param int $mode
  164. * @param array $customRenameColumns
  165. *
  166. * @return string
  167. */
  168. private function getColumnAlias($columnName, $mode, array $customRenameColumns)
  169. {
  170. switch ($mode) {
  171. case self::COLUMN_RENAMING_INCREMENT:
  172. return $columnName . $this->sqlCounter++;
  173. case self::COLUMN_RENAMING_CUSTOM:
  174. return isset($customRenameColumns[$columnName])
  175. ? $customRenameColumns[$columnName] : $columnName;
  176. case self::COLUMN_RENAMING_NONE:
  177. return $columnName;
  178. }
  179. }
  180. /**
  181. * Retrieves a class columns and join columns aliases that are used in the SELECT clause.
  182. *
  183. * This depends on the renaming mode selected by the user.
  184. *
  185. * @param string $className
  186. * @param int $mode
  187. * @param array $customRenameColumns
  188. *
  189. * @return array
  190. */
  191. private function getColumnAliasMap($className, $mode, array $customRenameColumns)
  192. {
  193. if ($customRenameColumns) { // for BC with 2.2-2.3 API
  194. $mode = self::COLUMN_RENAMING_CUSTOM;
  195. }
  196. $columnAlias = array();
  197. $class = $this->em->getClassMetadata($className);
  198. foreach ($class->getColumnNames() as $columnName) {
  199. $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns);
  200. }
  201. foreach ($class->associationMappings as $associationMapping) {
  202. if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
  203. foreach ($associationMapping['joinColumns'] as $joinColumn) {
  204. $columnName = $joinColumn['name'];
  205. $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns);
  206. }
  207. }
  208. }
  209. return $columnAlias;
  210. }
  211. /**
  212. * Adds the mappings of the results of native SQL queries to the result set.
  213. *
  214. * @param ClassMetadataInfo $class
  215. * @param array $queryMapping
  216. *
  217. * @return ResultSetMappingBuilder
  218. */
  219. public function addNamedNativeQueryMapping(ClassMetadataInfo $class, array $queryMapping)
  220. {
  221. if (isset($queryMapping['resultClass'])) {
  222. return $this->addNamedNativeQueryResultClassMapping($class, $queryMapping['resultClass']);
  223. }
  224. return $this->addNamedNativeQueryResultSetMapping($class, $queryMapping['resultSetMapping']);
  225. }
  226. /**
  227. * Adds the class mapping of the results of native SQL queries to the result set.
  228. *
  229. * @param ClassMetadataInfo $class
  230. * @param string $resultClassName
  231. *
  232. * @return ResultSetMappingBuilder
  233. */
  234. public function addNamedNativeQueryResultClassMapping(ClassMetadataInfo $class, $resultClassName)
  235. {
  236. $classMetadata = $this->em->getClassMetadata($resultClassName);
  237. $shortName = $classMetadata->reflClass->getShortName();
  238. $alias = strtolower($shortName[0]).'0';
  239. $this->addEntityResult($class->name, $alias);
  240. if ($classMetadata->discriminatorColumn) {
  241. $discriminatorColumn = $classMetadata->discriminatorColumn;
  242. $this->setDiscriminatorColumn($alias, $discriminatorColumn['name']);
  243. $this->addMetaResult($alias, $discriminatorColumn['name'], $discriminatorColumn['fieldName']);
  244. }
  245. foreach ($classMetadata->getColumnNames() as $key => $columnName) {
  246. $propertyName = $classMetadata->getFieldName($columnName);
  247. $this->addFieldResult($alias, $columnName, $propertyName);
  248. }
  249. foreach ($classMetadata->associationMappings as $associationMapping) {
  250. if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
  251. foreach ($associationMapping['joinColumns'] as $joinColumn) {
  252. $columnName = $joinColumn['name'];
  253. $this->addMetaResult($alias, $columnName, $columnName, $classMetadata->isIdentifier($columnName));
  254. }
  255. }
  256. }
  257. return $this;
  258. }
  259. /**
  260. * Adds the result set mapping of the results of native SQL queries to the result set.
  261. *
  262. * @param ClassMetadataInfo $class
  263. * @param string $resultSetMappingName
  264. *
  265. * @return ResultSetMappingBuilder
  266. */
  267. public function addNamedNativeQueryResultSetMapping(ClassMetadataInfo $class, $resultSetMappingName)
  268. {
  269. $counter = 0;
  270. $resultMapping = $class->getSqlResultSetMapping($resultSetMappingName);
  271. $rooShortName = $class->reflClass->getShortName();
  272. $rootAlias = strtolower($rooShortName[0]) . $counter;
  273. if (isset($resultMapping['entities'])) {
  274. foreach ($resultMapping['entities'] as $key => $entityMapping) {
  275. $classMetadata = $this->em->getClassMetadata($entityMapping['entityClass']);
  276. if ($class->reflClass->name == $classMetadata->reflClass->name) {
  277. $this->addEntityResult($classMetadata->name, $rootAlias);
  278. $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $rootAlias);
  279. } else {
  280. $shortName = $classMetadata->reflClass->getShortName();
  281. $joinAlias = strtolower($shortName[0]) . ++ $counter;
  282. $associations = $class->getAssociationsByTargetClass($classMetadata->name);
  283. foreach ($associations as $relation => $mapping) {
  284. $this->addJoinedEntityResult($mapping['targetEntity'], $joinAlias, $rootAlias, $relation);
  285. $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $joinAlias);
  286. }
  287. }
  288. }
  289. }
  290. if (isset($resultMapping['columns'])) {
  291. foreach ($resultMapping['columns'] as $entityMapping) {
  292. $this->addScalarResult($entityMapping['name'], $entityMapping['name']);
  293. }
  294. }
  295. return $this;
  296. }
  297. /**
  298. * Adds the entity result mapping of the results of native SQL queries to the result set.
  299. *
  300. * @param ClassMetadataInfo $classMetadata
  301. * @param array $entityMapping
  302. * @param string $alias
  303. *
  304. * @return ResultSetMappingBuilder
  305. *
  306. * @throws \InvalidArgumentException
  307. */
  308. public function addNamedNativeQueryEntityResultMapping(ClassMetadataInfo $classMetadata, array $entityMapping, $alias)
  309. {
  310. if (isset($entityMapping['discriminatorColumn']) && $entityMapping['discriminatorColumn']) {
  311. $discriminatorColumn = $entityMapping['discriminatorColumn'];
  312. $this->setDiscriminatorColumn($alias, $discriminatorColumn);
  313. $this->addMetaResult($alias, $discriminatorColumn, $discriminatorColumn);
  314. }
  315. if (isset($entityMapping['fields']) && !empty($entityMapping['fields'])) {
  316. foreach ($entityMapping['fields'] as $field) {
  317. $fieldName = $field['name'];
  318. $relation = null;
  319. if(strpos($fieldName, '.')){
  320. list($relation, $fieldName) = explode('.', $fieldName);
  321. }
  322. if (isset($classMetadata->associationMappings[$relation])) {
  323. if($relation) {
  324. $associationMapping = $classMetadata->associationMappings[$relation];
  325. $joinAlias = $alias.$relation;
  326. $parentAlias = $alias;
  327. $this->addJoinedEntityResult($associationMapping['targetEntity'], $joinAlias, $parentAlias, $relation);
  328. $this->addFieldResult($joinAlias, $field['column'], $fieldName);
  329. }else {
  330. $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name);
  331. }
  332. } else {
  333. if(!isset($classMetadata->fieldMappings[$fieldName])) {
  334. throw new \InvalidArgumentException("Entity '".$classMetadata->name."' has no field '".$fieldName."'. ");
  335. }
  336. $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name);
  337. }
  338. }
  339. } else {
  340. foreach ($classMetadata->getColumnNames() as $columnName) {
  341. $propertyName = $classMetadata->getFieldName($columnName);
  342. $this->addFieldResult($alias, $columnName, $propertyName);
  343. }
  344. }
  345. return $this;
  346. }
  347. /**
  348. * Generates the Select clause from this ResultSetMappingBuilder.
  349. *
  350. * Works only for all the entity results. The select parts for scalar
  351. * expressions have to be written manually.
  352. *
  353. * @param array $tableAliases
  354. *
  355. * @return string
  356. */
  357. public function generateSelectClause($tableAliases = array())
  358. {
  359. $sql = "";
  360. foreach ($this->columnOwnerMap as $columnName => $dqlAlias) {
  361. $tableAlias = isset($tableAliases[$dqlAlias])
  362. ? $tableAliases[$dqlAlias] : $dqlAlias;
  363. if ($sql) {
  364. $sql .= ", ";
  365. }
  366. $sql .= $tableAlias . ".";
  367. if (isset($this->fieldMappings[$columnName])) {
  368. $class = $this->em->getClassMetadata($this->declaringClasses[$columnName]);
  369. $sql .= $class->fieldMappings[$this->fieldMappings[$columnName]]['columnName'];
  370. } else if (isset($this->metaMappings[$columnName])) {
  371. $sql .= $this->metaMappings[$columnName];
  372. } else if (isset($this->discriminatorColumn[$columnName])) {
  373. $sql .= $this->discriminatorColumn[$columnName];
  374. }
  375. $sql .= " AS " . $columnName;
  376. }
  377. return $sql;
  378. }
  379. /**
  380. * @return string
  381. */
  382. public function __toString()
  383. {
  384. return $this->generateSelectClause(array());
  385. }
  386. }