How to properly unpack Protobuf Any type in PHP

Viewed 490

I'm desperately trying to check whether an "unknown" protobuf payload wrapped within an Any is some specific type (Heartbeat) and unpack it to a Heartbeat object using the code below. Both Heartbeat and StatusChanged are valid generated Protobuf classes.

/* <external service> */
$event = (new StatusChanged)->setId("testId");

$any = new Any;
$any->pack($event);
$protobufBinaryAny = $any->serializeToString();
/* </external service> */

/* $protobufBinaryAny is the incoming binary data */
$anyEvent = new Any();
$anyEvent->mergeFromString($protobufBinaryAny);

if ($anyEvent->is(Heartbeat::class)) {
    $this->processHeartbeat($anyEvent->unpack());
} else {
   throw new UnknownMessageTypeException($anyEvent->getTypeUrl());
}

When running the following code, I'd expect it to throw a UnknownMessageTypeException, however, I get this:

Call to a member function getFullName() on null.

This error happens at $data->is(Heartbeat::class), since the Heartbeat class obviously couldn't be found in the DescriptorPool.

/**
 * Google\Protobuf\Any
 * https://github.com/protocolbuffers/protobuf/blob/440a156e1cd5c783d8d64eef81a93b1df3d78b60/php/src/Google/Protobuf/Any.php#L315-L323
 */
class Any {
    public function is($klass)
    {
        $pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
        $desc = $pool->getDescriptorByClassName($klass);
        $fully_qualifed_name = $desc->getFullName();
        $type_url = GPBUtil::TYPE_URL_PREFIX.substr(
            $fully_qualifed_name, 1, strlen($fully_qualifed_name));
        return $this->type_url === $type_url;
    }
}

However, when explicitly initializing the Heartbeat before, it's working, since Heartbeat is now in the DescriptorPool.

foreach ([
    \GPBMetadata\Heartbeat::class,
    \GPBMetadata\StatusChanged::class,
] as $klass) {
    $klass::initOnce();
}

But this manual initialization thing just feels wrong. Am I supposed to do it like that, or did I just miss a fancy autoloading-feature in the non-existing PHP docs?

For the time being, I wrote an autoloader for this case, but it still just feels not right:

trait HandlesProtobufMessages
{
    function initializeProtobufMessages()
    {
        /** @var \Composer\Autoload\ClassLoader */
        $loader = require(__DIR__.'/../../vendor/autoload.php');

        $classes = array_filter(array_keys($loader->getClassMap()), function ($className) {
            $namespace = "GPBMetadata\\";
            return substr($className, 0, strlen($namespace)) === $namespace;
        });

        foreach ($classes as $class) {
            $class::initOnce();
        }
    }
}

Maybe you have more insights into this protobuf jungle and can help me out here, would be glad!

Thank you in advance

0 Answers
Related