Skip to content

Conversation

@AZabolotnikov
Copy link
Contributor

@AZabolotnikov AZabolotnikov commented Jan 18, 2026

If a DB table has a binary field, the field displays as null when checking the database state.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes an issue where binary fields were displaying as null when checking database state. The fix adds support for detecting and properly serializing binary data fields by converting them to hexadecimal representation.

Changes:

  • Added binary field handling logic that detects binary data using UTF-8 encoding validation
  • Created test infrastructure including a BinaryCast class and test cases to verify binary field state tracking
  • Updated test expectations to include the new binary_field in the TestModel

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Testing/TableTestState.php Core fix that detects binary data and converts it to hex representation before comparison
tests/support/Mock/Casts/BinaryCast.php New custom cast implementation for testing binary field behavior
tests/support/Mock/Models/TestModel.php Added binary_field to the test model for testing purposes
tests/ModelTestStateTest.php Added test case for binary field changes and updated expectations
tests/BaseRequestTest.php Updated test expectations to include binary_field in orderable fields
tests/fixtures/ModelTestStateTest/db_changes/test_models/assert_changes_binary_string.json Test fixture defining expected state changes for binary field updates

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -89,14 +89,24 @@ protected function prepareChanges(array $changes): array
return array_map(function ($item) use ($jsonFields) {
foreach ($jsonFields as $jsonField) {
if (Arr::has($item, $jsonField)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (Arr::has($item, $jsonField)) {
if (Arr::has($item, $jsonField) && !is_null($item[$jsonField])) {

$item[$jsonField] = json_decode($item[$jsonField], true);
$value = $item[$jsonField];

$item[$jsonField] = $this->isBinary($value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$item[$jsonField] = $this->isBinary($value)
$item[$jsonField] = ($this->isBinary($value))

Comment on lines 106 to 107
return !is_null($data)
&& !mb_check_encoding($data, 'UTF-8');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return !is_null($data)
&& !mb_check_encoding($data, 'UTF-8');
return !mb_check_encoding($data, 'UTF-8');

Comment on lines +27 to 28
'binary_field' => BinaryCast::class,
];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove extra class

Suggested change
'binary_field' => BinaryCast::class,
];
];
protected function binaryField(): Attribute
{
return Attribute::make(
get: fn (string $value) => bin2hex($value),
set: fn (string $value) => md5($value, true)
);
}

Copy link
Contributor Author

@AZabolotnikov AZabolotnikov Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DenTray casting by class also has problem. modelState take cast by class as jsonField, please look here. We need test by cast

I don't found how resolve problem in method isJsonCast

$this->mockGettingDatasetForChanges($changedDatasetMock, $initialDatasetMock, 'test_models');

$modelTestState = new ModelTestState(TestModel::class);
$modelTestState->assertChangesEqualsFixture('assert_changes_binary_string.json');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$modelTestState->assertChangesEqualsFixture('assert_changes_binary_string.json');
$modelTestState->assertChangesEqualsFixture('null_to_binary_string_changes');

'id' => 1,
'binary_field' => md5('some_string', true),
]]);
$changedDatasetMock = collect([[
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$changedDatasetMock = collect([[
$changedDatasetMock = collect([[

$this->mockGettingDatasetForChanges($changedDatasetMock, $initialDatasetMock, 'test_models');

$modelTestState = new ModelTestState(TestModel::class);
$modelTestState->assertChangesEqualsFixture('assert_changes_binary_string_to_null.json');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$modelTestState->assertChangesEqualsFixture('assert_changes_binary_string_to_null.json');
$modelTestState->assertChangesEqualsFixture('binary_string_to_null_changes');

@DenTray DenTray assigned AZabolotnikov and unassigned DenTray Jan 26, 2026
@AZabolotnikov
Copy link
Contributor Author

AZabolotnikov commented Jan 28, 2026

@DenTray I did a small research and found the following problem with the binary field:

  $this->exportJson('export_json/response.json', [
            'some_key' => md5('some_string', true),
        ]);

But I think this case is related only to the database test, because we get data from the DB facade , we should use bin2hex to cast the field inside test for model state

@AZabolotnikov AZabolotnikov requested a review from DenTray January 28, 2026 10:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@AZabolotnikov AZabolotnikov requested a review from Copilot January 28, 2026 10:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

$changes = array_diff_assoc($updatedItem, $originItem);

if (!empty($changes)) {
$changes = Arr::map($changes, fn ($field) => (!mb_check_encoding($field, 'UTF-8')) ? bin2hex($field) : $field);
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The binary field conversion using bin2hex is only applied to changed fields in updated records. However, created and deleted records (lines 78-79) may contain full records with binary fields that have not been converted yet. This could cause issues when comparing created/deleted records against fixtures if they contain binary data.

Consider extracting the binary conversion logic into a separate method and applying it consistently to all record types (created, updated, deleted) in the prepareChanges method or before passing records to prepareChanges.

Copilot uses AI. Check for mistakes.
$state = $this->getProtectedProperty($reflectionClass, 'state', $modelTestState);

$this->assertEquals(['json_field', 'castable_field'], $jsonFields);
$this->assertEquals(['json_field', 'castable_field', 'binary_field'], $jsonFields);
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation treats ALL fields with custom CastsAttributes implementations as JSON fields (including BinaryCast). This is problematic because not all custom casts represent JSON data. The fix in line 93 of TableTestState.php works around this by adding json_validate() checks, but this is a band-aid solution.

A more robust approach would be to:

  1. Introduce a marker interface (e.g., JsonCast) that JSON-based custom casts can implement
  2. Update isJsonCast() to only return true for casts that implement this interface or are the 'array' type
  3. This would prevent binary fields from being incorrectly treated as JSON fields in the first place

The current workaround works but may cause confusion and could lead to similar issues with other non-JSON custom casts in the future.

Suggested change
$this->assertEquals(['json_field', 'castable_field', 'binary_field'], $jsonFields);
$this->assertEquals(['json_field', 'castable_field'], $jsonFields);

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +125
public function testAssertChangesBinaryString()
{
$initialDatasetMock = collect([[
'id' => 1,
'binary_field' => null,
]]);

$changedDatasetMock = collect([[
'id' => 1,
'binary_field' => md5('some_string', true),
]]);

$this->mockGettingDatasetForChanges($changedDatasetMock, $initialDatasetMock, 'test_models');

$modelTestState = new ModelTestState(TestModel::class);
$modelTestState->assertChangesEqualsFixture('null_to_binary_string_changes');
}

public function testAssertChangesBinaryToNull()
{
$initialDatasetMock = collect([[
'id' => 1,
'binary_field' => md5('some_string', true),
]]);

$changedDatasetMock = collect([[
'id' => 1,
'binary_field' => null,
]]);

$this->mockGettingDatasetForChanges($changedDatasetMock, $initialDatasetMock, 'test_models');

$modelTestState = new ModelTestState(TestModel::class);
$modelTestState->assertChangesEqualsFixture('binary_string_to_null_changes');
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test cases only cover updating records with binary fields (null to binary and binary to null), but don't test creating new records with binary fields or deleting records that contain binary fields. Given that the binary conversion logic in TableTestState.php line 67 only applies to changed fields in updated records, there should be test coverage for created and deleted records with binary data to ensure they are handled correctly.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants