Calling setAssociationOverride in prePersist/preUpdate throws an exception #7516

Open
opened 2026-01-22 15:52:45 +01:00 by admin · 13 comments
Owner

Originally created by @jkabat on GitHub (May 30, 2025).

Bug Report

After upgrade to v3 I'm facing following exception "Typed property Doctrine\ORM\Mapping\ClassMetadata::$namingStrategy must not be accessed before initialization"

It might be caused by the fact naming strategy service is removed in compiler pass?

Q A
Version 3.3.2
Previous working version 2.20.2
Originally created by @jkabat on GitHub (May 30, 2025). ### Bug Report After upgrade to v3 I'm facing following exception "Typed property Doctrine\ORM\Mapping\ClassMetadata::$namingStrategy must not be accessed before initialization" It might be caused by the fact naming strategy service is removed in compiler pass? | Q | A |-------------------------------------------- | ------ | Version | 3.3.2 | Previous working version | 2.20.2
Author
Owner

@jkabat commented on GitHub (Jun 2, 2025):

During debugging I've found out removal of following config resolves the issue

    doctrine:
        orm:
            metadata_cache_driver:
                type: pool
                pool: doctrine.system_cache_pool

I will do more tests, but in the past I had to keep the setting for one of my bundles.

@ostrolucky could it be a problem with DoctrineBundle?
Also removing metadata_cache_driver can lead to performance degradation?

@jkabat commented on GitHub (Jun 2, 2025): During debugging I've found out removal of following config resolves the issue ```yaml doctrine: orm: metadata_cache_driver: type: pool pool: doctrine.system_cache_pool ``` I will do more tests, but in the past I had to keep the setting for one of my bundles. @ostrolucky could it be a problem with DoctrineBundle? Also removing metadata_cache_driver can lead to performance degradation?
Author
Owner

@ostrolucky commented on GitHub (Jun 2, 2025):

I don't think accessing uninitialized property is a problem with a bundle

@ostrolucky commented on GitHub (Jun 2, 2025): I don't think accessing uninitialized property is a problem with a bundle
Author
Owner

@jkabat commented on GitHub (Jun 2, 2025):

@ostrolucky thank you for quick response

@jkabat commented on GitHub (Jun 2, 2025): @ostrolucky thank you for quick response
Author
Owner

@jkabat commented on GitHub (Jun 2, 2025):

metadata_cache_driver removal was false positive. Sometimes it works, sometimes it is not and since it only happens in PROD environment I cant easily find the logic behind it.

My containers are having cache cleared before first usage. When I'm debugging, after running cache:warmup and reloading of phpfpm (clearing opcode cache), I saw few times that ClassMetadata had namingStrategy properly instantiated and everything worked. By repeating the request it failed again.

EDIT:

  • seems like after cache:warmup it works all the time, after cache:clear never.
  • cache:warmup is only one time go, next request fails again

Any clues @greg0ire ?

@jkabat commented on GitHub (Jun 2, 2025): metadata_cache_driver removal was false positive. Sometimes it works, sometimes it is not and since it only happens in PROD environment I cant easily find the logic behind it. My containers are having cache cleared before first usage. When I'm debugging, after running cache:warmup and reloading of phpfpm (clearing opcode cache), I saw few times that ClassMetadata had namingStrategy properly instantiated and everything worked. By repeating the request it failed again. EDIT: * seems like after cache:warmup it works all the time, after cache:clear never. * cache:warmup is only one time go, next request fails again Any clues @greg0ire ?
Author
Owner

@jkabat commented on GitHub (Jun 3, 2025):

I realized doctrine metadata are using 2 cache drivers in my production environment (depends of the cache driver setup): APC and PHPArray. When class metadata are reloaded from APC cache, "namingStrategy" property was not warmed up/instantiated, hence the exception.

Typed property Doctrine\ORM\Mapping\ClassMetadata::$namingStrategy must not be accessed before initialization

It happens on line https://github.com/doctrine/orm/blob/3.3.x/src/Mapping/ClassMetadata.php#L1396

However disabling of APC cache did not solve the problem. I wonder how come nobody has not found the issue yet...

@jkabat commented on GitHub (Jun 3, 2025): I realized doctrine metadata are using 2 cache drivers in my production environment (depends of the cache driver setup): APC and PHPArray. When class metadata are reloaded from APC cache, "namingStrategy" property was not warmed up/instantiated, hence the exception. ``` Typed property Doctrine\ORM\Mapping\ClassMetadata::$namingStrategy must not be accessed before initialization ``` It happens on line https://github.com/doctrine/orm/blob/3.3.x/src/Mapping/ClassMetadata.php#L1396 However disabling of APC cache did not solve the problem. I wonder how come nobody has not found the issue yet...
Author
Owner

@greg0ire commented on GitHub (Jun 14, 2025):

I'm reproducing the bug by adding $addressMetadata = unserialize(serialize($addressMetadata)); here: 05c8c5f114/tests/Tests/ORM/Mapping/ClassMetadataTest.php (L776)

