From df124eb51c0d6cde0f58326500fcddc5cf2a4d15 Mon Sep 17 00:00:00 2001 From: Fabien83560 Date: Fri, 5 Jun 2026 09:37:48 +0200 Subject: [PATCH] fix(provider): silence all apiforge errors to never block requests --- src/Laravel/ApiForgeMiddleware.php | 32 ++++++++--- src/Laravel/ApiForgeServiceProvider.php | 76 ++++++++++++++++--------- 2 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src/Laravel/ApiForgeMiddleware.php b/src/Laravel/ApiForgeMiddleware.php index f6b558b..4c71e8c 100644 --- a/src/Laravel/ApiForgeMiddleware.php +++ b/src/Laravel/ApiForgeMiddleware.php @@ -32,22 +32,36 @@ public function __construct( public function handle(Request $request, Closure $next): Response { $start = hrtime(true); - $inflight = $this->incrementInflight(); + $inflight = 1; + + try { + $inflight = $this->incrementInflight(); + } catch (\Throwable $e) { + error_log('[apiforgephp] incrementInflight error: ' . $e->getMessage()); + } try { $response = $next($request); } finally { - $this->decrementInflight(); + try { + $this->decrementInflight(); + } catch (\Throwable $e) { + error_log('[apiforgephp] decrementInflight error: ' . $e->getMessage()); + } } - $duration = (hrtime(true) - $start) / 1_000_000; - $path = '/' . ltrim($request->path(), '/'); - - if (!in_array($path, $this->ignorePaths, true)) { - $sampled = $this->sampling >= 1.0 || (mt_rand() / mt_getrandmax()) <= $this->sampling; - if ($sampled) { - $this->record($request, $response, $duration, $inflight); + try { + $duration = (hrtime(true) - $start) / 1_000_000; + $path = '/' . ltrim($request->path(), '/'); + + if (!in_array($path, $this->ignorePaths, true)) { + $sampled = $this->sampling >= 1.0 || (mt_rand() / mt_getrandmax()) <= $this->sampling; + if ($sampled) { + $this->record($request, $response, $duration, $inflight); + } } + } catch (\Throwable $e) { + error_log('[apiforgephp] middleware record error: ' . $e->getMessage()); } return $response; diff --git a/src/Laravel/ApiForgeServiceProvider.php b/src/Laravel/ApiForgeServiceProvider.php index c63723a..487c71d 100644 --- a/src/Laravel/ApiForgeServiceProvider.php +++ b/src/Laravel/ApiForgeServiceProvider.php @@ -59,48 +59,70 @@ public function boot(): void } $isCloud = (bool) config('apiforge.cloud_url'); + if (!$isCloud && config('apiforge.dashboard_enabled', true)) { - $this->registerDashboardRoutes(); + try { + $this->registerDashboardRoutes(); + } catch (\Throwable $e) { + error_log('[apiforgephp] dashboard registration error: ' . $e->getMessage()); + } } if ($isCloud) { - $this->app->booted(fn() => $this->syncKnownRoutes()); + $this->app->booted(function (): void { + try { + $this->syncKnownRoutes(); + } catch (\Throwable $e) { + error_log('[apiforgephp] syncKnownRoutes error: ' . $e->getMessage()); + } + }); } } private function syncKnownRoutes(): void { - $cloudUrl = config('apiforge.cloud_url'); - $apiKey = config('apiforge.api_key'); - if (!$cloudUrl || !$apiKey) { - return; - } + try { + $cloudUrl = config('apiforge.cloud_url'); + $apiKey = config('apiforge.api_key'); + if (!$cloudUrl || !$apiKey) { + return; + } - // Use a flag file to avoid re-registering on every request - $flag = sys_get_temp_dir() . '/apiforgephp_routes_' . substr(md5((string) $apiKey), 0, 8) . '.flag'; - if (file_exists($flag) && (time() - (int) filemtime($flag)) < 3600) { - return; - } + // Include effective UID so a flag created by root (e.g. artisan in a deploy + // container) does not block php-fpm running as www-data. + $uid = function_exists('posix_geteuid') ? (string) posix_geteuid() : '0'; + $flag = sys_get_temp_dir() . '/apiforgephp_routes_' . substr(md5((string) $apiKey . '|' . $uid), 0, 8) . '.flag'; - touch($flag); + if (file_exists($flag) && (time() - (int) filemtime($flag)) < 3600) { + return; + } + + // @touch: a permission error (cross-user /tmp flag, read-only FS…) must + // never propagate — route sync is best-effort, not request-critical. + if (@touch($flag) === false) { + return; + } - $routes = []; - foreach (\Illuminate\Support\Facades\Route::getRoutes() as $route) { - foreach ($route->methods() as $method) { - if (in_array($method, ['HEAD', 'OPTIONS'], true)) { - continue; + $routes = []; + foreach (\Illuminate\Support\Facades\Route::getRoutes() as $route) { + foreach ($route->methods() as $method) { + if (in_array($method, ['HEAD', 'OPTIONS'], true)) { + continue; + } + $routes[] = ['route' => '/' . ltrim($route->uri(), '/'), 'method' => $method]; } - $routes[] = ['route' => '/' . ltrim($route->uri(), '/'), 'method' => $method]; } - } - if (!empty($routes)) { - $transport = new CloudTransport( - (string) $cloudUrl, - (string) $apiKey, - (string) config('apiforge.service', 'default'), - ); - $transport->writeRoutes($routes); + if (!empty($routes)) { + $transport = new CloudTransport( + (string) $cloudUrl, + (string) $apiKey, + (string) config('apiforge.service', 'default'), + ); + $transport->writeRoutes($routes); + } + } catch (\Throwable $e) { + error_log('[apiforgephp] syncKnownRoutes skipped: ' . $e->getMessage()); } }