Description
Symfony version(s) affected
4.3.0-6.2 (all)
Description
Object Symfony\Component\Mime\Part\MessagePart
does not support serialization.
This will most likely manifest in Profiler failing to save the collected MessagePart, but it could break other (user) code as well.
There are really two problems:
- The object is missing the
__sleep
magic method. Parent__sleep
gets called, but that returns wrong/different property names than exist in the object (because parent's are private), so the most important property for this object ($message
) is not serialized, and when unserialized methods likegetBody
fail unexpectadly because property$message
is null. - Because parent's properties are private, serialization fails with error
Warning: serialize(): "filename" returned as member variable from __sleep() but does not exist
, as from the point of view of this child object that property truly does not exist. However that property is required for the object to work properly, and it should either be saved or parent constructor needs to be called correctly on unserialization (__wakeup
call).
How to reproduce
Add the following test to MessagePartTest:
public function testSerialize()
{
$email = (new Email())->from('fabien@symfony.com')->to('you@example.com')->text('content');
$email->getHeaders()->addIdHeader('Message-ID', $email->generateMessageId());
$p = new MessagePart($email);
$expected = clone $p;
$this->assertEquals($expected->toString(), unserialize(serialize($p))->toString());
}
You will observe it will fail.
Possible Solution (see #48314)
Implement magic __sleep
and __wakeup
methods, for example like so:
class MessagePart extends DataPart
{
// ...
/**
* @return array
*/
public function __sleep()
{
return ['message'];
}
public function __wakeup()
{
$this->__construct($this->message);
}
}
Additionally I think it would be a great idea to improve FileProfilerStorage so that it fails gracefully if (one of) the Collectors has an issue and fails the serialization. In testing I used something like this for the write
function (and inverse for doRead
):
try {
set_error_handler(static function (int $severity, string $message, string $file, int $line) use (&$errorMessages): bool { return true; });
foreach ($data['data'] as $key => $value) {
$data['data'][$key] = serialize($value);
}
} catch (Throwable) {
trigger_error(sprintf('Failed serializing collector %s (%s). ', $key, $value::class), E_USER_WARNING);
} finally {
restore_error_handler();
}
$data = serialize($data);
which still allows all but the one failing Collector to save so that you have at least something to debug with.