{"record":{"id":"8aaf7fb693f006e9","repo":"doctrine/orm","slug":"doctrine-orm-does-not-support-property-hook-on-s","errorCode":null,"errorMessage":"Doctrine ORM does not support property hook on %s::%s without using native lazy objects. Check https://github.com/doctrine/orm/issues/11624 for details of versions that support property hooks.","messagePattern":"Doctrine ORM does not support property hook on (.+?)::(.+?) without using native lazy objects\\. Check https://github\\.com/doctrine/orm/issues/11624 for details of versions that support property hooks\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/Proxy/ProxyFactory.php","lineNumber":341,"sourceCode":"\n        return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . InternalProxy::MARKER\n            . str_replace('\\\\', '', $className) . '.php';\n    }\n\n    private function getProxyFactory(string $className): Closure\n    {\n        $skippedProperties = [];\n        $class             = $this->em->getClassMetadata($className);\n        $identifiers       = array_flip($class->getIdentifierFieldNames());\n        $filter            = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE;\n        $reflector         = $class->getReflectionClass();\n\n        while ($reflector) {\n            foreach ($reflector->getProperties($filter) as $property) {\n                $name = $property->name;\n\n                if (PHP_VERSION_ID >= 80400 && count($property->getHooks()) > 0) {\n                    throw new LogicException(sprintf(\n                        'Doctrine ORM does not support property hook on %s::%s without using native lazy objects. Check https://github.com/doctrine/orm/issues/11624 for details of versions that support property hooks.',\n                        $property->getDeclaringClass()->getName(),\n                        $property->getName(),\n                    ));\n                }\n\n                if ($property->isStatic() || ! isset($identifiers[$name])) {\n                    continue;\n                }\n\n                $prefix = $property->isPrivate() ? \"\\0\" . $property->class . \"\\0\" : ($property->isProtected() ? \"\\0*\\0\" : '');\n\n                $skippedProperties[$prefix . $name] = true;\n            }\n\n            $filter    = ReflectionProperty::IS_PRIVATE;\n            $reflector = $reflector->getParentClass();\n        }","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/doctrine/orm/blob/d9b9ff73016bf598ae07515f97289ce8074e97a5/src/Proxy/ProxyFactory.php#L323-L359","documentation":"Doctrine's reflection-based lazy ghosts/proxies cannot intercept PHP 8.4 property hooks. When ProxyFactory builds a proxy (on first lazy load or getReference()) it walks every property of the class, and on PHP >= 8.4 any property with hooks makes it throw immediately — unless native lazy objects are enabled, which use the engine's lazy-object mechanism that supports hooks. Tracked in issue #11624.","triggerScenarios":"PHP >= 8.4, an entity (or its parent classes) declares a property with a get/set hook, and a proxy gets created for it — e.g., $em->getReference(User::class, $id) or accessing a lazy to-one association — while native lazy objects are NOT enabled via Configuration::enableNativeLazyObjects(true) (ORM 3.3+).","commonSituations":"Upgrading PHP to 8.4 and adopting property hooks (virtual hooks, `set` validation) on entities with existing lazy associations; new projects on 8.4 using hooks before enabling the ORM's native lazy object support.","solutions":["Enable native lazy objects: $config->enableNativeLazyObjects(true) (requires doctrine/orm >= 3.3 with PHP >= 8.4)","Remove property hooks from proxied entity classes","Upgrade doctrine/orm to a release supporting property hooks (see issue #11624) and follow its upgrade notes"],"exampleFix":"// before (PHP 8.4, no native lazy objects)\npublic string $name {\n    set (string $value) { $this->name = trim($value); }\n}\n// $em->getReference(User::class, 1) throws\n\n// after\n$config = ORMSetup::createAttributeMetadataConfiguration([...], true);\n$config->enableNativeLazyObjects(true);","handlingStrategy":"validation","validationCode":"if (PHP_VERSION_ID >= 80400) {\n    foreach ((new ReflectionClass($entityClass))->getProperties() as $prop) {\n        if (count($prop->getHooks()) > 0 && ! $config->isNativeLazyObjectsEnabled()) {\n            throw new RuntimeException('Enable native lazy objects or remove hooks on ' . $entityClass);\n        }\n    }\n}","typeGuard":"function entityHasPropertyHooks(string $class): bool\n{\n    if (PHP_VERSION_ID < 80400) { return false; }\n    foreach ((new ReflectionClass($class))->getProperties() as $p) {\n        if (count($p->getHooks()) > 0) { return true; }\n    }\n    return false;\n}","tryCatchPattern":null,"preventionTips":["Enable native lazy objects when adopting PHP 8.4 property hooks on entities","Add a CI check on PHP 8.4 that loads one proxy per entity","Keep hooked properties out of entities until the ORM version supports them"],"tags":["php-8-4","property-hooks","proxy","lazy-loading"],"backgroundTag":"php-property-hooks-unsupported","analyzedSha":"d9b9ff73016bf598ae07515f97289ce8074e97a5","analyzedAt":"2026-08-21T06:13:15.863Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}