Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
- New #939: Add `caseSensitive` option to like condition (@vjik)
- New #942: Allow PHP backed enums as values (@Tigrov)
- Enh #943: Add `getCacheKey()` and `getCacheTag()` methods to `AbstractPdoSchema` class (@Tigrov)
- Enh #925: Add callback to `Query::all()` and `Query::one()` methods (@Tigrov)

## 1.3.0 March 21, 2024

Expand Down
3 changes: 3 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace
### New methods

- `QuoterInterface::getRawTableName()` - returns the raw table name without quotes;
- `QueryInterface::resultCallback()` - allows to use a callback, to be called on all rows of the query result;
- `QueryInterface::getResultCallback()` - returns the callback to be called on all rows of the query result or
`null` if the callback is not set;
- `ConnectionInterface::getColumnFactory()` - returns the column factory object for concrete DBMS;
- `ConnectionInterface::getServerInfo()` - returns `ServerInfoInterface` instance which provides server information;
- `QueryBuilderInterface::buildColumnDefinition()` - builds column definition for `CREATE TABLE` statement;
Expand Down
50 changes: 45 additions & 5 deletions src/Query/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
*
* @psalm-import-type SelectValue from QueryPartsInterface
* @psalm-import-type IndexBy from QueryInterface
* @psalm-import-type ResultCallback from QueryInterface
*/
class Query implements QueryInterface
{
Expand All @@ -87,6 +88,8 @@
protected array $join = [];
protected array $orderBy = [];
protected array $params = [];
/** @psalm-var ResultCallback|null $resultCallback */
protected Closure|null $resultCallback = null;
protected array $union = [];
protected array $withQueries = [];
/** @psalm-var IndexBy|null $indexBy */
Expand Down Expand Up @@ -233,7 +236,26 @@
return [];
}

return DbArrayHelper::index($this->createCommand()->queryAll(), $this->indexBy);
$rows = $this->createCommand()->queryAll();

Check warning on line 239 in src/Query/Query.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Query.php#L239

Added line #L239 was not covered by tests

if (empty($rows)) {
return [];

Check warning on line 242 in src/Query/Query.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Query.php#L241-L242

Added lines #L241 - L242 were not covered by tests
}

if ($this->indexBy !== null) {
if (is_string($this->indexBy)) {
$indexes = array_column($rows, $this->indexBy);

Check warning on line 247 in src/Query/Query.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Query.php#L245-L247

Added lines #L245 - L247 were not covered by tests
} else {
$indexes = array_map($this->indexBy, $rows);

Check warning on line 249 in src/Query/Query.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Query.php#L249

Added line #L249 was not covered by tests
}
}

if ($this->resultCallback !== null) {
$rows = ($this->resultCallback)($rows);

Check warning on line 254 in src/Query/Query.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Query.php#L253-L254

Added lines #L253 - L254 were not covered by tests
}

/** @psalm-suppress MixedArgument */
return isset($indexes) ? array_combine($indexes, $rows) : $rows;

Check warning on line 258 in src/Query/Query.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Query.php#L258

Added line #L258 was not covered by tests
}

public function average(string $sql): int|float|null|string
Expand Down Expand Up @@ -437,6 +459,11 @@
return $this->params;
}

public function getResultCallback(): Closure|null
{
return $this->resultCallback;
}

public function getSelect(): array
{
return $this->select;
Expand Down Expand Up @@ -539,10 +566,17 @@

public function one(): array|object|null
{
return match ($this->emulateExecution) {
true => null,
false => $this->createCommand()->queryOne(),
};
if ($this->emulateExecution) {
return null;
}

$row = $this->createCommand()->queryOne();

if ($this->resultCallback === null || $row === null) {
return $row;

Check warning on line 576 in src/Query/Query.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Query.php#L575-L576

Added lines #L575 - L576 were not covered by tests
}

return ($this->resultCallback)([$row])[0];

Check warning on line 579 in src/Query/Query.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Query.php#L579

Added line #L579 was not covered by tests
}

public function orderBy(array|string|ExpressionInterface $columns): static
Expand Down Expand Up @@ -610,6 +644,12 @@
return $this;
}

public function resultCallback(Closure|null $resultCallback): static
{
$this->resultCallback = $resultCallback;
return $this;
}

