aybimyazilim/laravel-expo-notifications
Composer 安装命令:
composer require aybimyazilim/laravel-expo-notifications
包简介
Laravel için Expo Push Notifications servisi - React Native Expo uygulamalarına bildirim gönderme paketi
README 文档
README
Bu paket, Laravel uygulamalarından Expo (React Native) uygulamalarına push notification gönderebilmenizi sağlar. Kapsamlı hata yakalama, detaylı loglama ve notification takibi içerir.
✨ Özellikler
- ✅ Expo Push Notifications API entegrasyonu
- ✅ Kapsamlı hata yakalama ve handling
- ✅ Detaylı notification logları
- ✅ Veritabanı tabanlı notification takibi
- ✅ Bulk notification gönderimi
- ✅ İstatistik ve raporlama
- ✅ Queue desteği ve retry mekanizması
- ✅ Token validasyonu
- ✅ Receipt status kontrolü
- ✅ Artisan komutları
🚀 Kurulum
composer require aybimyazilim/laravel-expo-notifications
Config Dosyasını Yayınlayın
php artisan vendor:publish --tag=expo-notifications-config
Migration'ları Yayınlayın ve Çalıştırın
php artisan vendor:publish --tag=expo-notifications-migrations php artisan migrate
⚙️ Konfigürasyon
.env dosyanıza aşağıdaki ayarları ekleyin:
# Expo Notification Ayarları EXPO_NOTIFICATION_TIMEOUT=30 EXPO_DEFAULT_SOUND=default EXPO_DEFAULT_PRIORITY=default EXPO_DEFAULT_CHANNEL_ID=default # Loglama Ayarları EXPO_ENABLE_LOGGING=true EXPO_LOG_REQUESTS=true EXPO_LOG_RESPONSES=true # Queue Ayarları EXPO_QUEUE_CONNECTION=default EXPO_QUEUE_NAME=notifications # Retry Ayarları EXPO_RETRY_ENABLED=true EXPO_RETRY_ATTEMPTS=3 EXPO_RETRY_DELAY=60 # Validasyon EXPO_VALIDATE_TOKENS=true EXPO_MAX_TITLE_LENGTH=100 EXPO_MAX_BODY_LENGTH=200 # Bulk Notification EXPO_BULK_LIMIT=100 EXPO_BATCH_SIZE=20
📱 Kullanım
Basit Notification Sınıfı
<?php namespace App\Notifications; use AybimYazilim\LaravelExpoNotifications\Notifications\ExpoNotification; class NewMessageNotification extends ExpoNotification { protected $message; public function __construct($message) { parent::__construct(); $this->message = $message; } public function toExpo($notifiable): array { return [ 'title' => 'Yeni Mesajınız Var! 💬', 'body' => "Gönderen: {$this->message->sender->name}", 'data' => [ 'type' => 'message', 'message_id' => $this->message->id, 'sender_id' => $this->message->sender_id, 'action' => 'open_chat', ], 'sound' => 'default', 'priority' => 'high', 'channelId' => 'messages', ]; } }
User Model Ayarları
<?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use AybimYazilim\LaravelExpoNotifications\Models\ExpoNotificationLog; class User extends Authenticatable { // Expo token için routing public function routeNotificationForExpo() { return $this->expo_push_token; } // Notification geçmişi public function expoNotifications() { return $this->morphMany(ExpoNotificationLog::class, 'notifiable'); } // Son notification'ları al public function recentNotifications($limit = 10) { return $this->expoNotifications() ->latest() ->limit($limit) ->get(); } }
Notification Gönderimi
use App\Notifications\NewMessageNotification; use Illuminate\Support\Facades\Notification; // Kullanıcıya gönder $user = User::find(1); $user->notify(new NewMessageNotification($message)); // Birden fazla kullanıcıya gönder $users = User::whereNotNull('expo_push_token')->get(); Notification::send($users, new NewMessageNotification($message)); // Belirli bir token'a gönder Notification::route('expo', 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]') ->notify(new NewMessageNotification($message)); // Bulk gönderim $tokens = ['token1', 'token2', 'token3']; foreach ($tokens as $token) { Notification::route('expo', $token) ->notify(new NewMessageNotification($message)); }
Hata Yakalama
use AybimYazilim\LaravelExpoNotifications\Exceptions\ExpoNotificationException; use AybimYazilim\LaravelExpoNotifications\Exceptions\InvalidTokenException; try { $user->notify(new NewMessageNotification($message)); } catch (InvalidTokenException $e) { Log::error('Geçersiz Expo token: ' . $e->getMessage()); // Token'ı temizle veya güncelle $user->update(['expo_push_token' => null]); } catch (ExpoNotificationException $e) { Log::error('Expo notification hatası: ' . $e->getMessage()); }
📊 İstatistikler ve Loglama
Artisan Komutları
# Notification istatistiklerini görüntüle php artisan expo:stats 24h php artisan expo:stats 7d php artisan expo:stats 30d # Test notification gönder php artisan expo:test-notification "ExponentPushToken[xxx]" "Test Title" "Test Body"
Programatik İstatistikler
use AybimYazilim\LaravelExpoNotifications\Services\ExpoLogService; $logService = new ExpoLogService(); // Son 24 saatin istatistikleri $stats = $logService->getStats('24h'); /* [ 'total' => 250, 'sent' => 240, 'failed' => 10, 'pending' => 0, 'success_rate' => 96.0, 'period' => '24h' ] */ // Başarısız notification'ları al $failedNotifications = $logService->getFailedNotifications(50); // En çok kullanılan notification türleri $topTypes = $logService->getTopNotificationTypes(10, '30d'); // Günlük istatistikler (son 30 gün) $dailyStats = $logService->getDailyStats(30); // Hata analizi $errorAnalysis = $logService->getErrorAnalysis('7d');
🎯 Expo React Native Entegrasyonu
Expo uygulamanızda push token almak için:
import * as Notifications from 'expo-notifications'; import * as Device from 'expo-device'; import Constants from 'expo-constants'; import { useEffect, useRef, useState } from 'react'; import { Platform } from 'react-native'; // Notification handler'ı ayarla Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: false, }), }); export default function App() { const [expoPushToken, setExpoPushToken] = useState(''); const notificationListener = useRef(); const responseListener = useRef(); useEffect(() => { registerForPushNotificationsAsync().then(token => setExpoPushToken(token)); // Notification alındığında çalışır notificationListener.current = Notifications.addNotificationReceivedListener(notification => { console.log('Notification alındı:', notification); handleNotificationReceived(notification); }); // Notification'a tıklandığında çalışır responseListener.current = Notifications.addNotificationResponseReceivedListener(response => { console.log('Notification\'a tıklandı:', response); handleNotificationResponse(response); }); return () => { Notifications.removeNotificationSubscription(notificationListener.current); Notifications.removeNotificationSubscription(responseListener.current); }; }, []); return ( // Your app content ); } async function registerForPushNotificationsAsync() { let token; if (Platform.OS === 'android') { await Notifications.setNotificationChannelAsync('default', { name: 'default', importance: Notifications.AndroidImportance.MAX, vibrationPattern: [0, 250, 250, 250], lightColor: '#FF231F7C', }); // Özel kanallar oluştur await Notifications.setNotificationChannelAsync('messages', { name: 'Messages', importance: Notifications.AndroidImportance.HIGH, vibrationPattern: [0, 250, 250, 250], sound: 'message_sound.wav', }); await Notifications.setNotificationChannelAsync('orders', { name: 'Orders', importance: Notifications.AndroidImportance.HIGH, vibrationPattern: [0, 500, 250, 500], sound: 'order_sound.wav', }); await Notifications.setNotificationChannelAsync('promotions', { name: 'Promotions', importance: Notifications.AndroidImportance.DEFAULT, vibrationPattern: [0, 250], sound: 'promotion_sound.wav', }); } if (Device.isDevice) { const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== 'granted') { console.log('Push notification izni alınamadı!'); return; } try { token = (await Notifications.getExpoPushTokenAsync({ projectId: Constants.expoConfig?.extra?.eas?.projectId, })).data; console.log('Expo Push Token:', token); // Token'ı Laravel backend'e gönder await sendTokenToBackend(token); } catch (error) { console.error('Token alma hatası:', error); } } else { console.log('Push notifications sadece fiziksel cihazlarda çalışır'); } return token; } async function sendTokenToBackend(token) { try { const response = await fetch('https://your-laravel-app.com/api/user/expo-token', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, 'Accept': 'application/json', }, body: JSON.stringify({ expo_push_token: token, device_info: { platform: Platform.OS, version: Platform.Version, brand: Device.brand, model: Device.modelName, } }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); console.log('Token başarıyla gönderildi:', result); } catch (error) { console.error('Token gönderme hatası:', error); } } // Notification alındığında çağrılır function handleNotificationReceived(notification) { const { title, body, data } = notification.request.content; // Custom handling based on notification type switch (data?.type) { case 'message': // Update message count, show in-app notification etc. updateMessageCount(); break; case 'order_status': // Update order status, refresh order screen refreshOrderStatus(data.order_id); break; case 'promotion': // Show promotion banner, update promotions list showPromotionBanner(data.promotion_id); break; default: console.log('Unknown notification type:', data?.type); } } // Notification'a tıklandığında çağrılır function handleNotificationResponse(response) { const { data } = response.notification.request.content; // Navigate based on notification action switch (data?.action) { case 'open_chat': navigation.navigate('Chat', { messageId: data.message_id, senderId: data.sender_id }); break; case 'open_order': navigation.navigate('OrderDetail', { orderId: data.order_id }); break; case 'open_promotion': navigation.navigate('Promotion', { promotionId: data.promotion_id }); break; case 'open_profile': navigation.navigate('Profile'); break; default: navigation.navigate('Home'); } } // Helper functions function updateMessageCount() { // Update message count in your state management } function refreshOrderStatus(orderId) { // Refresh order status } function showPromotionBanner(promotionId) { // Show promotion banner }
🔧 Laravel API Endpoint'leri
// routes/api.php Route::middleware('auth:sanctum')->group(function () { Route::post('/user/expo-token', [UserController::class, 'updateExpoToken']); Route::get('/notifications/history', [NotificationController::class, 'history']); Route::post('/notifications/mark-as-read', [NotificationController::class, 'markAsRead']); }); // app/Http/Controllers/UserController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; class UserController extends Controller { public function updateExpoToken(Request $request): JsonResponse { $request->validate([ 'expo_push_token' => 'required|string', 'device_info' => 'nullable|array', ]); $user = $request->user(); $user->update([ 'expo_push_token' => $request->expo_push_token, 'device_info' => $request->device_info, 'token_updated_at' => now(), ]); return response()->json([ 'success' => true, 'message' => 'Expo token başarıyla güncellendi', ]); } } // app/Http/Controllers/NotificationController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; use AybimYazilim\LaravelExpoNotifications\Services\ExpoLogService; class NotificationController extends Controller { protected $expoLogService; public function __construct(ExpoLogService $expoLogService) { $this->expoLogService = $expoLogService; } public function history(Request $request): JsonResponse { $user = $request->user(); $history = $this->expoLogService->getUserNotificationHistory( get_class($user), $user->id, $request->get('limit', 50) ); return response()->json([ 'success' => true, 'data' => $history, ]); } public function markAsRead(Request $request): JsonResponse { $request->validate([ 'notification_ids' => 'required|array', 'notification_ids.*' => 'integer|exists:expo_notification_logs,id', ]); $user = $request->user(); ExpoNotificationLog::whereIn('id', $request->notification_ids) ->where('notifiable_type', get_class($user)) ->where('notifiable_id', $user->id) ->update(['read_at' => now()]); return response()->json([ 'success' => true, 'message' => 'Notification\'lar okundu olarak işaretlendi', ]); } }
📱 Advanced React Native Features
Notification Kategorileri ve İşlemler
// Notification kategorileri ve işlemleri tanımla import * as Notifications from 'expo-notifications'; // Kategorileri ayarla await Notifications.setNotificationCategoryAsync('message', [ { identifier: 'reply', buttonTitle: 'Yanıtla', textInput: { submitButtonTitle: 'Gönder', placeholder: 'Yanıtınızı yazın...', }, }, { identifier: 'mark_read', buttonTitle: 'Okundu', options: { opensAppToForeground: false, }, }, ]); await Notifications.setNotificationCategoryAsync('order', [ { identifier: 'view_order', buttonTitle: 'Siparişi Görüntüle', }, { identifier: 'track_order', buttonTitle: 'Takip Et', }, ]); // Action response handler responseListener.current = Notifications.addNotificationResponseReceivedListener(response => { const { actionIdentifier, userText } = response; const { data } = response.notification.request.content; switch (actionIdentifier) { case 'reply': handleReplyAction(data.message_id, userText); break; case 'mark_read': handleMarkAsReadAction(data.message_id); break; case 'view_order': navigation.navigate('OrderDetail', { orderId: data.order_id }); break; case 'track_order': navigation.navigate('OrderTracking', { orderId: data.order_id }); break; default: handleNotificationResponse(response); } }); async function handleReplyAction(messageId, replyText) { try { await fetch('https://your-laravel-app.com/api/messages/reply', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, }, body: JSON.stringify({ message_id: messageId, reply: replyText, }), }); } catch (error) { console.error('Reply gönderme hatası:', error); } }
Badge Yönetimi
import * as Notifications from 'expo-notifications'; // Badge sayısını ayarla async function setBadgeCount(count) { await Notifications.setBadgeCountAsync(count); } // Badge sayısını temizle async function clearBadge() { await Notifications.setBadgeCountAsync(0); } // Uygulama açıldığında badge'i temizle useEffect(() => { const subscription = AppState.addEventListener('change', (nextAppState) => { if (nextAppState === 'active') { clearBadge(); } }); return () => subscription?.remove(); }, []);
### Bulk Notification Gönderimi
```php
use AybimYazilim\LaravelExpoNotifications\Services\ExpoService;
$expoService = app(ExpoService::class);
$messages = [
[
'to' => 'ExponentPushToken[token1]',
'title' => 'Title 1',
'body' => 'Body 1',
],
[
'to' => 'ExponentPushToken[token2]',
'title' => 'Title 2',
'body' => 'Body 2',
],
// ... daha fazla mesaj
];
$response = $expoService->sendBulkNotifications($messages);
Custom Exception Handling
namespace App\Exceptions; use AybimYazilim\LaravelExpoNotifications\Exceptions\ExpoNotificationException; class Handler extends ExceptionHandler { public function register() { $this->reportable(function (ExpoNotificationException $e) { // Özel Expo notification hata loglama Log::channel('expo')->error('Expo Notification Error', [ 'message' => $e->getMessage(), 'context' => $e->getContext(), 'trace' => $e->getTraceAsString() ]); // Slack, email vb. bildirim gönder if (app()->environment('production')) { $this->notifyAdmins($e); } }); } }
🧪 Testing
use AybimYazilim\LaravelExpoNotifications\Tests\TestCase; use Illuminate\Support\Facades\Http; class ExpoNotificationTest extends TestCase { /** @test */ public function it_sends_expo_notification_successfully() { Http::fake([ 'exp.host/--/api/v2/push/send' => Http::response([ 'data' => [ [ 'status' => 'ok', 'id' => 'test-ticket-id' ] ] ]) ]); $user = User::factory()->create([ 'expo_push_token' => 'ExponentPushToken[test-token]' ]); $user->notify(new NewMessageNotification($message)); Http::assertSent(function ($request) { return str_contains($request->url(), 'exp.host') && $request['title'] === 'Yeni Mesajınız Var! 💬'; }); $this->assertDatabaseHas('expo_notification_logs', [ 'expo_token' => 'ExponentPushToken[test-token]', 'status' => 'sent' ]); } /** @test */ public function it_handles_invalid_token_error() { Http::fake([ 'exp.host/--/api/v2/push/send' => Http::response([ 'data' => [ [ 'status' => 'error', 'message' => 'DeviceNotRegistered', 'details' => [ 'error' => 'DeviceNotRegistered' ] ] ] ]) ]); $this->expectException(InvalidTokenException::class); $user = User::factory()->create([ 'expo_push_token' => 'invalid-token' ]); $user->notify(new NewMessageNotification($message)); } }
📋 Exception Türleri
ExpoNotificationException: Genel Expo notification hatalarıInvalidTokenException: Geçersiz veya kayıtlı olmayan tokenInvalidMessageException: Geçersiz mesaj formatı
🔍 Debugging
Debug modu için .env dosyanıza:
LOG_CHANNEL=stack EXPO_LOG_REQUESTS=true EXPO_LOG_RESPONSES=true
Log dosyalarında detaylı Expo notification gönderim bilgilerini görebilirsiniz.
📝 Changelog
v1.0.0
- ✅ Expo Push Notifications API entegrasyonu
- ✅ Kapsamlı hata yakalama sistemi
- ✅ Detaylı notification loglama
- ✅ Bulk notification desteği
- ✅ İstatistik ve raporlama
- ✅ Receipt status kontrolü
- ✅ Queue desteği ve retry mekanizması
🤝 Katkıda Bulunma
- Fork edin
- Feature branch oluşturun (
git checkout -b feature/amazing-feature) - Değişikliklerinizi commit edin (
git commit -m 'Add amazing feature') - Branch'i push edin (
git push origin feature/amazing-feature) - Pull Request oluşturun
📄 Lisans
Bu paket MIT lisansı altında yayınlanmıştır.
📞 Destek
Herhangi bir sorun yaşarsanız, GitHub Issues üzerinden bildirebilirsiniz.
aybimyazilim/laravel-expo-notifications 适用场景与选型建议
aybimyazilim/laravel-expo-notifications 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 63 次下载、GitHub Stars 达 0, 最近一次更新时间为 2025 年 09 月 12 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「notifications」 「mobile」 「laravel」 「expo」 「push-notifications」 「react-native」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 aybimyazilim/laravel-expo-notifications 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 aybimyazilim/laravel-expo-notifications 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 aybimyazilim/laravel-expo-notifications 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Beyonic PHP Library
A Laravel Nova package that adds a notification feed in your Nova app.
The skeleton application for NativePHP for Mobile.
PHP SDK for Mobile Message SMS API
Captures outgoing SMS notifications
Alfabank REST API integration
统计信息
- 总下载量: 63
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 13
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-09-12