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 final
What it means
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().
Source
Thrown at src/Framework/MockObject/TestDoubleBuilder.php:143
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;
}
/**
* Specifies the arguments for the constructor.
*
* @param array<mixed> $arguments
*
* @return $thisView on GitHub (pinned to f123cdb2a2)
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
Example fix
// before
$double = (new TestDoubleBuilder(Order::class))
->doubleProperties(['status']) // 'status' is final
->generate();
// after
$double = (new TestDoubleBuilder(Order::class))
->doubleProperties(['notes'])
->generate(); Defensive patterns
Strategy: type-guard
Validate before calling
$property = new ReflectionProperty(Order::class, 'status');
if ($property->isFinal()) {
// skip doubling 'status', use constructor args instead
} Type guard
/** @param list<non-empty-string> $properties @return list<non-empty-string> */
function nonFinalProperties(string $class, array $properties): array
{
$reflector = new ReflectionClass($class);
return array_values(array_filter(
$properties,
static fn (string $name): bool => $reflector->hasProperty($name)
&& $reflector->getProperty($name)->isPublic()
&& !$reflector->getProperty($name)->isStatic()
&& !$reflector->getProperty($name)->isFinal()
&& $reflector->getProperty($name)->hasType(),
));
} Prevention
- 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
When it happens
Trigger: Calling doubleProperties([...]) with a property name that is declared with the 'final' modifier on the class being doubled.
Common situations: Codebases adopting PHP final properties for immutability; tests written before the property was made final that still try to hook it.
Related errors
- Trying to double property "%s" of class "%s" with doubleProp
- Return value for %s::%s() cannot be generated: %s
- Trying to double property "%s" of class "%s" with doubleProp
- Comparison method %s::%s() does not exist.
- Cannot double method with invalid name "%s"
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/d8e72e5a4e3ef8e2.
Report an issue: GitHub.