doctrine/orm · error · InvalidArgumentException
::$ must not be nullable when used with…
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.
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
- ::$ must have a type when used with…
- ::$ must be readonly property
- Attempting to change readonly property
- Given property is not readonly.
- RootTypeWalker is to be used on SelectStatements only
AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21).
Data as JSON: /api/errors/4b9043180c521d89.
Report an issue: GitHub.
Appendix: 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)