yiisoft / yii2

Yii 2: The Fast, Secure and Professional PHP Framework
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
14.24k stars 6.9k forks source link

ActiveRecord::getDirtyAttributes() not working for JSON columns with multi-dimensional arrays #20191

Closed brandonkelly closed 5 months ago

brandonkelly commented 5 months ago

What steps will reproduce the problem?

  1. Create a DB table with a JSON column, and an ActiveRecord class for it.
  2. Create a row and set the JSON column to the value:
    {"foo": ["c", "b", "a"]}
  3. Run the following code:
    
    $record = MyActiveRecord::findOne();
    $record->jsonColumnName = [
       'foo' => ['b', 'c', 'a'],
    ];
    $record->save();

What is the expected result?

The new value will save.

What do you get instead?

The new value isn’t saved, because ActiveRecord::isValueDifferent() will return false for jsonColumnName.

This happens because isValueDifferent() normalizes each value with ArrayHelper::recursiveSort(), which sorts the array and all nested arrays using either sort() (if indexed) or ksort (if associative).

The intent seems to be to only do this for associative arrays, which probably makes sense since the order of values is generally not important for associative arrays. But it should definitely not be happening for non-associative arrays.

https://github.com/yiisoft/yii2/blob/d9d168b036158ba2b3f139bf5155c0bb4b093d20/framework/db/BaseActiveRecord.php#L1784-L1787

Additional info

Q A
Yii version 2.0.45, 2.0.50
PHP version n/a
Operating system n/a
brandonkelly commented 5 months ago

This bug is causing craftcms/cms#15154. As far as we could tell there wasn’t a good way to workaround it, so the least-hacky thing I came up with was to override ArrayHepler::recursiveSort() to ignore all non-associative arrays. Which is obviously not a great long-term solution since I’m effectively breaking that method.

So, hoping #20192 (or a similar fix) can get merged in and tagged in a Yii release ASAP.