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

Closed
opened 2026-01-22 15:50:25 +01:00 by admin · 11 comments
Owner

Originally created by @valkars on GitHub (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

Originally created by @valkars on GitHub (Mar 20, 2024). ### Bug Report <!-- Fill in the relevant information below to help triage your issue. --> | Q | A |------------ | ------ | BC Break | no | Version | 3.1.0 #### Summary <!-- Provide a summary describing the problem you are experiencing. --> 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 <!-- What is the current (buggy) behavior? --> Fatal error #### How to reproduce <!-- Provide steps to reproduce the bug. If possible, also add a code snippet with relevant configuration, entity mappings, DQL etc. Adding a failing Unit or Functional Test would help us a lot - you can submit one in a Pull Request separately, referencing this bug report. --> 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 <!-- What was the expected (correct) behavior? --> No errors
admin closed this issue 2026-01-22 15:50:25 +01:00
Author
Owner

@greg0ire commented on GitHub (Mar 20, 2024):

Are you using https://github.com/doctrine/persistence/releases/tag/3.3.2 ?

@greg0ire commented on GitHub (Mar 20, 2024): Are you using https://github.com/doctrine/persistence/releases/tag/3.3.2 ?
Author
Owner

@valkars commented on GitHub (Mar 20, 2024):

"name": "doctrine/persistence",
"version": "3.3.1",
@valkars commented on GitHub (Mar 20, 2024): ``` "name": "doctrine/persistence", "version": "3.3.1", ```
Author
Owner

@greg0ire commented on GitHub (Mar 20, 2024):

Please update.

@greg0ire commented on GitHub (Mar 20, 2024): Please update.
Author
Owner

@valkars commented on GitHub (Mar 20, 2024):

Update to 3.3.2 does not help, error remains

@valkars commented on GitHub (Mar 20, 2024): Update to 3.3.2 does not help, error remains
Author
Owner

@valkars commented on GitHub (Mar 20, 2024):

Because bug in ProxyFactory, not in Persistence package

@valkars commented on GitHub (Mar 20, 2024): Because bug in ProxyFactory, not in Persistence package
Author
Owner

@greg0ire commented on GitHub (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 https://github.com/doctrine/persistence/pull/348 (which was already in 3.3.1)

@greg0ire commented on GitHub (Mar 20, 2024): Oh right, I read too fast, and I think your fix is correct :thinking: , in fact I'm a bit surprised I didn't contribute it after contributing https://github.com/doctrine/persistence/pull/348 (which was already in 3.3.1)
Author
Owner

@greg0ire commented on GitHub (Mar 20, 2024):

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 commented on GitHub (Mar 20, 2024): 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.
Author
Owner

@greg0ire commented on GitHub (Mar 20, 2024):

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

@greg0ire commented on GitHub (Mar 20, 2024): Nope, it's not contributed to 2.x yet. Please send a PR :pray:
Author
Owner

@greg0ire commented on GitHub (Mar 20, 2024):

I think this only affects 3.1.x, because this is the branch where I contributed https://github.com/doctrine/orm/pull/11330

@greg0ire commented on GitHub (Mar 20, 2024): I think this only affects 3.1.x, because this is the branch where I contributed https://github.com/doctrine/orm/pull/11330
Author
Owner

@valkars commented on GitHub (Mar 20, 2024):

Made a PR

@valkars commented on GitHub (Mar 20, 2024): Made a PR
Author
Owner

@vytsci commented on GitHub (Mar 4, 2025):

Updated doctrine/persistence to 3.4.0; still, the error persists.

@vytsci commented on GitHub (Mar 4, 2025): Updated doctrine/persistence to 3.4.0; still, the error persists.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7350