ProxyQuery.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  5. * (c) Jonathan H. Wage <jonwage@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Sonata\DoctrineORMAdminBundle\Datagrid;
  11. use Doctrine\ORM\QueryBuilder;
  12. use Doctrine\ORM\Query;
  13. use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
  14. /**
  15. * This class try to unify the query usage with Doctrine
  16. */
  17. class ProxyQuery implements ProxyQueryInterface
  18. {
  19. protected $queryBuilder;
  20. protected $sortBy;
  21. protected $sortOrder;
  22. protected $parameterUniqueId;
  23. protected $entityJoinAliases;
  24. /**
  25. * @param mixed $queryBuilder
  26. */
  27. public function __construct($queryBuilder)
  28. {
  29. $this->queryBuilder = $queryBuilder;
  30. $this->uniqueParameterId = 0;
  31. $this->entityJoinAliases = array();
  32. }
  33. /**
  34. * {@inheritdoc}
  35. */
  36. public function execute(array $params = array(), $hydrationMode = null)
  37. {
  38. // always clone the original queryBuilder
  39. $queryBuilder = clone $this->queryBuilder;
  40. // todo : check how doctrine behave, potential SQL injection here ...
  41. if ($this->getSortBy()) {
  42. $sortBy = $this->getSortBy();
  43. if (strpos($sortBy, '.') === false) { // add the current alias
  44. $sortBy = $queryBuilder->getRootAlias() . '.' . $sortBy;
  45. }
  46. $queryBuilder->addOrderBy($sortBy, $this->getSortOrder());
  47. }
  48. return $this->getFixedQueryBuilder($queryBuilder)->getQuery()->execute($params, $hydrationMode);
  49. }
  50. /**
  51. * This method alters the query to return a clean set of object with a working
  52. * set of Object
  53. *
  54. * @param \Doctrine\ORM\QueryBuilder $queryBuilder
  55. *
  56. * @return \Doctrine\ORM\QueryBuilder
  57. */
  58. private function getFixedQueryBuilder(QueryBuilder $queryBuilder)
  59. {
  60. $queryBuilderId = clone $queryBuilder;
  61. // step 1 : retrieve the targeted class
  62. $from = $queryBuilderId->getDQLPart('from');
  63. $class = $from[0]->getFrom();
  64. // step 2 : retrieve the column id
  65. $idName = current($queryBuilderId->getEntityManager()->getMetadataFactory()->getMetadataFor($class)->getIdentifierFieldNames());
  66. // step 3 : retrieve the different subjects id
  67. $select = sprintf('%s.%s', $queryBuilderId->getRootAlias(), $idName);
  68. $queryBuilderId->resetDQLPart('select');
  69. $queryBuilderId->add('select', 'DISTINCT ' . $select);
  70. //for SELECT DISTINCT, ORDER BY expressions must appear in select list
  71. /* Consider
  72. SELECT DISTINCT x FROM tab ORDER BY y;
  73. For any particular x-value in the table there might be many different y
  74. values. Which one will you use to sort that x-value in the output?
  75. */
  76. // todo : check how doctrine behave, potential SQL injection here ...
  77. if ($this->getSortBy()) {
  78. $sortBy = $this->getSortBy();
  79. if (strpos($sortBy, '.') === false) { // add the current alias
  80. $sortBy = $queryBuilderId->getRootAlias() . '.' . $sortBy;
  81. }
  82. $sortBy .= ' AS __order_by';
  83. $queryBuilderId->addSelect($sortBy);
  84. }
  85. $results = $queryBuilderId->getQuery()->execute(array(), Query::HYDRATE_ARRAY);
  86. $idx = array();
  87. $connection = $queryBuilder->getEntityManager()->getConnection();
  88. foreach ($results as $id) {
  89. $idx[] = $connection->quote($id[$idName]);
  90. }
  91. // step 4 : alter the query to match the targeted ids
  92. if (count($idx) > 0) {
  93. $queryBuilder->andWhere(sprintf('%s IN (%s)', $select, implode(',', $idx)));
  94. $queryBuilder->setMaxResults(null);
  95. $queryBuilder->setFirstResult(null);
  96. }
  97. return $queryBuilder;
  98. }
  99. /**
  100. * {@inheritdoc}
  101. */
  102. public function __call($name, $args)
  103. {
  104. return call_user_func_array(array($this->queryBuilder, $name), $args);
  105. }
  106. /**
  107. * {@inheritdoc}
  108. */
  109. public function __get($name)
  110. {
  111. return $this->queryBuilder->$name;
  112. }
  113. /**
  114. * {@inheritdoc}
  115. */
  116. public function setSortBy($parentAssociationMappings, $fieldMapping)
  117. {
  118. $alias = $this->entityJoin($parentAssociationMappings);
  119. $this->sortBy = $alias . '.' . $fieldMapping['fieldName'];
  120. }
  121. /**
  122. * {@inheritdoc}
  123. */
  124. public function getSortBy()
  125. {
  126. return $this->sortBy;
  127. }
  128. /**
  129. * {@inheritdoc}
  130. */
  131. public function setSortOrder($sortOrder)
  132. {
  133. $this->sortOrder = $sortOrder;
  134. }
  135. /**
  136. * {@inheritdoc}
  137. */
  138. public function getSortOrder()
  139. {
  140. return $this->sortOrder;
  141. }
  142. /**
  143. * {@inheritdoc}
  144. */
  145. public function getSingleScalarResult()
  146. {
  147. $query = $this->queryBuilder->getQuery();
  148. return $query->getSingleScalarResult();
  149. }
  150. /**
  151. * {@inheritdoc}
  152. */
  153. public function __clone()
  154. {
  155. $this->queryBuilder = clone $this->queryBuilder;
  156. }
  157. /**
  158. * @return mixed
  159. */
  160. public function getQueryBuilder()
  161. {
  162. return $this->queryBuilder;
  163. }
  164. /**
  165. * {@inheritdoc}
  166. */
  167. public function setFirstResult($firstResult)
  168. {
  169. $this->queryBuilder->setFirstResult($firstResult);
  170. }
  171. /**
  172. * {@inheritdoc}
  173. */
  174. public function getFirstResult()
  175. {
  176. $this->queryBuilder->getFirstResult();
  177. }
  178. /**
  179. * {@inheritdoc}
  180. */
  181. public function setMaxResults($maxResults)
  182. {
  183. $this->queryBuilder->setMaxResults($maxResults);
  184. }
  185. /**
  186. * {@inheritdoc}
  187. */
  188. public function getMaxResults()
  189. {
  190. $this->queryBuilder->getMaxResults();
  191. }
  192. /**
  193. * {@inheritdoc}
  194. */
  195. public function getUniqueParameterId()
  196. {
  197. return $this->uniqueParameterId++;
  198. }
  199. /**
  200. * {@inheritdoc}
  201. */
  202. public function entityJoin(array $associationMappings)
  203. {
  204. $alias = $this->queryBuilder->getRootAlias();
  205. $newAlias = 's';
  206. foreach ($associationMappings as $associationMapping) {
  207. $newAlias .= '_' . $associationMapping['fieldName'];
  208. if (!in_array($newAlias, $this->entityJoinAliases)) {
  209. $this->entityJoinAliases[] = $newAlias;
  210. $this->queryBuilder->leftJoin(sprintf('%s.%s', $alias, $associationMapping['fieldName']), $newAlias);
  211. }
  212. $alias = $newAlias;
  213. }
  214. return $alias;
  215. }
  216. }