doctrine/orm · error · InvalidArgumentException
%s::$%s must have a type when used with TypedNoDefaultProper
Error message
%s::$%s must have a type when used with TypedNoDefaultPropertyAccessor
What it means
TypedNoDefaultPropertyAccessor implements the 'typed property without default value' strategy: on setValue(null) it unsets the property so PHP reports it as uninitialized rather than null. That trick only works for declared, non-nullable types, so the constructor requires the ReflectionProperty to have a type and throws InvalidArgumentException otherwise. PropertyAccessorFactory only wraps typed non-nullable properties, so this fires on direct/custom instantiation.
Source
Thrown at src/Mapping/PropertyAccessors/TypedNoDefaultPropertyAccessor.php:22
namespace Doctrine\ORM\Mapping\PropertyAccessors;
use Closure;
use InvalidArgumentException;
use ReflectionProperty;
use function assert;
use function sprintf;
/** @internal */
class TypedNoDefaultPropertyAccessor implements PropertyAccessor
{
private Closure|null $unsetter = null;
public function __construct(private PropertyAccessor $parent, private ReflectionProperty $reflectionProperty)
{
if (! $this->reflectionProperty->hasType()) {
throw new InvalidArgumentException(sprintf(
'%s::$%s must have a type when used with TypedNoDefaultPropertyAccessor',
$this->reflectionProperty->getDeclaringClass()->getName(),
$this->reflectionProperty->getName(),
));
}
if ($this->reflectionProperty->getType()->allowsNull()) {
throw new InvalidArgumentException(sprintf(
'%s::$%s must not be nullable when used with TypedNoDefaultPropertyAccessor',
$this->reflectionProperty->getDeclaringClass()->getName(),
$this->reflectionProperty->getName(),
));
}
}
public function setValue(object $object, mixed $value): void
{
if ($value === null) {View on GitHub (pinned to d9b9ff7301)
Solutions
- Only wrap typed, non-nullable properties; add if (! $refl->hasType() || $refl->getType()->allowsNull()) skip.
- Prefer PropertyAccessorFactory::createPropertyAccessor(), which applies the wrapping conditionally.
- Add a native type declaration to the property if it should participate in this strategy.
Example fix
// before
$accessor = new TypedNoDefaultPropertyAccessor($inner, $refl); // untyped $refl -> InvalidArgumentException
// after
$accessor = ($refl->hasType() && ! $refl->getType()->allowsNull())
? new TypedNoDefaultPropertyAccessor($inner, $refl)
: $inner; Defensive patterns
Strategy: validation
Validate before calling
$accessor = ($refl->hasType() && ! $refl->getType()->allowsNull())
? new TypedNoDefaultPropertyAccessor($inner, $refl)
: $inner; Prevention
- Use PropertyAccessorFactory instead of hand-built chains.
- Add native type declarations to entity properties before relying on unset-based strategies.
- Cover custom accessors with unit tests against typed/untyped properties.
When it happens
Trigger: Directly calling new TypedNoDefaultPropertyAccessor($parent, $reflectionProperty) for a property declared without a type (legacy `public $foo;`) — the untyped/nullable case is explicitly rejected by the constructor.
Common situations: Custom property-accessor chains that wrap every property unconditionally; legacy entities predating typed properties; code copied from PropertyAccessorFactory without the hasType()/allowsNull() guard.
Related errors
- %s::$%s must not be nullable when used with TypedNoDefaultPr
- %s::$%s must be readonly property
- The attribute "%s" is repeatable. Call getPropertyAttributeC
- The attribute "%s" is not repeatable. Call getPropertyAttrib
- This class requires PHP 8.4 or higher.
AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21).
Data as JSON: /api/errors/906be6241b135293.
Report an issue: GitHub.