ci4
1<?php
2
3declare(strict_types=1);4
5/**
6* This file is part of CodeIgniter 4 framework.
7*
8* (c) CodeIgniter Foundation <admin@codeigniter.com>
9*
10* For the full copyright and license information, please view
11* the LICENSE file that was distributed with this source code.
12*/
13
14namespace CodeIgniter\Database;15
16use CodeIgniter\Entity\Entity;17use stdClass;18
19/**
20* @template TConnection
21* @template TResult
22*
23* @implements ResultInterface<TConnection, TResult>
24*/
25abstract class BaseResult implements ResultInterface26{
27/**28* Connection ID
29*
30* @var object|resource
31* @phpstan-var TConnection
32*/
33public $connID;34
35/**36* Result ID
37*
38* @var false|object|resource
39* @phpstan-var false|TResult
40*/
41public $resultID;42
43/**44* Result Array
45*
46* @var list<array>
47*/
48public $resultArray = [];49
50/**51* Result Object
52*
53* @var list<object>
54*/
55public $resultObject = [];56
57/**58* Custom Result Object
59*
60* @var array
61*/
62public $customResultObject = [];63
64/**65* Current Row index
66*
67* @var int
68*/
69public $currentRow = 0;70
71/**72* The number of records in the query result
73*
74* @var int|null
75*/
76protected $numRows;77
78/**79* Row data
80*
81* @var array|null
82*/
83public $rowData;84
85/**86* Constructor
87*
88* @param object|resource $connID
89* @param object|resource $resultID
90* @phpstan-param TConnection $connID
91* @phpstan-param TResult $resultID
92*/
93public function __construct(&$connID, &$resultID)94{95$this->connID = $connID;96$this->resultID = $resultID;97}98
99/**100* Retrieve the results of the query. Typically an array of
101* individual data rows, which can be either an 'array', an
102* 'object', or a custom class name.
103*
104* @param string $type The row type. Either 'array', 'object', or a class name to use
105*/
106public function getResult(string $type = 'object'): array107{108if ($type === 'array') {109return $this->getResultArray();110}111
112if ($type === 'object') {113return $this->getResultObject();114}115
116return $this->getCustomResultObject($type);117}118
119/**120* Returns the results as an array of custom objects.
121*
122* @phpstan-param class-string $className
123*
124* @return array
125*/
126public function getCustomResultObject(string $className)127{128if (isset($this->customResultObject[$className])) {129return $this->customResultObject[$className];130}131
132if (! $this->isValidResultId()) {133return [];134}135
136// Don't fetch the result set again if we already have it137$_data = null;138if (($c = count($this->resultArray)) > 0) {139$_data = 'resultArray';140} elseif (($c = count($this->resultObject)) > 0) {141$_data = 'resultObject';142}143
144if ($_data !== null) {145for ($i = 0; $i < $c; $i++) {146$this->customResultObject[$className][$i] = new $className();147
148foreach ($this->{$_data}[$i] as $key => $value) {149$this->customResultObject[$className][$i]->{$key} = $value;150}151}152
153return $this->customResultObject[$className];154}155
156if ($this->rowData !== null) {157$this->dataSeek();158}159$this->customResultObject[$className] = [];160
161while ($row = $this->fetchObject($className)) {162if (! is_subclass_of($row, Entity::class) && method_exists($row, 'syncOriginal')) {163$row->syncOriginal();164}165
166$this->customResultObject[$className][] = $row;167}168
169return $this->customResultObject[$className];170}171
172/**173* Returns the results as an array of arrays.
174*
175* If no results, an empty array is returned.
176*/
177public function getResultArray(): array178{179if ($this->resultArray !== []) {180return $this->resultArray;181}182
183// In the event that query caching is on, the result_id variable184// will not be a valid resource so we'll simply return an empty185// array.186if (! $this->isValidResultId()) {187return [];188}189
190if ($this->resultObject !== []) {191foreach ($this->resultObject as $row) {192$this->resultArray[] = (array) $row;193}194
195return $this->resultArray;196}197
198if ($this->rowData !== null) {199$this->dataSeek();200}201
202while ($row = $this->fetchAssoc()) {203$this->resultArray[] = $row;204}205
206return $this->resultArray;207}208
209/**210* Returns the results as an array of objects.
211*
212* If no results, an empty array is returned.
213*
214* @return array<int, stdClass>
215* @phpstan-return list<stdClass>
216*/
217public function getResultObject(): array218{219if ($this->resultObject !== []) {220return $this->resultObject;221}222
223// In the event that query caching is on, the result_id variable224// will not be a valid resource so we'll simply return an empty225// array.226if (! $this->isValidResultId()) {227return [];228}229
230if ($this->resultArray !== []) {231foreach ($this->resultArray as $row) {232$this->resultObject[] = (object) $row;233}234
235return $this->resultObject;236}237
238if ($this->rowData !== null) {239$this->dataSeek();240}241
242while ($row = $this->fetchObject()) {243if (! is_subclass_of($row, Entity::class) && method_exists($row, 'syncOriginal')) {244$row->syncOriginal();245}246
247$this->resultObject[] = $row;248}249
250return $this->resultObject;251}252
253/**254* Wrapper object to return a row as either an array, an object, or
255* a custom class.
256*
257* If the row doesn't exist, returns null.
258*
259* @template T of object
260*
261* @param int|string $n The index of the results to return, or column name.
262* @param string $type The type of result object. 'array', 'object' or class name.
263* @phpstan-param class-string<T>|'array'|'object' $type
264*
265* @return array|float|int|object|stdClass|string|null
266* @phpstan-return ($n is string ? float|int|string|null : ($type is 'object' ? stdClass|null : ($type is 'array' ? array|null : T|null)))
267*/
268public function getRow($n = 0, string $type = 'object')269{270// $n is a column name.271if (! is_numeric($n)) {272// We cache the row data for subsequent uses273if (! is_array($this->rowData)) {274$this->rowData = $this->getRowArray();275}276
277// array_key_exists() instead of isset() to allow for NULL values278if (empty($this->rowData) || ! array_key_exists($n, $this->rowData)) {279return null;280}281
282return $this->rowData[$n];283}284
285if ($type === 'object') {286return $this->getRowObject($n);287}288
289if ($type === 'array') {290return $this->getRowArray($n);291}292
293return $this->getCustomRowObject($n, $type);294}295
296/**297* Returns a row as a custom class instance.
298*
299* If the row doesn't exist, returns null.
300*
301* @template T of object
302*
303* @param int $n The index of the results to return.
304* @phpstan-param class-string<T> $className
305*
306* @return object|null
307* @phpstan-return T|null
308*/
309public function getCustomRowObject(int $n, string $className)310{311if (! isset($this->customResultObject[$className])) {312$this->getCustomResultObject($className);313}314
315if (empty($this->customResultObject[$className])) {316return null;317}318
319if ($n !== $this->currentRow && isset($this->customResultObject[$className][$n])) {320$this->currentRow = $n;321}322
323return $this->customResultObject[$className][$this->currentRow];324}325
326/**327* Returns a single row from the results as an array.
328*
329* If row doesn't exist, returns null.
330*
331* @return array|null
332*/
333public function getRowArray(int $n = 0)334{335$result = $this->getResultArray();336if ($result === []) {337return null;338}339
340if ($n !== $this->currentRow && isset($result[$n])) {341$this->currentRow = $n;342}343
344return $result[$this->currentRow];345}346
347/**348* Returns a single row from the results as an object.
349*
350* If row doesn't exist, returns null.
351*
352* @return object|stdClass|null
353*/
354public function getRowObject(int $n = 0)355{356$result = $this->getResultObject();357if ($result === []) {358return null;359}360
361if ($n !== $this->customResultObject && isset($result[$n])) {362$this->currentRow = $n;363}364
365return $result[$this->currentRow];366}367
368/**369* Assigns an item into a particular column slot.
370*
371* @param array|string $key
372* @param array|object|stdClass|null $value
373*
374* @return void
375*/
376public function setRow($key, $value = null)377{378// We cache the row data for subsequent uses379if (! is_array($this->rowData)) {380$this->rowData = $this->getRowArray();381}382
383if (is_array($key)) {384foreach ($key as $k => $v) {385$this->rowData[$k] = $v;386}387
388return;389}390
391if ($key !== '' && $value !== null) {392$this->rowData[$key] = $value;393}394}395
396/**397* Returns the "first" row of the current results.
398*
399* @return array|object|null
400*/
401public function getFirstRow(string $type = 'object')402{403$result = $this->getResult($type);404
405return ($result === []) ? null : $result[0];406}407
408/**409* Returns the "last" row of the current results.
410*
411* @return array|object|null
412*/
413public function getLastRow(string $type = 'object')414{415$result = $this->getResult($type);416
417return ($result === []) ? null : $result[count($result) - 1];418}419
420/**421* Returns the "next" row of the current results.
422*
423* @return array|object|null
424*/
425public function getNextRow(string $type = 'object')426{427$result = $this->getResult($type);428if ($result === []) {429return null;430}431
432return isset($result[$this->currentRow + 1]) ? $result[++$this->currentRow] : null;433}434
435/**436* Returns the "previous" row of the current results.
437*
438* @return array|object|null
439*/
440public function getPreviousRow(string $type = 'object')441{442$result = $this->getResult($type);443if ($result === []) {444return null;445}446
447if (isset($result[$this->currentRow - 1])) {448$this->currentRow--;449}450
451return $result[$this->currentRow];452}453
454/**455* Returns an unbuffered row and move the pointer to the next row.
456*
457* @return array|object|null
458*/
459public function getUnbufferedRow(string $type = 'object')460{461if ($type === 'array') {462return $this->fetchAssoc();463}464
465if ($type === 'object') {466return $this->fetchObject();467}468
469return $this->fetchObject($type);470}471
472/**473* Number of rows in the result set; checks for previous count, falls
474* back on counting resultArray or resultObject, finally fetching resultArray
475* if nothing was previously fetched
476*/
477public function getNumRows(): int478{479if (is_int($this->numRows)) {480return $this->numRows;481}482if ($this->resultArray !== []) {483return $this->numRows = count($this->resultArray);484}485if ($this->resultObject !== []) {486return $this->numRows = count($this->resultObject);487}488
489return $this->numRows = count($this->getResultArray());490}491
492private function isValidResultId(): bool493{494return is_resource($this->resultID) || is_object($this->resultID);495}496
497/**498* Gets the number of fields in the result set.
499*/
500abstract public function getFieldCount(): int;501
502/**503* Generates an array of column names in the result set.
504*/
505abstract public function getFieldNames(): array;506
507/**508* Generates an array of objects representing field meta-data.
509*/
510abstract public function getFieldData(): array;511
512/**513* Frees the current result.
514*
515* @return void
516*/
517abstract public function freeResult();518
519/**520* Moves the internal pointer to the desired offset. This is called
521* internally before fetching results to make sure the result set
522* starts at zero.
523*
524* @return bool
525*/
526abstract public function dataSeek(int $n = 0);527
528/**529* Returns the result set as an array.
530*
531* Overridden by driver classes.
532*
533* @return array|false|null
534*/
535abstract protected function fetchAssoc();536
537/**538* Returns the result set as an object.
539*
540* Overridden by child classes.
541*
542* @return Entity|false|object|stdClass
543*/
544abstract protected function fetchObject(string $className = 'stdClass');545}
546