Files
sam-api/app/Exceptions/Handler.php
hskwon 8686b199ee feat: 더미 데이터 시더 추가 및 회계 관련 마이그레이션
- DummyDataSeeder 및 개별 시더 추가 (Client, BadDebt, Deposit 등)
- payments.paid_at nullable 마이그레이션
- subscriptions 취소 컬럼 추가
- clients 테이블 bad_debt 컬럼 제거
- PlanController, ClientService 수정
- 불필요한 claudedocs, flow-test 파일 정리
2025-12-24 08:54:52 +09:00

181 lines
6.4 KiB
PHP

<?php
namespace App\Exceptions;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;
class Handler extends ExceptionHandler
{
public function report(Throwable $e): void
{
try {
if (
app()->environment('local') || app()->environment('production')
|| app()->environment('LOCAL') || app()->environment('DEV')
) {
$this->sendSlackException($e);
}
} catch (\Throwable $ex) {
// app()이 아직 사용 불가하거나 env 문제가 있을 때는 무시
}
parent::report($e); // 로그는 그대로
}
protected function sendSlackException(Throwable $e): void
{
try {
$url = env('LOG_SLACK_WEBHOOK_URL');
if (! $url) {
return;
}
$ip = request()?->ip() ?? 'N/A'; // 요청이 없는 경우 대비
Http::post($url, [
'text' => "*[Laravel] 예외 발생!*\n".
"• 메시지: `{$e->getMessage()}`\n".
"• 위치: `{$e->getFile()}:{$e->getLine()}`\n".
'• 시간: '.now()->toDateTimeString()."\n".
"• IP: `{$ip}`",
]);
} catch (Throwable $ex) {
logger()->error('슬랙 전송 실패', ['message' => $ex->getMessage()]);
}
}
public function render($request, Throwable $exception)
{
// API 라우트는 무조건 JSON 응답 (Accept 헤더 없어도)
$isApiRoute = str_starts_with($request->path(), 'api/');
if ($request->expectsJson() || $isApiRoute) {
// 422 Unprocessable Entity - Validation 실패
if ($exception instanceof ValidationException) {
return response()->json([
'success' => false,
'message' => '입력값 검증 실패',
'error' => [
'code' => 422,
'details' => $exception->errors(),
],
], 422);
}
// 400 Bad Request
if ($exception instanceof BadRequestHttpException) {
return response()->json([
'success' => false,
'message' => '잘못된 요청',
'data' => null,
], 400);
}
// 401 Unauthorized
if ($exception instanceof AuthenticationException) {
// 토큰 만료 여부 확인
$errorCode = null;
$message = '인증 실패';
// Bearer 토큰이 있는 경우 만료 여부 확인
$bearerToken = $request->bearerToken();
if ($bearerToken) {
$token = \Laravel\Sanctum\PersonalAccessToken::findToken($bearerToken);
if ($token && $token->expires_at && $token->expires_at->isPast()) {
$errorCode = 'TOKEN_EXPIRED';
$message = __('error.token_expired');
}
}
return response()->json([
'success' => false,
'message' => $message,
'error_code' => $errorCode,
'data' => null,
], 401);
}
// 403 Forbidden
if ($exception instanceof AccessDeniedHttpException) {
return response()->json([
'success' => false,
'message' => '권한 없음',
'data' => null,
], 403);
}
// 404 Not Found - 라우트 없음
if ($exception instanceof NotFoundHttpException) {
return response()->json([
'success' => false,
'message' => '존재하지 않는 URI 또는 데이터',
'data' => null,
], 404);
}
// 404 Not Found - 모델 없음 (findOrFail 등)
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'success' => false,
'message' => '데이터를 찾을 수 없습니다',
'data' => null,
], 404);
}
// 405 Method Not Allowed
if ($exception instanceof MethodNotAllowedHttpException) {
return response()->json([
'success' => false,
'message' => '허용되지 않는 메서드',
'data' => null,
], 405);
}
// 400 Bad Request - 품목 코드 중복
if ($exception instanceof DuplicateCodeException) {
return response()->json([
'success' => false,
'message' => $exception->getMessage(),
'error' => [
'code' => 400,
],
'duplicate_id' => $exception->getDuplicateId(),
'duplicate_code' => $exception->getDuplicateCode(),
], 400);
}
// 500 Internal Server Error (기타 모든 에러)
if (
$exception instanceof HttpException &&
$exception->getStatusCode() === 500
) {
return response()->json([
'success' => false,
'message' => '서버 에러',
'data' => null,
], 500);
}
// 그 외 모든 예외
return response()->json([
'success' => false,
'message' => '서버 에러',
'data' => null,
], 500);
}
return parent::render($request, $exception);
}
}