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