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

Error in ProxyFactory using cloned proxy entity with Enum field #11386

Closed
valkars opened this issue Mar 20, 2024 · 10 comments · Fixed by #11387
Closed

Error in ProxyFactory using cloned proxy entity with Enum field #11386

valkars opened this issue Mar 20, 2024 · 10 comments · Fixed by #11387

Comments

@valkars
Copy link

valkars commented Mar 20, 2024

Bug Report

Q A
BC Break no
Version 3.1.0

Summary

I'll use simulated example. We have 2 entities (Cart, Customer) with OneToOne join. Customer entity has PHP Enum field. I fetch Cart from database, get Customer (receive proxy object), make copy with clone. When I try to access data of cloned entity - there is fatal error:
Typed property ReflectionProperty::$name must not be accessed before initialization in Doctrine\ORM\Proxy\ProxyFactory line 237.

foreach ($class->getReflectionProperties() as $property) {
    if (! $property || ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
        continue;
    }

In that cycle $property is a Doctrine\Persistence\Reflection\EnumReflectionProperty object and $name variable is not initialized.
EnumReflectionProperty has method getName() - using this method in ProxyFactory solves the problem.

public function getName(): string
{
    return $this->originalReflectionProperty->getName();
}

Possible fix:

foreach ($class->getReflectionProperties() as $property) {
    if (! $property || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
        continue;

Current behavior

Fatal error

How to reproduce

2 entities, enum object and example to reproduce:

<?php

namespace App\Entity;

use App\Repository\CartRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: CartRepository::class)]
class Cart
{
    #[ORM\Id, ORM\GeneratedValue, ORM\Column]
    private ?int $id = null;

    #[ORM\Column]
    private ?int $amount = null;

    #[ORM\OneToOne(inversedBy: 'cart', cascade: ['persist', 'remove'])]
    private ?Customer $customer = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getAmount(): ?int
    {
        return $this->amount;
    }

    public function setAmount(int $amount): static
    {
        $this->amount = $amount;

        return $this;
    }

    public function getCustomer(): ?Customer
    {
        return $this->customer;
    }

    public function setCustomer(?Customer $customer): self
    {
        $this->customer = $customer;

        return $this;
    }
}
<?php

namespace App\Entity;

use App\Enum\Type;
use App\Repository\CustomerRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: CustomerRepository::class)]
class Customer
{
    #[ORM\Id, ORM\GeneratedValue, ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $name = null;

    #[ORM\Column(type: Types::SMALLINT, nullable: true, enumType: Type::class, options: ['unsigned' => true])]
    private ?Type $type = null;

    #[ORM\OneToOne(mappedBy: 'customer')]
    private ?Cart $cart = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): static
    {
        $this->name = $name;

        return $this;
    }

    public function getType(): ?Type
    {
        return $this->type;
    }

    public function setType(Type $type): static
    {
        $this->type = $type;

        return $this;
    }

    public function getCart(): ?Cart
    {
        return $this->cart;
    }

    public function setCart(?Cart $cart): self
    {
        // unset the owning side of the relation if necessary
        if (null === $cart && null !== $this->cart) {
            $this->cart->setCustomer(null);
        }

        // set the owning side of the relation if necessary
        if (null !== $cart && $cart->getCustomer() !== $this) {
            $cart->setCustomer($this);
        }

        $this->cart = $cart;

        return $this;
    }
}
<?php

namespace App\Enum;

enum Type: int
{
    case MALE = 1;
    case FEMALE = 2;
}
$cart = $repo->find(1);
$customer = clone $cart->getCustomer();
echo $customer->getName();

Expected behavior

No errors

@greg0ire
Copy link
Member

@valkars
Copy link
Author

valkars commented Mar 20, 2024

"name": "doctrine/persistence",
"version": "3.3.1",

@greg0ire greg0ire closed this as not planned Won't fix, can't repro, duplicate, stale Mar 20, 2024
@greg0ire
Copy link
Member

Please update.

@valkars
Copy link
Author

valkars commented Mar 20, 2024

Update to 3.3.2 does not help, error remains

@valkars
Copy link
Author

valkars commented Mar 20, 2024

Because bug in ProxyFactory, not in Persistence package

@greg0ire greg0ire reopened this Mar 20, 2024
@greg0ire
Copy link
Member

greg0ire commented Mar 20, 2024

Oh right, I read too fast, and I think your fix is correct 🤔 , in fact I'm a bit surprised I didn't contribute it after contributing doctrine/persistence#348 (which was already in 3.3.1)

@greg0ire
Copy link
Member

I see you're using 3.1.0, maybe this is just a matter of merging up and releasing a new version, let me check.

@greg0ire
Copy link
Member

Nope, it's not contributed to 2.x yet. Please send a PR 🙏

@greg0ire
Copy link
Member

I think this only affects 3.1.x, because this is the branch where I contributed #11330

@valkars
Copy link
Author

valkars commented Mar 20, 2024

Made a PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants