Skip to content

Commit 845b663

Browse files
committed
fix: Entity::hasChanged() is unreliable for casts
1 parent 7734770 commit 845b663

File tree

2 files changed

+98
-6
lines changed

2 files changed

+98
-6
lines changed

system/Entity/Entity.php

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public function toArray(bool $onlyChanged = false, bool $cast = true, bool $recu
189189
* Returns the raw values of the current attributes.
190190
*
191191
* @param bool $onlyChanged If true, only return values that have changed since object creation
192-
* @param bool $recursive If true, inner entities will be casted as array as well.
192+
* @param bool $recursive If true, inner entities will be cast as array as well.
193193
*/
194194
public function toRawArray(bool $onlyChanged = false, bool $recursive = false): array
195195
{
@@ -212,7 +212,7 @@ public function toRawArray(bool $onlyChanged = false, bool $recursive = false):
212212
}
213213

214214
foreach ($this->attributes as $key => $value) {
215-
if (! $this->hasChanged($key)) {
215+
if (! $this->hasChangedAttributes($key)) {
216216
continue;
217217
}
218218

@@ -247,17 +247,40 @@ public function syncOriginal()
247247
* was created. Or, without a parameter, checks if any
248248
* properties have changed.
249249
*
250-
* @param string $key
250+
* @param string|null $key class property
251251
*/
252252
public function hasChanged(?string $key = null): bool
253253
{
254254
// If no parameter was given then check all attributes
255255
if ($key === null) {
256-
return $this->original !== $this->attributes;
256+
return $this->hasChangedAttributes();
257257
}
258258

259259
$key = $this->mapProperty($key);
260260

261+
return $this->hasChangedAttributes($key);
262+
}
263+
264+
/**
265+
* Checks a attribute to see if it has changed since the entity
266+
* was created. Or, without a parameter, checks if any
267+
* attributes have changed.
268+
*
269+
* @param string|null $key key of $this->attributes
270+
*/
271+
private function hasChangedAttributes(?string $key = null): bool
272+
{
273+
// If no parameter was given then check all attributes
274+
if ($key === null) {
275+
foreach ($this->attributes as $key => $value) {
276+
if ($this->isChanged($key)) {
277+
return true;
278+
}
279+
}
280+
281+
return false;
282+
}
283+
261284
// Key doesn't exist in either
262285
if (! array_key_exists($key, $this->original) && ! array_key_exists($key, $this->attributes)) {
263286
return false;
@@ -268,7 +291,7 @@ public function hasChanged(?string $key = null): bool
268291
return true;
269292
}
270293

271-
return $this->original[$key] !== $this->attributes[$key];
294+
return $this->isChanged($key);
272295
}
273296

274297
/**
@@ -470,6 +493,7 @@ public function __set(string $key, $value = null)
470493
* $p = $this->getMyProperty()
471494
*
472495
* @throws Exception
496+
* @params string $key class property
473497
*
474498
* @return mixed
475499
*/
@@ -487,7 +511,6 @@ public function __get(string $key)
487511
if (method_exists($this, $method)) {
488512
$result = $this->{$method}();
489513
}
490-
491514
// Otherwise return the protected property
492515
// if it exists.
493516
elseif (array_key_exists($key, $this->attributes)) {
@@ -506,6 +529,30 @@ public function __get(string $key)
506529
return $result;
507530
}
508531

532+
/**
533+
* Get cast value from the data array.
534+
*
535+
* @return mixed|null
536+
*/
537+
private function _getCastData(string $key, array $data)
538+
{
539+
$result = null;
540+
541+
if (array_key_exists($key, $data)) {
542+
$result = $this->castAs($data[$key], $key);
543+
}
544+
545+
return $result;
546+
}
547+
548+
/**
549+
* Check if the key value is changed.
550+
*/
551+
private function isChanged(string $key): bool
552+
{
553+
return $this->_getCastData($key, $this->original) !== $this->_getCastData($key, $this->attributes);
554+
}
555+
509556
/**
510557
* Returns true if a property exists names $key, or a getter method
511558
* exists named like for __get().

tests/system/Entity/EntityTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,51 @@ public function testHasChangedMappedChanged()
947947
$this->assertTrue($entity->hasChanged('createdAt'));
948948
}
949949

950+
/**
951+
* @see https://github.com/codeigniter4/CodeIgniter4/issues/5905
952+
*/
953+
public function testHasChangedCastsItem()
954+
{
955+
$data = [
956+
'id' => '1',
957+
'name' => 'John',
958+
'age' => '35',
959+
];
960+
$entity = new class ($data) extends \CodeIgniter\Entity {
961+
protected $casts = [
962+
'id' => 'integer',
963+
'name' => 'string',
964+
'age' => 'integer',
965+
];
966+
};
967+
$entity->syncOriginal();
968+
969+
$entity->age = 35;
970+
971+
$this->assertFalse($entity->hasChanged('age'));
972+
}
973+
974+
public function testHasChangedCastsWholeEntity()
975+
{
976+
$data = [
977+
'id' => '1',
978+
'name' => 'John',
979+
'age' => '35',
980+
];
981+
$entity = new class ($data) extends \CodeIgniter\Entity {
982+
protected $casts = [
983+
'id' => 'integer',
984+
'name' => 'string',
985+
'age' => 'integer',
986+
];
987+
};
988+
$entity->syncOriginal();
989+
990+
$entity->age = 35;
991+
992+
$this->assertFalse($entity->hasChanged());
993+
}
994+
950995
public function testHasChangedWholeEntity()
951996
{
952997
$entity = $this->getEntity();

0 commit comments

Comments
 (0)