Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions system/Entity/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,11 @@ private function normalizeValue(mixed $data): mixed
// Check for Entity instance (use raw values, recursive)
if ($data instanceof self) {
$objectData = $data->toRawArray(false, true);
} elseif ($data instanceof UnitEnum) {
Comment thread
michalsn marked this conversation as resolved.
return [
'__class' => $data::class,
'__enum' => $data instanceof BackedEnum ? $data->value : $data->name,
Comment thread
michalsn marked this conversation as resolved.
];
} elseif ($data instanceof JsonSerializable) {
$objectData = $data->jsonSerialize();
} elseif (method_exists($data, 'toArray')) {
Expand All @@ -469,11 +474,6 @@ private function normalizeValue(mixed $data): mixed
'__class' => $data::class,
'__datetime' => $data->format(DATE_RFC3339_EXTENDED),
];
} elseif ($data instanceof UnitEnum) {
return [
'__class' => $data::class,
'__enum' => $data instanceof BackedEnum ? $data->value : $data->name,
];
} else {
$objectData = get_object_vars($data);

Expand Down
30 changes: 30 additions & 0 deletions tests/_support/Entity/ArrayObjectWithToArray.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace Tests\Support\Entity;

use ArrayObject;

/**
* @extends ArrayObject<string, string>
*/
final class ArrayObjectWithToArray extends ArrayObject
{
/**
* @return array<string, string>
*/
public function toArray(): array
{
return ['array' => 'same'];
}
}
27 changes: 27 additions & 0 deletions tests/_support/Enum/JsonSerializableStateUnitEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace Tests\Support\Enum;

use JsonSerializable;

enum JsonSerializableStateUnitEnum implements JsonSerializable
{
case DRAFT;
case PUBLISHED;

public function jsonSerialize(): mixed
{
return ['json' => $this->name];
}
}
25 changes: 25 additions & 0 deletions tests/_support/Enum/StateEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace Tests\Support\Enum;

enum StateEnum: string
{
case DRAFT = 'draft';
case PUBLISHED = 'published';

public function toArray(): array
{
return self::cases();
}
Comment thread
maniaba marked this conversation as resolved.
}
25 changes: 25 additions & 0 deletions tests/_support/Enum/StateUnitEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace Tests\Support\Enum;

enum StateUnitEnum
{
case DRAFT;
case PUBLISHED;

public function toArray(): array
{
return self::cases();
}
}
127 changes: 127 additions & 0 deletions tests/system/Entity/EntityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@
use PHPUnit\Framework\Attributes\Group;
use ReflectionException;
use stdClass;
use Tests\Support\Entity\ArrayObjectWithToArray;
use Tests\Support\Entity\Cast\CastBase64;
use Tests\Support\Entity\Cast\CastPassParameters;
use Tests\Support\Entity\Cast\NotExtendsBaseCast;
use Tests\Support\Enum\ColorEnum;
use Tests\Support\Enum\JsonSerializableStateUnitEnum;
use Tests\Support\Enum\RoleEnum;
use Tests\Support\Enum\StateEnum;
use Tests\Support\Enum\StateUnitEnum;
use Tests\Support\Enum\StatusEnum;
use Tests\Support\SomeEntity;

Expand Down Expand Up @@ -1045,6 +1049,45 @@ public function testCastEnumSetWithUnitEnumObject(): void
$this->assertSame(ColorEnum::RED, $entity->color);
}

/**
* @see https://github.com/codeigniter4/CodeIgniter4/issues/10136
*/
public function testInjectRawDataWithBackedEnumThatHasToArrayMethod(): void
{
$entity = new class () extends Entity {};

$entity->injectRawData(['state' => StateEnum::DRAFT]);

$this->assertSame(StateEnum::DRAFT, $entity->toRawArray()['state']);
$this->assertFalse($entity->hasChanged('state'));
}

/**
* @see https://github.com/codeigniter4/CodeIgniter4/issues/10136
*/
public function testInjectRawDataWithUnitEnumThatHasToArrayMethod(): void
{
$entity = new class () extends Entity {};

$entity->injectRawData(['state' => StateUnitEnum::DRAFT]);

$this->assertSame(StateUnitEnum::DRAFT, $entity->toRawArray()['state']);
$this->assertFalse($entity->hasChanged('state'));
}

