{"record":{"id":"78813403633551a7","repo":"sebastianbergmann/phpunit","slug":"trying-to-double-property-s-of-class-s-with-788134","errorCode":null,"errorMessage":"Trying to double property \"%s\" of class \"%s\" with doubleProperties(), but it is static","messagePattern":"Trying to double property \"(.+?)\" of class \"(.+?)\" with doubleProperties\\(\\), but it is static","errorType":"exception","errorClass":"PropertyCannotBeDoubledException","httpStatus":null,"severity":"error","filePath":"src/Framework/MockObject/TestDoubleBuilder.php","lineNumber":135,"sourceCode":"                $e->getCode(),\n                $e,\n            );\n            // @codeCoverageIgnoreEnd\n        }\n\n        foreach ($properties as $propertyName) {\n            if (!$reflector->hasProperty($propertyName)) {\n                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;","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Framework/MockObject/TestDoubleBuilder.php#L117-L153","documentation":"doubleProperties() generates per-instance get/set hooks for doubled properties, but static properties are per-class state shared across all instances, so instance hooks are meaningless for them. After existence and public checks pass, ReflectionProperty::isStatic() returning true triggers PropertyCannotBeDoubledException with reason 'it is static' (TestDoubleBuilder.php:134-136). Only non-static public instance properties can be doubled.","triggerScenarios":"$this->getMockBuilder(Registry::class)->doubleProperties(['instances']) where Registry declares public static array $instances: the property exists and is public, but isStatic() is true, so the 'it is static' exception is thrown from the validation loop and the builder stops before generating the double.","commonSituations":"Registry/configuration/counter classes that keep global state in public static fields; refactor introducing a static cache property on a class whose other properties were being doubled; test authors reaching for property doubling to reset or fake global static state; static properties under inheritance (declared on a parent) behaving differently from the subclass the test mocks.","solutions":["Do not double the static property; fake static state through the class's static methods (mock a non-static collaborator instead, or redesign to instance state / dependency injection).","Reset static state in setUp()/tearDown() directly (Registry::$instances = []) rather than mocking it.","If the design allows, convert the property to a public non-static instance property so doubling applies.","Remove the name from doubleProperties() — static properties are categorically unsupported and the test must change approach."],"exampleFix":"// before\nclass Registry { public static array $instances = []; }\n$mock = $this->getMockBuilder(Registry::class)\n             ->doubleProperties(['instances']) // 'it is static'\n             ->getMock();\n\n// after: reset the static state, no property doubling\nprotected function tearDown(): void\n{\n    Registry::$instances = [];\n}","handlingStrategy":"validation","validationCode":"$reflector = new ReflectionClass(Registry::class);\nforeach ($props as $name) {\n    if ($reflector->hasProperty($name) && $reflector->getProperty($name)->isStatic()) {\n        self::fail(\"\\${$name} is static — reset it in setUp/tearDown instead of doubling\");\n    }\n}","typeGuard":"/** @param list<non-empty-string> $props @return list<non-empty-string> */\nfunction instancePropertiesOnly(string $class, array $props): array\n{\n    $r = new ReflectionClass($class);\n    return array_values(array_filter(\n        $props,\n        static fn (string $p): bool => $r->hasProperty($p) && !$r->getProperty($p)->isStatic(),\n    ));\n}","tryCatchPattern":"use PHPUnit\\Framework\\MockObject\\PropertyCannotBeDoubledException;\n\ntry {\n    $builder->doubleProperties($names);\n} catch (PropertyCannotBeDoubledException $e) {\n    if (str_contains($e->getMessage(), 'it is static')) {\n        // handle static state directly, no doubling\n        Registry::$instances = [];\n        $builder = $this->getMockBuilder(Registry::class); // rebuild without the static prop\n    } else {\n        throw $e;\n    }\n}","preventionTips":["Never list static properties in doubleProperties(); they cannot be doubled by design.","Reset static state in setUp()/tearDown() explicitly when tests touch classes with global state.","Prefer injecting stateful collaborators over static registries so mocks can replace them.","When refactoring a property to static, search tests for doubleProperties() usages in the same change."],"tags":["phpunit","mockobject","property-doubling","static","reflection"],"backgroundTag":"mocking-static-member","analyzedSha":"f123cdb2a2d49f15025794166cfed8bda8627dd2","analyzedAt":"2026-08-23T01:20:58.058Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}