-
Notifications
You must be signed in to change notification settings - Fork 0
Custom type
This page shows how to create and declare or override a type on Prime.
Prime types are used at ORM level to parse and normalize entity fields values using methods :
-
TypeInterface::fromDatabase(): parse database value to entity / domain value -
TypeInterface::toDatabase(): normalize entity / domain value to database value
Two levels of types are available :
- Facade types, implementing
FacadeTypeInterface, which performs extra transformation, and forwards storage to an actual database type. This is the base type used to declare a custom type. For example, typesjson,simpleArrayare facade types because they forward to platform types,textin this example. - Platform types, implementing
PlatformTypeInterface, which are low level types directly handled by the platform. This represents types likeint,string...
Types are resolved using their registered name. If a type is natively handled by the platform, the platform type will be used in place of facade type. So types like array or DateTime can be emulated by facade unless platform supports it at low level.
Note: At DBAL level (or if there is no mapping associated to requested column), a type resolution using PHP value is available, by calling
PlatformTypesInterface::resolve().
To create a facade type, you need to extend class Bdf\Prime\Types\AbstractFacadeType.
Then:
- Call parent constructor with type name (you can use
static::classto use type class name as type name). - Implements
fromDatabase()andtoDatabase()to normalize and parse database value. - Implements
phpType()that defines the PHP entity property type (used by entity generator). It can be a PHP type name or a class name. - Implements
defaultType()that defines the fallback platform type name.
Example:
Create a type that stores a configuration class into a JSON string:
<?php
use Bdf\Prime\Types\AbstractFacadeType;
class MyOptions
{
private array $config;
public function __construct(array $config = [])
{
$this->config = $config;
}
public function foo(): ?string
{
return $this->config['foo'] ?? null;
}
public function toArray(): array
{
return $this->config;
}
}
class MyOptionType extends AbstractFacadeType
{
public function __construct()
{
parent::__construct(MyOptions::class);
}
// Normalize object to an ini string
public function toDatabase($value)
{
// Ignore invalid values
if (!$value instanceof MyOptions) {
return null;
}
$ini = '';
foreach ($value->toArray() as $k => $v) {
$ini .= $k . ' = "' . $v . '"' . PHP_EOL;
}
return $ini;
}
// Parse database value to domain object
public function fromDatabase($value, array $fieldOptions = []): ?MyOptions
{
// Database value can be null, so return null if data is empty
if (!$value) {
return null;
}
return new MyOptions(parse_ini_string($value));
}
public function phpType() : string
{
return MyOptions::class;
}
protected function defaultType()
{
// ini config should be saved as human-readable text
return self::TEXT;
}
}Register the type:
<?php
use Bdf\Prime\ConnectionManager;
use Bdf\Prime\Locatorizable;
use Bdf\Prime\ServiceLocator;
use Bdf\Prime\Connection\ConnectionRegistry;
use Bdf\Prime\Connection\Configuration\ConfigurationResolver;
use Bdf\Prime\Configuration;
// declare your connection manager with a default configuration
$connections = new ConnectionManager(
new ConnectionRegistry(configResolver: new ConfigurationResolver(default: $config = new Configuration()))
);
$connections->declareConnection('myDB', 'mysql://root@127.0.0.1/test');
// Now you can declare the facade type, which will be available on all connections
$config->getTypes()->register(new MyOptionType());
// Create and configure prime ORM
$prime = new ServiceLocator($connections);
$prime->setDI($kernel->getContainer());
$prime->setSerializer($kernel->getContainer()->get('serializer'));
Locatorizable::configure($prime);Use custom type on a field:
<?php
use Bdf\Prime\Mapper\Mapper;
use Bdf\Prime\Mapper\Builder\FieldBuilder;
class UserMapper extends Mapper
{
// ...
/**
* {@inheritdoc}
*/
public function buildFields(FieldBuilder $builder): void
{
// To use custom type, simply call add() with custom type name as 2nd parameter
$builder->add('options', MyOptions::class)->nillable();
//...
}
// ...
}Warning: It's very discouraged to override a platform type. It may cause undefined behaviors !
To create a platform type, extend Bdf\Prime\Platform\AbstractPlatformType and implement missing methods. Constructor should be unmodified : type name will be injected at constructor.
Example:
Type that compresses blob data.
Creation:
<?php
use Bdf\Prime\Platform\AbstractPlatformType;
use Bdf\Prime\Types\PhpTypeInterface;
use Bdf\Prime\Schema\ColumnInterface;
use Doctrine\DBAL\Types\Types;
class CompressedBlob extends AbstractPlatformType
{
public function toDatabase($value)
{
// Compress string to database
return $value ? gzcompress($value) : $value;
}
public function fromDatabase($value, array $fieldOptions = [])
{
// Uncompress from database
return $value ? gzuncompress($value) : $value;
}
public function phpType() : string
{
return PhpTypeInterface::STRING;
}
public function declaration(ColumnInterface $column)
{
// Use doctrine type name
return Types::BLOB;
}
}Register the type:
<?php
use Bdf\Prime\ConnectionManager;
use Bdf\Prime\Locatorizable;
use Bdf\Prime\ServiceLocator;
use Bdf\Prime\Connection\ConnectionRegistry;
use Bdf\Prime\Connection\Configuration\ConfigurationResolver;
use Bdf\Prime\Configuration;
use Bdf\Prime\Types\TypeInterface;
// declare your connection manager with a default configuration
$connections = new ConnectionManager();
$connections->declareConnection('myDB', 'mysql://root@127.0.0.1/test');
// Declare platform type per database connection
$connections->getConnection('myDB')->getConfiguration()->addPlatformType(CompressedBlob::class, TypeInterface::BLOB); // Override "blob" type
// Create and configure prime ORM
$prime = new ServiceLocator($connections);
$prime->setDI($kernel->getContainer());
$prime->setSerializer($kernel->getContainer()->get('serializer'));
Locatorizable::configure($prime);The overridden type is now used natively:
<?php
use Bdf\Prime\Mapper\Mapper;
use Bdf\Prime\Mapper\Builder\FieldBuilder;
class AvatarMapper extends Mapper
{
// ...
/**
* {@inheritdoc}
*/
public function buildFields(FieldBuilder $builder): void
{
// CompressedBlob is used here instead of native blob
$builder->blob('imageData');
//...
}
// ...
}