method(); $isMutating = in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE']); // For PUT/PATCH/DELETE, capture the current state before the request runs $oldValues = null; $modelType = null; $modelId = null; if ($isMutating && in_array($method, ['PUT', 'PATCH', 'DELETE'])) { $resolvedModel = $this->resolveRouteModel($request); if ($resolvedModel) { $modelType = get_class($resolvedModel); $modelId = $resolvedModel->getKey(); if ($method !== 'POST') { $oldValues = $resolvedModel->toArray(); } } } // Execute the request $response = $next($request); // Only log if the request was successful (2xx status) if ($isMutating && $response->getStatusCode() >= 200 && $response->getStatusCode() < 300) { $action = $this->determineAction($method, $request); $newValues = null; // For POST/PUT/PATCH, capture new values from the model if (in_array($method, ['POST', 'PUT', 'PATCH'])) { $resolvedModel = $this->resolveRouteModel($request); if ($resolvedModel) { $modelType = get_class($resolvedModel); $modelId = $resolvedModel->getKey(); $newValues = $resolvedModel->toArray(); } elseif ($method === 'POST') { // For POST with resource creation, try to get the model from the response redirect $createdModel = $this->extractCreatedModel($request, $response); if ($createdModel) { $modelType = get_class($createdModel); $modelId = $createdModel->getKey(); $newValues = $createdModel->toArray(); } } } // Filter out sensitive fields from values $sensitiveFields = ['password', 'password_hash', 'remember_token', 'national_code', 'bank_account', 'base_salary']; $oldValues = $this->filterSensitiveFields($oldValues, $sensitiveFields); $newValues = $this->filterSensitiveFields($newValues, $sensitiveFields); try { AuditLog::create([ 'user_id' => Auth::id(), 'tenant_id' => session('tenant_id'), 'model_type' => $modelType, 'model_id' => $modelId, 'action' => $action, 'old_values' => $oldValues, 'new_values' => $newValues, 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), 'url' => $request->fullUrl(), 'method' => $method, ]); } catch (\Throwable $e) { // Silently fail — audit logging should never break the request logger()->error('Audit log failed: ' . $e->getMessage()); } } return $response; } /** * Resolve the Eloquent model from the current route. */ protected function resolveRouteModel(Request $request): ?\Illuminate\Database\Eloquent\Model { $route = $request->route(); if (!$route) { return null; } foreach ($route->parameters() as $parameter) { if ($parameter instanceof \Illuminate\Database\Eloquent\Model) { return $parameter; } } return null; } /** * Determine the action label based on HTTP method. */ protected function determineAction(string $method, Request $request): string { // Check if the route name suggests an approve/reject action $routeName = $request->route()?->getName() ?? ''; if (str_contains($routeName, 'approve')) { return 'approve'; } if (str_contains($routeName, 'reject')) { return 'reject'; } if (str_contains($routeName, 'settle')) { return 'settle'; } if (str_contains($routeName, 'status')) { return 'status_change'; } return match ($method) { 'POST' => 'create', 'PUT', 'PATCH' => 'update', 'DELETE' => 'delete', default => strtolower($method), }; } /** * Try to extract a newly created model from a redirect response. */ protected function extractCreatedModel(Request $request, $response): ?\Illuminate\Database\Eloquent\Model { // If the controller set a 'created_model' on the request, use it if ($request->attributes->has('_audit_created_model')) { return $request->attributes->get('_audit_created_model'); } // If the response redirects to a show route, try to extract model from redirect target if ($response instanceof \Illuminate\Http\RedirectResponse) { $targetUrl = $response->getTargetUrl(); $route = app('router')->getRoutes()->match( \Illuminate\Http\Request::create($targetUrl) ); foreach ($route->parameters() as $parameter) { if ($parameter instanceof \Illuminate\Database\Eloquent\Model) { return $parameter; } } } return null; } /** * Filter out sensitive fields from an array of values. */ protected function filterSensitiveFields(?array $values, array $sensitiveFields): ?array { if ($values === null) { return null; } foreach ($sensitiveFields as $field) { unset($values[$field]); } return $values; } }