Skip to content

fix: get raw value on uninitialized proxy#12379

Open
gseidel wants to merge 7 commits intodoctrine:3.6.xfrom
gseidel:fix-get-raw-value-on-proxy
Open

fix: get raw value on uninitialized proxy#12379
gseidel wants to merge 7 commits intodoctrine:3.6.xfrom
gseidel:fix-get-raw-value-on-proxy

Conversation

@gseidel
Copy link
Contributor

@gseidel gseidel commented Feb 16, 2026

The RawValuePropertyAccessor returns null from getValue if the accessed object is an uninitialized proxy. This leads to further problems. In my case, the BasicEntityPersister::loadOneToManyCollection function returns wrong objects if the parent is still an uninitialized proxy, because the query's WHERE clause tries to find rows where the ID is NULL.

@derrabus
Copy link
Member

Can you please add a functional test that reproduces your bug?

@gseidel
Copy link
Contributor Author

gseidel commented Feb 17, 2026

@derrabus I was struggling to isolate the bug from my application and was only able to reproduce it at on Mac with version 8.4.16. Check #12380. As you can see, it works fine in CI, and it also looks more like a PHP bug. Originally I just pass the page to a Symfony form.

On the other hand, return ((array) $object)[$this->key] ?? null; seems also not the proper way to access the raw value and still fails for uninitialized proxies. Why not use the native getRawValue function? Especially if the ReflectionProperty is already initialized at this point.

@greg0ire
Copy link
Member

greg0ire commented Feb 17, 2026

Could it be caused by XDebug? It looks similar to #12378

If yes, maybe we should add a conflict with old versions of XDebug in composer.json.

@gseidel
Copy link
Contributor Author

gseidel commented Feb 17, 2026

Yes, I believe this is the same issue, and it's also related to symfony/symfony#61058.

The bug in Xdebug makes it possible for objects to remain uninitialized even when they should have been initialized. As a result, an uninitialized proxy could end up reaching the RawValuePropertyAccessor. In my view, the fact that the accessor couldn't handle proxies properly is a bug in its own right — it's what ultimately caused the broken SQL queries, and it could lead to similar issues again in the future.

In #12378, @fabianbloching independently arrived at the same fix.

@greg0ire
Copy link
Member

In theory, I would be opposed to merging a fix for an upstream bug. In practice, your patch seems to make the code simpler. However, I'm not sure about the performance implications of this. Also, you're modifying the constructor of a class that is not declared as internal (maybe it should be, but still…).

There are existing phpbench tests that might call this piece of code. You can add an exception and run them to see if that's the case. Then, it's possible to save benchmark data and compare the data between your commit and the commit on 3.6.x

@gseidel
Copy link
Contributor Author

gseidel commented Feb 17, 2026

@greg0ire The constructor is private, so there is no BC-Break here. Benchmark is another topic, let me try to figure out if this change has a performance impact.

@greg0ire
Copy link
Member

Ah right. You can check the phpbench docs to figure this out, otherwise I might provide more help later.

@greg0ire
Copy link
Member

greg0ire commented Feb 17, 2026

This is the docs I was hinting at: https://phpbench.readthedocs.io/en/latest/guides/regression-testing.html

If I throw an Exception in getValue(), phpbench fails, and I can see this path is covered by several benchmarks:

\Doctrine\Performance\ChangeSet\UnitOfWorkComputeChangesBench

    benchChangeSetComputation     ERROR.....I0

\Doctrine\Performance\Hydration\MixedQueryFetchJoinArrayHydrationPerformanceBench

    benchHydration..........................I0 - Mo50.068ms (±0.00%)

\Doctrine\Performance\Hydration\MixedQueryFetchJoinFullObjectHydrationPerformanceBench

    benchHydration                ERROR.....I0

\Doctrine\Performance\Hydration\MixedQueryFetchJoinPartialObjectHydrationPerformanceBench

    benchHydration                ERROR.....I0

\Doctrine\Performance\Hydration\SimpleHydrationBench

    benchHydration                ERROR.....I0

\Doctrine\Performance\Hydration\SimpleInsertPerformanceBench

    benchHydration                ERROR.....I0

\Doctrine\Performance\Hydration\SimpleQueryArrayHydrationPerformanceBench

    benchHydration..........................I0 - Mo25.373ms (±0.00%)

\Doctrine\Performance\Hydration\SimpleQueryFullObjectHydrationPerformanceBench

    benchHydration                ERROR.....I0

\Doctrine\Performance\Hydration\SimpleQueryPartialObjectHydrationPerformanceBench

    benchHydration                ERROR.....I0

