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
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToArray.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToArray extends Transform
{
public function __construct()
{
parent::__construct(Type::Array);
}
}
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToBoolean.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToBoolean extends Transform
{
public function __construct()
{
parent::__construct(Type::Boolean);
}
}
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToCollection extends Transform
{
public function __construct()
{
parent::__construct(Type::Collection);
}
}
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToDate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToDate extends Transform
{
public function __construct()
{
parent::__construct(Type::Date);
}
}
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToDateTime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToDateTime extends Transform
{
public function __construct()
{
parent::__construct(Type::DateTime);
}
}
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToFloat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToFloat extends Transform
{
public function __construct()
{
parent::__construct(Type::Float);
}
}
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToInteger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToInteger extends Transform
{
public function __construct()
{
parent::__construct(Type::Integer);
}
}
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToJson.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToJson extends Transform
{
public function __construct()
{
parent::__construct(Type::Json);
}
}
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToObject extends Transform
{
public function __construct()
{
parent::__construct(Type::Object);
}
}
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToString.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToString extends Transform
{
public function __construct()
{
parent::__construct(Type::String);
}
}
15 changes: 15 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/ToTimestamp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class ToTimestamp extends Transform
{
public function __construct()
{
parent::__construct(Type::Timestamp);
}
}
25 changes: 25 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/Attributes/Transform.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Attributes;

use Attribute;
use Phaseolies\Database\Entity\Casts\Type;

#[Attribute(Attribute::TARGET_PROPERTY)]
class Transform
{
/**
* The resolved cast type string.
*
* @var string
*/
public readonly string $type;

/**
* @param Type|string $type
*/
public function __construct(Type|string $type)
{
$this->type = $type instanceof Type ? $type->value : $type;
}
}
167 changes: 167 additions & 0 deletions src/Phaseolies/Database/Entity/Casts/CastManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php

namespace Phaseolies\Database\Entity\Casts;

use Phaseolies\Database\Entity\Casts\Contracts\CastableInterface;
use Phaseolies\Database\Entity\Casts\Handlers\ArrayCast;
use Phaseolies\Database\Entity\Casts\Handlers\BooleanCast;
use Phaseolies\Database\Entity\Casts\Handlers\CollectionCast;
use Phaseolies\Database\Entity\Casts\Handlers\DateCast;
use Phaseolies\Database\Entity\Casts\Handlers\DateTimeCast;
use Phaseolies\Database\Entity\Casts\Handlers\DecimalCast;
use Phaseolies\Database\Entity\Casts\Handlers\EnumCast;
use Phaseolies\Database\Entity\Casts\Handlers\FloatCast;
use Phaseolies\Database\Entity\Casts\Handlers\IntegerCast;
use Phaseolies\Database\Entity\Casts\Handlers\ObjectCast;
use Phaseolies\Database\Entity\Casts\Handlers\StringCast;
use Phaseolies\Database\Entity\Casts\Handlers\TimestampCast;

class CastManager
{
/**
* Map of primitive cast type strings to handler classes.
*
* @var array<string, class-string<CastableInterface>>
*/
private static array $primitives = [
'int' => IntegerCast::class,
'integer' => IntegerCast::class,
'float' => FloatCast::class,
'double' => FloatCast::class,
'real' => FloatCast::class,
'bool' => BooleanCast::class,
'boolean' => BooleanCast::class,
'string' => StringCast::class,
'array' => ArrayCast::class,
'json' => ArrayCast::class,
'object' => ObjectCast::class,
'collection' => CollectionCast::class,
'datetime' => DateTimeCast::class,
'date' => DateCast::class,
'timestamp' => TimestampCast::class,
];

/**
* Cache of resolved cast handler instances keyed by cast string.
*
* @var array<string, CastableInterface>
*/
private static array $resolved = [];

/**
* Cast a raw database value to its PHP type on get.
*
* @param string $cast
* @param mixed $value
* @return mixed
*/
public static function get(string $cast, mixed $value): mixed
{
if ($value === null) {
return null;
}

return self::resolve($cast)->get($value);
}

/**
* Cast a PHP value to a database-compatible format on set.
*
* @param string $cast
* @param mixed $value
* @return mixed
*/
public static function set(string $cast, mixed $value): mixed
{
if ($value === null) {
return null;
}

return self::resolve($cast)->set($value);
}

/**
* Resolve the cast handler instance for a given cast string
*
* @param string $cast
* @return CastableInterface
* @throws \InvalidArgumentException
*/
public static function resolve(string $cast): CastableInterface
{
if (isset(self::$resolved[$cast])) {
return self::$resolved[$cast];
}

// Primitive cast — 'integer', 'float', 'boolean', etc.
if (isset(self::$primitives[$cast])) {
return self::$resolved[$cast] = new (self::$primitives[$cast])();
}

// Parameterised decimal cast — 'decimal:2', 'decimal:4'
if (str_starts_with($cast, 'decimal:')) {
$precision = (int) explode(':', $cast, 2)[1];
return self::$resolved[$cast] = new DecimalCast($precision);
}

// Parameterised datetime cast — 'datetime:d/m/Y'
if (str_starts_with($cast, 'datetime:')) {
$format = explode(':', $cast, 2)[1];
return self::$resolved[$cast] = new DateTimeCast($format);
}

// Enum cast — any PHP backed or unit enum
if (enum_exists($cast)) {
return self::$resolved[$cast] = new EnumCast($cast);
}

// Custom cast class — must implement CastableInterface
if (class_exists($cast) && is_subclass_of($cast, CastableInterface::class)) {
return self::$resolved[$cast] = new $cast();
}

throw new \InvalidArgumentException(
"Unsupported cast type [{$cast}]. Use a built-in type, a backed enum, or a class implementing CastableInterface."
);
}

/**
* Flush the resolved cast handler cache.
*
* @return void
*/
public static function flush(): void
{
self::$resolved = [];
}

/**
* Determine if a cast string is a complex type that requires
* set-casting before writing to the database.
*
* @param string $cast
* @return bool
*/
public static function requiresSetCast(string $cast): bool
{
$complex = ['array', 'json', 'object', 'collection', 'datetime', 'date', 'timestamp'];

if (in_array($cast, $complex, true)) {
return true;
}

if (str_starts_with($cast, 'decimal:') || str_starts_with($cast, 'datetime:')) {
return true;
}

if (enum_exists($cast)) {
return true;
}

if (class_exists($cast) && is_subclass_of($cast, CastableInterface::class)) {
return true;
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Phaseolies\Database\Entity\Casts\Contracts;

interface CastableInterface
{
/**
* Cast the raw database value to a PHP type on get.
*
* @param mixed $value
* @return mixed
*/
public function get(mixed $value): mixed;

/**
* Cast the PHP value to a database-compatible format on set.
*
* @param mixed $value
* @return mixed
*/
public function set(mixed $value): mixed;
}
Loading
Loading