CodeIgniter4 icon indicating copy to clipboard operation
CodeIgniter4 copied to clipboard

Bug: Tracking changes in Entity

Open neznaika0 opened this issue 2 months ago • 1 comments

PHP Version

8.4

CodeIgniter4 Version

latest, 4.6.3

CodeIgniter4 Installation Method

Git

Which operating systems have you tested for this bug?

Linux

Which server did you use?

cli-server (PHP built-in webserver)

Database

No response

What happened?

Since it became possible to set the properties of an entity after casting, there has been a problem tracking changes to the object. The principle of operation is now based on primitives: numbers, strings, and arrays...but not objects. By setting up casting in the Model, we get a single object for attributes and original. They are links. When you change a property, it also changes in the original. If we call hasChanged(), we will not get the correct result and the object will not save these changes to the database. The situation is complicated by nested objects like $user->profile->username->name. They must also be monitored.

In Laravel, only values are saved (mostly json). However, I think it's possible to replace an object with a similar value, and this is considered an unchanged state. This is fine for the database, but it may cause an error in the PHP code.

Steps to Reproduce

Try working with objects instead of primitives. Call hasChanged(), compare $attributes and $original values after changing objects.

Expected Output

I would like to properly track the state of an object, along with its nesting.

Anything else?

No response

neznaika0 avatar Oct 29 '25 10:10 neznaika0

Simple test code. Paste in Home.php controller. PHP 8.4.14

<?php

namespace App\Controllers;

use CodeIgniter\Entity\Entity;

class Home extends BaseController
{
    public function index(): string
    {
        // Test #1
        $user1 = (new User(['email' => '[email protected]', 'profile' => new Profile('Ivan', new Phone('+1234567890'))]))->syncOriginal();

        // Iinitial object or after Db find (with casting)
        d($user1->hasChanged()); // false, OK

        // Change email - primitive PHP type
        $user1->email = '[email protected]';
        d(
            $user1->hasChanged(),
            $user1->hasChanged('email'),
        ); // true, true, OK

        // Test #2
        $user2 = (new User(['email' => '[email protected]', 'profile' => new Profile('Ivan', new Phone('+1234567890'))]))->syncOriginal();

        // Change object
        $user2->profile->name  = 'Petr';
        $user2->profile->phone = new Phone('+9876543210');

        d(
            $user2->hasChanged(),
            $user2->hasChanged('profile'),
        ); // false, false, Fail

        dd($user1, $user2);

        return view('welcome_message');
    }
}

readonly class Phone
{
    public function __construct(public string $value)
    {
    }

    public function __toString()
    {
        return $this->value;
    }
}

class Profile
{
    public function __construct(public string $name, public Phone $phone)
    {
    }
}

class User extends Entity
{
}

neznaika0 avatar Oct 29 '25 19:10 neznaika0