Skip to content

Commit e492a60

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

File tree

5 files changed

+154
-9
lines changed

5 files changed

+154
-9
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ 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+
- `Model::unset()` does not persist the change. Call `Model::save()` to persist the change [#2578](https://github.com/jenssegers/laravel-mongodb/pull/2578) by [@GromNaN](https://github.com/GromNaN).
2021

2122
## [3.9.2] - 2022-09-01
2223

Diff for: src/Eloquent/Model.php

+64-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,13 @@ abstract class Model extends BaseModel
5255
*/
5356
protected $parentRelation;
5457

58+
/**
59+
* List of field names to unset from the document on save.
60+
*
61+
* @var array{string, true}
62+
*/
63+
private array $unset = [];
64+
5565
/**
5666
* Custom accessor for the model's id.
5767
*
@@ -151,7 +161,12 @@ public function getTable()
151161
public function getAttribute($key)
152162
{
153163
if (! $key) {
154-
return;
164+
return null;
165+
}
166+
167+
// An unset attribute is null or throw an exception.
168+
if (isset($this->unset[$key])) {
169+
return $this->throwMissingAttributeExceptionIfApplicable($key);
155170
}
156171

157172
// Dot notation support.
@@ -206,6 +221,9 @@ public function setAttribute($key, $value)
206221
return $this;
207222
}
208223

224+
// Setting an attribute cancels the unset operation.
225+
unset($this->unset[$key]);
226+
209227
return parent::setAttribute($key, $value);
210228
}
211229

@@ -239,6 +257,21 @@ public function getCasts()
239257
return $this->casts;
240258
}
241259

260+
/**
261+
* @inheritdoc
262+
*/
263+
public function getDirty()
264+
{
265+
$dirty = parent::getDirty();
266+
267+
// The specified value in the $unset expression does not impact the operation.
268+
if (! empty($this->unset)) {
269+
$dirty['$unset'] = $this->unset;
270+
}
271+
272+
return $dirty;
273+
}
274+
242275
/**
243276
* @inheritdoc
244277
*/
@@ -248,6 +281,11 @@ public function originalIsEquivalent($key)
248281
return false;
249282
}
250283

284+
// Calling unset on an attribute marks it as "not equivalent".
285+
if (isset($this->unset[$key])) {
286+
return false;
287+
}
288+
251289
$attribute = Arr::get($this->attributes, $key);
252290
$original = Arr::get($this->original, $key);
253291

@@ -275,11 +313,34 @@ public function originalIsEquivalent($key)
275313
&& strcmp((string) $attribute, (string) $original) === 0;
276314
}
277315

316+
/**
317+
* @inheritdoc
318+
*/
319+
public function offsetUnset($offset): void
320+
{
321+
parent::offsetUnset($offset);
322+
323+
// Force unsetting even if the attribute is not set.
324+
// End user can optimize DB calls by checking if the attribute is set before unsetting it.
325+
$this->unset[$offset] = true;
326+
}
327+
328+
/**
329+
* @inheritdoc
330+
*/
331+
public function offsetSet($offset, $value): void
332+
{
333+
parent::offsetSet($offset, $value);
334+
335+
// Setting an attribute cancels the unset operation.
336+
unset($this->unset[$offset]);
337+
}
338+
278339
/**
279340
* Remove one or more fields.
280341
*
281-
* @param mixed $columns
282-
* @return int
342+
* @param string|string[] $columns
343+
* @return void
283344
*/
284345
public function drop($columns)
285346
{
@@ -289,9 +350,6 @@ public function drop($columns)
289350
foreach ($columns as $column) {
290351
$this->__unset($column);
291352
}
292-
293-
// Perform unset only on current document
294-
return $this->newQuery()->where($this->getKeyName(), $this->getKey())->unset($columns);
295353
}
296354

297355
/**

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)