Skip to content

Commit e652b0c

Browse files
authored
PHPORM-75 Defer Model::unset($field) to the save() (#2578)
* PHPORM-75 Defer Model::unset($field) to the save() * Deprecate Model::drop(), use Model::unset() instead * Add assertions on isDirty
1 parent 929f284 commit e652b0c

File tree

5 files changed

+168
-22
lines changed

5 files changed

+168
-22
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file.
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).
2020
- Fix Query on `whereDate`, `whereDay`, `whereMonth`, `whereYear`, `whereTime` to use MongoDB operators [#2570](https://github.com/jenssegers/laravel-mongodb/pull/2376) by [@Davpyu](https://github.com/Davpyu) and [@GromNaN](https://github.com/GromNaN).
21+
- `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).
2122

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

Diff for: src/Eloquent/Model.php

+74-19
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ abstract class Model extends BaseModel
5252
*/
5353
protected $parentRelation;
5454

55+
/**
56+
* List of field names to unset from the document on save.
57+
*
58+
* @var array{string, true}
59+
*/
60+
private array $unset = [];
61+
5562
/**
5663
* Custom accessor for the model's id.
5764
*
@@ -151,7 +158,12 @@ public function getTable()
151158
public function getAttribute($key)
152159
{
153160
if (! $key) {
154-
return;
161+
return null;
162+
}
163+
164+
// An unset attribute is null or throw an exception.
165+
if (isset($this->unset[$key])) {
166+
return $this->throwMissingAttributeExceptionIfApplicable($key);
155167
}
156168

157169
// Dot notation support.
@@ -206,6 +218,9 @@ public function setAttribute($key, $value)
206218
return $this;
207219
}
208220

221+
// Setting an attribute cancels the unset operation.
222+
unset($this->unset[$key]);
223+
209224
return parent::setAttribute($key, $value);
210225
}
211226

@@ -239,6 +254,21 @@ public function getCasts()
239254
return $this->casts;
240255
}
241256

257+
/**
258+
* @inheritdoc
259+
*/
260+
public function getDirty()
261+
{
262+
$dirty = parent::getDirty();
263+
264+
// The specified value in the $unset expression does not impact the operation.
265+
if (! empty($this->unset)) {
266+
$dirty['$unset'] = $this->unset;
267+
}
268+
269+
return $dirty;
270+
}
271+
242272
/**
243273
* @inheritdoc
244274
*/
@@ -248,6 +278,11 @@ public function originalIsEquivalent($key)
248278
return false;
249279
}
250280

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

@@ -275,23 +310,56 @@ public function originalIsEquivalent($key)
275310
&& strcmp((string) $attribute, (string) $original) === 0;
276311
}
277312

313+
/**
314+
* @inheritdoc
315+
*/
316+
public function offsetUnset($offset): void
317+
{
318+
parent::offsetUnset($offset);
319+
320+
// Force unsetting even if the attribute is not set.
321+
// End user can optimize DB calls by checking if the attribute is set before unsetting it.
322+
$this->unset[$offset] = true;
323+
}
324+
325+
/**
326+
* @inheritdoc
327+
*/
328+
public function offsetSet($offset, $value): void
329+
{
330+
parent::offsetSet($offset, $value);
331+
332+
// Setting an attribute cancels the unset operation.
333+
unset($this->unset[$offset]);
334+
}
335+
278336
/**
279337
* Remove one or more fields.
280338
*
281-
* @param mixed $columns
282-
* @return int
339+
* @param string|string[] $columns
340+
* @return void
341+
*
342+
* @deprecated Use unset() instead.
283343
*/
284344
public function drop($columns)
345+
{
346+
$this->unset($columns);
347+
}
348+
349+
/**
350+
* Remove one or more fields.
351+
*
352+
* @param string|string[] $columns
353+
* @return void
354+
*/
355+
public function unset($columns)
285356
{
286357
$columns = Arr::wrap($columns);
287358

288359
// Unset attributes
289360
foreach ($columns as $column) {
290361
$this->__unset($column);
291362
}
292-
293-
// Perform unset only on current document
294-
return $this->newQuery()->where($this->getKeyName(), $this->getKey())->unset($columns);
295363
}
296364

297365
/**
@@ -502,19 +570,6 @@ protected function isGuardableColumn($key)
502570
return true;
503571
}
504572

505-
/**
506-
* @inheritdoc
507-
*/
508-
public function __call($method, $parameters)
509-
{
510-
// Unset method
511-
if ($method == 'unset') {
512-
return $this->drop(...$parameters);
513-
}
514-
515-
return parent::__call($method, $parameters);
516-
}
517-
518573
/**
519574
* @inheritdoc
520575
*/

Diff for: src/Query/Builder.php

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

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

Diff for: tests/ModelTest.php

+55
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,60 @@ 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+
$user->unset('note1');
515+
$this->assertFalse(isset($user->note1));
516+
$this->assertNull($user['note1']);
517+
$this->assertFalse($user->originalIsEquivalent('note1'));
518+
$this->assertTrue($user->isDirty());
519+
$this->assertSame(['$unset' => ['note1' => true]], $user->getDirty());
520+
521+
// Reset the previous value
522+
$user->note1 = 'ABC';
523+
$this->assertTrue($user->originalIsEquivalent('note1'));
524+
$this->assertFalse($user->isDirty());
525+
$this->assertSame([], $user->getDirty());
526+
527+
// Change the value
528+
$user->note1 = 'GHI';
529+
$this->assertTrue(isset($user->note1));
530+
$this->assertSame('GHI', $user['note1']);
531+
$this->assertFalse($user->originalIsEquivalent('note1'));
532+
$this->assertTrue($user->isDirty());
533+
$this->assertSame(['note1' => 'GHI'], $user->getDirty());
534+
535+
// Fetch to be sure the changes are not persisted yet
536+
$userCheck = User::find($user->_id);
537+
$this->assertSame('ABC', $userCheck['note1']);
538+
539+
// Persist the changes
540+
$user->save();
541+
542+
// Re-fetch to be sure
543+
$user = User::find($user->_id);
544+
545+
$this->assertTrue(isset($user->note1));
546+
$this->assertSame('GHI', $user->note1);
547+
$this->assertTrue($user->originalIsEquivalent('note1'));
548+
$this->assertFalse($user->isDirty());
494549
}
495550

496551
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)