{"record":{"id":"d8e72e5a4e3ef8e2","repo":"sebastianbergmann/phpunit","slug":"trying-to-double-property-s-of-class-s-with-d8e72e","errorCode":null,"errorMessage":"Trying to double property \"%s\" of class \"%s\" with doubleProperties(), but it is final","messagePattern":"Trying to double property \"(.+?)\" of class \"(.+?)\" with doubleProperties\\(\\), but it is final","errorType":"exception","errorClass":"PropertyCannotBeDoubledException","httpStatus":null,"severity":"error","filePath":"src/Framework/MockObject/TestDoubleBuilder.php","lineNumber":143,"sourceCode":"                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it does not exist');\n            }\n\n            $property = $reflector->getProperty($propertyName);\n\n            if (!$property->isPublic()) {\n                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is not public');\n            }\n\n            if ($property->isStatic()) {\n                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is static');\n            }\n\n            if ($property->isReadOnly()) {\n                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is readonly');\n            }\n\n            if ($property->isFinal()) {\n                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is final');\n            }\n\n            if (!$property->hasType()) {\n                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it does not declare a type');\n            }\n        }\n\n        $this->doubledProperties = array_merge($this->doubledProperties, $properties);\n\n        return $this;\n    }\n\n    /**\n     * Specifies the arguments for the constructor.\n     *\n     * @param array<mixed> $arguments\n     *\n     * @return $this","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Framework/MockObject/TestDoubleBuilder.php#L125-L161","documentation":"Thrown by TestDoubleBuilder::doubleProperties() when the property you want to double is declared final. A final property cannot be redeclared with property hooks in the generated subclass, so PHPUnit refuses the request after detecting ReflectionProperty::isFinal().","triggerScenarios":"Calling doubleProperties([...]) with a property name that is declared with the 'final' modifier on the class being doubled.","commonSituations":"Codebases adopting PHP final properties for immutability; tests written before the property was made final that still try to hook it.","solutions":["Remove the final property from the list passed to doubleProperties()","Supply the property's value via setConstructorArgs() instead of hook expectations","Rewrite the test to exercise the real property behavior rather than doubling it","Remove 'final' from the property declaration if hook doubling in tests is a hard requirement"],"exampleFix":"// before\n$double = (new TestDoubleBuilder(Order::class))\n    ->doubleProperties(['status']) // 'status' is final\n    ->generate();\n\n// after\n$double = (new TestDoubleBuilder(Order::class))\n    ->doubleProperties(['notes'])\n    ->generate();","handlingStrategy":"type-guard","validationCode":"$property = new ReflectionProperty(Order::class, 'status');\nif ($property->isFinal()) {\n    // skip doubling 'status', use constructor args instead\n}","typeGuard":"/** @param list<non-empty-string> $properties @return list<non-empty-string> */\nfunction nonFinalProperties(string $class, array $properties): array\n{\n    $reflector = new ReflectionClass($class);\n\n    return array_values(array_filter(\n        $properties,\n        static fn (string $name): bool => $reflector->hasProperty($name)\n            && $reflector->getProperty($name)->isPublic()\n            && !$reflector->getProperty($name)->isStatic()\n            && !$reflector->getProperty($name)->isFinal()\n            && $reflector->getProperty($name)->hasType(),\n    ));\n}","tryCatchPattern":null,"preventionTips":["Keep one shared filter that checks public/static/readonly/final/typed before doubling","When marking a production property final, grep tests for doubleProperties() usages naming it"],"tags":["phpunit","mock-object","test-double","final-property","reflection"],"backgroundTag":"final-member-override","analyzedSha":"f123cdb2a2d49f15025794166cfed8bda8627dd2","analyzedAt":"2026-08-23T01:20:58.058Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}