\Doctrine\Performance\Hydration\SimpleQueryScalarHydrationPerformanceBench

    benchHydration..........................I0 - Mo14.544ms (±0.00%)

\Doctrine\Performance\Hydration\SingleTableInheritanceHydrationPerformanceBench

    benchHydrateFixContracts      ERROR.....I0
    benchHydrateFlexContracts     ERROR.....I0
    benchHydrateUltraContracts    ERROR.....I0
    benchHydrateAllContracts      ERROR.....I0

\Doctrine\Performance\Hydration\SingleTableInheritanceInsertPerformanceBench

    benchInsertFixContracts       ERROR.....I0
    benchInsertFlexContracts      ERROR.....I0
    benchInsertUltraContracts     ERROR.....I0
    benchInsertAllContracts       ERROR.....I0

\Doctrine\Performance\LazyLoading\ProxyInstantiationTimeBench

    benchCmsUserInstantiation...............I0 - Mo61.937ms (±0.00%)
    benchCmsEmployeeInstantiation...........I0 - Mo65.276ms (±0.00%)

\Doctrine\Performance\LazyLoading\ProxyInitializationTimeBench

    benchCmsUserInitialization..............I0 - Mo5.196ms (±0.00%)
    benchCmsEmployeeInitialization..........I0 - Mo5.028ms (±0.00%)
    benchInitializationOfAlreadyInitialized.I0 - Mo2.334ms (±0.00%)
    benchInitializationOfAlreadyInitialized.I0 - Mo2.238ms (±0.00%)

\Doctrine\Performance\Query\QueryBoundParameterProcessingBench

    benchExecuteParsedQueryWithInferredPara.I0 - Mo1.692ms (±0.00%)
    benchExecuteParsedQueryWithDeclaredPara.I0 - Mo1.059ms (±0.00%)

@greg0ire
Copy link
Member

greg0ire commented Feb 17, 2026

Here are the results of the benchmark:

vendor/bin/phpbench run --report=aggregate --ref=original --retry-threshold=2 --iterations=10
PHPBench (1.4.3) running benchmarks... #standwithukraine
with configuration file: /home/greg/dev/doctrine-orm/patch/phpbench.json
with PHP version 8.4.17, xdebug ❌, opcache ❌
comparing [actual vs. original]

\Doctrine\Performance\ChangeSet\UnitOfWorkComputeChangesBench

    benchChangeSetComputation...............R1 I6 - [Mo819.131μs vs. Mo833.016μs] -1.67% (±0.84%)

\Doctrine\Performance\Hydration\MixedQueryFetchJoinArrayHydrationPerformanceBench

    benchHydration..........................R1 I8 - [Mo51.707ms vs. Mo49.932ms] +3.56% (±0.98%)

\Doctrine\Performance\Hydration\MixedQueryFetchJoinFullObjectHydrationPerformanceBench

    benchHydration..........................R1 I9 - [Mo45.207ms vs. Mo44.933ms] +0.61% (±0.99%)

\Doctrine\Performance\Hydration\MixedQueryFetchJoinPartialObjectHydrationPerformanceBench

    benchHydration..........................R1 I9 - [Mo41.417ms vs. Mo41.721ms] -0.73% (±0.93%)

\Doctrine\Performance\Hydration\SimpleHydrationBench

    benchHydration..........................R1 I9 - [Mo123.970ms vs. Mo124.349ms] -0.30% (±0.71%)

\Doctrine\Performance\Hydration\SimpleInsertPerformanceBench

    benchHydration..........................R1 I1 - [Mo33.598ms vs. Mo34.245ms] -1.89% (±0.82%)

\Doctrine\Performance\Hydration\SimpleQueryArrayHydrationPerformanceBench

    benchHydration..........................R1 I7 - [Mo27.313ms vs. Mo25.449ms] +7.32% (±0.76%)

\Doctrine\Performance\Hydration\SimpleQueryFullObjectHydrationPerformanceBench

    benchHydration..........................R1 I9 - [Mo146.873ms vs. Mo146.866ms] +0.01% (±0.74%)

\Doctrine\Performance\Hydration\SimpleQueryPartialObjectHydrationPerformanceBench

    benchHydration..........................R1 I9 - [Mo128.342ms vs. Mo129.334ms] -0.77% (±1.00%)

\Doctrine\Performance\Hydration\SimpleQueryScalarHydrationPerformanceBench

    benchHydration..........................R1 I1 - [Mo15.318ms vs. Mo14.396ms] +6.40% (±0.83%)