public function rightJoin(array|string $table, array|string $on = '', array $params = []): static
{
$this->join[] = ['RIGHT JOIN', $table, $on];
Expand Down
30 changes: 30 additions & 0 deletions src/Query/QueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* @psalm-type IndexBy = Closure(array):array-key|string
* @psalm-import-type ParamsType from ConnectionInterface
* @psalm-import-type SelectValue from QueryPartsInterface
* @psalm-type ResultCallback = Closure(non-empty-array<array>):non-empty-array<array|object>
*/
interface QueryInterface extends ExpressionInterface, QueryPartsInterface, QueryFunctionsInterface
{
Expand Down Expand Up @@ -210,6 +211,14 @@ public function getOrderBy(): array;
*/
public function getParams(): array;

/**
* Returns the callback to be called on all rows of the query result.
* `null` will be returned if the callback is not set.
*
* @psalm-return ResultCallback|null
*/
public function getResultCallback(): Closure|null;

/**
* @return array The "select" value.
* @psalm-return SelectValue
Expand Down Expand Up @@ -288,6 +297,27 @@ public function params(array $params): static;
*/
public function prepare(QueryBuilderInterface $builder): self;

/**
* Sets the callback, to be called on all rows of the query result before returning them.
*
* For example:
*
* ```php
* $users = (new Query($db))
* ->from('user')
* ->resultCallback(function (array $rows): array {
* foreach ($rows as &$row) {
* $row['name'] = strtoupper($row['name']);
* }
* return $rows;
* })
* ->all();
* ```
*
* @psalm-param ResultCallback|null $resultCallback
*/
public function resultCallback(Closure|null $resultCallback): static;

/**
* Returns the query results as a scalar value.
* The value returned will be the first column in the first row of the query results.
Expand Down
18 changes: 18 additions & 0 deletions tests/AbstractQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Yiisoft\Db\Tests;

use Closure;
use PHPUnit\Framework\TestCase;
use Throwable;
use Yiisoft\Db\Exception\Exception;
Expand Down Expand Up @@ -824,4 +825,21 @@ public function testCountGreaterThanPhpIntMax(): void

$this->assertSame('12345678901234567890', $query->count());
}

public function testResultCallback(): void
{
$db = $this->getConnection();

$query = (new Query($db));

$this->assertNull($query->getResultCallback());

$query->resultCallback(fn (array $rows) => array_map(fn (array $row) => (object) $row, $rows));

$this->assertInstanceOf(Closure::class, $query->getResultCallback());

$query->resultCallback(null);

$this->assertNull($query->getResultCallback());
}
}
59 changes: 59 additions & 0 deletions tests/Common/CommonQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,38 @@
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\Tests\AbstractQueryTest;

use function array_keys;

abstract class CommonQueryTest extends AbstractQueryTest
{
public function testAllEmpty(): void
{
$db = $this->getConnection(true);

$query = (new Query($db))->from('customer')->where(['id' => 0]);

$this->assertSame([], $query->all());
}

public function testAllWithIndexBy(): void
{
$db = $this->getConnection(true);

$query = (new Query($db))
->from('customer')
->indexBy('name');

$this->assertSame(['user1', 'user2', 'user3'], array_keys($query->all()));

$query = (new Query($db))
->from('customer')
->indexBy(fn (array $row) => $row['id'] * 2);

$this->assertSame([2, 4, 6], array_keys($query->all()));

$db->close();
}

public function testColumnIndexByWithClosure()
{
$db = $this->getConnection(true);
Expand Down Expand Up @@ -83,6 +113,35 @@ public function testSelectWithoutFrom()
$db->close();
}

public function testCallbackAll(): void
{
$db = $this->getConnection(true);

$query = (new Query($db))
->from('customer')
->resultCallback(fn (array $rows) => array_map(fn (array $row) => (object) $row, $rows));

foreach ($query->all() as $row) {
$this->assertIsObject($row);
}

$db->close();
}

public function testCallbackOne(): void
{
$db = $this->getConnection(true);

$query = (new Query($db))
->from('customer')
->where(['id' => 2])
->resultCallback(fn (array $rows) => [(object) $rows[0]]);

$this->assertIsObject($query->one());

$db->close();
}

public function testLikeDefaultCaseSensitive(): void
{
$db = $this->getConnection(true);
Expand Down