Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Zendservice Google Gcm Laravel Package

zendframework/zendservice-google-gcm

A Zend Framework 2 service for sending push notifications via Google Cloud Messaging (GCM). It wraps the GCM HTTP endpoint, letting PHP apps deliver messages to Android devices through a simple object-oriented API. Note: GCM was retired in 2019 in favor of Firebase Cloud Messaging (FCM).

View on GitHub
Deep Wiki
Context7

Getting Started

zendframework/zendservice-google-gcm is a small ZF2 module that wraps the (now defunct) Google Cloud Messaging HTTP endpoint. You don't need ZF2 or a module manager to use it from Laravel — Composer autoloads the classes and you instantiate them directly.

composer require zendframework/zendservice-google-gcm

Composer will print Package zendframework/zendservice-google-gcm is abandoned and likely pull in zendframework/zend-http and zendframework/zend-json, which are also abandoned. On modern PHP this is the first thing to verify before writing any code:

php -r "require 'vendor/autoload.php'; new ZendService\Google\Gcm\Client(); echo 'ok';"

Where to look first

  • src/ZendService/Google/Gcm/Client.php — ~150 lines; the whole package is basically setApiKey() plus send(Message $message).
  • src/ZendService/Google/Gcm/Message.php — the request DTO (setRegistrationIds, addData, setCollapseKey, setTimeToLive, setDryRun, …).
  • src/ZendService/Google/Gcm/Response.php — parses the JSON reply into Result objects.
  • Module.php — ignore it entirely. It exists only so ZF2's ModuleManager could register config; Laravel's Composer autoloader is all you need.

First use case

The canonical flow: a queued job takes a batch of Android device tokens, builds a Message, sends it, and interprets per-token results.

use ZendService\Google\Gcm\Client;
use ZendService\Google\Gcm\Message;

$client = new Client();
$client->setApiKey(config('services.gcm.api_key'));

$message = new Message();
$message->setRegistrationIds($tokens);
$message->setData(['title' => 'Hi', 'body' => 'Hello']);
$message->setTimeToLive(3600);

$response = $client->send($message);

Before building anything on this, confirm the endpoint is still reachable — see Gotchas.

Implementation Patterns

Bind it once in a service provider. The client is stateless apart from the API key, so a singleton is fine.

// app/Providers/AppServiceProvider.php
$this->app->singleton(Client::class, function () {
    $client = new Client();
    $client->setApiKey(config('services.gcm.api_key'));
    return $client;
});
// config/services.php
'gcm' => [
    'api_key' => env('GCM_API_KEY'),
],

Wrap it, don't sprinkle it. The package has no Laravel idioms (no facade, no config publishing, no queue integration). A thin application service gives you a seam for testing and for swapping to FCM later.

final class GcmPusher
{
    public function __construct(private Client $client) {}

    /** @param string[] $tokens */
    public function send(array $tokens, array $data): array
    {
        $results = [];

        foreach (array_chunk($tokens, 1000) as $chunk) {
            $message = (new Message())
                ->setRegistrationIds($chunk)
                ->setData($data)
                ->setTimeToLive(3600);

            $response = $this->client->send($message);

            foreach ($response->getResults() as $i => $result) {
                $results[$chunk[$i] ?? $i] = $result;
            }

            // back-pressure between chunks
            usleep(100_000);
        }

        return $results;
    }
}

Always chunk at 1000. A single GCM request accepts at most 1000 registration IDs. Response::getResults() is positional — $results[$i] corresponds to $registrationIds[$i] in the message you sent, so keep the chunk array around to map errors back to tokens.

Handle canonical IDs and dead tokens in the same loop. This is the part people skip, and it's why old apps accumulate thousands of stale tokens.

foreach ($response->getResults() as $i => $result) {
    $token = $chunk[$i];

    if ($error = $result->getError()) {
        match ($error) {
            'NotRegistered', 'InvalidRegistration' => DeviceToken::where('token', $token)->delete(),
            'Unavailable', 'InternalServerError',
            'DeviceMessageRateExceeded'            => $retry[] = $token,
            default                                => report(new PushFailure($token, $error)),
        };
        continue;
    }

    if ($canonical = $result->getRegistrationId()) {
        DeviceToken::where('token', $token)->update(['token' => $canonical]);
    }
}

Push from a queued job, one job per 1000-token batch. The HTTP call is synchronous and can take seconds; you don't want it inside a request cycle.

final class SendPush implements ShouldQueue
{
    use Queueable;

    public int $tries = 3;
    public array $backoff = [10, 60, 300];

    public function __construct(public string $token, public array $payload) {}

    public function handle(GcmPusher $pusher): void
    {
        $pusher->send([$this->token], $this->payload);
    }
}

With $backoff, the queue handles Unavailable retries for you — just throw on retryable errors and record non-retryable ones.

Payload rules to encode in your DTO. GCM data values must be strings; nested arrays are not allowed.

$message->setData(array_map(
    fn ($v) => is_scalar($v) ? (string) $v : json_encode($v),
    $payload
));

Dry run for staging. setDryRun(true) validates the message without delivering, but it still makes a network request — it is not a local mock.

Testing. Http::fake() and Http::assertSent() do nothing here; the package uses Zend\Http\Client directly, not the Laravel HTTP client or Guzzle. Test by binding a fake into the container:

$this->mock(Client::class, function ($mock) use ($response) {
    $mock->shouldReceive('send')->once()->andReturn($response);
});

or, cleaner, mock your own GcmPusher and keep the package out of your test suite entirely.

Gotchas and Tips

The service is dead. GCM was deprecated in April 2018 and shut down in April 2019; android.googleapis.com/gcm/send no longer accepts traffic. Every request from this package will fail against production. The library is archived, has 28 stars and zero dependents, and the last release (2019-02-07) predates the shutdown. Treat any use of it as a historical exercise or a stopgap while you migrate.

The API key doesn't exist anymore. The Authorization: key=<server key> model was replaced by FCM HTTP v1, which uses OAuth2 bearer tokens minted from a service account. There is no way to obtain a legacy GCM server key. If config('services.gcm.api_key') is null or a placeholder, Client::send() will still POST and you'll get a 401 — with no hint that the credential type itself is retired.

Migration path. The direct replacement is FCM HTTP v1 (https://fcm.googleapis.com/v1/projects/{project}/messages:send), one token per request, OAuth2 auth. In Laravel: kreait/laravel-firebase or laravel-notification-channels/fcm. Your GcmPusher interface should be designed so the body can be swapped without touching call sites — which is the main argument for wrapping it rather than injecting Client into controllers.

PHP 8 / dependency rot. The package is PSR-0-autoloaded and depends on zendframework/zend-http and zendframework/zend-json, both archived and superseded by Laminas. On PHP 8.1+ you'll hit deprecation notices from the Zend HTTP layer (implicit nullable params, dynamic properties). If Composer resolution blocks you, zendframework/zend-http can often be substituted with laminas/laminas-http via a replace block, but the package's use Zend\Http\... statements won't be rewritten — you'd need a fork.

No Laravel HTTP integration. There's no way to inject Zend\Http\Client through the constructor; Client creates its own. That means no Http::fake(), no global middleware, no Http::retry(), and no per-request timeout config without subclassing Client and overriding send(). Timeouts default to Zend's own (10s connect / no explicit total), which is a common source of stuck queue workers.

Response parsing is thin. Response::getResults() returns Result objects; getError() returns null on success and getRegistrationId() returns an empty string (not null) when there is no canonical ID — guard with if ($canonical !== '') rather than a truthiness check on a method chain.

Error codes worth a lookup table:

Error Meaning Action
NotRegistered Token no longer valid Delete token
InvalidRegistration Malformed token Delete token
MismatchSenderId Token belongs to another project Delete token
Unavailable Server-side hiccup Retry with backoff
InternalServerError Transient Retry with backoff
DeviceMessageRateExceeded Per-device flood Backoff
MessageTooBig > 4 KB total payload Trim / send ID only
InvalidTtl TTL out of range Fix config

Rate limits are yours to manage. The package has no throttling and no sendMulti. Fan-out to 100k devices means 100 serial HTTP requests; concurrency and rate limiting live entirely in your job batching.

Extension points are limited to two. Subclass Client to override send() (custom timeout, logging, metrics) or to inject a configured HTTP client; subclass Message if you need a typed payload builder. There is no event system, no middleware pipeline, and no response hook.

Uninstall before you forget. If you're only reading this to evaluate the package, skip the install — composer require will drag three abandoned Zend packages into your composer.lock and trigger composer audit warnings forever.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource