Files
sam-manage/docs/TROUBLESHOOTING.md
hskwon 2752a6b7c3 docs: 트러블슈팅 가이드 추가
테넌트 관리 개발 중 발생한 주요 오류와 해결 방법 문서화:
- HTMX 관련 오류 (URL 라우팅, CSRF, ViewComposer 충돌)
- FormRequest 유효성 검증 오류
- 데이터베이스 외래키 제약 오류

디버깅 팁 및 예방 체크리스트 포함
2025-11-24 11:43:04 +09:00

7.3 KiB

Troubleshooting Guide

프로젝트에서 발생한 오류와 해결 방법을 정리한 문서입니다.


📋 목차

  1. HTMX 관련 오류
  2. Laravel FormRequest 오류
  3. 데이터베이스 제약 오류

HTMX 관련 오류

1. HTMX 요청이 잘못된 URL로 전송되는 문제

증상:

Request URL: https://mng.sam.kr/tenants/285/edit
Expected URL: https://mng.sam.kr/api/admin/tenants/285

폼에 hx-put="/api/admin/tenants/{{ $id }}"로 설정했는데도 현재 페이지 URL로 요청이 전송됨.

원인: HTMX가 상대 경로를 현재 페이지 기준으로 해석

해결 방법: url() 헬퍼 함수로 전체 URL 사용

<!-- ❌ 잘못된 방법 -->
<form hx-put="/api/admin/tenants/{{ $id }}">

<!-- ✅ 올바른 방법 -->
<form hx-put="{{ url('/api/admin/tenants/' . $id) }}">

관련 파일:

  • resources/views/tenants/edit.blade.php

커밋: bc3777a


2. HTMX DELETE 요청 시 419 CSRF 토큰 오류

증상:

DELETE https://mng.sam.kr/api/admin/tenants/284
Status Code: 419 unknown status

원인: htmx.ajax() 호출 시 CSRF 토큰 헤더 누락

해결 방법: HTMX ajax 요청에 CSRF 토큰 헤더 추가

// ❌ 잘못된 방법
htmx.ajax('DELETE', `/api/admin/tenants/${id}`, {
    target: '#tenant-table',
    swap: 'none'
});

// ✅ 올바른 방법
htmx.ajax('DELETE', `/api/admin/tenants/${id}`, {
    target: '#tenant-table',
    swap: 'none',
    headers: {
        'X-CSRF-TOKEN': '{{ csrf_token() }}'
    }
});

관련 파일:

  • resources/views/tenants/index.blade.php

커밋: bc3777a


3. ViewServiceProvider가 변수를 덮어쓰는 문제

증상:

Method Illuminate\Database\Eloquent\Collection::hasPages does not exist.

컨트롤러에서 LengthAwarePaginator를 전달했는데 뷰에서 Collection으로 변경됨.

원인: ViewServiceProvider의 글로벌 View Composer가 모든 뷰(*)에 같은 변수명으로 데이터 주입

// ViewServiceProvider.php
View::composer('*', function ($view) {
    $tenants = Tenant::active()->get(); // Collection 반환
    $view->with('tenants', $tenants); // 컨트롤러의 $tenants를 덮어씀
});

해결 방법: 전역 View Composer의 변수명을 다르게 지정

// ✅ 올바른 방법
View::composer('*', function ($view) {
    $globalTenants = Tenant::active()->get();
    $view->with('globalTenants', $globalTenants);
});

관련 파일:

  • app/Providers/ViewServiceProvider.php
  • resources/views/partials/tenant-selector.blade.php

커밋: f49cfd9


Laravel FormRequest 오류

4. FormRequest 유효성 검증 실패로 인한 302 리다이렉트

증상:

PUT https://mng.sam.kr/api/admin/tenants/285
Status Code: 302 Found
Location: https://mng.sam.kr/tenants/285/edit

API 엔드포인트가 JSON 대신 HTML 리다이렉트 반환.

원인: UpdateTenantRequest에서 라우트 파라미터 이름이 잘못됨

// routes/api.php
Route::put('/{id}', [TenantController::class, 'update']);

// UpdateTenantRequest.php
$tenantId = $this->route('tenant'); // ❌ 'tenant'는 존재하지 않음

