doctrine/instantiator · error · Doctrine\Instantiator\Exception\UnexpectedValueException
An exception was raised while trying to instantiate an insta
Error message
An exception was raised while trying to instantiate an instance of "%s" via un-serialization
What it means
When a class cannot be created via ReflectionClass::newInstanceWithoutConstructor() (final classes with internal ancestors, src/Instantiator.php:213-216), the instantiator falls back to a synthetic serialized payload like 'C:12:"ClassName":0:{}' and probes unserialize() with it (checkIfUnSerializationIsSupported(), src/Instantiator.php:167-190). If that probe unserialize() throws an Exception, fromSerializationTriggeredException() wraps it with the class name and chains the original as previous. The real cause is always in getPrevious(): userland code that runs during unserialization (Serializable::unserialize(), __wakeup(), __unserialize()) threw on the empty probe payload.
Source
Thrown at src/Exception/UnexpectedValueException.php:29
use function sprintf;
/**
* Exception for given parameters causing invalid/unexpected state on instantiation
*/
class UnexpectedValueException extends BaseUnexpectedValueException implements ExceptionInterface
{
/**
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @template T of object
*/
public static function fromSerializationTriggeredException(
ReflectionClass $reflectionClass,
Exception $exception,
): self {
return new self(
sprintf(
'An exception was raised while trying to instantiate an instance of "%s" via un-serialization',
$reflectionClass->getName(),
),
0,
$exception,
);
}
/**
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @template T of object
*/
public static function fromUncleanUnSerialization(
ReflectionClass $reflectionClass,
string $errorString,
int $errorCode,
string $errorFile,
int $errorLine,View on GitHub (pinned to cbb879d6ee)
Solutions
- Catch the exception and inspect getPrevious() — it holds the original exception thrown inside unserialize(), which names the actual faulting code.
- Make Serializable::unserialize() / __wakeup() / __unserialize() tolerate the empty payload: return early on '' or empty arrays instead of throwing.
- Prefer the modern __serialize()/__unserialize() pair over the legacy Serializable interface when you control the class.
- If the throwing behavior cannot be changed, construct the object yourself (new $className(...) or ReflectionClass::newInstanceWithoutConstructor() when the class allows it) instead of using this instantiator for that type.
Example fix
// before
class Money implements \Serializable
{
public function unserialize($data): void
{
if ($data === '') {
throw new \InvalidArgumentException('empty payload'); // breaks the Instantiator probe
}
// ...
}
}
// after: tolerate the empty payload used by the instantiation probe
public function unserialize($data): void
{
if ($data === '') {
return;
}
// ...
} Defensive patterns
Strategy: try-catch
Validate before calling
$reflection = new ReflectionClass($className);
if ($reflection->isSubclassOf(Serializable::class)
|| $reflection->hasMethod('__wakeup')
|| $reflection->hasMethod('__unserialize')
) {
// the unserialization probe will invoke this code with an empty payload:
// make sure it cannot throw before relying on Instantiator
$instance = $reflection->newInstanceWithoutConstructor();
} else {
$instance = (new Instantiator())->instantiate($className);
} Type guard
function isSafeForUnserializationProbe(string $className): bool
{
$r = new ReflectionClass($className);
return ! $r->isSubclassOf(Serializable::class)
&& ! $r->hasMethod('__wakeup')
&& ! $r->hasMethod('__unserialize');
} Try / catch
use Doctrine\Instantiator\Exception\UnexpectedValueException;
try {
$instance = $instantiator->instantiate($className);
} catch (UnexpectedValueException $e) {
$rootCause = $e->getPrevious(); // the exception thrown inside unserialize()
// log $rootCause for diagnosis, then fall back to explicit construction
return new $className(...$constructorArgs);
} Prevention
- Never throw from Serializable::unserialize() on an empty payload — return early instead.
- Prefer __serialize()/__unserialize() over the legacy Serializable interface.
- Keep __wakeup()/__unserialize() side-effect free and tolerant of empty object state.
- Avoid extending internal final classes when you need constructor-less instantiation.
When it happens
Trigger: instantiate() hits the unserialization fallback path and the probe unserialize() throws instead of raising a PHP warning: typically a class implementing Serializable whose unserialize('') throws (empty-string validation, argument checks), or a __wakeup()/__unserialize() that throws when the object state is empty. The catch at src/Instantiator.php:203-204 converts the thrown Exception into this wrapper.
Common situations: Legacy classes implementing the old Serializable interface with strict payload validation that rejects the empty payload the library probes with; __wakeup() implementations that assert preconditions on properties the synthetic payload never sets; final classes extending internal SPL/PDO classes forced onto the unserialize path; code migrated from __wakeup to __unserialize that now throws where it previously warned.
Related errors
- Could not produce an instance of "%s" via un-serialization,
- The provided type "%s" is an interface, and cannot be instan
- The provided type "%s" is a trait, and cannot be instantiate
- The provided class "%s" does not exist
- The provided class "%s" is abstract, and cannot be instantia
AI-assisted analysis of doctrine/instantiator@cbb879d6ee (2026-08-21).
Data as JSON: /api/errors/ea0d612b5d85aa57.
Report an issue: GitHub.