| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- <?php
- /**
- * PHP Version 5.4
- *
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
- * @since CakePHP(tm) v 3.0.0
- * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
- */
- namespace Cake\ORM;
- use Cake\Collection\CollectionTrait;
- use Cake\Database\Exception;
- use Cake\Database\Type;
- use \Countable;
- use \Iterator;
- use \JsonSerializable;
- use \Serializable;
- /**
- * Represents the results obtained after executing a query for an specific table
- * This object is responsible for correctly nesting result keys reported from
- * the query, casting each field to the correct type and executing the extra
- * queries required for eager loading external associations.
- *
- */
- class ResultSet implements Countable, Iterator, Serializable, JsonSerializable {
- use CollectionTrait;
- /**
- * Original query from where results were generated
- *
- * @var Query
- */
- protected $_query;
- /**
- * Database statement holding the results
- *
- * @var \Cake\Database\StatementInterface
- */
- protected $_statement;
- /**
- * Points to the next record number that should be fetched
- *
- * @var integer
- */
- protected $_index = 0;
- /**
- * Last record fetched from the statement
- *
- * @var array
- */
- protected $_current;
- /**
- * Default table instance
- *
- * @var \Cake\ORM\Table
- */
- protected $_defaultTable;
- /**
- * List of associations that should be eager loaded
- *
- * @var array
- */
- protected $_associationMap = [];
- /**
- * Map of fields that are fetched from the statement with
- * their type and the table they belong to
- *
- * @var string
- */
- protected $_map;
- /**
- * Results that have been fetched or hydrated into the results.
- *
- * @var array
- */
- protected $_results = [];
- /**
- * Whether to hydrate results into objects or not
- *
- * @var boolean
- */
- protected $_hydrate = true;
- /**
- * The fully namespaced name of the class to use for hydrating results
- *
- * @var string
- */
- protected $_entityClass;
- /**
- * Whether or not to buffer results fetched from the statement
- *
- * @var boolean
- */
- protected $_useBuffering = true;
- /**
- * Constructor
- *
- * @param Query from where results come
- * @param \Cake\Database\StatementInterface $statement
- * @return void
- */
- public function __construct($query, $statement) {
- $this->_query = $query;
- $this->_statement = $statement;
- $this->_defaultTable = $this->_query->repository();
- $this->_calculateAssociationMap();
- $this->_hydrate = $this->_query->hydrate();
- $this->_entityClass = $query->repository()->entityClass();
- $this->_useBuffering = $query->bufferResults();
- }
- /**
- * Returns the current record in the result iterator
- *
- * Part of Iterator interface.
- *
- * @return array|object
- */
- public function current() {
- return $this->_current;
- }
- /**
- * Returns the key of the current record in the iterator
- *
- * Part of Iterator interface.
- *
- * @return integer
- */
- public function key() {
- return $this->_index;
- }
- /**
- * Advances the iterator pointer to the next record
- *
- * Part of Iterator interface.
- *
- * @return void
- */
- public function next() {
- $this->_index++;
- }
- /**
- * Rewind a ResultSet.
- *
- * Part of Iterator interface.
- *
- * @throws Cake\Database\Exception
- * @return void
- */
- public function rewind() {
- if ($this->_index == 0) {
- return;
- }
- if (!$this->_useBuffering) {
- $msg = 'You cannot rewind an un-buffered ResultSet. Use Query::bufferResults() to get a buffered ResultSet.';
- throw new Exception($msg);
- }
- $this->_index = 0;
- }
- /**
- * Whether there are more results to be fetched from the iterator
- *
- * Part of Iterator interface.
- *
- * @return boolean
- */
- public function valid() {
- if (isset($this->_results[$this->_index])) {
- $this->_current = $this->_results[$this->_index];
- return true;
- }
- $this->_current = $this->_fetchResult();
- $valid = $this->_current !== false;
- if (!$valid && $this->_statement) {
- $this->_statement->closeCursor();
- }
- if ($valid) {
- $this->_bufferResult($this->_current);
- }
- return $valid;
- }
- /**
- * Serialize a resultset.
- *
- * Part of Serializable interface.
- *
- * @return string Serialized object
- */
- public function serialize() {
- while ($this->valid()) {
- $this->next();
- }
- return serialize($this->_results);
- }
- /**
- * Unserialize a resultset.
- *
- * Part of Serializable interface.
- *
- * @param string Serialized object
- */
- public function unserialize($serialized) {
- $this->_results = unserialize($serialized);
- }
- /**
- * Returns the first result in this set and blocks the set so that no other
- * results can be fetched.
- *
- * When using serialized results, the index will be incremented past the
- * end of the results simulating the behavior when the result set is backed
- * by a statement.
- *
- * @return array|object
- */
- public function first() {
- if (isset($this->_results[0])) {
- return $this->_results[0];
- }
- if ($this->valid()) {
- if ($this->_statement) {
- $this->_statement->closeCursor();
- }
- if (!$this->_statement && $this->_results) {
- $this->_index = count($this->_results);
- }
- return $this->_current;
- }
- return null;
- }
- /**
- * Gives the number of rows in the result set.
- *
- * Part of the Countable interface.
- *
- * @return integer
- */
- public function count() {
- if ($this->_statement) {
- return $this->_statement->rowCount();
- }
- return count($this->_results);
- }
- /**
- * Calculates the list of associations that should get eager loaded
- * when fetching each record
- *
- * @return void
- */
- protected function _calculateAssociationMap() {
- $contain = $this->_query->eagerLoader()->normalized($this->_defaultTable);
- if (!$contain) {
- return;
- }
- $map = [];
- $visitor = function($level) use (&$visitor, &$map) {
- foreach ($level as $assoc => $meta) {
- $map[$assoc] = [
- 'instance' => $meta['instance'],
- 'canBeJoined' => $meta['canBeJoined'],
- 'entityClass' => $meta['instance']->target()->entityClass()
- ];
- if (!empty($meta['associations'])) {
- $visitor($meta['associations']);
- }
- }
- };
- $visitor($contain, []);
- $this->_associationMap = $map;
- }
- /**
- * Helper function to fetch the next result from the statement or
- * seeded results.
- *
- * @return mixed
- */
- protected function _fetchResult() {
- if (!$this->_statement) {
- return false;
- }
- $row = $this->_statement->fetch('assoc');
- if ($row === false) {
- return $row;
- }
- return $this->_groupResult($row);
- }
- /**
- * Correctly nest results keys including those coming from associations
- *
- * @param mixed|boolean $row array containing columns and values or false if there is no results
- * @return array
- */
- protected function _groupResult($row) {
- $defaultAlias = $this->_defaultTable->alias();
- $results = [];
- foreach ($row as $key => $value) {
- $table = $defaultAlias;
- $field = $key;
- if (empty($this->_map[$key])) {
- $parts = explode('__', $key);
- if (count($parts) > 1) {
- $this->_map[$key] = $parts;
- }
- }
- if (!empty($this->_map[$key])) {
- list($table, $field) = $this->_map[$key];
- }
- $results[$table][$field] = $value;
- }
- $results[$defaultAlias] = $this->_castValues(
- $this->_defaultTable,
- $results[$defaultAlias]
- );
- $options = [
- 'useSetters' => false,
- 'markClean' => true,
- 'markNew' => false,
- 'guard' => false
- ];
- foreach (array_reverse($this->_associationMap) as $alias => $assoc) {
- if (!isset($results[$alias])) {
- continue;
- }
- $instance = $assoc['instance'];
- $results[$alias] = $this->_castValues($instance->target(), $results[$alias]);
- if ($this->_hydrate && $assoc['canBeJoined']) {
- $entity = new $assoc['entityClass']($results[$alias], $options);
- $entity->clean();
- $results[$alias] = $entity;
- }
- $results = $instance->transformRow($results);
- }
- $results = $results[$defaultAlias];
- if ($this->_hydrate && !($results instanceof Entity)) {
- $results = new $this->_entityClass($results, $options);
- }
- return $results;
- }
- /**
- * Casts all values from a row brought from a table to the correct
- * PHP type.
- *
- * @param Table $table
- * @param array $values
- * @return array
- */
- protected function _castValues($table, $values) {
- $alias = $table->alias();
- $driver = $this->_query->connection()->driver();
- if (empty($this->types[$alias])) {
- $schema = $table->schema();
- foreach ($schema->columns() as $col) {
- $this->types[$alias][$col] = Type::build($schema->columnType($col));
- }
- }
- foreach ($values as $field => $value) {
- if (!isset($this->types[$alias][$field])) {
- continue;
- }
- $values[$field] = $this->types[$alias][$field]->toPHP($value, $driver);
- }
- return $values;
- }
- /**
- * Conditionally buffer the passed result
- *
- * @param array $result the result fetch from the database
- * @return void
- */
- protected function _bufferResult($result) {
- if ($this->_useBuffering) {
- $this->_results[] = $result;
- }
- }
- }
|