1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 3.0.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Datasource;
16:
17: use BadMethodCallException;
18: use Cake\Collection\Iterator\MapReduce;
19: use Cake\Datasource\Exception\RecordNotFoundException;
20:
21: /**
22: * Contains the characteristics for an object that is attached to a repository and
23: * can retrieve results based on any criteria.
24: */
25: trait QueryTrait
26: {
27: /**
28: * Instance of a table object this query is bound to
29: *
30: * @var \Cake\Datasource\RepositoryInterface
31: */
32: protected $_repository;
33:
34: /**
35: * A ResultSet.
36: *
37: * When set, query execution will be bypassed.
38: *
39: * @var \Cake\Datasource\ResultSetInterface|null
40: * @see \Cake\Datasource\QueryTrait::setResult()
41: */
42: protected $_results;
43:
44: /**
45: * List of map-reduce routines that should be applied over the query
46: * result
47: *
48: * @var array
49: */
50: protected $_mapReduce = [];
51:
52: /**
53: * List of formatter classes or callbacks that will post-process the
54: * results when fetched
55: *
56: * @var callable[]
57: */
58: protected $_formatters = [];
59:
60: /**
61: * A query cacher instance if this query has caching enabled.
62: *
63: * @var \Cake\Datasource\QueryCacher|null
64: */
65: protected $_cache;
66:
67: /**
68: * Holds any custom options passed using applyOptions that could not be processed
69: * by any method in this class.
70: *
71: * @var array
72: */
73: protected $_options = [];
74:
75: /**
76: * Whether the query is standalone or the product of an eager load operation.
77: *
78: * @var bool
79: */
80: protected $_eagerLoaded = false;
81:
82: /**
83: * Returns the default table object that will be used by this query,
84: * that is, the table that will appear in the from clause.
85: *
86: * When called with a Table argument, the default table object will be set
87: * and this query object will be returned for chaining.
88: *
89: * @param \Cake\Datasource\RepositoryInterface|null $table The default table object to use
90: * @return \Cake\Datasource\RepositoryInterface|$this
91: */
92: public function repository(RepositoryInterface $table = null)
93: {
94: if ($table === null) {
95: deprecationWarning(
96: 'Using Query::repository() as getter is deprecated. ' .
97: 'Use getRepository() instead.'
98: );
99:
100: return $this->getRepository();
101: }
102:
103: $this->_repository = $table;
104:
105: return $this;
106: }
107:
108: /**
109: * Returns the default table object that will be used by this query,
110: * that is, the table that will appear in the from clause.
111: *
112: * @return \Cake\Datasource\RepositoryInterface
113: */
114: public function getRepository()
115: {
116: return $this->_repository;
117: }
118:
119: /**
120: * Set the result set for a query.
121: *
122: * Setting the resultset of a query will make execute() a no-op. Instead
123: * of executing the SQL query and fetching results, the ResultSet provided to this
124: * method will be returned.
125: *
126: * This method is most useful when combined with results stored in a persistent cache.
127: *
128: * @param \Cake\Datasource\ResultSetInterface $results The results this query should return.
129: * @return $this
130: */
131: public function setResult($results)
132: {
133: $this->_results = $results;
134:
135: return $this;
136: }
137:
138: /**
139: * Executes this query and returns a results iterator. This function is required
140: * for implementing the IteratorAggregate interface and allows the query to be
141: * iterated without having to call execute() manually, thus making it look like
142: * a result set instead of the query itself.
143: *
144: * @return \Iterator
145: */
146: public function getIterator()
147: {
148: return $this->all();
149: }
150:
151: /**
152: * Enable result caching for this query.
153: *
154: * If a query has caching enabled, it will do the following when executed:
155: *
156: * - Check the cache for $key. If there are results no SQL will be executed.
157: * Instead the cached results will be returned.
158: * - When the cached data is stale/missing the result set will be cached as the query
159: * is executed.
160: *
161: * ### Usage
162: *
163: * ```
164: * // Simple string key + config
165: * $query->cache('my_key', 'db_results');
166: *
167: * // Function to generate key.
168: * $query->cache(function ($q) {
169: * $key = serialize($q->clause('select'));
170: * $key .= serialize($q->clause('where'));
171: * return md5($key);
172: * });
173: *
174: * // Using a pre-built cache engine.
175: * $query->cache('my_key', $engine);
176: *
177: * // Disable caching
178: * $query->cache(false);
179: * ```
180: *
181: * @param \Closure|string|false $key Either the cache key or a function to generate the cache key.
182: * When using a function, this query instance will be supplied as an argument.
183: * @param string|\Cake\Cache\CacheEngine $config Either the name of the cache config to use, or
184: * a cache config instance.
185: * @return $this
186: */
187: public function cache($key, $config = 'default')
188: {
189: if ($key === false) {
190: $this->_cache = null;
191:
192: return $this;
193: }
194: $this->_cache = new QueryCacher($key, $config);
195:
196: return $this;
197: }
198:
199: /**
200: * Returns the current configured query `_eagerLoaded` value
201: *
202: * @return bool
203: */
204: public function isEagerLoaded()
205: {
206: return $this->_eagerLoaded;
207: }
208:
209: /**
210: * Sets the query instance to be an eager loaded query. If no argument is
211: * passed, the current configured query `_eagerLoaded` value is returned.
212: *
213: * @deprecated 3.5.0 Use isEagerLoaded() for the getter part instead.
214: * @param bool|null $value Whether or not to eager load.
215: * @return $this|bool
216: */
217: public function eagerLoaded($value = null)
218: {
219: if ($value === null) {
220: deprecationWarning(
221: 'Using ' . get_called_class() . '::eagerLoaded() as a getter is deprecated. ' .
222: 'Use isEagerLoaded() instead.'
223: );
224:
225: return $this->_eagerLoaded;
226: }
227: $this->_eagerLoaded = $value;
228:
229: return $this;
230: }
231:
232: /**
233: * Returns a key => value array representing a single aliased field
234: * that can be passed directly to the select() method.
235: * The key will contain the alias and the value the actual field name.
236: *
237: * If the field is already aliased, then it will not be changed.
238: * If no $alias is passed, the default table for this query will be used.
239: *
240: * @param string $field The field to alias
241: * @param string|null $alias the alias used to prefix the field
242: * @return array
243: */
244: public function aliasField($field, $alias = null)
245: {
246: $namespaced = strpos($field, '.') !== false;
247: $aliasedField = $field;
248:
249: if ($namespaced) {
250: list($alias, $field) = explode('.', $field);
251: }
252:
253: if (!$alias) {
254: $alias = $this->getRepository()->getAlias();
255: }
256:
257: $key = sprintf('%s__%s', $alias, $field);
258: if (!$namespaced) {
259: $aliasedField = $alias . '.' . $field;
260: }
261:
262: return [$key => $aliasedField];
263: }
264:
265: /**
266: * Runs `aliasField()` for each field in the provided list and returns
267: * the result under a single array.
268: *
269: * @param array $fields The fields to alias
270: * @param string|null $defaultAlias The default alias
271: * @return array
272: */
273: public function aliasFields($fields, $defaultAlias = null)
274: {
275: $aliased = [];
276: foreach ($fields as $alias => $field) {
277: if (is_numeric($alias) && is_string($field)) {
278: $aliased += $this->aliasField($field, $defaultAlias);
279: continue;
280: }
281: $aliased[$alias] = $field;
282: }
283:
284: return $aliased;
285: }
286:
287: /**
288: * Fetch the results for this query.
289: *
290: * Will return either the results set through setResult(), or execute this query
291: * and return the ResultSetDecorator object ready for streaming of results.
292: *
293: * ResultSetDecorator is a traversable object that implements the methods found
294: * on Cake\Collection\Collection.
295: *
296: * @return \Cake\Datasource\ResultSetInterface
297: */
298: public function all()
299: {
300: if ($this->_results !== null) {
301: return $this->_results;
302: }
303:
304: if ($this->_cache) {
305: $results = $this->_cache->fetch($this);
306: }
307: if (!isset($results)) {
308: $results = $this->_decorateResults($this->_execute());
309: if ($this->_cache) {
310: $this->_cache->store($this, $results);
311: }
312: }
313: $this->_results = $results;
314:
315: return $this->_results;
316: }
317:
318: /**
319: * Returns an array representation of the results after executing the query.
320: *
321: * @return array
322: */
323: public function toArray()
324: {
325: return $this->all()->toArray();
326: }
327:
328: /**
329: * Register a new MapReduce routine to be executed on top of the database results
330: * Both the mapper and caller callable should be invokable objects.
331: *
332: * The MapReduce routing will only be run when the query is executed and the first
333: * result is attempted to be fetched.
334: *
335: * If the first argument is set to null, it will return the list of previously
336: * registered map reduce routines. This is deprecated as of 3.6.0 - use getMapReducers() instead.
337: *
338: * If the third argument is set to true, it will erase previous map reducers
339: * and replace it with the arguments passed.
340: *
341: * @param callable|null $mapper The mapper callable.
342: * @param callable|null $reducer The reducing function.
343: * @param bool $overwrite Set to true to overwrite existing map + reduce functions.
344: * @return $this|array
345: * @see \Cake\Collection\Iterator\MapReduce for details on how to use emit data to the map reducer.
346: */
347: public function mapReduce(callable $mapper = null, callable $reducer = null, $overwrite = false)
348: {
349: if ($overwrite) {
350: $this->_mapReduce = [];
351: }
352: if ($mapper === null) {
353: if (!$overwrite) {
354: deprecationWarning(
355: 'Using QueryTrait::mapReduce() as a getter is deprecated. ' .
356: 'Use getMapReducers() instead.'
357: );
358: }
359:
360: return $this->_mapReduce;
361: }
362: $this->_mapReduce[] = compact('mapper', 'reducer');
363:
364: return $this;
365: }
366:
367: /**
368: * Returns the list of previously registered map reduce routines.
369: *
370: * @return array
371: */
372: public function getMapReducers()
373: {
374: return $this->_mapReduce;
375: }
376:
377: /**
378: * Registers a new formatter callback function that is to be executed when trying
379: * to fetch the results from the database.
380: *
381: * Formatting callbacks will get a first parameter, an object implementing
382: * `\Cake\Collection\CollectionInterface`, that can be traversed and modified at will.
383: *
384: * Callbacks are required to return an iterator object, which will be used as
385: * the return value for this query's result. Formatter functions are applied
386: * after all the `MapReduce` routines for this query have been executed.
387: *
388: * If the first argument is set to null, it will return the list of previously
389: * registered format routines. This is deprecated as of 3.6.0 - use getResultFormatters() instead.
390: *
391: * If the second argument is set to true, it will erase previous formatters
392: * and replace them with the passed first argument.
393: *
394: * ### Example:
395: *
396: * ```
397: * // Return all results from the table indexed by id
398: * $query->select(['id', 'name'])->formatResults(function ($results) {
399: * return $results->indexBy('id');
400: * });
401: *
402: * // Add a new column to the ResultSet
403: * $query->select(['name', 'birth_date'])->formatResults(function ($results) {
404: * return $results->map(function ($row) {
405: * $row['age'] = $row['birth_date']->diff(new DateTime)->y;
406: * return $row;
407: * });
408: * });
409: * ```
410: *
411: * @param callable|null $formatter The formatting callable.
412: * @param bool|int $mode Whether or not to overwrite, append or prepend the formatter.
413: * @return $this|array
414: */
415: public function formatResults(callable $formatter = null, $mode = 0)
416: {
417: if ($mode === self::OVERWRITE) {
418: $this->_formatters = [];
419: }
420: if ($formatter === null) {
421: if ($mode !== self::OVERWRITE) {
422: deprecationWarning(
423: 'Using QueryTrait::formatResults() as a getter is deprecated. ' .
424: 'Use getResultFormatters() instead.'
425: );
426: }
427:
428: return $this->_formatters;
429: }
430:
431: if ($mode === self::PREPEND) {
432: array_unshift($this->_formatters, $formatter);
433:
434: return $this;
435: }
436:
437: $this->_formatters[] = $formatter;
438:
439: return $this;
440: }
441:
442: /**
443: * Returns the list of previously registered format routines.
444: *
445: * @return array
446: */
447: public function getResultFormatters()
448: {
449: return $this->_formatters;
450: }
451:
452: /**
453: * Returns the first result out of executing this query, if the query has not been
454: * executed before, it will set the limit clause to 1 for performance reasons.
455: *
456: * ### Example:
457: *
458: * ```
459: * $singleUser = $query->select(['id', 'username'])->first();
460: * ```
461: *
462: * @return \Cake\Datasource\EntityInterface|array|null The first result from the ResultSet.
463: */
464: public function first()
465: {
466: if ($this->_dirty) {
467: $this->limit(1);
468: }
469:
470: return $this->all()->first();
471: }
472:
473: /**
474: * Get the first result from the executing query or raise an exception.
475: *
476: * @throws \Cake\Datasource\Exception\RecordNotFoundException When there is no first record.
477: * @return \Cake\Datasource\EntityInterface|array The first result from the ResultSet.
478: */
479: public function firstOrFail()
480: {
481: $entity = $this->first();
482: if (!$entity) {
483: /** @var \Cake\ORM\Table $table */
484: $table = $this->getRepository();
485: throw new RecordNotFoundException(sprintf(
486: 'Record not found in table "%s"',
487: $table->getTable()
488: ));
489: }
490:
491: return $entity;
492: }
493:
494: /**
495: * Returns an array with the custom options that were applied to this query
496: * and that were not already processed by another method in this class.
497: *
498: * ### Example:
499: *
500: * ```
501: * $query->applyOptions(['doABarrelRoll' => true, 'fields' => ['id', 'name']);
502: * $query->getOptions(); // Returns ['doABarrelRoll' => true]
503: * ```
504: *
505: * @see \Cake\Datasource\QueryInterface::applyOptions() to read about the options that will
506: * be processed by this class and not returned by this function
507: * @return array
508: */
509: public function getOptions()
510: {
511: return $this->_options;
512: }
513:
514: /**
515: * Enables calling methods from the result set as if they were from this class
516: *
517: * @param string $method the method to call
518: * @param array $arguments list of arguments for the method to call
519: * @return mixed
520: * @throws \BadMethodCallException if no such method exists in result set
521: */
522: public function __call($method, $arguments)
523: {
524: $resultSetClass = $this->_decoratorClass();
525: if (in_array($method, get_class_methods($resultSetClass))) {
526: $results = $this->all();
527:
528: return $results->$method(...$arguments);
529: }
530: throw new BadMethodCallException(
531: sprintf('Unknown method "%s"', $method)
532: );
533: }
534:
535: /**
536: * Populates or adds parts to current query clauses using an array.
537: * This is handy for passing all query clauses at once.
538: *
539: * @param array $options the options to be applied
540: * @return $this
541: */
542: abstract public function applyOptions(array $options);
543:
544: /**
545: * Executes this query and returns a traversable object containing the results
546: *
547: * @return \Traversable
548: */
549: abstract protected function _execute();
550:
551: /**
552: * Decorates the results iterator with MapReduce routines and formatters
553: *
554: * @param \Traversable $result Original results
555: * @return \Cake\Datasource\ResultSetInterface
556: */
557: protected function _decorateResults($result)
558: {
559: $decorator = $this->_decoratorClass();
560: foreach ($this->_mapReduce as $functions) {
561: $result = new MapReduce($result, $functions['mapper'], $functions['reducer']);
562: }
563:
564: if (!empty($this->_mapReduce)) {
565: $result = new $decorator($result);
566: }
567:
568: foreach ($this->_formatters as $formatter) {
569: $result = $formatter($result);
570: }
571:
572: if (!empty($this->_formatters) && !($result instanceof $decorator)) {
573: $result = new $decorator($result);
574: }
575:
576: return $result;
577: }
578:
579: /**
580: * Returns the name of the class to be used for decorating results
581: *
582: * @return string
583: */
584: protected function _decoratorClass()
585: {
586: return ResultSetDecorator::class;
587: }
588: }
589: