Every entity in Cycle ORM must have an associated Mapper object. Mappers are responsible for entity creation and hydration, and they must issue a set of persist commands which will be processed later within the transaction.
You can implement your own Mapper using Cycle/ORM/MapperInterface:
The ORM will create a mapper using Spiral\Core\FactoryInterface
which means your Mapper is able to request
dependencies available in the container associated with ORM Factory.
Some parameters will be provided by ORM itself, such as:
You are able to use a single Mapper implementation for multiple entities.
There are different types of mappers out of the box:
Database Mapper is an abstract mapper, that provides basic capabilities to work with entities persisted in SQL databases.
You can implement your own mapper to implement custom entity carrying model but still rely on common SQL functionality. Let's define our model first:
class Entity
{
public function __construct(
private array $data = []
) {
}
public function setData(array $data): void
{
$this->data = $data;
}
public function getData(): array
{
return $this->data;
}
public function __get($name)
{
return $this->data[$name] ?? null;
}
// ...
}
We can now implement our mapper to handle creation and hydration of our entity:
use Cycle\ORM\Schema;
use Cycle\ORM\ORMInterface;
use Cycle\ORM\Mapper\DatabaseMapper;
final class CustomMapper extends DatabaseMapper
{
private string $class;
public function __construct(ORMInterface $orm, string $role)
{
parent::__construct($orm, $role);
// entity class
$this->class = $orm->getSchema()->define($role, Schema::ENTITY);
}
/** @inheritdoc */
public function init(array $data): object
{
$class = $this->class;
return new $class($data);
}
/** @inheritdoc */
public function hydrate(object $entity, array $data): object
{
$entity->setData($data);
return $entity;
}
/** @inheritdoc */
public function extract(object $entity): array
{
return $entity->getData();
}
/**
* Get entity columns.
*/
public function fetchFields(object $entity): array
{
// fetch entity fields and ignore custom columns
return array_intersect_key(
$this->extract($entity),
$this->columns + $this->parentColumns
);
}
public function fetchRelations(object $entity): array
{
return array_intersect_key(
$this->extract($entity),
$this->relationMap->getRelations()
);
}
}
You can now create your entity and associate it with the custom mapper:
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Column;
#[Entity(
mapper: CustomMapper::class
)]
#[Column(type: 'string', name: 'id', property: 'id')]
class User extends Entity
{
}
Update your ORM schema to register the entity. You can use your entity freely after this operation.
ORM can work with different types of entities within one system.
A similar approach can be used to implement AR-like entities. You would have to expose a global instance of ORM in order
to gain access to it from your entity's save()
and delete()
methods:
use Cycle\ORM\EntityManager;
class Entity
{
// ...
public function save(): void
{
(new EntityManager(App::getORM()))->persist($this)->run();
}
public function delete(): void
{
(new EntityManager(App::getORM()))->delete($this)->run();
}
}