Просмотр исходного кода

升级 Laravel 13,后台实时推送改用 Reverb + Laravel Echo

Laravel 9 → 13:
- PHP ^8.3(composer platform 固定 8.3.0),composer.lock 纳入版本控制
- firebase/php-jwt 7(JWT_SECRET 须 ≥ 32 字节,后台需重新登录)、Sanctum 4、Tinker 3、PHPUnit 12,显式依赖 brick/math 1.0
- 修复 Carbon 3 下投注额 30 天判断恒成立、brick/math RoundingMode 枚举改名
- CSRF 中间件改继承 PreventRequestForgery,Sanctum 配置键更新
- 测试适配 PHPUnit 12 / Laravel 13

后台实时推送:
- 移除 Node 网关与票据协议,改由 admin-realtime:relay 读取事件表、同步广播到 Reverb
- 事件表增加连续 seq,GET /admin/realtime/replay 断线补发,积压或过期时返回 resync
- POST /admin/broadcasting/auth 校验后台 JWT 与按钮权限;JWT 到期由 relay 调用 Reverb 断开
- 事件表、列表行解析、频道不变,melbet_sport-api 写入方式不变
- 更新 admin-websocket.md、前端接入文档与升级说明

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
doge 23 часов назад
Родитель
Сommit
7d13767aa8
60 измененных файлов с 13375 добавлено и 883 удалено
  1. 0 2
      .gitignore
  2. 111 0
      app/Console/Commands/RelayAdminRealtimeEvents.php
  3. 38 0
      app/Events/AdminRealtimeBroadcast.php
  4. 1 1
      app/Http/Controllers/admin/Bet.php
  5. 69 16
      app/Http/Controllers/admin/Realtime.php
  6. 1 1
      app/Http/Kernel.php
  7. 1 1
      app/Http/Middleware/VerifyCsrfToken.php
  8. 129 0
      app/Services/AdminRealtime/AdminRealtimeAccess.php
  9. 85 20
      app/Services/AdminRealtime/AdminRealtimeFeed.php
  10. 0 67
      app/Services/AdminRealtime/AdminRealtimeTicket.php
  11. 1 1
      app/Services/ShareCommissionRecorder.php
  12. 20 17
      composer.json
  13. 11671 0
      composer.lock
  14. 26 14
      config/admin_realtime.php
  15. 20 2
      config/broadcasting.php
  16. 105 0
      config/reverb.php
  17. 2 1
      config/sanctum.php
  18. 27 0
      database/migrations/2026_10_31_210000_add_seq_to_admin_realtime_events.php
  19. 243 187
      docs/admin-websocket.md
  20. 5 0
      docs/前端交付/README.md
  21. 2 1
      docs/前端交付/bot-28/后台/README.md
  22. 197 0
      docs/前端交付/bot-28/后台/后台实时推送-前端接入.md
  23. 25 0
      docs/前端交付/交付清单.json
  24. 2 1
      docs/前端交付/交付清单.md
  25. 130 0
      docs/后端/Laravel13升级说明.md
  26. 197 0
      docs/总后台/后台实时推送-前端接入.md
  27. 22 12
      example.env
  28. 0 315
      realtime/admin-gateway.js
  29. 0 37
      realtime/package-lock.json
  30. 0 15
      realtime/package.json
  31. 5 4
      routes/admin.php
  32. 1 5
      tests/Integration/ActivityCatalogTest.php
  33. 11 9
      tests/Integration/ActivityRewardEligibilityTest.php
  34. 121 33
      tests/Integration/AdminRealtimeTest.php
  35. 1 5
      tests/Integration/AgentApiRegressionTest.php
  36. 1 3
      tests/Integration/AgentApplicationTest.php
  37. 3 4
      tests/Integration/AgentPageConfigurationTest.php
  38. 4 5
      tests/Integration/AnnouncementTest.php
  39. 3 6
      tests/Integration/AutomaticRewardGrantTest.php
  40. 14 18
      tests/Integration/BannerPublicationTest.php
  41. 3 4
      tests/Integration/MaintenanceTest.php
  42. 1 3
      tests/Integration/MemberTokenIdleTimeoutTest.php
  43. 1 5
      tests/Integration/MenuIconTest.php
  44. 1 5
      tests/Integration/PaymentChannelOptionsTest.php
  45. 1 3
      tests/Integration/PromotionActivitiesTest.php
  46. 1 3
      tests/Integration/RechargeBonusTest.php
  47. 6 7
      tests/Integration/ReportingTest.php
  48. 3 7
      tests/Integration/SettleLedgerTest.php
  49. 1 3
      tests/Integration/ShareAdminManagementTest.php
  50. 10 13
      tests/Integration/ShareCommissionRecordingTest.php
  51. 1 1
      tests/Integration/SharingBetFactsTest.php
  52. 5 4
      tests/Integration/SharingSettlementTest.php
  53. 3 4
      tests/Integration/SiteConfigurationTest.php
  54. 3 4
      tests/Integration/SiteMessageTest.php
  55. 1 5
      tests/Integration/UsdtRechargeConfigTest.php
  56. 13 0
      tests/Support/JsonResponseFactory.php
  57. 2 1
      tests/Unit/PromotionAmountFormatterTest.php
  58. 3 2
      tests/Unit/SharingEffectiveBetTest.php
  59. 18 3
      tests/Unit/SharingFootballSettlementTest.php
  60. 4 3
      tests/Unit/ThirdGamePlayerIdTest.php

+ 0 - 2
.gitignore

@@ -21,11 +21,9 @@ yarn-error.log
 /.fleet
 /.idea
 /.vscode
-/composer.lock
 /api.txt
 /apidoc.json
 /package-lock.json
 /src/
 /doc/
 DOCKER_README.md
-/realtime/node_modules

+ 111 - 0
app/Console/Commands/RelayAdminRealtimeEvents.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Events\AdminRealtimeBroadcast;
+use App\Services\AdminRealtime\AdminRealtimeAccess;
+use App\Services\AdminRealtime\AdminRealtimeFeed;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Broadcast;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Str;
+
+/**
+ * 常驻进程:把 admin_realtime_events 中的事件解析成列表行并广播到 Reverb,
+ * 同时扫描会员上下线、断开 JWT 已过期的后台连接。需由 Supervisor 守护,全局只运行一个实例。
+ */
+class RelayAdminRealtimeEvents extends Command
+{
+    private const LOCK = 'admin_realtime:relay';
+    private const LOCK_TTL = 30;
+    private const SESSION_SWEEP_SECONDS = 5;
+
+    protected $signature = 'admin-realtime:relay
+        {--once : 只处理一批后退出,用于排查}
+        {--max-seconds=3600 : 运行指定秒数后正常退出,由 Supervisor 拉起新进程释放内存}';
+    protected $description = '后台实时推送:读取事件表并广播到 Reverb';
+
+    private bool $running = true;
+
+    public function handle(): int
+    {
+        if (!config('admin_realtime.enabled')) {
+            $this->error('实时推送未启用(ADMIN_WS_ENABLED=false)');
+            return self::FAILURE;
+        }
+        $owner = Str::random(16);
+        // 上个进程被强杀时锁要等 LOCK_TTL 才过期,这里等待而不是立即退出,避免 Supervisor 连续重启判定失败
+        for ($waited = 0; !Cache::add(self::LOCK, $owner, self::LOCK_TTL); $waited++) {
+            if ($waited >= self::LOCK_TTL + 5 || $this->option('once')) {
+                $this->error('已有 relay 实例在运行');
+                return self::FAILURE;
+            }
+            sleep(1);
+        }
+        DB::connection()->disableQueryLog();
+        $this->trap(fn() => [SIGTERM, SIGINT, SIGQUIT], function () {
+            $this->running = false;
+        });
+
+        $deadline = time() + max(60, (int) $this->option('max-seconds'));
+        $pollMs = max(200, (int) config('admin_realtime.poll_ms', 1000));
+        $cursor = AdminRealtimeFeed::startCursor();
+        $nextSweep = 0;
+        $this->info("relay 启动,游标 {$cursor}");
+
+        try {
+            while ($this->running) {
+                if (Cache::get(self::LOCK) !== $owner) {
+                    $this->error('relay 锁已失效,退出以免重复广播');
+                    return self::FAILURE;
+                }
+                Cache::put(self::LOCK, $owner, self::LOCK_TTL);
+
+                $hasMore = false;
+                try {
+                    $batch = AdminRealtimeFeed::next($cursor);
+                    $cursor = $batch['cursor'];
+                    $hasMore = $batch['has_more'];
+                    foreach ($batch['events'] as $message) {
+                        $this->broadcast($message);
+                    }
+                    if (time() >= $nextSweep) {
+                        AdminRealtimeAccess::terminateExpiredSessions();
+                        $nextSweep = time() + self::SESSION_SWEEP_SECONDS;
+                    }
+                } catch (\Throwable $e) {
+                    // 数据库等临时故障:记录后稍后重试,已分配 seq 的事件前端可凭补发接口追回
+                    Log::channel('realtime')->error('admin_realtime_relay_failed', ['cursor' => $cursor, 'error' => $e->getMessage()]);
+                    sleep(min(10, (int) ceil($pollMs * 5 / 1000)));
+                }
+
+                if ($this->option('once') || time() >= $deadline) {
+                    break;
+                }
+                if (!$hasMore) {
+                    usleep($pollMs * 1000);
+                }
+            }
+        } finally {
+            if (Cache::get(self::LOCK) === $owner) {
+                Cache::forget(self::LOCK);
+            }
+        }
+
+        return self::SUCCESS;
+    }
+
+    /** 单条广播失败(Reverb 不可用、消息过大)只记录,前端重连后经补发接口按 seq 追回 */
+    private function broadcast(array $message): void
+    {
+        try {
+            Broadcast::queue(new AdminRealtimeBroadcast($message));
+        } catch (\Throwable $e) {
+            Log::channel('realtime')->error('admin_realtime_broadcast_failed', [
+                'seq' => $message['seq'], 'channel' => $message['channel'], 'error' => $e->getMessage(),
+            ]);
+        }
+    }
+}

+ 38 - 0
app/Events/AdminRealtimeBroadcast.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Events;
+
+use Illuminate\Broadcasting\PrivateChannel;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
+
+/**
+ * 后台列表实时推送的单条消息,由 admin-realtime:relay 同步广播(不经队列)。
+ * 前端 Echo:echo.private('admin.orders.sport').listen('.order.created', handler)
+ */
+class AdminRealtimeBroadcast implements ShouldBroadcastNow
+{
+    /** @param array{seq:int,id:int,channel:string,event:string,action:string,key:string,data:array,source:string,created_at:string} $message */
+    public function __construct(public readonly array $message)
+    {
+    }
+
+    public function broadcastConnections(): array
+    {
+        return [config('admin_realtime.connection')];
+    }
+
+    public function broadcastOn(): array
+    {
+        return [new PrivateChannel($this->message['channel'])];
+    }
+
+    public function broadcastAs(): string
+    {
+        return $this->message['event'];
+    }
+
+    public function broadcastWith(): array
+    {
+        return $this->message;
+    }
+}

+ 1 - 1
app/Http/Controllers/admin/Bet.php

@@ -57,7 +57,7 @@ class Bet extends Controller
             if ((isset($params['member_id']) || isset($params['username'])) && isset($params['start_time'])) {
                 $start = Carbon::parse($params['start_time']);
                 $end = Carbon::parse($params['end_time']);
-                if ($end->diffInDays($start) < 30) $totalAmount = $query->sum('amount');
+                if ($start->diffInDays($end, true) < 30) $totalAmount = $query->sum('amount');
 
             }
 

+ 69 - 16
app/Http/Controllers/admin/Realtime.php

@@ -4,47 +4,100 @@ namespace App\Http\Controllers\admin;
 
 use App\Constants\HttpStatus;
 use App\Http\Controllers\Controller;
+use App\Models\Admin;
+use App\Services\AdminRealtime\AdminRealtimeAccess;
 use App\Services\AdminRealtime\AdminRealtimeFeed;
-use App\Services\AdminRealtime\AdminRealtimeTicket;
+use App\Services\AdminRealtime\AdminRealtimeRows;
 use App\Services\JwtService;
 use Exception;
 use Illuminate\Http\JsonResponse;
+use Illuminate\Validation\ValidationException;
 
-/** 后台订单、用户列表实时推送,接入说明见 docs/admin-websocket.md */
+/** 后台订单、用户列表实时推送(Reverb + Laravel Echo),接入说明见 docs/admin-websocket.md */
 class Realtime extends Controller
 {
-    /** 后台登录态换取推送网关连接票据 */
-    public function ticket(JwtService $jwtService): JsonResponse
+    /** Echo 连接参数、可订阅频道、当前 seq 与登录过期时间 */
+    public function config(JwtService $jwtService): JsonResponse
     {
         try {
             if (!config('admin_realtime.enabled')) {
                 throw new Exception('实时推送未启用');
             }
-            $token = str_replace('Bearer ', '', (string) request()->header('Authorization'));
-            $session = $jwtService->validateToken($token);
-            $result = AdminRealtimeTicket::issue((int) request()->user->id, isset($session->exp) ? (int) $session->exp : null);
+            $session = $jwtService->validateToken($this->bearerToken());
+            $client = config('admin_realtime.client');
+            $result = [
+                'broadcaster' => 'reverb',
+                'key' => (string) $client['key'],
+                'ws_host' => (string) $client['host'],
+                'ws_port' => (int) $client['port'],
+                'force_tls' => $client['scheme'] === 'https',
+                'auth_endpoint' => '/admin/broadcasting/auth',
+                'channels' => AdminRealtimeAccess::allowedChannels((int) request()->user->id),
+                'last_seq' => AdminRealtimeFeed::lastSeq(),
+                'session_expires_at' => isset($session->exp) ? (int) $session->exp : null,
+            ];
         } catch (Exception $e) {
             return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
         }
         return $this->success($result);
     }
 
-    /** 推送网关内部拉取事件,仅凭 X-Realtime-Secret 访问 */
-    public function events(): JsonResponse
+    /**
+     * Echo 私有频道授权(Pusher 协议),直接返回 {auth, channel_data}。
+     * 不走 admin.jwt:该中间件未登录时仍返回 HTTP 200,Echo 无法区分,这里改用 401 / 403。
+     */
+    public function auth(JwtService $jwtService): JsonResponse
     {
-        $secret = (string) config('admin_realtime.internal_secret');
-        if ($secret === '' || !hash_equals($secret, (string) request()->header('X-Realtime-Secret'))) {
-            return response()->json(['code' => HttpStatus::CUSTOM_ERROR, 'timestamp' => time(), 'msg' => 'forbidden', 'data' => []], 403);
-        }
         if (!config('admin_realtime.enabled')) {
-            return $this->error(HttpStatus::CUSTOM_ERROR, '实时推送未启用');
+            return $this->denied(403, '实时推送未启用');
+        }
+        $session = $jwtService->validateToken($this->bearerToken());
+        $admin = $session && isset($session->user_id) ? Admin::query()->find($session->user_id) : null;
+        if (!$admin) {
+            return $this->denied(401, __('messages.' . HttpStatus::AUTHORIZATION_HEADER_NOT_FOUND));
+        }
+        $channelName = (string) request()->input('channel_name');
+        $channel = str_starts_with($channelName, 'private-') ? substr($channelName, 8) : '';
+        if (!isset(AdminRealtimeRows::CHANNELS[$channel]) || !in_array($channel, AdminRealtimeAccess::allowedChannels((int) $admin->id), true)) {
+            return $this->denied(403, '无权订阅该频道');
         }
         try {
-            $gaps = array_filter(explode(',', (string) request()->input('gaps', '')), 'ctype_digit');
-            $result = AdminRealtimeFeed::pull((int) request()->input('after', -1), $gaps);
+            $auth = AdminRealtimeAccess::authorize((int) $admin->id, isset($session->exp) ? (int) $session->exp : null, $channelName, (string) request()->input('socket_id'));
         } catch (\Throwable $e) {
+            return $this->denied(403, '参数错误');
+        }
+        return response()->json($auth);
+    }
+
+    /** 断线重连或首次订阅后补发 seq 之后的事件;返回 resync=true 时前端应重新拉取列表 */
+    public function replay(): JsonResponse
+    {
+        try {
+            request()->validate([
+                'after_seq' => ['required', 'integer', 'min:0'],
+                'channels' => ['required', 'string'],
+            ]);
+            if (!config('admin_realtime.enabled')) {
+                throw new Exception('实时推送未启用');
+            }
+            $allowed = AdminRealtimeAccess::allowedChannels((int) request()->user->id);
+            $channels = array_values(array_intersect(array_map('trim', explode(',', (string) request()->input('channels'))), $allowed));
+            $result = AdminRealtimeFeed::replay((int) request()->input('after_seq'), $channels) + ['channels' => $channels];
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
             return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
         }
         return $this->success($result);
     }
+
+    private function bearerToken(): string
+    {
+        return str_replace('Bearer ', '', (string) request()->header('Authorization'));
+    }
+
+    private function denied(int $status, string $message): JsonResponse
+    {
+        return response()->json(['code' => HttpStatus::CUSTOM_ERROR, 'timestamp' => time(), 'msg' => $message, 'data' => []], $status);
+    }
 }

+ 1 - 1
app/Http/Kernel.php

@@ -63,7 +63,7 @@ class Kernel extends HttpKernel
     ];
 
     //路由中间件。 这些中间件可以分配给组,也可以单独使用。
-    protected $routeMiddleware = [
+    protected $middlewareAliases = [
         'admin.jwt' => \App\Http\Middleware\JwtAdminMiddleware::class,
         'jwt' => \App\Http\Middleware\JwtMiddleware::class,
         'check.button.uri' => \App\Http\Middleware\CheckButtonPermission::class,

+ 1 - 1
app/Http/Middleware/VerifyCsrfToken.php

@@ -2,7 +2,7 @@
 
 namespace App\Http\Middleware;
 
-use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
+use Illuminate\Foundation\Http\Middleware\PreventRequestForgery as Middleware;
 
 class VerifyCsrfToken extends Middleware
 {

+ 129 - 0
app/Services/AdminRealtime/AdminRealtimeAccess.php

@@ -0,0 +1,129 @@
+<?php
+
+namespace App\Services\AdminRealtime;
+
+use App\Services\MenuService;
+use App\Services\RoleService;
+use App\Services\RoleUserService;
+use Illuminate\Support\Facades\Broadcast;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Log;
+use Pusher\Pusher;
+
+/**
+ * 后台实时推送的访问控制:频道授权与登录过期断开。
+ *
+ * 频道授权响应在 channel_data 中携带「会话 user_id」(管理员 id + JWT 过期时间),Reverb 把它记在连接上;
+ * relay 定期把已过期的会话登记出来,调用 Reverb 的 terminate 接口断开这些连接。
+ * 断开后前端自动重连会重新授权,过期的 JWT 授权失败(401),前端据此跳转登录。
+ */
+final class AdminRealtimeAccess
+{
+    private const SESSIONS = 'admin_realtime:sessions';
+
+    /** @return list<string> 管理员可订阅的频道,口径与 CheckButtonPermission 对列表接口的按钮权限一致 */
+    public static function allowedChannels(int $adminId): array
+    {
+        $ttl = max(0, (int) config('admin_realtime.permission_cache_seconds', 60));
+        if ($ttl === 0) {
+            return self::resolveChannels($adminId);
+        }
+
+        return Cache::remember('admin_realtime:channels:' . $adminId, $ttl, static fn(): array => self::resolveChannels($adminId));
+    }
+
+    private static function resolveChannels(int $adminId): array
+    {
+        $channels = array_keys(AdminRealtimeRows::CHANNELS);
+        if ($adminId === 1) {
+            return $channels;
+        }
+        $roleIds = collect(RoleUserService::findAll(['user_id' => $adminId]))->pluck('role_id')->toArray() ?: [-1];
+        $granted = [];
+        foreach (RoleService::model()::with(['menus' => function ($query) {
+            $query->where('type', MenuService::model()::TYPE_BUTTON);
+        }])->whereIn('id', $roleIds)->get() as $role) {
+            $granted = array_merge($granted, $role['menus_uris'] ?: []);
+        }
+
+        return array_values(array_filter($channels, static function (string $channel) use ($granted): bool {
+            $uri = AdminRealtimeRows::CHANNELS[$channel][1];
+            $menu = MenuService::findOne(['uri' => $uri, 'type' => MenuService::model()::TYPE_BUTTON, 'status' => MenuService::model()::STATUS_SHOW]);
+            return empty($menu) || in_array($uri, $granted, true);
+        }));
+    }
+
+    /**
+     * Pusher 协议私有频道授权,返回 {auth, channel_data}
+     *
+     * @param string $channelName 带 private- 前缀的频道名
+     * @throws \Pusher\PusherException socket_id 或频道名格式不合法
+     */
+    public static function authorize(int $adminId, ?int $sessionExpiresAt, string $channelName, string $socketId): array
+    {
+        $userId = self::sessionUserId($adminId, $sessionExpiresAt);
+        $auth = self::pusher()->authorizeChannel($channelName, $socketId, json_encode(['user_id' => $userId]));
+        if ($sessionExpiresAt) {
+            self::rememberSession($userId, $sessionExpiresAt);
+        }
+
+        return json_decode($auth, true);
+    }
+
+    public static function sessionUserId(int $adminId, ?int $sessionExpiresAt): string
+    {
+        return $adminId . ':' . (int) $sessionExpiresAt;
+    }
+
+    /** 断开 JWT 已过期的会话,由 relay 定期调用;返回断开的会话 user_id */
+    public static function terminateExpiredSessions(): array
+    {
+        $expired = [];
+        Cache::lock(self::SESSIONS . ':lock', 5)->block(3, function () use (&$expired) {
+            $sessions = Cache::get(self::SESSIONS, []);
+            $now = time();
+            foreach ($sessions as $userId => $exp) {
+                if ($exp <= $now) {
+                    $expired[] = (string) $userId;
+                    unset($sessions[$userId]);
+                }
+            }
+            if ($expired) {
+                Cache::forever(self::SESSIONS, $sessions);
+            }
+        });
+
+        foreach ($expired as $userId) {
+            try {
+                self::pusher()->terminateUserConnections($userId);
+            } catch (\Throwable $e) {
+                Log::channel('realtime')->error('admin_realtime_terminate_failed', ['user_id' => $userId, 'error' => $e->getMessage()]);
+            }
+        }
+
+        return $expired;
+    }
+
+    /** 同一会话订阅多个频道会重复授权,已登记的不再加锁写缓存 */
+    private static function rememberSession(string $userId, int $exp): void
+    {
+        if (isset(Cache::get(self::SESSIONS, [])[$userId])) {
+            return;
+        }
+        try {
+            Cache::lock(self::SESSIONS . ':lock', 5)->block(3, function () use ($userId, $exp) {
+                $sessions = Cache::get(self::SESSIONS, []);
+                $sessions[$userId] = $exp;
+                Cache::forever(self::SESSIONS, $sessions);
+            });
+        } catch (\Throwable $e) {
+            // 登记失败只影响到期主动断开,过期后重连仍会因授权失败断开
+            Log::channel('realtime')->error('admin_realtime_session_register_failed', ['user_id' => $userId, 'error' => $e->getMessage()]);
+        }
+    }
+
+    private static function pusher(): Pusher
+    {
+        return Broadcast::connection(config('admin_realtime.connection'))->getPusher();
+    }
+}

+ 85 - 20
app/Services/AdminRealtime/AdminRealtimeFeed.php

@@ -3,56 +3,121 @@
 namespace App\Services\AdminRealtime;
 
 use App\Services\OnlineUserService;
+use Illuminate\Support\Collection;
 use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
 /**
- * 推送网关的拉取入口:按 id 递增读取事件表并解析成列表行,顺带扫描会员上下线。
+ * 事件表的读取端:relay 按 id 递增取出未广播的事件、分配连续 seq 并解析成列表行;前端断线后按 seq 补发。
  *
  * 写入方可能在长事务内插入事件(melbet_sport-api),小 id 可能晚于大 id 提交,
- * 因此网关会把跳过的 id 记为 gaps 在随后一段时间内补拉,本接口按 gaps 原样返回已出现的行。
+ * 因此 relay 在 gap_seconds 内仍会补发游标之前未广播的行;seq 按广播先后分配,补发不会被前端漏掉。
  */
 final class AdminRealtimeFeed
 {
     private const ONLINE_SNAPSHOT = 'admin_realtime:online_snapshot';
     private const ONLINE_LOCK = 'admin_realtime:online_scan';
+    private const GAP_WINDOW = 10000;
+
+    /** relay 启动时的游标:继续上次广播到的位置;从未广播过则从当前最大 id 开始,不回放历史 */
+    public static function startCursor(): int
+    {
+        $delivered = DB::table('admin_realtime_events')->whereNotNull('seq')->max('id');
+
+        return (int) ($delivered ?? DB::table('admin_realtime_events')->max('id') ?? 0);
+    }
 
     /**
-     * @param int $after 网关已处理的最大事件 id;小于 0 表示网关刚启动,只返回当前最大 id 不回放历史
-     * @param list<int> $gaps 网关等待补拉的 id
-     * @return array{last_id:int,ids:list<int>,events:list<array>,has_more:bool}
+     * 取出一批待广播事件并分配 seq,顺带扫描会员上下线
+     *
+     * @return array{cursor:int,events:list<array>,has_more:bool}
      */
-    public static function pull(int $after, array $gaps = []): array
+    public static function next(int $cursor): array
     {
         self::scanOnline();
-        if ($after < 0) {
-            return ['last_id' => (int) DB::table('admin_realtime_events')->max('id'), 'ids' => [], 'events' => [], 'has_more' => false];
-        }
-
         $limit = max(1, min(1000, (int) config('admin_realtime.batch_size', 200)));
-        $rows = DB::table('admin_realtime_events')->where('id', '>', $after)->orderBy('id')->limit($limit)->get();
-        $hasMore = $rows->count() >= $limit;
-        $gaps = array_values(array_filter(array_map('intval', $gaps), static fn(int $id): bool => $id > 0 && $id <= $after));
-        if ($gaps) {
-            $rows = DB::table('admin_realtime_events')->whereIn('id', array_slice($gaps, 0, 500))->orderBy('id')->get()->concat($rows);
+        $gapFrom = now('Asia/Shanghai')->subSeconds(max(0, (int) config('admin_realtime.gap_seconds', 60)))->format('Y-m-d H:i:s');
+        $rows = DB::table('admin_realtime_events')
+            ->whereNull('seq')
+            ->where('id', '>', max(0, $cursor - self::GAP_WINDOW))
+            ->where(fn($q) => $q->where('id', '>', $cursor)->orWhere('created_at', '>=', $gapFrom))
+            ->orderBy('id')->limit($limit)->get();
+        if ($rows->isEmpty()) {
+            return ['cursor' => $cursor, 'events' => [], 'has_more' => false];
         }
 
+        self::assignSeq($rows);
+
         return [
-            'last_id' => (int) ($rows->where('id', '>', $after)->max('id') ?? $after),
-            'ids' => $rows->pluck('id')->map(static fn($id): int => (int) $id)->values()->all(),
+            'cursor' => max($cursor, (int) $rows->max('id')),
             'events' => self::resolve($rows->all()),
-            'has_more' => $hasMore,
+            'has_more' => $rows->count() >= $limit,
+        ];
+    }
+
+    /** relay 单实例运行,seq 从当前最大值连续递增 */
+    private static function assignSeq(Collection $rows): void
+    {
+        $seq = (int) DB::table('admin_realtime_events')->max('seq');
+        $cases = '';
+        foreach ($rows as $row) {
+            $row->seq = ++$seq;
+            $cases .= ' WHEN ' . (int) $row->id . ' THEN ' . $seq;
+        }
+        DB::table('admin_realtime_events')
+            ->whereIn('id', $rows->pluck('id')->all())
+            ->update(['seq' => DB::raw('CASE id' . $cases . ' END')]);
+    }
+
+    public static function lastSeq(): int
+    {
+        return (int) DB::table('admin_realtime_events')->max('seq');
+    }
+
+    /**
+     * 断线补发:返回 seq 大于 $afterSeq 的事件(按当前数据重新解析),只含请求且有权限的频道。
+     * 游标已被清理、无效或积压过多时返回 resync,前端应重新拉取列表。
+     *
+     * @param list<string> $channels
+     * @return array{resync:bool,reason?:string,last_seq:int,events:list<array>}
+     */
+    public static function replay(int $afterSeq, array $channels): array
+    {
+        $lastSeq = self::lastSeq();
+        $resync = static fn(string $reason): array => ['resync' => true, 'reason' => $reason, 'last_seq' => $lastSeq, 'events' => []];
+        if ($afterSeq < 0 || $afterSeq > $lastSeq) {
+            return $resync('invalid_cursor');
+        }
+        if ($afterSeq === $lastSeq) {
+            return ['resync' => false, 'last_seq' => $lastSeq, 'events' => []];
+        }
+        if ($afterSeq > 0 && !DB::table('admin_realtime_events')->where('seq', $afterSeq)->exists()) {
+            return $resync('history_unavailable');
+        }
+        $limit = max(1, (int) config('admin_realtime.replay_limit', 1000));
+        $rows = DB::table('admin_realtime_events')->where('seq', '>', $afterSeq)->where('seq', '<=', $lastSeq)
+            ->orderBy('seq')->limit($limit + 1)->get();
+        if ($rows->count() > $limit) {
+            return $resync('too_many_events');
+        }
+        $wanted = array_flip($channels);
+
+        return [
+            'resync' => false,
+            'last_seq' => $lastSeq,
+            'events' => array_values(array_filter(self::resolve($rows->all()), static fn(array $e): bool => isset($wanted[$e['channel']]))),
         ];
     }
 
-    /** 同一记录在一批内多次变更只解析一次,取最后一条事件的 id;先新增后更新仍按 created 推送 */
+    /** 同一记录在一批内多次变更只解析一次,取最后一条事件的 seq;先新增后更新仍按 created 推送 */
     private static function resolve(array $rows): array
     {
         $groups = [];
         foreach ($rows as $row) {
             $key = $row->resource . ':' . $row->resource_id;
             $created = ($groups[$key]['created'] ?? false) || $row->action === 'created';
+            unset($groups[$key]);
             $groups[$key] = ['row' => $row, 'created' => $created];
         }
 
@@ -70,6 +135,7 @@ final class AdminRealtimeFeed
             }
             foreach ($messages as $message) {
                 $events[] = [
+                    'seq' => (int) $row->seq,
                     'id' => (int) $row->id,
                     'channel' => $message['channel'],
                     'event' => self::eventName((string) $row->resource, $action),
@@ -81,7 +147,6 @@ final class AdminRealtimeFeed
                 ];
             }
         }
-        usort($events, static fn(array $a, array $b): int => $a['id'] <=> $b['id']);
 
         return $events;
     }

+ 0 - 67
app/Services/AdminRealtime/AdminRealtimeTicket.php

@@ -1,67 +0,0 @@
-<?php
-
-namespace App\Services\AdminRealtime;
-
-use App\Services\MenuService;
-use App\Services\RoleService;
-use App\Services\RoleUserService;
-use RuntimeException;
-
-/**
- * 推送网关连接票据:后台登录态(admin.jwt)换取短时效票据,网关用同一密钥校验签名。
- * 票据内携带该管理员可订阅的频道,口径与 CheckButtonPermission 对列表接口的按钮权限一致。
- */
-final class AdminRealtimeTicket
-{
-    /**
-     * @param int|null $sessionExpiresAt 后台 JWT 的过期时间戳,网关到期主动断开
-     */
-    public static function issue(int $adminId, ?int $sessionExpiresAt): array
-    {
-        $secret = (string) config('admin_realtime.ticket_secret');
-        if ($secret === '') {
-            throw new RuntimeException('未配置 ADMIN_WS_SECRET');
-        }
-        $channels = self::allowedChannels($adminId);
-        $ttl = max(10, (int) config('admin_realtime.ticket_ttl', 60));
-        $now = time();
-        $body = self::base64Url(json_encode([
-            'aid' => $adminId, 'ch' => $channels, 'iat' => $now, 'exp' => $now + $ttl,
-            'sxp' => $sessionExpiresAt, 'nonce' => bin2hex(random_bytes(8)),
-        ]));
-
-        return [
-            'ticket' => $body . '.' . self::base64Url(hash_hmac('sha256', $body, $secret, true)),
-            'expires_in' => $ttl,
-            'url' => (string) config('admin_realtime.public_url'),
-            'channels' => $channels,
-        ];
-    }
-
-    /** @return list<string> */
-    public static function allowedChannels(int $adminId): array
-    {
-        $channels = array_keys(AdminRealtimeRows::CHANNELS);
-        if ($adminId === 1) {
-            return $channels;
-        }
-        $roleIds = collect(RoleUserService::findAll(['user_id' => $adminId]))->pluck('role_id')->toArray() ?: [-1];
-        $granted = [];
-        foreach (RoleService::model()::with(['menus' => function ($query) {
-            $query->where('type', MenuService::model()::TYPE_BUTTON);
-        }])->whereIn('id', $roleIds)->get() as $role) {
-            $granted = array_merge($granted, $role['menus_uris'] ?: []);
-        }
-
-        return array_values(array_filter($channels, static function (string $channel) use ($granted): bool {
-            $uri = AdminRealtimeRows::CHANNELS[$channel][1];
-            $menu = MenuService::findOne(['uri' => $uri, 'type' => MenuService::model()::TYPE_BUTTON, 'status' => MenuService::model()::STATUS_SHOW]);
-            return empty($menu) || in_array($uri, $granted, true);
-        }));
-    }
-
-    private static function base64Url(string $value): string
-    {
-        return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
-    }
-}

+ 1 - 1
app/Services/ShareCommissionRecorder.php

@@ -121,6 +121,6 @@ class ShareCommissionRecorder
     {
         if (is_float($value) && is_finite($value)) $value = sprintf('%.10F', $value);
         if (!is_scalar($value) || !preg_match('/^-?\d{1,16}(?:\.\d+)?$/', (string)$value)) return null;
-        return (string)BigDecimal::of((string)$value)->toScale($scale, RoundingMode::HALF_UP);
+        return (string)BigDecimal::of((string)$value)->toScale($scale, RoundingMode::HalfUp);
     }
 }

+ 20 - 17
composer.json

@@ -1,7 +1,7 @@
 {
   "name": "laravel/laravel",
   "type": "project",
-  "minimum-stability": "dev",
+  "minimum-stability": "stable",
   "prefer-stable": true,
   "description": "The Laravel Framework.",
   "keywords": [
@@ -10,35 +10,35 @@
   ],
   "license": "MIT",
   "require": {
-    "php": ">=8.0",
+    "php": "^8.3",
+    "ext-bcmath": "*",
     "ext-curl": "*",
     "ext-gd": "*",
     "ext-json": "*",
-    "ext-bcmath": "*",
+    "brick/math": "^1.0",
     "endroid/qr-code": "^5.0",
-    "firebase/php-jwt": "^6.10",
+    "firebase/php-jwt": "^7.0",
     "guzzlehttp/guzzle": "^7.2",
-    "irazasyed/telegram-bot-sdk": "^3.15",
+    "irazasyed/telegram-bot-sdk": "^3.16",
     "kornrunner/keccak": "^1.1",
     "kornrunner/secp256k1": "^0.3.0",
-    "laravel-lang/lang": "^14.7",
-    "laravel/framework": "^9.19",
-    "laravel/sanctum": "^3.3",
-    "laravel/tinker": "^2.7",
+    "laravel-lang/lang": "^15.0",
+    "laravel/framework": "^13.0",
+    "laravel/reverb": "^1.6",
+    "laravel/sanctum": "^4.0",
+    "laravel/tinker": "^3.0",
     "phpseclib/phpseclib": "^3.0",
     "simplesoftwareio/simple-qrcode": "^4.2",
     "simplito/elliptic-php": "^1.0",
     "stephenhill/base58": "^1.1"
-
   },
   "require-dev": {
-    "fakerphp/faker": "^1.9.1",
-    "laravel/pint": "^1.0",
-    "laravel/sail": "^1.0.1",
-    "mockery/mockery": "^1.4.4",
-    "nunomaduro/collision": "^6.1",
-    "phpunit/phpunit": "^9.5.10",
-    "spatie/laravel-ignition": "^1.0"
+    "fakerphp/faker": "^1.23",
+    "laravel/pint": "^1.18",
+    "laravel/sail": "^1.41",
+    "mockery/mockery": "^1.6",
+    "nunomaduro/collision": "^8.6",
+    "phpunit/phpunit": "^12.0"
   },
   "autoload": {
     "psr-4": {
@@ -77,6 +77,9 @@
     }
   },
   "config": {
+    "platform": {
+      "php": "8.3.0"
+    },
     "optimize-autoloader": true,
     "preferred-install": "dist",
     "sort-packages": true,

+ 11671 - 0
composer.lock

@@ -0,0 +1,11671 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "fe2c263a9aec7707b250935e9c1ed256",
+    "packages": [
+        {
+            "name": "archtechx/enums",
+            "version": "v1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/archtechx/enums.git",
+                "reference": "81375b71c176f680880a95e7448d84258cfb5c72"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/archtechx/enums/zipball/81375b71c176f680880a95e7448d84258cfb5c72",
+                "reference": "81375b71c176f680880a95e7448d84258cfb5c72",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "larastan/larastan": "^2.4",
+                "orchestra/testbench": "^8.0 || ^9.0",
+                "pestphp/pest": "^2.0",
+                "pestphp/pest-plugin-laravel": "^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "phpstan": {
+                    "includes": [
+                        "extension.neon"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "ArchTech\\Enums\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Samuel Štancl",
+                    "email": "samuel@archte.ch"
+                }
+            ],
+            "description": "Helpers for making PHP enums more lovable.",
+            "support": {
+                "issues": "https://github.com/archtechx/enums/issues",
+                "source": "https://github.com/archtechx/enums/tree/v1.1.2"
+            },
+            "time": "2025-06-06T23:15:09+00:00"
+        },
+        {
+            "name": "bacon/bacon-qr-code",
+            "version": "2.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Bacon/BaconQrCode.git",
+                "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22",
+                "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22",
+                "shasum": ""
+            },
+            "require": {
+                "dasprid/enum": "^1.0.3",
+                "ext-iconv": "*",
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "phly/keep-a-changelog": "^2.1",
+                "phpunit/phpunit": "^7 | ^8 | ^9",
+                "spatie/phpunit-snapshot-assertions": "^4.2.9",
+                "squizlabs/php_codesniffer": "^3.4"
+            },
+            "suggest": {
+                "ext-imagick": "to generate QR code images"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "BaconQrCode\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-2-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Ben Scholzen 'DASPRiD'",
+                    "email": "mail@dasprids.de",
+                    "homepage": "https://dasprids.de/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "BaconQrCode is a QR code generator for PHP.",
+            "homepage": "https://github.com/Bacon/BaconQrCode",
+            "support": {
+                "issues": "https://github.com/Bacon/BaconQrCode/issues",
+                "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8"
+            },
+            "time": "2022-12-07T17:46:57+00:00"
+        },
+        {
+            "name": "brick/math",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/brick/math.git",
+                "reference": "2effe05d2177c451b86c6a073196a4034c02f211"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/brick/math/zipball/2effe05d2177c451b86c6a073196a4034c02f211",
+                "reference": "2effe05d2177c451b86c6a073196a4034c02f211",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.2"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "2.2.13",
+                "phpstan/phpstan-phpunit": "2.0.18",
+                "phpunit/phpunit": "^11.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Brick\\Math\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Arbitrary-precision arithmetic library",
+            "keywords": [
+                "Arbitrary-precision",
+                "BigInteger",
+                "BigRational",
+                "arithmetic",
+                "bigdecimal",
+                "bignum",
+                "bignumber",
+                "brick",
+                "decimal",
+                "integer",
+                "math",
+                "mathematics",
+                "rational"
+            ],
+            "support": {
+                "issues": "https://github.com/brick/math/issues",
+                "source": "https://github.com/brick/math/tree/1.0.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/BenMorel",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-09-12T10:28:18+00:00"
+        },
+        {
+            "name": "carbonphp/carbon-doctrine-types",
+            "version": "3.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git",
+                "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc",
+                "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1"
+            },
+            "conflict": {
+                "doctrine/dbal": "<4.0.0 || >=5.0.0"
+            },
+            "require-dev": {
+                "doctrine/dbal": "^4.0.0",
+                "nesbot/carbon": "^2.71.0 || ^3.0.0",
+                "phpunit/phpunit": "^10.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "KyleKatarn",
+                    "email": "kylekatarnls@gmail.com"
+                }
+            ],
+            "description": "Types to use Carbon in Doctrine",
+            "keywords": [
+                "carbon",
+                "date",
+                "datetime",
+                "doctrine",
+                "time"
+            ],
+            "support": {
+                "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues",
+                "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/kylekatarnls",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/Carbon",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-09-06T14:11:38+00:00"
+        },
+        {
+            "name": "clue/redis-protocol",
+            "version": "v0.3.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/clue/redis-protocol.git",
+                "reference": "6f565332f5531b7722d1e9c445314b91862f6d6c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/clue/redis-protocol/zipball/6f565332f5531b7722d1e9c445314b91862f6d6c",
+                "reference": "6f565332f5531b7722d1e9c445314b91862f6d6c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Clue\\Redis\\Protocol\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Christian Lück",
+                    "email": "christian@lueck.tv"
+                }
+            ],
+            "description": "A streaming Redis protocol (RESP) parser and serializer written in pure PHP.",
+            "homepage": "https://github.com/clue/redis-protocol",
+            "keywords": [
+                "parser",
+                "protocol",
+                "redis",
+                "resp",
+                "serializer",
+                "streaming"
+            ],
+            "support": {
+                "issues": "https://github.com/clue/redis-protocol/issues",
+                "source": "https://github.com/clue/redis-protocol/tree/v0.3.2"
+            },
+            "funding": [
+                {
+                    "url": "https://clue.engineering/support",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/clue",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-08-07T11:06:28+00:00"
+        },
+        {
+            "name": "clue/redis-react",
+            "version": "v2.8.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/clue/reactphp-redis.git",
+                "reference": "84569198dfd5564977d2ae6a32de4beb5a24bdca"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/clue/reactphp-redis/zipball/84569198dfd5564977d2ae6a32de4beb5a24bdca",
+                "reference": "84569198dfd5564977d2ae6a32de4beb5a24bdca",
+                "shasum": ""
+            },
+            "require": {
+                "clue/redis-protocol": "^0.3.2",
+                "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+                "php": ">=5.3",
+                "react/event-loop": "^1.2",
+                "react/promise": "^3.2 || ^2.0 || ^1.1",
+                "react/promise-timer": "^1.11",
+                "react/socket": "^1.16"
+            },
+            "require-dev": {
+                "clue/block-react": "^1.5",
+                "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Clue\\React\\Redis\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Christian Lück",
+                    "email": "christian@clue.engineering"
+                }
+            ],
+            "description": "Async Redis client implementation, built on top of ReactPHP.",
+            "homepage": "https://github.com/clue/reactphp-redis",
+            "keywords": [
+                "async",
+                "client",
+                "database",
+                "reactphp",
+                "redis"
+            ],
+            "support": {
+                "issues": "https://github.com/clue/reactphp-redis/issues",
+                "source": "https://github.com/clue/reactphp-redis/tree/v2.8.0"
+            },
+            "funding": [
+                {
+                    "url": "https://clue.engineering/support",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/clue",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-01-03T16:18:33+00:00"
+        },
+        {
+            "name": "composer/semver",
+            "version": "3.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/composer/semver.git",
+                "reference": "d92543b7ede9c4ec6b59853cefb061e39866d2cf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/composer/semver/zipball/d92543b7ede9c4ec6b59853cefb061e39866d2cf",
+                "reference": "d92543b7ede9c4ec6b59853cefb061e39866d2cf",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.2 || ^7.0 || ^8.0"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^1.11",
+                "symfony/phpunit-bridge": "^3 || ^7"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Composer\\Semver\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nils Adermann",
+                    "email": "naderman@naderman.de",
+                    "homepage": "http://www.naderman.de"
+                },
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "http://seld.be"
+                },
+                {
+                    "name": "Rob Bast",
+                    "email": "rob.bast@gmail.com",
+                    "homepage": "http://robbast.nl"
+                }
+            ],
+            "description": "Version comparison library that offers utilities, version constraint parsing and validation.",
+            "keywords": [
+                "semantic",
+                "semver",
+                "validation",
+                "versioning"
+            ],
+            "support": {
+                "irc": "ircs://irc.libera.chat:6697/composer",
+                "issues": "https://github.com/composer/semver/issues",
+                "source": "https://github.com/composer/semver/tree/3.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://packagist.com",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/composer",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-10-29T09:13:34+00:00"
+        },
+        {
+            "name": "dasprid/enum",
+            "version": "1.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/DASPRiD/Enum.git",
+                "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
+                "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1 <9.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
+                "squizlabs/php_codesniffer": "*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "DASPRiD\\Enum\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-2-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Ben Scholzen 'DASPRiD'",
+                    "email": "mail@dasprids.de",
+                    "homepage": "https://dasprids.de/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "PHP 7.1 enum implementation",
+            "keywords": [
+                "enum",
+                "map"
+            ],
+            "support": {
+                "issues": "https://github.com/DASPRiD/Enum/issues",
+                "source": "https://github.com/DASPRiD/Enum/tree/1.0.7"
+            },
+            "time": "2025-09-16T12:23:56+00:00"
+        },
+        {
+            "name": "dflydev/dot-access-data",
+            "version": "v3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dflydev/dflydev-dot-access-data.git",
+                "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f",
+                "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^0.12.42",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
+                "scrutinizer/ocular": "1.6.0",
+                "squizlabs/php_codesniffer": "^3.5",
+                "vimeo/psalm": "^4.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Dflydev\\DotAccessData\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Dragonfly Development Inc.",
+                    "email": "info@dflydev.com",
+                    "homepage": "http://dflydev.com"
+                },
+                {
+                    "name": "Beau Simensen",
+                    "email": "beau@dflydev.com",
+                    "homepage": "http://beausimensen.com"
+                },
+                {
+                    "name": "Carlos Frutos",
+                    "email": "carlos@kiwing.it",
+                    "homepage": "https://github.com/cfrutos"
+                },
+                {
+                    "name": "Colin O'Dell",
+                    "email": "colinodell@gmail.com",
+                    "homepage": "https://www.colinodell.com"
+                }
+            ],
+            "description": "Given a deep data structure, access data by dot notation.",
+            "homepage": "https://github.com/dflydev/dflydev-dot-access-data",
+            "keywords": [
+                "access",
+                "data",
+                "dot",
+                "notation"
+            ],
+            "support": {
+                "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
+                "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3"
+            },
+            "time": "2024-07-08T12:26:09+00:00"
+        },
+        {
+            "name": "doctrine/inflector",
+            "version": "2.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/inflector.git",
+                "reference": "46351b6812b7b6d52b82c3efa5dfb786a198ec88"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/inflector/zipball/46351b6812b7b6d52b82c3efa5dfb786a198ec88",
+                "reference": "46351b6812b7b6d52b82c3efa5dfb786a198ec88",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^12.0 || ^14.0",
+                "phpstan/phpstan": "^1.12 || ^2.0",
+                "phpstan/phpstan-phpunit": "^1.4 || ^2.0",
+                "phpstan/phpstan-strict-rules": "^1.6 || ^2.0",
+                "phpunit/phpunit": "^8.5 || ^12.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Inflector\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
+                {
+                    "name": "Roman Borschel",
+                    "email": "roman@code-factory.org"
+                },
+                {
+                    "name": "Benjamin Eberlei",
+                    "email": "kontakt@beberlei.de"
+                },
+                {
+                    "name": "Jonathan Wage",
+                    "email": "jonwage@gmail.com"
+                },
+                {
+                    "name": "Johannes Schmitt",
+                    "email": "schmittjoh@gmail.com"
+                }
+            ],
+            "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.",
+            "homepage": "https://www.doctrine-project.org/projects/inflector.html",
+            "keywords": [
+                "inflection",
+                "inflector",
+                "lowercase",
+                "manipulation",
+                "php",
+                "plural",
+                "singular",
+                "strings",
+                "uppercase",
+                "words"
+            ],
+            "support": {
+                "issues": "https://github.com/doctrine/inflector/issues",
+                "source": "https://github.com/doctrine/inflector/tree/2.1.1"
+            },
+            "funding": [
+                {
+                    "url": "https://www.doctrine-project.org/sponsorship.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.patreon.com/phpdoctrine",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-26T08:14:14+00:00"
+        },
+        {
+            "name": "doctrine/lexer",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/lexer.git",
+                "reference": "71d305eaa6480d3d3460c7d053d1da7749fa9cca"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/lexer/zipball/71d305eaa6480d3d3460c7d053d1da7749fa9cca",
+                "reference": "71d305eaa6480d3d3460c7d053d1da7749fa9cca",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^14",
+                "phpstan/phpstan": "^2",
+                "phpunit/phpunit": "^10.5.58 || ^12.5.4"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Common\\Lexer\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
+                {
+                    "name": "Roman Borschel",
+                    "email": "roman@code-factory.org"
+                },
+                {
+                    "name": "Johannes Schmitt",
+                    "email": "schmittjoh@gmail.com"
+                }
+            ],
+            "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+            "homepage": "https://www.doctrine-project.org/projects/lexer.html",
+            "keywords": [
+                "annotations",
+                "docblock",
+                "lexer",
+                "parser",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/doctrine/lexer/issues",
+                "source": "https://github.com/doctrine/lexer/tree/3.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://www.doctrine-project.org/sponsorship.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.patreon.com/phpdoctrine",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-01T06:59:29+00:00"
+        },
+        {
+            "name": "dragon-code/contracts",
+            "version": "2.25.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/TheDragonCode/contracts.git",
+                "reference": "13d1254801026be5ba33cf1309a414953869175f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/TheDragonCode/contracts/zipball/13d1254801026be5ba33cf1309a414953869175f",
+                "reference": "13d1254801026be5ba33cf1309a414953869175f",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-message": "^1.0.1 || ^2.0",
+                "symfony/http-kernel": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0",
+                "symfony/polyfill-php80": "^1.23"
+            },
+            "conflict": {
+                "andrey-helldar/contracts": "*"
+            },
+            "require-dev": {
+                "illuminate/database": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "phpdocumentor/reflection-docblock": "^5.0 || ^6.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "DragonCode\\Contracts\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andrey Helldar",
+                    "email": "helldar@dragon-code.pro",
+                    "homepage": "https://dragon-code.pro"
+                }
+            ],
+            "description": "A set of contracts for any project",
+            "keywords": [
+                "contracts",
+                "interfaces"
+            ],
+            "support": {
+                "source": "https://github.com/TheDragonCode/contracts"
+            },
+            "funding": [
+                {
+                    "url": "https://boosty.to/dragon-code",
+                    "type": "boosty"
+                },
+                {
+                    "url": "https://yoomoney.ru/to/410012608840929",
+                    "type": "yoomoney"
+                }
+            ],
+            "abandoned": true,
+            "time": "2026-03-17T21:50:20+00:00"
+        },
+        {
+            "name": "dragon-code/pretty-array",
+            "version": "4.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/TheDragonCode/pretty-array.git",
+                "reference": "b94034d92172a5d14a578822d68b2a8f8b5388e0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/TheDragonCode/pretty-array/zipball/b94034d92172a5d14a578822d68b2a8f8b5388e0",
+                "reference": "b94034d92172a5d14a578822d68b2a8f8b5388e0",
+                "shasum": ""
+            },
+            "require": {
+                "dragon-code/contracts": "^2.20",
+                "dragon-code/support": "^6.11.2",
+                "ext-dom": "*",
+                "ext-mbstring": "*",
+                "php": "^8.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.6 || ^10.0 || ^11.0 || ^12.0"
+            },
+            "suggest": {
+                "symfony/thanks": "Give thanks (in the form of a GitHub) to your fellow PHP package maintainers"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "DragonCode\\PrettyArray\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andrey Helldar",
+                    "email": "helldar@dragon-code.pro",
+                    "homepage": "https://dragon-code.pro"
+                }
+            ],
+            "description": "Simple conversion of an array to a pretty view",
+            "keywords": [
+                "andrey helldar",
+                "array",
+                "dragon",
+                "dragon code",
+                "pretty",
+                "pretty array"
+            ],
+            "support": {
+                "issues": "https://github.com/TheDragonCode/pretty-array/issues",
+                "source": "https://github.com/TheDragonCode/pretty-array"
+            },
+            "funding": [
+                {
+                    "url": "https://boosty.to/dragon-code",
+                    "type": "boosty"
+                },
+                {
+                    "url": "https://yoomoney.ru/to/410012608840929",
+                    "type": "yoomoney"
+                }
+            ],
+            "abandoned": "symfony/var-exporter",
+            "time": "2025-02-24T15:35:24+00:00"
+        },
+        {
+            "name": "dragon-code/support",
+            "version": "6.17.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/TheDragonCode/support.git",
+                "reference": "82a465953267989883d64b921e9725600a5073b5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/TheDragonCode/support/zipball/82a465953267989883d64b921e9725600a5073b5",
+                "reference": "82a465953267989883d64b921e9725600a5073b5",
+                "shasum": ""
+            },
+            "require": {
+                "dragon-code/contracts": "^2.22.0",
+                "ext-bcmath": "*",
+                "ext-ctype": "*",
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "php": "^8.1",
+                "psr/http-message": "^1.0.1 || ^2.0",
+                "voku/portable-ascii": "^1.4.8 || ^2.0.1"
+            },
+            "conflict": {
+                "andrey-helldar/support": "*"
+            },
+            "require-dev": {
+                "illuminate/contracts": "^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "phpunit/phpunit": "^9.6 || ^11.0 || ^12.0",
+                "symfony/var-dumper": "^6.0 || ^7.0"
+            },
+            "suggest": {
+                "dragon-code/laravel-support": "Various helper files for the Laravel and Lumen frameworks",
+                "symfony/thanks": "Give thanks (in the form of a GitHub) to your fellow PHP package maintainers"
+            },
+            "type": "library",
+            "extra": {
+                "dragon-code": {
+                    "docs-generator": {
+                        "preview": {
+                            "brand": "php",
+                            "vendor": "The Dragon Code"
+                        }
+                    }
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "DragonCode\\Support\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andrey Helldar",
+                    "email": "helldar@dragon-code.pro",
+                    "homepage": "https://dragon-code.pro"
+                }
+            ],
+            "description": "Support package is a collection of helpers and tools for any project.",
+            "keywords": [
+                "dragon",
+                "dragon-code",
+                "framework",
+                "helper",
+                "helpers",
+                "laravel",
+                "php",
+                "support",
+                "symfony",
+                "yii",
+                "yii2"
+            ],
+            "support": {
+                "issues": "https://github.com/TheDragonCode/support/issues",
+                "source": "https://github.com/TheDragonCode/support"
+            },
+            "funding": [
+                {
+                    "url": "https://boosty.to/dragon-code",
+                    "type": "boosty"
+                },
+                {
+                    "url": "https://yoomoney.ru/to/410012608840929",
+                    "type": "yoomoney"
+                }
+            ],
+            "abandoned": "illuminate/support",
+            "time": "2026-04-03T15:06:29+00:00"
+        },
+        {
+            "name": "dragonmantank/cron-expression",
+            "version": "v3.6.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dragonmantank/cron-expression.git",
+                "reference": "4189372ffeff09bf14f731f67a9de90c7b413ebe"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/4189372ffeff09bf14f731f67a9de90c7b413ebe",
+                "reference": "4189372ffeff09bf14f731f67a9de90c7b413ebe",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.2|^8.3|^8.4|^8.5"
+            },
+            "replace": {
+                "mtdowling/cron-expression": "^1.0"
+            },
+            "require-dev": {
+                "phpstan/extension-installer": "^1.4.3",
+                "phpstan/phpstan": "^2.1.31",
+                "phpunit/phpunit": "^11.5.43 || ^12.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Cron\\": "src/Cron/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Chris Tankersley",
+                    "email": "chris@ctankersley.com",
+                    "homepage": "https://github.com/dragonmantank"
+                }
+            ],
+            "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
+            "keywords": [
+                "cron",
+                "schedule"
+            ],
+            "support": {
+                "issues": "https://github.com/dragonmantank/cron-expression/issues",
+                "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/dragonmantank",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-10-19T17:53:57+00:00"
+        },
+        {
+            "name": "egulias/email-validator",
+            "version": "4.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/egulias/EmailValidator.git",
+                "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
+                "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/lexer": "^2.0 || ^3.0",
+                "php": ">=8.1",
+                "symfony/polyfill-intl-idn": "^1.26"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^10.2",
+                "vimeo/psalm": "^5.12"
+            },
+            "suggest": {
+                "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Egulias\\EmailValidator\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Eduardo Gulias Davis"
+                }
+            ],
+            "description": "A library for validating emails against several RFCs",
+            "homepage": "https://github.com/egulias/EmailValidator",
+            "keywords": [
+                "email",
+                "emailvalidation",
+                "emailvalidator",
+                "validation",
+                "validator"
+            ],
+            "support": {
+                "issues": "https://github.com/egulias/EmailValidator/issues",
+                "source": "https://github.com/egulias/EmailValidator/tree/4.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/egulias",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-03-06T22:45:56+00:00"
+        },
+        {
+            "name": "endroid/qr-code",
+            "version": "5.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/endroid/qr-code.git",
+                "reference": "0cc00f0626b73bc71a1ea17af01387d0ac75e046"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/endroid/qr-code/zipball/0cc00f0626b73bc71a1ea17af01387d0ac75e046",
+                "reference": "0cc00f0626b73bc71a1ea17af01387d0ac75e046",
+                "shasum": ""
+            },
+            "require": {
+                "bacon/bacon-qr-code": "^2.0.5",
+                "php": "^8.1"
+            },
+            "conflict": {
+                "khanamiryan/qrcode-detector-decoder": "^1.0.6"
+            },
+            "require-dev": {
+                "endroid/quality": "dev-main",
+                "ext-gd": "*",
+                "khanamiryan/qrcode-detector-decoder": "^1.0.4||^2.0.2",
+                "setasign/fpdf": "^1.8.2"
+            },
+            "suggest": {
+                "ext-gd": "Enables you to write PNG images",
+                "khanamiryan/qrcode-detector-decoder": "Enables you to use the image validator",
+                "roave/security-advisories": "Makes sure package versions with known security issues are not installed",
+                "setasign/fpdf": "Enables you to use the PDF writer"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "5.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Endroid\\QrCode\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jeroen van den Enden",
+                    "email": "info@endroid.nl"
+                }
+            ],
+            "description": "Endroid QR Code",
+            "homepage": "https://github.com/endroid/qr-code",
+            "keywords": [
+                "code",
+                "endroid",
+                "php",
+                "qr",
+                "qrcode"
+            ],
+            "support": {
+                "issues": "https://github.com/endroid/qr-code/issues",
+                "source": "https://github.com/endroid/qr-code/tree/5.0.7"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/endroid",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-08T11:24:40+00:00"
+        },
+        {
+            "name": "evenement/evenement",
+            "version": "v3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/igorw/evenement.git",
+                "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc",
+                "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9 || ^6"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Evenement\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Igor Wiedler",
+                    "email": "igor@wiedler.ch"
+                }
+            ],
+            "description": "Événement is a very simple event dispatching library for PHP",
+            "keywords": [
+                "event-dispatcher",
+                "event-emitter"
+            ],
+            "support": {
+                "issues": "https://github.com/igorw/evenement/issues",
+                "source": "https://github.com/igorw/evenement/tree/v3.0.2"
+            },
+            "time": "2023-08-08T05:53:35+00:00"
+        },
+        {
+            "name": "firebase/php-jwt",
+            "version": "v7.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/googleapis/php-jwt.git",
+                "reference": "0501f5729b9e2448516674aff07c7d1727869b8e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/0501f5729b9e2448516674aff07c7d1727869b8e",
+                "reference": "0501f5729b9e2448516674aff07c7d1727869b8e",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.0"
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "^7.4||^8.0",
+                "phpfastcache/phpfastcache": "^9.2",
+                "phpseclib/phpseclib": "~3.0",
+                "phpspec/prophecy-phpunit": "^2.2",
+                "phpunit/phpunit": "^9.5",
+                "psr/cache": "^2.0||^3.0",
+                "psr/http-client": "^1.0",
+                "psr/http-factory": "^1.0"
+            },
+            "suggest": {
+                "ext-sodium": "Support EdDSA (Ed25519) signatures",
+                "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present",
+                "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures"
+            },
+            "type": "library",
+            "extra": {
+                "component": {
+                    "id": "jwt",
+                    "path": "Jwt",
+                    "entry": "README.md",
+                    "target": "googleapis/php-jwt.git"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Firebase\\JWT\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Neuman Vong",
+                    "email": "neuman+pear@twilio.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Anant Narayanan",
+                    "email": "anant@php.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+            "homepage": "https://github.com/googleapis/php-jwt",
+            "keywords": [
+                "jwt",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/googleapis/php-jwt/issues",
+                "source": "https://github.com/googleapis/php-jwt/tree/v7.2.1"
+            },
+            "time": "2026-09-28T17:51:46+00:00"
+        },
+        {
+            "name": "fruitcake/php-cors",
+            "version": "v1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/fruitcake/php-cors.git",
+                "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379",
+                "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1",
+                "symfony/http-foundation": "^5.4|^6.4|^7.3|^8"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^2",
+                "phpunit/phpunit": "^9",
+                "squizlabs/php_codesniffer": "^4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Fruitcake\\Cors\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fruitcake",
+                    "homepage": "https://fruitcake.nl"
+                },
+                {
+                    "name": "Barryvdh",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "Cross-origin resource sharing library for the Symfony HttpFoundation",
+            "homepage": "https://github.com/fruitcake/php-cors",
+            "keywords": [
+                "cors",
+                "laravel",
+                "symfony"
+            ],
+            "support": {
+                "issues": "https://github.com/fruitcake/php-cors/issues",
+                "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0"
+            },
+            "funding": [
+                {
+                    "url": "https://fruitcake.nl",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/barryvdh",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-12-03T09:33:47+00:00"
+        },
+        {
+            "name": "genkgo/php-asn1",
+            "version": "v2.9.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/genkgo/php-asn1.git",
+                "reference": "dc535345d0ecc69181c6a1e17e57a625bd01f891"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/genkgo/php-asn1/zipball/dc535345d0ecc69181c6a1e17e57a625bd01f891",
+                "reference": "dc535345d0ecc69181c6a1e17e57a625bd01f891",
+                "shasum": ""
+            },
+            "require": {
+                "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "~2.0",
+                "phpunit/phpunit": "^9.0"
+            },
+            "suggest": {
+                "ext-bcmath": "BCmath is the fallback extension for big integer calculations",
+                "ext-curl": "For loading OID information from the web if they have not bee defined statically",
+                "ext-gmp": "GMP is the preferred extension for big integer calculations",
+                "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "FG\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frederik Bosch",
+                    "email": "f.bosch@genkgo.nl",
+                    "role": "Author"
+                },
+                {
+                    "name": "Friedrich Große",
+                    "email": "friedrich.grosse@gmail.com",
+                    "homepage": "https://github.com/FGrosse",
+                    "role": "Author"
+                },
+                {
+                    "name": "All contributors",
+                    "homepage": "https://github.com/genkgo/php-asn1/contributors"
+                }
+            ],
+            "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.",
+            "homepage": "https://github.com/FGrosse/PHPASN1",
+            "keywords": [
+                "DER",
+                "asn.1",
+                "asn1",
+                "ber",
+                "binary",
+                "decoding",
+                "encoding",
+                "x.509",
+                "x.690",
+                "x509",
+                "x690"
+            ],
+            "support": {
+                "issues": "https://github.com/genkgo/php-asn1/issues",
+                "source": "https://github.com/genkgo/php-asn1/tree/v2.9.0"
+            },
+            "time": "2026-01-06T11:43:05+00:00"
+        },
+        {
+            "name": "graham-campbell/result-type",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/GrahamCampbell/Result-Type.git",
+                "reference": "adccca3324eece92ca35463648c12b9e6293c05b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/adccca3324eece92ca35463648c12b9e6293c05b",
+                "reference": "adccca3324eece92ca35463648c12b9e6293c05b",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.10"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.5.52 || ^9.6.34 || ^10.5.63 || ^11.5.55 || ^12.5.14"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "GrahamCampbell\\ResultType\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "An Implementation Of The Result Type",
+            "keywords": [
+                "Graham Campbell",
+                "GrahamCampbell",
+                "Result Type",
+                "Result-Type",
+                "result"
+            ],
+            "support": {
+                "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
+                "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.2.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-08-24T09:06:52+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "7.16.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "81755d989b3007ca7f88a069857553cd08e7dc2e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/81755d989b3007ca7f88a069857553cd08e7dc2e",
+                "reference": "81755d989b3007ca7f88a069857553cd08e7dc2e",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^2.6",
+                "guzzlehttp/psr7": "^2.13.2",
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-client": "^1.0",
+                "symfony/deprecation-contracts": "^2.5 || ^3.0",
+                "symfony/polyfill-php80": "^1.25"
+            },
+            "provide": {
+                "psr/http-client-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-curl": "*",
+                "guzzle/client-integration-tests": "3.0.3",
+                "guzzlehttp/test-server": "^0.7",
+                "php-http/message-factory": "^1.1",
+                "phpunit/phpunit": "^8.5.52 || ^9.6.34",
+                "psr/log": "^1.1 || ^2.0 || ^3.0"
+            },
+            "suggest": {
+                "ext-curl": "Required for CURL handler support",
+                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "psr-18",
+                "psr-7",
+                "rest",
+                "web service"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/guzzle/issues",
+                "source": "https://github.com/guzzle/guzzle/tree/7.16.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-25T21:35:21+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "2.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "3d88c829bd5f13d13847b39067dbf09a60af2154"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/3d88c829bd5f13d13847b39067dbf09a60af2154",
+                "reference": "3d88c829bd5f13d13847b39067dbf09a60af2154",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "symfony/deprecation-contracts": "^2.5 || ^3.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.52 || ^9.6.34"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/promises/issues",
+                "source": "https://github.com/guzzle/promises/tree/2.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-18T12:46:59+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "2.13.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "adee536f95101929f262b8c59e8a61472f347d58"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/adee536f95101929f262b8c59e8a61472f347d58",
+                "reference": "adee536f95101929f262b8c59e8a61472f347d58",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.1 || ^2.0",
+                "ralouphie/getallheaders": "^3.0",
+                "symfony/deprecation-contracts": "^2.5 || ^3.0",
+                "symfony/polyfill-php80": "^1.25"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "1.0",
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "http-interop/http-factory-tests": "1.1.0",
+                "jshttp/mime-db": "1.54.0.1",
+                "phpunit/phpunit": "^8.5.52 || ^9.6.34"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://sagikazarmark.hu"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/psr7/issues",
+                "source": "https://github.com/guzzle/psr7/tree/2.13.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-17T23:49:46+00:00"
+        },
+        {
+            "name": "guzzlehttp/uri-template",
+            "version": "v2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/uri-template.git",
+                "reference": "7a466ad606491eb6528c717482f7cca77f1851f3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/uri-template/zipball/7a466ad606491eb6528c717482f7cca77f1851f3",
+                "reference": "7a466ad606491eb6528c717482f7cca77f1851f3",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.4 || ^8.0",
+                "symfony/polyfill-php80": "^1.25"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^9.6.34",
+                "uri-template/tests": "1.0.2"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\UriTemplate\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                }
+            ],
+            "description": "A polyfill class for uri_template of PHP",
+            "keywords": [
+                "guzzlehttp",
+                "uri-template"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/uri-template/issues",
+                "source": "https://github.com/guzzle/uri-template/tree/v2.0.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-08-24T17:13:02+00:00"
+        },
+        {
+            "name": "irazasyed/telegram-bot-sdk",
+            "version": "v3.16.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/irazasyed/telegram-bot-sdk.git",
+                "reference": "b0e7845ec5fb3ddf2088c489a80dca7b59d5df7f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/irazasyed/telegram-bot-sdk/zipball/b0e7845ec5fb3ddf2088c489a80dca7b59d5df7f",
+                "reference": "b0e7845ec5fb3ddf2088c489a80dca7b59d5df7f",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/guzzle": "^7.5.1",
+                "guzzlehttp/psr7": "^2.5",
+                "illuminate/support": "9 - 13",
+                "league/event": "^2.2 || ^3.0",
+                "php": ">=8.0",
+                "psr/container": "^1.1 || ^2.0",
+                "psr/event-dispatcher": "^1.0"
+            },
+            "require-dev": {
+                "irazasyed/docgen": "^0.2",
+                "pestphp/pest": "^1.22 || ^2.0 || ^3.0",
+                "php-parallel-lint/php-parallel-lint": "^1.3",
+                "phpspec/prophecy": "^1.17",
+                "phpspec/prophecy-phpunit": "^2.0",
+                "rector/rector": "^0.16.0 || ^0.17.0 || ^0.18.0 || ^0.19.0 || ^1.0.0 || ^2.0.0"
+            },
+            "suggest": {
+                "illuminate/container": "Hold dependencies to be injected in commands constructors",
+                "irazasyed/larasupport": "Allows you to use any Laravel Package in Lumen by adding support!"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "aliases": {
+                        "Telegram": "Telegram\\Bot\\Laravel\\Facades\\Telegram"
+                    },
+                    "providers": [
+                        "Telegram\\Bot\\Laravel\\TelegramServiceProvider"
+                    ]
+                },
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Telegram\\Bot\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Irfaq Syed",
+                    "email": "github@lukonet.net",
+                    "homepage": "https://github.com/irazasyed"
+                }
+            ],
+            "description": "The Unofficial Telegram Bot API PHP SDK",
+            "homepage": "https://github.com/irazasyed/telegram-bot-sdk",
+            "keywords": [
+                "laravel",
+                "laravel telegram",
+                "telegram",
+                "telegram bot",
+                "telegram bot api",
+                "telegram php",
+                "telegram sdk"
+            ],
+            "support": {
+                "issues": "https://github.com/irazasyed/telegram-bot-sdk/issues",
+                "source": "https://github.com/irazasyed/telegram-bot-sdk/tree/v3.16.0"
+            },
+            "time": "2026-03-07T09:57:43+00:00"
+        },
+        {
+            "name": "kornrunner/keccak",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/kornrunner/php-keccak.git",
+                "reference": "433749d28e117fb97baf9f2631b92b5d9ab3c890"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/kornrunner/php-keccak/zipball/433749d28e117fb97baf9f2631b92b5d9ab3c890",
+                "reference": "433749d28e117fb97baf9f2631b92b5d9ab3c890",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3",
+                "symfony/polyfill-mbstring": "^1.8"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "kornrunner\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Boris Momcilovic",
+                    "homepage": "https://github.com/kornrunner/php-keccak"
+                }
+            ],
+            "description": "Pure PHP implementation of Keccak",
+            "keywords": [
+                "keccak",
+                "sha-3",
+                "sha3-256"
+            ],
+            "support": {
+                "issues": "https://github.com/kornrunner/php-keccak/issues",
+                "source": "https://github.com/kornrunner/php-keccak/tree/1.1.0"
+            },
+            "time": "2020-12-07T15:40:44+00:00"
+        },
+        {
+            "name": "kornrunner/secp256k1",
+            "version": "0.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/kornrunner/php-secp256k1.git",
+                "reference": "0cfc81c5a1aae3f654ad34ee9d77f0bb9bdde2ef"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/kornrunner/php-secp256k1/zipball/0cfc81c5a1aae3f654ad34ee9d77f0bb9bdde2ef",
+                "reference": "0cfc81c5a1aae3f654ad34ee9d77f0bb9bdde2ef",
+                "shasum": ""
+            },
+            "require": {
+                "paragonie/ecc": "^2",
+                "php": ">=8.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "kornrunner\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Boris Momčilović",
+                    "email": "boris.momcilovic@gmail.com"
+                }
+            ],
+            "description": "Pure PHP secp256k1",
+            "keywords": [
+                "curve",
+                "ecc",
+                "elliptic",
+                "secp256k1"
+            ],
+            "support": {
+                "issues": "https://github.com/kornrunner/php-secp256k1/issues",
+                "source": "https://github.com/kornrunner/php-secp256k1/tree/0.3.0"
+            },
+            "time": "2024-04-25T09:22:43+00:00"
+        },
+        {
+            "name": "laravel-lang/config",
+            "version": "1.17.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Laravel-Lang/config.git",
+                "reference": "7970b89045998b73aab4ffd99a04c6f7f5b3f4f8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Laravel-Lang/config/zipball/7970b89045998b73aab4ffd99a04c6f7f5b3f4f8",
+                "reference": "7970b89045998b73aab4ffd99a04c6f7f5b3f4f8",
+                "shasum": ""
+            },
+            "require": {
+                "archtechx/enums": "^1.0",
+                "illuminate/config": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "laravel-lang/locale-list": "^1.6",
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "orchestra/testbench": "^8.23 || ^9.1 || ^10.0 || ^11.0",
+                "pestphp/pest": "^2.34 || ^3.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "LaravelLang\\Config\\ServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "LaravelLang\\Config\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andrey Helldar",
+                    "email": "helldar@dragon-code.pro",
+                    "homepage": "https://dragon-code.pro"
+                },
+                {
+                    "name": "Laravel-Lang Team",
+                    "homepage": "https://laravel-lang.com"
+                }
+            ],
+            "description": "The Laravel-Lang config package",
+            "keywords": [
+                "Laravel-lang",
+                "Settings",
+                "config",
+                "lang",
+                "languages",
+                "laravel",
+                "locale",
+                "locales",
+                "localization",
+                "localizations",
+                "translation",
+                "translations"
+            ],
+            "support": {
+                "issues": "https://github.com/Laravel-Lang/config/issues",
+                "source": "https://github.com/Laravel-Lang/config/tree/1.17.1"
+            },
+            "time": "2026-09-20T09:41:38+00:00"
+        },
+        {
+            "name": "laravel-lang/lang",
+            "version": "15.37.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Laravel-Lang/lang.git",
+                "reference": "a3a9ef9b039d10db442bfb3bed0a97c9b50f30a7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Laravel-Lang/lang/zipball/a3a9ef9b039d10db442bfb3bed0a97c9b50f30a7",
+                "reference": "a3a9ef9b039d10db442bfb3bed0a97c9b50f30a7",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "laravel-lang/publisher": "^16.0",
+                "php": "^8.2"
+            },
+            "conflict": {
+                "laravel/framework": "<11.0.7"
+            },
+            "require-dev": {
+                "dragon-code/codestyler": "^6.0",
+                "laravel-lang/status-generator": "^2.11",
+                "phpunit/phpunit": "^11.0 || ^12.0",
+                "symfony/var-dumper": "^7.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "LaravelLang\\Lang\\ServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "LaravelLang\\Lang\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Laravel-Lang Team",
+                    "homepage": "https://github.com/Laravel-Lang"
+                }
+            ],
+            "description": "List of 126 languages for Laravel Framework, Laravel Jetstream, Laravel Fortify, Laravel Breeze, Laravel Cashier, Laravel Nova, Laravel Spark and Laravel UI",
+            "keywords": [
+                "lang",
+                "languages",
+                "laravel",
+                "lpm"
+            ],
+            "support": {
+                "issues": "https://github.com/Laravel-Lang/lang/issues",
+                "source": "https://github.com/Laravel-Lang/lang"
+            },
+            "funding": [
+                {
+                    "url": "https://boosty.to/dragon-code",
+                    "type": "boosty"
+                },
+                {
+                    "url": "https://yoomoney.ru/to/410012608840929",
+                    "type": "yoomoney"
+                }
+            ],
+            "time": "2026-10-09T09:55:05+00:00"
+        },
+        {
+            "name": "laravel-lang/locale-list",
+            "version": "1.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Laravel-Lang/locale-list.git",
+                "reference": "48e61c7f0a957420d4aaf5d35653889c25c4e2d4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Laravel-Lang/locale-list/zipball/48e61c7f0a957420d4aaf5d35653889c25c4e2d4",
+                "reference": "48e61c7f0a957420d4aaf5d35653889c25c4e2d4",
+                "shasum": ""
+            },
+            "require": {
+                "archtechx/enums": "^0.3.2 || ^1.0",
+                "php": "^8.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "LaravelLang\\LocaleList\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andrey Helldar",
+                    "email": "helldar@dragon-code.pro",
+                    "homepage": "https://dragon-code.pro"
+                },
+                {
+                    "name": "Laravel-Lang Team",
+                    "homepage": "https://laravel-lang.com"
+                }
+            ],
+            "description": "List of localizations available in Laravel Lang projects",
+            "keywords": [
+                "Laravel-lang",
+                "lang",
+                "languages",
+                "laravel",
+                "locale",
+                "locales",
+                "localization",
+                "translation",
+                "translations"
+            ],
+            "support": {
+                "issues": "https://github.com/Laravel-Lang/locale-list/issues",
+                "source": "https://github.com/Laravel-Lang/locale-list"
+            },
+            "funding": [
+                {
+                    "url": "https://boosty.to/dragon-code",
+                    "type": "boosty"
+                },
+                {
+                    "url": "https://yoomoney.ru/to/410012608840929",
+                    "type": "yoomoney"
+                }
+            ],
+            "time": "2026-01-20T08:17:15+00:00"
+        },
+        {
+            "name": "laravel-lang/locales",
+            "version": "2.11.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Laravel-Lang/locales.git",
+                "reference": "761aa3cfbc5bbe29eb958c9839e7dd3806193bac"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Laravel-Lang/locales/zipball/761aa3cfbc5bbe29eb958c9839e7dd3806193bac",
+                "reference": "761aa3cfbc5bbe29eb958c9839e7dd3806193bac",
+                "shasum": ""
+            },
+            "require": {
+                "archtechx/enums": "^0.3.2 || ^1.0",
+                "dragon-code/support": "^6.11.3",
+                "ext-json": "*",
+                "illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "laravel-lang/config": "^1.12",
+                "laravel-lang/locale-list": "^1.5",
+                "laravel-lang/native-country-names": "^1.5",
+                "laravel-lang/native-currency-names": "^1.6",
+                "laravel-lang/native-locale-names": "^2.5",
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "orchestra/testbench": "^8.0 || ^9.0 || ^10.0 || ^11.0",
+                "pestphp/pest": "^2.24.1 || ^3.0 || ^4.0",
+                "symfony/var-dumper": "^6.0 || ^7.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "LaravelLang\\Locales\\ServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "LaravelLang\\Locales\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andrey Helldar",
+                    "email": "helldar@dragon-code.pro"
+                },
+                {
+                    "name": "Laravel-Lang Team",
+                    "homepage": "https://laravel-lang.com"
+                }
+            ],
+            "description": "Basic functionality for working with localizations",
+            "keywords": [
+                "laravel",
+                "locale",
+                "locales",
+                "localization",
+                "translation",
+                "translations"
+            ],
+            "support": {
+                "issues": "https://github.com/Laravel-Lang/locales/issues",
+                "source": "https://github.com/Laravel-Lang/locales"
+            },
+            "time": "2026-03-17T22:40:41+00:00"
+        },
+        {
+            "name": "laravel-lang/native-country-names",
+            "version": "1.8.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Laravel-Lang/native-country-names.git",
+                "reference": "1d293138e34eb9e914bc4568cdebac2cb0a2eb0e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Laravel-Lang/native-country-names/zipball/1d293138e34eb9e914bc4568cdebac2cb0a2eb0e",
+                "reference": "1d293138e34eb9e914bc4568cdebac2cb0a2eb0e",
+                "shasum": ""
+            },
+            "require": {
+                "dragon-code/support": "^6.11",
+                "ext-json": "*",
+                "illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "laravel-lang/locale-list": "^1.5",
+                "pestphp/pest": "^2.0 || ^3.0 || ^4.0",
+                "punic/punic": "^3.8",
+                "symfony/console": "^6.0 || ^7.0 || ^8.0",
+                "symfony/process": "^6.0 || ^7.0 || ^8.0",
+                "symfony/var-dumper": "^6.0 || ^7.0 || ^8.0",
+                "vlucas/phpdotenv": "^5.6"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "LaravelLang\\NativeCountryNames\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andrey Helldar",
+                    "email": "helldar@dragon-code.pro"
+                },
+                {
+                    "name": "Laravel-Lang Team",
+                    "homepage": "https://laravel-lang.com"
+                }
+            ],
+            "description": "The project contains native translations of country names",
+            "keywords": [
+                "Laravel-lang",
+                "countries",
+                "country",
+                "lang",
+                "languages",
+                "laravel",
+                "locale",
+                "locales",
+                "localization",
+                "territories",
+                "territory",
+                "translation",
+                "translations"
+            ],
+            "support": {
+                "issues": "https://github.com/Laravel-Lang/native-country-names/issues",
+                "source": "https://github.com/Laravel-Lang/native-country-names"
+            },
+            "funding": [
+                {
+                    "url": "https://boosty.to/dragon-code",
+                    "type": "boosty"
+                },
+                {
+                    "url": "https://yoomoney.ru/to/410012608840929",
+                    "type": "yoomoney"
+                }
+            ],
+            "time": "2026-03-17T22:01:15+00:00"
+        },
+        {
+            "name": "laravel-lang/native-currency-names",
+            "version": "1.10.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Laravel-Lang/native-currency-names.git",
+                "reference": "7ebe95a9942bebf6afd61986fa18c82dade3d583"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Laravel-Lang/native-currency-names/zipball/7ebe95a9942bebf6afd61986fa18c82dade3d583",
+                "reference": "7ebe95a9942bebf6afd61986fa18c82dade3d583",
+                "shasum": ""
+            },
+            "require": {
+                "dragon-code/support": "^6.11",
+                "ext-json": "*",
+                "illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "laravel-lang/locale-list": "^1.2",
+                "pestphp/pest": "^2.0 || ^3.0 || ^4.0",
+                "punic/punic": "^3.8",
+                "symfony/console": "^6.0 || ^7.0",
+                "symfony/process": "^6.0 || ^7.0",
+                "symfony/var-dumper": "^6.0 || ^7.0",
+                "vlucas/phpdotenv": "^5.6"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "LaravelLang\\NativeCurrencyNames\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andrey Helldar",
+                    "email": "helldar@dragon-code.pro"
+                },
+                {
+                    "name": "Laravel-Lang Team",
+                    "homepage": "https://laravel-lang.com"
+                }
+            ],
+            "description": "The project contains native translations of currency names",
+            "keywords": [
+                "Laravel-lang",
+                "currency",
+                "lang",
+                "languages",
+                "laravel",
+                "locale",
+                "locales",
+                "localization",
+                "translation",
+                "translations"
+            ],
+            "support": {
+                "issues": "https://github.com/Laravel-Lang/native-currency-names/issues",
+                "source": "https://github.com/Laravel-Lang/native-currency-names"
+            },
+            "funding": [
+                {
+                    "url": "https://boosty.to/dragon-code",
+                    "type": "boosty"
+                },
+                {
+                    "url": "https://yoomoney.ru/to/410012608840929",
+                    "type": "yoomoney"
+                }
+            ],
+            "time": "2026-06-20T16:50:37+00:00"
+        },
+        {
+            "name": "laravel-lang/native-locale-names",
+            "version": "2.9.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Laravel-Lang/native-locale-names.git",
+                "reference": "e5925182bad34654203a5d93544357ca00a6d9c0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Laravel-Lang/native-locale-names/zipball/e5925182bad34654203a5d93544357ca00a6d9c0",
+                "reference": "e5925182bad34654203a5d93544357ca00a6d9c0",
+                "shasum": ""
+            },
+            "require": {
+                "dragon-code/support": "^6.11",
+                "ext-json": "*",
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "laravel-lang/locale-list": "^1.2",
+                "pestphp/pest": "^2.24.3 || ^3.0 || ^4.0",
+                "punic/punic": "^3.8",
+                "symfony/console": "^6.0 || ^7.0",
+                "symfony/process": "^6.0 || ^7.0",
+                "symfony/var-dumper": "^6.0 || ^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "LaravelLang\\NativeLocaleNames\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andrey Helldar",
+                    "email": "helldar@dragon-code.pro"
+                },
+                {
+                    "name": "Laravel-Lang Team",
+                    "homepage": "https://laravel-lang.com"
+                }
+            ],
+            "description": "The project contains native translations of locale names",
+            "keywords": [
+                "Laravel-lang",
+                "lang",
+                "languages",
+                "laravel",
+                "locale",
+                "locales",
+                "localization",
+                "translation",
+                "translations"
+            ],
+            "support": {
+                "issues": "https://github.com/Laravel-Lang/native-locale-names/issues",
+                "source": "https://github.com/Laravel-Lang/native-locale-names"
+            },
+            "funding": [
+                {
+                    "url": "https://boosty.to/dragon-code",
+                    "type": "boosty"
+                },
+                {
+                    "url": "https://yoomoney.ru/to/410012608840929",
+                    "type": "yoomoney"
+                }
+            ],
+            "time": "2026-06-20T16:52:11+00:00"
+        },
+        {
+            "name": "laravel-lang/publisher",
+            "version": "16.8.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Laravel-Lang/publisher.git",
+                "reference": "e5d3383f5385c2102f8a0d3dbe488ed86cd0250f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Laravel-Lang/publisher/zipball/e5d3383f5385c2102f8a0d3dbe488ed86cd0250f",
+                "reference": "e5d3383f5385c2102f8a0d3dbe488ed86cd0250f",
+                "shasum": ""
+            },
+            "require": {
+                "composer/semver": "^3.4",
+                "dragon-code/pretty-array": "^4.1",
+                "dragon-code/support": "^6.11.3",
+                "ext-json": "*",
+                "illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "illuminate/console": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0",
+                "laravel-lang/config": "^1.12",
+                "laravel-lang/locales": "^2.10",
+                "league/commonmark": "^2.4.1",
+                "league/config": "^1.2",
+                "php": "^8.1"
+            },
+            "conflict": {
+                "laravel-lang/attributes": "<2.0",
+                "laravel-lang/http-statuses": "<3.0",
+                "laravel-lang/lang": "<11.0"
+            },
+            "require-dev": {
+                "laravel-lang/json-fallback": "^2.2",
+                "orchestra/testbench": "^8.14 || ^9.0 || ^10.0 || ^11.0",
+                "phpunit/phpunit": "^10.4.2 || ^11.0 || ^12.0",
+                "symfony/var-dumper": "^6.3.6 || ^7.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "LaravelLang\\Publisher\\ServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "LaravelLang\\Publisher\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andrey Helldar",
+                    "email": "helldar@dragon-code.pro"
+                },
+                {
+                    "name": "Laravel-Lang Team",
+                    "homepage": "https://laravel-lang.com"
+                }
+            ],
+            "description": "Localization publisher for your Laravel application",
+            "keywords": [
+                "Laravel-lang",
+                "breeze",
+                "cashier",
+                "fortify",
+                "framework",
+                "i18n",
+                "jetstream",
+                "lang",
+                "languages",
+                "laravel",
+                "locale",
+                "locales",
+                "localization",
+                "localizations",
+                "lpm",
+                "lumen",
+                "nova",
+                "publisher",
+                "spark",
+                "trans",
+                "translation",
+                "translations",
+                "validations"
+            ],
+            "support": {
+                "issues": "https://github.com/Laravel-Lang/publisher/issues",
+                "source": "https://github.com/Laravel-Lang/publisher"
+            },
+            "funding": [
+                {
+                    "url": "https://boosty.to/dragon-code",
+                    "type": "boosty"
+                },
+                {
+                    "url": "https://yoomoney.ru/to/410012608840929",
+                    "type": "yoomoney"
+                }
+            ],
+            "time": "2026-03-17T22:56:20+00:00"
+        },
+        {
+            "name": "laravel/framework",
+            "version": "v13.39.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/framework.git",
+                "reference": "d52c3b3129e4c8e213c97cdc68f94b0b7d27f181"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/framework/zipball/d52c3b3129e4c8e213c97cdc68f94b0b7d27f181",
+                "reference": "d52c3b3129e4c8e213c97cdc68f94b0b7d27f181",
+                "shasum": ""
+            },
+            "require": {
+                "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0",
+                "composer-runtime-api": "^2.2",
+                "doctrine/inflector": "^2.0.5",
+                "dragonmantank/cron-expression": "^3.4",
+                "egulias/email-validator": "^4.0",
+                "ext-ctype": "*",
+                "ext-filter": "*",
+                "ext-hash": "*",
+                "ext-mbstring": "*",
+                "ext-openssl": "*",
+                "ext-session": "*",
+                "ext-tokenizer": "*",
+                "fruitcake/php-cors": "^1.3",
+                "guzzlehttp/guzzle": "^7.8.2 || ^8.0",
+                "guzzlehttp/promises": "^2.0.3 || ^3.0",
+                "guzzlehttp/psr7": "^2.9 || ^3.0",
+                "guzzlehttp/uri-template": "^1.0 || ^2.0",
+                "laravel/prompts": "^0.3.25",
+                "laravel/serializable-closure": "^2.0.10",
+                "league/commonmark": "^2.8.1",
+                "league/flysystem": "^3.25.1",
+                "league/flysystem-local": "^3.25.1",
+                "league/uri": "^7.5.1",
+                "monolog/monolog": "^3.10",
+                "nesbot/carbon": "^3.8.4",
+                "nunomaduro/termwind": "^2.0",
+                "php": "^8.3",
+                "psr/container": "^1.1.1 || ^2.0.1",
+                "psr/http-message": "^1.0 || ^2.0",
+                "psr/log": "^1.0 || ^2.0 || ^3.0",
+                "psr/simple-cache": "^1.0 || ^2.0 || ^3.0",
+                "ramsey/uuid": "^4.7",
+                "symfony/console": "^7.4.0 || ^8.0.0",
+                "symfony/error-handler": "^7.4.0 || ^8.0.0",
+                "symfony/finder": "^7.4.0 || ^8.0.0",
+                "symfony/http-foundation": "^7.4.0 || ^8.0.0",
+                "symfony/http-kernel": "^7.4.0 || ^8.0.0",
+                "symfony/mailer": "^7.4.0 || ^8.0.0",
+                "symfony/mime": "^7.4.0 || ^8.0.0",
+                "symfony/polyfill-php84": "^1.36",
+                "symfony/polyfill-php85": "^1.36",
+                "symfony/polyfill-php86": "^1.36",
+                "symfony/process": "^7.4.5 || ^8.0.5",
+                "symfony/routing": "^7.4.0 || ^8.0.0",
+                "symfony/uid": "^7.4.0 || ^8.0.0",
+                "symfony/var-dumper": "^7.4.0 || ^8.0.0",
+                "tijsverkoyen/css-to-inline-styles": "^2.2.5",
+                "vlucas/phpdotenv": "^5.6.1",
+                "voku/portable-ascii": "^2.0.2"
+            },
+            "conflict": {
+                "tightenco/collect": "<5.5.33"
+            },
+            "provide": {
+                "psr/container-implementation": "1.1 || 2.0",
+                "psr/log-implementation": "1.0 || 2.0 || 3.0",
+                "psr/simple-cache-implementation": "1.0 || 2.0 || 3.0"
+            },
+            "replace": {
+                "illuminate/auth": "self.version",
+                "illuminate/broadcasting": "self.version",
+                "illuminate/bus": "self.version",
+                "illuminate/cache": "self.version",
+                "illuminate/collections": "self.version",
+                "illuminate/concurrency": "self.version",
+                "illuminate/conditionable": "self.version",
+                "illuminate/config": "self.version",
+                "illuminate/console": "self.version",
+                "illuminate/container": "self.version",
+                "illuminate/contracts": "self.version",
+                "illuminate/cookie": "self.version",
+                "illuminate/database": "self.version",
+                "illuminate/encryption": "self.version",
+                "illuminate/events": "self.version",
+                "illuminate/filesystem": "self.version",
+                "illuminate/hashing": "self.version",
+                "illuminate/http": "self.version",
+                "illuminate/image": "self.version",
+                "illuminate/json-schema": "self.version",
+                "illuminate/log": "self.version",
+                "illuminate/macroable": "self.version",
+                "illuminate/mail": "self.version",
+                "illuminate/notifications": "self.version",
+                "illuminate/pagination": "self.version",
+                "illuminate/pipeline": "self.version",
+                "illuminate/process": "self.version",
+                "illuminate/queue": "self.version",
+                "illuminate/redis": "self.version",
+                "illuminate/reflection": "self.version",
+                "illuminate/routing": "self.version",
+                "illuminate/session": "self.version",
+                "illuminate/support": "self.version",
+                "illuminate/testing": "self.version",
+                "illuminate/translation": "self.version",
+                "illuminate/validation": "self.version",
+                "illuminate/view": "self.version",
+                "spatie/once": "*"
+            },
+            "require-dev": {
+                "ably/ably-php": "^1.0",
+                "aws/aws-sdk-php": "^3.322.9",
+                "ext-gmp": "*",
+                "fakerphp/faker": "^1.24",
+                "intervention/image": "^4.2",
+                "laravel/pint": "^1.18",
+                "league/flysystem-aws-s3-v3": "^3.25.1",
+                "league/flysystem-ftp": "^3.25.1",
+                "league/flysystem-path-prefixing": "^3.25.1",
+                "league/flysystem-read-only": "^3.25.1",
+                "league/flysystem-sftp-v3": "^3.25.1",
+                "mockery/mockery": "^1.6.10",
+                "opis/json-schema": "^2.4.1",
+                "orchestra/testbench-core": "^11.6.0",
+                "pda/pheanstalk": "^7.0.0 || ^8.0.0",
+                "php-http/discovery": "^1.15",
+                "phpstan/phpstan": "2.3.1",
+                "phpunit/phpunit": "^11.5.50 || ^12.5.8 || ^13.0.3",
+                "predis/predis": "^2.3 || ^3.0",
+                "rector/rector": "2.6.3",
+                "resend/resend-php": "^1.0",
+                "symfony/cache": "^7.4.0 || ^8.0.0",
+                "symfony/http-client": "^7.4.0 || ^8.0.0",
+                "symfony/mercure": "^0.8.0",
+                "symfony/psr-http-message-bridge": "^7.4.0 || ^8.0.0",
+                "symfony/translation": "^7.4.0 || ^8.0.0",
+                "web-token/jwt-library": "^4.1"
+            },
+            "suggest": {
+                "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
+                "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).",
+                "brianium/paratest": "Required to run tests in parallel (^7.0 || ^8.0).",
+                "ext-apcu": "Required to use the APC cache driver.",
+                "ext-fileinfo": "Required to use the Filesystem class.",
+                "ext-ftp": "Required to use the Flysystem FTP driver.",
+                "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().",
+                "ext-memcached": "Required to use the memcache cache driver.",
+                "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.",
+                "ext-pdo": "Required to use all database features.",
+                "ext-posix": "Required to use all features of the queue worker.",
+                "ext-redis": "Required to use the Redis cache and queue drivers (^4.0 || ^5.0 || ^6.0).",
+                "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).",
+                "filp/whoops": "Required for friendly error pages in development (^2.14.3).",
+                "intervention/image": "Required to use the image processing features (^4.2).",
+                "laravel/tinker": "Required to use the tinker console command (^2.0).",
+                "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).",
+                "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).",
+                "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).",
+                "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)",
+                "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).",
+                "mockery/mockery": "Required to use mocking (^1.6).",
+                "pda/pheanstalk": "Required to use the beanstalk queue driver (^7.0 || ^8.0).",
+                "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).",
+                "phpunit/phpunit": "Required to use assertions and run tests (^11.5.50 || ^12.5.8 || ^13.0.3).",
+                "predis/predis": "Required to use the predis connector (^2.3 || ^3.0).",
+                "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0 || ^7.0).",
+                "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0 || ^1.0).",
+                "spatie/fork": "Required to use the 'fork' concurrency driver (^1.2).",
+                "symfony/cache": "Required to PSR-6 cache bridge (^7.4 || ^8.0).",
+                "symfony/filesystem": "Required to enable support for relative symbolic links (^7.4 || ^8.0).",
+                "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.4 || ^8.0).",
+                "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.4 || ^8.0).",
+                "symfony/mercure": "Required to use the Mercure broadcast driver (^0.8).",
+                "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.4 || ^8.0).",
+                "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.4 || ^8.0).",
+                "web-token/jwt-library": "Required to sign Mercure JWTs and use end-to-end encrypted channels (^4.1)."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "13.0.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/Illuminate/Collections/functions.php",
+                    "src/Illuminate/Collections/helpers.php",
+                    "src/Illuminate/Events/functions.php",
+                    "src/Illuminate/Filesystem/functions.php",
+                    "src/Illuminate/Foundation/helpers.php",
+                    "src/Illuminate/Log/functions.php",
+                    "src/Illuminate/Reflection/helpers.php",
+                    "src/Illuminate/Support/functions.php",
+                    "src/Illuminate/Support/helpers.php"
+                ],
+                "psr-4": {
+                    "Illuminate\\": "src/Illuminate/",
+                    "Illuminate\\Support\\": [
+                        "src/Illuminate/Macroable/",
+                        "src/Illuminate/Collections/",
+                        "src/Illuminate/Conditionable/",
+                        "src/Illuminate/Reflection/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "The Laravel Framework.",
+            "homepage": "https://laravel.com",
+            "keywords": [
+                "framework",
+                "laravel"
+            ],
+            "support": {
+                "issues": "https://github.com/laravel/framework/issues",
+                "source": "https://github.com/laravel/framework"
+            },
+            "time": "2026-10-27T14:04:39+00:00"
+        },
+        {
+            "name": "laravel/prompts",
+            "version": "v0.3.26",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/prompts.git",
+                "reference": "2d1e9bf6bab8d3f383c254db7f2b4ea8816d55e0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/prompts/zipball/2d1e9bf6bab8d3f383c254db7f2b4ea8816d55e0",
+                "reference": "2d1e9bf6bab8d3f383c254db7f2b4ea8816d55e0",
+                "shasum": ""
+            },
+            "require": {
+                "composer-runtime-api": "^2.2",
+                "ext-mbstring": "*",
+                "php": "^8.1",
+                "symfony/console": "^6.2|^7.0|^8.0"
+            },
+            "conflict": {
+                "illuminate/console": ">=10.17.0 <10.25.0",
+                "laravel/framework": ">=10.17.0 <10.25.0"
+            },
+            "require-dev": {
+                "illuminate/collections": "^10.0|^11.0|^12.0|^13.0",
+                "mockery/mockery": "^1.5",
+                "pestphp/pest": "^2.3|^3.4|^4.0",
+                "phpstan/phpstan": "^1.12.28",
+                "phpstan/phpstan-mockery": "^1.1.3"
+            },
+            "suggest": {
+                "ext-pcntl": "Required for the spinner to be animated."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "0.3.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/helpers.php"
+                ],
+                "psr-4": {
+                    "Laravel\\Prompts\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Add beautiful and user-friendly forms to your command-line applications.",
+            "support": {
+                "source": "https://github.com/laravel/prompts/tree/v0.3.26"
+            },
+            "time": "2026-10-09T14:45:13+00:00"
+        },
+        {
+            "name": "laravel/reverb",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/reverb.git",
+                "reference": "e233471f53641a447d3b7a2150446d47a0e8c043"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/reverb/zipball/e233471f53641a447d3b7a2150446d47a0e8c043",
+                "reference": "e233471f53641a447d3b7a2150446d47a0e8c043",
+                "shasum": ""
+            },
+            "require": {
+                "clue/redis-react": "^2.6",
+                "guzzlehttp/psr7": "^2.6",
+                "illuminate/console": "^10.47|^11.0|^12.0|^13.0",
+                "illuminate/contracts": "^10.47|^11.0|^12.0|^13.0",
+                "illuminate/http": "^10.47|^11.0|^12.0|^13.0",
+                "illuminate/support": "^10.47|^11.0|^12.0|^13.0",
+                "laravel/prompts": "^0.1.15|^0.2.0|^0.3.0",
+                "php": "^8.2",
+                "pusher/pusher-php-server": "^7.2",
+                "ratchet/rfc6455": "^0.4",
+                "react/promise-timer": "^1.10",
+                "react/socket": "^1.14",
+                "symfony/console": "^6.0|^7.0|^8.0",
+                "symfony/http-foundation": "^6.3|^7.0|^8.0"
+            },
+            "require-dev": {
+                "jasonmccreary/double": "~0.10",
+                "orchestra/testbench": "^8.36|^9.15|^10.8|^11.0",
+                "pestphp/pest": "^2.0|^3.0|^4.0",
+                "phpstan/phpstan": "^1.10",
+                "ratchet/pawl": "^0.4.1",
+                "react/async": "^4.2",
+                "react/http": "^1.9"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Laravel\\Reverb\\ApplicationManagerServiceProvider",
+                        "Laravel\\Reverb\\ReverbServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Laravel\\Reverb\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                },
+                {
+                    "name": "Joe Dixon",
+                    "email": "joe@laravel.com"
+                }
+            ],
+            "description": "Laravel Reverb provides a real-time WebSocket communication backend for Laravel applications.",
+            "keywords": [
+                "WebSockets",
+                "laravel",
+                "real-time",
+                "websocket"
+            ],
+            "support": {
+                "source": "https://github.com/laravel/reverb/tree/v1.12.0"
+            },
+            "time": "2026-09-22T14:25:47+00:00"
+        },
+        {
+            "name": "laravel/sanctum",
+            "version": "v4.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/sanctum.git",
+                "reference": "fee27a573d1a013af3721d86153a65e0b11927e6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/sanctum/zipball/fee27a573d1a013af3721d86153a65e0b11927e6",
+                "reference": "fee27a573d1a013af3721d86153a65e0b11927e6",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "illuminate/console": "^11.0|^12.0|^13.0",
+                "illuminate/contracts": "^11.0|^12.0|^13.0",
+                "illuminate/database": "^11.0|^12.0|^13.0",
+                "illuminate/support": "^11.0|^12.0|^13.0",
+                "php": "^8.2",
+                "symfony/console": "^7.0|^8.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.6",
+                "orchestra/testbench": "^9.15|^10.8|^11.0",
+                "phpstan/phpstan": "^1.10"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Laravel\\Sanctum\\SanctumServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Laravel\\Sanctum\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
+            "keywords": [
+                "auth",
+                "laravel",
+                "sanctum"
+            ],
+            "support": {
+                "issues": "https://github.com/laravel/sanctum/issues",
+                "source": "https://github.com/laravel/sanctum"
+            },
+            "time": "2026-06-23T18:26:55+00:00"
+        },
+        {
+            "name": "laravel/serializable-closure",
+            "version": "v2.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/serializable-closure.git",
+                "reference": "2d5869a838bbcf37e0d8b0568fc41914e81374b5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/2d5869a838bbcf37e0d8b0568fc41914e81374b5",
+                "reference": "2d5869a838bbcf37e0d8b0568fc41914e81374b5",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "illuminate/support": "^10.0|^11.0|^12.0|^13.0",
+                "nesbot/carbon": "^2.67|^3.0",
+                "pestphp/pest": "^2.36|^3.0|^4.0",
+                "phpstan/phpstan": "^2.0",
+                "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Laravel\\SerializableClosure\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                },
+                {
+                    "name": "Nuno Maduro",
+                    "email": "nuno@laravel.com"
+                }
+            ],
+            "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.",
+            "keywords": [
+                "closure",
+                "laravel",
+                "serializable"
+            ],
+            "support": {
+                "issues": "https://github.com/laravel/serializable-closure/issues",
+                "source": "https://github.com/laravel/serializable-closure"
+            },
+            "time": "2026-09-22T14:32:34+00:00"
+        },
+        {
+            "name": "laravel/tinker",
+            "version": "v3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/tinker.git",
+                "reference": "4faba77764bd33411735936acdf30446d058c78b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/tinker/zipball/4faba77764bd33411735936acdf30446d058c78b",
+                "reference": "4faba77764bd33411735936acdf30446d058c78b",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/console": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
+                "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
+                "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
+                "php": "^8.1",
+                "psy/psysh": "^0.12.0",
+                "symfony/var-dumper": "^5.4|^6.0|^7.0|^8.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "~1.3.3|^1.4.2",
+                "phpstan/phpstan": "^1.10",
+                "phpunit/phpunit": "^10.5|^11.5"
+            },
+            "suggest": {
+                "illuminate/database": "The Illuminate Database package (^8.0|^9.0|^10.0|^11.0|^12.0|^13.0)."
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Laravel\\Tinker\\TinkerServiceProvider"
+                    ]
+                },
+                "branch-alias": {
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Laravel\\Tinker\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "Powerful REPL for the Laravel framework.",
+            "keywords": [
+                "REPL",
+                "Tinker",
+                "laravel",
+                "psysh"
+            ],
+            "support": {
+                "issues": "https://github.com/laravel/tinker/issues",
+                "source": "https://github.com/laravel/tinker/tree/v3.0.2"
+            },
+            "time": "2026-03-17T14:54:13+00:00"
+        },
+        {
+            "name": "league/commonmark",
+            "version": "2.10.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/commonmark.git",
+                "reference": "8eb53e906fd60c67038d22f1f1cab420ed58b8be"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/8eb53e906fd60c67038d22f1f1cab420ed58b8be",
+                "reference": "8eb53e906fd60c67038d22f1f1cab420ed58b8be",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "league/config": "^1.1.1",
+                "php": "^7.4 || ^8.0",
+                "psr/event-dispatcher": "^1.0",
+                "symfony/deprecation-contracts": "^2.1 || ^3.0",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "require-dev": {
+                "cebe/markdown": "^1.0",
+                "commonmark/cmark": "0.31.1",
+                "commonmark/commonmark.js": "0.31.1",
+                "composer/package-versions-deprecated": "^1.8",
+                "embed/embed": "^4.4",
+                "erusev/parsedown": "^1.0",
+                "ext-json": "*",
+                "github/gfm": "0.29.0",
+                "michelf/php-markdown": "^1.4 || ^2.0",
+                "nyholm/psr7": "^1.5",
+                "phpstan/phpstan": "^2.0.0",
+                "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0",
+                "scrutinizer/ocular": "^1.8.1",
+                "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0",
+                "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0",
+                "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0",
+                "unleashedtech/php-coding-standard": "^3.1.1",
+                "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
+            },
+            "suggest": {
+                "symfony/yaml": "v2.3+ required if using the Front Matter extension"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.11-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\CommonMark\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Colin O'Dell",
+                    "email": "colinodell@gmail.com",
+                    "homepage": "https://www.colinodell.com",
+                    "role": "Lead Developer"
+                }
+            ],
+            "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
+            "homepage": "https://commonmark.thephpleague.com",
+            "keywords": [
+                "commonmark",
+                "flavored",
+                "gfm",
+                "github",
+                "github-flavored",
+                "markdown",
+                "md",
+                "parser"
+            ],
+            "support": {
+                "docs": "https://commonmark.thephpleague.com/",
+                "forum": "https://github.com/thephpleague/commonmark/discussions",
+                "issues": "https://github.com/thephpleague/commonmark/issues",
+                "rss": "https://github.com/thephpleague/commonmark/releases.atom",
+                "source": "https://github.com/thephpleague/commonmark"
+            },
+            "funding": [
+                {
+                    "url": "https://www.colinodell.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.paypal.me/colinpodell/10.00",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/colinodell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/league/commonmark",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-31T13:35:37+00:00"
+        },
+        {
+            "name": "league/config",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/config.git",
+                "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
+                "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
+                "shasum": ""
+            },
+            "require": {
+                "dflydev/dot-access-data": "^3.0.1",
+                "nette/schema": "^1.2",
+                "php": "^7.4 || ^8.0"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^1.8.2",
+                "phpunit/phpunit": "^9.5.5",
+                "scrutinizer/ocular": "^1.8.1",
+                "unleashedtech/php-coding-standard": "^3.1",
+                "vimeo/psalm": "^4.7.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Config\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Colin O'Dell",
+                    "email": "colinodell@gmail.com",
+                    "homepage": "https://www.colinodell.com",
+                    "role": "Lead Developer"
+                }
+            ],
+            "description": "Define configuration arrays with strict schemas and access values with dot notation",
+            "homepage": "https://config.thephpleague.com",
+            "keywords": [
+                "array",
+                "config",
+                "configuration",
+                "dot",
+                "dot-access",
+                "nested",
+                "schema"
+            ],
+            "support": {
+                "docs": "https://config.thephpleague.com/",
+                "issues": "https://github.com/thephpleague/config/issues",
+                "rss": "https://github.com/thephpleague/config/releases.atom",
+                "source": "https://github.com/thephpleague/config"
+            },
+            "funding": [
+                {
+                    "url": "https://www.colinodell.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.paypal.me/colinpodell/10.00",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/colinodell",
+                    "type": "github"
+                }
+            ],
+            "time": "2022-12-11T20:36:23+00:00"
+        },
+        {
+            "name": "league/event",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/event.git",
+                "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/event/zipball/ec38ff7ea10cad7d99a79ac937fbcffb9334c210",
+                "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.0",
+                "psr/event-dispatcher": "^1.0"
+            },
+            "provide": {
+                "psr/event-dispatcher-implementation": "1.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.16",
+                "phpstan/phpstan": "^0.12.45",
+                "phpunit/phpunit": "^8.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Event\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frenky.net"
+                }
+            ],
+            "description": "Event package",
+            "keywords": [
+                "emitter",
+                "event",
+                "listener"
+            ],
+            "support": {
+                "issues": "https://github.com/thephpleague/event/issues",
+                "source": "https://github.com/thephpleague/event/tree/3.0.3"
+            },
+            "time": "2024-09-04T16:06:53+00:00"
+        },
+        {
+            "name": "league/flysystem",
+            "version": "3.36.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/flysystem.git",
+                "reference": "f7fb152932f30072d573510cbd4dd657d6475b25"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f7fb152932f30072d573510cbd4dd657d6475b25",
+                "reference": "f7fb152932f30072d573510cbd4dd657d6475b25",
+                "shasum": ""
+            },
+            "require": {
+                "league/flysystem-local": "^3.0.0",
+                "league/mime-type-detection": "^1.0.0",
+                "php": "^8.0.2"
+            },
+            "conflict": {
+                "async-aws/core": "<1.19.0",
+                "async-aws/s3": "<1.14.0",
+                "aws/aws-sdk-php": "3.209.31 || 3.210.0",
+                "guzzlehttp/guzzle": "<7.0",
+                "guzzlehttp/ringphp": "<1.1.1",
+                "phpseclib/phpseclib": "3.0.15",
+                "symfony/http-client": "<5.2"
+            },
+            "require-dev": {
+                "async-aws/s3": "^1.5 || ^2.0",
+                "async-aws/simple-s3": "^1.1 || ^2.0",
+                "aws/aws-sdk-php": "^3.295.10",
+                "composer/semver": "^3.0",
+                "ext-fileinfo": "*",
+                "ext-ftp": "*",
+                "ext-mongodb": "^1.3|^2",
+                "ext-zip": "*",
+                "friendsofphp/php-cs-fixer": "^3.5",
+                "google/cloud-storage": "^1.23",
+                "guzzlehttp/psr7": "^2.6",
+                "microsoft/azure-storage-blob": "^1.1",
+                "mongodb/mongodb": "^1.2|^2",
+                "phpseclib/phpseclib": "^3.0.36",
+                "phpstan/phpstan": "^1.10",
+                "phpunit/phpunit": "^9.5.11|^10.0",
+                "sabre/dav": "^4.6.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "League\\Flysystem\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frankdejonge.nl"
+                }
+            ],
+            "description": "File storage abstraction for PHP",
+            "keywords": [
+                "WebDAV",
+                "aws",
+                "cloud",
+                "file",
+                "files",
+                "filesystem",
+                "filesystems",
+                "ftp",
+                "s3",
+                "sftp",
+                "storage"
+            ],
+            "support": {
+                "issues": "https://github.com/thephpleague/flysystem/issues",
+                "source": "https://github.com/thephpleague/flysystem/tree/3.36.0"
+            },
+            "time": "2026-09-02T08:00:27+00:00"
+        },
+        {
+            "name": "league/flysystem-local",
+            "version": "3.35.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/flysystem-local.git",
+                "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/a099b24dce160f3b2239043d13d47c4a1a214ea4",
+                "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4",
+                "shasum": ""
+            },
+            "require": {
+                "ext-fileinfo": "*",
+                "league/flysystem": "^3.0.0",
+                "league/mime-type-detection": "^1.0.0",
+                "php": "^8.0.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "League\\Flysystem\\Local\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frankdejonge.nl"
+                }
+            ],
+            "description": "Local filesystem adapter for Flysystem.",
+            "keywords": [
+                "Flysystem",
+                "file",
+                "files",
+                "filesystem",
+                "local"
+            ],
+            "support": {
+                "source": "https://github.com/thephpleague/flysystem-local/tree/3.35.3"
+            },
+            "time": "2026-08-12T13:29:21+00:00"
+        },
+        {
+            "name": "league/mime-type-detection",
+            "version": "1.17.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/mime-type-detection.git",
+                "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76",
+                "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76",
+                "shasum": ""
+            },
+            "require": {
+                "ext-fileinfo": "*",
+                "php": "^7.4 || ^8.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^3.2",
+                "phpstan/phpstan": "^0.12.68",
+                "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "League\\MimeTypeDetection\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frankdejonge.nl"
+                }
+            ],
+            "description": "Mime-type detection for Flysystem",
+            "support": {
+                "issues": "https://github.com/thephpleague/mime-type-detection/issues",
+                "source": "https://github.com/thephpleague/mime-type-detection/tree/1.17.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/frankdejonge",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/league/flysystem",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-07-09T11:49:27+00:00"
+        },
+        {
+            "name": "league/uri",
+            "version": "7.9.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/uri.git",
+                "reference": "40403f5c3141457a61f7f1aba6706e8fa4a0ece5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/uri/zipball/40403f5c3141457a61f7f1aba6706e8fa4a0ece5",
+                "reference": "40403f5c3141457a61f7f1aba6706e8fa4a0ece5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "league/uri-interfaces": "^7.9.1",
+                "php": "^8.1",
+                "psr/http-factory": "^1"
+            },
+            "conflict": {
+                "league/uri-schemes": "^1.0"
+            },
+            "suggest": {
+                "ext-bcmath": "to improve IPV4 host parsing",
+                "ext-dom": "to convert the URI into an HTML anchor tag",
+                "ext-fileinfo": "to create Data URI from file contennts",
+                "ext-gmp": "to improve IPV4 host parsing",
+                "ext-intl": "to handle IDN host with the best performance",
+                "ext-uri": "to use the PHP native URI class",
+                "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain",
+                "league/uri-components": "to provide additional tools to manipulate URI objects components",
+                "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP",
+                "php-64bit": "to improve IPV4 host parsing",
+                "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
+                "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "7.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Uri\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ignace Nyamagana Butera",
+                    "email": "nyamsprod@gmail.com",
+                    "homepage": "https://nyamsprod.com"
+                }
+            ],
+            "description": "URI manipulation library",
+            "homepage": "https://uri.thephpleague.com",
+            "keywords": [
+                "URN",
+                "data-uri",
+                "file-uri",
+                "ftp",
+                "hostname",
+                "http",
+                "https",
+                "middleware",
+                "parse_str",
+                "parse_url",
+                "psr-7",
+                "query-string",
+                "querystring",
+                "rfc2141",
+                "rfc3986",
+                "rfc3987",
+                "rfc6570",
+                "rfc8141",
+                "uri",
+                "uri-template",
+                "url",
+                "ws"
+            ],
+            "support": {
+                "docs": "https://uri.thephpleague.com",
+                "forum": "https://thephpleague.slack.com",
+                "issues": "https://github.com/thephpleague/uri-src/issues",
+                "source": "https://github.com/thephpleague/uri/tree/7.9.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sponsors/nyamsprod",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-10-27T07:14:23+00:00"
+        },
+        {
+            "name": "league/uri-interfaces",
+            "version": "7.9.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/uri-interfaces.git",
+                "reference": "994300a79e17f179112dc259f8df0c4de982f736"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/994300a79e17f179112dc259f8df0c4de982f736",
+                "reference": "994300a79e17f179112dc259f8df0c4de982f736",
+                "shasum": ""
+            },
+            "require": {
+                "ext-filter": "*",
+                "php": "^8.1",
+                "psr/http-message": "^1.1 || ^2.0"
+            },
+            "suggest": {
+                "ext-bcmath": "to improve IPV4 host parsing",
+                "ext-gmp": "to improve IPV4 host parsing",
+                "ext-intl": "to handle IDN host with the best performance",
+                "php-64bit": "to improve IPV4 host parsing",
+                "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
+                "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "7.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Uri\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ignace Nyamagana Butera",
+                    "email": "nyamsprod@gmail.com",
+                    "homepage": "https://nyamsprod.com"
+                }
+            ],
+            "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI",
+            "homepage": "https://uri.thephpleague.com",
+            "keywords": [
+                "data-uri",
+                "file-uri",
+                "ftp",
+                "hostname",
+                "http",
+                "https",
+                "parse_str",
+                "parse_url",
+                "psr-7",
+                "query-string",
+                "querystring",
+                "rfc3986",
+                "rfc3987",
+                "rfc6570",
+                "uri",
+                "url",
+                "ws"
+            ],
+            "support": {
+                "docs": "https://uri.thephpleague.com",
+                "forum": "https://thephpleague.slack.com",
+                "issues": "https://github.com/thephpleague/uri-src/issues",
+                "source": "https://github.com/thephpleague/uri-interfaces/tree/7.9.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sponsors/nyamsprod",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-10-27T07:14:23+00:00"
+        },
+        {
+            "name": "monolog/monolog",
+            "version": "3.12.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/monolog.git",
+                "reference": "d7059e4c691d05ea1222b5124bed0f961be46e15"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/d7059e4c691d05ea1222b5124bed0f961be46e15",
+                "reference": "d7059e4c691d05ea1222b5124bed0f961be46e15",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "psr/log": "^2.0 || ^3.0"
+            },
+            "provide": {
+                "psr/log-implementation": "3.0.0"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "^3.0",
+                "doctrine/couchdb": "~1.0@dev",
+                "elasticsearch/elasticsearch": "^7 || ^8",
+                "ext-json": "*",
+                "graylog2/gelf-php": "^1.4.2 || ^2.0",
+                "guzzlehttp/guzzle": "^7.4.5",
+                "guzzlehttp/psr7": "^2.2",
+                "mongodb/mongodb": "^1.8 || ^2.0",
+                "php-amqplib/php-amqplib": "~2.4 || ^3",
+                "php-console/php-console": "^3.1.8",
+                "phpstan/phpstan": "^2",
+                "phpstan/phpstan-deprecation-rules": "^2",
+                "phpstan/phpstan-strict-rules": "^2",
+                "phpunit/phpunit": "^10.5.17 || ^11.0.7",
+                "predis/predis": "^1.1 || ^2",
+                "psr/clock": "^1.0",
+                "rollbar/rollbar": "^4.0",
+                "ruflin/elastica": "^7 || ^8",
+                "symfony/mailer": "^5.4 || ^6",
+                "symfony/mime": "^5.4 || ^6"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+                "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
+                "ext-mbstring": "Allow to work properly with unicode symbols",
+                "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+                "ext-openssl": "Required to send log messages using SSL",
+                "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
+                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+                "psr/clock": "Required to pass a clock to the Logger and control the timestamp of log records",
+                "rollbar/rollbar": "Allow sending log messages to Rollbar",
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Monolog\\": "src/Monolog"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "https://seld.be"
+                }
+            ],
+            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+            "homepage": "https://github.com/Seldaek/monolog",
+            "keywords": [
+                "log",
+                "logging",
+                "psr-3"
+            ],
+            "support": {
+                "issues": "https://github.com/Seldaek/monolog/issues",
+                "source": "https://github.com/Seldaek/monolog/tree/3.12.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/Seldaek",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-09-29T19:21:12+00:00"
+        },
+        {
+            "name": "nesbot/carbon",
+            "version": "3.15.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/CarbonPHP/carbon.git",
+                "reference": "ce0e5636fae7d1e99856eb70cae4c719a284aa08"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ce0e5636fae7d1e99856eb70cae4c719a284aa08",
+                "reference": "ce0e5636fae7d1e99856eb70cae4c719a284aa08",
+                "shasum": ""
+            },
+            "require": {
+                "carbonphp/carbon-doctrine-types": "<100.0",
+                "ext-json": "*",
+                "php": "^8.1",
+                "psr/clock": "^1.0",
+                "symfony/clock": "^6.3.12 || ^7.0 || ^8.0",
+                "symfony/polyfill-mbstring": "^1.0",
+                "symfony/polyfill-time": "^1.0",
+                "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0"
+            },
+            "provide": {
+                "psr/clock-implementation": "1.0"
+            },
+            "require-dev": {
+                "doctrine/dbal": "^3.6.3 || ^4.0",
+                "doctrine/orm": "^2.15.2 || ^3.0",
+                "friendsofphp/php-cs-fixer": "^v3.87.1",
+                "kylekatarnls/multi-tester": "^2.5.3",
+                "phpmd/phpmd": "^3.0@dev",
+                "phpstan/extension-installer": "^1.4.3",
+                "phpstan/phpstan": "^2.1.22",
+                "phpunit/phpunit": "^10.5.53",
+                "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0"
+            },
+            "bin": [
+                "bin/carbon"
+            ],
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Carbon\\Laravel\\ServiceProvider"
+                    ]
+                },
+                "phpstan": {
+                    "includes": [
+                        "extension.neon"
+                    ]
+                },
+                "branch-alias": {
+                    "dev-2.x": "2.x-dev",
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Carbon\\": "src/Carbon/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Brian Nesbitt",
+                    "email": "brian@nesbot.com",
+                    "homepage": "https://markido.com"
+                },
+                {
+                    "name": "kylekatarnls",
+                    "homepage": "https://github.com/kylekatarnls"
+                }
+            ],
+            "description": "An API extension for DateTime that supports 281 different languages.",
+            "homepage": "https://carbonphp.github.io/carbon/",
+            "keywords": [
+                "date",
+                "datetime",
+                "time"
+            ],
+            "support": {
+                "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html",
+                "issues": "https://github.com/CarbonPHP/carbon/issues",
+                "source": "https://github.com/CarbonPHP/carbon"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sponsors/kylekatarnls",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/Carbon#sponsor",
+                    "type": "opencollective"
+                },
+                {
+                    "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-25T14:32:48+00:00"
+        },
+        {
+            "name": "nette/schema",
+            "version": "v1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nette/schema.git",
+                "reference": "af9fa8a86bfca9b02b10346220db26e62ef942ec"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nette/schema/zipball/af9fa8a86bfca9b02b10346220db26e62ef942ec",
+                "reference": "af9fa8a86bfca9b02b10346220db26e62ef942ec",
+                "shasum": ""
+            },
+            "require": {
+                "nette/utils": "^4.0",
+                "php": "8.1 - 8.6"
+            },
+            "require-dev": {
+                "nette/phpstan-rules": "^1.0.7",
+                "nette/tester": "^2.6",
+                "phpstan/extension-installer": "^1.4@stable",
+                "phpstan/phpstan": "^2.3@stable",
+                "tracy/tracy": "^2.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Nette\\": "src"
+                },
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0-only",
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                }
+            ],
+            "description": "📐 Nette Schema: validates and normalizes data such as configuration or API input against a schema described in a few readable lines of PHP, with precise error messages.",
+            "homepage": "https://doc.nette.org/schema",
+            "keywords": [
+                "configuration",
+                "data validation",
+                "nette",
+                "normalization",
+                "schema",
+                "validation"
+            ],
+            "support": {
+                "issues": "https://github.com/nette/schema/issues",
+                "source": "https://github.com/nette/schema/tree/v1.4.0"
+            },
+            "time": "2026-10-22T02:12:50+00:00"
+        },
+        {
+            "name": "nette/utils",
+            "version": "v4.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nette/utils.git",
+                "reference": "6bf24518accf32cf845bca305e0b92078700fd01"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nette/utils/zipball/6bf24518accf32cf845bca305e0b92078700fd01",
+                "reference": "6bf24518accf32cf845bca305e0b92078700fd01",
+                "shasum": ""
+            },
+            "require": {
+                "php": "8.2 - 8.6"
+            },
+            "conflict": {
+                "nette/finder": "<3",
+                "nette/schema": "<1.2.2"
+            },
+            "require-dev": {
+                "jetbrains/phpstorm-attributes": "^1.2",
+                "nette/phpstan-rules": "^1.0",
+                "nette/tester": "^2.5",
+                "phpstan/extension-installer": "^1.4@stable",
+                "phpstan/phpstan": "^2.1@stable",
+                "tracy/tracy": "^2.9"
+            },
+            "suggest": {
+                "ext-gd": "to use Image",
+                "ext-iconv": "to use Strings::chr(), ord() and reverse()",
+                "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
+                "ext-json": "to use Nette\\Utils\\Json",
+                "ext-mbstring": "to use Strings::lower() etc...",
+                "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Nette\\": "src"
+                },
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0-only",
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                },
+                {
+                    "name": "Nette Community",
+                    "homepage": "https://nette.org/contributors"
+                }
+            ],
+            "description": "🛠  Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+            "homepage": "https://nette.org",
+            "keywords": [
+                "array",
+                "core",
+                "datetime",
+                "images",
+                "json",
+                "nette",
+                "paginator",
+                "password",
+                "slugify",
+                "string",
+                "unicode",
+                "utf-8",
+                "utility",
+                "validation"
+            ],
+            "support": {
+                "issues": "https://github.com/nette/utils/issues",
+                "source": "https://github.com/nette/utils/tree/v4.1.6"
+            },
+            "time": "2026-10-20T17:07:56+00:00"
+        },
+        {
+            "name": "nikic/php-parser",
+            "version": "v5.9.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nikic/PHP-Parser.git",
+                "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def",
+                "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-tokenizer": "*",
+                "php": ">=7.4"
+            },
+            "require-dev": {
+                "ircmaxell/php-yacc": "^0.0.7",
+                "phpunit/phpunit": "^9.0"
+            },
+            "bin": [
+                "bin/php-parse"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpParser\\": "lib/PhpParser"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov"
+                }
+            ],
+            "description": "A PHP parser written in PHP",
+            "keywords": [
+                "parser",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/nikic/PHP-Parser/issues",
+                "source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0"
+            },
+            "time": "2026-09-13T18:51:52+00:00"
+        },
+        {
+            "name": "nunomaduro/termwind",
+            "version": "v2.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nunomaduro/termwind.git",
+                "reference": "712a31b768f5daea284c2169a7d227031001b9a8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8",
+                "reference": "712a31b768f5daea284c2169a7d227031001b9a8",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "php": "^8.2",
+                "symfony/console": "^7.4.4 || ^8.0.4"
+            },
+            "require-dev": {
+                "illuminate/console": "^11.47.0",
+                "laravel/pint": "^1.27.1",
+                "mockery/mockery": "^1.6.12",
+                "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2",
+                "phpstan/phpstan": "^1.12.32",
+                "phpstan/phpstan-strict-rules": "^1.6.2",
+                "symfony/var-dumper": "^7.3.5 || ^8.0.4",
+                "thecodingmachine/phpstan-strict-rules": "^1.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Termwind\\Laravel\\TermwindServiceProvider"
+                    ]
+                },
+                "branch-alias": {
+                    "dev-2.x": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/Functions.php"
+                ],
+                "psr-4": {
+                    "Termwind\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nuno Maduro",
+                    "email": "enunomaduro@gmail.com"
+                }
+            ],
+            "description": "It's like Tailwind CSS, but for the console.",
+            "keywords": [
+                "cli",
+                "console",
+                "css",
+                "package",
+                "php",
+                "style"
+            ],
+            "support": {
+                "issues": "https://github.com/nunomaduro/termwind/issues",
+                "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0"
+            },
+            "funding": [
+                {
+                    "url": "https://www.paypal.com/paypalme/enunomaduro",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/nunomaduro",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/xiCO2k",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-02-16T23:10:27+00:00"
+        },
+        {
+            "name": "paragonie/constant_time_encoding",
+            "version": "v3.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/constant_time_encoding.git",
+                "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
+                "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8"
+            },
+            "require-dev": {
+                "infection/infection": "^0",
+                "nikic/php-fuzzer": "^0",
+                "phpunit/phpunit": "^9|^10|^11",
+                "vimeo/psalm": "^4|^5|^6"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "ParagonIE\\ConstantTime\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com",
+                    "role": "Maintainer"
+                },
+                {
+                    "name": "Steve 'Sc00bz' Thomas",
+                    "email": "steve@tobtu.com",
+                    "homepage": "https://www.tobtu.com",
+                    "role": "Original Developer"
+                }
+            ],
+            "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
+            "keywords": [
+                "base16",
+                "base32",
+                "base32_decode",
+                "base32_encode",
+                "base64",
+                "base64_decode",
+                "base64_encode",
+                "bin2hex",
+                "encoding",
+                "hex",
+                "hex2bin",
+                "rfc4648"
+            ],
+            "support": {
+                "email": "info@paragonie.com",
+                "issues": "https://github.com/paragonie/constant_time_encoding/issues",
+                "source": "https://github.com/paragonie/constant_time_encoding"
+            },
+            "time": "2025-09-24T15:06:41+00:00"
+        },
+        {
+            "name": "paragonie/ecc",
+            "version": "v2.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/phpecc.git",
+                "reference": "1a49380410b8ce826bc7fd3de6324053e049205c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/phpecc/zipball/1a49380410b8ce826bc7fd3de6324053e049205c",
+                "reference": "1a49380410b8ce826bc7fd3de6324053e049205c",
+                "shasum": ""
+            },
+            "require": {
+                "ext-gmp": "*",
+                "genkgo/php-asn1": "^2",
+                "paragonie/sodium_compat": "^1|^2",
+                "php": "^7.1||^8.0"
+            },
+            "require-dev": {
+                "bitcoin/bips": "dev-master",
+                "c2sp/wycheproof": "dev-master",
+                "ext-json": "*",
+                "phpunit/phpunit": "^6|^7|^8|^9",
+                "squizlabs/php_codesniffer": "^2|^3",
+                "symfony/yaml": "^2.6|^3.0|^4|^5|^6|^7",
+                "vimeo/psalm": "^2|^3|^4|^5|^6"
+            },
+            "suggest": {
+                "ext-openssl": "(PHP 8.1, OpenSSL 3+) Improved performance, less worries about side-channels"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Mdanter\\Ecc\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com",
+                    "role": "New Maintainer"
+                },
+                {
+                    "name": "Matyas Danter",
+                    "homepage": "http://matejdanter.com/",
+                    "role": "Previous Author"
+                },
+                {
+                    "name": "Thibaud Fabre",
+                    "email": "thibaud@aztech.io",
+                    "homepage": "http://aztech.io",
+                    "role": "Previous Maintainer"
+                },
+                {
+                    "name": "Thomas Kerin",
+                    "email": "afk11@users.noreply.github.com",
+                    "role": "Previous Maintainer"
+                }
+            ],
+            "description": "PHP Elliptic Curve Cryptography library",
+            "homepage": "https://github.com/phpecc/phpecc",
+            "keywords": [
+                "Diffie",
+                "ECDSA",
+                "Hellman",
+                "curve",
+                "ecdh",
+                "elliptic",
+                "nistp192",
+                "nistp224",
+                "nistp256",
+                "nistp384",
+                "nistp521",
+                "phpecc",
+                "secp256k1",
+                "secp256r1"
+            ],
+            "support": {
+                "issues": "https://github.com/paragonie/phpecc/issues",
+                "source": "https://github.com/paragonie/phpecc/tree/v2.6.0"
+            },
+            "time": "2026-08-20T07:00:03+00:00"
+        },
+        {
+            "name": "paragonie/random_compat",
+            "version": "v9.99.100",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/random_compat.git",
+                "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
+                "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">= 7"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*|5.*",
+                "vimeo/psalm": "^1"
+            },
+            "suggest": {
+                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+            },
+            "type": "library",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com"
+                }
+            ],
+            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+            "keywords": [
+                "csprng",
+                "polyfill",
+                "pseudorandom",
+                "random"
+            ],
+            "support": {
+                "email": "info@paragonie.com",
+                "issues": "https://github.com/paragonie/random_compat/issues",
+                "source": "https://github.com/paragonie/random_compat"
+            },
+            "time": "2020-10-15T08:29:30+00:00"
+        },
+        {
+            "name": "paragonie/sodium_compat",
+            "version": "v2.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/sodium_compat.git",
+                "reference": "3246b36803c177847e677862fedeb7f937869adb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/3246b36803c177847e677862fedeb7f937869adb",
+                "reference": "3246b36803c177847e677862fedeb7f937869adb",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1",
+                "php-64bit": "*"
+            },
+            "require-dev": {
+                "infection/infection": "^0",
+                "nikic/php-fuzzer": "^0",
+                "phpunit/phpunit": "^7|^8|^9|^10|^11",
+                "vimeo/psalm": "^4|^5|^6"
+            },
+            "suggest": {
+                "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "autoload.php"
+                ],
+                "psr-4": {
+                    "ParagonIE\\Sodium\\": "namespaced/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "ISC"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com"
+                },
+                {
+                    "name": "Frank Denis",
+                    "email": "jedisct1@pureftpd.org"
+                }
+            ],
+            "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists",
+            "keywords": [
+                "Authentication",
+                "BLAKE2b",
+                "ChaCha20",
+                "ChaCha20-Poly1305",
+                "Chapoly",
+                "Curve25519",
+                "Ed25519",
+                "EdDSA",
+                "Edwards-curve Digital Signature Algorithm",
+                "Elliptic Curve Diffie-Hellman",
+                "Poly1305",
+                "Pure-PHP cryptography",
+                "RFC 7748",
+                "RFC 8032",
+                "Salpoly",
+                "Salsa20",
+                "X25519",
+                "XChaCha20-Poly1305",
+                "XSalsa20-Poly1305",
+                "Xchacha20",
+                "Xsalsa20",
+                "aead",
+                "cryptography",
+                "ecdh",
+                "elliptic curve",
+                "elliptic curve cryptography",
+                "encryption",
+                "libsodium",
+                "php",
+                "public-key cryptography",
+                "secret-key cryptography",
+                "side-channel resistant"
+            ],
+            "support": {
+                "issues": "https://github.com/paragonie/sodium_compat/issues",
+                "source": "https://github.com/paragonie/sodium_compat/tree/v2.5.2"
+            },
+            "time": "2026-08-18T23:46:48+00:00"
+        },
+        {
+            "name": "phpoption/phpoption",
+            "version": "1.10.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/schmittjoh/php-option.git",
+                "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/67b192b6a42ec03944b972d6e633ddec78ad2c6d",
+                "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.54 || ^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.33"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                },
+                "branch-alias": {
+                    "dev-master": "1.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpOption\\": "src/PhpOption/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Johannes M. Schmitt",
+                    "email": "schmittjoh@gmail.com",
+                    "homepage": "https://github.com/schmittjoh"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "Option Type for PHP",
+            "keywords": [
+                "language",
+                "option",
+                "php",
+                "type"
+            ],
+            "support": {
+                "issues": "https://github.com/schmittjoh/php-option/issues",
+                "source": "https://github.com/schmittjoh/php-option/tree/1.10.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-08-24T00:54:40+00:00"
+        },
+        {
+            "name": "phpseclib/phpseclib",
+            "version": "3.0.57",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpseclib/phpseclib.git",
+                "reference": "d17e0ddaeaf6f22f7e007cbb437d78792fe2a0e4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/d17e0ddaeaf6f22f7e007cbb437d78792fe2a0e4",
+                "reference": "d17e0ddaeaf6f22f7e007cbb437d78792fe2a0e4",
+                "shasum": ""
+            },
+            "require": {
+                "paragonie/constant_time_encoding": "^1|^2|^3",
+                "paragonie/random_compat": "^1.4|^2.0|^9.99.99",
+                "php": ">=5.6.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "*"
+            },
+            "suggest": {
+                "ext-dom": "Install the DOM extension to load XML formatted public keys.",
+                "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
+                "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
+                "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
+                "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "phpseclib/bootstrap.php"
+                ],
+                "psr-4": {
+                    "phpseclib3\\": "phpseclib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jim Wigginton",
+                    "email": "terrafrost@php.net",
+                    "role": "Lead Developer"
+                },
+                {
+                    "name": "Patrick Monnerat",
+                    "email": "pm@datasphere.ch",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Andreas Fischer",
+                    "email": "bantu@phpbb.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Hans-Jürgen Petrich",
+                    "email": "petrich@tronic-media.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "graham@alt-three.com",
+                    "role": "Developer"
+                }
+            ],
+            "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
+            "homepage": "http://phpseclib.sourceforge.net",
+            "keywords": [
+                "BigInteger",
+                "aes",
+                "asn.1",
+                "asn1",
+                "blowfish",
+                "crypto",
+                "cryptography",
+                "encryption",
+                "rsa",
+                "security",
+                "sftp",
+                "signature",
+                "signing",
+                "ssh",
+                "twofish",
+                "x.509",
+                "x509"
+            ],
+            "support": {
+                "issues": "https://github.com/phpseclib/phpseclib/issues",
+                "source": "https://github.com/phpseclib/phpseclib/tree/3.0.57"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/terrafrost",
+                    "type": "github"
+                },
+                {
+                    "url": "https://www.patreon.com/phpseclib",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-08-26T12:13:21+00:00"
+        },
+        {
+            "name": "psr/clock",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/clock.git",
+                "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+                "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0 || ^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Clock\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for reading the clock.",
+            "homepage": "https://github.com/php-fig/clock",
+            "keywords": [
+                "clock",
+                "now",
+                "psr",
+                "psr-20",
+                "time"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/clock/issues",
+                "source": "https://github.com/php-fig/clock/tree/1.0.0"
+            },
+            "time": "2022-11-25T14:36:26+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/container/issues",
+                "source": "https://github.com/php-fig/container/tree/2.0.2"
+            },
+            "time": "2021-11-05T16:47:00+00:00"
+        },
+        {
+            "name": "psr/event-dispatcher",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/event-dispatcher.git",
+                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\EventDispatcher\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Standard interfaces for event handling.",
+            "keywords": [
+                "events",
+                "psr",
+                "psr-14"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/event-dispatcher/issues",
+                "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+            },
+            "time": "2019-01-08T18:20:26+00:00"
+        },
+        {
+            "name": "psr/http-client",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-client.git",
+                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0 || ^8.0",
+                "psr/http-message": "^1.0 || ^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Client\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP clients",
+            "homepage": "https://github.com/php-fig/http-client",
+            "keywords": [
+                "http",
+                "http-client",
+                "psr",
+                "psr-18"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-client"
+            },
+            "time": "2023-09-23T14:17:50+00:00"
+        },
+        {
+            "name": "psr/http-factory",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-factory.git",
+                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1",
+                "psr/http-message": "^1.0 || ^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+            "keywords": [
+                "factory",
+                "http",
+                "message",
+                "psr",
+                "psr-17",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-factory"
+            },
+            "time": "2024-04-15T12:06:14+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+                "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-message/tree/2.0"
+            },
+            "time": "2023-04-04T09:54:51+00:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+                "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/log/tree/3.0.2"
+            },
+            "time": "2024-09-11T13:17:53+00:00"
+        },
+        {
+            "name": "psr/simple-cache",
+            "version": "3.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
+                "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
+            },
+            "time": "2021-10-29T13:26:27+00:00"
+        },
+        {
+            "name": "psy/psysh",
+            "version": "v0.12.24",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/bobthecow/psysh.git",
+                "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1",
+                "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-tokenizer": "*",
+                "nikic/php-parser": "^5.0 || ^4.0",
+                "php": "^8.0 || ^7.4",
+                "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4",
+                "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4"
+            },
+            "conflict": {
+                "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.2",
+                "composer/class-map-generator": "^1.6"
+            },
+            "suggest": {
+                "composer/class-map-generator": "Improved tab completion performance with better class discovery.",
+                "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)",
+                "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well."
+            },
+            "bin": [
+                "bin/psysh"
+            ],
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": false,
+                    "forward-command": false
+                },
+                "branch-alias": {
+                    "dev-main": "0.12.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions.php"
+                ],
+                "psr-4": {
+                    "Psy\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Justin Hileman",
+                    "email": "justin@justinhileman.info"
+                }
+            ],
+            "description": "An interactive shell for modern PHP.",
+            "homepage": "https://psysh.org",
+            "keywords": [
+                "REPL",
+                "console",
+                "interactive",
+                "shell"
+            ],
+            "support": {
+                "issues": "https://github.com/bobthecow/psysh/issues",
+                "source": "https://github.com/bobthecow/psysh/tree/v0.12.24"
+            },
+            "time": "2026-06-29T15:41:09+00:00"
+        },
+        {
+            "name": "pusher/pusher-php-server",
+            "version": "7.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/pusher/pusher-http-php.git",
+                "reference": "058d8464246118110a341fc2e6e70c7c8b6a7f2c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/058d8464246118110a341fc2e6e70c7c8b6a7f2c",
+                "reference": "058d8464246118110a341fc2e6e70c7c8b6a7f2c",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "ext-json": "*",
+                "guzzlehttp/guzzle": "^7.8.2 || ^8.0",
+                "guzzlehttp/promises": "^2.0.3 || ^3.0",
+                "guzzlehttp/psr7": "^2.6.3 || ^3.0",
+                "php": "^7.3|^8.0",
+                "psr/http-client": "^1.0",
+                "psr/log": "^1.0 || ^2.0 || ^3.0"
+            },
+            "require-dev": {
+                "overtrue/phplint": "^2.3",
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Pusher\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Library for interacting with the Pusher REST API",
+            "keywords": [
+                "events",
+                "messaging",
+                "php-pusher-server",
+                "publish",
+                "push",
+                "pusher",
+                "real time",
+                "real-time",
+                "realtime",
+                "rest",
+                "trigger"
+            ],
+            "support": {
+                "issues": "https://github.com/pusher/pusher-http-php/issues",
+                "source": "https://github.com/pusher/pusher-http-php/tree/7.3.0"
+            },
+            "time": "2026-08-07T12:47:11+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "support": {
+                "issues": "https://github.com/ralouphie/getallheaders/issues",
+                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+            },
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "ramsey/collection",
+            "version": "2.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ramsey/collection.git",
+                "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
+                "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "captainhook/plugin-composer": "^5.3",
+                "ergebnis/composer-normalize": "^2.45",
+                "fakerphp/faker": "^1.24",
+                "hamcrest/hamcrest-php": "^2.0",
+                "jangregor/phpstan-prophecy": "^2.1",
+                "mockery/mockery": "^1.6",
+                "php-parallel-lint/php-console-highlighter": "^1.0",
+                "php-parallel-lint/php-parallel-lint": "^1.4",
+                "phpspec/prophecy-phpunit": "^2.3",
+                "phpstan/extension-installer": "^1.4",
+                "phpstan/phpstan": "^2.1",
+                "phpstan/phpstan-mockery": "^2.0",
+                "phpstan/phpstan-phpunit": "^2.0",
+                "phpunit/phpunit": "^10.5",
+                "ramsey/coding-standard": "^2.3",
+                "ramsey/conventional-commits": "^1.6",
+                "roave/security-advisories": "dev-latest"
+            },
+            "type": "library",
+            "extra": {
+                "captainhook": {
+                    "force-install": true
+                },
+                "ramsey/conventional-commits": {
+                    "configFile": "conventional-commits.json"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Ramsey\\Collection\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ben Ramsey",
+                    "email": "ben@benramsey.com",
+                    "homepage": "https://benramsey.com"
+                }
+            ],
+            "description": "A PHP library for representing and manipulating collections.",
+            "keywords": [
+                "array",
+                "collection",
+                "hash",
+                "map",
+                "queue",
+                "set"
+            ],
+            "support": {
+                "issues": "https://github.com/ramsey/collection/issues",
+                "source": "https://github.com/ramsey/collection/tree/2.1.1"
+            },
+            "time": "2025-03-22T05:38:12+00:00"
+        },
+        {
+            "name": "ramsey/uuid",
+            "version": "4.9.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ramsey/uuid.git",
+                "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ramsey/uuid/zipball/75d73f48d02797c2c285a7e9f348fadc0102ffe2",
+                "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2",
+                "shasum": ""
+            },
+            "require": {
+                "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0",
+                "php": "^8.0",
+                "ramsey/collection": "^1.2 || ^2.0"
+            },
+            "replace": {
+                "rhumsaa/uuid": "self.version"
+            },
+            "require-dev": {
+                "captainhook/captainhook": "^5.25",
+                "captainhook/plugin-composer": "^5.3",
+                "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+                "ergebnis/composer-normalize": "^2.47",
+                "mockery/mockery": "^1.6",
+                "paragonie/random-lib": "^2",
+                "php-mock/php-mock": "^2.6",
+                "php-mock/php-mock-mockery": "^1.5",
+                "php-parallel-lint/php-parallel-lint": "^1.4.0",
+                "phpbench/phpbench": "^1.2.14",
+                "phpstan/extension-installer": "^1.4",
+                "phpstan/phpstan": "^2.1",
+                "phpstan/phpstan-mockery": "^2.0",
+                "phpstan/phpstan-phpunit": "^2.0",
+                "phpunit/phpunit": "^9.6",
+                "slevomat/coding-standard": "^8.18",
+                "squizlabs/php_codesniffer": "^3.13"
+            },
+            "suggest": {
+                "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
+                "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
+                "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
+                "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+                "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+            },
+            "type": "library",
+            "extra": {
+                "captainhook": {
+                    "force-install": true
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions.php"
+                ],
+                "psr-4": {
+                    "Ramsey\\Uuid\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
+            "keywords": [
+                "guid",
+                "identifier",
+                "uuid"
+            ],
+            "support": {
+                "issues": "https://github.com/ramsey/uuid/issues",
+                "source": "https://github.com/ramsey/uuid/tree/4.9.4"
+            },
+            "time": "2026-09-16T11:39:30+00:00"
+        },
+        {
+            "name": "ratchet/rfc6455",
+            "version": "v0.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ratchetphp/RFC6455.git",
+                "reference": "9b05f371219cbaf9748b505f139617dd0715592b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/9b05f371219cbaf9748b505f139617dd0715592b",
+                "reference": "9b05f371219cbaf9748b505f139617dd0715592b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.4",
+                "psr/http-factory-implementation": "^1.0",
+                "symfony/polyfill-php80": "^1.15"
+            },
+            "require-dev": {
+                "guzzlehttp/psr7": "^2.7",
+                "phpunit/phpunit": "^9.5",
+                "react/socket": "^1.3"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Ratchet\\RFC6455\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Chris Boden",
+                    "email": "cboden@gmail.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Matt Bonneau",
+                    "role": "Developer"
+                }
+            ],
+            "description": "RFC6455 WebSocket protocol handler",
+            "homepage": "http://socketo.me",
+            "keywords": [
+                "WebSockets",
+                "rfc6455",
+                "websocket"
+            ],
+            "support": {
+                "chat": "https://gitter.im/reactphp/reactphp",
+                "issues": "https://github.com/ratchetphp/RFC6455/issues",
+                "source": "https://github.com/ratchetphp/RFC6455/tree/v0.4.1"
+            },
+            "time": "2026-06-06T14:34:23+00:00"
+        },
+        {
+            "name": "react/cache",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/reactphp/cache.git",
+                "reference": "d47c472b64aa5608225f47965a484b75c7817d5b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b",
+                "reference": "d47c472b64aa5608225f47965a484b75c7817d5b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "react/promise": "^3.0 || ^2.0 || ^1.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "React\\Cache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Christian Lück",
+                    "email": "christian@clue.engineering",
+                    "homepage": "https://clue.engineering/"
+                },
+                {
+                    "name": "Cees-Jan Kiewiet",
+                    "email": "reactphp@ceesjankiewiet.nl",
+                    "homepage": "https://wyrihaximus.net/"
+                },
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com",
+                    "homepage": "https://sorgalla.com/"
+                },
+                {
+                    "name": "Chris Boden",
+                    "email": "cboden@gmail.com",
+                    "homepage": "https://cboden.dev/"
+                }
+            ],
+            "description": "Async, Promise-based cache interface for ReactPHP",
+            "keywords": [
+                "cache",
+                "caching",
+                "promise",
+                "reactphp"
+            ],
+            "support": {
+                "issues": "https://github.com/reactphp/cache/issues",
+                "source": "https://github.com/reactphp/cache/tree/v1.2.0"
+            },
+            "funding": [
+                {
+                    "url": "https://opencollective.com/reactphp",
+                    "type": "open_collective"
+                }
+            ],
+            "time": "2022-11-30T15:59:55+00:00"
+        },
+        {
+            "name": "react/dns",
+            "version": "v1.14.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/reactphp/dns.git",
+                "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3",
+                "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "react/cache": "^1.0 || ^0.6 || ^0.5",
+                "react/event-loop": "^1.2",
+                "react/promise": "^3.2 || ^2.7 || ^1.2.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+                "react/async": "^4.3 || ^3 || ^2",
+                "react/promise-timer": "^1.11"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "React\\Dns\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Christian Lück",
+                    "email": "christian@clue.engineering",
+                    "homepage": "https://clue.engineering/"
+                },
+                {
+                    "name": "Cees-Jan Kiewiet",
+                    "email": "reactphp@ceesjankiewiet.nl",
+                    "homepage": "https://wyrihaximus.net/"
+                },
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com",
+                    "homepage": "https://sorgalla.com/"
+                },
+                {
+                    "name": "Chris Boden",
+                    "email": "cboden@gmail.com",
+                    "homepage": "https://cboden.dev/"
+                }
+            ],
+            "description": "Async DNS resolver for ReactPHP",
+            "keywords": [
+                "async",
+                "dns",
+                "dns-resolver",
+                "reactphp"
+            ],
+            "support": {
+                "issues": "https://github.com/reactphp/dns/issues",
+                "source": "https://github.com/reactphp/dns/tree/v1.14.0"
+            },
+            "funding": [
+                {
+                    "url": "https://opencollective.com/reactphp",
+                    "type": "open_collective"
+                }
+            ],
+            "time": "2025-11-18T19:34:28+00:00"
+        },
+        {
+            "name": "react/event-loop",
+            "version": "v1.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/reactphp/event-loop.git",
+                "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
+                "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+            },
+            "suggest": {
+                "ext-pcntl": "For signal handling support when using the StreamSelectLoop"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "React\\EventLoop\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Christian Lück",
+                    "email": "christian@clue.engineering",
+                    "homepage": "https://clue.engineering/"
+                },
+                {
+                    "name": "Cees-Jan Kiewiet",
+                    "email": "reactphp@ceesjankiewiet.nl",
+                    "homepage": "https://wyrihaximus.net/"
+                },
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com",
+                    "homepage": "https://sorgalla.com/"
+                },
+                {
+                    "name": "Chris Boden",
+                    "email": "cboden@gmail.com",
+                    "homepage": "https://cboden.dev/"
+                }
+            ],
+            "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
+            "keywords": [
+                "asynchronous",
+                "event-loop"
+            ],
+            "support": {
+                "issues": "https://github.com/reactphp/event-loop/issues",
+                "source": "https://github.com/reactphp/event-loop/tree/v1.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://opencollective.com/reactphp",
+                    "type": "open_collective"
+                }
+            ],
+            "time": "2025-11-17T20:46:25+00:00"
+        },
+        {
+            "name": "react/promise",
+            "version": "v3.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/reactphp/promise.git",
+                "reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
+                "reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "1.12.28 || 1.4.10",
+                "phpunit/phpunit": "^9.6 || ^7.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "React\\Promise\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com",
+                    "homepage": "https://sorgalla.com/"
+                },
+                {
+                    "name": "Christian Lück",
+                    "email": "christian@clue.engineering",
+                    "homepage": "https://clue.engineering/"
+                },
+                {
+                    "name": "Cees-Jan Kiewiet",
+                    "email": "reactphp@ceesjankiewiet.nl",
+                    "homepage": "https://wyrihaximus.net/"
+                },
+                {
+                    "name": "Chris Boden",
+                    "email": "cboden@gmail.com",
+                    "homepage": "https://cboden.dev/"
+                }
+            ],
+            "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+            "keywords": [
+                "promise",
+                "promises"
+            ],
+            "support": {
+                "issues": "https://github.com/reactphp/promise/issues",
+                "source": "https://github.com/reactphp/promise/tree/v3.3.0"
+            },
+            "funding": [
+                {
+                    "url": "https://opencollective.com/reactphp",
+                    "type": "open_collective"
+                }
+            ],
+            "time": "2025-08-19T18:57:03+00:00"
+        },
+        {
+            "name": "react/promise-timer",
+            "version": "v1.11.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/reactphp/promise-timer.git",
+                "reference": "4f70306ed66b8b44768941ca7f142092600fafc1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/4f70306ed66b8b44768941ca7f142092600fafc1",
+                "reference": "4f70306ed66b8b44768941ca7f142092600fafc1",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3",
+                "react/event-loop": "^1.2",
+                "react/promise": "^3.2 || ^2.7.0 || ^1.2.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "React\\Promise\\Timer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Christian Lück",
+                    "email": "christian@clue.engineering",
+                    "homepage": "https://clue.engineering/"
+                },
+                {
+                    "name": "Cees-Jan Kiewiet",
+                    "email": "reactphp@ceesjankiewiet.nl",
+                    "homepage": "https://wyrihaximus.net/"
+                },
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com",
+                    "homepage": "https://sorgalla.com/"
+                },
+                {
+                    "name": "Chris Boden",
+                    "email": "cboden@gmail.com",
+                    "homepage": "https://cboden.dev/"
+                }
+            ],
+            "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
+            "homepage": "https://github.com/reactphp/promise-timer",
+            "keywords": [
+                "async",
+                "event-loop",
+                "promise",
+                "reactphp",
+                "timeout",
+                "timer"
+            ],
+            "support": {
+                "issues": "https://github.com/reactphp/promise-timer/issues",
+                "source": "https://github.com/reactphp/promise-timer/tree/v1.11.0"
+            },
+            "funding": [
+                {
+                    "url": "https://opencollective.com/reactphp",
+                    "type": "open_collective"
+                }
+            ],
+            "time": "2024-06-04T14:27:45+00:00"
+        },
+        {
+            "name": "react/socket",
+            "version": "v1.17.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/reactphp/socket.git",
+                "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08",
+                "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08",
+                "shasum": ""
+            },
+            "require": {
+                "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+                "php": ">=5.3.0",
+                "react/dns": "^1.13",
+                "react/event-loop": "^1.2",
+                "react/promise": "^3.2 || ^2.6 || ^1.2.1",
+                "react/stream": "^1.4"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+                "react/async": "^4.3 || ^3.3 || ^2",
+                "react/promise-stream": "^1.4",
+                "react/promise-timer": "^1.11"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "React\\Socket\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Christian Lück",
+                    "email": "christian@clue.engineering",
+                    "homepage": "https://clue.engineering/"
+                },
+                {
+                    "name": "Cees-Jan Kiewiet",
+                    "email": "reactphp@ceesjankiewiet.nl",
+                    "homepage": "https://wyrihaximus.net/"
+                },
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com",
+                    "homepage": "https://sorgalla.com/"
+                },
+                {
+                    "name": "Chris Boden",
+                    "email": "cboden@gmail.com",
+                    "homepage": "https://cboden.dev/"
+                }
+            ],
+            "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+            "keywords": [
+                "Connection",
+                "Socket",
+                "async",
+                "reactphp",
+                "stream"
+            ],
+            "support": {
+                "issues": "https://github.com/reactphp/socket/issues",
+                "source": "https://github.com/reactphp/socket/tree/v1.17.0"
+            },
+            "funding": [
+                {
+                    "url": "https://opencollective.com/reactphp",
+                    "type": "open_collective"
+                }
+            ],
+            "time": "2025-11-19T20:47:34+00:00"
+        },
+        {
+            "name": "react/stream",
+            "version": "v1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/reactphp/stream.git",
+                "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d",
+                "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d",
+                "shasum": ""
+            },
+            "require": {
+                "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+                "php": ">=5.3.8",
+                "react/event-loop": "^1.2"
+            },
+            "require-dev": {
+                "clue/stream-filter": "~1.2",
+                "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "React\\Stream\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Christian Lück",
+                    "email": "christian@clue.engineering",
+                    "homepage": "https://clue.engineering/"
+                },
+                {
+                    "name": "Cees-Jan Kiewiet",
+                    "email": "reactphp@ceesjankiewiet.nl",
+                    "homepage": "https://wyrihaximus.net/"
+                },
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com",
+                    "homepage": "https://sorgalla.com/"
+                },
+                {
+                    "name": "Chris Boden",
+                    "email": "cboden@gmail.com",
+                    "homepage": "https://cboden.dev/"
+                }
+            ],
+            "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+            "keywords": [
+                "event-driven",
+                "io",
+                "non-blocking",
+                "pipe",
+                "reactphp",
+                "readable",
+                "stream",
+                "writable"
+            ],
+            "support": {
+                "issues": "https://github.com/reactphp/stream/issues",
+                "source": "https://github.com/reactphp/stream/tree/v1.4.0"
+            },
+            "funding": [
+                {
+                    "url": "https://opencollective.com/reactphp",
+                    "type": "open_collective"
+                }
+            ],
+            "time": "2024-06-11T12:45:25+00:00"
+        },
+        {
+            "name": "simplesoftwareio/simple-qrcode",
+            "version": "4.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/SimpleSoftwareIO/simple-qrcode.git",
+                "reference": "916db7948ca6772d54bb617259c768c9cdc8d537"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/SimpleSoftwareIO/simple-qrcode/zipball/916db7948ca6772d54bb617259c768c9cdc8d537",
+                "reference": "916db7948ca6772d54bb617259c768c9cdc8d537",
+                "shasum": ""
+            },
+            "require": {
+                "bacon/bacon-qr-code": "^2.0",
+                "ext-gd": "*",
+                "php": ">=7.2|^8.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "~1",
+                "phpunit/phpunit": "~9"
+            },
+            "suggest": {
+                "ext-imagick": "Allows the generation of PNG QrCodes.",
+                "illuminate/support": "Allows for use within Laravel."
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "aliases": {
+                        "QrCode": "SimpleSoftwareIO\\QrCode\\Facades\\QrCode"
+                    },
+                    "providers": [
+                        "SimpleSoftwareIO\\QrCode\\QrCodeServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "SimpleSoftwareIO\\QrCode\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Simple Software LLC",
+                    "email": "support@simplesoftware.io"
+                }
+            ],
+            "description": "Simple QrCode is a QR code generator made for Laravel.",
+            "homepage": "https://www.simplesoftware.io/#/docs/simple-qrcode",
+            "keywords": [
+                "Simple",
+                "generator",
+                "laravel",
+                "qrcode",
+                "wrapper"
+            ],
+            "support": {
+                "issues": "https://github.com/SimpleSoftwareIO/simple-qrcode/issues",
+                "source": "https://github.com/SimpleSoftwareIO/simple-qrcode/tree/4.2.0"
+            },
+            "time": "2021-02-08T20:43:55+00:00"
+        },
+        {
+            "name": "simplito/bigint-wrapper-php",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/simplito/bigint-wrapper-php.git",
+                "reference": "cf21ec76d33f103add487b3eadbd9f5033a25930"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/simplito/bigint-wrapper-php/zipball/cf21ec76d33f103add487b3eadbd9f5033a25930",
+                "reference": "cf21ec76d33f103add487b3eadbd9f5033a25930",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "BI\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Simplito Team",
+                    "email": "s.smyczynski@simplito.com",
+                    "homepage": "https://simplito.com"
+                }
+            ],
+            "description": "Common interface for php_gmp and php_bcmath modules",
+            "support": {
+                "issues": "https://github.com/simplito/bigint-wrapper-php/issues",
+                "source": "https://github.com/simplito/bigint-wrapper-php/tree/1.0.0"
+            },
+            "time": "2018-02-27T12:38:08+00:00"
+        },
+        {
+            "name": "simplito/bn-php",
+            "version": "1.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/simplito/bn-php.git",
+                "reference": "83446756a81720eacc2ffb87ff97958431451fd6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/simplito/bn-php/zipball/83446756a81720eacc2ffb87ff97958431451fd6",
+                "reference": "83446756a81720eacc2ffb87ff97958431451fd6",
+                "shasum": ""
+            },
+            "require": {
+                "simplito/bigint-wrapper-php": "~1.0.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "BN\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Simplito Team",
+                    "email": "s.smyczynski@simplito.com",
+                    "homepage": "https://simplito.com"
+                }
+            ],
+            "description": "Big number implementation compatible with bn.js",
+            "support": {
+                "issues": "https://github.com/simplito/bn-php/issues",
+                "source": "https://github.com/simplito/bn-php/tree/1.1.4"
+            },
+            "time": "2024-01-10T16:16:59+00:00"
+        },
+        {
+            "name": "simplito/elliptic-php",
+            "version": "1.0.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/simplito/elliptic-php.git",
+                "reference": "be321666781be2be2c89c79c43ffcac834bc8868"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/simplito/elliptic-php/zipball/be321666781be2be2c89c79c43ffcac834bc8868",
+                "reference": "be321666781be2be2c89c79c43ffcac834bc8868",
+                "shasum": ""
+            },
+            "require": {
+                "ext-gmp": "*",
+                "simplito/bn-php": "~1.1.0"
+            },
+            "require-dev": {
+                "phpbench/phpbench": "@dev",
+                "phpunit/phpunit": "*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Elliptic\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Simplito Team",
+                    "email": "s.smyczynski@simplito.com",
+                    "homepage": "https://simplito.com"
+                }
+            ],
+            "description": "Fast elliptic curve cryptography",
+            "homepage": "https://github.com/simplito/elliptic-php",
+            "keywords": [
+                "Curve25519",
+                "ECDSA",
+                "Ed25519",
+                "EdDSA",
+                "cryptography",
+                "curve",
+                "curve25519-weier",
+                "ecc",
+                "ecdh",
+                "elliptic",
+                "nistp192",
+                "nistp224",
+                "nistp256",
+                "nistp384",
+                "nistp521",
+                "secp256k1"
+            ],
+            "support": {
+                "issues": "https://github.com/simplito/elliptic-php/issues",
+                "source": "https://github.com/simplito/elliptic-php/tree/1.0.12"
+            },
+            "time": "2024-01-09T14:57:04+00:00"
+        },
+        {
+            "name": "stephenhill/base58",
+            "version": "v1.1.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/stephen-hill/base58php.git",
+                "reference": "bd9fc19c788160a2f85ba0a19cd800eaf5ba5e99"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/stephen-hill/base58php/zipball/bd9fc19c788160a2f85ba0a19cd800eaf5ba5e99",
+                "reference": "bd9fc19c788160a2f85ba0a19cd800eaf5ba5e99",
+                "shasum": ""
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1",
+                "phpunit/phpunit": "4.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "StephenHill\\": "src/",
+                    "StephenHill\\Benchmarks\\": "benchmarks/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Stephen Hill",
+                    "email": "stephen@gatekiller.co.uk"
+                }
+            ],
+            "description": "Base58 Encoding and Decoding Library for PHP",
+            "support": {
+                "issues": "https://github.com/stephen-hill/base58php/issues",
+                "source": "https://github.com/stephen-hill/base58php/tree/v1.1.5"
+            },
+            "time": "2019-08-22T16:51:59+00:00"
+        },
+        {
+            "name": "symfony/clock",
+            "version": "v7.4.21",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/clock.git",
+                "reference": "07437f612dca89c1894c1db04491002b1e798f03"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/clock/zipball/07437f612dca89c1894c1db04491002b1e798f03",
+                "reference": "07437f612dca89c1894c1db04491002b1e798f03",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "psr/clock": "^1.0",
+                "symfony/polyfill-php83": "^1.28"
+            },
+            "provide": {
+                "psr/clock-implementation": "1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "Resources/now.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\Clock\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Decouples applications from the system clock",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "clock",
+                "psr20",
+                "time"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/clock/tree/v7.4.21"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-12T14:31:36+00:00"
+        },
+        {
+            "name": "symfony/console",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/console.git",
+                "reference": "8012f842110b414fd1cc641adb0ed8423353b0bb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/console/zipball/8012f842110b414fd1cc641adb0ed8423353b0bb",
+                "reference": "8012f842110b414fd1cc641adb0ed8423353b0bb",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/service-contracts": "^2.5|^3",
+                "symfony/string": "^7.2|^8.0"
+            },
+            "conflict": {
+                "symfony/dependency-injection": "<6.4",
+                "symfony/dotenv": "<6.4",
+                "symfony/event-dispatcher": "<6.4",
+                "symfony/lock": "<6.4",
+                "symfony/process": "<6.4"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0|2.0|3.0"
+            },
+            "require-dev": {
+                "psr/log": "^1|^2|^3",
+                "symfony/config": "^6.4|^7.0|^8.0",
+                "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+                "symfony/event-dispatcher": "^6.4|^7.0|^8.0",
+                "symfony/http-foundation": "^6.4|^7.0|^8.0",
+                "symfony/http-kernel": "^6.4|^7.0|^8.0",
+                "symfony/lock": "^6.4|^7.0|^8.0",
+                "symfony/messenger": "^6.4|^7.0|^8.0",
+                "symfony/process": "^6.4|^7.0|^8.0",
+                "symfony/stopwatch": "^6.4|^7.0|^8.0",
+                "symfony/var-dumper": "^6.4|^7.0|^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Console\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Eases the creation of beautiful and testable command line interfaces",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "cli",
+                "command-line",
+                "console",
+                "terminal"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/console/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-28T11:08:27+00:00"
+        },
+        {
+            "name": "symfony/css-selector",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/css-selector.git",
+                "reference": "4cdbb4a575111d771791339a9d5231155918f699"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/4cdbb4a575111d771791339a9d5231155918f699",
+                "reference": "4cdbb4a575111d771791339a9d5231155918f699",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\CssSelector\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Jean-François Simon",
+                    "email": "jeanfrancois.simon@sensiolabs.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Converts CSS selectors to XPath expressions",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/css-selector/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-18T14:39:27+00:00"
+        },
+        {
+            "name": "symfony/deprecation-contracts",
+            "version": "v3.7.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/deprecation-contracts.git",
+                "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
+                "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.7-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "function.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A generic function and convention to trigger deprecation notices",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-06-05T06:23:12+00:00"
+        },
+        {
+            "name": "symfony/error-handler",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/error-handler.git",
+                "reference": "545852cfe9c4979c82389155b4d8d05f8864e4f1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/error-handler/zipball/545852cfe9c4979c82389155b4d8d05f8864e4f1",
+                "reference": "545852cfe9c4979c82389155b4d8d05f8864e4f1",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "psr/log": "^1|^2|^3",
+                "symfony/polyfill-php85": "^1.32",
+                "symfony/var-dumper": "^6.4|^7.0|^8.0"
+            },
+            "conflict": {
+                "symfony/deprecation-contracts": "<2.5",
+                "symfony/http-kernel": "<6.4"
+            },
+            "require-dev": {
+                "symfony/console": "^6.4|^7.0|^8.0",
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/http-kernel": "^6.4|^7.0|^8.0",
+                "symfony/serializer": "^6.4|^7.0|^8.0",
+                "symfony/webpack-encore-bundle": "^1.0|^2.0"
+            },
+            "bin": [
+                "Resources/bin/patch-type-declarations"
+            ],
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\ErrorHandler\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides tools to manage errors and ease debugging PHP code",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/error-handler/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-25T14:40:16+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher",
+            "version": "v7.4.17",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher.git",
+                "reference": "d269974ee93c61d03620ffee358355bfdb471d66"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d269974ee93c61d03620ffee358355bfdb471d66",
+                "reference": "d269974ee93c61d03620ffee358355bfdb471d66",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "symfony/event-dispatcher-contracts": "^2.5|^3"
+            },
+            "conflict": {
+                "symfony/dependency-injection": "<6.4",
+                "symfony/service-contracts": "<2.5"
+            },
+            "provide": {
+                "psr/event-dispatcher-implementation": "1.0",
+                "symfony/event-dispatcher-implementation": "2.0|3.0"
+            },
+            "require-dev": {
+                "psr/log": "^1|^2|^3",
+                "symfony/config": "^6.4|^7.0|^8.0",
+                "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+                "symfony/error-handler": "^6.4|^7.0|^8.0",
+                "symfony/expression-language": "^6.4|^7.0|^8.0",
+                "symfony/framework-bundle": "^6.4|^7.0|^8.0",
+                "symfony/http-foundation": "^6.4|^7.0|^8.0",
+                "symfony/service-contracts": "^2.5|^3",
+                "symfony/stopwatch": "^6.4|^7.0|^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\EventDispatcher\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.17"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-08-21T17:40:08+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher-contracts",
+            "version": "v3.7.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+                "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e",
+                "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "psr/event-dispatcher": "^1"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\EventDispatcher\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to dispatching event",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-06-05T06:23:12+00:00"
+        },
+        {
+            "name": "symfony/finder",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "e13420a8e0a76bdde1e7be09f923e62ad13739f2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/e13420a8e0a76bdde1e7be09f923e62ad13739f2",
+                "reference": "e13420a8e0a76bdde1e7be09f923e62ad13739f2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2"
+            },
+            "require-dev": {
+                "symfony/filesystem": "^6.4|^7.0|^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Finds files and directories via an intuitive fluent interface",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/finder/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-19T16:56:54+00:00"
+        },
+        {
+            "name": "symfony/http-foundation",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/http-foundation.git",
+                "reference": "63eda1d0c4de229d6b3e825709e49dd5c149699a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/63eda1d0c4de229d6b3e825709e49dd5c149699a",
+                "reference": "63eda1d0c4de229d6b3e825709e49dd5c149699a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/polyfill-mbstring": "^1.1"
+            },
+            "conflict": {
+                "doctrine/dbal": "<3.6",
+                "symfony/cache": "<6.4.12|>=7.0,<7.1.5"
+            },
+            "require-dev": {
+                "doctrine/dbal": "^3.6|^4",
+                "predis/predis": "^1.1|^2.0",
+                "symfony/cache": "^6.4.12|^7.1.5|^8.0",
+                "symfony/clock": "^6.4|^7.0|^8.0",
+                "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+                "symfony/expression-language": "^6.4|^7.0|^8.0",
+                "symfony/http-kernel": "^6.4|^7.0|^8.0",
+                "symfony/mime": "^6.4|^7.0|^8.0",
+                "symfony/rate-limiter": "^6.4|^7.0|^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\HttpFoundation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Defines an object-oriented layer for the HTTP specification",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/http-foundation/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-27T13:26:03+00:00"
+        },
+        {
+            "name": "symfony/http-kernel",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/http-kernel.git",
+                "reference": "87e727b33983238d3f2ea50f9f62265499ca1a6d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/87e727b33983238d3f2ea50f9f62265499ca1a6d",
+                "reference": "87e727b33983238d3f2ea50f9f62265499ca1a6d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "psr/log": "^1|^2|^3",
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/error-handler": "^6.4|^7.0|^8.0",
+                "symfony/event-dispatcher": "^7.3|^8.0",
+                "symfony/http-foundation": "^7.4|^8.0",
+                "symfony/polyfill-ctype": "^1.8"
+            },
+            "conflict": {
+                "symfony/browser-kit": "<6.4",
+                "symfony/cache": "<6.4",
+                "symfony/config": "<6.4",
+                "symfony/console": "<6.4",
+                "symfony/dependency-injection": "<6.4",
+                "symfony/doctrine-bridge": "<6.4",
+                "symfony/flex": "<2.10",
+                "symfony/form": "<6.4",
+                "symfony/http-client": "<6.4",
+                "symfony/http-client-contracts": "<2.5",
+                "symfony/mailer": "<6.4",
+                "symfony/messenger": "<6.4",
+                "symfony/translation": "<6.4",
+                "symfony/translation-contracts": "<2.5",
+                "symfony/twig-bridge": "<6.4",
+                "symfony/validator": "<6.4",
+                "symfony/var-dumper": "<6.4",
+                "twig/twig": "<3.12"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0|2.0|3.0"
+            },
+            "require-dev": {
+                "psr/cache": "^1.0|^2.0|^3.0",
+                "symfony/browser-kit": "^6.4|^7.0|^8.0",
+                "symfony/clock": "^6.4|^7.0|^8.0",
+                "symfony/config": "^6.4|^7.0|^8.0",
+                "symfony/console": "^6.4|^7.0|^8.0",
+                "symfony/css-selector": "^6.4|^7.0|^8.0",
+                "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0",
+                "symfony/dom-crawler": "^6.4|^7.0|^8.0",
+                "symfony/expression-language": "^6.4|^7.0|^8.0",
+                "symfony/finder": "^6.4|^7.0|^8.0",
+                "symfony/http-client-contracts": "^2.5|^3",
+                "symfony/process": "^6.4|^7.0|^8.0",
+                "symfony/property-access": "^7.1|^8.0",
+                "symfony/routing": "^6.4|^7.0|^8.0",
+                "symfony/serializer": "^7.1|^8.0",
+                "symfony/stopwatch": "^6.4|^7.0|^8.0",
+                "symfony/translation": "^6.4|^7.0|^8.0",
+                "symfony/translation-contracts": "^2.5|^3",
+                "symfony/uid": "^6.4|^7.0|^8.0",
+                "symfony/validator": "^6.4|^7.0|^8.0",
+                "symfony/var-dumper": "^6.4|^7.0|^8.0",
+                "symfony/var-exporter": "^6.4|^7.0|^8.0",
+                "twig/twig": "^3.12|^4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\HttpKernel\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides a structured process for converting a Request into a Response",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/http-kernel/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-28T18:18:28+00:00"
+        },
+        {
+            "name": "symfony/mailer",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/mailer.git",
+                "reference": "1aa1e2ee7515826bb17b0ad0d2f16962fcaa7859"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/mailer/zipball/1aa1e2ee7515826bb17b0ad0d2f16962fcaa7859",
+                "reference": "1aa1e2ee7515826bb17b0ad0d2f16962fcaa7859",
+                "shasum": ""
+            },
+            "require": {
+                "egulias/email-validator": "^2.1.10|^3|^4",
+                "php": ">=8.2",
+                "psr/event-dispatcher": "^1",
+                "psr/log": "^1|^2|^3",
+                "symfony/event-dispatcher": "^6.4|^7.0|^8.0",
+                "symfony/mime": "^7.2|^8.0",
+                "symfony/service-contracts": "^2.5|^3"
+            },
+            "conflict": {
+                "symfony/http-client-contracts": "<2.5",
+                "symfony/http-kernel": "<6.4",
+                "symfony/messenger": "<6.4",
+                "symfony/mime": "<6.4",
+                "symfony/twig-bridge": "<6.4"
+            },
+            "require-dev": {
+                "symfony/console": "^6.4|^7.0|^8.0",
+                "symfony/http-client": "^6.4|^7.0|^8.0",
+                "symfony/messenger": "^6.4|^7.0|^8.0",
+                "symfony/twig-bridge": "^6.4|^7.0|^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Mailer\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Helps sending emails",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/mailer/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-23T16:51:52+00:00"
+        },
+        {
+            "name": "symfony/mime",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/mime.git",
+                "reference": "94a4c017435bc515f5ac80421a2c555fb9ca3676"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/mime/zipball/94a4c017435bc515f5ac80421a2c555fb9ca3676",
+                "reference": "94a4c017435bc515f5ac80421a2c555fb9ca3676",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/polyfill-intl-idn": "^1.10",
+                "symfony/polyfill-mbstring": "^1.0"
+            },
+            "conflict": {
+                "egulias/email-validator": "~3.0.0",
+                "phpdocumentor/reflection-docblock": "<5.2|>=7",
+                "phpdocumentor/type-resolver": "<1.5.1",
+                "symfony/mailer": "<6.4",
+                "symfony/serializer": "<6.4.3|>7.0,<7.0.3"
+            },
+            "require-dev": {
+                "egulias/email-validator": "^2.1.10|^3.1|^4",
+                "league/html-to-markdown": "^5.0",
+                "phpdocumentor/reflection-docblock": "^5.2|^6.0",
+                "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+                "symfony/process": "^6.4|^7.0|^8.0",
+                "symfony/property-access": "^6.4|^7.0|^8.0",
+                "symfony/property-info": "^6.4|^7.0|^8.0",
+                "symfony/serializer": "^6.4.44|^7.4.17|^8.1.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Mime\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Allows manipulating MIME messages",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "mime",
+                "mime-type"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/mime/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-28T17:57:58+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.37.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "141046a8f9477948ff284fa65be2095baafb94f2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
+                "reference": "141046a8f9477948ff284fa65be2095baafb94f2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "provide": {
+                "ext-ctype": "*"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-04-10T16:19:22+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-grapheme",
+            "version": "v1.43.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+                "reference": "5a8f33c87aeecffce24e721b5064fab05f4becf8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5a8f33c87aeecffce24e721b5064fab05f4becf8",
+                "reference": "5a8f33c87aeecffce24e721b5064fab05f4becf8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's grapheme_* functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "grapheme",
+                "intl",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.3"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-15T09:18:53+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-idn",
+            "version": "v1.43.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-idn.git",
+                "reference": "533d68cf99c8f8ee2e8893c59ed59192e92cfd74"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/533d68cf99c8f8ee2e8893c59ed59192e92cfd74",
+                "reference": "533d68cf99c8f8ee2e8893c59ed59192e92cfd74",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2",
+                "symfony/polyfill-intl-normalizer": "^1.10"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Idn\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Laurent Bassin",
+                    "email": "laurent@bassin.info"
+                },
+                {
+                    "name": "Trevor Rowbotham",
+                    "email": "trevor.rowbotham@pm.me"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "idn",
+                "intl",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.43.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-09-23T14:40:04+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-normalizer",
+            "version": "v1.43.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+                "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b",
+                "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's Normalizer class and related functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "intl",
+                "normalizer",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-09-23T22:02:09+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.43.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/122889ed24dc000165bcbfd77ec412f15cbdfc6a",
+                "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a",
+                "shasum": ""
+            },
+            "require": {
+                "ext-iconv": "*",
+                "php": ">=7.2"
+            },
+            "provide": {
+                "ext-mbstring": "*"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.3"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-28T09:35:49+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.43.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74",
+                "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-09-25T15:50:05+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php83",
+            "version": "v1.43.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php83.git",
+                "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/80ccff923a8d61f73ebe9da0aa94e25232f004ce",
+                "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php83\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php83/tree/v1.43.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-09-25T15:50:05+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php84",
+            "version": "v1.43.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php84.git",
+                "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015",
+                "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php84\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-09-23T22:01:09+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php85",
+            "version": "v1.43.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php85.git",
+                "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/86a42cbcc07adc331481a5ab310a4024bfc227f6",
+                "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php85\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php85/tree/v1.43.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-09-25T15:46:47+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php86",
+            "version": "v1.43.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php86.git",
+                "reference": "8840df45b24f33a49dc419fa18dc0057231ae1d8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/8840df45b24f33a49dc419fa18dc0057231ae1d8",
+                "reference": "8840df45b24f33a49dc419fa18dc0057231ae1d8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php86\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php86/tree/v1.43.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-09-25T15:46:47+00:00"
+        },
+        {
+            "name": "symfony/polyfill-time",
+            "version": "v1.42.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-time.git",
+                "reference": "ac9f1ed802dd58f37e720388130c088434aa09fc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-time/zipball/ac9f1ed802dd58f37e720388130c088434aa09fc",
+                "reference": "ac9f1ed802dd58f37e720388130c088434aa09fc",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Time\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ignace Philippe Nyamagana Butera",
+                    "email": "nyamsprod@gmail.com"
+                },
+                {
+                    "name": "Tim Düsterhus",
+                    "email": "tim@tideways-gmbh.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Time API added to PHP 8.6",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "duration",
+                "polyfill",
+                "portable",
+                "shim",
+                "time"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-time/tree/v1.42.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-08-07T13:08:13+00:00"
+        },
+        {
+            "name": "symfony/polyfill-uuid",
+            "version": "v1.43.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-uuid.git",
+                "reference": "b23ff8245cc5c4479ea2be3d0132d2e6dd433993"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/b23ff8245cc5c4479ea2be3d0132d2e6dd433993",
+                "reference": "b23ff8245cc5c4479ea2be3d0132d2e6dd433993",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "provide": {
+                "ext-uuid": "*"
+            },
+            "suggest": {
+                "ext-uuid": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Uuid\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Grégoire Pineau",
+                    "email": "lyrixx@lyrixx.info"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for uuid functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "uuid"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-uuid/tree/v1.43.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-09-24T05:20:52+00:00"
+        },
+        {
+            "name": "symfony/process",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/process.git",
+                "reference": "a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/process/zipball/a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a",
+                "reference": "a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Process\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Executes commands in sub-processes",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/process/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-25T14:40:16+00:00"
+        },
+        {
+            "name": "symfony/routing",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/routing.git",
+                "reference": "f79ed6fbf60d9e32d358054f1915295f3b8a6f7a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/routing/zipball/f79ed6fbf60d9e32d358054f1915295f3b8a6f7a",
+                "reference": "f79ed6fbf60d9e32d358054f1915295f3b8a6f7a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "symfony/deprecation-contracts": "^2.5|^3"
+            },
+            "conflict": {
+                "symfony/config": "<6.4",
+                "symfony/dependency-injection": "<6.4",
+                "symfony/yaml": "<6.4"
+            },
+            "require-dev": {
+                "psr/log": "^1|^2|^3",
+                "symfony/config": "^6.4|^7.0|^8.0",
+                "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+                "symfony/expression-language": "^6.4|^7.0|^8.0",
+                "symfony/http-foundation": "^6.4|^7.0|^8.0",
+                "symfony/yaml": "^6.4|^7.0|^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Routing\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Maps an HTTP request to a set of configuration variables",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "router",
+                "routing",
+                "uri",
+                "url"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/routing/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-26T21:52:50+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v3.7.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257",
+                "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "psr/container": "^1.1|^2.0",
+                "symfony/deprecation-contracts": "^2.5|^3"
+            },
+            "conflict": {
+                "ext-psr": "<1.1|>=2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Test/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/service-contracts/tree/v3.7.3"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-07-27T15:39:01+00:00"
+        },
+        {
+            "name": "symfony/string",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/string.git",
+                "reference": "8dff7da3a56638f1d189c5368183294102de4ced"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/string/zipball/8dff7da3a56638f1d189c5368183294102de4ced",
+                "reference": "8dff7da3a56638f1d189c5368183294102de4ced",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "symfony/deprecation-contracts": "^2.5|^3.0",
+                "symfony/polyfill-ctype": "~1.8",
+                "symfony/polyfill-intl-grapheme": "~1.33",
+                "symfony/polyfill-intl-normalizer": "~1.0",
+                "symfony/polyfill-mbstring": "~1.0"
+            },
+            "conflict": {
+                "symfony/translation-contracts": "<2.5"
+            },
+            "require-dev": {
+                "symfony/emoji": "^7.1|^8.0",
+                "symfony/http-client": "^6.4|^7.0|^8.0",
+                "symfony/intl": "^6.4|^7.0|^8.0",
+                "symfony/translation-contracts": "^2.5|^3.0",
+                "symfony/var-exporter": "^6.4|^7.0|^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "Resources/functions.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\String\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "grapheme",
+                "i18n",
+                "string",
+                "unicode",
+                "utf-8",
+                "utf8"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/string/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-23T16:51:52+00:00"
+        },
+        {
+            "name": "symfony/translation",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/translation.git",
+                "reference": "a892cd1807bfaf0a10690359c47fdf95b162ef22"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/a892cd1807bfaf0a10690359c47fdf95b162ef22",
+                "reference": "a892cd1807bfaf0a10690359c47fdf95b162ef22",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/translation-contracts": "^2.5.3|^3.3"
+            },
+            "conflict": {
+                "nikic/php-parser": "<5.0",
+                "symfony/config": "<6.4",
+                "symfony/console": "<6.4",
+                "symfony/dependency-injection": "<6.4",
+                "symfony/http-client-contracts": "<2.5",
+                "symfony/http-kernel": "<6.4",
+                "symfony/service-contracts": "<2.5",
+                "symfony/twig-bundle": "<6.4",
+                "symfony/yaml": "<6.4"
+            },
+            "provide": {
+                "symfony/translation-implementation": "2.3|3.0"
+            },
+            "require-dev": {
+                "nikic/php-parser": "^5.0",
+                "psr/log": "^1|^2|^3",
+                "symfony/config": "^6.4|^7.0|^8.0",
+                "symfony/console": "^6.4|^7.0|^8.0",
+                "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+                "symfony/finder": "^6.4|^7.0|^8.0",
+                "symfony/http-client-contracts": "^2.5|^3.0",
+                "symfony/http-kernel": "^6.4|^7.0|^8.0",
+                "symfony/intl": "^6.4|^7.0|^8.0",
+                "symfony/polyfill-intl-icu": "^1.21",
+                "symfony/routing": "^6.4|^7.0|^8.0",
+                "symfony/service-contracts": "^2.5|^3",
+                "symfony/yaml": "^6.4|^7.0|^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "Resources/functions.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\Translation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides tools to internationalize your application",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/translation/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-25T14:40:16+00:00"
+        },
+        {
+            "name": "symfony/translation-contracts",
+            "version": "v3.7.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/translation-contracts.git",
+                "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621",
+                "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Translation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Test/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to translation",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-06-05T06:23:12+00:00"
+        },
+        {
+            "name": "symfony/uid",
+            "version": "v7.4.21",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/uid.git",
+                "reference": "3e706a245b5343ea93be9cafe84525bfb54e60fc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/uid/zipball/3e706a245b5343ea93be9cafe84525bfb54e60fc",
+                "reference": "3e706a245b5343ea93be9cafe84525bfb54e60fc",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "symfony/polyfill-uuid": "^1.15"
+            },
+            "require-dev": {
+                "symfony/console": "^6.4|^7.0|^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Uid\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Grégoire Pineau",
+                    "email": "lyrixx@lyrixx.info"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides an object-oriented API to generate and represent UIDs",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "UID",
+                "ulid",
+                "uuid"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/uid/tree/v7.4.21"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-12T14:31:36+00:00"
+        },
+        {
+            "name": "symfony/var-dumper",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-dumper.git",
+                "reference": "6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618",
+                "reference": "6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/polyfill-mbstring": "~1.0"
+            },
+            "conflict": {
+                "symfony/console": "<6.4"
+            },
+            "require-dev": {
+                "symfony/console": "^6.4|^7.0|^8.0",
+                "symfony/http-kernel": "^6.4|^7.0|^8.0",
+                "symfony/process": "^6.4|^7.0|^8.0",
+                "symfony/uid": "^6.4|^7.0|^8.0",
+                "twig/twig": "^3.12|^4.0"
+            },
+            "bin": [
+                "Resources/bin/var-dump-server"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "Resources/functions/dump.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\VarDumper\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides mechanisms for walking through any arbitrary PHP variable",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "debug",
+                "dump"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/var-dumper/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-25T21:04:04+00:00"
+        },
+        {
+            "name": "tijsverkoyen/css-to-inline-styles",
+            "version": "v2.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git",
+                "reference": "f0292ccf0ec75843d65027214426b6b163b48b41"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41",
+                "reference": "f0292ccf0ec75843d65027214426b6b163b48b41",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-libxml": "*",
+                "php": "^7.4 || ^8.0",
+                "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^2.0",
+                "phpstan/phpstan-phpunit": "^2.0",
+                "phpunit/phpunit": "^8.5.21 || ^9.5.10"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "TijsVerkoyen\\CssToInlineStyles\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Tijs Verkoyen",
+                    "email": "css_to_inline_styles@verkoyen.eu",
+                    "role": "Developer"
+                }
+            ],
+            "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.",
+            "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
+            "support": {
+                "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues",
+                "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0"
+            },
+            "time": "2025-12-02T11:56:42+00:00"
+        },
+        {
+            "name": "vlucas/phpdotenv",
+            "version": "v5.8.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpdotenv/phpdotenv.git",
+                "reference": "96149103e62ac67e915f6d2ef59b72dee2e011e0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpdotenv/phpdotenv/zipball/96149103e62ac67e915f6d2ef59b72dee2e011e0",
+                "reference": "96149103e62ac67e915f6d2ef59b72dee2e011e0",
+                "shasum": ""
+            },
+            "require": {
+                "ext-pcre": "*",
+                "graham-campbell/result-type": "^1.2",
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.10",
+                "symfony/polyfill-ctype": "^1.26",
+                "symfony/polyfill-mbstring": "^1.26",
+                "symfony/polyfill-php80": "^1.26"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-filter": "*",
+                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+            },
+            "suggest": {
+                "ext-filter": "Required to use the boolean validator."
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                },
+                "branch-alias": {
+                    "dev-master": "5.8-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Dotenv\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Vance Lucas",
+                    "email": "vance@vancelucas.com",
+                    "homepage": "https://github.com/vlucas"
+                }
+            ],
+            "description": "Loads environment variables from `.env` to `$_ENV` and `$_SERVER` automagically, and optionally to `getenv()`.",
+            "keywords": [
+                "dotenv",
+                "env",
+                "environment"
+            ],
+            "support": {
+                "issues": "https://github.com/phpdotenv/phpdotenv/issues",
+                "source": "https://github.com/phpdotenv/phpdotenv/tree/v5.8.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-25T16:26:19+00:00"
+        },
+        {
+            "name": "voku/portable-ascii",
+            "version": "2.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/voku/portable-ascii.git",
+                "reference": "8e1051fe39379367aecf014f41744ce7539a856f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f",
+                "reference": "8e1051fe39379367aecf014f41744ce7539a856f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5"
+            },
+            "suggest": {
+                "ext-intl": "Use Intl for transliterator_transliterate() support"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "voku\\": "src/voku/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Lars Moelleken",
+                    "homepage": "https://www.moelleken.org/"
+                }
+            ],
+            "description": "Portable ASCII library - performance optimized (ascii) string functions for php.",
+            "homepage": "https://github.com/voku/portable-ascii",
+            "keywords": [
+                "ascii",
+                "clean",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/voku/portable-ascii/issues",
+                "source": "https://github.com/voku/portable-ascii/tree/2.1.1"
+            },
+            "funding": [
+                {
+                    "url": "https://www.paypal.me/moelleken",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/voku",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/portable-ascii",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://www.patreon.com/voku",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-04-26T05:33:54+00:00"
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "fakerphp/faker",
+            "version": "v1.24.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/FakerPHP/Faker.git",
+                "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5",
+                "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.4 || ^8.0",
+                "psr/container": "^1.0 || ^2.0",
+                "symfony/deprecation-contracts": "^2.2 || ^3.0"
+            },
+            "conflict": {
+                "fzaninotto/faker": "*"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.4.1",
+                "doctrine/persistence": "^1.3 || ^2.0",
+                "ext-intl": "*",
+                "phpunit/phpunit": "^9.5.26",
+                "symfony/phpunit-bridge": "^5.4.16"
+            },
+            "suggest": {
+                "doctrine/orm": "Required to use Faker\\ORM\\Doctrine",
+                "ext-curl": "Required by Faker\\Provider\\Image to download images.",
+                "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.",
+                "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.",
+                "ext-mbstring": "Required for multibyte Unicode string functionality."
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Faker\\": "src/Faker/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "François Zaninotto"
+                }
+            ],
+            "description": "Faker is a PHP library that generates fake data for you.",
+            "keywords": [
+                "data",
+                "faker",
+                "fixtures"
+            ],
+            "support": {
+                "issues": "https://github.com/FakerPHP/Faker/issues",
+                "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1"
+            },
+            "time": "2024-11-21T13:46:39+00:00"
+        },
+        {
+            "name": "filp/whoops",
+            "version": "2.18.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/filp/whoops.git",
+                "reference": "50670ba239926c50f2073e868f6672f775cd57ec"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/filp/whoops/zipball/50670ba239926c50f2073e868f6672f775cd57ec",
+                "reference": "50670ba239926c50f2073e868f6672f775cd57ec",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0",
+                "psr/log": "^1.0.1 || ^2.0 || ^3.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.0",
+                "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3 || ^10.5.58",
+                "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0"
+            },
+            "suggest": {
+                "symfony/var-dumper": "Pretty print complex values better with var-dumper available",
+                "whoops/soap": "Formats errors as SOAP responses"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Whoops\\": "src/Whoops/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Filipe Dobreira",
+                    "homepage": "https://github.com/filp",
+                    "role": "Developer"
+                }
+            ],
+            "description": "php error handling for cool kids",
+            "homepage": "https://filp.github.io/whoops/",
+            "keywords": [
+                "error",
+                "exception",
+                "handling",
+                "library",
+                "throwable",
+                "whoops"
+            ],
+            "support": {
+                "issues": "https://github.com/filp/whoops/issues",
+                "source": "https://github.com/filp/whoops/tree/2.18.7"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/denis-sokolov",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-10-18T13:17:38+00:00"
+        },
+        {
+            "name": "hamcrest/hamcrest-php",
+            "version": "v3.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/hamcrest/hamcrest-php.git",
+                "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b61cd040da1a4925bc90a51c074f5297e7c0fa52",
+                "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-dom": "*",
+                "php": "^7.4|^8.0"
+            },
+            "replace": {
+                "cordoval/hamcrest-php": "*",
+                "davedevelopment/hamcrest-php": "*",
+                "kodova/hamcrest-php": "*"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^2.1",
+                "phpstan/phpstan-phpunit": "^2.0",
+                "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0",
+                "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "hamcrest"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "description": "This is the PHP port of Hamcrest Matchers",
+            "keywords": [
+                "test"
+            ],
+            "support": {
+                "issues": "https://github.com/hamcrest/hamcrest-php/issues",
+                "source": "https://github.com/hamcrest/hamcrest-php/tree/v3.0.0"
+            },
+            "time": "2026-03-17T11:56:53+00:00"
+        },
+        {
+            "name": "laravel/pint",
+            "version": "v1.32.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/pint.git",
+                "reference": "006bd7825eb01daf92959d060b89cd9514fe4253"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/pint/zipball/006bd7825eb01daf92959d060b89cd9514fe4253",
+                "reference": "006bd7825eb01daf92959d060b89cd9514fe4253",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "ext-tokenizer": "*",
+                "ext-xml": "*",
+                "php": "^8.3.0"
+            },
+            "require-dev": {
+                "composer/semver": "^3.4.4",
+                "friendsofphp/php-cs-fixer": "^3.95.25",
+                "illuminate/view": "^13.31.0",
+                "larastan/larastan": "^3.11.0",
+                "laravel-zero/framework": "^13.0.0",
+                "laravel/agent-detector": "^2.0.2",
+                "laravel/prompts": "^0.3.24",
+                "mockery/mockery": "^1.6.15",
+                "nunomaduro/termwind": "^2.4.0",
+                "pestphp/pest": "^4.7.8"
+            },
+            "bin": [
+                "builds/pint"
+            ],
+            "type": "project",
+            "autoload": {
+                "psr-4": {
+                    "App\\": "app/",
+                    "Database\\Seeders\\": "database/seeders/",
+                    "Database\\Factories\\": "database/factories/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nuno Maduro",
+                    "email": "enunomaduro@gmail.com"
+                }
+            ],
+            "description": "An opinionated code formatter for PHP.",
+            "homepage": "https://laravel.com",
+            "keywords": [
+                "dev",
+                "format",
+                "formatter",
+                "lint",
+                "linter",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/laravel/pint/issues",
+                "source": "https://github.com/laravel/pint"
+            },
+            "time": "2026-09-10T10:47:55+00:00"
+        },
+        {
+            "name": "laravel/sail",
+            "version": "v1.69.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/sail.git",
+                "reference": "3587ac9d68e8f2eaf36bd9fb003a4555861dca9d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/sail/zipball/3587ac9d68e8f2eaf36bd9fb003a4555861dca9d",
+                "reference": "3587ac9d68e8f2eaf36bd9fb003a4555861dca9d",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0|^13.0",
+                "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0|^13.0",
+                "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0|^13.0",
+                "php": "^8.0",
+                "symfony/console": "^6.0|^7.0|^8.0",
+                "symfony/yaml": "^6.0|^7.0|^8.0"
+            },
+            "require-dev": {
+                "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0|^11.0",
+                "phpstan/phpstan": "^2.0"
+            },
+            "bin": [
+                "bin/sail"
+            ],
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Laravel\\Sail\\SailServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Laravel\\Sail\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "Docker files for running a basic Laravel application.",
+            "keywords": [
+                "docker",
+                "laravel"
+            ],
+            "support": {
+                "issues": "https://github.com/laravel/sail/issues",
+                "source": "https://github.com/laravel/sail"
+            },
+            "time": "2026-10-08T20:06:52+00:00"
+        },
+        {
+            "name": "mockery/mockery",
+            "version": "1.6.15",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mockery/mockery.git",
+                "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mockery/mockery/zipball/967a801bd188989a5669bd280f252d51c0fdc9ee",
+                "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee",
+                "shasum": ""
+            },
+            "require": {
+                "hamcrest/hamcrest-php": "^2.0 || ^3.0",
+                "php": ">=7.3"
+            },
+            "conflict": {
+                "phpunit/phpunit": "<8.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.6.36",
+                "symplify/easy-coding-standard": "^13.2.17"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "library/helpers.php",
+                    "library/Mockery.php"
+                ],
+                "psr-4": {
+                    "Mockery\\": "library/Mockery"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Pádraic Brady",
+                    "email": "padraic.brady@gmail.com",
+                    "homepage": "https://github.com/padraic",
+                    "role": "Author"
+                },
+                {
+                    "name": "Dave Marshall",
+                    "email": "dave.marshall@atstsolutions.co.uk",
+                    "homepage": "https://davedevelopment.co.uk",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Nathanael Esayeas",
+                    "email": "nathanael.esayeas@protonmail.com",
+                    "homepage": "https://github.com/ghostwriter",
+                    "role": "Lead Developer"
+                }
+            ],
+            "description": "Mockery is a simple yet flexible PHP mock object framework",
+            "homepage": "https://github.com/mockery/mockery",
+            "keywords": [
+                "BDD",
+                "TDD",
+                "library",
+                "mock",
+                "mock objects",
+                "mockery",
+                "stub",
+                "test",
+                "test double",
+                "testing"
+            ],
+            "support": {
+                "docs": "https://docs.mockery.io/",
+                "issues": "https://github.com/mockery/mockery/issues",
+                "rss": "https://github.com/mockery/mockery/releases.atom",
+                "security": "https://github.com/mockery/mockery/security/advisories",
+                "source": "https://github.com/mockery/mockery"
+            },
+            "time": "2026-08-19T19:37:52+00:00"
+        },
+        {
+            "name": "myclabs/deep-copy",
+            "version": "1.14.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/DeepCopy.git",
+                "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae",
+                "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.0"
+            },
+            "conflict": {
+                "doctrine/collections": "<1.6.8",
+                "doctrine/common": "<2.13.3 || >=3 <3.2.2"
+            },
+            "require-dev": {
+                "doctrine/collections": "^1.6.8",
+                "doctrine/common": "^2.13.3 || ^3.2.2",
+                "phpspec/prophecy": "^1.10",
+                "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/DeepCopy/deep_copy.php"
+                ],
+                "psr-4": {
+                    "DeepCopy\\": "src/DeepCopy/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Create deep copies (clones) of your objects",
+            "keywords": [
+                "clone",
+                "copy",
+                "duplicate",
+                "object",
+                "object graph"
+            ],
+            "support": {
+                "issues": "https://github.com/myclabs/DeepCopy/issues",
+                "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/mnapoli",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-08-11T10:17:44+00:00"
+        },
+        {
+            "name": "nunomaduro/collision",
+            "version": "v8.9.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nunomaduro/collision.git",
+                "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nunomaduro/collision/zipball/fb53eacd509a1d303858e2d20cfebf2d630254ec",
+                "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec",
+                "shasum": ""
+            },
+            "require": {
+                "filp/whoops": "^2.18.4",
+                "nunomaduro/termwind": "^2.4.0",
+                "php": "^8.2.0",
+                "symfony/console": "^7.4.14 || ^8.1.1"
+            },
+            "conflict": {
+                "laravel/framework": "<11.48.0 || >=14.0.0",
+                "phpunit/phpunit": "<11.5.50 || >=14.0.0"
+            },
+            "require-dev": {
+                "brianium/paratest": "^7.8.5",
+                "larastan/larastan": "^3.10.0",
+                "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.20.0",
+                "laravel/pint": "^1.29.3",
+                "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.3.5",
+                "pestphp/pest": "^3.8.5 || ^4.7.5 || ^5.0.0",
+                "sebastian/environment": "^7.2.1 || ^8.1.2 || ^9.3.2"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider"
+                    ]
+                },
+                "branch-alias": {
+                    "dev-8.x": "8.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "./src/Adapters/Phpunit/Autoload.php"
+                ],
+                "psr-4": {
+                    "NunoMaduro\\Collision\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nuno Maduro",
+                    "email": "enunomaduro@gmail.com"
+                }
+            ],
+            "description": "Cli error handling for console/command-line PHP applications.",
+            "keywords": [
+                "artisan",
+                "cli",
+                "command-line",
+                "console",
+                "dev",
+                "error",
+                "handling",
+                "laravel",
+                "laravel-zero",
+                "php",
+                "symfony"
+            ],
+            "support": {
+                "issues": "https://github.com/nunomaduro/collision/issues",
+                "source": "https://github.com/nunomaduro/collision"
+            },
+            "funding": [
+                {
+                    "url": "https://www.paypal.com/paypalme/enunomaduro",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/nunomaduro",
+                    "type": "github"
+                },
+                {
+                    "url": "https://www.patreon.com/nunomaduro",
+                    "type": "patreon"
+                }
+            ],
+            "time": "2026-07-15T19:09:14+00:00"
+        },
+        {
+            "name": "phar-io/manifest",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phar-io/manifest.git",
+                "reference": "54750ef60c58e43759730615a392c31c80e23176"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+                "reference": "54750ef60c58e43759730615a392c31c80e23176",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-libxml": "*",
+                "ext-phar": "*",
+                "ext-xmlwriter": "*",
+                "phar-io/version": "^3.0.1",
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+            "support": {
+                "issues": "https://github.com/phar-io/manifest/issues",
+                "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/theseer",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-03T12:33:53+00:00"
+        },
+        {
+            "name": "phar-io/version",
+            "version": "3.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phar-io/version.git",
+                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Library for handling version information and constraints",
+            "support": {
+                "issues": "https://github.com/phar-io/version/issues",
+                "source": "https://github.com/phar-io/version/tree/3.2.1"
+            },
+            "time": "2022-02-21T01:04:05+00:00"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "12.5.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "7440368d94738d54f2c1de24dbd3871793bbb0dd"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7440368d94738d54f2c1de24dbd3871793bbb0dd",
+                "reference": "7440368d94738d54f2c1de24dbd3871793bbb0dd",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-libxml": "*",
+                "ext-xmlwriter": "*",
+                "nikic/php-parser": "^5.9.0",
+                "php": ">=8.3",
+                "phpunit/php-text-template": "^5.0.1",
+                "sebastian/complexity": "^5.0",
+                "sebastian/environment": "^8.1.2",
+                "sebastian/lines-of-code": "^4.0.1",
+                "sebastian/version": "^6.0",
+                "theseer/tokenizer": "^2.0.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.5.37"
+            },
+            "suggest": {
+                "ext-pcov": "PHP extension that provides line coverage",
+                "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "12.5.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+                "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.8"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-03T14:03:50+00:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "6.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "a248d1640ab059b075f53a2ef0f9856e864e06b5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a248d1640ab059b075f53a2ef0f9856e864e06b5",
+                "reference": "a248d1640ab059b075f53a2ef0f9856e864e06b5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.5.33"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "6.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+                "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
+                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-08-25T14:40:53+00:00"
+        },
+        {
+            "name": "phpunit/php-invoker",
+            "version": "6.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-invoker.git",
+                "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406",
+                "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "ext-pcntl": "*",
+                "phpunit/phpunit": "^12.0"
+            },
+            "suggest": {
+                "ext-pcntl": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "6.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Invoke callables with a timeout",
+            "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+            "keywords": [
+                "process"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+                "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+                "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-02-07T04:58:58+00:00"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "5.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "babab73b7ef9fae406f2eb3ee11323a61caa63d5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/babab73b7ef9fae406f2eb3ee11323a61caa63d5",
+                "reference": "babab73b7ef9fae406f2eb3ee11323a61caa63d5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.5.37"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "5.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+                "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
+                "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-03T04:51:29+00:00"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "8.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc",
+                "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "8.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+                "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+                "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-02-07T04:59:38+00:00"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "12.5.42",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "1d6dbe748c079e5d2b03bca1be69d0f4cc52a578"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1d6dbe748c079e5d2b03bca1be69d0f4cc52a578",
+                "reference": "1d6dbe748c079e5d2b03bca1be69d0f4cc52a578",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-filter": "*",
+                "ext-json": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-xmlwriter": "*",
+                "myclabs/deep-copy": "^1.14.0",
+                "phar-io/manifest": "^2.0.4",
+                "phar-io/version": "^3.2.1",
+                "php": ">=8.3",
+                "phpunit/php-code-coverage": "^12.5.8",
+                "phpunit/php-file-iterator": "^6.0.2",
+                "phpunit/php-invoker": "^6.0.0",
+                "phpunit/php-text-template": "^5.0.1",
+                "phpunit/php-timer": "^8.0.0",
+                "sebastian/cli-parser": "^4.2.1",
+                "sebastian/comparator": "^7.1.8",
+                "sebastian/diff": "^7.0.1",
+                "sebastian/environment": "^8.1.2",
+                "sebastian/exporter": "^7.0.3",
+                "sebastian/global-state": "^8.0.3",
+                "sebastian/object-enumerator": "^7.0.0",
+                "sebastian/recursion-context": "^7.0.1",
+                "sebastian/type": "^6.0.4",
+                "sebastian/version": "^6.0.1",
+                "staabm/side-effects-detector": "^1.0.5"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "12.5-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/Framework/Assert/Functions.php"
+                ],
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+                "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.42"
+            },
+            "funding": [
+                {
+                    "url": "https://phpunit.de/sponsoring.html",
+                    "type": "other"
+                }
+            ],
+            "time": "2026-10-27T04:15:03+00:00"
+        },
+        {
+            "name": "sebastian/cli-parser",
+            "version": "4.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/cli-parser.git",
+                "reference": "7d05781b13f7dec9043a629a21d086ed74582a15"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/7d05781b13f7dec9043a629a21d086ed74582a15",
+                "reference": "7d05781b13f7dec9043a629a21d086ed74582a15",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.5.25"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "4.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for parsing CLI options",
+            "homepage": "https://github.com/sebastianbergmann/cli-parser",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+                "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+                "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-05-17T05:29:34+00:00"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "7.1.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "7c65c1e79836812819705b473a90c12399542485"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485",
+                "reference": "7c65c1e79836812819705b473a90c12399542485",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-mbstring": "*",
+                "php": ">=8.3",
+                "sebastian/diff": "^7.0",
+                "sebastian/exporter": "^7.0.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.5.25"
+            },
+            "suggest": {
+                "ext-bcmath": "For comparing BcMath\\Number objects"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "7.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "https://github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/comparator/issues",
+                "security": "https://github.com/sebastianbergmann/comparator/security/policy",
+                "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.8"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-05-21T04:45:25+00:00"
+        },
+        {
+            "name": "sebastian/complexity",
+            "version": "5.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/complexity.git",
+                "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb",
+                "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb",
+                "shasum": ""
+            },
+            "require": {
+                "nikic/php-parser": "^5.0",
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "5.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for calculating the complexity of PHP code units",
+            "homepage": "https://github.com/sebastianbergmann/complexity",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/complexity/issues",
+                "security": "https://github.com/sebastianbergmann/complexity/security/policy",
+                "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-02-07T04:55:25+00:00"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "7.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6",
+                "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.5.33",
+                "symfony/process": "^7.4.17"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "7.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff",
+                "udiff",
+                "unidiff",
+                "unified diff"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/diff/issues",
+                "security": "https://github.com/sebastianbergmann/diff/security/policy",
+                "source": "https://github.com/sebastianbergmann/diff/tree/7.0.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/diff",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-08-25T15:35:54+00:00"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "8.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9d32c685773823b1983e256ae4ecd48a10d6e439",
+                "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.5.26"
+            },
+            "suggest": {
+                "ext-posix": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "8.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "https://github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/environment/issues",
+                "security": "https://github.com/sebastianbergmann/environment/security/policy",
+                "source": "https://github.com/sebastianbergmann/environment/tree/8.1.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/environment",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-05-25T13:40:20+00:00"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "7.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23",
+                "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "php": ">=8.3",
+                "sebastian/recursion-context": "^7.0.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.5.25"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "7.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "https://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/exporter/issues",
+                "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+                "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-05-20T04:37:17+00:00"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "8.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b164d3274d6537ab462591c5755f76a8f5b1aae9",
+                "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3",
+                "sebastian/object-reflector": "^5.0",
+                "sebastian/recursion-context": "^7.0.1"
+            },
+            "require-dev": {
+                "ext-dom": "*",
+                "phpunit/phpunit": "^12.5.28"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "8.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "https://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/global-state/issues",
+                "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+                "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-06-01T15:10:33+00:00"
+        },
+        {
+            "name": "sebastian/lines-of-code",
+            "version": "4.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+                "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d543b8ef219dcd8da262cbb958639a96bedba10e",
+                "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e",
+                "shasum": ""
+            },
+            "require": {
+                "nikic/php-parser": "^5.7.0",
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.5.25"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for counting the lines of code in PHP source code",
+            "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+                "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
+                "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-05-19T16:22:07+00:00"
+        },
+        {
+            "name": "sebastian/object-enumerator",
+            "version": "7.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+                "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894",
+                "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3",
+                "sebastian/object-reflector": "^5.0",
+                "sebastian/recursion-context": "^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "7.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+            "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+                "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+                "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-02-07T04:57:48+00:00"
+        },
+        {
+            "name": "sebastian/object-reflector",
+            "version": "5.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-reflector.git",
+                "reference": "4bfa827c969c98be1e527abd576533293c634f6a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a",
+                "reference": "4bfa827c969c98be1e527abd576533293c634f6a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "5.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Allows reflection of object attributes, including inherited and non-public ones",
+            "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+                "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+                "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-02-07T04:58:17+00:00"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "7.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c",
+                "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "7.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "https://github.com/sebastianbergmann/recursion-context",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+                "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+                "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-08-13T04:44:59+00:00"
+        },
+        {
+            "name": "sebastian/type",
+            "version": "6.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/type.git",
+                "reference": "82ff822c2edc46724be9f7411d3163021f602773"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/82ff822c2edc46724be9f7411d3163021f602773",
+                "reference": "82ff822c2edc46724be9f7411d3163021f602773",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^12.5.25"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "6.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Collection of value objects that represent the types of the PHP type system",
+            "homepage": "https://github.com/sebastianbergmann/type",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/type/issues",
+                "security": "https://github.com/sebastianbergmann/type/security/policy",
+                "source": "https://github.com/sebastianbergmann/type/tree/6.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/type",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-05-20T06:45:45+00:00"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "6.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "9e935a16187e14f39b0e79910dabd614972cdb26"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/9e935a16187e14f39b0e79910dabd614972cdb26",
+                "reference": "9e935a16187e14f39b0e79910dabd614972cdb26",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "6.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/version/issues",
+                "security": "https://github.com/sebastianbergmann/version/security/policy",
+                "source": "https://github.com/sebastianbergmann/version/tree/6.0.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/version",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-04T05:50:08+00:00"
+        },
+        {
+            "name": "staabm/side-effects-detector",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/staabm/side-effects-detector.git",
+                "reference": "d8334211a140ce329c13726d4a715adbddd0a163"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163",
+                "reference": "d8334211a140ce329c13726d4a715adbddd0a163",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": "^7.4 || ^8.0"
+            },
+            "require-dev": {
+                "phpstan/extension-installer": "^1.4.3",
+                "phpstan/phpstan": "^1.12.6",
+                "phpunit/phpunit": "^9.6.21",
+                "symfony/var-dumper": "^5.4.43",
+                "tomasvotruba/type-coverage": "1.0.0",
+                "tomasvotruba/unused-public": "1.0.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "lib/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "A static analysis tool to detect side effects in PHP code",
+            "keywords": [
+                "static analysis"
+            ],
+            "support": {
+                "issues": "https://github.com/staabm/side-effects-detector/issues",
+                "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/staabm",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-10-20T05:08:20+00:00"
+        },
+        {
+            "name": "symfony/yaml",
+            "version": "v7.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/yaml.git",
+                "reference": "1ed6afa13d48c579c51cbf32e38f50686313497b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/1ed6afa13d48c579c51cbf32e38f50686313497b",
+                "reference": "1ed6afa13d48c579c51cbf32e38f50686313497b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.2",
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/polyfill-ctype": "^1.8"
+            },
+            "conflict": {
+                "symfony/console": "<6.4"
+            },
+            "require-dev": {
+                "symfony/console": "^6.4|^7.0|^8.0"
+            },
+            "bin": [
+                "Resources/bin/yaml-lint"
+            ],
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Yaml\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Loads and dumps YAML files",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/yaml/tree/v7.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-10-18T14:39:27+00:00"
+        },
+        {
+            "name": "theseer/tokenizer",
+            "version": "2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/theseer/tokenizer.git",
+                "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4",
+                "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-tokenizer": "*",
+                "ext-xmlwriter": "*",
+                "php": "^8.1"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+            "support": {
+                "issues": "https://github.com/theseer/tokenizer/issues",
+                "source": "https://github.com/theseer/tokenizer/tree/2.0.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/theseer",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-12-08T11:19:18+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {},
+    "prefer-stable": true,
+    "prefer-lowest": false,
+    "platform": {
+        "php": "^8.3",
+        "ext-bcmath": "*",
+        "ext-curl": "*",
+        "ext-gd": "*",
+        "ext-json": "*"
+    },
+    "platform-dev": {},
+    "platform-overrides": {
+        "php": "8.3.0"
+    },
+    "plugin-api-version": "2.9.0"
+}

+ 26 - 14
config/admin_realtime.php

@@ -2,29 +2,41 @@
 
 /*
 | 后台实时推送(订单 / 用户列表)。完整说明见 docs/admin-websocket.md。
-| 业务写入在事务提交后登记到 admin_realtime_events,推送网关(realtime/admin-gateway.js)
-| 轮询内部接口取出事件并推给已登录的后台管理员。
+| 业务写入在事务提交后登记到 admin_realtime_events,常驻命令 admin-realtime:relay
+| 读取事件、解析成列表行,经 Laravel 广播(Reverb)推给已登录且有权限的后台管理员。
 */
 return [
-    // 关闭时不登记任何事件,未执行迁移或未部署网关时保持关闭
+    // 关闭时不登记任何事件,未执行迁移或未启动 Reverb 与 relay 时保持关闭
     'enabled' => (bool) env('ADMIN_WS_ENABLED', false),
 
-    // 前端连接地址,随票据接口返回,例如 wss://admin.example.com/admin-ws
-    'public_url' => env('ADMIN_WS_PUBLIC_URL', ''),
+    // 广播连接名,见 config/broadcasting.php
+    'connection' => env('ADMIN_WS_BROADCASTER', 'reverb'),
 
-    // 票据签名密钥(与网关共用),未配置时退回 JWT_SECRET
-    'ticket_secret' => env('ADMIN_WS_SECRET') ?: env('JWT_SECRET', ''),
-    'ticket_ttl' => (int) env('ADMIN_WS_TICKET_TTL', 60),
+    // 前端 Laravel Echo 的连接参数,随 GET /admin/realtime/config 返回;
+    // REVERB_HOST/PORT/SCHEME 是 Laravel 访问 Reverb 的内网地址,二者通常不同
+    'client' => [
+        'key' => env('REVERB_APP_KEY'),
+        'host' => env('ADMIN_WS_PUBLIC_HOST', ''),
+        'port' => (int) env('ADMIN_WS_PUBLIC_PORT', 443),
+        'scheme' => env('ADMIN_WS_PUBLIC_SCHEME', 'https'),
+    ],
 
-    // 网关调用内部拉取接口时携带的密钥(Header: X-Realtime-Secret),未配置时内部接口拒绝访问
-    'internal_secret' => env('ADMIN_WS_INTERNAL_SECRET', ''),
-
-    // 单次拉取最多处理的事件数
+    // relay 无新事件时的轮询间隔(毫秒)与单批处理的事件数
+    'poll_ms' => (int) env('ADMIN_WS_POLL_MS', 1000),
     'batch_size' => (int) env('ADMIN_WS_BATCH_SIZE', 200),
 
-    // 在线状态扫描间隔(秒),由拉取接口顺带执行
+    // 跳过的小 id 可能是写入方事务尚未提交,在该时长内仍会补发(秒)
+    'gap_seconds' => (int) env('ADMIN_WS_GAP_SECONDS', 60),
+
+    // 断线补发单次最多返回的事件数,超过则要求前端重新拉列表
+    'replay_limit' => (int) env('ADMIN_WS_REPLAY_LIMIT', 1000),
+
+    // 管理员可订阅频道的缓存时长(秒),按钮权限变更最迟在此时长后生效
+    'permission_cache_seconds' => (int) env('ADMIN_WS_PERMISSION_CACHE_SECONDS', 60),
+
+    // 在线状态扫描间隔(秒),由 relay 顺带执行
     'online_scan_seconds' => (int) env('ADMIN_WS_ONLINE_SCAN_SECONDS', 15),
 
-    // 事件表保留时长(小时)
+    // 事件表保留时长(小时),也是断线补发能回溯的最长时间
     'retention_hours' => (int) env('ADMIN_WS_RETENTION_HOURS', 72),
 ];

+ 20 - 2
config/broadcasting.php

@@ -11,11 +11,11 @@ return [
     | framework when an event needs to be broadcast. You may set this to
     | any of the connections defined in the "connections" array below.
     |
-    | Supported: "pusher", "ably", "redis", "log", "null"
+    | Supported: "reverb", "pusher", "ably", "redis", "log", "null"
     |
     */
 
-    'default' => env('BROADCAST_DRIVER', 'null'),
+    'default' => env('BROADCAST_CONNECTION', env('BROADCAST_DRIVER', 'null')),
 
     /*
     |--------------------------------------------------------------------------
@@ -30,6 +30,24 @@ return [
 
     'connections' => [
 
+        // 后台实时推送(config/admin_realtime.php)固定走此连接,Laravel 经内网 HTTP 调用 Reverb
+        'reverb' => [
+            'driver' => 'reverb',
+            'key' => env('REVERB_APP_KEY'),
+            'secret' => env('REVERB_APP_SECRET'),
+            'app_id' => env('REVERB_APP_ID'),
+            'options' => [
+                'host' => env('REVERB_HOST'),
+                'port' => env('REVERB_PORT', 443),
+                'scheme' => env('REVERB_SCHEME', 'https'),
+                'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
+            ],
+            'client_options' => [
+                'connect_timeout' => 3,
+                'timeout' => 5,
+            ],
+        ],
+
         'pusher' => [
             'driver' => 'pusher',
             'key' => env('PUSHER_APP_KEY'),

+ 105 - 0
config/reverb.php

@@ -0,0 +1,105 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Reverb Server
+    |--------------------------------------------------------------------------
+    |
+    | This option controls the default server used by Reverb to handle
+    | incoming messages as well as broadcasting message to all your
+    | connected clients. At this time only "reverb" is supported.
+    |
+    */
+
+    'default' => env('REVERB_SERVER', 'reverb'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Reverb Servers
+    |--------------------------------------------------------------------------
+    |
+    | Here you may define details for each of the supported Reverb servers.
+    | Each server has its own configuration options that are defined in
+    | the array below. You should ensure all the options are present.
+    |
+    */
+
+    'servers' => [
+
+        'reverb' => [
+            'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
+            'port' => env('REVERB_SERVER_PORT', 8080),
+            'path' => env('REVERB_SERVER_PATH', ''),
+            'hostname' => env('REVERB_HOST'),
+            'options' => [
+                'tls' => [],
+            ],
+            // Laravel 推送事件的 HTTP 请求体上限;后台列表行可能超过 10KB,默认放宽到 512KB
+            'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 524_288),
+            'scaling' => [
+                'enabled' => env('REVERB_SCALING_ENABLED', false),
+                'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
+                'server' => [
+                    'url' => env('REDIS_URL'),
+                    'host' => env('REDIS_HOST', '127.0.0.1'),
+                    'port' => env('REDIS_PORT', '6379'),
+                    'username' => env('REDIS_USERNAME'),
+                    'password' => env('REDIS_PASSWORD'),
+                    'database' => env('REDIS_DB', '0'),
+                    'timeout' => env('REDIS_TIMEOUT', 60),
+                ],
+            ],
+            'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
+            'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15),
+        ],
+
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Reverb Applications
+    |--------------------------------------------------------------------------
+    |
+    | Here you may define how Reverb applications are managed. If you choose
+    | to use the "config" provider, you may define an array of apps which
+    | your server will support, including their connection credentials.
+    |
+    */
+
+    'apps' => [
+
+        'provider' => 'config',
+
+        'apps' => [
+            [
+                'key' => env('REVERB_APP_KEY'),
+                'secret' => env('REVERB_APP_SECRET'),
+                'app_id' => env('REVERB_APP_ID'),
+                'options' => [
+                    'host' => env('REVERB_HOST'),
+                    'port' => env('REVERB_PORT', 443),
+                    'scheme' => env('REVERB_SCHEME', 'https'),
+                    'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
+                ],
+                // 允许连接的前端域名(不含协议),逗号分隔,例如 admin.example.com;未配置时不限制
+                'allowed_origins' => array_values(array_filter(array_map('trim', explode(',', (string) env('REVERB_ALLOWED_ORIGINS', '*'))))),
+                'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
+                'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30),
+                'max_connections' => env('REVERB_APP_MAX_CONNECTIONS'),
+                'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
+                // 后台推送只有服务端下发,不接受前端 whisper 等客户端事件
+                'accept_client_events_from' => env('REVERB_APP_ACCEPT_CLIENT_EVENTS_FROM', 'none'),
+                'rate_limiting' => [
+                    'enabled' => env('REVERB_APP_RATE_LIMITING_ENABLED', false),
+                    'max_attempts' => env('REVERB_APP_RATE_LIMIT_MAX_ATTEMPTS', 60),
+                    'decay_seconds' => env('REVERB_APP_RATE_LIMIT_DECAY_SECONDS', 60),
+                    'terminate_on_limit' => env('REVERB_APP_RATE_LIMIT_TERMINATE', false),
+                ],
+            ],
+        ],
+
+    ],
+
+];

+ 2 - 1
config/sanctum.php

@@ -60,8 +60,9 @@ return [
     */
 
     'middleware' => [
-        'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
+        'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
         'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
+        'validate_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
     ],
 
 ];

+ 27 - 0
database/migrations/2026_10_31_210000_add_seq_to_admin_realtime_events.php

@@ -0,0 +1,27 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+/**
+ * seq 为 relay 广播时分配的连续序号:写入方事务提交顺序与 id 顺序不一致,
+ * 前端断线补发按 seq 而不是 id 追赶,才不会漏掉晚提交的小 id。写入方无需关心该列。
+ */
+return new class extends Migration {
+    public function up(): void
+    {
+        Schema::table('admin_realtime_events', function (Blueprint $t) {
+            $t->unsignedBigInteger('seq')->nullable()->after('id');
+            $t->unique('seq', 'admin_realtime_seq');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('admin_realtime_events', function (Blueprint $t) {
+            $t->dropUnique('admin_realtime_seq');
+            $t->dropColumn('seq');
+        });
+    }
+};

+ 243 - 187
docs/admin-websocket.md

@@ -1,7 +1,9 @@
-# 后台订单 / 用户列表 WebSocket 实时推送
+# 后台订单 / 用户列表实时推送(Reverb + Laravel Echo)
 
 后台各订单列表、用户列表、在线用户列表通过 WebSocket 接收「新增 / 变更 / 删除 / 上下线」事件。事件里带的 `data` 就是对应列表接口返回的那一行,前端可以直接插入或替换表格行,不用再请求一次列表。
 
+2026-10-31:随 Laravel 13 升级,推送通道由自研 Node 网关(`realtime/admin-gateway.js`,原生 WebSocket + 一次性票据)改为 Laravel 官方的 [Reverb](https://laravel.com/docs/13.x/reverb) 与 [Laravel Echo](https://laravel.com/docs/13.x/broadcasting#client-side-installation)。事件表、列表行解析、频道与载荷的 `data` 均不变;前端需要按第 2、5 节改用 Echo;可直接交给后台前端的独立说明见[后台实时推送-前端接入](总后台/后台实时推送-前端接入.md)。
+
 ## 1. 架构与选型
 
 ```
@@ -9,33 +11,42 @@
    │ 事务提交后登记一条「表 + 主键 + 动作」
    ▼
 bot_admin_realtime_events(事件表,bot-28 与 melbet_sport-api 都能写)
-   ▲ 每秒拉取 GET /admin/realtime/events(内部密钥)
-   │ Laravel 用列表接口自己的查询把记录解析成列表行
-realtime/admin-gateway.js(Node 推送网关,单进程常驻)
-   │ wss://<后台域名>/admin-ws?ticket=…
+   │ php artisan admin-realtime:relay(常驻,单实例)
+   │   按 id 读取未广播的事件 → 分配连续 seq → 用列表接口自己的查询解析成列表行
+   │   顺带:每 15 秒比对在线会员;每 5 秒断开 JWT 已过期的后台连接
+   ▼ Laravel 广播(ShouldBroadcastNow,内网 HTTP,不经队列)
+Reverb(php artisan reverb:start,常驻)
+   │ wss://<后台域名>/app/<key>(Pusher 协议)
    ▼
-后台前端(只推送给已登录、且有对应列表按钮权限的管理员)
+后台前端 Laravel Echo(私有频道,按列表按钮权限授权)
 ```
 
-选型理由:
+| 关注点 | 由谁负责 |
+| --- | --- |
+| 连接、心跳、自动重连、频道订阅协议 | Reverb + Echo(Pusher 协议),不再自行维护 |
+| 频道授权 | `POST /admin/broadcasting/auth`:校验后台 JWT,按列表按钮权限签名 |
+| 登录过期断开 | 授权时把「管理员 id + JWT 过期时间」写进连接;relay 到期调用 Reverb 断开,重连授权返回 401 |
+| 断线补发、去重、重新同步列表 | 应用自己实现:事件带连续 `seq`,前端重连后调 `GET /admin/realtime/replay` 追赶,积压过多或历史已清理时返回 `resync` |
+| 晚提交事件(melbet 长事务) | relay 在 `ADMIN_WS_GAP_SECONDS` 内补发游标之前未广播的行,`seq` 按广播先后分配,补发接口不会漏 |
+
+选型说明:
 
-- 项目原来没有广播基础设施(`BROADCAST_DRIVER` 为 `null/log`,没有 pusher / reverb / laravel-websockets 依赖)。会员端已有的 Swoole WebSocket(melbet_sport-api `websocket:start`)采用「业务事务内写事件表、常驻进程轮询推送」,本方案沿用同一思路,不引入 Redis 发布订阅或第三方服务。
-- 事件表放在共享业务库里,**melbet_sport-api 不需要任何新依赖**就能写入同一张表,前台产生的订单也能推到后台(见第 7 节)。
-- Laravel 9 不能用 Reverb,laravel-websockets 已停止维护;后台推送连接数少,一个依赖 `ws` 的 Node 小进程就够了。服务器已有 Node(`node/` 目录的 TRON 转账脚本在用)。
-- 推送的行由 Laravel 解析:直接以 `id` 参数调用各列表接口自己的查询与格式化,所以推送行和列表行一致,以后列表改了字段,推送跟着变。
-- 事件登记放在事务提交之后(`DB::afterCommit`),事务回滚就不推送;登记、解析、推送任何一步失败都只记 `storage/logs/realtime/` 日志,不影响下单、充值等主业务。
+- 事件表(outbox)与列表行解析保留。事件表放在共享业务库里,**melbet_sport-api 不需要任何新依赖**就能写入同一张表(见第 7 节),不用跟着换框架。
+- 广播用 `ShouldBroadcastNow` 在 relay 进程内同步发送,**不需要队列 worker**;Reverb 不可用时广播失败只记日志,前端重连后凭 `seq` 补发。
+- Reverb 只负责投递,不保存历史,所以 `seq`、补发接口和 `resync` 仍由应用实现。
+- 事件登记放在事务提交之后(`DB::afterCommit`),事务回滚就不推送;登记、解析、广播任何一步失败都只记 `storage/logs/realtime/` 日志,不影响下单、充值等主业务。
 
 ## 2. 接入流程
 
-1. 登录后台后调用 `GET /admin/realtime/ticket` 换取一次性连接票据(有效期 60 秒,只能使用一次)。
-2. 连接 `data.url + '?ticket=' + data.ticket`。
-3. 收到 `welcome` 后发送 `subscribe`,订阅当前页面需要的频道。
-4. 处理 `event` 消息:按 `channel` 找到列表,按 `action` 插入、替换或删除行。
-5. 断线后重新换票重连,订阅时带上收到过的最大事件 `id`(`last_event_id`);收到 `resync` 时重新请求一次列表。
+1. 登录后台后调用 `GET /admin/realtime/config`,取得 Echo 连接参数、可订阅频道、当前 `last_seq` 与登录过期时间。
+2. 用返回参数创建 Echo(`broadcaster: 'reverb'`),授权地址 `/admin/broadcasting/auth`,请求头带后台 token。
+3. 进入列表页时 `echo.private(channel).listen('.order.created', …)` 等;频道名不带 `private-` 前缀,事件名前面加 `.`。
+4. **每次订阅成功(含自动重连后的重新订阅)**调用 `GET /admin/realtime/replay?after_seq=<收到过的最大 seq>&channels=<频道>` 追赶期间的事件;返回 `resync: true` 时重新请求一次列表。
+5. 订阅失败且状态码为 401 时走后台登录失效流程,不要重试。
 
-### 2.1 换取票据
+### 2.1 连接参数
 
-`GET /admin/realtime/ticket`,请求头 `Authorization: Bearer <后台 token>`,只要求后台登录态,不需要额外菜单权限。
+`GET /admin/realtime/config`,请求头 `Authorization: Bearer <后台 token>`,只要求后台登录态。
 
 ```json
 {
@@ -43,59 +54,71 @@ realtime/admin-gateway.js(Node 推送网关,单进程常驻)
   "timestamp": 1793444400,
   "msg": "OK",
   "data": {
-    "ticket": "eyJhaWQiOjEsImNoIjpbImFkbWluLm9yZGVycy5zcG9ydCJdLC4uLn0.Q2xx…",
-    "expires_in": 60,
-    "url": "wss://admin.example.com/admin-ws",
-    "channels": ["admin.orders.sport", "admin.orders.withdraw", "admin.users", "admin.users.online"]
+    "broadcaster": "reverb",
+    "key": "8f1c…",
+    "ws_host": "admin.example.com",
+    "ws_port": 443,
+    "force_tls": true,
+    "auth_endpoint": "/admin/broadcasting/auth",
+    "channels": ["admin.orders.sport", "admin.orders.withdraw", "admin.users", "admin.users.online"],
+    "last_seq": 1532,
+    "session_expires_at": 1793530800
   }
 }
 ```
 
 | 字段 | 说明 |
 | --- | --- |
-| `ticket` | 一次性票据,拼在连接地址 `?ticket=` 后面;每次连接(包括重连)都要重新换取 |
-| `expires_in` | 票据有效秒数,默认 60 |
-| `url` | 网关连接地址(服务端 `ADMIN_WS_PUBLIC_URL`);为空时前端用自己的默认值 |
-| `channels` | 当前管理员可订阅的频道,按各列表接口的按钮权限过滤(与 `check.button.uri` 口径一致,超级管理员 `id=1` 拥有全部频道) |
+| `key` | Reverb 应用 key(公开值,`REVERB_APP_KEY`)。`REVERB_APP_SECRET` 只在服务端 |
+| `ws_host` / `ws_port` / `force_tls` | Echo 的 `wsHost`、`wsPort`/`wssPort`、`forceTLS` |
+| `auth_endpoint` | 私有频道授权地址,相对后台 API 域名 |
+| `channels` | 当前管理员可订阅的频道,按各列表接口的按钮权限过滤(与 `check.button.uri` 口径一致,超级管理员 `id=1` 拥有全部频道);权限变更最迟 60 秒后生效 |
+| `last_seq` | 当前最大事件序号。首次订阅成功后以它为 `after_seq` 调补发接口,补上「拉列表」与「订阅完成」之间的事件 |
+| `session_expires_at` | 后台 token 过期时间戳。到期后服务端会断开连接,前端也可以据此提前处理 |
 
 未启用推送(`ADMIN_WS_ENABLED=false`)时返回 `code != 0`、`msg` 含「实时推送未启用」,前端退回为只用 HTTP 列表,不需要重试。
 
-### 2.2 连接与关闭码
+### 2.2 频道授权
 
-```
-wss://admin.example.com/admin-ws?ticket=<ticket>
-```
+Echo 订阅私有频道时自动请求 `POST /admin/broadcasting/auth`(表单字段 `socket_id`、`channel_name`,请求头带后台 token),前端只需在 Echo 配置里带上请求头。
 
-| 关闭码 | 含义 | 前端处理 |
+| HTTP 状态 | 含义 | 前端处理 |
 | --- | --- | --- |
-| `4401` reason `invalid ticket` | 票据无效、过期或已被使用 | 重新换票后重连 |
-| `4401` reason `session expired` | 后台登录 token 已过期,网关主动断开 | 走后台的登录失效流程,不要自动重连 |
-| `1012` | 网关重启 | 退避后换票重连 |
-| `1006` 等其它 | 网络断开 | 退避(1s、2s、5s、10s,最多 30s)后换票重连 |
+| `200` | 返回 `{"auth": "…", "channel_data": "{\"user_id\":\"1:1793530800\"}"}`,Echo 自动完成订阅 | 无 |
+| `401` | 未登录或 token 已过期 | 走后台登录失效流程,停止 Echo(`echo.disconnect()`) |
+| `403` | 没有该列表的按钮权限、频道不存在或推送未启用 | 该页面退回为只用 HTTP 列表 |
 
-网关每 25 秒发送 WebSocket ping,浏览器会自动回 pong,前端不用处理。需要应用层心跳时可以发 `{"type":"ping"}`。
+该接口不经过 `admin.jwt` 中间件(中间件未登录时仍返回 HTTP 200,Echo 无法识别),由控制器自行校验 token 并返回上述状态码。
 
-### 2.3 消息格式
+### 2.3 登录过期断开
 
-前端发送:
+授权响应的 `channel_data` 里带有「管理员 id:JWT 过期时间」,Reverb 把它记在连接上。relay 每 5 秒检查一次,到期的会话调用 Reverb 的断开接口;Echo 会自动重连并重新授权,此时 token 已过期,授权返回 401,前端按 2.2 处理即可。同一管理员重新登录后的新会话过期时间不同,不会被误断。
 
-| 消息 | 说明 |
-| --- | --- |
-| `{"type":"subscribe","channels":["admin.orders.sport"],"last_event_id":1532}` | 订阅频道;`last_event_id` 可选,填写后会补发断线期间该频道的事件 |
-| `{"type":"unsubscribe","channels":["admin.orders.sport"]}` | 取消订阅(离开页面时调用) |
-| `{"type":"ping"}` | 应用层心跳,回 `pong` |
+### 2.4 断线补发与重新同步
 
-网关发送:
+`GET /admin/realtime/replay?after_seq=1532&channels=admin.orders.sport,admin.orders.withdraw`(`admin.jwt`)
 
-| `type` | 示例 | 说明 |
-| --- | --- | --- |
-| `welcome` | `{"type":"welcome","admin_id":1,"channels":[…],"last_event_id":1532,"server_time":1793444400}` | 连接成功。`last_event_id` 是网关已经处理到的事件 id |
-| `subscribed` | `{"type":"subscribed","channels":["admin.orders.sport"],"denied":["admin.orders.withdraw"],"last_event_id":1532}` | `denied` 是没有权限的频道,已被忽略 |
-| `unsubscribed` | `{"type":"unsubscribed","channels":[…]}` | |
-| `event` | 见第 4 节 | 列表数据变化 |
-| `resync` | `{"type":"resync","channels":["admin.orders.sport"],"reason":"history_unavailable"}` | 断线太久,网关不再保留期间的事件,需重新请求这些频道的列表 |
-| `pong` | `{"type":"pong","server_time":1793444400}` | |
-| `error` | `{"type":"error","message":"invalid json"}` | 前端发了无法识别的消息 |
+```json
+{
+  "code": 0,
+  "msg": "OK",
+  "data": {
+    "resync": false,
+    "last_seq": 1540,
+    "channels": ["admin.orders.sport", "admin.orders.withdraw"],
+    "events": [ { "seq": 1535, "channel": "admin.orders.sport", "event": "order.updated", "...": "与推送消息相同" } ]
+  }
+}
+```
+
+| 字段 | 说明 |
+| --- | --- |
+| `events` | `seq > after_seq` 的事件,只含请求且有权限的频道,按当前数据重新解析,按 `seq` 升序 |
+| `resync` | `true` 时 `events` 为空,前端应重新请求这些频道的列表,并把游标设为 `last_seq` |
+| `reason` | `resync` 的原因:`history_unavailable`(游标对应的事件已被清理,默认保留 72 小时)、`too_many_events`(积压超过 `ADMIN_WS_REPLAY_LIMIT`,默认 1000)、`invalid_cursor` |
+| `channels` | 实际处理的频道(无权限的已剔除) |
+
+补发与实时推送可能重叠,前端用 `seq + channel` 去重。
 
 ## 3. 频道与列表页
 
@@ -116,18 +139,18 @@ wss://admin.example.com/admin-ws?ticket=<ticket>
 | `admin.users` | `GET /admin/user`(用户列表) | `id`(users.id) | `user.created` |
 | `admin.users.online` | `GET /admin/online/users`(在线用户) | `member_id` | `user.online` `user.offline` |
 
-同一笔代收订单会同时推到 `admin.orders.payment` 和 `admin.orders.third_party_deposit` 两个频道(两张列表都显示它),两条消息的 `id` 相同、`channel` 不同。
+同一笔代收订单会同时推到 `admin.orders.payment` 和 `admin.orders.third_party_deposit` 两个频道(两张列表都显示它),两条消息的 `seq` 相同、`channel` 不同。
 
 `GET /admin/withdraw` 与 `GET /admin/withdraw/rmb` 新增了可选查询参数 `id`(精确匹配),收到事件后想单独刷新一行也可以用它;其余列表原本就支持 `id`(在线用户用 `member_id`)。
 
 ## 4. 事件载荷
 
-通用结构:
+Echo 收到的事件对象(`broadcastAs` 为 `event` 字段,监听时写作 `.order.created`):
 
 ```json
 {
-  "type": "event",
-  "id": 1533,
+  "seq": 1533,
+  "id": 2871,
   "channel": "admin.orders.withdraw",
   "event": "order.updated",
   "action": "updated",
@@ -140,7 +163,8 @@ wss://admin.example.com/admin-ws?ticket=<ticket>
 
 | 字段 | 说明 |
 | --- | --- |
-| `id` | 事件 id,全局递增;用于去重和断线补发(`last_event_id`)。少数情况下较小的 id 会晚到(写入方事务提交较晚),不要据此丢弃事件 |
+| `seq` | 广播序号,全局连续递增;用于去重(`seq + channel`)和断线补发(`after_seq`)。晚提交的事件也会拿到新的、更大的 `seq` |
+| `id` | 事件表 id,仅供排查;可能乱序,不要用来补发 |
 | `channel` | 频道,决定更新哪张列表 |
 | `event` | `order.created` / `order.updated` / `order.deleted` / `user.created` / `user.online` / `user.offline` |
 | `action` | `created` / `updated` / `deleted` / `online` / `offline`,即 `event` 的后半段 |
@@ -149,7 +173,7 @@ wss://admin.example.com/admin-ws?ticket=<ticket>
 | `source` | 事件来源:`bot-28`,或接入后的 `melbet_sport-api` |
 | `created_at` | 事件登记时间(Asia/Shanghai) |
 
-`data` 在推送时按数据库的最新状态解析,并不是变更瞬间的快照。同一记录在一秒内多次变化只推一次;新增后马上又更新时仍按 `created` 推送。若记录不在列表接口的口径内(例如被列表本身的联表或筛选条件排除),不推送。
+`data` 在推送时按数据库的最新状态解析,并不是变更瞬间的快照。同一记录在一批内多次变化只推一次;新增后马上又更新时仍按 `created` 推送。若记录不在列表接口的口径内(例如被列表本身的联表或筛选条件排除),不推送。单条消息上限由 `REVERB_MAX_REQUEST_SIZE` 决定(默认放宽到 512KB),超过时该条广播失败、记日志,前端可经补发接口取回。
 
 ### 4.1 示例:USDT 提现 `admin.orders.withdraw`
 
@@ -157,8 +181,8 @@ wss://admin.example.com/admin-ws?ticket=<ticket>
 
 ```json
 {
-  "type": "event",
-  "id": 1533,
+  "seq": 1533,
+  "id": 2871,
   "channel": "admin.orders.withdraw",
   "event": "order.created",
   "action": "created",
@@ -194,8 +218,8 @@ wss://admin.example.com/admin-ws?ticket=<ticket>
 
 ```json
 {
-  "type": "event",
-  "id": 1534,
+  "seq": 1534,
+  "id": 2872,
   "channel": "admin.users.online",
   "event": "user.online",
   "action": "online",
@@ -221,11 +245,11 @@ wss://admin.example.com/admin-ws?ticket=<ticket>
 下线:
 
 ```json
-{ "type": "event", "id": 1590, "channel": "admin.users.online", "event": "user.offline", "action": "offline",
+{ "seq": 1590, "id": 2930, "channel": "admin.users.online", "event": "user.offline", "action": "offline",
   "key": { "member_id": "9001" }, "data": null, "source": "bot-28", "created_at": "2026-10-31 10:31:00" }
 ```
 
-在线判定与在线列表一致(存在未过期 `user_session`,或 30 分钟内活跃,不含虚拟用户)。网关每 `ADMIN_WS_ONLINE_SCAN_SECONDS`(默认 15 秒)比对一次在线集合,所以上下线最多延迟一个扫描周期;「30 分钟未活跃自动下线」这类没有写入动作的变化也会推送。按端统计(`GET /admin/online/platformCount`)没有单独推送,收到上下线事件后可以节流(例如 10 秒一次)重新请求。
+在线判定与在线列表一致(存在未过期 `user_session`,或 30 分钟内活跃,不含虚拟用户)。relay 每 `ADMIN_WS_ONLINE_SCAN_SECONDS`(默认 15 秒)比对一次在线集合,所以上下线最多延迟一个扫描周期;「30 分钟未活跃自动下线」这类没有写入动作的变化也会推送。按端统计(`GET /admin/online/platformCount`)没有单独推送,收到上下线事件后可以节流(例如 10 秒一次)重新请求。
 
 ### 4.3 其它频道的 `data`
 
@@ -251,80 +275,114 @@ wss://admin.example.com/admin-ws?ticket=<ticket>
 - **按 `key` 定位行**:`created` 时行不存在就插入、已存在就替换;`updated` 时替换,行不在当前页可以忽略(或标记「有更新」);`deleted` / `offline` 时删除。
 - **对照当前筛选条件**:`created` / `updated` 的行如果不满足页面当前的搜索条件(状态、会员、时间范围等),不要插入(已在表里的可以移除);只在第一页、且排序为默认倒序时把新行插到顶部,其它页提示「有 N 条新记录」并让管理员手动刷新。
 - **合计、总数**:列表接口返回的 `total`、`total_amount` 等汇总值推送里没有,需要时节流重新请求一次列表,或前端自行加减。
-- **去重**:用 `channel + id` 去重;同一 `channel + key` 收到多次时总以最后到达的 `data` 为准(`data` 总是最新状态)。
-- **记录 `last_event_id`**:保存收到过的最大事件 `id`;重连后在 `subscribe` 里带上它,网关会补发期间的事件。
-- **收到 `resync`、或换票失败后重新连上时**:重新请求一次当前页列表,再继续接收事件。网关重启、断线时间过长(默认保留最近 5000 条事件)都会触发。
-- **页面切换**:离开列表页时 `unsubscribe`,进入时 `subscribe`;整个后台共用一条连接即可。
-- **推送不可用时**:换票失败或连接持续失败时退回原有的手动刷新 / 轮询,不影响页面使用。
+- **去重**:用 `seq + channel` 去重;同一 `channel + key` 收到多次时总以 `seq` 最大的 `data` 为准。
+- **补发**:每次 `subscribed` 回调里按该频道的续传点调补发接口(规则见下);收到 `resync` 时重新请求当前页列表。
+- **页面切换**:离开列表页时 `echo.leave(channel)`,进入时重新 `private(channel)`;整个后台共用一个 Echo 实例。
+- **推送不可用时**:`config` 返回未启用、授权 403、或连接持续处于 `unavailable` 时退回原有的手动刷新 / 轮询,不影响页面使用。
+
+依赖:`npm install laravel-echo pusher-js`。
+
+补发游标的规则(参考实现已按此处理):
+
+- 只用**实时收到**的事件推进全局游标 `liveSeq`。同一连接上的消息按 `seq` 顺序到达,收到 `seq = S` 说明已订阅频道在 S 之前的事件都已收到;补发接口经 HTTP 返回,不保证与连接上的消息先后一致,不能用它推进 `liveSeq`。
+- 断线时给每个已订阅频道记下续传点 `pending[channel] = liveSeq`(已有的不覆盖);新订阅的频道续传点也取当时的 `liveSeq`。
+- 每个频道订阅成功后各自以自己的续传点调补发接口,成功后清除续传点。不能共用一个全局游标,否则先完成的频道会把游标推过其它频道还没补的事件。
 
 参考实现:
 
 ```js
-class AdminRealtime {
-  constructor(http) {
+import Echo from 'laravel-echo';
+import Pusher from 'pusher-js';
+
+export class AdminRealtime {
+  constructor(http, token, onSessionExpired) {
     this.http = http;                 // 已带后台 token 的请求实例
-    this.handlers = new Map();        // channel => fn(event)
-    this.resyncHandlers = new Map();  // channel => fn()
-    this.lastEventId = null;
-    this.retry = 0;
+    this.token = token;
+    this.onSessionExpired = onSessionExpired;
+    this.liveSeq = 0;                 // 实时收到的最大 seq
+    this.pending = new Map();         // channel => 待补发的起点 seq
+    this.watched = new Map();         // channel => { onEvent, onResync }
+    this.seen = new Set();            // `${seq}:${channel}` 去重
   }
 
-  on(channel, onEvent, onResync) {
-    this.handlers.set(channel, onEvent);
-    this.resyncHandlers.set(channel, onResync);
-    this.send({ type: 'subscribe', channels: [channel], last_event_id: this.lastEventId });
+  async start() {
+    const res = await this.http.get('/admin/realtime/config');
+    if (res.code !== 0) return false; // 未启用推送,保持 HTTP 列表
+    const c = res.data;
+    this.allowed = new Set(c.channels);
+    this.liveSeq = c.last_seq;
+    this.echo = new Echo({
+      broadcaster: 'reverb',
+      Pusher,
+      key: c.key,
+      wsHost: c.ws_host,
+      wsPort: c.ws_port,
+      wssPort: c.ws_port,
+      forceTLS: c.force_tls,
+      enabledTransports: ['ws', 'wss'],
+      authEndpoint: API_BASE + c.auth_endpoint,
+      auth: { headers: { Authorization: `Bearer ${this.token}` } },
+    });
+    this.echo.connector.pusher.connection.bind('state_change', ({ current }) => {
+      if (current === 'connected') return;
+      for (const channel of this.watched.keys()) {
+        if (!this.pending.has(channel)) this.pending.set(channel, this.liveSeq);
+      }
+    });
+    return true;
   }
 
-  off(channel) {
-    this.handlers.delete(channel);
-    this.resyncHandlers.delete(channel);
-    this.send({ type: 'unsubscribe', channels: [channel] });
+  /** onEvent(e) 处理单条事件;onResync() 重新请求列表 */
+  watch(channel, events, onEvent, onResync) {
+    if (!this.echo || !this.allowed.has(channel)) return;
+    this.watched.set(channel, { onEvent, onResync });
+    this.pending.set(channel, this.liveSeq);
+    const sub = this.echo.private(channel);
+    events.forEach((name) => sub.listen(`.${name}`, (e) => {
+      this.liveSeq = Math.max(this.liveSeq, e.seq);
+      this.deliver(e);
+    }));
+    // 首次订阅与每次重连后的重新订阅都会触发
+    sub.subscribed(() => this.replay(channel));
+    sub.error((err) => {
+      if (err?.status === 401) {
+        this.echo.disconnect();
+        this.onSessionExpired();
+      }
+    });
   }
 
-  async connect() {
-    let res;
+  async replay(channel) {
+    const after = this.pending.get(channel);
+    if (after === undefined || !this.watched.has(channel)) return;
     try {
-      res = await this.http.get('/admin/realtime/ticket');
+      const res = await this.http.get('/admin/realtime/replay', { params: { after_seq: after, channels: channel } });
+      if (res.code !== 0) throw new Error(res.msg);
+      if (res.data.resync) this.watched.get(channel)?.onResync();
+      else res.data.events.forEach((e) => this.deliver(e));
     } catch (e) {
-      return this.reconnect();
+      this.watched.get(channel)?.onResync(); // 补发失败时直接重新拉列表
     }
-    if (res.code !== 0) return; // 未启用推送,保持 HTTP 列表
-    const ws = new WebSocket(`${res.data.url}?ticket=${encodeURIComponent(res.data.ticket)}`);
-    this.ws = ws;
-    ws.onmessage = (e) => this.onMessage(JSON.parse(e.data));
-    ws.onopen = () => {
-      this.retry = 0;
-      const channels = [...this.handlers.keys()];
-      // 重连时带上 last_event_id:网关补发期间事件,或回 resync 让页面重新拉列表
-      if (channels.length) this.send({ type: 'subscribe', channels, last_event_id: this.lastEventId });
-    };
-    ws.onclose = (e) => {
-      if (e.code === 4401 && e.reason === 'session expired') return; // 交给登录失效流程
-      this.reconnect();
-    };
-  }
-
-  reconnect() {
-    const delay = [1000, 2000, 5000, 10000, 30000][Math.min(this.retry++, 4)];
-    setTimeout(() => this.connect(), delay);
+    if (this.pending.get(channel) === after) this.pending.delete(channel);
   }
 
-  onMessage(msg) {
-    if (msg.type === 'welcome' && this.lastEventId === null) this.lastEventId = msg.last_event_id;
-    if (msg.type === 'event') {
-      this.lastEventId = Math.max(this.lastEventId ?? 0, msg.id);
-      this.handlers.get(msg.channel)?.(msg);
-    }
-    if (msg.type === 'resync') msg.channels.forEach((c) => this.resyncHandlers.get(c)?.());
+  deliver(e) {
+    const id = `${e.seq}:${e.channel}`;
+    if (this.seen.has(id)) return;
+    this.seen.add(id);
+    if (this.seen.size > 20000) this.seen = new Set([...this.seen].slice(-10000));
+    this.watched.get(e.channel)?.onEvent(e);
   }
 
-  send(payload) {
-    if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(payload));
+  unwatch(channel) {
+    this.watched.delete(channel);
+    this.pending.delete(channel);
+    this.echo?.leave(channel);
   }
 }
 
 // 体彩订单列表页
-realtime.on('admin.orders.sport', (e) => {
+realtime.watch('admin.orders.sport', ['order.created', 'order.updated', 'order.deleted'], (e) => {
   const index = rows.findIndex((r) => r.id === e.key.id);
   if (e.action === 'deleted') return index >= 0 && rows.splice(index, 1);
   if (!matchesFilters(e.data)) return index >= 0 && rows.splice(index, 1);
@@ -347,7 +405,7 @@ bot-28 内以下写入会产生事件:
   - 支付通道代付回调的状态更新(`PaymentOrderService`)
   - 后台批量设置打款信息、批量锁定 / 解锁提现与代付(`PaymentOrder`、`Withdraw` 控制器)
   - 会员合并时转移的投注与支付订单(`SecretService`)
-- **在线状态**:不依赖写入点,由拉取接口定期比对在线集合,bot-28 与 melbet_sport-api 的登录、活跃都能覆盖。
+- **在线状态**:不依赖写入点,由 relay 定期比对在线集合,bot-28 与 melbet_sport-api 的登录、活跃都能覆盖。
 
 以后新增不经模型的批量写入时,在写入后调用:
 
@@ -362,7 +420,7 @@ bot-28 内以下写入会产生事件:
 
 会员前台 API(melbet_sport-api)也直接写这些表:体彩下单与结算(`SportOrder`)、六合彩 / 极速 / 足球游戏下注、投注、充值、代收代付、提现、**会员注册**。**这些写入目前不会产生事件**,在该仓库接入前,前台产生的新订单、新用户不会实时出现在后台列表(列表本身照常,刷新可见)。在线状态不受影响。
 
-接入方式:在 melbet_sport-api 对应写入之后往同一张表插入一行即可,不需要新增依赖或配置。以 ThinkPHP 模型事件为例:
+接入方式:在 melbet_sport-api 对应写入之后往同一张表插入一行即可,不需要新增依赖或配置,也不需要跟着换框架或接入 Reverb。`seq` 列由 relay 填写,写入方不要赋值。以 ThinkPHP 模型事件为例:
 
 ```php
 // app/common/service/AdminRealtimeEvent.php
@@ -396,83 +454,80 @@ public static function onAfterUpdate($model) { AdminRealtimeEvent::push('sport_o
 
 - `resource` 只能是第 6 节列出的表名(`users` 只处理 `created`);`action` 用 `created` / `updated` / `deleted`。时间使用 Asia/Shanghai。
 - ThinkPHP 的模型事件只在模型 `save()` / `create()` 时触发,`Db::name()->update()` 这类批量写入需要在写入后手动调用 `push()`。
-- 在事务内插入也可以(回滚即不推送)。网关会把暂未提交的事件 id 记下,在 60 秒内补拉;事务持续超过 60 秒的事件可能漏推,长事务建议提交后再插入。
+- 在事务内插入也可以(回滚即不推送)。relay 会在 60 秒(`ADMIN_WS_GAP_SECONDS`)内补发暂未提交的事件;事务持续超过该时长的事件可能漏推,长事务建议提交后再插入。
 - 插入失败必须吞掉异常,不能影响下单、支付。
 
 ## 8. 部署
 
-### 8.1 后台(Laravel)
+需要两个常驻进程:**Reverb** 与 **relay**,各运行一个实例。不需要队列 worker,不需要 Node。
+
+### 8.1 迁移与配置
 
 ```bash
-php artisan migrate --path=database/migrations/2026_10_31_160000_create_admin_realtime_events.php --force
+php artisan migrate --force
 ```
 
+涉及 `2026_10_31_160000_create_admin_realtime_events.php`(事件表)与 `2026_10_31_210000_add_seq_to_admin_realtime_events.php`(`seq` 列)。
+
 `.env` 新增(示例见 `example.env`):
 
 | 变量 | 说明 |
 | --- | --- |
-| `ADMIN_WS_ENABLED` | `true` 开启。未执行迁移、未启动网关时保持 `false`,此时不登记任何事件 |
-| `ADMIN_WS_PUBLIC_URL` | 前端连接地址,如 `wss://admin.example.com/admin-ws` |
-| `ADMIN_WS_SECRET` | 票据签名密钥,后台与网关一致;留空则用 `JWT_SECRET` |
-| `ADMIN_WS_INTERNAL_SECRET` | 网关调用内部接口的密钥,必填(随机长字符串) |
+| `ADMIN_WS_ENABLED` | `true` 开启。未执行迁移、未启动 Reverb 与 relay 时保持 `false`,此时不登记任何事件 |
+| `ADMIN_WS_PUBLIC_HOST` / `ADMIN_WS_PUBLIC_PORT` / `ADMIN_WS_PUBLIC_SCHEME` | 前端 Echo 连接的公网地址,如 `admin.example.com` / `443` / `https` |
+| `REVERB_APP_ID` / `REVERB_APP_KEY` / `REVERB_APP_SECRET` | Reverb 应用凭据。KEY、SECRET 用 `openssl rand -hex 16` 生成;SECRET 不得下发前端 |
+| `REVERB_HOST` / `REVERB_PORT` / `REVERB_SCHEME` | Laravel 访问 Reverb HTTP 接口的内网地址,如 `127.0.0.1` / `8080` / `http` |
+| `REVERB_SERVER_HOST` / `REVERB_SERVER_PORT` | Reverb 监听地址,建议 `127.0.0.1` / `8080`,由 Nginx 对外 |
+| `REVERB_ALLOWED_ORIGINS` | 允许连接的后台前端域名,逗号分隔、不含协议,如 `admin.example.com` |
+| `REVERB_MAX_REQUEST_SIZE` | 单条广播请求上限,默认 `524288`(512KB) |
+| `ADMIN_WS_POLL_MS` | relay 空闲时的轮询间隔,默认 `1000` |
+| `ADMIN_WS_GAP_SECONDS` | 晚提交事件的补发窗口,默认 `60` |
+| `ADMIN_WS_REPLAY_LIMIT` | 补发接口单次最多事件数,超过返回 `resync`,默认 `1000` |
 | `ADMIN_WS_ONLINE_SCAN_SECONDS` | 在线状态扫描间隔,默认 `15` |
-| `ADMIN_WS_RETENTION_HOURS` | 事件表保留时长,默认 `72` |
+| `ADMIN_WS_RETENTION_HOURS` | 事件表保留时长(也是补发能回溯的最长时间),默认 `72` |
 
-改完执行 `php artisan config:clear`(线上有配置缓存时为 `config:cache`)。调度器已加入每小时 `admin-realtime:prune` 清理过期事件,沿用现有 `schedule:run` 定时任务即可。
+改完执行 `php artisan config:cache`。调度器已加入每小时 `admin-realtime:prune` 清理过期事件,沿用现有 `schedule:run` 定时任务即可。
 
-`GET /admin/realtime/events` 是给网关用的内部接口,凭 `X-Realtime-Secret` 访问;建议在 Nginx 上只允许本机访问:
-
-```nginx
-location = /admin/realtime/events {
-    allow 127.0.0.1;
-    deny all;
-    try_files $uri /index.php?$query_string;
-}
-```
-
-### 8.2 推送网关(Node ≥ 18)
-
-```bash
-cd realtime && npm ci --omit=dev
-```
-
-网关默认读取项目根目录 `.env`(可用 `ADMIN_WS_ENV_FILE` 指定),也可以直接用环境变量:
-
-| 变量 | 默认 | 说明 |
-| --- | --- | --- |
-| `ADMIN_WS_HOST` | `127.0.0.1` | 监听地址,由 Nginx 反代,不直接对外 |
-| `ADMIN_WS_PORT` | `6002` | 监听端口 |
-| `ADMIN_WS_PATH` | `/admin-ws` | WebSocket 路径 |
-| `ADMIN_WS_LARAVEL_URL` | `APP_URL` | 网关访问后台的地址,建议 `http://127.0.0.1` |
-| `ADMIN_WS_LARAVEL_HOST` | 空 | 用 IP 访问后台、Nginx 需要按域名匹配站点时填写后台域名(作为 Host 头) |
-| `ADMIN_WS_POLL_MS` | `1000` | 拉取间隔(毫秒) |
-| `ADMIN_WS_ALLOWED_ORIGINS` | 空(不限制) | 允许的前端 Origin,逗号分隔,如 `https://admin.example.com` |
-| `ADMIN_WS_REPLAY_SIZE` | `5000` | 用于断线补发的最近事件条数 |
-
-进程守护(二选一,只运行**一个**网关进程):
+### 8.2 进程守护(Supervisor)
 
 ```ini
-; /etc/supervisor/conf.d/bot28-admin-ws.conf
-[program:bot28-admin-ws]
-directory=/www/wwwroot/bot-28/realtime
-command=node admin-gateway.js
+; /etc/supervisor/conf.d/bot28-realtime.conf
+[program:bot28-reverb]
+directory=/www/wwwroot/bot-28
+command=php artisan reverb:start
 autostart=true
 autorestart=true
 stopsignal=TERM
+stopwaitsecs=10
 user=www
+minfds=10000
 redirect_stderr=true
-stdout_logfile=/www/wwwroot/bot-28/storage/logs/realtime/gateway.log
-```
+stdout_logfile=/www/wwwroot/bot-28/storage/logs/realtime/reverb.log
 
-```bash
-pm2 start realtime/admin-gateway.js --name bot28-admin-ws && pm2 save
+[program:bot28-admin-relay]
+directory=/www/wwwroot/bot-28
+command=php artisan admin-realtime:relay
+autostart=true
+autorestart=true
+stopsignal=TERM
+stopwaitsecs=15
+user=www
+redirect_stderr=true
+stdout_logfile=/www/wwwroot/bot-28/storage/logs/realtime/relay.log
 ```
 
-Nginx 反代(后台域名的 server 块内):
+- relay 默认运行 1 小时后正常退出(`--max-seconds`),由 Supervisor 拉起新进程,避免长驻内存增长;收到 `SIGTERM` 会处理完当前批次再退出。
+- relay 用缓存锁保证全局只有一个实例;被强杀后新进程最多等待约 35 秒拿到锁。多台服务器部署时缓存必须是共享的(Redis),且只在一台上运行 relay。
+- 发布新代码后执行 `php artisan reverb:restart` 并重启 relay(`supervisorctl restart bot28-admin-relay`)。
+- Reverb 连接数多时需调高文件句柄(`minfds`);后台管理员连接数少,默认即可。详见 [Reverb 进程管理](https://laravel.com/docs/13.x/reverb#process-management)。
+
+### 8.3 Nginx
+
+后台域名的 server 块内,把 Pusher 协议路径反代到 Reverb:
 
 ```nginx
-location /admin-ws {
-    proxy_pass http://127.0.0.1:6002;
+location /app {
+    proxy_pass http://127.0.0.1:8080;
     proxy_http_version 1.1;
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection "upgrade";
@@ -482,36 +537,37 @@ location /admin-ws {
 }
 ```
 
-健康检查(在服务器本机执行):
-
-```bash
-curl -s http://127.0.0.1:6002/health
-```
-
-返回 `ok: true`、`cursor` 随业务增长即正常;`last_error` 为拉取内部接口的最近错误(如 `HTTP 403` 表示内部密钥不一致,`code=… 实时推送未启用` 表示后台 `ADMIN_WS_ENABLED` 未开启)。
+`/apps/...` 是 Reverb 的服务端 HTTP 接口(广播、断开连接),只供本机 Laravel 调用,**不要**对外反代。
 
-### 8.3 发布顺序
+### 8.4 发布顺序
 
 1. 发布代码,执行迁移,配置 `.env`,`ADMIN_WS_ENABLED` 先保持 `false`。
-2. 安装并启动网关,配置 Nginx。
-3. 将 `ADMIN_WS_ENABLED` 改为 `true` 并刷新配置缓存,`/health` 正常后通知前端上线。
+2. 启动 Reverb,配置 Nginx;`curl -s http://127.0.0.1:8080/up` 返回 200。
+3. 将 `ADMIN_WS_ENABLED` 改为 `true` 并 `config:cache`,启动 relay;`storage/logs/realtime/relay.log` 出现「relay 启动」。
+4. 后台页面实际下单核对推送,再通知前端上线。
+
+关闭推送:`ADMIN_WS_ENABLED=false` 并刷新配置,业务写入不再登记事件,relay 退出(Supervisor 会反复拉起并立即退出,关闭期间可 `supervisorctl stop bot28-admin-relay`)。
 
-关闭推送:`ADMIN_WS_ENABLED=false` 并刷新配置即可,业务写入不再登记事件,网关可保留运行。
+从旧 Node 网关切换:停止并删除 `bot28-admin-ws` 进程与 Nginx 的 `/admin-ws`、`/admin/realtime/events` 配置,删除 `.env` 中的 `ADMIN_WS_PUBLIC_URL`、`ADMIN_WS_SECRET`、`ADMIN_WS_INTERNAL_SECRET`、`ADMIN_WS_PORT`、`ADMIN_WS_PATH`、`ADMIN_WS_LARAVEL_URL`。前端须同时改为 Echo(旧的 `/admin/realtime/ticket` 已移除)。
 
-### 8.4 本地开发
+### 8.5 本地开发
 
 ```bash
 php artisan migrate
 ```
 
 ```bash
-cd realtime && npm install && node admin-gateway.js
+php artisan reverb:start --debug
+```
+
+```bash
+php artisan admin-realtime:relay
 ```
 
-`.env` 中设置 `ADMIN_WS_ENABLED=true`、`ADMIN_WS_INTERNAL_SECRET=任意值`、`ADMIN_WS_PUBLIC_URL=ws://127.0.0.1:6002/admin-ws`、`ADMIN_WS_LARAVEL_URL` 为本地后台地址。
+`.env` 中设置 `ADMIN_WS_ENABLED=true`、`REVERB_APP_KEY`/`SECRET` 任意值、`REVERB_HOST=127.0.0.1`、`REVERB_PORT=8080`、`REVERB_SCHEME=http`、`ADMIN_WS_PUBLIC_HOST=127.0.0.1`、`ADMIN_WS_PUBLIC_PORT=8080`、`ADMIN_WS_PUBLIC_SCHEME=http`。
 
 ## 9. 验证情况
 
-- `tests/Integration/AdminRealtimeTest.php`(隔离 SQLite):事务提交后才登记、回滚丢弃、关闭时不登记、事件表缺失不影响业务事务、模型钩子只推订单与新用户、推送行与 `/admin/withdraw` 列表行逐字段一致、删除只带主键、按 gaps 补拉、票据签名与 Node 网关算法互通。
-- 网关用模拟内部接口做了端到端验证:无效 / 重复票据 4401、无权限频道被拒、未提交事件补拉后送达、按 `last_event_id` 补发、历史不足时 `resync`、后台登录过期断开、健康检查。
-- 在线用户扫描与各订单列表的单行解析依赖 MySQL 语法(`COLLATE` 等),未在真实数据库、真实 Nginx / wss 环境下验证;上线后请按 8.3 用 `/health` 与后台页面实际下单核对。
+- `tests/Integration/AdminRealtimeTest.php`(隔离 SQLite):事务提交后才登记、回滚丢弃、关闭时不登记、事件表缺失不影响业务事务、模型钩子只推订单与新用户、推送行与 `/admin/withdraw` 列表行逐字段一致、删除只带主键、晚提交事件按更大的 `seq` 补发且超出窗口不补、按 `seq` 补发与频道过滤、三种 `resync`、频道授权签名与 Reverb 校验算法一致、401/403、过期会话只断开一次且 Reverb 不可达只记日志。
+- 本机端到端(SQLite + `reverb:start` + `admin-realtime:relay` + Node 中的 `laravel-echo@2.1.6`/`pusher-js@8.4.0`):授权订阅成功、relay 广播实时送达且未订阅频道收不到;token 40 秒到期时 Reverb 断开连接、自动重连授权 401;Reverb 停止期间写入的事件广播失败记日志,Reverb 恢复后 Echo 自动重连、补发接口取回缺失事件并继续实时接收;relay 收到 `SIGTERM` 正常退出并释放锁。
+- 在线用户扫描与各订单列表的单行解析依赖 MySQL 语法(`COLLATE` 等),未在真实数据库、真实 Nginx / wss 环境下验证;上线后请按 8.4 实际下单核对。

+ 5 - 0
docs/前端交付/README.md

@@ -2,6 +2,11 @@
 
 本目录是2026-10-26的接口文档交付副本,按**项目 → 前台/后台/代理端**组织。
 
+## 2026-10-31 增补
+
+- [后台实时推送-前端接入](bot-28/后台/后台实时推送-前端接入.md):使用 Laravel Echo + Reverb(同日由原生 WebSocket 方案修订)。包含连接参数、频道授权、14 个频道、事件格式、表格更新、断线补发与登录失效处理。
+- 下述原始范围保留;加上本次新增的1份正文,当前共22个来源文件。
+
 ## 本次范围
 
 - **bot-28**:从 `c1b4269bc9c45e2610b9078ddb39a1215b0d81aa` 到当前工作区,包含已提交及全部未提交;首次整理时HEAD也是该提交;后续按用户反馈同步修订,修订记录见交付清单JSON。

+ 2 - 1
docs/前端交付/bot-28/后台/README.md

@@ -4,6 +4,7 @@
 
 | 功能 | 文档 |
 |---|---|
+| 实时推送:Laravel Echo + Reverb、频道授权、表格更新、断线补发(2026-10-31 增补) | [后台实时推送-前端接入](后台实时推送-前端接入.md) |
 | 公告:多语言、展示平台、发布时段、启停和日志 | [公告系统-后台接口](公告系统-后台接口.md) |
 | 信息:发送对象、多语言、定时发送、撤回和日志 | [信息管理-后台接口](信息管理-后台接口.md) |
 | 维护:整站与游戏分类、平台、时区、起止时间、开关和日志 | [整站与游戏维护-后台接口](整站与游戏维护-后台接口.md) |
@@ -19,6 +20,6 @@
 
 公告的活动选择器复用 `GET /admin/ActivityReward?page=1&limit=20`,其中 `page`、`limit` 必填;读取 `data.data[]` 中的 `id`、`title`,需要 `admin/ActivityReward` 权限。该选择器对应既有优惠活动列表。
 
-公告、信息管理、维护三份文档在基准提交中已存在,本次按用户要求补充交付。其余文件属于指定范围的未提交增量。另新增一份报表页面独立修正单。报表汇总、资金流动、龙虎榜分别提供完整接入文档。本目录共12个来源文件。
+公告、信息管理、维护三份文档在基准提交中已存在,本次按用户要求补充交付。其余文件属于指定范围的未提交增量。另新增一份报表页面独立修正单。报表汇总、资金流动、龙虎榜分别提供完整接入文档。原交付包含12个来源文件,2026-10-31 增补实时推送接入说明后共13个。
 
 [会员前台接口目录](../../melbet_sport-api/前台/README.md) · [交付总目录](../../README.md) · [来源与排除清单](../../交付清单.md)

+ 197 - 0
docs/前端交付/bot-28/后台/后台实时推送-前端接入.md

@@ -0,0 +1,197 @@
+# 后台实时推送:前端接入
+
+更新日期:2026-10-31。适用于 bot-28 后台订单、用户和在线用户列表;与 `upgrade/laravel-13` 分支当前实现一致。
+
+## 客户端选型
+
+**使用 Laravel Echo + `pusher-js` 连接 Laravel Reverb(Pusher 协议)。** 安装:
+
+```bash
+npm install laravel-echo pusher-js
+```
+
+后端已从自研 Node 网关迁移到 Reverb:原生 WebSocket 的 `GET /admin/realtime/ticket`、`welcome` / `subscribe` / `event` 消息与 `4401` 关闭码均已移除,旧前端代码需要替换。官方客户端文档见 [Laravel Broadcasting:客户端安装](https://laravel.com/docs/13.x/broadcasting#client-side-installation)。
+
+连接、心跳和自动重连由 Echo 处理;**断线补发、去重和重新同步列表仍需前端按本文实现**,Reverb 不保存历史消息。
+
+升级部署若更换后台 JWT 密钥,旧管理员 token 会失效,前端沿用已有登录失效处理,重新登录后再连接推送。
+
+## 接入顺序
+
+1. 使用后台管理员 token 请求 `GET /admin/realtime/config`,取得连接参数、可订阅频道和当前 `last_seq`。
+2. 用返回参数创建一个全局 Echo 实例,授权请求头带后台 token。
+3. 进入列表页时订阅该页频道(须在 `data.channels` 中),再按当前筛选请求 HTTP 列表作为基线。
+4. 每次订阅成功(包括自动重连后的重新订阅)调用补发接口追赶期间的事件;返回 `resync: true` 时重新请求列表。
+5. 订阅失败且 HTTP 状态为 401 时停止 Echo,走登录失效流程。
+
+整个后台共用一个 Echo 实例,切换页面时 `echo.leave(channel)` 后再订阅新频道;退出登录时 `echo.disconnect()`,清空去重记录与游标。
+
+## 连接参数
+
+```http
+GET /admin/realtime/config
+Authorization: Bearer <后台管理员 token>
+```
+
+成功响应示例:
+
+```json
+{
+  "code": 0,
+  "timestamp": 1793444400,
+  "msg": "OK",
+  "data": {
+    "broadcaster": "reverb",
+    "key": "<Reverb 应用 key>",
+    "ws_host": "admin.example.com",
+    "ws_port": 443,
+    "force_tls": true,
+    "auth_endpoint": "/admin/broadcasting/auth",
+    "channels": ["admin.orders.withdraw", "admin.users.online"],
+    "last_seq": 1532,
+    "session_expires_at": 1793530800
+  }
+}
+```
+
+- `key`、`ws_host`、`ws_port`、`force_tls` 对应 Echo 的 `key`、`wsHost`、`wsPort`/`wssPort`、`forceTLS`。`key` 是公开值,不是密钥。
+- `auth_endpoint` 相对后台 API 域名,拼接方式与其它后台接口一致。
+- `channels` 按当前管理员列表权限过滤;不要假定每个账号都能订阅全部频道。权限调整最迟 60 秒后生效。
+- `last_seq` 是首次补发的起点,`session_expires_at` 是 token 过期时间戳。
+- HTTP 请求失败、业务 `code != 0` 时保留 HTTP 列表;返回「实时推送未启用」时不要持续重试。
+
+创建 Echo 示例(`config` 为上述 `data`,`API_BASE` 为后台 API 地址):
+
+```js
+import Echo from 'laravel-echo';
+import Pusher from 'pusher-js';
+
+const echo = new Echo({
+  broadcaster: 'reverb',
+  Pusher,
+  key: config.key,
+  wsHost: config.ws_host,
+  wsPort: config.ws_port,
+  wssPort: config.ws_port,
+  forceTLS: config.force_tls,
+  enabledTransports: ['ws', 'wss'],
+  authEndpoint: API_BASE + config.auth_endpoint,
+  auth: { headers: { Authorization: `Bearer ${token}` } },
+});
+```
+
+## 频道与订阅
+
+频道均为私有频道,订阅时**不加** `private-` 前缀;事件名是自定义名称,监听时**前面加点**:
+
+```js
+echo.private('admin.orders.withdraw')
+  .listen('.order.created', onEvent)
+  .listen('.order.updated', onEvent)
+  .listen('.order.deleted', onEvent);
+```
+
+| 频道 | HTTP 列表接口 | 行主键 | 监听的事件 |
+| --- | --- | --- | --- |
+| `admin.orders.sport` | `/admin/order/list` | `id` | `.order.created` `.order.updated` `.order.deleted` |
+| `admin.orders.lhc` | `/admin/lhcOrder/list` | `id` | 同上 |
+| `admin.orders.sport_game` | `/admin/sportGameOrder/list` | `id` | 同上 |
+| `admin.orders.jisu_game` | `/admin/jisuGameOrder/list` | `id` | 同上 |
+| `admin.orders.third_game` | `/admin/thirdGame/orders` | `id` | 同上 |
+| `admin.orders.bet` | `/admin/bet` | `id` | 同上 |
+| `admin.orders.recharge` | `/admin/wallet` | `id` | 同上 |
+| `admin.orders.payment` | `/admin/paymentOrder`(代收,`type=1`) | `id` | 同上 |
+| `admin.orders.third_party_deposit` | `/admin/thirdPartyDeposit`(三方充值,`type=1`) | `id` | 同上 |
+| `admin.orders.payment_withdraw` | `/admin/withdraw/rmb`(`type=2/4`) | `id` | 同上 |
+| `admin.orders.withdraw` | `/admin/withdraw` | `id` | 同上 |
+| `admin.orders.manual_audit` | `/admin/manualAudit` | `id` | 同上 |
+| `admin.users` | `/admin/user` | `id` | `.user.created` |
+| `admin.users.online` | `/admin/online/users` | `member_id` | `.user.online` `.user.offline` |
+
+Echo 订阅私有频道时自动请求授权接口 `POST /admin/broadcasting/auth`:
+
+| HTTP 状态 | 含义 | 前端处理 |
+| --- | --- | --- |
+| `200` | 授权成功 | 无 |
+| `401` | 未登录或 token 已过期 | `echo.disconnect()`,走登录失效流程,不要重试 |
+| `403` | 无该列表权限、频道不存在或推送未启用 | 该页面保留 HTTP 列表,不要重试 |
+
+在订阅对象上用 `.error((err) => …)` 读取 `err.status`。
+
+## 事件与表格处理
+
+Echo 回调收到的对象:
+
+```json
+{
+  "seq": 1533,
+  "id": 2871,
+  "channel": "admin.orders.withdraw",
+  "event": "order.updated",
+  "action": "updated",
+  "key": { "id": 88 },
+  "data": { "id": 88, "member_id": "9001", "amount": 12.5, "status": 1 },
+  "source": "bot-28",
+  "created_at": "2026-10-31 10:00:00"
+}
+```
+
+示例的 `data` 仅展示部分字段。实际 `data` 与该频道 HTTP 列表返回的单行字段一致,由服务端读取推送时的最新状态;不是变化瞬间的快照。
+
+| `action` | 前端处理 |
+| --- | --- |
+| `created` / `online` | 按 `key` 插入或更新;先检查当前筛选、分页和排序 |
+| `updated` | 替换当前页已有行;更新后不满足筛选则移除;当前页没有的行可提示有更新 |
+| `deleted` / `offline` | 按 `key` 移除;此时 `data` 为 `null` |
+
+- `seq` 是全局连续的广播序号,用于去重和补发;`id` 是事件表编号,仅供排查;`key.id` 或 `key.member_id` 才是行主键,三者不能混用。
+- 用 **`seq + channel`** 去重,并限制去重缓存大小。同一笔代收订单会同时推给代收与三方充值两个频道,`seq` 相同。
+- 只有第一页、默认倒序且符合筛选时才直接把新行放到顶部;其它分页提示有新数据,保留管理员当前浏览位置。
+- 合计、总数和按端在线统计没有随行同步推送,需要时节流重新请求对应 HTTP 接口;推送不可用时继续提供原有的刷新方式。
+
+## 断线补发与重新同步
+
+```http
+GET /admin/realtime/replay?after_seq=1532&channels=admin.orders.withdraw
+Authorization: Bearer <后台管理员 token>
+```
+
+```json
+{
+  "code": 0,
+  "msg": "OK",
+  "data": {
+    "resync": false,
+    "last_seq": 1540,
+    "channels": ["admin.orders.withdraw"],
+    "events": [{ "seq": 1535, "channel": "admin.orders.withdraw", "event": "order.updated", "...": "与实时事件相同" }]
+  }
+}
+```
+
+- `events` 为 `seq > after_seq` 的事件,按 `seq` 升序,处理方式与实时事件相同(同样去重)。
+- `resync: true` 时 `events` 为空,`reason` 为 `history_unavailable`(超出保留期,默认 72 小时)、`too_many_events`(积压超过 1000 条)或 `invalid_cursor`;重新请求该频道列表即可。
+
+游标规则(必须遵守,否则多频道时会漏事件):
+
+1. 维护 `liveSeq`:初值为 `config.last_seq`,**只用 Echo 实时收到的事件推进**,补发接口返回的事件不推进它。
+2. 订阅新频道时,记下该频道的续传点 `pending[channel] = liveSeq`。
+3. 连接状态离开 `connected` 时(`echo.connector.pusher.connection.bind('state_change', …)`),给每个已订阅且没有续传点的频道记 `pending[channel] = liveSeq`。
+4. 频道订阅成功回调(`.subscribed(…)`,首次与每次重连都会触发)里,以该频道自己的续传点调补发接口,处理完后清除续传点;请求失败时直接重新请求列表。
+
+完整的连接管理器参考实现见[后台 WebSocket 完整说明](../../../admin-websocket.md)第 5 节,已在本机 Reverb 上验证过断线补发。
+
+## 重连与登录失效
+
+| 情况 | 处理 |
+| --- | --- |
+| 网络断开、Reverb 重启 | Echo 自动重连并重新订阅,订阅成功回调里按上节补发 |
+| 连接长时间处于 `unavailable` | 保留 HTTP 列表与手动刷新,Echo 会继续尝试 |
+| token 到期 | 服务端主动断开连接,Echo 重连时授权返回 401,按上表停止 Echo 并走登录失效流程;也可按 `session_expires_at` 提前处理 |
+| 退出登录 | `echo.disconnect()`,清空去重记录和游标 |
+
+心跳由 Echo 与 Reverb 自动完成,前端无需实现。
+
+上线联调应确认:正常账号能收到授权频道,无权限频道授权 403;重复消息不重复插行;筛选和翻页不被新行打乱;断网恢复后能补齐期间的变化;token 过期与退出登录后不会继续重连。当前说明已在本机 Reverb 上用 `laravel-echo@2.1.6`、`pusher-js@8.4.0` 验证,尚未完成生产环境与浏览器联调。
+
+完整行字段、后端部署与实现说明见[后台 WebSocket 完整说明](../../../admin-websocket.md)。

+ 25 - 0
docs/前端交付/交付清单.json

@@ -244,6 +244,17 @@
       "git_status": "??",
       "source_sha256": "82b9661fc19659d42e068c1de64b1396d22b6b62e8745da79f110101d1273f32",
       "delivery_sha256": "82b9661fc19659d42e068c1de64b1396d22b6b62e8745da79f110101d1273f32"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/总后台/后台实时推送-前端接入.md",
+      "delivery_path": "bot-28/后台/后台实时推送-前端接入.md",
+      "role": "后台",
+      "kind": "正文",
+      "git_status": "??",
+      "source_sha256": "e76cce84a33b0fbed5e85f2970e736a2ed4d7b0e1fc8f402f84f27347f415699",
+      "delivery_sha256": "2e389b58114cf922508f6021c55a1103a924e5d042b659855d144c9e6ea5ecfc",
+      "revised_on": "2026-10-31"
     }
   ],
   "excluded": [
@@ -332,6 +343,20 @@
       "project": "bot-28",
       "head_commit": "23a69f51ca35f09750f4acb3f6c2b475abb1147c",
       "description": "按用户要求取消龙虎榜和营业报表订单明细的未分配公共资金与成本区块;列表接口不再返回unallocated独立对象,汇总成本与净收益计算保留"
+    },
+    {
+      "date": "2026-10-31",
+      "project": "bot-28",
+      "head_commit": "b738a7d80f4fa7b9304ed33b1bf25b1d75b956ea",
+      "branch": "upgrade/laravel-13",
+      "description": "增补后台实时推送前端独立接入文档与交付副本,明确 Laravel 13 升级沿用 Node 网关和原生 WebSocket,尚未迁移 Reverb;同步目录、文件清单及 SHA-256"
+    },
+    {
+      "date": "2026-10-31",
+      "project": "bot-28",
+      "head_commit": "b738a7d80f4fa7b9304ed33b1bf25b1d75b956ea",
+      "branch": "upgrade/laravel-13",
+      "description": "后台实时推送改用 Reverb + Laravel Echo,移除 Node 网关与原生 WebSocket 票据协议;前端接入文档改为 Echo 连接参数、频道授权、seq 补发与登录失效处理,同步目录说明及 SHA-256"
     }
   ]
 }

+ 2 - 1
docs/前端交付/交付清单.md

@@ -4,10 +4,11 @@
 
 本轮重新整理的[报表汇总](bot-28/后台/报表汇总-前端接入.md)、[资金流动](bot-28/后台/资金流动-前端接入.md)、[龙虎榜](bot-28/后台/龙虎榜-前端接入.md)为三份完整前端接入文档,可直接转发。原合并文件作为目录入口保留。三页已补充全部时间查询,并统一移除统计说明与元数据、完整性提示和列表数据状态列。龙虎榜及营业报表订单明细也已取消“未分配公共资金与成本”区块。
 
-共21个来源文件:bot-28后台12个,melbet_sport-api前台9个;其中16份正文、3份目录、1份修正单、1份示例数据。状态列保留整理时的Git状态,后续修订见JSON的revisions。
+原交付共21个来源文件;2026-10-31 增补后台实时推送接入正文后,共22个来源文件:bot-28后台13个,melbet_sport-api前台9个;其中17份正文、3份目录、1份修正单、1份示例数据。状态列保留整理时的Git状态,后续修订见JSON的revisions。
 
 | 项目 | 来源路径 | 状态 | 交付文件 | 类型 |
 |---|---|---|---|---|
+| bot-28 | `docs/总后台/后台实时推送-前端接入.md` | ??(2026-10-31 增补) | [后台实时推送-前端接入.md](bot-28/后台/后台实时推送-前端接入.md) | 正文 |
 | bot-28 | `docs/总后台/独立活动-后台接口.md` | M | [独立活动-后台接口.md](bot-28/后台/独立活动-后台接口.md) | 目录 |
 | bot-28 | `docs/总后台/独立活动/净亏邀请-后台接口.md` | M | [净亏邀请-后台接口.md](bot-28/后台/独立活动/净亏邀请-后台接口.md) | 正文 |
 | bot-28 | `docs/总后台/独立活动/首充奖励-后台接口.md` | M | [首充奖励-后台接口.md](bot-28/后台/独立活动/首充奖励-后台接口.md) | 正文 |

+ 130 - 0
docs/后端/Laravel13升级说明.md

@@ -0,0 +1,130 @@
+# 后台(bot-28)升级到 Laravel 13
+
+分支:`upgrade/laravel-13`。范围只有 bot-28;melbet_sport-api 是 ThinkPHP 8,不受影响。
+
+按官方 10、11、12、13 各版本升级指南逐项核对后一次性升级。应用目录结构(`app/Http/Kernel.php`、`app/Console/Kernel.php`、`app/Exceptions/Handler.php`、`config/app.php` 的 providers)保持 Laravel 9 写法,官方说明 11 起仍兼容旧结构,不建议迁移。
+
+## 上线前必须确认
+
+| 项目 | 要求 | 不满足的后果 |
+| --- | --- | --- |
+| PHP | **≥ 8.3**(本地以 8.4 验证),扩展 bcmath、curl、gd、json、pdo_mysql、redis | composer 无法安装,站点无法启动 |
+| `JWT_SECRET` | **≥ 32 字节**(`php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'`) | firebase/php-jwt 7 拒绝短密钥:后台登录报错,已有 token 全部校验失败 |
+| 后台登录态 | 更换 `JWT_SECRET` 后所有后台管理员需重新登录 | — |
+| Composer | ≥ 2.2 | — |
+
+`JWT_SECRET` 只用于 bot-28 自己签发和校验的 token;melbet_sport-api 用 `thans/jwt` 单独配置,不受影响。
+
+## 依赖变化
+
+| 包 | 旧 | 新 | 说明 |
+| --- | --- | --- | --- |
+| laravel/framework | ^9.19 | ^13.0(验证于 13.39) | |
+| php | >=8.0 | ^8.3 | Laravel 13 最低要求 |
+| laravel/sanctum | ^3.3 | ^4.0 | 迁移已在 `database/migrations`,无需重新发布 |
+| laravel/tinker | ^2.7 | ^3.0 | |
+| laravel/reverb | — | ^1.6(验证于 1.12) | 新增:后台实时推送改用 Reverb,见下文 |
+| firebase/php-jwt | ^6.10 | ^7.0 | 6.x 有安全公告,composer 默认拒绝安装;7.x 要求 HS256 密钥 ≥ 32 字节 |
+| brick/math | (间接依赖 0.11) | ^1.0(显式声明) | `RoundingMode` 改为枚举,代码已改;本仓库直接使用,故显式声明 |
+| laravel-lang/lang | ^14.7 | ^15.0 | |
+| irazasyed/telegram-bot-sdk | ^3.15 | ^3.16 | |
+| nesbot/carbon | 2.x | 3.x | 随框架升级 |
+| monolog/monolog | 2.x | 3.x | 只用到内置 Handler,无需改动 |
+| symfony/* | 6.x | 7.4(依锁文件) | 按 PHP 8.3 解析;不再使用首次在 PHP 8.4 上安装的 Symfony 8.1 |
+| phpunit/phpunit | ^9.5 | ^12.0 | |
+| nunomaduro/collision | ^6.1 | ^8.6 | |
+| spatie/laravel-ignition | ^1.0 | 移除 | 仅开发依赖 |
+| minimum-stability | dev | stable | |
+
+`composer.lock` 已从 `.gitignore` 移除,必须随升级代码一起提交、部署;`composer.json` 的 `config.platform.php` 固定为 `8.3.0`,按最低支持的 PHP 版本解析依赖,避免在本地 PHP 8.4 上锁入仅支持 8.4 的包。线上只运行 `composer install`,使用已验证的锁定版本,不在服务器运行 `composer update`。
+
+Git 忽略 lock 文件不代表 Composer 忽略它:已有 lock 时 `composer install` 会使用它;缺少 lock 时才解析依赖并生成 lock。旧 Laravel 9 lock 与本次约束冲突,会导致安装失败,因此不能只更新 `composer.json`。
+
+## 代码改动
+
+- **Carbon 3 `diffIn*` 带符号**:`admin/Bet.php` 的「30 天内才统计投注额」原写法 `$end->diffInDays($start)` 在 Carbon 3 下为负数,条件恒成立,会对任意时间范围求和。改为 `$start->diffInDays($end, true)`。其余 7 处 `diffIn*` 均已先校验开始 ≤ 结束,结果不变。
+- **brick/math 1.0**:`ShareCommissionRecorder` 的 `RoundingMode::HALF_UP` 改为 `RoundingMode::HalfUp`(否则分享佣金登记报错)。
+- **CSRF 中间件改名**:`App\Http\Middleware\VerifyCsrfToken` 改继承 `PreventRequestForgery`(13 中 `VerifyCsrfToken` 仅为弃用别名)。web 组本来就未启用 CSRF,行为不变。
+- **Sanctum 4 配置**:`config/sanctum.php` 的中间件键改为 `authenticate_session` / `encrypt_cookies` / `validate_csrf_token`。
+- **`$routeMiddleware` 改名 `$middlewareAliases`**:可选改名,行为不变。
+- `example.env` 的 `JWT_SECRET` 改为空值并注明长度要求。
+
+## 测试改动(不影响线上)
+
+- PHPUnit 12 不再读取 `/** @dataProvider */` 注释:15 个测试文件改为 `#[DataProvider]` 属性,数据提供方法改为 `static`。
+- Laravel 13 的 `response()` 有返回类型:隔离测试中自写的 JSON 响应桩统一换成 `tests/Support/JsonResponseFactory`。
+- PHPUnit 12 的 `TestCase::status()` 为 final:`BannerPublicationTest` 的辅助方法改名 `setStatus()`。
+- Laravel 12 起 `Grammar` 构造需要连接;`chunkById` 生成的 SQL 多了 `where "id" is not null`;MySQL 语法编译会读取服务器版本:相应测试夹具已调整(「不连库」测试改用只回答版本、拒绝执行 SQL 的 PDO 替身)。
+- 足球结算测试默认逐级向上查找 `melbet_sport-api`,兼容主检出和仓库内嵌套工作树;仓库位于其他位置时,设置 `SHARING_TP_ROOT=/绝对路径/melbet_sport-api`。缺少依赖仓库会明确报错,不跳过测试。
+
+## 已核对、无需改动
+
+- **缓存 / Redis / Session 键名**:`config/cache.php`、`config/database.php`、`config/session.php` 都显式定义了前缀和 cookie 名,13 的默认值变化不生效;redis 缓存仓库自身前缀为空,11 去掉 `:` 后缀也不改变键名。和 melbet_sport-api 共用的 Redis 键不受影响。
+- **缓存与 Session 反序列化**:配置中没有 `serializable_classes`、`serialization`,沿用旧行为(可反序列化任意对象 / `php` 序列化),现有缓存和登录 session 不失效。
+- **upsert**:两处调用都传了非空 `uniqueBy`。
+- **迁移**:没有 `->change()`、`float`/`double` 列,无 Doctrine DBAL 依赖。
+- **eager load 加 limit**(11 起按父记录分别限制):应用中没有这种写法。
+- 没有 `$dates` 属性、`dispatchNow`、`Redirect::home`、`(string) DB::raw()`、`Cache::tags`、自定义 `casts()` 方法、全局 `array_first/array_last` 辅助函数、`image` 校验规则。
+- `createFromTimestamp` 均显式传了时区(Carbon 3 默认改为 UTC)。
+- 文件系统 `local` 磁盘已显式配置为 `storage/app`(12 的默认值变化不生效)。
+
+## 后台实时推送改用 Reverb + Laravel Echo
+
+升级后同一分支把后台订单 / 用户列表的推送通道从自研 Node 网关换成官方 Reverb,完整说明见 [admin-websocket.md](../admin-websocket.md),前端改动见[后台实时推送-前端接入](../总后台/后台实时推送-前端接入.md)。
+
+- **保留**:事件表 `bot_admin_realtime_events`(melbet_sport-api 继续写,无需改框架)、列表行解析、14 个频道与载荷 `data`、按钮权限口径。
+- **新增**:`config/reverb.php`、`config/broadcasting.php` 的 `reverb` 连接、常驻命令 `admin-realtime:relay`(读事件表、同步广播,不需要队列 worker)、迁移 `2026_10_31_210000_add_seq_to_admin_realtime_events`(连续 `seq` 用于补发)、`POST /admin/broadcasting/auth`、`GET /admin/realtime/config`、`GET /admin/realtime/replay`。
+- **移除**:`realtime/`(Node 网关)、`GET /admin/realtime/ticket`、内部接口 `GET /admin/realtime/events` 及 `ADMIN_WS_SECRET` / `ADMIN_WS_INTERNAL_SECRET` 等变量。
+- **前端须同步上线**:旧的原生 WebSocket 票据协议已不可用。
+- 登录过期断开:授权时把「管理员 id:JWT 过期时间」记在 Reverb 连接上,relay 到期调用 Reverb 断开,重连授权返回 401。
+
+## 部署步骤
+
+首次部署前,备份旧发布目录(包括 `composer.json`、原 `composer.lock`、`vendor` 和运行配置)作为回滚版本。更新到升级代码时,确认新的受版本控制的 `composer.lock` 已替换服务器上的旧文件;若 Git 因旧的未跟踪 lock 拒绝切换,先将它移到备份目录,再更新代码。
+
+```bash
+php -v   # 确认 ≥ 8.3
+composer validate --no-check-publish
+composer install --no-dev --optimize-autoloader
+composer check-platform-reqs --no-dev
+php artisan optimize:clear --except=cache
+php artisan migrate --force
+php artisan optimize
+```
+
+`--except=cache` 必须保留:这里只清除配置、路由、事件、视图等框架缓存。`optimize:clear` 默认还会调用 `cache:clear`,Redis 缓存驱动最终执行 `FLUSHDB`,忽略键前缀并清空当前 Redis 数据库,会影响与 melbet_sport-api 共用的业务数据和锁。不要用 `cache:clear` 或 `Cache::flush()` 替代。
+
+随后重启 php-fpm、队列 worker(如有)与 `schedule:run` 所在的 cron 环境使用的 PHP 版本。后台实时推送需新增 Supervisor 守护的 `php artisan reverb:start` 与 `php artisan admin-realtime:relay`、Nginx 反代 `/app`,并停用旧的 Node 网关,步骤见 [admin-websocket.md 第 8 节](../admin-websocket.md#8-部署)。更换 `JWT_SECRET` 后通知后台人员重新登录。
+
+回滚:切换到上线前备份的旧发布目录,恢复配套的旧 `composer.json`、原 `composer.lock` 与 `vendor`,再重建旧版本的框架缓存并重启服务。旧分支忽略 lock,单独切回代码不会恢复匹配的依赖;不要在旧代码上保留 Laravel 13 的 lock,也不要临时用 `composer update` 重新解析旧依赖。可恢复原 `JWT_SECRET`,或保留新密钥(旧版本同样可用)。
+
+## 验证情况
+
+- 首次升级验证(按 PHP 8.3 重新锁定依赖前,本地 PHP 8.4.18 + Laravel 13.39.0):`php artisan about`、`optimize`(config / events / routes / views 缓存)成功;内核处理 HTTP 请求正常(JWT 中间件拒绝无效 token、内部接口无密钥返回 403、未知路由返回 JSON 404)。
+- 400 条路由逐条核对控制器与中间件可解析。
+- JWT:8 字节密钥签发失败(预期),32 字节密钥签发与校验正常。
+- 修正工作树路径、按 PHP 8.3 重新锁定依赖后,在本地 PHP 8.4.18 + Laravel 13.39.0 + Symfony 7.4 复跑,测试数、断言数和错误/失败项与 master(Laravel 9)一致,**并非全部通过**:
+
+  | 测试集 | 测试数 | 断言数 | 错误 | 失败 | 跳过 |
+  | --- | --- | --- | --- | --- | --- |
+  | Unit(两边一致) | 90 | 300 | 2 | 0 | 0 |
+  | Integration(两边一致) | 651 | 6988 | 1 | 2 | 10 |
+
+  既有错误/失败为 `NoPayServiceTest` 2 个(未注册 config)、`ThirdGamePlayerIdentityTest` 1 个(SQLite 无 MD5)、`ShareCommissionRecordingTest` 2 个(与「返现明细登记失败不再回滚充值」提交的行为变更不符)。PHPUnit 12 另报告 21 项弃用提示,不能据此声称两种测试运行器的全部输出相同。
+
+  复跑命令(在对应检出目录运行,均不加载项目 `.env` 或应用 providers):
+
+  ```bash
+  php -d xdebug.mode=off -d error_reporting=8191 vendor/bin/phpunit --no-configuration --bootstrap tests/bootstrap-agent.php tests/Unit
+  php -d xdebug.mode=off -d error_reporting=8191 vendor/bin/phpunit --no-configuration --bootstrap tests/bootstrap-agent.php tests/Integration
+  ```
+
+- 新 lock 的 `composer validate --no-check-publish`、`composer install --dry-run --no-dev --no-scripts`(当前工作树和全新临时目录)与本机 `composer check-platform-reqs --no-dev` 通过;`php artisan about --only=environment` 正常启动并显示 Laravel 13.39.0。依赖按 PHP 8.3.0 解析,但尚未在实际 PHP 8.3 运行时执行测试。
+- 已通过记录子命令的方式验证 `optimize:clear --except=cache`:只选择 `config:clear`、`clear-compiled`、`event:clear`、`route:clear`、`view:clear`,不选择 `cache:clear`;该检查未执行实际清缓存子命令、未访问 Redis。
+- 改用 Reverb 后复跑(本地 PHP 8.4.18):Integration 654 个测试、7005 个断言,新增的 3 个推送测试通过,错误/失败项仍为上表既有的 3 项;Unit 不变。另在本机用 SQLite + Reverb + relay + Node 中的 Laravel Echo 做了端到端验证(授权、实时送达、token 到期断开、Reverb 重启后补发)。
+- 未在真实 MySQL、Redis、线上 PHP 环境与浏览器中验证;上线前请在测试环境完整走一遍后台登录、下单、充值、提现、报表。
+
+## 既有问题(与升级无关,未处理)
+
+- 三条路由指向不存在的代码:`GET /fingerprint`(`api\Fingerprint` 类不存在)、`api/setA`、`admin/wallet/getChannel`(方法不存在)。13 的 `php artisan route:list` 会因此报错,可用 `--path` 过滤查看。
+- 多处给 `Request` 动态加 `$user` 属性,PHP 8.2+ 报弃用提示(PHP 9 将报错)。

+ 197 - 0
docs/总后台/后台实时推送-前端接入.md

@@ -0,0 +1,197 @@
+# 后台实时推送:前端接入
+
+更新日期:2026-10-31。适用于 bot-28 后台订单、用户和在线用户列表;与 `upgrade/laravel-13` 分支当前实现一致。
+
+## 客户端选型
+
+**使用 Laravel Echo + `pusher-js` 连接 Laravel Reverb(Pusher 协议)。** 安装:
+
+```bash
+npm install laravel-echo pusher-js
+```
+
+后端已从自研 Node 网关迁移到 Reverb:原生 WebSocket 的 `GET /admin/realtime/ticket`、`welcome` / `subscribe` / `event` 消息与 `4401` 关闭码均已移除,旧前端代码需要替换。官方客户端文档见 [Laravel Broadcasting:客户端安装](https://laravel.com/docs/13.x/broadcasting#client-side-installation)。
+
+连接、心跳和自动重连由 Echo 处理;**断线补发、去重和重新同步列表仍需前端按本文实现**,Reverb 不保存历史消息。
+
+升级部署若更换后台 JWT 密钥,旧管理员 token 会失效,前端沿用已有登录失效处理,重新登录后再连接推送。
+
+## 接入顺序
+
+1. 使用后台管理员 token 请求 `GET /admin/realtime/config`,取得连接参数、可订阅频道和当前 `last_seq`。
+2. 用返回参数创建一个全局 Echo 实例,授权请求头带后台 token。
+3. 进入列表页时订阅该页频道(须在 `data.channels` 中),再按当前筛选请求 HTTP 列表作为基线。
+4. 每次订阅成功(包括自动重连后的重新订阅)调用补发接口追赶期间的事件;返回 `resync: true` 时重新请求列表。
+5. 订阅失败且 HTTP 状态为 401 时停止 Echo,走登录失效流程。
+
+整个后台共用一个 Echo 实例,切换页面时 `echo.leave(channel)` 后再订阅新频道;退出登录时 `echo.disconnect()`,清空去重记录与游标。
+
+## 连接参数
+
+```http
+GET /admin/realtime/config
+Authorization: Bearer <后台管理员 token>
+```
+
+成功响应示例:
+
+```json
+{
+  "code": 0,
+  "timestamp": 1793444400,
+  "msg": "OK",
+  "data": {
+    "broadcaster": "reverb",
+    "key": "<Reverb 应用 key>",
+    "ws_host": "admin.example.com",
+    "ws_port": 443,
+    "force_tls": true,
+    "auth_endpoint": "/admin/broadcasting/auth",
+    "channels": ["admin.orders.withdraw", "admin.users.online"],
+    "last_seq": 1532,
+    "session_expires_at": 1793530800
+  }
+}
+```
+
+- `key`、`ws_host`、`ws_port`、`force_tls` 对应 Echo 的 `key`、`wsHost`、`wsPort`/`wssPort`、`forceTLS`。`key` 是公开值,不是密钥。
+- `auth_endpoint` 相对后台 API 域名,拼接方式与其它后台接口一致。
+- `channels` 按当前管理员列表权限过滤;不要假定每个账号都能订阅全部频道。权限调整最迟 60 秒后生效。
+- `last_seq` 是首次补发的起点,`session_expires_at` 是 token 过期时间戳。
+- HTTP 请求失败、业务 `code != 0` 时保留 HTTP 列表;返回「实时推送未启用」时不要持续重试。
+
+创建 Echo 示例(`config` 为上述 `data`,`API_BASE` 为后台 API 地址):
+
+```js
+import Echo from 'laravel-echo';
+import Pusher from 'pusher-js';
+
+const echo = new Echo({
+  broadcaster: 'reverb',
+  Pusher,
+  key: config.key,
+  wsHost: config.ws_host,
+  wsPort: config.ws_port,
+  wssPort: config.ws_port,
+  forceTLS: config.force_tls,
+  enabledTransports: ['ws', 'wss'],
+  authEndpoint: API_BASE + config.auth_endpoint,
+  auth: { headers: { Authorization: `Bearer ${token}` } },
+});
+```
+
+## 频道与订阅
+
+频道均为私有频道,订阅时**不加** `private-` 前缀;事件名是自定义名称,监听时**前面加点**:
+
+```js
+echo.private('admin.orders.withdraw')
+  .listen('.order.created', onEvent)
+  .listen('.order.updated', onEvent)
+  .listen('.order.deleted', onEvent);
+```
+
+| 频道 | HTTP 列表接口 | 行主键 | 监听的事件 |
+| --- | --- | --- | --- |
+| `admin.orders.sport` | `/admin/order/list` | `id` | `.order.created` `.order.updated` `.order.deleted` |
+| `admin.orders.lhc` | `/admin/lhcOrder/list` | `id` | 同上 |
+| `admin.orders.sport_game` | `/admin/sportGameOrder/list` | `id` | 同上 |
+| `admin.orders.jisu_game` | `/admin/jisuGameOrder/list` | `id` | 同上 |
+| `admin.orders.third_game` | `/admin/thirdGame/orders` | `id` | 同上 |
+| `admin.orders.bet` | `/admin/bet` | `id` | 同上 |
+| `admin.orders.recharge` | `/admin/wallet` | `id` | 同上 |
+| `admin.orders.payment` | `/admin/paymentOrder`(代收,`type=1`) | `id` | 同上 |
+| `admin.orders.third_party_deposit` | `/admin/thirdPartyDeposit`(三方充值,`type=1`) | `id` | 同上 |
+| `admin.orders.payment_withdraw` | `/admin/withdraw/rmb`(`type=2/4`) | `id` | 同上 |
+| `admin.orders.withdraw` | `/admin/withdraw` | `id` | 同上 |
+| `admin.orders.manual_audit` | `/admin/manualAudit` | `id` | 同上 |
+| `admin.users` | `/admin/user` | `id` | `.user.created` |
+| `admin.users.online` | `/admin/online/users` | `member_id` | `.user.online` `.user.offline` |
+
+Echo 订阅私有频道时自动请求授权接口 `POST /admin/broadcasting/auth`:
+
+| HTTP 状态 | 含义 | 前端处理 |
+| --- | --- | --- |
+| `200` | 授权成功 | 无 |
+| `401` | 未登录或 token 已过期 | `echo.disconnect()`,走登录失效流程,不要重试 |
+| `403` | 无该列表权限、频道不存在或推送未启用 | 该页面保留 HTTP 列表,不要重试 |
+
+在订阅对象上用 `.error((err) => …)` 读取 `err.status`。
+
+## 事件与表格处理
+
+Echo 回调收到的对象:
+
+```json
+{
+  "seq": 1533,
+  "id": 2871,
+  "channel": "admin.orders.withdraw",
+  "event": "order.updated",
+  "action": "updated",
+  "key": { "id": 88 },
+  "data": { "id": 88, "member_id": "9001", "amount": 12.5, "status": 1 },
+  "source": "bot-28",
+  "created_at": "2026-10-31 10:00:00"
+}
+```
+
+示例的 `data` 仅展示部分字段。实际 `data` 与该频道 HTTP 列表返回的单行字段一致,由服务端读取推送时的最新状态;不是变化瞬间的快照。
+
+| `action` | 前端处理 |
+| --- | --- |
+| `created` / `online` | 按 `key` 插入或更新;先检查当前筛选、分页和排序 |
+| `updated` | 替换当前页已有行;更新后不满足筛选则移除;当前页没有的行可提示有更新 |
+| `deleted` / `offline` | 按 `key` 移除;此时 `data` 为 `null` |
+
+- `seq` 是全局连续的广播序号,用于去重和补发;`id` 是事件表编号,仅供排查;`key.id` 或 `key.member_id` 才是行主键,三者不能混用。
+- 用 **`seq + channel`** 去重,并限制去重缓存大小。同一笔代收订单会同时推给代收与三方充值两个频道,`seq` 相同。
+- 只有第一页、默认倒序且符合筛选时才直接把新行放到顶部;其它分页提示有新数据,保留管理员当前浏览位置。
+- 合计、总数和按端在线统计没有随行同步推送,需要时节流重新请求对应 HTTP 接口;推送不可用时继续提供原有的刷新方式。
+
+## 断线补发与重新同步
+
+```http
+GET /admin/realtime/replay?after_seq=1532&channels=admin.orders.withdraw
+Authorization: Bearer <后台管理员 token>
+```
+
+```json
+{
+  "code": 0,
+  "msg": "OK",
+  "data": {
+    "resync": false,
+    "last_seq": 1540,
+    "channels": ["admin.orders.withdraw"],
+    "events": [{ "seq": 1535, "channel": "admin.orders.withdraw", "event": "order.updated", "...": "与实时事件相同" }]
+  }
+}
+```
+
+- `events` 为 `seq > after_seq` 的事件,按 `seq` 升序,处理方式与实时事件相同(同样去重)。
+- `resync: true` 时 `events` 为空,`reason` 为 `history_unavailable`(超出保留期,默认 72 小时)、`too_many_events`(积压超过 1000 条)或 `invalid_cursor`;重新请求该频道列表即可。
+
+游标规则(必须遵守,否则多频道时会漏事件):
+
+1. 维护 `liveSeq`:初值为 `config.last_seq`,**只用 Echo 实时收到的事件推进**,补发接口返回的事件不推进它。
+2. 订阅新频道时,记下该频道的续传点 `pending[channel] = liveSeq`。
+3. 连接状态离开 `connected` 时(`echo.connector.pusher.connection.bind('state_change', …)`),给每个已订阅且没有续传点的频道记 `pending[channel] = liveSeq`。
+4. 频道订阅成功回调(`.subscribed(…)`,首次与每次重连都会触发)里,以该频道自己的续传点调补发接口,处理完后清除续传点;请求失败时直接重新请求列表。
+
+完整的连接管理器参考实现见[后台 WebSocket 完整说明](../admin-websocket.md)第 5 节,已在本机 Reverb 上验证过断线补发。
+
+## 重连与登录失效
+
+| 情况 | 处理 |
+| --- | --- |
+| 网络断开、Reverb 重启 | Echo 自动重连并重新订阅,订阅成功回调里按上节补发 |
+| 连接长时间处于 `unavailable` | 保留 HTTP 列表与手动刷新,Echo 会继续尝试 |
+| token 到期 | 服务端主动断开连接,Echo 重连时授权返回 401,按上表停止 Echo 并走登录失效流程;也可按 `session_expires_at` 提前处理 |
+| 退出登录 | `echo.disconnect()`,清空去重记录和游标 |
+
+心跳由 Echo 与 Reverb 自动完成,前端无需实现。
+
+上线联调应确认:正常账号能收到授权频道,无权限频道授权 403;重复消息不重复插行;筛选和翻页不被新行打乱;断网恢复后能补齐期间的变化;token 过期与退出登录后不会继续重连。当前说明已在本机 Reverb 上用 `laravel-echo@2.1.6`、`pusher-js@8.4.0` 验证,尚未完成生产环境与浏览器联调。
+
+完整行字段、后端部署与实现说明见[后台 WebSocket 完整说明](../admin-websocket.md)。

+ 22 - 12
example.env

@@ -125,22 +125,30 @@ ZIMU_CASH_QUERY_GATEWAY=https://api.zimu808.com/api/v1/mch/openapi/cash/cashInfo
 ZIMU_PAY_RECHARGE_USER_IDS=
 
 # 后台订单/用户列表实时推送(docs/admin-websocket.md)
-# 执行迁移并启动 realtime/admin-gateway.js 后再设为 true
+# 执行迁移,启动 php artisan reverb:start 与 php artisan admin-realtime:relay 后再设为 true
 ADMIN_WS_ENABLED=false
-# 前端连接地址(经 Nginx 反代到网关端口)
-ADMIN_WS_PUBLIC_URL=wss://admin.example.com/admin-ws
-# 连接票据签名密钥,网关与后台必须一致;留空则使用 JWT_SECRET
-ADMIN_WS_SECRET=
-# 网关调用 /admin/realtime/events 的内部密钥,必填
-ADMIN_WS_INTERNAL_SECRET=
-# 以下供网关进程读取
-ADMIN_WS_PORT=6002
-ADMIN_WS_PATH=/admin-ws
-ADMIN_WS_LARAVEL_URL=http://127.0.0.1
+# 前端 Echo 连接的公网地址(Nginx 反代 /app 到 Reverb)
+ADMIN_WS_PUBLIC_HOST=admin.example.com
+ADMIN_WS_PUBLIC_PORT=443
+ADMIN_WS_PUBLIC_SCHEME=https
 ADMIN_WS_POLL_MS=1000
 ADMIN_WS_ONLINE_SCAN_SECONDS=15
 ADMIN_WS_RETENTION_HOURS=72
 
+# Reverb(Laravel 官方 WebSocket 服务)。KEY/SECRET 用 openssl rand -hex 16 生成,SECRET 不得下发前端
+REVERB_APP_ID=bot28-admin
+REVERB_APP_KEY=
+REVERB_APP_SECRET=
+# Laravel 调用 Reverb HTTP 接口的内网地址
+REVERB_HOST=127.0.0.1
+REVERB_PORT=8080
+REVERB_SCHEME=http
+# Reverb 监听地址,只监听本机,由 Nginx 对外
+REVERB_SERVER_HOST=127.0.0.1
+REVERB_SERVER_PORT=8080
+# 允许连接的后台前端域名,逗号分隔,不含协议
+REVERB_ALLOWED_ORIGINS=admin.example.com
+
 # 数据库
 DB_CONNECTION=mysql
 DB_HOST=127.0.0.1
@@ -162,7 +170,9 @@ MAIL_PASSWORD=your_email_password
 MAIL_EXP=600 # 邮件验证码有效期(秒)
 
 # JWT配置
-JWT_SECRET=881FrcXs
+# firebase/php-jwt 7 要求 HS256 密钥至少 32 字节,过短会导致后台无法登录;更换后已登录的后台 token 全部失效
+# 生成:php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'
+JWT_SECRET=
 JWT_EXP=86400
 
 #缓存配置

+ 0 - 315
realtime/admin-gateway.js

@@ -1,315 +0,0 @@
-'use strict';
-
-/**
- * 后台实时推送网关(订单 / 用户列表),说明见 docs/admin-websocket.md。
- *
- * 1. 前端用 GET /admin/realtime/ticket 换取票据后连接 ws(s)://host/admin-ws?ticket=xxx;
- * 2. 网关用 ADMIN_WS_SECRET 校验票据签名,只允许订阅票据内的频道;
- * 3. 网关每 ADMIN_WS_POLL_MS 调用 Laravel 内部接口 /admin/realtime/events 拉取已解析好的列表行并推送。
- *
- * 配置读取顺序:进程环境变量 > 项目根目录 .env(可用 ADMIN_WS_ENV_FILE 指定)。
- */
-
-const crypto = require('crypto');
-const fs = require('fs');
-const http = require('http');
-const https = require('https');
-const path = require('path');
-const { WebSocketServer, WebSocket } = require('ws');
-
-loadEnvFile(process.env.ADMIN_WS_ENV_FILE || path.resolve(__dirname, '..', '.env'));
-
-const config = {
-    host: env('ADMIN_WS_HOST', '127.0.0.1'),
-    port: Number(env('ADMIN_WS_PORT', '6002')),
-    path: env('ADMIN_WS_PATH', '/admin-ws'),
-    laravelUrl: env('ADMIN_WS_LARAVEL_URL', env('APP_URL', 'http://127.0.0.1')).replace(/\/+$/, ''),
-    laravelHost: env('ADMIN_WS_LARAVEL_HOST', ''),
-    pollMs: Math.max(200, Number(env('ADMIN_WS_POLL_MS', '1000'))),
-    ticketSecret: env('ADMIN_WS_SECRET', '') || env('JWT_SECRET', ''),
-    internalSecret: env('ADMIN_WS_INTERNAL_SECRET', ''),
-    allowedOrigins: env('ADMIN_WS_ALLOWED_ORIGINS', '').split(',').map((s) => s.trim()).filter(Boolean),
-    replaySize: Math.max(100, Number(env('ADMIN_WS_REPLAY_SIZE', '5000'))),
-    gapTtlMs: 60000,
-    heartbeatMs: 25000,
-};
-
-if (!config.ticketSecret || !config.internalSecret) {
-    log('error', 'ADMIN_WS_SECRET(或 JWT_SECRET) 与 ADMIN_WS_INTERNAL_SECRET 必须配置');
-    process.exit(1);
-}
-
-const clients = new Set();
-const usedNonces = new Map(); // nonce => 过期时间戳,票据一次性
-const gaps = new Map(); // 未出现的事件 id => 首次发现时间
-const buffer = []; // 最近推送过的事件,用于断线重连补发
-const state = { cursor: -1, historyFrom: -1, lastPollAt: null, lastError: null, polling: false };
-
-// ---------------------------------------------------------------- WebSocket
-
-const server = http.createServer((req, res) => {
-    if (req.url === '/health') {
-        res.writeHead(200, { 'Content-Type': 'application/json' });
-        res.end(JSON.stringify({
-            ok: state.lastError === null, connections: clients.size, cursor: state.cursor, history_from: state.historyFrom,
-            gaps: gaps.size, buffered: buffer.length, last_poll_at: state.lastPollAt, last_error: state.lastError,
-        }));
-        return;
-    }
-    res.writeHead(404);
-    res.end();
-});
-
-const wss = new WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
-
-server.on('upgrade', (req, socket, head) => {
-    const url = new URL(req.url, 'http://localhost');
-    if (url.pathname !== config.path) {
-        socket.destroy();
-        return;
-    }
-    if (config.allowedOrigins.length && !config.allowedOrigins.includes(req.headers.origin)) {
-        socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
-        socket.destroy();
-        return;
-    }
-    const ticket = verifyTicket(url.searchParams.get('ticket'));
-    wss.handleUpgrade(req, socket, head, (ws) => {
-        // 握手完成后再关闭,前端可凭 4401 区分「票据无效,需要重新换票」
-        if (!ticket) {
-            ws.close(4401, 'invalid ticket');
-            return;
-        }
-        onConnection(ws, ticket, req);
-    });
-});
-
-function onConnection(ws, ticket, req) {
-    const client = {
-        ws, adminId: ticket.aid, allowed: new Set(ticket.ch || []), subs: new Set(), alive: true, timer: null,
-        ip: req.headers['x-forwarded-for'] || req.socket.remoteAddress,
-    };
-    clients.add(client);
-    if (ticket.sxp) {
-        const ms = ticket.sxp * 1000 - Date.now();
-        client.timer = setTimeout(() => ws.close(4401, 'session expired'), Math.max(0, Math.min(ms, 2147483647)));
-    }
-    log('info', `connect admin=${client.adminId} ip=${client.ip} total=${clients.size}`);
-
-    ws.on('pong', () => { client.alive = true; });
-    ws.on('message', (raw) => onMessage(client, raw));
-    ws.on('close', () => {
-        clearTimeout(client.timer);
-        clients.delete(client);
-        log('info', `disconnect admin=${client.adminId} total=${clients.size}`);
-    });
-    ws.on('error', () => {});
-
-    send(client, { type: 'welcome', admin_id: client.adminId, channels: [...client.allowed], last_event_id: state.cursor, server_time: now() });
-}
-
-function onMessage(client, raw) {
-    let msg;
-    try {
-        msg = JSON.parse(String(raw));
-    } catch (e) {
-        send(client, { type: 'error', message: 'invalid json' });
-        return;
-    }
-    const channels = Array.isArray(msg.channels) ? msg.channels.map(String) : [];
-    switch (msg.type) {
-        case 'ping':
-            send(client, { type: 'pong', server_time: now() });
-            return;
-        case 'subscribe': {
-            const granted = channels.filter((c) => client.allowed.has(c));
-            const denied = channels.filter((c) => !client.allowed.has(c));
-            granted.forEach((c) => client.subs.add(c));
-            send(client, { type: 'subscribed', channels: granted, denied, last_event_id: state.cursor });
-            if (granted.length && msg.last_event_id !== undefined && msg.last_event_id !== null) {
-                replay(client, granted, Number(msg.last_event_id));
-            }
-            return;
-        }
-        case 'unsubscribe':
-            channels.forEach((c) => client.subs.delete(c));
-            send(client, { type: 'unsubscribed', channels });
-            return;
-        default:
-            send(client, { type: 'error', message: 'unknown type' });
-    }
-}
-
-/** 断线期间的事件仍在缓冲区内就补发,否则通知前端重新拉列表 */
-function replay(client, channels, lastEventId) {
-    if (!Number.isFinite(lastEventId) || state.historyFrom < 0 || lastEventId < state.historyFrom) {
-        send(client, { type: 'resync', channels, reason: 'history_unavailable' });
-        return;
-    }
-    const wanted = new Set(channels);
-    buffer.filter((e) => e.id > lastEventId && wanted.has(e.channel)).forEach((e) => send(client, e));
-}
-
-function broadcast(event) {
-    for (const client of clients) {
-        if (client.subs.has(event.channel)) send(client, event);
-    }
-}
-
-function send(client, payload) {
-    if (client.ws.readyState === WebSocket.OPEN) client.ws.send(JSON.stringify(payload));
-}
-
-setInterval(() => {
-    for (const client of clients) {
-        if (!client.alive) {
-            client.ws.terminate();
-            continue;
-        }
-        client.alive = false;
-        client.ws.ping();
-    }
-    const t = now();
-    for (const [nonce, exp] of usedNonces) if (exp < t) usedNonces.delete(nonce);
-}, config.heartbeatMs).unref();
-
-// ---------------------------------------------------------------- 票据
-
-function verifyTicket(ticket) {
-    const [body, sig] = String(ticket || '').split('.');
-    if (!body || !sig) return null;
-    const expected = crypto.createHmac('sha256', config.ticketSecret).update(body).digest('base64url');
-    if (expected.length !== sig.length || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) return null;
-    let payload;
-    try {
-        payload = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
-    } catch (e) {
-        return null;
-    }
-    if (!payload || !payload.aid || !payload.exp || payload.exp < now() || !payload.nonce || usedNonces.has(payload.nonce)) return null;
-    if (payload.sxp && payload.sxp < now()) return null;
-    usedNonces.set(payload.nonce, payload.exp);
-    return payload;
-}
-
-// ---------------------------------------------------------------- 拉取事件
-
-async function poll() {
-    if (state.polling) return;
-    state.polling = true;
-    let delay = config.pollMs;
-    try {
-        const query = new URLSearchParams({ after: String(state.cursor) });
-        if (gaps.size) query.set('gaps', [...gaps.keys()].slice(0, 500).join(','));
-        const body = await getJson(`${config.laravelUrl}/admin/realtime/events?${query}`);
-        if (Number(body.code) !== 0) throw new Error(`code=${body.code} msg=${body.msg}`);
-        handleBatch(body.data || {});
-        state.lastError = null;
-        if (body.data && body.data.has_more) delay = 0;
-    } catch (e) {
-        if (state.lastError !== e.message) log('error', `poll failed: ${e.message}`);
-        state.lastError = e.message;
-        delay = Math.min(config.pollMs * 5, 10000);
-    } finally {
-        state.lastPollAt = new Date().toISOString();
-        state.polling = false;
-        setTimeout(poll, delay);
-    }
-}
-
-function handleBatch(data) {
-    const lastId = Number(data.last_id || 0);
-    const seen = new Set((data.ids || []).map(Number));
-    seen.forEach((id) => gaps.delete(id));
-
-    if (state.cursor < 0) {
-        // 刚启动:从当前最大 id 开始,不回放历史
-        state.cursor = lastId;
-        state.historyFrom = lastId;
-        log('info', `start from event id ${lastId}`);
-        return;
-    }
-    // 跳过的 id 可能是写入方事务尚未提交,记为 gap 稍后补拉
-    if (lastId > state.cursor && lastId - state.cursor <= 10000) {
-        const t = Date.now();
-        for (let id = state.cursor + 1; id < lastId; id++) if (!seen.has(id)) gaps.set(id, t);
-    }
-    state.cursor = Math.max(state.cursor, lastId);
-    const t = Date.now();
-    for (const [id, at] of gaps) if (t - at > config.gapTtlMs) gaps.delete(id);
-
-    for (const event of data.events || []) {
-        const message = { type: 'event', ...event };
-        buffer.push(message);
-        broadcast(message);
-    }
-    if (buffer.length > config.replaySize) {
-        const removed = buffer.splice(0, buffer.length - config.replaySize);
-        state.historyFrom = Math.max(state.historyFrom, removed[removed.length - 1].id);
-    }
-}
-
-function getJson(url) {
-    return new Promise((resolve, reject) => {
-        const target = new URL(url);
-        const headers = { Accept: 'application/json', 'X-Realtime-Secret': config.internalSecret };
-        if (config.laravelHost) headers.Host = config.laravelHost;
-        const req = (target.protocol === 'https:' ? https : http).request(target, { method: 'GET', headers, timeout: 20000 }, (res) => {
-            let raw = '';
-            res.setEncoding('utf8');
-            res.on('data', (chunk) => { raw += chunk; });
-            res.on('end', () => {
-                if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
-                try {
-                    resolve(JSON.parse(raw));
-                } catch (e) {
-                    reject(new Error('invalid json from laravel'));
-                }
-            });
-        });
-        req.on('timeout', () => req.destroy(new Error('timeout')));
-        req.on('error', reject);
-        req.end();
-    });
-}
-
-// ---------------------------------------------------------------- 工具
-
-function now() {
-    return Math.floor(Date.now() / 1000);
-}
-
-function env(key, fallback) {
-    const value = process.env[key];
-    return value === undefined || value === '' ? fallback : value;
-}
-
-function loadEnvFile(file) {
-    if (!fs.existsSync(file)) return;
-    for (const line of fs.readFileSync(file, 'utf8').split(/\r?\n/)) {
-        const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
-        if (!match || process.env[match[1]] !== undefined) continue;
-        let value = match[2];
-        if (/^(['"]).*\1$/.test(value)) value = value.slice(1, -1);
-        else value = value.replace(/\s+#.*$/, '').trim();
-        process.env[match[1]] = value;
-    }
-}
-
-function log(level, message) {
-    (level === 'error' ? console.error : console.log)(`[${new Date().toISOString()}] [admin-ws] ${level}: ${message}`);
-}
-
-function shutdown() {
-    for (const client of clients) client.ws.close(1012, 'server restart');
-    server.close(() => process.exit(0));
-    setTimeout(() => process.exit(0), 3000).unref();
-}
-
-process.on('SIGTERM', shutdown);
-process.on('SIGINT', shutdown);
-
-server.listen(config.port, config.host, () => {
-    log('info', `listening on ${config.host}:${config.port}${config.path}, laravel=${config.laravelUrl}`);
-    poll();
-});

+ 0 - 37
realtime/package-lock.json

@@ -1,37 +0,0 @@
-{
-  "name": "bot-28-admin-realtime",
-  "lockfileVersion": 3,
-  "requires": true,
-  "packages": {
-    "": {
-      "name": "bot-28-admin-realtime",
-      "dependencies": {
-        "ws": "^8.18.0"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/ws": {
-      "version": "8.22.1",
-      "resolved": "https://registry.npmmirror.com/ws/-/ws-8.22.1.tgz",
-      "integrity": "sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=10.0.0"
-      },
-      "peerDependencies": {
-        "bufferutil": "^4.0.1",
-        "utf-8-validate": ">=5.0.2"
-      },
-      "peerDependenciesMeta": {
-        "bufferutil": {
-          "optional": true
-        },
-        "utf-8-validate": {
-          "optional": true
-        }
-      }
-    }
-  }
-}

+ 0 - 15
realtime/package.json

@@ -1,15 +0,0 @@
-{
-  "name": "bot-28-admin-realtime",
-  "private": true,
-  "description": "后台订单/用户列表实时推送网关,见 docs/admin-websocket.md",
-  "main": "admin-gateway.js",
-  "scripts": {
-    "start": "node admin-gateway.js"
-  },
-  "engines": {
-    "node": ">=18"
-  },
-  "dependencies": {
-    "ws": "^8.18.0"
-  }
-}

+ 5 - 4
routes/admin.php

@@ -76,8 +76,8 @@ Route::prefix('/sync')->group(function () {
     Route::get('/exchange', [Sync::class, 'exchange']);
 });
 
-// 后台实时推送网关内部拉取事件(X-Realtime-Secret 鉴权,网关每秒调用,不走 api 限流)
-Route::get('/realtime/events', [Realtime::class, 'events'])->withoutMiddleware('throttle:api');
+// 后台实时推送:Echo 私有频道授权(控制器内校验后台 JWT,失败返回 401/403;每次连接按频道各调用一次,不走 api 限流)
+Route::post('/broadcasting/auth', [Realtime::class, 'auth'])->withoutMiddleware('throttle:api');
 
 Route::get('/fake', [Bet::class, 'fake']);
 Route::get('/fakel', [Issue::class, 'fakeLotteryDraw']);
@@ -558,8 +558,9 @@ Route::middleware(['admin.jwt'])->group(function () {
     });
 
 
-    // 后台实时推送:换取 WebSocket 连接票据(登录即可,频道按列表按钮权限过滤)
-    Route::get('/realtime/ticket', [Realtime::class, 'ticket']);
+    // 后台实时推送:Echo 连接参数与断线补发(登录即可,频道按列表按钮权限过滤)
+    Route::get('/realtime/config', [Realtime::class, 'config']);
+    Route::get('/realtime/replay', [Realtime::class, 'replay']);
 
     Route::prefix('/upload')->group(function () {
         Route::post('/uploadFile', [Upload::class, 'store']);

+ 1 - 5
tests/Integration/ActivityCatalogTest.php

@@ -46,11 +46,7 @@ class ActivityCatalogTest extends TestCase
         $translator = new Translator(new ArrayLoader(), 'zh');
         $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app));
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) {
-                return new JsonResponse($data, $status, $headers, $options);
-            }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) {
             return app('validator')->make($this->all(), $rules)->validate();
         });

+ 11 - 9
tests/Integration/ActivityRewardEligibilityTest.php

@@ -24,6 +24,7 @@ use Illuminate\Translation\ArrayLoader;
 use Illuminate\Translation\Translator;
 use Illuminate\Validation\Factory;
 use Illuminate\Validation\DatabasePresenceVerifier;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 /** Runs real controllers and services against SQLite only; never boots application providers. */
@@ -50,11 +51,7 @@ class ActivityRewardEligibilityTest extends TestCase
         $validator = new Factory($translator, $this->app);
         $validator->setPresenceVerifier(new DatabasePresenceVerifier($db->getDatabaseManager()));
         $this->app->instance('validator', $validator);
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) {
-                return new JsonResponse($data, $status, $headers, $options);
-            }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) {
             return app('validator')->make($this->all(), $rules)->validate();
         });
@@ -305,7 +302,13 @@ class ActivityRewardEligibilityTest extends TestCase
         DB::setDefaultConnection('mysql_filter_preview');
         try {
             $connection = DB::connection();
-            $connection->setPdo(static function () { throw new \RuntimeException('This SQL preview must never connect to a database.'); });
+            // Laravel 11+ 的 MySQL 语法编译需要服务器版本;用只回答版本、拒绝执行 SQL 的替身,仍保证不连库
+            $connection->setPdo(new class('sqlite::memory:') extends \PDO {
+                public function getAttribute(int $attribute): mixed { return $attribute === \PDO::ATTR_SERVER_VERSION ? '8.0.36' : parent::getAttribute($attribute); }
+                public function prepare(string $query, array $options = []): \PDOStatement|false { throw new \RuntimeException('This SQL preview must never connect to a database.'); }
+                public function query(string $query, ?int $fetchMode = null, mixed ...$fetchModeArgs): \PDOStatement|false { throw new \RuntimeException('This SQL preview must never connect to a database.'); }
+                public function exec(string $statement): int|false { throw new \RuntimeException('This SQL preview must never connect to a database.'); }
+            });
             $this->request('/admin/ActivityReward', ['page' => 1, 'limit' => 10, 'platform' => 'h5', 'status' => '0', 'lang' => 'en']);
             $queries = $connection->pretend(function () {
                 $result = (new AdminActivityReward())->index()->getData(true);
@@ -731,7 +734,7 @@ class ActivityRewardEligibilityTest extends TestCase
         $this->assertSame('充值', DB::table('balance_logs')->where('type', 1)->value('change_type'));
     }
 
-    /** @dataProvider automaticRewardWriteFailures */
+    #[DataProvider('automaticRewardWriteFailures')]
     public function test_automatic_reward_failure_rolls_back_recipients_count_bonus_ledger_and_wallet(string $category, string $failure): void
     {
         [$recharge, $return] = $this->automaticRewardFixtures();
@@ -889,7 +892,7 @@ class ActivityRewardEligibilityTest extends TestCase
     public function test_award_locks_activity_then_recipient_for_current_read_but_precheck_does_not_lock(): void
     {
         $activity = $this->activity(['reward_quota' => 1]);
-        $grammar = new class extends \Illuminate\Database\Query\Grammars\SQLiteGrammar {
+        $grammar = new class(DB::connection()) extends \Illuminate\Database\Query\Grammars\SQLiteGrammar {
             public array $reads = [];
 
             public function compileSelect(\Illuminate\Database\Query\Builder $query)
@@ -900,7 +903,6 @@ class ActivityRewardEligibilityTest extends TestCase
                 return parent::compileSelect($query);
             }
         };
-        $grammar->setTablePrefix(DB::connection()->getTablePrefix());
         DB::connection()->setQueryGrammar($grammar);
 
         $this->assertTrue(ActivityQuotaService::canAward($activity, '1001'));

+ 121 - 33
tests/Integration/AdminRealtimeTest.php

@@ -8,7 +8,11 @@ use App\Models\Withdraw;
 use App\Services\AdminRealtime\AdminRealtime;
 use App\Services\AdminRealtime\AdminRealtimeFeed;
 use App\Services\AdminRealtime\AdminRealtimeRows;
-use App\Services\AdminRealtime\AdminRealtimeTicket;
+use App\Services\AdminRealtime\AdminRealtimeAccess;
+use App\Services\JwtService;
+use App\Http\Controllers\admin\Realtime as RealtimeController;
+use Illuminate\Broadcasting\BroadcastManager;
+use Illuminate\Contracts\Broadcasting\Factory as BroadcastingFactory;
 use Carbon\Carbon;
 use Illuminate\Cache\ArrayStore;
 use Illuminate\Cache\Repository as CacheRepository;
@@ -42,7 +46,8 @@ class AdminRealtimeTest extends TestCase
         $this->app = new Application(dirname(__DIR__, 2));
         $this->app->instance('config', new Repository([
             'app' => ['locale' => 'zh', 'timezone' => 'Asia/Shanghai'],
-            'admin_realtime' => ['enabled' => true, 'ticket_secret' => 'ticket-secret', 'ticket_ttl' => 60, 'public_url' => 'wss://admin.test/admin-ws', 'batch_size' => 200, 'online_scan_seconds' => 15, 'retention_hours' => 72],
+            'admin_realtime' => ['enabled' => true, 'connection' => 'reverb', 'batch_size' => 200, 'gap_seconds' => 60, 'replay_limit' => 1000,
+                'permission_cache_seconds' => 60, 'online_scan_seconds' => 15, 'retention_hours' => 72],
         ]));
         Facade::setFacadeApplication($this->app);
         $events = new Dispatcher($this->app); $this->app->instance('events', $events);
@@ -60,13 +65,12 @@ class AdminRealtimeTest extends TestCase
         };
         $this->app->instance('log', $this->log);
         $this->app->instance('cache', new CacheRepository(new ArrayStore()));
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) { return app('validator')->make($this->all(), $rules)->validate(); });
         $this->app->instance('request', Request::create('/admin/realtime/events', 'GET'));
 
         (require dirname(__DIR__, 2) . '/database/migrations/2026_10_31_160000_create_admin_realtime_events.php')->up();
+        (require dirname(__DIR__, 2) . '/database/migrations/2026_10_31_210000_add_seq_to_admin_realtime_events.php')->up();
         Schema::create('users', function (Blueprint $t) {
             $t->id(); $t->string('member_id'); $t->string('user_id')->default(''); $t->string('first_name')->default('');
             $t->string('admin_note')->nullable(); $t->string('language')->nullable(); $t->integer('recharge_channel_group_id')->nullable(); $t->timestamps();
@@ -135,19 +139,20 @@ class AdminRealtimeTest extends TestCase
 
     public function test_feed_resolves_rows_with_the_list_endpoint_shape(): void
     {
-        $this->assertSame(['last_id' => 0, 'ids' => [], 'events' => [], 'has_more' => false], AdminRealtimeFeed::pull(-1));
+        $this->assertSame(0, AdminRealtimeFeed::startCursor());
+        $this->assertSame(['cursor' => 0, 'events' => [], 'has_more' => false], AdminRealtimeFeed::next(0));
 
         $withdraw = Withdraw::create(['member_id' => '9001', 'amount' => 12.5, 'to_account' => 12.5]);
         $withdraw->update(['status' => 1]);
         Withdraw::create(['member_id' => '9001', 'amount' => 3]);
-        $result = AdminRealtimeFeed::pull(0);
+        $result = AdminRealtimeFeed::next(0);
 
-        $this->assertSame(3, $result['last_id']);
-        $this->assertSame([1, 2, 3], $result['ids']);
+        $this->assertSame(3, $result['cursor']);
+        $this->assertSame([1, 2, 3], DB::table('admin_realtime_events')->orderBy('id')->pluck('seq')->map(fn ($v) => (int) $v)->all());
         $this->assertCount(2, $result['events'], '同一记录一批内合并');
         $first = $result['events'][0];
-        $this->assertSame(['id' => 2, 'channel' => 'admin.orders.withdraw', 'event' => 'order.created', 'action' => 'created', 'key' => ['id' => 1]],
-            array_intersect_key($first, array_flip(['id', 'channel', 'event', 'action', 'key'])));
+        $this->assertSame(['seq' => 2, 'id' => 2, 'channel' => 'admin.orders.withdraw', 'event' => 'order.created', 'action' => 'created', 'key' => ['id' => 1]],
+            array_intersect_key($first, array_flip(['seq', 'id', 'channel', 'event', 'action', 'key'])));
 
         $listRequest = Request::create('/admin/withdraw', 'GET');
         $this->app->instance('request', $listRequest);
@@ -158,31 +163,114 @@ class AdminRealtimeTest extends TestCase
         $this->assertSame(1, $first['data']['status']);
 
         $withdraw->delete();
-        $deleted = AdminRealtimeFeed::pull(3)['events'];
-        $this->assertSame([['channel' => 'admin.orders.withdraw', 'action' => 'deleted', 'key' => ['id' => 1], 'data' => null]],
-            array_map(fn ($e) => array_intersect_key($e, array_flip(['channel', 'action', 'key', 'data'])), $deleted));
-
-        $gapped = AdminRealtimeFeed::pull(4, ['2']);
-        $this->assertSame([2], $gapped['ids'], '按 gaps 补拉已处理游标之前的事件');
-        $this->assertSame(4, $gapped['last_id']);
+        $deleted = AdminRealtimeFeed::next(3)['events'];
+        $this->assertSame([['seq' => 4, 'channel' => 'admin.orders.withdraw', 'action' => 'deleted', 'key' => ['id' => 1], 'data' => null]],
+            array_map(fn ($e) => array_intersect_key($e, array_flip(['seq', 'channel', 'action', 'key', 'data'])), $deleted));
+        $this->assertSame(4, AdminRealtimeFeed::startCursor(), '重启后从上次广播到的 id 继续');
         $this->assertNotEmpty(array_filter($this->log->records, fn ($r) => $r[1] === 'admin_realtime_online_scan_failed'),
-            'SQLite 不支持在线判定 SQL,扫描失败只记日志不影响拉取');
+            'SQLite 不支持在线判定 SQL,扫描失败只记日志不影响广播');
+    }
+
+    public function test_feed_delivers_late_commits_with_a_later_seq(): void
+    {
+        $insert = fn (int $id, string $at) => DB::table('admin_realtime_events')->insert([
+            'id' => $id, 'resource' => 'withdraws', 'resource_id' => (string) $id, 'action' => 'deleted', 'source' => 'melbet', 'created_at' => $at,
+        ]);
+        $insert(1, '2026-10-31 09:59:59');
+        $insert(3, '2026-10-31 09:59:59');
+        $this->assertSame([1, 3], array_column(AdminRealtimeFeed::next(0)['events'], 'id'));
+
+        $insert(2, '2026-10-31 09:59:30');
+        $insert(4, '2026-10-31 09:58:00');
+        $insert(5, '2026-10-31 09:58:00');
+        $batch = AdminRealtimeFeed::next(3);
+        $this->assertSame([[3, 2], [4, 4], [5, 5]], array_map(fn ($e) => [$e['seq'], $e['id']], $batch['events']),
+            '游标之前晚提交的 id 仍会广播,seq 按广播先后递增');
+        $this->assertSame(5, $batch['cursor']);
+
+        $insert(6, '2026-10-31 10:00:00');
+        $insert(7, '2026-10-31 10:00:00');
+        DB::table('admin_realtime_events')->where('id', 6)->delete();
+        AdminRealtimeFeed::next(5);
+        $insert(6, '2026-10-31 09:58:00');
+        $this->assertSame([], AdminRealtimeFeed::next(7)['events'], '超过 gap_seconds 的晚提交不再补发');
+    }
+
+    public function test_replay_by_seq_filters_channels_and_requests_resync(): void
+    {
+        $withdraw = Withdraw::create(['member_id' => '9001', 'amount' => 1]);
+        Withdraw::create(['member_id' => '9001', 'amount' => 2]);
+        User::query()->create(['member_id' => '9003', 'first_name' => 'Carol']);
+        $withdraw->delete();
+        AdminRealtimeFeed::next(0);
+        $this->assertSame(4, AdminRealtimeFeed::lastSeq());
+
+        $replay = AdminRealtimeFeed::replay(1, ['admin.orders.withdraw']);
+        $this->assertFalse($replay['resync']);
+        $this->assertSame(4, $replay['last_seq']);
+        $this->assertSame([[2, 'order.created'], [4, 'order.deleted']], array_map(fn ($e) => [$e['seq'], $e['event']], $replay['events']),
+            '只返回请求的频道,并按当前数据解析');
+        $this->assertSame([], AdminRealtimeFeed::replay(4, ['admin.orders.withdraw'])['events']);
+        $this->assertSame('invalid_cursor', AdminRealtimeFeed::replay(9, ['admin.orders.withdraw'])['reason']);
+
+        $this->app['config']->set('admin_realtime.replay_limit', 2);
+        $this->assertSame('too_many_events', AdminRealtimeFeed::replay(0, ['admin.orders.withdraw'])['reason']);
+
+        DB::table('admin_realtime_events')->where('seq', 1)->delete();
+        $this->assertSame('history_unavailable', AdminRealtimeFeed::replay(1, ['admin.orders.withdraw'])['reason'], '游标已被清理时重新拉列表');
+    }
+
+    public function test_channel_auth_signs_session_user_for_reverb(): void
+    {
+        $this->bindBroadcasting();
+        Schema::create('admin', function (Blueprint $t) { $t->id(); $t->string('username'); $t->timestamps(); });
+        DB::table('admin')->insert(['id' => 1, 'username' => 'root']);
+        $exp = time() + 3600;
+        $token = \Firebase\JWT\JWT::encode(['iat' => time(), 'exp' => $exp, 'sub' => 1, 'user_id' => 1], str_repeat('k', 32), 'HS256');
+
+        $call = function (?string $token, string $channel) {
+            $request = Request::create('/admin/broadcasting/auth', 'POST', ['socket_id' => '123.456', 'channel_name' => $channel]);
+            if ($token) $request->headers->set('Authorization', 'Bearer ' . $token);
+            $this->app->instance('request', $request);
+            return (new RealtimeController())->auth(new JwtService());
+        };
+
+        $response = $call($token, 'private-admin.orders.withdraw');
+        $this->assertSame(200, $response->getStatusCode());
+        $body = $response->getData(true);
+        $this->assertSame(['user_id' => "1:{$exp}"], json_decode($body['channel_data'], true));
+        // 与 Reverb 校验私有频道的签名算法一致(InteractsWithPrivateChannels::verify)
+        $this->assertSame('app-key:' . hash_hmac('sha256', "123.456:private-admin.orders.withdraw:{$body['channel_data']}", 'app-secret'), $body['auth']);
+
+        $this->assertSame(401, $call(null, 'private-admin.orders.withdraw')->getStatusCode());
+        $this->assertSame(401, $call($token . 'x', 'private-admin.orders.withdraw')->getStatusCode());
+        $this->assertSame(403, $call($token, 'private-admin.orders.unknown')->getStatusCode());
+        $this->assertSame(403, $call($token, 'admin.orders.withdraw')->getStatusCode(), '只授权私有频道');
+        $this->assertSame(array_keys(AdminRealtimeRows::CHANNELS), AdminRealtimeAccess::allowedChannels(1));
+    }
+
+    public function test_expired_sessions_are_terminated_once(): void
+    {
+        $this->bindBroadcasting();
+        AdminRealtimeAccess::authorize(7, time() - 1, 'private-admin.orders.withdraw', '1.1');
+        AdminRealtimeAccess::authorize(7, time() - 1, 'private-admin.users', '1.1');
+        AdminRealtimeAccess::authorize(8, time() + 600, 'private-admin.users', '2.2');
+
+        $this->assertSame(['7:' . (time() - 1)], AdminRealtimeAccess::terminateExpiredSessions());
+        $this->assertSame([], AdminRealtimeAccess::terminateExpiredSessions(), '已断开的会话不重复处理');
+        $this->assertNotEmpty(array_filter($this->log->records, fn ($r) => $r[1] === 'admin_realtime_terminate_failed'),
+            'Reverb 不可达时只记日志');
     }
 
-    public function test_ticket_is_signed_for_gateway_and_lists_channels(): void
+    private function bindBroadcasting(): void
     {
-        $ticket = AdminRealtimeTicket::issue(1, 1893456000);
-        [$body, $sig] = explode('.', $ticket['ticket']);
-        $this->assertSame(rtrim(strtr(base64_encode(hash_hmac('sha256', $body, 'ticket-secret', true)), '+/', '-_'), '='), $sig);
-        $payload = json_decode(base64_decode(strtr($body, '-_', '+/')), true);
-        $this->assertSame(1, $payload['aid']);
-        $this->assertSame(1893456000, $payload['sxp']);
-        $this->assertSame(array_keys(AdminRealtimeRows::CHANNELS), $payload['ch']);
-        $this->assertSame('wss://admin.test/admin-ws', $ticket['url']);
-
-        $node = trim((string) shell_exec('command -v node'));
-        if ($node === '') $this->markTestIncomplete('node 不可用,跳过网关签名互通校验');
-        $script = "const c=require('crypto');const [b,s]=process.argv[1].split('.');process.stdout.write(String(c.createHmac('sha256','ticket-secret').update(b).digest('base64url')===s))";
-        $this->assertSame('true', shell_exec(escapeshellarg($node) . ' -e ' . escapeshellarg($script) . ' ' . escapeshellarg($ticket['ticket'])));
+        $this->app['config']->set('broadcasting.connections.reverb', [
+            'driver' => 'reverb', 'key' => 'app-key', 'secret' => 'app-secret', 'app_id' => 'app-id',
+            'options' => ['host' => '127.0.0.1', 'port' => 1, 'scheme' => 'http', 'useTLS' => false],
+            'client_options' => ['connect_timeout' => 1, 'timeout' => 1],
+        ]);
+        $this->app['config']->set('app.jwt_secret', str_repeat('k', 32));
+        $this->app['config']->set('app.jwt_exp', 3600);
+        $this->app->instance(BroadcastingFactory::class, new BroadcastManager($this->app));
     }
 }

+ 1 - 5
tests/Integration/AgentApiRegressionTest.php

@@ -70,11 +70,7 @@ class AgentApiRegressionTest extends TestCase
         Request::macro('validate', function (array $rules) {
             return app('validator')->make($this->all(), $rules)->validate();
         });
-        $responses = new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) {
-                return new JsonResponse($data, $status, $headers, $options);
-            }
-        };
+        $responses = new \Tests\Support\JsonResponseFactory();
         $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, $responses);
         Schema::create('users', function (Blueprint $t) {
             $t->id(); $t->string('member_id'); $t->string('username'); $t->string('first_name')->default('');

+ 1 - 3
tests/Integration/AgentApplicationTest.php

@@ -52,9 +52,7 @@ class AgentApplicationTest extends TestCase
         $validator = new Factory($translator, $this->app);
         $validator->setPresenceVerifier(new \Illuminate\Validation\DatabasePresenceVerifier($db->getDatabaseManager()));
         $this->app->instance('validator', $validator);
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) { return app('validator')->make($this->all(), $rules)->validate(); });
         Schema::create('users', function (Blueprint $t) {
             $t->id(); $t->string('user_id')->unique(); $t->string('member_id')->unique(); $t->string('account');

+ 3 - 4
tests/Integration/AgentPageConfigurationTest.php

@@ -17,6 +17,7 @@ use Illuminate\Translation\ArrayLoader;
 use Illuminate\Translation\Translator;
 use Illuminate\Validation\Factory;
 use Illuminate\Validation\ValidationException;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 class AgentPageConfigurationTest extends TestCase
@@ -35,9 +36,7 @@ class AgentPageConfigurationTest extends TestCase
         $translator = new Translator(new ArrayLoader(), 'zh'); $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app));
         $this->app->instance('log', new \Psr\Log\NullLogger());
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         $request = Request::create('/admin/agent/pageConfig'); $request->user = (object)['id' => 1, 'username' => 'admin']; $this->app->instance('request', $request);
         DB::statement('CREATE TABLE bot_operation_audits (id INTEGER PRIMARY KEY AUTOINCREMENT,resource_type TEXT,resource_id INTEGER,action TEXT,changes TEXT,operator_id INTEGER,operator_name TEXT,created_at TEXT)');
         $this->migration()->up();
@@ -88,7 +87,7 @@ class AgentPageConfigurationTest extends TestCase
         foreach (['<table>', '<td style="color: red">30%</td>', '中文', 'href="https://t.me/customer_service"', 'rel="noopener noreferrer"'] as $safe) $this->assertStringContainsString($safe, $html);
     }
 
-    /** @dataProvider invalidValues */
+    #[DataProvider('invalidValues')]
     public function test_invalid_values_do_not_partially_save(string $field, $invalid): void
     {
         $before = $this->service->save($this->payload());

+ 4 - 5
tests/Integration/AnnouncementTest.php

@@ -21,6 +21,7 @@ use Illuminate\Translation\ArrayLoader;
 use Illuminate\Translation\Translator;
 use Illuminate\Validation\Factory;
 use Illuminate\Validation\ValidationException;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 /** Actual admin controller, migration, publication and audit queries, with isolated SQLite only. */
@@ -46,9 +47,7 @@ class AnnouncementTest extends TestCase
         $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app));
         $this->app->instance('log', new \Psr\Log\NullLogger());
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) { return app('validator')->make($this->all(), $rules)->validate(); });
         Schema::create('operation_audits', function (Blueprint $t) {
             $t->id(); $t->string('resource_type'); $t->integer('resource_id'); $t->string('action'); $t->json('changes');
@@ -235,7 +234,7 @@ class AnnouncementTest extends TestCase
         $this->assertSame(1, $this->service->listing(['category' => 'update'])['total']);
     }
 
-    /** @dataProvider invalidInputs */
+    #[DataProvider('invalidInputs')]
     public function test_invalid_input_is_rejected_without_announcement_or_log_writes(array $values): void
     {
         try { $this->create($values); $this->fail('Expected invalid input'); }
@@ -303,7 +302,7 @@ class AnnouncementTest extends TestCase
         $this->assertSame(0, $saved['code']); $this->assertArrayNotHasKey('activity_id', $saved['data']);
     }
 
-    /** @dataProvider saveErrors */
+    #[DataProvider('saveErrors')]
     public function test_save_failures_report_the_reason_without_exposing_sql(string $state, int $code, string $expected): void
     {
         $this->app->instance('log', new \Psr\Log\NullLogger());

+ 3 - 6
tests/Integration/AutomaticRewardGrantTest.php

@@ -27,6 +27,7 @@ use Illuminate\Support\Facades\Schema;
 use Illuminate\Translation\ArrayLoader;
 use Illuminate\Translation\Translator;
 use Illuminate\Validation\Factory;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Tester\CommandTester;
 
@@ -53,11 +54,7 @@ class AutomaticRewardGrantTest extends TestCase
         $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app));
         $this->app->instance('log', new \Psr\Log\NullLogger());
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) {
-                return new JsonResponse($data, $status, $headers, $options);
-            }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) {
             return app('validator')->make($this->all(), $rules)->validate();
         });
@@ -188,7 +185,7 @@ class AutomaticRewardGrantTest extends TestCase
         $this->assertSame(0, (new RewardGrantSettings())->rebate()->getData(true)['code']);
     }
 
-    /** @dataProvider invalidSettings */
+    #[DataProvider('invalidSettings')]
     public function test_invalid_settings_do_not_change_the_switch_or_time(array $values): void
     {
         $this->request('/admin/rebate/grant-settings', $values, 'POST');

+ 14 - 18
tests/Integration/BannerPublicationTest.php

@@ -45,11 +45,7 @@ class BannerPublicationTest extends TestCase
         $translator = new Translator(new ArrayLoader(), 'zh');
         $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app));
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) {
-                return new JsonResponse($data, $status, $headers, $options);
-            }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) {
             return app('validator')->make($this->all(), $rules)->validate();
         });
@@ -97,7 +93,7 @@ class BannerPublicationTest extends TestCase
         return (new AdminBanner())->update()->getData(true);
     }
 
-    private function status(array $params): array
+    private function setStatus(array $params): array
     {
         $this->request('status', $params);
         return (new AdminBanner())->status()->getData(true);
@@ -166,7 +162,7 @@ class BannerPublicationTest extends TestCase
         $this->assertSame('app', $banner->type);
         $this->assertSame('en', $banner->lang);
         $this->assertSame(27, (int) $banner->sort);
-        $this->assertSame(HttpStatus::OK, $this->status(['id' => $banner->id, 'status' => 0])['code']);
+        $this->assertSame(HttpStatus::OK, $this->setStatus(['id' => $banner->id, 'status' => 0])['code']);
         $result = $this->save(['id' => $banner->id, 'title' => '编辑标题', 'type' => 'app', 'lang' => 'en']);
         $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
         $banner->refresh();
@@ -185,7 +181,7 @@ class BannerPublicationTest extends TestCase
         $id = $this->banner(['sort' => 19, 'lang' => 'en', 'link' => 'https://example.test']);
         foreach (['status', 'manual_status'] as $field) {
             foreach ([0, 0, 1, 1, '0', '1'] as $status) {
-                $result = $this->status(['id' => $id, $field => $status]);
+                $result = $this->setStatus(['id' => $id, $field => $status]);
                 $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
                 $row = DB::table('banner')->find($id);
                 $this->assertSame((int) $status, (int) $row->status);
@@ -212,7 +208,7 @@ class BannerPublicationTest extends TestCase
             $this->assertSame($expected, (int) DB::table('banner')->where('id', $id)->value('status'));
         }
         foreach ([[0, 1], [1, 0]] as [$canonical, $manual]) {
-            $result = $this->status(['id' => $id, 'status' => $canonical, 'manual_status' => $manual]);
+            $result = $this->setStatus(['id' => $id, 'status' => $canonical, 'manual_status' => $manual]);
             $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
             $this->assertSame($canonical, (int) DB::table('banner')->where('id', $id)->value('status'));
             $result = $this->save(['id' => $id, 'title' => '兼容完整编辑', 'status' => $canonical, 'manual_status' => $manual]);
@@ -251,10 +247,10 @@ class BannerPublicationTest extends TestCase
             $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
             $row = $assertPublication($id, 0);
 
-            $result = $this->status(['id' => $id, 'status' => 1, 'manual_status' => $row['manual_status']]);
+            $result = $this->setStatus(['id' => $id, 'status' => 1, 'manual_status' => $row['manual_status']]);
             $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
             $row = $assertPublication($id, 1);
-            $result = $this->status(['id' => $id, 'status' => 0, 'manual_status' => $row['manual_status']]);
+            $result = $this->setStatus(['id' => $id, 'status' => 0, 'manual_status' => $row['manual_status']]);
             $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
             $row = $assertPublication($id, 0);
 
@@ -287,7 +283,7 @@ class BannerPublicationTest extends TestCase
         foreach ([['status' => 1, 'manual_status' => 'invalid'], ['status' => 'invalid', 'manual_status' => 1]] as $fields) {
             $result = $this->save($fields + ['id' => $id, 'title' => '不应保存']);
             $this->assertSame(HttpStatus::VALIDATION_FAILED, $result['code']);
-            $result = $this->status($fields + ['id' => $id]);
+            $result = $this->setStatus($fields + ['id' => $id]);
             $this->assertSame(HttpStatus::VALIDATION_FAILED, $result['code']);
             $this->assertSame(0, (int) DB::table('banner')->where('id', $id)->value('status'));
             $this->assertSame('轮播图', DB::table('banner')->where('id', $id)->value('title'));
@@ -307,10 +303,10 @@ class BannerPublicationTest extends TestCase
             ['id' => 'invalid', 'status' => 0], ['manual_status' => 0],
             ['id' => $id, 'manual_status' => null], ['id' => $id, 'manual_status' => -1],
             ['id' => $id, 'manual_status' => 2], ['id' => $id, 'manual_status' => 'invalid']] as $params) {
-            $this->assertSame(HttpStatus::VALIDATION_FAILED, $this->status($params)['code']);
+            $this->assertSame(HttpStatus::VALIDATION_FAILED, $this->setStatus($params)['code']);
             $this->assertSame(1, (int) DB::table('banner')->where('id', $id)->value('status'));
         }
-        $result = $this->status(['id' => 999999, 'status' => 0]);
+        $result = $this->setStatus(['id' => 999999, 'status' => 0]);
         $this->assertSame(HttpStatus::CUSTOM_ERROR, $result['code']);
         $this->assertStringContainsString('数据不存在', $result['msg']);
         $this->assertSame(1, DB::table('banner')->count());
@@ -430,7 +426,7 @@ class BannerPublicationTest extends TestCase
             $before = (array) DB::table('banner')->find($id);
             foreach (['status', 'manual_status'] as $field) {
                 foreach ([0, 1] as $value) {
-                    $result = $this->status(['id' => $id, $field => $value]);
+                    $result = $this->setStatus(['id' => $id, $field => $value]);
                     $this->assertSame(HttpStatus::CUSTOM_ERROR, $result['code']);
                     $this->assertStringContainsString('关联活动', $result['msg']);
                     $this->assertStringContainsString('不能手动上下架', $result['msg']);
@@ -438,7 +434,7 @@ class BannerPublicationTest extends TestCase
                 }
             }
             foreach ([['status' => null], ['manual_status' => null], ['status' => 'invalid'], ['manual_status' => []]] as $fields) {
-                $result = $this->status(['id' => $id] + $fields);
+                $result = $this->setStatus(['id' => $id] + $fields);
                 $this->assertSame(HttpStatus::VALIDATION_FAILED, $result['code']);
                 $this->assertSame($before, (array) DB::table('banner')->find($id));
             }
@@ -479,7 +475,7 @@ class BannerPublicationTest extends TestCase
         $result = $this->save(['id' => $id, 'link' => null]);
         $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
         $this->assertState($this->decorated($now)[$id], 0, 0, 'manual', null);
-        $result = $this->status(['id' => $id, 'manual_status' => 1]);
+        $result = $this->setStatus(['id' => $id, 'manual_status' => 1]);
         $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
         $this->assertState($this->decorated($now)[$id], 1, 1, null, null);
         $result = $this->save(['id' => $id, 'link' => '7', 'status' => 0, 'manual_status' => 0]);
@@ -523,7 +519,7 @@ class BannerPublicationTest extends TestCase
         $this->assertState($this->decorated(time())[$id], null, 0, 'activity_missing', '500');
         foreach (['status', 'manual_status'] as $field) {
             foreach ([0, 1] as $value) {
-                $result = $this->status(['id' => $id, $field => $value]);
+                $result = $this->setStatus(['id' => $id, $field => $value]);
                 $this->assertSame(HttpStatus::CUSTOM_ERROR, $result['code']);
                 $this->assertStringContainsString('关联活动', $result['msg']);
                 $this->assertStringContainsString('不能手动上下架', $result['msg']);

+ 3 - 4
tests/Integration/MaintenanceTest.php

@@ -24,6 +24,7 @@ use Illuminate\Translation\ArrayLoader;
 use Illuminate\Translation\Translator;
 use Illuminate\Validation\Factory;
 use Illuminate\Validation\ValidationException;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 class MaintenanceTest extends TestCase
@@ -43,9 +44,7 @@ class MaintenanceTest extends TestCase
         $this->app->bind('db.schema', fn () => $db->schema());
         $translator = new Translator(new ArrayLoader(), 'zh'); $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app)); $this->app->instance('log', new \Psr\Log\NullLogger());
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) { return app('validator')->make($this->all(), $rules)->validate(); });
         Schema::create('operation_audits', function (Blueprint $t) {
             $t->id(); $t->string('resource_type'); $t->integer('resource_id'); $t->string('action'); $t->json('changes');
@@ -153,7 +152,7 @@ class MaintenanceTest extends TestCase
         $this->assertSame(1, $this->service->listing(['scope_code' => 'third:2'])['total']);
     }
 
-    /** @dataProvider invalidInputs */
+    #[DataProvider('invalidInputs')]
     public function test_invalid_schedules_scopes_and_payloads_leave_no_changes(array $input): void
     {
         $before = (array) DB::table('maintenance_settings')->where('id', $this->id())->first();

+ 1 - 3
tests/Integration/MemberTokenIdleTimeoutTest.php

@@ -34,9 +34,7 @@ class MemberTokenIdleTimeoutTest extends TestCase
         $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']);
         $db->setAsGlobal(); $db->bootEloquent(); $app->instance('db', $db->getDatabaseManager());
         $app->instance('translator', new Translator(new ArrayLoader(), 'zh'));
-        $app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); }
-        });
+        $app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         if (!function_exists('create_token') || !function_exists('lang')) require_once dirname(__DIR__, 2) . '/app/Helpers/helpers.php';
         $db->schema()->create('users', function (Blueprint $t) { $t->id(); $t->string('user_id'); $t->string('member_id'); $t->integer('is_banned')->default(0); $t->bigInteger('delete_time')->nullable(); });
         $db->schema()->create('user_session', function (Blueprint $t) {

+ 1 - 5
tests/Integration/MenuIconTest.php

@@ -37,11 +37,7 @@ class MenuIconTest extends TestCase
         $translator = new Translator(new ArrayLoader(), 'zh');
         $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app));
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) {
-                return new JsonResponse($data, $status, $headers, $options);
-            }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) {
             return app('validator')->make($this->all(), $rules)->validate();
         });

+ 1 - 5
tests/Integration/PaymentChannelOptionsTest.php

@@ -43,11 +43,7 @@ class PaymentChannelOptionsTest extends TestCase
         $translator = new Translator(new ArrayLoader(), 'zh');
         $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app));
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) {
-                return new JsonResponse($data, $status, $headers, $options);
-            }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) {
             return app('validator')->make($this->all(), $rules)->validate();
         });

+ 1 - 3
tests/Integration/PromotionActivitiesTest.php

@@ -39,9 +39,7 @@ class PromotionActivitiesTest extends TestCase
         $this->app->instance('translator', new Translator(new ArrayLoader(), 'zh'));
         $this->app->instance('validator', new Factory($this->app['translator'], $this->app));
         $this->app->instance('log', new \Psr\Log\NullLogger()); $this->app->instance('request', Request::create('/api/promotion/progress'));
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new \Illuminate\Http\JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function ($rules) { return app('validator')->make($this->all(), $rules)->validate(); });
         foreach ([
             'activity_rewards' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,title TEXT,sub_title TEXT,type INTEGER,status INTEGER,start_time INTEGER,end_time INTEGER,lang TEXT,params TEXT,detail_image TEXT,pc_image TEXT,detail_banner TEXT,pc_banner TEXT,content TEXT,created_at TEXT,updated_at TEXT',

+ 1 - 3
tests/Integration/RechargeBonusTest.php

@@ -37,9 +37,7 @@ class RechargeBonusTest extends TestCase
         $db->setEventDispatcher(new \Illuminate\Events\Dispatcher($this->app)); $db->setAsGlobal(); $db->bootEloquent(); $this->app->instance('db', $db->getDatabaseManager()); $this->app->bind('db.schema', fn () => $db->schema());
         $this->app->instance('translator', new Translator(new ArrayLoader(), 'zh'));
         $this->app->instance('validator', new Factory($this->app['translator'], $this->app)); $this->app->instance('log', new \Psr\Log\NullLogger());
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new \Illuminate\Http\JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function ($rules) { return app('validator')->make($this->all(), $rules)->validate(); });
         $request = Request::create('/admin/rechargeBonus/save', 'POST'); $request->user = (object)['id' => 1, 'username' => 'admin']; $this->app->instance('request', $request);
         foreach ([

+ 6 - 7
tests/Integration/ReportingTest.php

@@ -22,6 +22,7 @@ use Illuminate\Translation\ArrayLoader;
 use Illuminate\Translation\Translator;
 use Illuminate\Validation\Factory;
 use Illuminate\Validation\ValidationException;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 /** Real source adapters, financial reconciliation, filters and aggregation; isolated SQLite only. */
@@ -41,9 +42,7 @@ class ReportingTest extends TestCase
         $db->setEventDispatcher(new Dispatcher($this->app)); $db->setAsGlobal(); $db->bootEloquent(); $this->app->instance('db', $db->getDatabaseManager()); $this->app->bind('db.schema', fn () => $db->schema());
         $translator = new Translator(new ArrayLoader(), 'zh'); $this->app->instance('translator', $translator); $this->app->instance('validator', new Factory($translator, $this->app));
         $this->app['validator']->setPresenceVerifier(new \Illuminate\Validation\DatabasePresenceVerifier($db->getDatabaseManager()));
-        $this->app->instance('log', new \Psr\Log\NullLogger()); $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance('log', new \Psr\Log\NullLogger()); $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function ($rules) { return app('validator')->make($this->all(), $rules)->validate(); });
         $request = Request::create('/admin/reporting/summary'); $request->user = (object) ['id' => 1, 'username' => 'admin']; $this->app->instance('request', $request);
         foreach ([
@@ -192,7 +191,7 @@ class ReportingTest extends TestCase
         ];
     }
 
-    /** @dataProvider invalidF1Receipts */
+    #[DataProvider('invalidF1Receipts')]
     public function test_unrelated_or_unbalanced_receipts_do_not_fabricate_lottery_settlement_time(array $receipt): void
     {
         $this->f1LotteryWithoutCompletionContext(); $this->f1PayoutReceipt($receipt);
@@ -611,7 +610,7 @@ class ReportingTest extends TestCase
         return $cases;
     }
 
-    /** @dataProvider thirdPartyReportCases */
+    #[DataProvider('thirdPartyReportCases')]
     public function test_third_party_categories_use_provider_settlement_time_and_preserve_net_payout_math(int $type, int $status, string $net, string $stake, string $payout, string $refund, string $gross): void
     {
         DB::statement('CREATE TABLE bot_third_game_orders (id INTEGER PRIMARY KEY,order_key TEXT,member_id TEXT,user_id INTEGER,game_type INTEGER,bet_amount TEXT,settled_amount TEXT,status INTEGER,currency TEXT,bet_time TEXT,last_update_time TEXT,created_at TEXT,updated_at TEXT)');
@@ -648,7 +647,7 @@ class ReportingTest extends TestCase
         ];
     }
 
-    /** @dataProvider sportsReportCases */
+    #[DataProvider('sportsReportCases')]
     public function test_f1_sports_uses_official_settlement_or_refund_time(int $status, int $settlement, int $returnStatus, string $win, string $returned, string $stake, string $payout, string $refund, string $gross): void
     {
         DB::statement('CREATE TABLE bot_sport_order (id INTEGER PRIMARY KEY,user_id TEXT,amount TEXT,win_amount TEXT,return_amount TEXT,status INTEGER,pay_status INTEGER,settlement_status INTEGER,return_status INTEGER,create_time INTEGER,update_time INTEGER,settlement_time INTEGER,return_operation_time INTEGER)');
@@ -838,7 +837,7 @@ class ReportingTest extends TestCase
         $this->assertSame(1, $this->reader->events(['member_account' => 'member2', 'kind' => 'deposit'])['total']);
     }
 
-    /** @dataProvider invalidQueries */
+    #[DataProvider('invalidQueries')]
     public function test_invalid_filters_do_not_query_unbounded_dates(array $input): void { $this->expectException(ValidationException::class); $this->reader->summary($input); }
     public static function invalidQueries(): array { return [[['start_date' => '2026-02-30']], [['end_date' => '2026-10-27']], [['start_date' => '2020-01-01']],
         [['platform' => 'ios']], [['settlement_status' => 'paid']], [['limit' => 999]], [['start_date' => []]], [['end_date' => []]],

+ 3 - 7
tests/Integration/SettleLedgerTest.php

@@ -18,6 +18,7 @@ use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Facade;
 use Illuminate\Translation\ArrayLoader;
 use Illuminate\Translation\Translator;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Psr\Log\NullLogger;
 
@@ -41,12 +42,7 @@ class SettleLedgerTest extends TestCase
         $this->app->instance('db', $db->getDatabaseManager());
         $this->app->instance('cache', new CacheRepository(new ArrayStore()));
         $this->app->instance('log', new NullLogger());
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0): JsonResponse
-            {
-                return new JsonResponse($data, $status, $headers, $options);
-            }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
 
         foreach (['pc_issues', 'issues'] as $table) {
             $db->schema()->create($table, function (Blueprint $t) {
@@ -112,7 +108,7 @@ class SettleLedgerTest extends TestCase
         ];
     }
 
-    /** @dataProvider settlementGames */
+    #[DataProvider('settlementGames')]
     public function test_settlement_ledger_matches_wallet_and_accumulated_consumption(string $game, string $issueTable, string $betType): void
     {
         DB::table($issueTable)->insert([

+ 1 - 3
tests/Integration/ShareAdminManagementTest.php

@@ -49,9 +49,7 @@ class ShareAdminManagementTest extends TestCase
         $translator = new Translator(new ArrayLoader(), 'zh');
         $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app));
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) { return app('validator')->make($this->all(), $rules)->validate(); });
         $db->schema()->create('share_settings', function (Blueprint $t) {
             $t->id(); $t->integer('enabled')->default(0); $t->integer('benefit_enabled')->default(0);

+ 10 - 13
tests/Integration/ShareCommissionRecordingTest.php

@@ -23,6 +23,7 @@ use Illuminate\Support\Facades\Facade;
 use Illuminate\Translation\ArrayLoader;
 use Illuminate\Translation\Translator;
 use Illuminate\Validation\Factory;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Tester\CommandTester;
 
@@ -45,11 +46,7 @@ class ShareCommissionRecordingTest extends TestCase
         $translator = new Translator(new ArrayLoader(), 'zh');
         $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app));
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) {
-                return new JsonResponse($data, $status, $headers, $options);
-            }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) {
             return app('validator')->make($this->all(), $rules)->validate();
         });
@@ -130,7 +127,7 @@ class ShareCommissionRecordingTest extends TestCase
         return [['充值'], ['三方充值']];
     }
 
-    /** @dataProvider depositTypes */
+    #[DataProvider('depositTypes')]
     public function test_existing_rebate_payment_registers_real_commission_and_is_visible_in_summary(string $type): void
     {
         BalanceLogService::addLog('1002', '100.00', '0.00', '100.00', $type, 88, '');
@@ -259,7 +256,7 @@ class ShareCommissionRecordingTest extends TestCase
         ];
     }
 
-    /** @dataProvider unapprovedManualOrders */
+    #[DataProvider('unapprovedManualOrders')]
     public function test_unapproved_or_unrelated_order_cannot_be_credited_by_manual_deposit_audit(int $status, int $type): void
     {
         $this->manualOrder($status, $type);
@@ -302,7 +299,7 @@ class ShareCommissionRecordingTest extends TestCase
         return [['人工充值', 'PROMO', 1], ['优惠活动', 'PROMO', 1], ['充值', '', 1], ['充值', 'MISSING', 1], ['充值', 'PROMO', 2]];
     }
 
-    /** @dataProvider unrelatedDeposits */
+    #[DataProvider('unrelatedDeposits')]
     public function test_recording_does_not_expand_the_existing_payment_rules(string $type, string $code, int $from): void
     {
         DB::table('users')->where('member_id', '1002')->update(['agent_user_code' => $code, 'from' => $from]);
@@ -420,7 +417,7 @@ class ShareCommissionRecordingTest extends TestCase
         ];
     }
 
-    /** @dataProvider invalidHistoricalLogs */
+    #[DataProvider('invalidHistoricalLogs')]
     public function test_invalid_historical_logs_are_reported_instead_of_booked(array $extra, string $reason): void
     {
         $rebate = $this->historicalRebate($extra);
@@ -554,7 +551,7 @@ class ShareCommissionRecordingTest extends TestCase
         ];
     }
 
-    /** @dataProvider brokenReferralStages */
+    #[DataProvider('brokenReferralStages')]
     public function test_diagnostics_locate_real_user_referral_and_wallet_gaps(int $from, string $code, bool $missingWallet, array $expected): void
     {
         DB::table('users')->where('member_id', '1002')->update(['from' => $from, 'agent_user_code' => $code]);
@@ -630,7 +627,7 @@ class ShareCommissionRecordingTest extends TestCase
         ];
     }
 
-    /** @dataProvider clearedReportDates */
+    #[DataProvider('clearedReportDates')]
     public function test_summary_without_dates_includes_all_history_and_keeps_pagination_and_global_totals(array $dates): void
     {
         $this->summaryHistory();
@@ -656,7 +653,7 @@ class ShareCommissionRecordingTest extends TestCase
         ];
     }
 
-    /** @dataProvider selectedSummaryDates */
+    #[DataProvider('selectedSummaryDates')]
     public function test_summary_explicit_dates_apply_to_list_count_and_totals(string $start, string $end, string $commission, string $feeRate): void
     {
         $this->summaryHistory();
@@ -742,7 +739,7 @@ class ShareCommissionRecordingTest extends TestCase
         })->getData(true);
     }
 
-    /** @dataProvider clearedReportDates */
+    #[DataProvider('clearedReportDates')]
     public function test_members_without_dates_includes_historical_registration_days_without_backfill(array $dates): void
     {
         $this->memberHistory();

+ 1 - 1
tests/Integration/SharingBetFactsTest.php

@@ -221,7 +221,7 @@ class SharingBetFactsTest extends TestCase
         DB::table('share_system_state')->insert(['key' => 'relations_lock', 'value' => '1']);
         $changed = false;
         DB::listen(function ($event) use (&$changed) {
-            if (!$changed && str_contains($event->sql, 'from "bot_lhc_order" order by "id"')) {
+            if (!$changed && preg_match('/from "bot_lhc_order" (where "id" is not null )?order by "id"/', $event->sql)) {
                 $changed = true;
                 DB::table('lhc_order')->where('id', 10)->update(['lottery_status' => 4, 'updated_at' => Carbon::now()->timestamp]);
             }

+ 5 - 4
tests/Integration/SharingSettlementTest.php

@@ -13,6 +13,7 @@ use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Foundation\Application;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Facade;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Tester\CommandTester;
 
@@ -324,7 +325,7 @@ class SharingSettlementTest extends TestCase
         ];
     }
 
-    /** @dataProvider recoveryKillPoints */
+    #[DataProvider('recoveryKillPoints')]
     public function test_sigkill_and_fresh_service_resume_persisted_dates(string $date,string $point,array $pending,string $earned): void
     {
         if (!function_exists('posix_kill') || !defined('SIGKILL') || !function_exists('proc_open')) $this->markTestSkipped('Requires POSIX signals and subprocesses');
@@ -504,7 +505,7 @@ class SharingSettlementTest extends TestCase
         foreach ($tables as $table) $this->assertSame($before[$table], DB::table($table)->get()->toJson(), $table);
     }
 
-    /** @dataProvider forbiddenDebugOptions */
+    #[DataProvider('forbiddenDebugOptions')]
     public function test_future_dates_and_incompatible_debug_options_are_rejected_before_writing(array $options): void
     {
         $this->fact(2, '200', '2026-10-25');
@@ -578,7 +579,7 @@ class SharingSettlementTest extends TestCase
         $this->assertSame(0,DB::table('share_ledger')->count());
     }
 
-    /** @dataProvider retiredGates */
+    #[DataProvider('retiredGates')]
     public function test_complete_policy_settles_immediately_without_legacy_switch_or_activation_date($activation,int $benefit): void
     {
         DB::table('share_settings')->update(['activation_date'=>$activation,'benefit_enabled'=>$benefit]);
@@ -612,7 +613,7 @@ class SharingSettlementTest extends TestCase
         $this->assertSame(['missing_effective_bet_threshold'],array_column($diagnosis['blockers'],'code'));
     }
 
-    /** @dataProvider diagnosticExclusions */
+    #[DataProvider('diagnosticExclusions')]
     public function test_diagnose_explains_the_same_member_and_relationship_filters_as_settlement(string $reason): void
     {
         $this->fact(2,'200','2026-10-25');

+ 3 - 4
tests/Integration/SiteConfigurationTest.php

@@ -17,6 +17,7 @@ use Illuminate\Translation\ArrayLoader;
 use Illuminate\Translation\Translator;
 use Illuminate\Validation\Factory;
 use Illuminate\Validation\ValidationException;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 class SiteConfigurationTest extends TestCase
@@ -35,9 +36,7 @@ class SiteConfigurationTest extends TestCase
         $translator = new Translator(new ArrayLoader(), 'zh'); $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app));
         $this->app->instance('log', new \Psr\Log\NullLogger());
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         $request = Request::create('/admin/site/config'); $request->user = (object) ['id' => 1, 'username' => 'admin']; $this->app->instance('request', $request);
         DB::statement('CREATE TABLE bot_operation_audits (id INTEGER PRIMARY KEY AUTOINCREMENT,resource_type TEXT,resource_id INTEGER,action TEXT,changes TEXT,operator_id INTEGER,operator_name TEXT,created_at TEXT)');
         DB::statement('CREATE TABLE bot_menus (id INTEGER PRIMARY KEY,title TEXT)');
@@ -74,7 +73,7 @@ class SiteConfigurationTest extends TestCase
         $this->assertSame($this->emptyConfig(), $this->service->save(['translations' => [], 'domains' => []]));
     }
 
-    /** @dataProvider invalidPayloads */
+    #[DataProvider('invalidPayloads')]
     public function test_invalid_configuration_does_not_partially_save(array $payload): void
     {
         try { $this->service->save($payload); $this->fail('Expected validation failure'); }

+ 3 - 4
tests/Integration/SiteMessageTest.php

@@ -24,6 +24,7 @@ use Illuminate\Translation\ArrayLoader;
 use Illuminate\Translation\Translator;
 use Illuminate\Validation\Factory;
 use Illuminate\Validation\ValidationException;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 /** Real dispatch SQL, translations, ownership, retraction and audit transactions; SQLite only. */
@@ -46,9 +47,7 @@ class SiteMessageTest extends TestCase
         $this->app->bind('db.schema', fn () => $db->schema());
         $translator = new Translator(new ArrayLoader(), 'zh'); $this->app->instance('translator', $translator);
         $this->app->instance('validator', new Factory($translator, $this->app)); $this->app->instance('log', new \Psr\Log\NullLogger());
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) { return app('validator')->make($this->all(), $rules)->validate(); });
         Schema::create('users', function (Blueprint $t) {
             $t->id(); $t->string('user_id'); $t->string('member_id'); $t->string('account'); $t->string('username'); $t->string('first_name');
@@ -196,7 +195,7 @@ class SiteMessageTest extends TestCase
         $this->assertSame(1, $this->service->dispatchDue()['sent']); $this->assertSame(2, DB::table('site_message_recipients')->count());
     }
 
-    /** @dataProvider invalidInputs */
+    #[DataProvider('invalidInputs')]
     public function test_invalid_messages_never_leave_partial_rows(array $input): void
     {
         try { $this->send($input); $this->fail('Expected validation failure'); } catch (ValidationException $e) { $this->assertNotEmpty($e->errors()); }

+ 1 - 5
tests/Integration/UsdtRechargeConfigTest.php

@@ -42,11 +42,7 @@ class UsdtRechargeConfigTest extends TestCase
         $this->app->instance('db', $db->getDatabaseManager());
         $this->app->instance('translator', new Translator(new ArrayLoader(), 'zh'));
         $this->app->instance('validator', new Factory($this->app['translator'], $this->app));
-        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
-            public function json($data, $status = 200, $headers = [], $options = 0) {
-                return new JsonResponse($data, $status, $headers, $options);
-            }
-        });
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
         Request::macro('validate', function (array $rules) {
             return app('validator')->make($this->all(), $rules)->validate();
         });

+ 13 - 0
tests/Support/JsonResponseFactory.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Tests\Support;
+
+use Illuminate\Routing\ResponseFactory;
+
+/** 隔离测试只需要 response()->json();Laravel 13 起 response() 要求返回真正的 ResponseFactory。 */
+class JsonResponseFactory extends ResponseFactory
+{
+    public function __construct()
+    {
+    }
+}

+ 2 - 1
tests/Unit/PromotionAmountFormatterTest.php

@@ -4,11 +4,12 @@ namespace Tests\Unit;
 
 use App\Support\PromotionAmountFormatter;
 use Illuminate\Support\Collection;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 class PromotionAmountFormatterTest extends TestCase
 {
-    /** @dataProvider amounts */
+    #[DataProvider('amounts')]
     public function test_amounts_use_two_truncated_decimals_but_hide_a_zero_fraction($input, $expected): void
     {
         $this->assertSame($expected, PromotionAmountFormatter::amount($input));

+ 3 - 2
tests/Unit/SharingEffectiveBetTest.php

@@ -4,17 +4,18 @@ namespace Tests\Unit;
 
 use App\Services\Sharing\EffectiveBet;
 use App\Services\BetService;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 class SharingEffectiveBetTest extends TestCase
 {
-    /** @dataProvider outcomes */
+    #[DataProvider('outcomes')]
     public function test_calculates_per_bet_net_exposure(string $stake, string $profit, bool $settled, bool $voided, string $expected): void
     {
         $this->assertSame($expected, EffectiveBet::amount($stake, $profit, $settled, $voided));
     }
 
-    public function outcomes(): array
+    public static function outcomes(): array
     {
         return [
             ['100', '80', true, false, '80.0000000000'],

+ 18 - 3
tests/Unit/SharingFootballSettlementTest.php

@@ -2,6 +2,7 @@
 
 namespace Tests\Unit;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 class SharingFootballSettlementTest extends TestCase
@@ -9,10 +10,24 @@ class SharingFootballSettlementTest extends TestCase
     protected function setUp(): void
     {
         parent::setUp();
-        require_once dirname(__DIR__, 3) . '/melbet_sport-api/app/common/service/FootballSettlementService.php';
+        $servicePath = '/app/common/service/FootballSettlementService.php';
+        $siteRoot = getenv('SHARING_TP_ROOT');
+        if (!$siteRoot) {
+            // 支持主检出旁的同级仓库,也支持仓库内部的嵌套工作树。
+            $ancestor = dirname(__DIR__, 2);
+            do {
+                $ancestor = dirname($ancestor);
+                $siteRoot = $ancestor . '/melbet_sport-api';
+            } while (!is_file($siteRoot . $servicePath) && $ancestor !== dirname($ancestor));
+        }
+        $serviceFile = rtrim($siteRoot, '/\\') . $servicePath;
+        if (!is_file($serviceFile)) {
+            throw new \RuntimeException('FootballSettlementService.php not found; set SHARING_TP_ROOT to the melbet_sport-api repository.');
+        }
+        require_once $serviceFile;
     }
 
-    /** @dataProvider footballOutcomes */
+    #[DataProvider('footballOutcomes')]
     public function test_actual_wallet_return_includes_unlost_principal(string $selection, string $expectedProfit, string $expectedPayout): void
     {
         $detail = ['odds' => [['values' => [['value' => $selection, 'odd' => '1.80']]]]];
@@ -22,7 +37,7 @@ class SharingFootballSettlementTest extends TestCase
         $this->assertSame($expectedPayout, \app\common\service\FootballSettlementService::payoutAmount($result));
     }
 
-    public function footballOutcomes(): array
+    public static function footballOutcomes(): array
     {
         return [
             ['Over 1.5', '80', '180.00'],

+ 4 - 3
tests/Unit/ThirdGamePlayerIdTest.php

@@ -7,6 +7,7 @@ use InvalidArgumentException;
 use Illuminate\Config\Repository;
 use Illuminate\Foundation\Application;
 use Illuminate\Support\Facades\Facade;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 class ThirdGamePlayerIdTest extends TestCase
@@ -26,7 +27,7 @@ class ThirdGamePlayerIdTest extends TestCase
         Facade::clearResolvedInstances(); Facade::setFacadeApplication(null); parent::tearDown();
     }
 
-    /** @dataProvider playerIds */
+    #[DataProvider('playerIds')]
     public function test_member_player_ids_meet_provider_length_without_changing_valid_accounts(int $id, string $expected): void
     {
         $actual = (new ThirdGameBalanceService())->playerIdFromUserId($id);
@@ -40,7 +41,7 @@ class ThirdGamePlayerIdTest extends TestCase
             [1000, 'u1000'], [751434, 'u751434'], [9999999999, 'u9999999999']];
     }
 
-    /** @dataProvider invalidIds */
+    #[DataProvider('invalidIds')]
     public function test_invalid_user_ids_fail_locally_instead_of_producing_an_invalid_provider_account(int $id): void
     {
         $this->expectException(InvalidArgumentException::class);
@@ -66,7 +67,7 @@ class ThirdGamePlayerIdTest extends TestCase
         $service->playerIdFromUserId(1000000000);
     }
 
-    /** @dataProvider invalidPrefixes */
+    #[DataProvider('invalidPrefixes')]
     public function test_invalid_prefixes_do_not_fall_back_to_production(string $prefix): void
     {
         $this->app['config']->set('third_game.player_prefix', $prefix);