ProxyQuery.php 6.4 KB

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