-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug: Entity::hasChanged() is unreliable for casts #5905
Comments
Ref #2441 (comment) |
|
Workaround: <?php
namespace App\Controllers;
class Home extends BaseController
{
public function index()
{
$data = [
'id' => '1',
'name' => 'John',
'age' => '35',
];
$entity = new class ($data) extends \CodeIgniter\Entity\Entity {
protected $casts = [
'id' => 'integer',
'name' => 'string',
'age' => 'integer',
];
protected function setAge($value)
{
$this->attributes['age'] = (string) $value;
}
};
$entity->syncOriginal();
var_dump($entity->age);
var_dump($entity->hasChanged());
$entity->age = 35;
var_dump($entity->age);
var_dump($entity->hasChanged());
}
} |
Hello how are you.
public function hasChanged(?string $key = null) : bool
{
// If no parameter was given then check all attributes
if ($key === null) {
foreach ($this->attributes as $key => $value) {
// It's a new element
if (! array_key_exists($key, $this->original)) {
return true;
}
if ($this->hasChangedWithCast($key)) {
return true;
}
}
return false;
}
// Key doesn't exist in either
if (! array_key_exists($key, $this->original) && ! array_key_exists($key, $this->attributes)) {
return false;
}
// It's a new element
if (! array_key_exists($key, $this->original) && array_key_exists($key, $this->attributes)) {
return true;
}
return $this->hasChangedWithCast($key);
}
public function hasChangedWithCast(string $key) : bool
{
$original = $this->castAs($this->original[$key], $key, 'get');
$actual = $this->castAs($this->attributes[$key], $key, 'get');
$changed = false;
if ($original !== $actual) {
$changed = true;
// At the moment they are not the same.
// If a property has a Cast defined.
if (!empty($this->casts[$key])) {
$type = $this->casts[$key];
// If it is a numeric Cast
if (in_array($type, ['int','float','double'])) {
// To compare numbers better convert them to strings.
if ((string)$original === (string)$actual) {
$changed = false;
}
}
}
}
return $changed;
} I created a class called BaseEntity that extends from Entity and in it I added this method. |
@Patricio-Byte-Solution Could you send a PR to review your code? |
Instead of adding a mutator to set a value or extending the InetegerCast class, or adding your own class to cast types, you change the logic of the hasChange() method. Why? |
Hi @iRedds. |
I don't understand you. protected function setAge($value)
{
$this->attributes['age'] = (string) $value;
} |
@iRedds if we cast both values and then compare them, we can solve the problem of the type difference? |
@kenjis This will only work with primitive types. |
That would result in having to write a lot of code that seems redundant. If I specify casting in Would applying specified casts on setting properties break something? That would solve reported problem. Another solution is modifying |
My fix attempt #5944 |
@iRedds All values from the database are primitive types. |
All values from the database are strings. |
Hey guys, since my attempt #5944 can't be accepted, can you suggest any other solution? Right now I just can't use CodeIgniter4 for the simplest task: editing database entries (rows). Maybe I should describe my original problem. My site allows editing existing database entries by displaying their values in a HTML form. User can edit / submit form to update database entry. The problem is that:
I wanted to use |
Ref #5944 (comment) |
Unfortunately the current Entity is designed as a loosely typed Entity. See #2441 (comment) But now year 2022, we need a strictly typed Entity. |
I sent a PR #6284 |
Entity Class still not works has multiple problems, saving data to the database trough a Model triggers "No data to update" despite hasChanged() returns TRUE Another thing is now i cannot array_column($object, 'key'); And maybe fix these issues where you want to update a row in a database, by changing only one value of a column that is mapped ? Can we reopen this please ? |
@crustamet If you have another issue, please create a new issue with the details.
|
@kenjis array_column() worked before I don`t know why but maybe the magic functions converted into array or something... |
Well i finnaly found why it is not working after making a new project and putting piece by piece a model and another with entities and everything $model = new Model(); And then i wanted to have it as an entity But after then i changed a db column lets say id_foreign_key to id_foreign So i did that and then my array_column stopped working. Conclusion. DataMap
Does something with the object by not allowing the array_column to function as before if i change this to return TRUE instead of FALSE the array_columns works again even with datamap enabled |
@kenjis array_column does not accept objects, but array_column converts the object into an array as it states in the definition function, so even if you pass an object array_column still works even with an object array_column(array $array, int|string|null $column_key, int|string|null $index_key = null): array |
If you add |
No it does not just the array_column returns empty instead of an array of ids But please tell me about this line here https://github.com/codeigniter4/CodeIgniter4/blob/develop/system/Entity/Entity.php#L520 |
<?php
declare(strict_types=1);
namespace App\Controllers;
class Home extends BaseController
{
public function index()
{
$obj = (object) ['a' => 1];
array_column($obj, 'a');
}
}
|
@crustamet |
@kenjis True but array column does not work on a single object entity it does return an array of objects try the following use stdClass;
returns Array ( [0] => 1 [1] => 1 ) and from your answer, i do not want to remove DataMappingIsset
and sure we can move it down a little bit ?
i really dont know why it returns false if the column is mapped for sure the attribute is set trough that mapped item or ?
why this is made to return false if the column is mapped does that mean that the attribute of the entity is not set or what ? |
Okay, the following code works as you say. <?php
declare(strict_types=1);
namespace App\Controllers;
use stdClass;
class Home extends BaseController
{
public function index()
{
$std1 = new stdClass();
$std1->a = '1';
$std2 = new stdClass();
$std2->a = '1';
$res = [];
$res[] = $std1;
$res[] = $std2;
$RES = array_column($res, 'a');
d($RES);
}
} |
@ crustamet
Why don't you run tests by yourself? |
@crustamet When CodeIgniter4/system/Entity/Entity.php Lines 520 to 522 in fb0583f
I don't get your issue. |
@kenjis i understand now, the code is good The only case that isMappedDbColumn not working correctly is when you have the datamap mapping for the same key as the key on the attributes
So array_column and maybe other things not working is when you have the key equal with the datamap using this
certainly this was without intention. the only problem is when you have the same key on the datamap, should warn you maybe ? So even if I set a value for the mapped column isset() still not working because it is a mapped column but this should return TRUE, because the key from the mappedcolumns is equal with the key of the attribute. Really don't understand how it says is not set after I've just set it ? |
@crustamet I don't understand the meaning of |
Well the thing is i had implemented this entity and the model, and after i had made some db column rename, and i had to remap all the columns, after i did that i renamed again my db columns. all my entites had datamap so i thought i could let 'ID_Test' => 'ID_Test' i never knew it will return the attribute as not set. It was a mistake because i didn`t know how it worked before, for today this is not a mistake and it is on purpose. If this will not work in the future it must return an error for the datamap that cannot be the same as the db column or something.... |
@crustamet It seems to me that setting |
Thanks @kenjis you where of big help. I would make a PR but I have never did one before ^^ |
@crustamet If you send a PR, you need to sign your git commits. About unit testing, see https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#unit-testing I recommend that you first send a very small PR, such as just correcting a typo. |
Why not create a conditional rule: All values for tables must be string/null? |
No. It may be After all, it seems to me that Entity now does not hold the values it should as PHP, so it cannot make the correct comparisons. |
I sent a PR #7995 |
FYI: When you use MySQL, if you set |
I sent a draft PR #8243 |
PHP Version
8.0
CodeIgniter4 Version
4.1.9
CodeIgniter4 Installation Method
Manual (zip or tar.gz)
Which operating systems have you tested for this bug?
Linux
Which server did you use?
cgi-fcgi
Database
No response
What happened?
The problem is that
\CodeIgniter\Entity::hasChanged()
may returntrue
even if there isn't any data change.This problem is caused by two facts:
hasChanged()
ignores all casts (it compares$original
with$attributes
directly, bypassingcastAs()
)Steps to Reproduce
Expected Output
Anything else?
Current output:
The text was updated successfully, but these errors were encountered: