定制 swanflutter/notification-master 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

swanflutter/notification-master

Composer 安装命令:

composer require swanflutter/notification-master

包简介

A modern PHP 8.2+ / Laravel push notification package supporting Firebase FCM HTTP v1 and polling mode.

README 文档

README

A modern, production-ready PHP 8.2+ / Laravel push notification package built on the Firebase Cloud Messaging (FCM) HTTP v1 API.

Supports real-time FCM delivery and a polling-mode fallback — all with a clean fluent API, typed exceptions, and first-class Laravel integration.

Latest Version on Packagist PHP Version License: MIT

Table of Contents

Requirements

Dependency Version
PHP ^8.2
guzzlehttp/guzzle ^7.9
firebase/php-jwt ^6.10
Laravel (optional) ^10.0 | ^11.0 | ^12.0

Installation

composer require swanflutter/notification-master

Laravel Setup

The package is auto-discovered. After installing, publish the config file:

php artisan vendor:publish --tag=push-notification-config

This creates config/push-notification.php in your application.

Set the path to your Firebase service-account JSON file in .env:

FIREBASE_CREDENTIALS=/absolute/path/to/service-account.json

The service-account JSON is generated in the Firebase Console under Project Settings → Service Accounts → Generate New Private Key.

Standalone PHP Setup

No framework integration required — just instantiate FirebasePush directly:

use SwanFlutter\NotificationMaster\FirebasePush;

$push = new FirebasePush('/path/to/service-account.json');

// or pass the decoded array directly:
$push = new FirebasePush([
    'type'         => 'service_account',
    'project_id'   => 'my-project',
    'private_key'  => '-----BEGIN RSA PRIVATE KEY-----...',
    'client_email' => 'firebase-adminsdk@my-project.iam.gserviceaccount.com',
]);

Configuration

config/push-notification.php (after publishing):

return [
    // Path to your Firebase service-account JSON (or the decoded array).
    'credentials' => env('FIREBASE_CREDENTIALS', storage_path('app/firebase/service-account.json')),

    // Validate payload without sending to devices (staging/testing).
    'dry_run' => env('PUSH_DRY_RUN', false),

    // Automatic retries on transient errors (429 / 5xx). Set 0 to disable.
    'retries'        => env('PUSH_RETRIES', 1),
    'retry_delay_ms' => env('PUSH_RETRY_DELAY_MS', 500),

    // Polling mode store driver: 'cache' (default) or 'array' (in-memory).
    'poll_store' => env('PUSH_POLL_STORE', 'cache'),

    // TTL in seconds for the cache-backed poll store.
    'poll_ttl' => env('PUSH_POLL_TTL', 86400),
];

Usage — FCM Push

Send to a Device Token

use SwanFlutter\NotificationMaster\FirebasePush;
use SwanFlutter\NotificationMaster\Message;

$push = new FirebasePush(config('push-notification.credentials'));

$push->sendToToken(token: 'device-registration-token', title: 'Hello', body: 'World');

Send to a Topic

$push->sendToTopic(topic: 'news', title: 'Breaking News', body: 'Something just happened.');

Send to a Condition

use SwanFlutter\NotificationMaster\Message;

$push->send(
    Message::create()
        ->toCondition("'sports' in topics || 'tech' in topics")
        ->title('Multi-topic alert')
        ->body('This goes to sports and tech subscribers.')
);

Multicast (Multiple Tokens)

$tokens = ['token-1', 'token-2', 'token-3'];

$results = $push->sendToMany($tokens, Message::create()
    ->title('Broadcast')
    ->body('Hello everyone!')
);

foreach ($results as $token => $result) {
    if ($result['success']) {
        echo "Sent to {$token}\n";
    } else {
        echo "Failed for {$token}: {$result['error']}\n";
    }
}

Advanced Message Builder

use SwanFlutter\NotificationMaster\Message;

$message = Message::create()
    ->toToken('device-token')
    ->title('Flash Sale!')
    ->body('50% off — today only.')
    ->image('https://cdn.example.com/banner.jpg')
    ->data([
        'route'      => 'shop',
        'product_id' => '42',
    ])
    ->ttl(3600)
    ->android([
        'priority'     => 'high',
        'notification' => [
            'channel_id' => 'promotions',
            'sound'      => 'default',
        ],
    ])
    ->apns([
        'payload' => [
            'aps' => [
                'sound' => 'default',
                'badge' => 1,
            ],
        ],
    ])
    ->webpush([
        'notification' => ['icon' => '/icon.png'],
    ])
    ->analyticsLabel('flash_sale_july');

$push->send($message);

Raw Payload

Send any FCM-compatible payload directly, bypassing the builder:

$push->sendRaw([
    'message' => [
        'token'        => 'device-token',
        'notification' => ['title' => 'Raw', 'body' => 'Payload'],
    ],
]);

Dry-Run Mode

// Messages are validated but NOT delivered.
$push->dryRun()->send($message);

Retry Configuration

// 3 retries, 1 second base delay (doubled each attempt).
$push->withRetries(3, 1000)->send($message);

Usage — Laravel Notification Channel

Add toFcm() to your Laravel notification class:

use Illuminate\Notifications\Notification;
use SwanFlutter\NotificationMaster\Message;

class OrderShipped extends Notification
{
    public function via(object $notifiable): array
    {
        return ['fcm'];
    }

    public function toFcm(object $notifiable): Message
    {
        return Message::create()
            ->title('Your order has shipped!')
            ->body("Order #{$this->order->id} is on its way.")
            ->data(['order_id' => (string) $this->order->id]);
    }
}

Your notifiable model must implement routeNotificationForFcm() if no target is set on the Message:

// In your User model:
public function routeNotificationForFcm(): string
{
    return $this->fcm_token;
}

Then send it like any other Laravel notification:

$user->notify(new OrderShipped($order));

Usage — Laravel Facade

use SwanFlutter\NotificationMaster\Laravel\Facades\PushNotification;
use SwanFlutter\NotificationMaster\Message;

PushNotification::sendToToken(token: 'device-token', title: 'Hi', body: 'Hello from facade!');

PushNotification::send(
    Message::create()->toTopic('alerts')->title('Alert')->body('Pay attention.')
);

Usage — Polling Mode

Polling mode stores notifications server-side so that client devices can retrieve them on demand via a regular HTTP call. No native push infrastructure required.

How Polling Works

Backend                       Client Device
  │                                │
  │── enqueue(userId, message) ──▶ │  (notification stored)
  │                                │
  │◀── GET /api/notifications ──── │  (device polls every N seconds)
  │── poll(userId) ──────────────▶ │  (returns & marks as delivered)

Enqueue a Notification

use SwanFlutter\NotificationMaster\Polling\NotificationPoller;
use SwanFlutter\NotificationMaster\Message;

$poller = app(NotificationPoller::class);

$id = $poller->enqueue(
    recipientId: (string) $user->id,
    message: Message::create()
        ->title('New message')
        ->body('You have a new message from Alice.')
        ->data(['chat_id' => '99']),
);

Schedule delivery for a future time:

$poller->enqueue(
    recipientId: (string) $user->id,
    message: $message,
    deliverAt: new \DateTimeImmutable('+30 minutes'),
);

Client Polling Endpoint

// routes/api.php
Route::middleware('auth:sanctum')->get('/notifications/poll', function (Request $request) {
    $poller        = app(\SwanFlutter\NotificationMaster\Polling\NotificationPoller::class);
    $notifications = $poller->poll((string) $request->user()->id);

    return response()->json(['notifications' => $notifications]);
});

Flush & Forward via FCM

Deliver all queued notifications immediately via FCM and mark them delivered:

use SwanFlutter\NotificationMaster\Contracts\PushNotificationInterface;
use SwanFlutter\NotificationMaster\Polling\NotificationPoller;

$results = app(NotificationPoller::class)->flushAndSend(
    deviceToken: $user->fcm_token,
    recipientId: (string) $user->id,
    sender:      app(PushNotificationInterface::class),
);

Custom Poll Store

Implement PollStoreInterface to use your own storage backend (Eloquent, Redis, SQS, …):

use SwanFlutter\NotificationMaster\Polling\Store\PollStoreInterface;

class DatabasePollStore implements PollStoreInterface
{
    public function push(string $recipientId, array $notification): void
    {
        DB::table('push_notifications')->insert([
            'recipient_id' => $recipientId,
            'payload'      => json_encode($notification),
            'delivered'    => false,
            'created_at'   => now(),
        ]);
    }

    public function pending(string $recipientId): array
    {
        return DB::table('push_notifications')
            ->where('recipient_id', $recipientId)
            ->where('delivered', false)
            ->get()
            ->map(fn($row) => json_decode($row->payload, true))
            ->toArray();
    }

    public function markDelivered(string $recipientId, array $ids): void
    {
        DB::table('push_notifications')
            ->where('recipient_id', $recipientId)
            ->whereIn(DB::raw("JSON_UNQUOTE(JSON_EXTRACT(payload, '$.id'))"), $ids)
            ->update(['delivered' => true]);
    }

    public function purge(string $recipientId): void
    {
        DB::table('push_notifications')
            ->where('recipient_id', $recipientId)
            ->delete();
    }
}

Bind it in your AppServiceProvider:

use SwanFlutter\NotificationMaster\Polling\Store\PollStoreInterface;

$this->app->bind(PollStoreInterface::class, DatabasePollStore::class);

Exception Handling

All exceptions extend SwanFlutter\NotificationMaster\Exceptions\PushNotificationException.

Exception When thrown
InvalidCredentialsException Service-account file missing, unreadable, or malformed
AuthenticationException JWT signing or Google OAuth token fetch failed
InvalidMessageException Message missing a target or content
SendingFailedException FCM HTTP request failed (network / FCM error); includes getFcmResponse()
use SwanFlutter\NotificationMaster\Exceptions\SendingFailedException;
use SwanFlutter\NotificationMaster\Exceptions\PushNotificationException;

try {
    $push->send($message);
} catch (SendingFailedException $e) {
    // FCM-level error
    logger()->error('FCM error', [
        'message' => $e->getMessage(),
        'fcm'     => $e->getFcmResponse(),
    ]);
} catch (PushNotificationException $e) {
    // Any other package error (credentials, auth, validation)
    logger()->error('Push error: ' . $e->getMessage());
}

Testing

composer test

For unit tests, use ArrayPollStore as a lightweight in-memory poll store:

use SwanFlutter\NotificationMaster\Polling\NotificationPoller;
use SwanFlutter\NotificationMaster\Polling\Store\ArrayPollStore;
use SwanFlutter\NotificationMaster\Message;

$store  = new ArrayPollStore();
$poller = new NotificationPoller($store);

$poller->enqueue('user-1', Message::create()->toToken('t')->title('Hi')->body('Test'));

$pending = $poller->poll('user-1');
assert(count($pending) === 1);
assert($poller->count('user-1') === 0); // marked delivered

Changelog

See CHANGELOG.md for a full history of changes.

License

This package is open-source software licensed under the MIT License.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-13

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固