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 Apple Apns Laravel Package

zendframework/zendservice-apple-apns

Zend Framework service for Apple Push Notification Service (APNs). Send push notifications to iOS devices from PHP using certificate-based connections, with support for Apple’s feedback service.

View on GitHub
Deep Wiki
Context7

Getting Started

Before writing a line of code, confirm you actually need this package: ZendService_Apple_Apns speaks Apple's legacy binary provider protocol (gateway.push.apple.com:2195) and the feedback service (feedback.push.apple.com:2196). Apple retired both, so the package can no longer deliver a notification to a real device. In practice you install it for exactly two reasons: you are maintaining a legacy app that already depends on it, or you are reading its payload-building code as a reference while porting to HTTP/2. If you want to send pushes today, stop and go straight to curl with CURL_HTTP_VERSION_2_0 against api.push.apple.com with a JWT token (or a maintained library such as edamov/pushok).

Assuming legacy maintenance, the minimum path is:

composer require zendframework/zendservice-apple-apns

Composer will flag the package as abandoned. Check the resolved PHP constraint with composer show zendframework/zendservice-apple-apns — the 2019 release targets PHP 5.6/7.x, so on PHP 8 you will likely need a fork or a patched constraint rather than --ignore-platform-reqs.

Next, produce a PEM containing both the certificate and the private key (the client passes it to stream_socket_client as local_cert):

openssl pkcs12 -in apns.p12 -out apns.pem -nodes -clcerts   # -nodes = no passphrase

Then the first use case, a single alert:

use ZendService\Apple\Apns\Client\Message as ApnsClient;
use ZendService\Apple\Apns\Message;
use ZendService\Apple\Apns\Response\Message as MessageResponse;

$client = new ApnsClient();
$client->open(__DIR__ . '/certs/apns.pem', ApnsClient::PRODUCTION, $passphrase ?: null);

$message = new Message();
$message->setToken('a1b2c3…64 hex chars…');
$message->setAlert('Your order shipped');
$message->setBadge(3);
$message->setSound('default');

$response = $client->send($message);
if ($response->getCode() !== MessageResponse::RESULT_OK) {
    error_log('APNS write failed: ' . $response->getCode());
}
$client->close();

Look first at ZendService\Apple\Apns\Client\AbstractClient (socket setup, environment map), Message (payload assembly), and Message\Alert (localized/action alerts). Everything else is thin.

Implementation Patterns

Wrap the client in a small service, not in controllers. The socket is opened per open() call and TLS handshake dominates latency, so create one client per job/request and reuse send() for many messages:

class ApnsNotifier
{
    public function __construct(private string $pem, private ?string $pass, private string $env) {}

    public function sendMany(array $pushes): array
    {
        $client = new ApnsClient();
        $client->open($this->pem, $this->env, $this->pass);
        $results = [];
        try {
            foreach ($pushes as $token => $payload) {
                $message = new Message();
                $message->setToken($token)
                        ->setAlert($payload['body'] ?? null)
                        ->setBadge((int) ($payload['badge'] ?? 0))
                        ->setSound($payload['sound'] ?? 'default')
                        ->setCustom($payload['data'] ?? []);
                $results[$token] = $client->send($message)->getCode();
            }
        } finally {
            $client->close();
        }
        return $results;
    }
}

Queue the work, not the socket. Push sending belongs in a queued job (dispatch(new SendPush($userId, $payload))) so an APNS hiccup cannot block an HTTP request. Because there is no per-message delivery feedback for the format this library uses, your job only records "write attempted"; delivery truth comes later from token pruning.

Localized and action alerts use the Alert object instead of a string:

use ZendService\Apple\Apns\Message\Alert;

$alert = (new Alert())
    ->setBody('Your order shipped')
    ->setLocKey('SHIP_BODY')
    ->setLocArgs(['1234'])
    ->setActionLocKey('SHIP_ACTION');

$message->setAlert($alert);

Silent/background refresh — set content-available and omit alert/badge/sound:

$message->setContentAvailable(1);

Custom data goes through setCustom([...]) and is merged next to aps. Never put an aps key in there; it will collide with the generated dictionary.

Feedback pruning is the only reliable hygiene mechanism the package offers. Run it as a scheduled command and delete the returned tokens from your device_tokens table:

$feedback = new Feedback();
$feedback->open($pem, Feedback::PRODUCTION, $pass);
foreach ($feedback->feedback() as $record) {
    DeviceToken::where('token', $record->getToken())->delete();
}
$feedback->close();

Testing. Both clients extend AbstractClient, and there are MessageInterface/client interfaces, so either inject a fake implementation of MessageInterface (only getToken() and getPayload() matter) or subclass the client to point at a local TLS mock instead of the Apple host. Assert on getPayload() JSON — that is where every bug in this library actually lives, and it is testable without a socket.

Gotchas and Tips

  • The protocol is dead. Both the provider and feedback endpoints are gone. Any code path here is a no-op at best; treat this package as a spec document, not a transport.
  • 256-byte payload ceiling. The binary protocol's limit, not the modern 4 KB. json_encode of a fat custom array silently blows past it — validate strlen($message->getPayload()) before sending and truncate or drop fields.
  • Blind writes. With the simple-format packet this library emits, send() reports RESULT_OK when fwrite succeeded, not when Apple accepted the token. Invalid tokens produce no error, so a RESULT_OK is meaningless for retry logic; only prune via feedback.
  • Token hygiene. Strip <, >, and spaces from device tokens and lowercase them before setToken(). A non-hex string produces a malformed packet that fails silently.
  • Badge must be an integer. setBadge('3') can serialize as "3", which Apple rejects; cast it. setBadge(0) correctly clears the badge — the null check inside the payload builder distinguishes 0 from unset.
  • TLS negotiation. Old PHP/OpenSSL defaults can fail the handshake against APNS with a generic RuntimeException from open(). Check openssl_get_cert_locations() and your openssl.cafile before blaming the certificate.
  • Certificate expiry is the classic 3 a.m. incident. The .pem is date-bound and per-environment; sandbox certs cannot talk to production and vice versa. Wire a monitor that parses openssl_x509_parse() and alerts 30 days out.
  • Keep the PEM out of the web root, readable only by the PHP user, and pass a passphrase via env if the key is encrypted — never commit the .p12.
  • Socket lifecycle. Forgetting close() leaks a file descriptor per FPM request; conversely, opening inside a loop will make a 10k-token broadcast take minutes. Batch and close.
  • Extension point for hosts/ports. If you must keep the code alive against a proxy or mock, subclass AbstractClient and override the environment map rather than hard-coding stream_socket_client calls in your own class.
  • Migration path. Port the payload logic (it is the only durable asset) to a JWT + HTTP/2 sender, keep the JSON shape identical, and run both in parallel behind a feature flag before deleting the Zend dependency.
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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel