Summary
getChannels() calls get_object_vars() without a type check, so a server response where channels is an empty JSON array crashes the client:
TypeError: get_object_vars(): Argument #1 ($object) must be of type object, array given
src/Pusher.php:664
The interesting part: this library already guards against exactly this, but only on one code path.
process_trigger_result() (added in #323, fixing #322) checks the type:
https://github.com/pusher/pusher-http-php/blob/master/src/Pusher.php#L1195-L1197
if (property_exists($result, 'channels') && is_object($result->channels)) {
$result->channels = get_object_vars($result->channels);
}
getChannels() does not:
https://github.com/pusher/pusher-http-php/blob/master/src/Pusher.php#L660-L666
public function getChannels(array $params = []): object
{
$result = $this->get('/channels', $params);
$result->channels = get_object_vars($result->channels);
return $result;
}
Why it happens in practice
Pusher-protocol implementations other than Pusher itself are common now (Reverb, Sockudo, Soketi). I hit this with Laravel Reverb v1.11.1, which serialises an empty channel list as [] instead of {} — I reported it there as laravel/reverb#402, and it should be fixed on their side too.
But the failure mode is worth guarding here regardless, because it is silent and intermittent: the response is an array only while no channel is occupied. One connected client turns the PHP array into a string-keyed map, json_encode emits an object, and the crash disappears. So it reproduces on idle systems and vanishes under load.
Measured side by side, same signed request:
Reverb → {"channels":[]}
Sockudo → {"channels":{}}
Suggestion
Apply the same guard that process_trigger_result() already uses:
$result->channels = is_object($result->channels) ? get_object_vars($result->channels) : (array) $result->channels;
This keeps the documented return shape (channels as an array of channel names) for every server, instead of a fatal error for some of them. Happy to send a PR.
Environment
- pusher/pusher-php-server ^7.2
- PHP 8.3
Summary
getChannels()callsget_object_vars()without a type check, so a server response wherechannelsis an empty JSON array crashes the client:The interesting part: this library already guards against exactly this, but only on one code path.
process_trigger_result()(added in #323, fixing #322) checks the type:https://github.com/pusher/pusher-http-php/blob/master/src/Pusher.php#L1195-L1197
getChannels()does not:https://github.com/pusher/pusher-http-php/blob/master/src/Pusher.php#L660-L666
Why it happens in practice
Pusher-protocol implementations other than Pusher itself are common now (Reverb, Sockudo, Soketi). I hit this with Laravel Reverb v1.11.1, which serialises an empty channel list as
[]instead of{}— I reported it there as laravel/reverb#402, and it should be fixed on their side too.But the failure mode is worth guarding here regardless, because it is silent and intermittent: the response is an array only while no channel is occupied. One connected client turns the PHP array into a string-keyed map,
json_encodeemits an object, and the crash disappears. So it reproduces on idle systems and vanishes under load.Measured side by side, same signed request:
Suggestion
Apply the same guard that
process_trigger_result()already uses:This keeps the documented return shape (
channelsas an array of channel names) for every server, instead of a fatal error for some of them. Happy to send a PR.Environment