Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Issue with Single Table Inheritance (STI) and Type Casting in Child Entities #440

Open
1 task done
butschster opened this issue Sep 27, 2023 · 1 comment
Open
1 task done
Assignees
Labels
status:to be verified Needs to be reproduced and validated. type:bug Bug

Comments

@butschster
Copy link
Contributor

butschster commented Sep 27, 2023

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.

@butschster butschster added type:bug Bug status:to be verified Needs to be reproduced and validated. labels Sep 27, 2023
@butschster butschster added this to Cycle Sep 27, 2023
@butschster butschster moved this to Backlog in Cycle Sep 27, 2023
@wolfy-j
Copy link
Contributor

wolfy-j commented Jan 23, 2024

Valid usecase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status:to be verified Needs to be reproduced and validated. type:bug Bug
Projects
Status: Backlog
Development

No branches or pull requests

3 participants