라우트 파라미터는 {id}인데 route('tenant')로 가져오려 해서 null 반환. → code 유일성 검증이 자기 자신을 제외하지 못함 → 유효성 검증 실패 → Laravel이 자동으로 이전 페이지로 리다이렉트

해결 방법: 라우트 파라미터 이름을 정확히 매칭

// ✅ 올바른 방법
$tenantId = $this->route('id');

return [
    'code' => [
        'required',
        'string',
        'max:50',
        Rule::unique('tenants', 'code')->ignore($tenantId),
    ],
];

관련 파일:

  • app/Http/Requests/UpdateTenantRequest.php
  • routes/api.php

커밋: bc3777a


데이터베이스 제약 오류

5. 외래키 제약으로 인한 영구 삭제 실패

증상:

SQLSTATE[23000]: Integrity constraint violation: 1451
Cannot delete or update a parent row: a foreign key constraint fails
(`samdb`.`user_tenants`, CONSTRAINT `user_tenants_tenant_id_foreign`
FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`))

원인: 테넌트를 삭제하려 하는데, user_tenants 테이블에 아직 이 테넌트를 참조하는 레코드가 남아있음.

해결 방법: 영구 삭제 전에 관련 데이터를 먼저 삭제

public function forceDeleteTenant(int $id): bool
{
    $tenant = Tenant::withTrashed()->findOrFail($id);

    // ✅ 관련 데이터 먼저 삭제
    $tenant->users()->detach();              // user_tenants 관계 삭제
    $tenant->departments()->forceDelete();   // 부서 영구 삭제
    $tenant->menus()->forceDelete();         // 메뉴 영구 삭제
    $tenant->roles()->forceDelete();         // 역할 영구 삭제

    return $tenant->forceDelete();
}

관련 파일:

  • app/Services/TenantService.php

커밋: bc3777a

참고:

  • detach(): Many-to-Many 관계 삭제 (pivot 테이블)
  • forceDelete(): SoftDelete 무시하고 영구 삭제

디버깅 팁

1. HTMX 요청 디버깅

브라우저 개발자 도구:

Network 탭 → Request URL, Method, Status Code 확인
Headers 탭 → Request/Response Headers 확인
Payload 탭 → 전송된 데이터 확인
Response 탭 → 서버 응답 내용 확인

HTMX 이벤트 로깅:

document.body.addEventListener('htmx:configRequest', function(evt) {
    console.log('HTMX Request:', evt.detail);
});

document.body.addEventListener('htmx:afterRequest', function(evt) {
    console.log('HTMX Response:', evt.detail);
});

2. Laravel 로그 확인

# 최근 로그 확인
tail -50 storage/logs/laravel.log

# 에러만 필터링
tail -100 storage/logs/laravel.log | grep "local.ERROR"

# 실시간 로그 모니터링
tail -f storage/logs/laravel.log

3. 캐시 클리어

# 뷰 캐시 삭제
php artisan view:clear

# 전체 캐시 삭제
php artisan cache:clear
php artisan config:clear
php artisan route:clear

# 컴파일된 뷰 파일 수동 삭제
rm -rf storage/framework/views/*.php

# Docker 컨테이너의 OPcache 클리어
docker exec sam-mng-1 php -r "opcache_reset();"

예방 체크리스트

HTMX 사용 시

  • CSRF 토큰 헤더 추가 (X-CSRF-TOKEN)
  • 전체 URL 사용 (url() 헬퍼)
  • 올바른 HTTP 메서드 사용 (GET, POST, PUT, DELETE)
  • 응답 처리 이벤트 리스너 등록

FormRequest 작성 시

  • 라우트 파라미터 이름 확인
  • 유일성 검증 시 현재 레코드 제외 (ignore())
  • 날짜 필드는 nullable + date 검증

데이터 삭제 시

  • 외래키 관계 확인
  • 관련 데이터 먼저 삭제
  • SoftDelete vs ForceDelete 구분
  • 트랜잭션 사용 고려

캐시 관련

  • 코드 변경 후 캐시 클리어
  • 뷰 수정 후 view:clear
  • 설정 변경 후 config:clear

참고 링크