Grido@master
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Todo
  • Download

Namespaces

  • Grido
    • Components
      • Actions
      • Columns
      • Filters
    • DataSources
    • PropertyAccessors
    • Translations

Classes

  • Grido\Translations\FileTranslator
  1 <?php
  2 
  3 /**
  4  * This file is part of the Grido (https://github.com/o5/grido)
  5  *
  6  * Copyright (c) 2011 Petr Bugyík (http://petr.bugyik.cz)
  7  *
  8  * For the full copyright and license information, please view
  9  * the file LICENSE.md that was distributed with this source code.
 10  */
 11 
 12 namespace Grido\DataSources;
 13 
 14 use Doctrine\ORM\Tools\Pagination\Paginator;
 15 use Grido\Components\Filters\Condition;
 16 use Nette\Utils\Strings;
 17 
 18 /**
 19  * Doctrine data source.
 20  *
 21  * @package     Grido
 22  * @subpackage  DataSources
 23  * @author      Martin Jantosovic <martin.jantosovic@freya.sk>
 24  * @author      Petr Bugyík
 25  *
 26  * @property-read \Doctrine\ORM\QueryBuilder $qb
 27  * @property-read array $filterMapping
 28  * @property-read array $sortMapping
 29  * @property-read int $count
 30  * @property-read array $data
 31  */
 32 class Doctrine extends \Nette\Object implements IDataSource
 33 {
 34     /** @var \Doctrine\ORM\QueryBuilder */
 35     protected $qb;
 36 
 37     /** @var array Map column to the query builder */
 38     protected $filterMapping;
 39 
 40     /** @var array Map column to the query builder */
 41     protected $sortMapping;
 42 
 43     /** @var bool use OutputWalker in Doctrine Paginator */
 44     protected $useOutputWalkers;
 45 
 46     /** @var bool fetch join collection in Doctrine Paginator */
 47     protected $fetchJoinCollection = TRUE;
 48 
 49     /** @var array */
 50     protected $rand;
 51 
 52     /**
 53      * If $sortMapping is not set and $filterMapping is set,
 54      * $filterMapping will be used also as $sortMapping.
 55      * @param \Doctrine\ORM\QueryBuilder $qb
 56      * @param array $filterMapping Maps columns to the DQL columns
 57      * @param array $sortMapping Maps columns to the DQL columns
 58      */
 59     public function __construct(\Doctrine\ORM\QueryBuilder $qb, $filterMapping = NULL, $sortMapping = NULL)
 60     {
 61         $this->qb = $qb;
 62         $this->filterMapping = $filterMapping;
 63         $this->sortMapping = $sortMapping;
 64 
 65         if (!$this->sortMapping && $this->filterMapping) {
 66             $this->sortMapping = $this->filterMapping;
 67         }
 68     }
 69 
 70     /**
 71      * @param bool $useOutputWalkers
 72      * @return \Grido\DataSources\Doctrine
 73      */
 74     public function setUseOutputWalkers($useOutputWalkers)
 75     {
 76         $this->useOutputWalkers = $useOutputWalkers;
 77         return $this;
 78     }
 79 
 80     /**
 81      * @param bool $fetchJoinCollection
 82      * @return \Grido\DataSources\Doctrine
 83      */
 84     public function setFetchJoinCollection($fetchJoinCollection)
 85     {
 86         $this->fetchJoinCollection = $fetchJoinCollection;
 87         return $this;
 88     }
 89 
 90     /**
 91      * @return \Doctrine\ORM\Query
 92      */
 93     public function getQuery()
 94     {
 95         return $this->qb->getQuery();
 96     }
 97 
 98     /**
 99      * @return \Doctrine\ORM\QueryBuilder
100      */
101     public function getQb()
102     {
103         return $this->qb;
104     }
105 
106     /**
107      * @return array|NULL
108      */
109     public function getFilterMapping()
110     {
111         return $this->filterMapping;
112     }
113 
114     /**
115      * @return array|NULL
116      */
117     public function getSortMapping()
118     {
119         return $this->sortMapping;
120     }
121 
122     /**
123      * @param Condition $condition
124      * @param \Doctrine\ORM\QueryBuilder $qb
125      */
126     protected function makeWhere(Condition $condition, \Doctrine\ORM\QueryBuilder $qb = NULL)
127     {
128         $qb = $qb === NULL
129             ? $this->qb
130             : $qb;
131 
132         if ($condition->callback) {
133             return callback($condition->callback)->invokeArgs(array($condition->value, $qb));
134         }
135 
136         $columns = $condition->column;
137         foreach ($columns as $key => $column) {
138             if (!Condition::isOperator($column)) {
139                 $columns[$key] = (isset($this->filterMapping[$column])
140                     ? $this->filterMapping[$column]
141                     : (Strings::contains($column, ".") ? $column : $this->qb->getRootAlias() . '.' . $column));
142             }
143         }
144 
145         $condition->setColumn($columns);
146         list($where) = $condition->__toArray(NULL, NULL, FALSE);
147 
148         $rand = $this->getRand();
149         $where = preg_replace_callback('/\?/', function() use ($rand) {
150             static $i = -1;
151             $i++;
152             return ":$rand{$i}";
153         }, $where);
154 
155         $qb->andWhere($where);
156 
157         foreach ($condition->getValueForColumn() as $i => $val) {
158             $qb->setParameter("$rand{$i}", $val);
159         }
160     }
161 
162     /**
163      * @return string
164      */
165     protected function getRand()
166     {
167         do {
168             $rand = \Nette\Utils\Strings::random(4, 'a-z');
169         } while (isset($this->rand[$rand]));
170 
171         $this->rand[$rand] = $rand;
172         return $rand;
173     }
174 
175     /*********************************** interface IDataSource ************************************/
176 
177     /**
178      * @return int
179      */
180     public function getCount()
181     {
182         $paginator = new Paginator($this->getQuery(), $this->fetchJoinCollection);
183         $paginator->setUseOutputWalkers($this->useOutputWalkers);
184 
185         return $paginator->count();
186     }
187 
188     /**
189      * It is possible to use query builder with additional columns.
190      * In this case, only item at index [0] is returned, because
191      * it should be an entity object.
192      * @return array
193      */
194     public function getData()
195     {
196         // Paginator is better if the query uses ManyToMany associations
197         $usePaginator = $this->qb->getMaxResults() !== NULL || $this->qb->getFirstResult() !== NULL;
198         $data = array();
199 
200         if ($usePaginator) {
201             $paginator = new Paginator($this->getQuery());
202 
203             // Convert paginator to the array
204             foreach ($paginator as $result) {
205                 // Return only entity itself
206                 $data[] = is_array($result)
207                     ? $result[0]
208                     : $result;
209             }
210         } else {
211 
212             foreach ($this->qb->getQuery()->getResult() as $result) {
213                 // Return only entity itself
214                 $data[] = is_array($result)
215                     ? $result[0]
216                     : $result;
217             }
218         }
219 
220         return $data;
221     }
222 
223     /**
224      * Sets filter.
225      * @param array $conditions
226      */
227     public function filter(array $conditions)
228     {
229         foreach ($conditions as $condition) {
230             $this->makeWhere($condition);
231         }
232     }
233 
234     /**
235      * Sets offset and limit.
236      * @param int $offset
237      * @param int $limit
238      */
239     public function limit($offset, $limit)
240     {
241         $this->qb->setFirstResult($offset)
242             ->setMaxResults($limit);
243     }
244 
245     /**
246      * Sets sorting.
247      * @param array $sorting
248      */
249     public function sort(array $sorting)
250     {
251         foreach ($sorting as $key => $value) {
252             $column = isset($this->sortMapping[$key])
253                 ? $this->sortMapping[$key]
254                 : $this->qb->getRootAlias() . '.' . $key;
255 
256             $this->qb->addOrderBy($column, $value);
257         }
258     }
259 
260     /**
261      * @param mixed $column
262      * @param array $conditions
263      * @param int $limit
264      * @return array
265      * @throws \InvalidArgumentException
266      */
267     public function suggest($column, array $conditions, $limit)
268     {
269         $qb = clone $this->qb;
270         $qb->setMaxResults($limit);
271 
272         if (is_string($column)) {
273             $mapping = isset($this->filterMapping[$column])
274                 ? $this->filterMapping[$column]
275                 : $qb->getRootAlias() . '.' . $column;
276 
277             $qb->select($mapping)->distinct();
278         }
279 
280         foreach ($conditions as $condition) {
281             $this->makeWhere($condition, $qb);
282         }
283 
284         $items = array();
285         $data = $qb->getQuery()->getScalarResult();
286         foreach ($data as $row) {
287             if (is_string($column)) {
288                 $value = (string) current($row);
289             } elseif (is_callable($column)) {
290                 $value = (string) $column($row);
291             } else {
292                 $type = gettype($column);
293                 throw new \InvalidArgumentException("Column of suggestion must be string or callback, $type given.");
294             }
295 
296             $items[$value] = \Nette\Templating\Helpers::escapeHtml($value);
297         }
298 
299         return array_values($items);
300     }
301 }
302 
Grido@master API documentation generated by ApiGen