QueryTrait.php 18 KB

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