\Doctrine\Performance\Hydration\SingleTableInheritanceHydrationPerformanceBench

    benchHydrateFixContracts................R1 I8 - [Mo1.907ms vs. Mo1.909ms] -0.08% (±0.37%)
    benchHydrateFlexContracts...............R1 I0 - [Mo2.285ms vs. Mo2.274ms] +0.50% (±0.79%)
    benchHydrateUltraContracts..............R1 I0 - [Mo2.011ms vs. Mo2.009ms] +0.11% (±0.71%)
    benchHydrateAllContracts................R3 I8 - [Mo2.597ms vs. Mo2.581ms] +0.62% (±0.90%)

\Doctrine\Performance\Hydration\SingleTableInheritanceInsertPerformanceBench

    benchInsertFixContracts.................R1 I3 - [Mo4.562ms vs. Mo4.683ms] -2.57% (±0.63%)
    benchInsertFlexContracts................R2 I8 - [Mo4.726ms vs. Mo4.671ms] +1.18% (±0.74%)
    benchInsertUltraContracts...............R2 I8 - [Mo4.754ms vs. Mo4.854ms] -2.05% (±0.90%)
    benchInsertAllContracts.................R2 I7 - [Mo6.359ms vs. Mo6.368ms] -0.14% (±0.73%)

\Doctrine\Performance\LazyLoading\ProxyInstantiationTimeBench

    benchCmsUserInstantiation...............R1 I6 - [Mo62.749ms vs. Mo62.639ms] +0.18% (±1.08%)
    benchCmsEmployeeInstantiation...........R3 I9 - [Mo61.360ms vs. Mo60.701ms] +1.08% (±1.03%)

\Doctrine\Performance\LazyLoading\ProxyInitializationTimeBench

    benchCmsUserInitialization..............R1 I8 - [Mo5.558ms vs. Mo5.425ms] +2.46% (±0.98%)
    benchCmsEmployeeInitialization..........R1 I9 - [Mo5.298ms vs. Mo5.294ms] +0.08% (±1.07%)
    benchInitializationOfAlreadyInitialized.R1 I3 - [Mo2.262ms vs. Mo2.250ms] +0.52% (±0.82%)
    benchInitializationOfAlreadyInitialized.R2 I9 - [Mo2.221ms vs. Mo2.260ms] -1.75% (±1.14%)

\Doctrine\Performance\Query\QueryBoundParameterProcessingBench

    benchExecuteParsedQueryWithInferredPara.R2 I9 - [Mo1.611ms vs. Mo1.621ms] -0.64% (±0.96%)
    benchExecuteParsedQueryWithDeclaredPara.R2 I9 - [Mo1.044ms vs. Mo1.053ms] -0.87% (±0.87%)