/**
* @see https://github.com/codeigniter4/CodeIgniter4/issues/10136
*/
public function testInjectRawDataWithUnitEnumThatImplementsJsonSerializable(): void
{
$entity = new class () extends Entity {};

$entity->injectRawData(['state' => JsonSerializableStateUnitEnum::DRAFT]);

$this->assertSame(JsonSerializableStateUnitEnum::DRAFT, $entity->toRawArray()['state']);
$this->assertFalse($entity->hasChanged('state'));
}

public function testAsArray(): void
{
$entity = $this->getEntity();
Expand Down Expand Up @@ -1975,6 +2018,46 @@ public function jsonSerialize(): mixed
$this->assertTrue($entity->hasChanged('data'));
}

public function testHasChangedPrefersJsonSerializableOverToArray(): void
{
$entity = new class () extends Entity {
protected $attributes = [
'data' => null,
];
};

$data = new class ('original') implements JsonSerializable {
public function __construct(private string $value)
{
}

public function jsonSerialize(): mixed
{
return ['json' => $this->value];
}

public function setValue(string $value): void
{
$this->value = $value;
}

/**
* @return array<string, string>
*/
public function toArray(): array
{
return ['array' => 'same'];
}
};

$entity->data = $data;
$entity->syncOriginal();

$data->setValue('modified');

$this->assertTrue($entity->hasChanged('data'));
}

public function testHasChangedDoesNotDetectUnchangedObject(): void
{
$entity = new class () extends Entity {
Expand Down Expand Up @@ -2278,6 +2361,50 @@ public function toArray(): array
$this->assertTrue($entity->hasChanged('custom'));
}

public function testHasChangedPrefersToArrayOverTraversable(): void
{
$entity = new class () extends Entity {
protected $attributes = [
'items' => null,
];
};

$items = new ArrayObjectWithToArray(['iterator' => 'original']);

$entity->items = $items;
$entity->syncOriginal();

$items->exchangeArray(['iterator' => 'modified']);

$this->assertFalse($entity->hasChanged('items'));
}

public function testHasChangedPrefersToArrayOverDateTimeInterface(): void
{
$entity = new class () extends Entity {
protected $attributes = [
'date' => null,
];
};

$date = new class ('2024-01-01 00:00:00') extends DateTime {
/**
* @return array<string, string>
*/
public function toArray(): array
{
return ['date' => 'same'];
}
};

$entity->date = $date;
$entity->syncOriginal();

$date->modify('+1 day');

$this->assertFalse($entity->hasChanged('date'));
}

public function testHasChangedScalarOptimizationWithNullValues(): void
{
$entity = new class () extends Entity {
Expand Down
1 change: 1 addition & 0 deletions user_guide_src/source/changelogs/v4.7.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Bugs Fixed
- **Database:** Fixed a bug where ``BaseConnection::listTables()`` could return a sparse array when using cached table names after a table was dropped.
- **Database:** Fixed a bug where the PostgreSQL driver's ``increment()`` and ``decrement()`` methods were not working for numeric columns.
- **Database:** Fixed a bug where the SQLSRV driver's decrement method was adding instead of subtracting the decrement value when ``$castTextToInt`` was false.
- **Entity:** Fixed a bug where ``Entity::normalizeValue()`` did not handle ``UnitEnum`` before checking for ``toArray()``, causing enums implementing ``toArray()`` to be incorrectly normalized as generic objects instead of enums.
- **Kint:** Fixed a bug where stale Content Security Policy nonces were reused in worker mode, causing browser CSP violations for Debug Toolbar assets.
- **Language:** Fixed a bug where ``Language::getLine()`` returned the literal dot-notation key instead of the nested array value when the requested key resolved to an intermediate array three or more levels deep.
- **Toolbar:** Fixed a bug where the Logs collector raised an undefined property error when using a third-party PSR-3 logger.
Expand Down
Loading