doctrine/orm · error · InvalidArgumentException
%s::$%s must not be nullable when used with TypedNoDefaultPr
Error message
%s::$%s must not be nullable when used with TypedNoDefaultPropertyAccessor
What it means
The second invariant of TypedNoDefaultPropertyAccessor: since its setValue(null) path unsets the property to represent 'uninitialized', a nullable type would make null and uninitialized indistinguishable and break the strategy. The constructor therefore throws InvalidArgumentException when the property type allows null, even if it is typed.
Source
Thrown at src/Mapping/PropertyAccessors/TypedNoDefaultPropertyAccessor.php:30
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) {
if ($this->unsetter === null) {
$propertyName = $this->reflectionProperty->getName();
$this->unsetter = function () use ($propertyName): void {
unset($this->$propertyName);
};
}
$unsetter = $this->unsetter->bindTo($object, $this->reflectionProperty->getDeclaringClass()->getName());View on GitHub (pinned to d9b9ff7301)
Solutions
- Exclude nullable properties from this accessor; wrap only `public string $x;`-style declarations.
- Use PropertyAccessorFactory::createPropertyAccessor() — it already checks allowsNull() before wrapping.
- If the property must be nullable, remove the TypedNoDefaultPropertyAccessor wrapper and handle null writes with the parent accessor.
Example fix
// before
$accessor = new TypedNoDefaultPropertyAccessor($inner, $refl); // ?string $name -> InvalidArgumentException
// after
$accessor = (! $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
- Only wrap non-nullable typed properties with TypedNoDefaultPropertyAccessor.
- Do not widen entity property types to nullable without adjusting accessor wiring.
- Prefer PropertyAccessorFactory, which checks allowsNull() for you.
When it happens
Trigger: Directly constructing TypedNoDefaultPropertyAccessor for a property declared nullable (e.g. `public ?string $name;` or `public string|null $name;`) — typed but allowing null.
Common situations: Custom accessor pipelines wrapping nullable fields; widening a property's type to nullable later while a custom wrapper still targets it; copied factory logic without the allowsNull() branch.
Related errors
- %s::$%s must have a type when used with TypedNoDefaultProper
- %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/4b9043180c521d89.
Report an issue: GitHub.