sebastianbergmann/phpunit · error · PropertyCannotBeDoubledException

Trying to double property "%s" of class "%s" with doubleProp

Error message

Trying to double property "%s" of class "%s" with doubleProperties(), but it is static

What it means

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.

Source

Thrown at src/Framework/MockObject/TestDoubleBuilder.php:135

                $e->getCode(),
                $e,
            );
            // @codeCoverageIgnoreEnd
        }

        foreach ($properties as $propertyName) {
            if (!$reflector->hasProperty($propertyName)) {
                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it does not exist');
            }

            $property = $reflector->getProperty($propertyName);

            if (!$property->isPublic()) {
                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is not public');
            }

            if ($property->isStatic()) {
                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is static');
            }

            if ($property->isReadOnly()) {
                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is readonly');
            }

            if ($property->isFinal()) {
                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is final');
            }

            if (!$property->hasType()) {
                throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it does not declare a type');
            }
        }

        $this->doubledProperties = array_merge($this->doubledProperties, $properties);

        return $this;

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. 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).
  2. Reset static state in setUp()/tearDown() directly (Registry::$instances = []) rather than mocking it.
  3. If the design allows, convert the property to a public non-static instance property so doubling applies.
  4. Remove the name from doubleProperties() — static properties are categorically unsupported and the test must change approach.

Example fix

// before
class Registry { public static array $instances = []; }
$mock = $this->getMockBuilder(Registry::class)
             ->doubleProperties(['instances']) // 'it is static'
             ->getMock();

// after: reset the static state, no property doubling
protected function tearDown(): void
{
    Registry::$instances = [];
}
Defensive patterns

Strategy: validation

Validate before calling

$reflector = new ReflectionClass(Registry::class);
foreach ($props as $name) {
    if ($reflector->hasProperty($name) && $reflector->getProperty($name)->isStatic()) {
        self::fail("\${$name} is static — reset it in setUp/tearDown instead of doubling");
    }
}

Type guard

/** @param list<non-empty-string> $props @return list<non-empty-string> */
function instancePropertiesOnly(string $class, array $props): array
{
    $r = new ReflectionClass($class);
    return array_values(array_filter(
        $props,
        static fn (string $p): bool => $r->hasProperty($p) && !$r->getProperty($p)->isStatic(),
    ));
}

Try / catch

use PHPUnit\Framework\MockObject\PropertyCannotBeDoubledException;

try {
    $builder->doubleProperties($names);
} catch (PropertyCannotBeDoubledException $e) {
    if (str_contains($e->getMessage(), 'it is static')) {
        // handle static state directly, no doubling
        Registry::$instances = [];
        $builder = $this->getMockBuilder(Registry::class); // rebuild without the static prop
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: $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.

Common situations: 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.

Related errors


AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23). Data as JSON: /api/errors/78813403633551a7. Report an issue: GitHub.