Subjects: 26, Assertions: 0, Failures: 0, Errors: 0
+-----------------------------------------------------------+-----------------------------------------------------+-----+------+-----+-----------------+------------------+-----------------+
| benchmark                                                 | subject                                             | set | revs | its | mem_peak        | mode             | rstdev          |
+-----------------------------------------------------------+-----------------------------------------------------+-----+------+-----+-----------------+------------------+-----------------+
| UnitOfWorkComputeChangesBench                             | benchChangeSetComputation                           |     | 1    | 10  | 5.346mb -0.04%  | 819.131μs -1.67% | ±0.84% -6.04%   |
| MixedQueryFetchJoinArrayHydrationPerformanceBench         | benchHydration                                      |     | 1    | 10  | 28.726mb -0.01% | 51.707ms +3.56%  | ±0.98% +33.50%  |
| MixedQueryFetchJoinFullObjectHydrationPerformanceBench    | benchHydration                                      |     | 1    | 10  | 16.477mb -0.01% | 45.207ms +0.61%  | ±0.99% +7.14%   |
| MixedQueryFetchJoinPartialObjectHydrationPerformanceBench | benchHydration                                      |     | 1    | 10  | 13.904mb -0.02% | 41.417ms -0.73%  | ±0.93% -6.16%   |
| SimpleHydrationBench                                      | benchHydration                                      |     | 1    | 10  | 50.213mb -0.00% | 123.970ms -0.30% | ±0.71% +16.89%  |
| SimpleInsertPerformanceBench                              | benchHydration                                      |     | 1    | 10  | 13.555mb -0.02% | 33.598ms -1.89%  | ±0.82% -19.74%  |
| SimpleQueryArrayHydrationPerformanceBench                 | benchHydration                                      |     | 1    | 10  | 12.907mb -0.01% | 27.313ms +7.32%  | ±0.76% -17.10%  |
| SimpleQueryFullObjectHydrationPerformanceBench            | benchHydration                                      |     | 1    | 10  | 49.553mb -0.00% | 146.873ms +0.01% | ±0.74% -2.15%   |
| SimpleQueryPartialObjectHydrationPerformanceBench         | benchHydration                                      |     | 1    | 10  | 35.777mb -0.01% | 128.342ms -0.77% | ±1.00% +16.01%  |
| SimpleQueryScalarHydrationPerformanceBench                | benchHydration                                      |     | 1    | 10  | 13.027mb -0.01% | 15.318ms +6.40%  | ±0.83% +52.13%  |
| SingleTableInheritanceHydrationPerformanceBench           | benchHydrateFixContracts                            |     | 1    | 10  | 7.031mb -0.11%  | 1.907ms -0.08%   | ±0.37% -27.64%  |
| SingleTableInheritanceHydrationPerformanceBench           | benchHydrateFlexContracts                           |     | 1    | 10  | 7.092mb -0.11%  | 2.285ms +0.50%   | ±0.79% +15.42%  |
| SingleTableInheritanceHydrationPerformanceBench           | benchHydrateUltraContracts                          |     | 1    | 10  | 7.032mb -0.11%  | 2.011ms +0.11%   | ±0.71% +25.40%  |
| SingleTableInheritanceHydrationPerformanceBench           | benchHydrateAllContracts                            |     | 1    | 10  | 7.134mb -0.10%  | 2.597ms +0.62%   | ±0.90% +35.98%  |
| SingleTableInheritanceInsertPerformanceBench              | benchInsertFixContracts                             |     | 1    | 10  | 6.736mb -0.11%  | 4.562ms -2.57%   | ±0.63% -28.51%  |
| SingleTableInheritanceInsertPerformanceBench              | benchInsertFlexContracts                            |     | 1    | 10  | 6.743mb -0.11%  | 4.726ms +1.18%   | ±0.74% -31.33%  |
| SingleTableInheritanceInsertPerformanceBench              | benchInsertUltraContracts                           |     | 1    | 10  | 6.767mb -0.11%  | 4.754ms -2.05%   | ±0.90% -9.76%   |
| SingleTableInheritanceInsertPerformanceBench              | benchInsertAllContracts                             |     | 1    | 10  | 6.964mb -0.11%  | 6.359ms -0.14%   | ±0.73% +14.66%  |
| ProxyInstantiationTimeBench                               | benchCmsUserInstantiation                           |     | 1    | 10  | 5.443mb -0.03%  | 62.749ms +0.18%  | ±1.08% +17.17%  |
| ProxyInstantiationTimeBench                               | benchCmsEmployeeInstantiation                       |     | 1    | 10  | 5.230mb -0.03%  | 61.360ms +1.08%  | ±1.03% +36.51%  |
| ProxyInitializationTimeBench                              | benchCmsUserInitialization                          |     | 1    | 10  | 39.037mb -0.00% | 5.558ms +2.46%   | ±0.98% +9.31%   |
| ProxyInitializationTimeBench                              | benchCmsEmployeeInitialization                      |     | 1    | 10  | 39.037mb -0.00% | 5.298ms +0.08%   | ±1.07% +10.59%  |
| ProxyInitializationTimeBench                              | benchInitializationOfAlreadyInitializedCmsUsers     |     | 1    | 10  | 39.037mb -0.00% | 2.262ms +0.52%   | ±0.82% -29.99%  |
| ProxyInitializationTimeBench                              | benchInitializationOfAlreadyInitializedCmsEmployees |     | 1    | 10  | 39.037mb -0.00% | 2.221ms -1.75%   | ±1.14% +115.18% |
| QueryBoundParameterProcessingBench                        | benchExecuteParsedQueryWithInferredParameterType    |     | 1    | 10  | 6.149mb -0.02%  | 1.611ms -0.64%   | ±0.96% +115.18% |
| QueryBoundParameterProcessingBench                        | benchExecuteParsedQueryWithDeclaredParameterType    |     | 1    | 10  | 6.051mb -0.02%  | 1.044ms -0.87%   | ±0.87% +7.80%   |
+----------------------------------------------------------

As you can see, at least 2 benchmarks are negatively impacted: SimpleQueryArrayHydrationPerformanceBench and SimpleQueryScalarHydrationPerformanceBench, so not by a lot

EDIT: I do not reproduce this result reliably 🤦 This is probably fine from the performance standpoint.

@greg0ire
Copy link
Member

Performance-centric discussion about the array cast: doctrine/persistence#307 (comment)

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 this pull request may close these issues.

3 participants