cycle/orm

๐Ÿ› Issue with Single Table Inheritance (STI) and Type Casting in Child Entities

butschster opened this issue ยท 1 comments

No duplicates ๐Ÿฅฒ.

  • I have searched for a similar issue in our bug tracker and didn't find any solutions.

What happened?

I've encountered an issue regarding Single Table Inheritance (STI) when working with CycleORM. Specifically, the problem arises with type casting in child entities. The ORM seems to be utilizing the parent entity's type cast rules instead of the child's, which is leading to incorrect behavior.

Here's a simplified example to demonstrate the issue:

// Parent entity
#[Entity]
class Asset {
    #[Column(type: 'jsonb', nullable: true, typecast: Data::class)]
    public ?object $data = null;
}

// Child entity
#[Entity]
#[SingleTable(value: 'domain')]
class Domain extends Asset {
    #[Column(type: 'jsonb', nullable: true, typecast: DomainData::class)]
    public ?object $data = null;
}

In this setup, whenever I attempt to fetch a Domain entity from the database, the data property is always being cast using the Data class from the parent Asset entity, ignoring the specified DomainData class in the child entity.

The root of the problem seems to lie in the Cycle\ORM\Service\Implementation\EntityFactory::make method:

blic function make(
    string $role,
    array $data = [],
    int $status = Node::NEW,
    bool $typecast = false
): object {
    // ... snip ...
    $role = $data[LoaderInterface::ROLE_KEY] ?? $role;
    // ... snip ...
    $rRole = $this->resolveRole($role);
    $mapper = $this->mapperProvider->getMapper($rRole);
    $castedData = $typecast ? $mapper->cast($data) : $data;
    // ... snip ...
    $e = $mapper->init($data, $role);
    return $mapper->hydrate($e, $relMap->init($this, $node, $castedData));
}

As seen in the code above, it resolves the role to asset initially, then proceeds to cast the data using this resolved role, which in turn picks up the typecast rules from the Asset class instead of the Domain class.

Moreover, it appears that the ORM schema does not accumulate type cast rules for child entities. Here

I propose a modified approach for the make method to handle STI with type casting correctly, somewhat along the following lines:

public function make(
    string $role,
    array $data = [],
    int $status = Node::NEW,
    bool $typecast = false
): object {
    // 1. Resolve parent role [asset]
    // 2. Find the proper child role [asset_domain] using the discriminator
    // 3. Retrieve the schema with type casters for the child [asset_domain] role
    // 4. Cast data using the proper child [asset_domain] role based on the discriminator
    // 5. Create the child [Domain] object
    // 6. Hydrate the casted data into the child object
}

This modified approach ensures that the correct child role is utilized when casting data, hence respecting the type cast rules defined in the child entities.