Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
126 changes: 126 additions & 0 deletions system/DataCaster/Cast/EnumCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

declare(strict_types=1);

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

namespace CodeIgniter\DataCaster\Cast;

use BackedEnum;
use CodeIgniter\DataCaster\Exceptions\CastException;
use ReflectionEnum;
use UnitEnum;

/**
* Class EnumCast
*
* Handles casting for PHP enums (both backed and unit enums)
*
* (PHP) [enum --> value/name] --> (DB driver) --> (DB column) int|string
* [ <-- value/name] <-- (DB driver) <-- (DB column) int|string
*/
class EnumCast extends BaseCast implements CastInterface
{
/**
* @param array<int, string> $params
*/
public static function get(
mixed $value,
array $params = [],
?object $helper = null,
): mixed {
if (! is_string($value) && ! is_int($value)) {
self::invalidTypeValueError($value);
}

$enumClass = $params[0] ?? null;

if ($enumClass === null) {
throw CastException::forMissingEnumClass();
}

if (! enum_exists($enumClass)) {
throw CastException::forNotEnum($enumClass);
}

$reflection = new ReflectionEnum($enumClass);

// Unit enum
if (! $reflection->isBacked()) {
// Unit enum - match by name
foreach ($enumClass::cases() as $case) {
if ($case->name === $value) {
return $case;
}
}

throw CastException::forInvalidEnumCaseName($enumClass, $value);
}

// Backed enum - validate and cast the value to proper type
$backingType = $reflection->getBackingType();

// Cast to proper type (int or string)
if ($backingType->getName() === 'int') {
$value = (int) $value;
} elseif ($backingType->getName() === 'string') {
$value = (string) $value;
}

$enum = $enumClass::tryFrom($value);

if ($enum === null) {
throw CastException::forInvalidEnumValue($enumClass, $value);
}

return $enum;
}

/**
* @param array<int, string> $params
*/
public static function set(
mixed $value,
array $params = [],
?object $helper = null,
): int|string {
if (! is_object($value) || ! enum_exists($value::class)) {
self::invalidTypeValueError($value);
}

// Get the expected enum class
$enumClass = $params[0] ?? null;

if ($enumClass === null) {
throw CastException::forMissingEnumClass();
}

if (! enum_exists($enumClass)) {
throw CastException::forNotEnum($enumClass);
}

// Validate that the enum is of the expected type
if (! $value instanceof $enumClass) {
throw CastException::forInvalidEnumType($enumClass, $value::class);
}

$reflection = new ReflectionEnum($value::class);

// Backed enum - return the properly typed backing value
if ($reflection->isBacked()) {
/** @var BackedEnum $value */
return $value->value;
}

// Unit enum - return the case name
/** @var UnitEnum $value */
return $value->name;
}
}
2 changes: 2 additions & 0 deletions system/DataCaster/DataCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use CodeIgniter\DataCaster\Cast\CastInterface;
use CodeIgniter\DataCaster\Cast\CSVCast;
use CodeIgniter\DataCaster\Cast\DatetimeCast;
use CodeIgniter\DataCaster\Cast\EnumCast;
use CodeIgniter\DataCaster\Cast\FloatCast;
use CodeIgniter\DataCaster\Cast\IntBoolCast;
use CodeIgniter\DataCaster\Cast\IntegerCast;
Expand Down Expand Up @@ -48,6 +49,7 @@ final class DataCaster
'boolean' => BooleanCast::class,
'csv' => CSVCast::class,
'datetime' => DatetimeCast::class,
'enum' => EnumCast::class,
'double' => FloatCast::class,
'float' => FloatCast::class,
'int' => IntegerCast::class,
Expand Down
16 changes: 0 additions & 16 deletions system/Entity/Cast/BaseCast.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,11 @@
*/
abstract class BaseCast implements CastInterface
{
/**
* Get
*
* @param array|bool|float|int|object|string|null $value Data
* @param array $params Additional param
*
* @return array|bool|float|int|object|string|null
*/
public static function get($value, array $params = [])
{
return $value;
}

/**
* Set
*
* @param array|bool|float|int|object|string|null $value Data
* @param array $params Additional param
*
* @return array|bool|float|int|object|string|null
*/
public static function set($value, array $params = [])
{
return $value;
Expand Down
4 changes: 2 additions & 2 deletions system/Entity/Cast/CastInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface CastInterface
* Takes a raw value from Entity, returns its value for PHP.
*
* @param array|bool|float|int|object|string|null $value Data
* @param array $params Additional param
* @param array<int, string> $params Additional param
*
* @return array|bool|float|int|object|string|null
*/
Expand All @@ -36,7 +36,7 @@ public static function get($value, array $params = []);
* Takes a PHP value, returns its raw value for Entity.
*
* @param array|bool|float|int|object|string|null $value Data
* @param array $params Additional param
* @param array<int, string> $params Additional param
*
* @return array|bool|float|int|object|string|null
*/
Expand Down
142 changes: 142 additions & 0 deletions system/Entity/Cast/EnumCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

declare(strict_types=1);

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

namespace CodeIgniter\Entity\Cast;

use BackedEnum;
use CodeIgniter\Entity\Exceptions\CastException;
use ReflectionEnum;
use UnitEnum;

/**
* Class EnumCast
*
* Handles casting for PHP enums (both backed and unit enums)
*/
class EnumCast extends BaseCast
{
/**
* {@inheritDoc}
*/
public static function get($value, array $params = [])
{
$enumClass = $params[0] ?? null;

if ($enumClass === null) {
throw CastException::forMissingEnumClass();
}

if (! enum_exists($enumClass)) {
throw CastException::forNotEnum($enumClass);
}

$reflection = new ReflectionEnum($enumClass);

// Backed enum - validate and cast the value to proper type
if ($reflection->isBacked()) {
$backingType = $reflection->getBackingType();

// Cast to proper type (int or string)
if ($backingType->getName() === 'int') {
$value = (int) $value;
} elseif ($backingType->getName() === 'string') {
$value = (string) $value;
}

$enum = $enumClass::tryFrom($value);

if ($enum === null) {
throw CastException::forInvalidEnumValue($enumClass, $value);
}

return $enum;
}

// Unit enum - match by name
foreach ($enumClass::cases() as $case) {
if ($case->name === $value) {
return $case;
}
}

throw CastException::forInvalidEnumCaseName($enumClass, $value);
}

/**
* {@inheritDoc}
*/
public static function set($value, array $params = []): int|string|null
{
// Get the expected enum class
$enumClass = $params[0] ?? null;

if ($enumClass === null) {
throw CastException::forMissingEnumClass();
}

if (! enum_exists($enumClass)) {
throw CastException::forNotEnum($enumClass);
}

// If it's already an enum object, validate and extract its value
if (is_object($value) && enum_exists($value::class)) {
// Validate that the enum is of the expected type
if (! $value instanceof $enumClass) {
throw CastException::forInvalidEnumType($enumClass, $value::class);
}

$reflection = new ReflectionEnum($value::class);

// Backed enum - return the properly typed backing value
if ($reflection->isBacked()) {
/** @var BackedEnum $value */
return $value->value;
}

// Unit enum - return the case name
/** @var UnitEnum $value */
return $value->name;
}

$reflection = new ReflectionEnum($enumClass);

// Validate backed enum values
if ($reflection->isBacked()) {
$backingType = $reflection->getBackingType();

// Cast to proper type (int or string)
if ($backingType->getName() === 'int') {
$value = (int) $value;
} elseif ($backingType->getName() === 'string') {
$value = (string) $value;
}

if ($enumClass::tryFrom($value) === null) {
throw CastException::forInvalidEnumValue($enumClass, $value);
}

return $value;
}

// Validate unit enum case names - must be a string
$value = (string) $value;

foreach ($enumClass::cases() as $case) {
if ($case->name === $value) {
return $value;
}
}

throw CastException::forInvalidEnumCaseName($enumClass, $value);
}
}
2 changes: 2 additions & 0 deletions system/Entity/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use CodeIgniter\Entity\Cast\BooleanCast;
use CodeIgniter\Entity\Cast\CSVCast;
use CodeIgniter\Entity\Cast\DatetimeCast;
use CodeIgniter\Entity\Cast\EnumCast;
use CodeIgniter\Entity\Cast\FloatCast;
use CodeIgniter\Entity\Cast\IntBoolCast;
use CodeIgniter\Entity\Cast\IntegerCast;
Expand Down Expand Up @@ -92,6 +93,7 @@ class Entity implements JsonSerializable
'csv' => CSVCast::class,
'datetime' => DatetimeCast::class,
'double' => FloatCast::class,
'enum' => EnumCast::class,
'float' => FloatCast::class,
'int' => IntegerCast::class,
'integer' => IntegerCast::class,
Expand Down
Loading
Loading