Skip to content

Commit 1fbb798

Browse files
committed
PHPORM-75 Defer Model::unset($field) to the save()
1 parent af13eda commit 1fbb798

File tree

5 files changed

+150
-9
lines changed

5 files changed

+150
-9
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ All notable changes to this project will be documented in this file.
1717
- Fix validation of unique values when the validated value is found as part of an existing value. [#21](https://github.com/GromNaN/laravel-mongodb/pull/21) by [@GromNaN](https://github.com/GromNaN).
1818
- Support `%` and `_` in `like` expression [#17](https://github.com/GromNaN/laravel-mongodb/pull/17) by [@GromNaN](https://github.com/GromNaN).
1919
- Change signature of `Query\Builder::__constructor` to match the parent class [#26](https://github.com/GromNaN/laravel-mongodb-private/pull/26) by [@GromNaN](https://github.com/GromNaN).
20+
- `Query\Builder::update()` accepts a mix of field to set and `$`-prefixed operators
21+
- `Model::unset()` does not persist the change anymore. Call `Model::save()` to persist the change.
2022

2123
## [3.9.2] - 2022-09-01
2224

Diff for: src/Eloquent/Model.php

+59-6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
use MongoDB\BSON\UTCDateTime;
2121
use function uniqid;
2222

23+
/**
24+
* @method void unset(string|string[] $columns) Remove one or more fields.
25+
*/
2326
abstract class Model extends BaseModel
2427
{
2528
use HybridRelations, EmbedsRelations;
@@ -52,6 +55,8 @@ abstract class Model extends BaseModel
5255
*/
5356
protected $parentRelation;
5457

58+
private array $unset = [];
59+
5560
/**
5661
* Custom accessor for the model's id.
5762
*
@@ -151,7 +156,12 @@ public function getTable()
151156
public function getAttribute($key)
152157
{
153158
if (! $key) {
154-
return;
159+
return null;
160+
}
161+
162+
// An unset attribute is null or throw an exception.
163+
if (isset($this->unset[$key])) {
164+
return $this->throwMissingAttributeExceptionIfApplicable($key);
155165
}
156166

157167
// Dot notation support.
@@ -206,6 +216,9 @@ public function setAttribute($key, $value)
206216
return $this;
207217
}
208218

219+
// Setting an attribute cancels the unset operation.
220+
unset($this->unset[$key]);
221+
209222
return parent::setAttribute($key, $value);
210223
}
211224

@@ -239,6 +252,21 @@ public function getCasts()
239252
return $this->casts;
240253
}
241254

255+
/**
256+
* @inheritdoc
257+
*/
258+
public function getDirty()
259+
{
260+
$dirty = parent::getDirty();
261+
262+
// The specified value in the $unset expression does not impact the operation.
263+
if (! empty($this->unset)) {
264+
$dirty['$unset'] = $this->unset;
265+
}
266+
267+
return $dirty;
268+
}
269+
242270
/**
243271
* @inheritdoc
244272
*/
@@ -248,6 +276,11 @@ public function originalIsEquivalent($key)
248276
return false;
249277
}
250278

279+
// Calling unset on an attribute marks it as "not equivalent".
280+
if (isset($this->unset[$key])) {
281+
return false;
282+
}
283+
251284
$attribute = Arr::get($this->attributes, $key);
252285
$original = Arr::get($this->original, $key);
253286

@@ -275,11 +308,34 @@ public function originalIsEquivalent($key)
275308
&& strcmp((string) $attribute, (string) $original) === 0;
276309
}
277310

311+
/**
312+
* @inheritdoc
313+
*/
314+
public function offsetUnset($offset): void
315+
{
316+
parent::offsetUnset($offset);
317+
318+
// Force unsetting even if the attribute is not set.
319+
// End user can optimize DB calls by checking if the attribute is set before unsetting it.
320+
$this->unset[$offset] = true;
321+
}
322+
323+
/**
324+
* @inheritdoc
325+
*/
326+
public function offsetSet($offset, $value): void
327+
{
328+
parent::offsetSet($offset, $value);
329+
330+
// Setting an attribute cancels the unset operation.
331+
unset($this->unset[$offset]);
332+
}
333+
278334
/**
279335
* Remove one or more fields.
280336
*
281-
* @param mixed $columns
282-
* @return int
337+
* @param string|string[] $columns
338+
* @return void
283339
*/
284340
public function drop($columns)
285341
{
@@ -289,9 +345,6 @@ public function drop($columns)
289345
foreach ($columns as $column) {
290346
$this->__unset($column);
291347
}
292-
293-
// Perform unset only on current document
294-
return $this->newQuery()->where($this->getKeyName(), $this->getKey())->unset($columns);
295348
}
296349

297350
/**

Diff for: src/Query/Builder.php

+6-3
Original file line numberDiff line numberDiff line change
@@ -605,9 +605,12 @@ public function insertGetId(array $values, $sequence = null)
605605
*/
606606
public function update(array $values, array $options = [])
607607
{
608-
// Use $set as default operator.
609-
if (! str_starts_with(key($values), '$')) {
610-
$values = ['$set' => $values];
608+
// Use $set as default operator for field names that are not in an operator
609+
foreach ($values as $key => $value) {
610+
if (! str_starts_with($key, '$')) {
611+
$values['$set'][$key] = $value;
612+
unset($values[$key]);
613+
}
611614
}
612615

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

Diff for: tests/ModelTest.php

+51
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,10 @@ public function testUnset(): void
473473

474474
$user1->unset('note1');
475475

476+
$this->assertFalse(isset($user1->note1));
477+
478+
$user1->save();
479+
476480
$this->assertFalse(isset($user1->note1));
477481
$this->assertTrue(isset($user1->note2));
478482
$this->assertTrue(isset($user2->note1));
@@ -488,9 +492,56 @@ public function testUnset(): void
488492
$this->assertTrue(isset($user2->note2));
489493

490494
$user2->unset(['note1', 'note2']);
495+
$user2->save();
491496

492497
$this->assertFalse(isset($user2->note1));
493498
$this->assertFalse(isset($user2->note2));
499+
500+
// Re-re-fetch to be sure
501+
$user2 = User::find($user2->_id);
502+
503+
$this->assertFalse(isset($user2->note1));
504+
$this->assertFalse(isset($user2->note2));
505+
}
506+
507+
public function testUnsetAndSet(): void
508+
{
509+
$user = User::create(['name' => 'John Doe', 'note1' => 'ABC', 'note2' => 'DEF']);
510+
511+
$this->assertTrue($user->originalIsEquivalent('note1'));
512+
513+
// Unset the value
514+
unset($user['note1']);
515+
$this->assertFalse(isset($user->note1));
516+
$this->assertNull($user['note1']);
517+
$this->assertFalse($user->originalIsEquivalent('note1'));
518+
$this->assertSame(['$unset' => ['note1' => true]], $user->getDirty());
519+
520+
// Reset the previous value
521+
$user->note1 = 'ABC';
522+
$this->assertTrue($user->originalIsEquivalent('note1'));
523+
$this->assertSame([], $user->getDirty());
524+
525+
// Change the value
526+
$user->note1 = 'GHI';
527+
$this->assertTrue(isset($user->note1));
528+
$this->assertSame('GHI', $user['note1']);
529+
$this->assertFalse($user->originalIsEquivalent('note1'));
530+
$this->assertSame(['note1' => 'GHI'], $user->getDirty());
531+
532+
// Fetch to be sure the changes are not persisted yet
533+
$userCheck = User::find($user->_id);
534+
$this->assertSame('ABC', $userCheck['note1']);
535+
536+
// Persist the changes
537+
$user->save();
538+
539+
// Re-fetch to be sure
540+
$user = User::find($user->_id);
541+
542+
$this->assertTrue(isset($user->note1));
543+
$this->assertSame('GHI', $user->note1);
544+
$this->assertTrue($user->originalIsEquivalent('note1'));
494545
}
495546

496547
public function testDates(): void

Diff for: tests/QueryBuilderTest.php

+32
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,38 @@ public function testUpdate()
203203
$this->assertEquals(20, $jane['age']);
204204
}
205205

206+
public function testUpdateOperators()
207+
{
208+
DB::collection('users')->insert([
209+
['name' => 'Jane Doe', 'age' => 20],
210+
['name' => 'John Doe', 'age' => 19],
211+
]);
212+
213+
DB::collection('users')->where('name', 'John Doe')->update(
214+
[
215+
'$unset' => ['age' => 1],
216+
'ageless' => true,
217+
]
218+
);
219+
DB::collection('users')->where('name', 'Jane Doe')->update(
220+
[
221+
'$inc' => ['age' => 1],
222+
'$set' => ['pronoun' => 'she'],
223+
'ageless' => false,
224+
]
225+
);
226+
227+
$john = DB::collection('users')->where('name', 'John Doe')->first();
228+
$jane = DB::collection('users')->where('name', 'Jane Doe')->first();
229+
230+
$this->assertArrayNotHasKey('age', $john);
231+
$this->assertTrue($john['ageless']);
232+
233+
$this->assertEquals(21, $jane['age']);
234+
$this->assertEquals('she', $jane['pronoun']);
235+
$this->assertFalse($jane['ageless']);
236+
}
237+
206238
public function testDelete()
207239
{
208240
DB::collection('users')->insert([

0 commit comments

Comments
 (0)