{"record":{"id":"b7104d3270be00e5","repo":"sebastianbergmann/phpunit","slug":"trying-to-double-property-s-of-class-s-with-b7104d","errorCode":null,"errorMessage":"Trying to double property \"%s\" of class \"%s\" with doubleProperties(), but it does not declare a type","messagePattern":"Trying to double property \"(.+?)\" of class \"(.+?)\" with doubleProperties\\(\\), but it does not declare a type","errorType":"exception","errorClass":"PropertyCannotBeDoubledException","httpStatus":null,"severity":"error","filePath":"src/Framework/MockObject/TestDoubleBuilder.php","lineNumber":147,"sourceCode":"\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\n     */\n    public function setConstructorArgs(array $arguments): static\n    {\n        $this->constructorArgs = $arguments;","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Framework/MockObject/TestDoubleBuilder.php#L129-L165","documentation":"Thrown by TestDoubleBuilder::doubleProperties() when the target property declares no type. Doubling works by redeclaring the property with generated hooks, and a hooked property in PHP must have a declared type, so PHPUnit rejects untyped properties via ReflectionProperty::hasType().","triggerScenarios":"Calling doubleProperties([...]) with a property that has no type declaration (e.g. 'public $cache;' or a property only documented with a docblock like @var).","commonSituations":"Legacy classes with untyped properties or properties typed only in docblocks; migrating old tests to the new property-doubling API.","solutions":["Add a native type declaration to the property in the production class, then keep it in doubleProperties()","Remove the untyped property from the doubling list","If the type cannot be added, double a method that returns the value instead of the property"],"exampleFix":"// before\nclass Settings {\n    public $cache; // no type -> PropertyCannotBeDoubledException\n}\n\n// after\nclass Settings {\n    public ?array $cache = null; // typed, now doublable\n}","handlingStrategy":"validation","validationCode":"$property = new ReflectionProperty(Settings::class, 'cache');\nif (!$property->hasType()) {\n    // add a native type to Settings::$cache before doubling it\n}","typeGuard":"function isTypedProperty(string $class, string $property): bool\n{\n    return (new ReflectionClass($class))\n        ->getProperty($property)\n        ->hasType();\n}","tryCatchPattern":null,"preventionTips":["Enable strict property typing in CI (e.g. via phpstan level with property type checks) so untyped properties surface early","Do not rely on @var docblocks for properties you intend to double"],"tags":["phpunit","mock-object","test-double","untyped-property","property-hooks"],"backgroundTag":"untyped-property-mock","analyzedSha":"f123cdb2a2d49f15025794166cfed8bda8627dd2","analyzedAt":"2026-08-23T01:20:58.058Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}