The "bug" is slightly different if I try it on 2.x (it complains about the object being null, because there is no property type).

I'm using quotes because nobody ever complained about this on 2.x, where the code is quite different and maybe mapField and setAttributeOverride on 2.x are never called on a class metadata pulled from the cache, and maybe I failed to preserve this while refactoring this for 3.x 🤔

It would be great if you could get a stack trace for your error, so that we can understand which method calls _validateAndCompleteAssociationMapping

@greg0ire commented on GitHub (Jun 14, 2025): I'm reproducing the bug by adding `$addressMetadata = unserialize(serialize($addressMetadata));` here: https://github.com/doctrine/orm/blob/05c8c5f114fe9c8540e105f5f1c4f592253bf116/tests/Tests/ORM/Mapping/ClassMetadataTest.php#L776 The "bug" is slightly different if I try it on 2.x (it complains about the object being `null`, because there is no property type). I'm using quotes because nobody ever complained about this on 2.x, where the code is quite different and maybe `mapField` and `setAttributeOverride` on 2.x are never called on a class metadata pulled from the cache, and maybe I failed to preserve this while refactoring this for 3.x 🤔 It would be great if you could get a [stack trace](https://symfony.com/doc/current/contributing/code/stack_trace.html) for your error, so that we can understand which method calls `_validateAndCompleteAssociationMapping`
Author
Owner

@Bryce-Colton commented on GitHub (Jun 16, 2025):

We are experiencing a similar issue.

We have some code that manipulate metadata to add some relation at runtime (to handle custom fields).
Here is the sample :

$metadata->mapOneToOne([
                    'fieldName' => $entityField,
                    'targetEntity' => $refClassName,
                    'joinColumns' => [[
                        'name' => $databaseField,
                        'referencedColumnName' => 'id',
                        'nullable' => true,
                    ]],
                ]);

If container running in

  • APP_ENV=dev
  • APP_DEBUG=0

We got "Typed property Doctrine\ORM\Mapping\ClassMetadata::$namingStrategy must not be accessed before initialization"

If container running in

  • APP_ENV=dev
  • APP_DEBUG=1

It works like expected.

NB : Clear cache command is executed each time container are recreated with env changes. We have reproduced on remote staging server and on local machine (where cache files are mounted to the container).

We hope this helps you identify the root cause of these bugs.

Here the stack trace from profiler (see Request/Response => exception)

Image

Html report : doctrine-orm-stack-trace.txt

@Bryce-Colton commented on GitHub (Jun 16, 2025): We are experiencing a similar issue. We have some code that manipulate metadata to add some relation at runtime (to handle custom fields). Here is the sample : ```php $metadata->mapOneToOne([ 'fieldName' => $entityField, 'targetEntity' => $refClassName, 'joinColumns' => [[ 'name' => $databaseField, 'referencedColumnName' => 'id', 'nullable' => true, ]], ]); ``` If container running in - APP_ENV=dev - APP_DEBUG=0 We got "Typed property Doctrine\ORM\Mapping\ClassMetadata::$namingStrategy must not be accessed before initialization" If container running in - APP_ENV=dev - APP_DEBUG=1 It works like expected. NB : Clear cache command is executed each time container are recreated with env changes. We have reproduced on remote staging server and on local machine (where cache files are mounted to the container). We hope this helps you identify the root cause of these bugs. Here the stack trace from profiler (see Request/Response => exception) <img width="671" alt="Image" src="https://github.com/user-attachments/assets/d6b7ab2c-5835-492f-87af-e83f6caeb77e" /> Html report : [doctrine-orm-stack-trace.txt](https://github.com/user-attachments/files/20756433/doctrine-orm-stack-trace.txt)
Author
Owner

@greg0ire commented on GitHub (Jun 16, 2025):

@jkabat are you maybe manipulating metadata at runtime as well?

@Bryce-Colton why does it need to be at runtime in your case? Seems inefficient 🤔

@greg0ire commented on GitHub (Jun 16, 2025): @jkabat are you maybe manipulating metadata at runtime as well? @Bryce-Colton why does it need to be at runtime in your case? Seems inefficient 🤔
Author
Owner

@jkabat commented on GitHub (Jun 17, 2025):

@greg0ire yes, I'm overriding association before specific PERSIST/UPDATE. Unfortunately I couldn't find proper way to make it work without this "hack" (default association is OK for all hydrate operations).

Exception happens in this method:

        $md->setAssociationOverride('callPhone', [
            'joinColumns' => [[
                'name' => $columnName,
                'unique' => false,
                'nullable' => true,
                'onDelete' => null,
                'columnDefinition' => null,
                'referencedColumnName' => 'number',
            ]],
        ]);

...and then in https://github.com/doctrine/orm/blob/3.3.x/src/Mapping/ClassMetadata.php#L1396 as said before.

Temporary solution for me is to bind namingStrategy property of ClassMetadata with the value from doctrine configuration just before calling setAssociationOverride.

@jkabat commented on GitHub (Jun 17, 2025): @greg0ire yes, I'm overriding association before specific PERSIST/UPDATE. Unfortunately I couldn't find proper way to make it work without this "hack" (default association is OK for all hydrate operations). Exception happens in this method: ```php $md->setAssociationOverride('callPhone', [ 'joinColumns' => [[ 'name' => $columnName, 'unique' => false, 'nullable' => true, 'onDelete' => null, 'columnDefinition' => null, 'referencedColumnName' => 'number', ]], ]); ``` ...and then in `https://github.com/doctrine/orm/blob/3.3.x/src/Mapping/ClassMetadata.php#L1396` as said before. Temporary solution for me is to bind namingStrategy property of ClassMetadata with the value from doctrine configuration just before calling setAssociationOverride.
Author
Owner

@greg0ire commented on GitHub (Jun 17, 2025):

How / why was it working in 2.x ? Maybe it was because the naming strategy, which was, I think, null, was never actually used in code triggered by setAssociationOverride? I guess that code includes _validateAndCompleteAssociationMapping at some point?

@greg0ire commented on GitHub (Jun 17, 2025): How / why was it working in 2.x ? Maybe it was because the naming strategy, which was, I think, `null`, was never actually used in code triggered by `setAssociationOverride`? I guess that code includes `_validateAndCompleteAssociationMapping` at some point?
Author
Owner

@jkabat commented on GitHub (Jun 18, 2025):

Yes, setAssociationOverride triggers _validateAndCompleteAssociationMapping. In v2 was not all properties strict typed and maybe in case of NULL, default namingStrategy was used. Hence no exception.

@jkabat commented on GitHub (Jun 18, 2025): Yes, `setAssociationOverride` triggers `_validateAndCompleteAssociationMapping.` In v2 was not all properties strict typed and maybe in case of NULL, default namingStrategy was used. Hence no exception.
Author
Owner

@Bryce-Colton commented on GitHub (Jun 18, 2025):

I can confirm that the bug only occurs when attempting to modify the metadata by calling methods that internally trigger _validateAndCompleteAssociationMapping() (e.g. setAssociationOverride(), mapOneToOne(), etc.).

Like @jkabat, I implemented a workaround to ensure that the namingStrategy property is initialized before calling these methods.

Here is the patch I applied: gist

And an example of how I use it in my code:


$metadata = $this->entityManager->getClassMetadata($entityClassName);
$namingStrategy = $this->entityManager->getConfiguration()->getNamingStrategy();
DoctrineMetadataPatcher::ensureNamingStrategy($metadata, $namingStrategy);

$metadata->mapOneToOne(...);

@greg0ire : Thank you in advance for the work you're doing on this — it’s not an easy one, but your contribution is really appreciated.

@Bryce-Colton commented on GitHub (Jun 18, 2025): I can confirm that the bug only occurs when attempting to modify the metadata by calling methods that internally trigger _validateAndCompleteAssociationMapping() (e.g. setAssociationOverride(), mapOneToOne(), etc.). Like @jkabat, I implemented a workaround to ensure that the namingStrategy property is initialized before calling these methods. Here is the patch I applied: [gist](https://gist.github.com/Bryce-Colton/fb26aa8961f3e389135632d586a9b66e) And an example of how I use it in my code: ```php $metadata = $this->entityManager->getClassMetadata($entityClassName); $namingStrategy = $this->entityManager->getConfiguration()->getNamingStrategy(); DoctrineMetadataPatcher::ensureNamingStrategy($metadata, $namingStrategy); $metadata->mapOneToOne(...); ``` @greg0ire : Thank you in advance for the work you're doing on this — it’s not an easy one, but your contribution is really appreciated.
Author
Owner

@jkabat commented on GitHub (Jun 18, 2025):

Thanks for sharing your workaround @Bryce-Colton

I used closure bind technique (🙈) to initialize private property (until I get rid of setAssociationOverride or the issue gets a proper fix).

    private function bindNamingStrategy(ClassMetadata $md, EntityManagerInterface $entityManager): void
    {
        $function = function (NamingStrategy $namingStrategy): void {
            /** @phpstan-ignore-next-line */
            $this->namingStrategy = $namingStrategy;
        };

        $closure = \Closure::bind($function, $md, $md);
        $closure($entityManager->getConfiguration()->getNamingStrategy());
    }
@jkabat commented on GitHub (Jun 18, 2025): Thanks for sharing your workaround @Bryce-Colton I used closure bind technique (🙈) to initialize private property (until I get rid of setAssociationOverride or the issue gets a proper fix). ```php private function bindNamingStrategy(ClassMetadata $md, EntityManagerInterface $entityManager): void { $function = function (NamingStrategy $namingStrategy): void { /** @phpstan-ignore-next-line */ $this->namingStrategy = $namingStrategy; }; $closure = \Closure::bind($function, $md, $md); $closure($entityManager->getConfiguration()->getNamingStrategy()); } ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7516