Working with a query result
All the query execution methods return an instance of \sad_spirit\pg_wrapper\Result
,
which encapsulates the native \PgSql\Result.
It uses the the type converter factory (the same one Connection
was configured with) to convert strings in query result to PHP values.
If the query did not return any rows, the object’s only useful method will be getAffectedRows()
,
otherwise it provides several ways to iterate over the result.
Result
public API
Note
Instances of this class are created in Connection
and PreparedStatement
methods.
Its __construct()
method is protected, and its “named constructor” method
createFromReturnValue()
is marked internal and shouldn’t be used outside Connection
and PreparedStatement
.
The public API is as follows
namespace sad_spirit\pg_wrapper;
class Result implements \Iterator, \Countable, \ArrayAccess
{
// metadata getters
public function getAffectedRows(): int;
public function getFieldNames(): string[];
public function getFieldCount(): int;
public function getTableOID(int|string $fieldIndex): int|string|null;
// configuring the returned rows
public function setType(int|string $fieldIndex, mixed $type): $this;
public function setMode(int $mode = \PGSQL_ASSOC): $this;
// fetching all rows as array
public function fetchColumn(int|string $fieldIndex): array;
public function fetchAll(
?int $mode = null,
int|string|null $keyColumn = null,
bool $forceArray = false,
bool $group = false
): array;
// custom iterators
public function iterateColumn(int|string $fieldIndex): \Traversable;
public function iterateNumeric(): \Traversable;
public function iterateAssociative(): \Traversable;
public function iterateKeyedAssociative(?string $keyColumn = null, bool $forceArray = false): \Traversable;
public function iterateKeyedNumeric(int $keyColumn = 0, bool $forceArray = false): \Traversable;
// Implementations of interface methods omitted
}
Implementing Iterator, Countable, and ArrayAccess predefined interfaces allows easy iteration over the query result and access to specific rows:
echo "The query returned " . count($result) . " rows\r\n";
if (count($result) > 0) {
echo "Id of the first row is " . $result[0]['id'] . "\r\n";
}
foreach ($result as $row) {
// Do some stuff
}
Note that ArrayAccess
is implemented read-only for obvious reasons, so trying to do something like
$result[0] = ['foo', 'bar'];
unset($result[1]);
will cause a BadMethodCallException
.
Getting result metadata
public function getAffectedRows(): int
Returns number of rows affected by
INSERT
,UPDATE
, andDELETE
queries.In case of
SELECT
queries this will be equal to whatcount($result)
returns.public function getFieldNames(): string[]
Returns the names of fields (columns) in the result.
public function getFieldCount(): int
Returns the number of fields (columns) in the result.
public function getTableOID(int|string $fieldIndex): int|string|null
Returns the
OID
for a table that contains the given result field.Will return
null
if the field is e.g. a literal or a calculated value.
The methods are pretty self-explanatory except getTableOID()
. It returns what
pg_field_table() with $oid_only = true
would
return for that field except null
is returned instead of false
. The OID being returned is different
from OIDs used by type converter factories as it will be a primary key in pg_class
system table containing rows for database relations, rather than in pg_type
which contains type data.
Knowing the source table for a field can be quite helpful when transforming the result from an array to domain objects.
Configuring row format
public function setMode(int $mode = \PGSQL_ASSOC): $this
Sets how the returned rows are indexed. It accepts either
PGSQL_ASSOC
orPGSQL_NUM
(but notPGSQL_BOTH
) constants used by pg_fetch_row().This affects rows returned either when iterating over the result object with
foreach
or accessing the array offsets of it.public function setType(int|string $fieldIndex, mixed $type): $this
Explicitly sets the type converter for the result field.
Result
uses the type converter factory used by theConnection
, so$type
should be acceptable for that.
Using setMode()
is straightforward:
$result = $connection->executeParams(
'select article_id, article_title from articles where article_id = $1',
[13]
);
$result->setMode(PGSQL_ASSOC);
var_dump($result[0]);
$result->setMode(PGSQL_NUM);
var_dump($result[0]);
with the following output
array(2) {
'article_id' =>
int(13)
'article_title' =>
string(37) "Abusing sad-spirit/pg-wrapper package"
}
array(2) {
[0] =>
int(13)
[1] =>
string(37) "Abusing sad-spirit/pg-wrapper package"
}
It is not generally needed to use setType()
as proper converters are deduced from result metadata,
the exception is columns defined by row constructor.
Getting the whole result as array
public function fetchColumn(int|string $fieldIndex): array
Returns an array containing all values from a given column in the result set.
$fieldIndex
can be either a column name or its 0-based numeric index.public function fetchAll(?int $mode = null, int|string|null $keyColumn = null, bool $forceArray = false, bool $group = false): array
Returns an array containing all rows of the result set.
$mode
can be eitherPGSQL_ASSOC
orPGSQL_NUM
constant specifying how the rows are indexed. Ifnull
, defaults to one set bysetMode()
.$keyColumn
can be either a column name or its 0-based numeric index. If given, values of this column will be used as keys in the outer array.$forceArray
is only useful if$keyColumn
is specified and the query returns exactly two columns. Iffalse
an array of the formkey column value => other column value
is returned. Iftrue
the values will be one element arrays with other column’s values, instead of values directly.$group
is useful when$keyColumn
is specified and its values may be non-unique. Iftrue
, the values in the returned array are wrapped in another array. If there are duplicate values in key column, values of other columns will be appended to this array instead of overwriting previous ones.
fetchColumn()
is straightforward as well as fetchAll()
with default arguments:
$result = $connection->execute('select article_id, article_title from articles order by article_id');
var_dump($result->fetchAll());
var_dump($result->fetchColumn('article_title'));
will output
array(2) {
[0] =>
array(2) {
'article_id' =>
int(12)
'article_title' =>
string(35) "Using sad-spirit/pg-wrapper package"
}
[1] =>
array(2) {
'article_id' =>
int(13)
'article_title' =>
string(37) "Abusing sad-spirit/pg-wrapper package"
}
}
array(2) {
[0] =>
string(35) "Using sad-spirit/pg-wrapper package"
[1] =>
string(37) "Abusing sad-spirit/pg-wrapper package"
}
Using the $keyColumn
argument with fetchAll()
is a bit more tricky:
$result = $connection->execute("select * from (values (1, 'one'), (2, 'two'), (2, 'three')) as v (id, name)");
echo "Default \$forceArray and \$group:\n";
var_dump($result->fetchAll(keyColumn: 'id'));
echo "\n\$forceArray = true:\n";
var_dump($result->fetchAll(keyColumn: 0, forceArray: true));
echo "\n\$group = true:\n";
var_dump($result->fetchAll(keyColumn: 'id', group: true));
echo "\nexplicit mode, \$forceArray = true, \$group = true:\n";
var_dump($result->fetchAll(\PGSQL_NUM, 0, true, true));
outputs
Default $forceArray and $group:
array(2) {
[1]=>
string(3) "one"
[2]=>
string(5) "three"
}
$forceArray = true:
array(2) {
[1]=>
array(1) {
["name"]=>
string(3) "one"
}
[2]=>
array(1) {
["name"]=>
string(5) "three"
}
}
$group = true:
array(2) {
[1]=>
array(1) {
[0]=>
string(3) "one"
}
[2]=>
array(2) {
[0]=>
string(3) "two"
[1]=>
string(5) "three"
}
}
explicit mode, $forceArray = true, $group = true:
array(2) {
[1]=>
array(1) {
[0]=>
array(1) {
[0]=>
string(3) "one"
}
}
[2]=>
array(2) {
[0]=>
array(1) {
[0]=>
string(3) "two"
}
[1]=>
array(1) {
[0]=>
string(5) "three"
}
}
}
Custom iterators
All the below functions are generator ones, using yield
to return rows.
public function iterateColumn(int|string $fieldIndex): \Traversable
Returns an iterator over a single column of the result.
$fieldIndex
is either a column name or its 0-based numeric index.Unless you really need an array of column values, it is recommended to use this rather than
fetchColumn()
, as it doesn’t have to populate an array.public function iterateAssociative(): \Traversable
Returns an iterator over result with values representing result rows as associative arrays.
This is similar to calling
$result->setMode(PGSQL_ASSOC)
and then iterating over$result
withforeach
.public function iterateNumeric(): \Traversable
Returns an iterator over result with values representing result rows as enumerated arrays.
This is similar to calling
$result->setMode(PGSQL_NUM)
and then iterating over$result
withforeach
.public function iterateKeyedAssociative(?string $keyColumn = null, bool $forceArray = false): \Traversable
Returns an iterator over result with keys corresponding to the values of the given column and values representing either the values of the remaining column or the rest of the columns as associative arrays.
$keyColumn
is the column name, ifnull
then the first column will be used.$forceArray
is applicable when the query returns exactly two columns. Iffalse
, the other column’s values will be returned directly, iftrue
they will be wrapped in an array keyed with the column name.public function iterateKeyedNumeric(int $keyColumn = 0, bool $forceArray = false): \Traversable
Returns an iterator over result with keys corresponding to the values of the column with the given index and values representing either the values of the remaining column or the rest of the columns as enumerated arrays.
$keyColumn
is the 0-based numeric index.$forceArray
is applicable when the query returns exactly two columns. Iffalse
the other column’s values will be returned directly, iftrue
they will be wrapped in an array.
It is recommended to use iterateKeyedAssociative()
and iterateKeyedNumeric()
instead of fetchAll()
with
$keyColumn
specified, unless you really need the array returned by the latter. If you just need to iterate,
the behaviour is similar, note the duplicate keys, though:
$result = $connection->execute("select * from (values (1, 'one'), (2, 'two'), (2, 'three')) as v (id, name)");
echo "iterateKeyedAssociative(): \n";
foreach ($result->iterateKeyedAssociative('id') as $k => $v) {
echo "$k => $v\n";
}
echo "\nfetchAll(): \n";
foreach ($result->fetchAll(keyColumn: 'id') as $k => $v) {
echo "$k => $v\n";
}
results in
iterateKeyedAssociative():
1 => one
2 => two
2 => three
fetchAll():
1 => one
2 => three