Skip to content

Commit

Permalink
PHPORM-230 Convert DateTimeInterface to UTCDateTime in queries (#3105)
Browse files Browse the repository at this point in the history
* PHPORM-230 Convert DateTimeInterface to UTCDateTime in queries

* Alias id to _id in subdocuments
  • Loading branch information
GromNaN authored Aug 26, 2024
1 parent d2c6de9 commit bd9ef30
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 51 deletions.
65 changes: 21 additions & 44 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
use function array_map;
use function array_merge;
use function array_values;
use function array_walk_recursive;
use function assert;
use function blank;
use function call_user_func;
Expand Down Expand Up @@ -689,17 +688,7 @@ public function insert(array $values)
$values = [$values];
}

// Compatibility with Eloquent queries that uses "id" instead of MongoDB's _id
foreach ($values as &$document) {
if (isset($document['id'])) {
if (isset($document['_id']) && $document['_id'] !== $document['id']) {
throw new InvalidArgumentException('Cannot insert document with different "id" and "_id" values');
}

$document['_id'] = $document['id'];
unset($document['id']);
}
}
$values = $this->aliasIdForQuery($values);

$options = $this->inheritConnectionOptions();

Expand Down Expand Up @@ -876,6 +865,7 @@ public function delete($id = null)
}

$wheres = $this->compileWheres();
$wheres = $this->aliasIdForQuery($wheres);
$options = $this->inheritConnectionOptions();

if (is_int($this->limit)) {
Expand Down Expand Up @@ -1070,16 +1060,12 @@ protected function performUpdate(array $update, array $options = [])
$options['multiple'] = true;
}

// Since "id" is an alias for "_id", we prevent updating it
foreach ($update as $operator => $fields) {
if (array_key_exists('id', $fields)) {
throw new InvalidArgumentException('Cannot update "id" field.');
}
}
$update = $this->aliasIdForQuery($update);

$options = $this->inheritConnectionOptions($options);

$wheres = $this->compileWheres();
$wheres = $this->aliasIdForQuery($wheres);
$result = $this->collection->updateMany($wheres, $update, $options);
if ($result->isAcknowledged()) {
return $result->getModifiedCount() ? $result->getModifiedCount() : $result->getUpsertedCount();
Expand Down Expand Up @@ -1191,32 +1177,12 @@ protected function compileWheres(): array
}
}

// Convert DateTime values to UTCDateTime.
if (isset($where['value'])) {
if (is_array($where['value'])) {
array_walk_recursive($where['value'], function (&$item, $key) {
if ($item instanceof DateTimeInterface) {
$item = new UTCDateTime($item);
}
});
} else {
if ($where['value'] instanceof DateTimeInterface) {
$where['value'] = new UTCDateTime($where['value']);
}
}
} elseif (isset($where['values'])) {
if (is_array($where['values'])) {
array_walk_recursive($where['values'], function (&$item, $key) {
if ($item instanceof DateTimeInterface) {
$item = new UTCDateTime($item);
}
});
} elseif ($where['values'] instanceof CarbonPeriod) {
$where['values'] = [
new UTCDateTime($where['values']->getStartDate()),
new UTCDateTime($where['values']->getEndDate()),
];
}
// Convert CarbonPeriod to DateTime interval.
if (isset($where['values']) && $where['values'] instanceof CarbonPeriod) {
$where['values'] = [
$where['values']->getStartDate(),
$where['values']->getEndDate(),
];
}

// In a sequence of "where" clauses, the logical operator of the
Expand Down Expand Up @@ -1631,12 +1597,21 @@ public function orWhereIntegerNotInRaw($column, $values, $boolean = 'and')
private function aliasIdForQuery(array $values): array
{
if (array_key_exists('id', $values)) {
if (array_key_exists('_id', $values)) {
throw new InvalidArgumentException('Cannot have both "id" and "_id" fields.');
}

$values['_id'] = $values['id'];
unset($values['id']);
}

foreach ($values as $key => $value) {
if (is_string($key) && str_ends_with($key, '.id')) {
$newkey = substr($key, 0, -3) . '._id';
if (array_key_exists($newkey, $values)) {
throw new InvalidArgumentException(sprintf('Cannot have both "%s" and "%s" fields.', $key, $newkey));
}

$values[substr($key, 0, -3) . '._id'] = $value;
unset($values[$key]);
}
Expand All @@ -1645,6 +1620,8 @@ private function aliasIdForQuery(array $values): array
foreach ($values as &$value) {
if (is_array($value)) {
$value = $this->aliasIdForQuery($value);
} elseif ($value instanceof DateTimeInterface) {
$value = new UTCDateTime($value);
}
}

Expand Down
5 changes: 2 additions & 3 deletions tests/Query/AggregationBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
use InvalidArgumentException;
use MongoDB\BSON\Document;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Builder\BuilderEncoder;
use MongoDB\Builder\Expression;
use MongoDB\Builder\Pipeline;
Expand All @@ -33,8 +32,8 @@ public function tearDown(): void
public function testCreateAggregationBuilder(): void
{
User::insert([
['name' => 'John Doe', 'birthday' => new UTCDateTime(new DateTimeImmutable('1989-01-01'))],
['name' => 'Jane Doe', 'birthday' => new UTCDateTime(new DateTimeImmutable('1990-01-01'))],
['name' => 'John Doe', 'birthday' => new DateTimeImmutable('1989-01-01')],
['name' => 'Jane Doe', 'birthday' => new DateTimeImmutable('1990-01-01')],
]);

// Create the aggregation pipeline from the query builder
Expand Down
6 changes: 6 additions & 0 deletions tests/Query/BuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,12 @@ function (Builder $builder) {
fn (Builder $builder) => $builder->whereBetween('id', [[1], [2, 3]]),
];

$date = new DateTimeImmutable('2018-09-30 15:00:00 +02:00');
yield 'where $lt DateTimeInterface' => [
['find' => [['created_at' => ['$lt' => new UTCDateTime($date)]], []]],
fn (Builder $builder) => $builder->where('created_at', '<', $date),
];

$period = now()->toPeriod(now()->addMonth());
yield 'whereBetween CarbonPeriod' => [
[
Expand Down
12 changes: 8 additions & 4 deletions tests/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1053,16 +1053,20 @@ public function testIncrementEach()
#[TestWith(['id', 'id'])]
#[TestWith(['id', '_id'])]
#[TestWith(['_id', 'id'])]
#[TestWith(['_id', '_id'])]
public function testIdAlias($insertId, $queryId): void
{
DB::collection('items')->insert([$insertId => 'abc', 'name' => 'Karting']);
$item = DB::collection('items')->where($queryId, '=', 'abc')->first();
DB::table('items')->insert([$insertId => 'abc', 'name' => 'Karting']);
$item = DB::table('items')->where($queryId, '=', 'abc')->first();
$this->assertNotNull($item);
$this->assertSame('abc', $item['id']);
$this->assertSame('Karting', $item['name']);

DB::collection('items')->where($insertId, '=', 'abc')->update(['name' => 'Bike']);
$item = DB::collection('items')->where($queryId, '=', 'abc')->first();
DB::table('items')->where($insertId, '=', 'abc')->update(['name' => 'Bike']);
$item = DB::table('items')->where($queryId, '=', 'abc')->first();
$this->assertSame('Bike', $item['name']);

$result = DB::table('items')->where($queryId, '=', 'abc')->delete();
$this->assertSame(1, $result);
}
}

0 comments on commit bd9ef30

Please sign in to comment.