Skip to content

[Mime] MessagePart does not support serialization - Profiler fails if Message with MessagePart is collected #48313

Closed
@Amunak

Description

@Amunak

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 like getBody 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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions