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