In some cases, you might want to wrap the value using a custom value object (similarly to DateTime columns which are
wrapped as DateTimeImmutable
). It can be achieved by creating a custom column wrapper and typecasting the column value
to it.
In order to define a column wrapper, we have to implement an object with a static factory method.
We will use the UUID column and the castValue()
method as an example.
use Ramsey\Uuid\Uuid as UuidBody;
use Ramsey\Uuid\UuidInterface;
use Cycle\Database\DatabaseInterface;
final class Uuid
{
private function __construct(
private UuidInterface $uuid;
) {
}
public function __toString(): string
{
return $this->uuid->toString();
}
public static function create(): static
{
return new static(
UuidBody::uuid4()
);
}
public static function castValue(string $value, DatabaseInterface $db): static
{
return new static(
UuidBody::fromString($value)
);
}
}
Note
ThecastValue
method will receive the raw value content and the database it's associated with. Make sure to implement the__toString
method on your wrapper to store it in the database. See below how to use a custom serialization strategy.
To assign a column wrapper to an entity use the column option typecast
. You can specify typecast
as any callable:
function name, a method name (using ::
separator or declaring as array).
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Column;
#[Entity]
class User
{
#[Column(type: 'primary')]
private $id;
#[Column(type: 'string', typecast: [Uuid::class, 'castValue'])]
private Uuid $uuid;
}
Note
In the example we've declared a typecast rule to theuuid
field. By default ORM will process the rule using default Typecast Handlers that allows using for callables.
We can use this column wrapper after the schema update:
$user = new User();
$user->uuid = Uuid::create();
$manager = new \Cycle\ORM\EntityManager($orm);
$manager->persist($user);
$manager->run();
The column will be automatically wrapped upon retrieving the entity from the database:
$user = $orm->getRepository(User::class)->findOne();
print_r($user);
To change the wrapped column value you have to create new object:
$user = $orm->getRepository(User::class)->findOne();
$user->uuid = Uuid::create();
$manager = new \Cycle\ORM\EntityManager($orm);
$manager->persist($user);
$manager->run();
In some cases you might want to store values in the database in binary form, you can achieve that by
implementing Cycle\Database\Injection\ValueInterface
in order to gain access to low-level query compilation:
use Ramsey\Uuid\Uuid as UuidBody;
use Ramsey\Uuid\UuidInterface;
use Cycle\Database\DatabaseInterface;
use Cycle\Database\Injection\ValueInterface;
class Uuid implements ValueInterface
{
private function __construct(
private UuidInterface $uuid;
) {
}
public function rawValue(): string
{
return $this->uuid->getBytes();
}
public function rawType(): int
{
return \PDO::PARAM_LOB;
}
public function __toString(): string
{
return $this->uuid->toString();
}
public static function create(): static
{
return new static(
UuidBody::uuid4()
);
}
public static function castValue(string $value, DatabaseInterface $db): static
{
if (is_resource($value)) {
// postgres
$value = fread($value, 16);
}
return new static(
UuidBody::fromBytes($value)
);
}
}
Now, the Uuid column will be stored in a blob form.
You can also implement the Expression or Parameter interfaces in your column wrapper to achieve more complex logic.
The default Typecast Handlers supports for typecast values to Enum classes since ORM v2.2.0.
Note
You should update thecycle/annotated
package to v3.2.0 if you want to use the feature in atributes.
Note
The ORM and the DBAL support Backed Enums only, not Pure Enums.
enum UserType: string
{
case Guest = 'guest';
case User = 'user';
case Admin = 'admin';
}
To use the ability you should specify the enum class name in the column option typecast
.
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Column;
#[Entity]
class User
{
#[Column(type: 'primary')]
private $id;
#[Column(type: 'string', typecast: UserType::class)]
private UserType $type;
}
Note
All incorrect enum values will be casted tonull
. And, of course, you can use theenum
type to create a DB-level restriction:php#[Column(type: 'enum(guest,user,admin)', typecast: UserType::class)]
Column wrapper looks like a magical. When you declared in the column option typecast
a class name, the ORM
will check the class contains the typecast
method. If the method exists then ORM will use that method
(DeclaredClass::typecast()
) to create casted value for entity field.
Note
Cycle ORM had the column wrapers feature since a 1.x version and by the reason the feature has migrated to v2.0.You should consider that magical behavior when you are declaring some
enum
in the typecast column option: ORM could useEnum::typecast
method to create casted value instead of theBackedEnum::tryFrom
.
enum UserType: string
{
case Guest = 'guest';
case User = 'user';
case Admin = 'admin';
public static function typecast(string $value): self
{
return self::from($value);
}
}
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Column;
#[Entity]
class User
{
#[Column(type: 'primary')]
private $id;
#[Column(
type: 'string',
typecast: UserType::class // will be converted to [UserType::class, 'typecast']
)]
private UserType $type;
}