라라벨 filament, 엑셀 업로드 시 생성되는 임시 CSV 파일 자동 삭제 및 Try-Catch 예외 처리

포스트 썸네일 이미지

지금까지 LARAVEL EXCEL 패키지를 설치하고, Articles 리소스 테이블의 데이터를 엑셀 파일로 내보내는 기능과 반대로 엑셀 파일의 데이터를 데이터베이스에 일괄 업로드하는 기능을 구현했다.

그런데 현재로서는 엑셀 파일의 데이터를 DB에 업로드할 때마다 storage 디렉터리에 csv 파일이 쌓이게 된다.


이번 포스트에서는 엑셀 파일의 데이터를 DB에 업로드해도 storage 디렉터리에 csv 파일이 남지 않도록 코드를 수정하면서, try-catch문을 사용해서 예외 처리도 해보겠다.




이전 포스트




전체적인 코드 수정은 이 깃허브 링크를 통해 확인하자.




업로드할 때마다 쌓이는 csv 파일을 삭제


storage 디렉터리 엑셀 파일이 저장되어 있다

화살표로 표시한 VSCode의 왼쪽을 보면 imports 디렉터리 안에 엑셀 파일이 하나 생겼다.

이전 포스트에서 구현한 엑셀 파일의 데이터를 DB에 일괄 업로드하는 기능을 사용할 때마다 이렇게 엑셀 파일들이 하나씩 쌓이게 되는 것이다.


일단 파일을 삭제해 보겠다.

그런데 파일만 삭제하지 않고, imports 폴더 자체를 삭제할 거다.


이전 포스트에서 storage/app/public 바로 밑이 아닌, storage/app/public/imports라는 하위 폴더를 만들어서 파일들을 깔끔하게 모아 저장하도록 했었다.

그런데 이제는 그냥 엑셀 파일이 저장되지 않도록 할 것이기 때문에 imports라는 하위 폴더도 필요가 없게 된 것이다.





삭제할 파일이나 디렉터리에 마우스 오른쪽 클릭을 하면 뜨는 메뉴들

VSCode의 왼쪽에 있는 폴더나 파일에 대고 마우스 오른쪽 클릭을 하면 이런 메뉴들이 뜬다.

여기서 가장 아래에 있는 Delete Permanently를 클릭하자.




권한이 없다며 삭제가 되지 않는다

그러면 원래는 삭제가 돼야 하는데, 권한이 없다며 삭제가 되지 않는다.




sudo chown -R 소유자:www-data src/storage/ src/bootstrap/cache/

오랜만에 사용하는 명령어다.

이전 포스트에서 생성된 imports 디렉터리와 파일을 수정 가능한 상태가 되도록 Ubuntu 터미널에 위의 명령어를 실행한다.

소유자에는 본인의 계정을 넣는다. (본인 계정을 확인하는 명령어는 whoami)


그러면 이제는 삭제할 수 있을 거다.




이 명령어를 처음 사용했던 포스트를 링크해 두겠다.





업로드해도 csv 파일이 쌓이지 않도록 수정


app/Filament/Resources/Articles/Pages/ListArticles.php

Action::make('importArticles')
    ->label('Import Articles')
    ->icon('heroicon-o-document-arrow-up')
    ->schema([
        FileUpload::make('attachment')
            ->disk('public')
            ->required(),
    ])
    ->action(function (array $data){
        $filePath = Storage::disk('public')->path($data['attachment']);

        Excel::import(new ArticlesImport, $filePath);

        // 추가
        if (file_exists($filePath)) {
            unlink($filePath);
        }

        Notification::make()
            ->title('Articles Imported')
            ->success()
            ->send();
    }),

새로 추가된 코드는 // 추가라고 주석을 붙인 부분뿐이다.

그리고 ->schema([ ... ]) 안의 ->directory('imports')를 삭제했다.


  • if (file_exists($filePath)): file_exists() 함수는 지정한 위치에 파일(또는 디렉터리)이 진짜로 존재하지 않는지, 아니면 존재하는지 확인해 주는 php의 기본 검사 함수다. $filePath 변수에 담긴 서버의 실제 절대 경로(예: /var/www/storage/app/public/imports/articles.csv)에 파일이 진짜로 존재하는지 확인한다.
  • unlink($filePath): 파일이 실제로 존재하면 true를 반환하여 if문의 {} 안의 코드를 실행한다. php의 기본 파일 삭제 함수인 unlink()를 호출하여, 지정된 경로($filePath)의 물리적인 파일 시스템 데이터를 서버에서 영구히 삭제한다.




unlink() 함수에 대해서는 php만으로 CRUD 기능을 구현할 때 다뤘었던 적이 있다.




여기까지만 해도 기능은 완성이 된 거지만, 혹시라도 엑셀 데이터 파싱 중 에러(예: 컬럼 누락, 포맷 오류 등)가 발생해 Excel::import에서 예외Exception가 터질 수 있으니, 만약 에러가 나더라도 무조건 임시 파일은 삭제되게 만들고 싶다면 아래처럼 try-catch문을 활용해 보자.





try-catch문을 활용해 예외 처리


app/Filament/Resources/Articles/Pages/ListArticles.php

Action::make('importArticles')
    ->label('Import Articles')
    ->icon('heroicon-o-document-arrow-up')
    ->schema([
        FileUpload::make('attachment')
            ->disk('public')
            ->required(),
    ])
    ->action(function (array $data){
        $disk = Storage::disk('public');
        $relativePath = $data['attachment'];

        $filePath = $disk->path($relativePath);

        try {
            Excel::import(new ArticlesImport, $filePath);

            Notification::make()
                ->title('Articles Imported')
                ->success()
                ->send();
        } catch (\Throwable $e) {
            Notification::make()
                ->title('Import Failed')
                ->body('파일 처리 중 오류가 발생했습니다: ' . $e->getMessage())
                ->danger()
                ->send();
        } finally {
            if ($disk->exists($relativePath)) {
                $disk->delete($relativePath);
            }
        }
    }),

이게 최종 버전의 코드다.

바뀐 곳은 ->action(function (array $data){ ... }) 내의 코드뿐이다.


수정 전의 코드와 비교해보면, 단순히 파일을 받아 DB에 넣는다는 기본 기능에서 예외 처리, 자원 정리(임시 파일 삭제), 안전성 확보 측면으로 완전히 보강되었다.


  • $disk = Storage::disk('public');: 디스크 객체를 변수화 했다. public 스토리지 디스크 객체를 $disk 변수에 할당하여 재사용할 것이다. (경로 구하기, 파일 존재 여부 확인, 삭제 등에 공통 활용)
  • $relativePath = $data['attachment'];: 업로드된 파일의 디스크 내 상대 경로를 $relativePath 변수에 할당한다.
  • $filePath = $disk->path($relativePath);: 스토리지 디스크의 path() 메서드를 호출하여 서버 OS의 실제 물리 절대 경로로 변환한다.
  • try: 예외 감지 블록이다. 엑셀 읽기 및 DB 저장 작업 도중 발생할 수 있는 에러(예외)를 감지하기 위한 영역을 여는 것이다.
  • Excel::import(new ArticlesImport, $filePath);: 지정된 절대 경로의 엑셀 파일을 가져와 ArticlesImport 규칙에 따라 DB에 저장한다.
  • catch (\Throwable $e): 예외 포착 블록이다. try 내부 실행 중 모든 종류의 오류(Exception 또는 Fatal Error 등 Throwable)가 발생했을 때 이쪽으로 제어권이 넘어가며, 발생한 에러 객체를 $e 변수로 받아온다.
  • Notification::make()-> ...: 에러가 발생하면 빨간색의 위험(danger) 알림 팝업을 띄우고, 사용자에게 어떤 에러 메시지($e->getMessage())가 발생했는지 알려준다.
  • finally: 최종 정리 블록이다. try문 내의 성공 여부, catch문의 예외 처리 여부와 상관없이 무조건 마지막에 반드시 실행되는 구문을 시작한다.
  • if ($disk->exists($relativePath)) { ... }: 업로드된 상대 경로에 파일이 실제로 존재하면 $disk->delete()로 서버에서 깔끔하게 삭제한다.





try-catch문이란?


try, catch, finally는 프로그램 실행 도중 예상치 못한 에러가 발생해도 전체 프로그램이 멈추거나 튕기지 않게 방어하고, 사용했던 자원을 안전하게 마무리 정리할 때 사용하는 예외 처리 구문이다.




1. 각 구문의 역할


구문 비유 역할
try 일단 시도해보기 에러가 발생할 가능성이 있는 핵심 코드(외부 파일 읽기, DB 작업 등)를 실행한다.
catch 에러 났을 때 대처하기 try 블록에서 에러가 터지면 시스템 중단 대신 이곳으로 건너뛰어 에러 로그를 남기거나 알림을 띄운다.
finally 무슨 일이 있어도 뒷정리하기 성공하든 에러가 나든 관계없이 무조건 실행되어, 작업 과정에서 남은 임시 파일 삭제나 DB 연결 종료 같은 자원 해제를 담당한다.




2. 언제 사용해야 할까?


  • 외부 시스템・파일과 연동할 때: 엑셀 파일 읽기, 외부 API 통신, 이메일 발송 등 서버 외부 요소에 의존하여 실패 가능성이 항상 존재하는 작업
  • 트랜잭션이나 자원 관리가 필요할 때: 임시 파일 삭제, DB 트랜잭션 롤백, DB 커넥션 종료 등 실패하더라도 반드시 원상복구해야 하는 뒷정리 작업이 필요할 때




오늘의 포스트는 여기까지다.

이번 포스트에서 다룬 안전한 예외 처리와 자동 자원 정리 기법을 이용해서 라라벨 프로젝트 서버를 항상 안정적이고 깔끔하게 유지하도록 하자.




다음 포스트

이 글이 도움이 됐거나 유익했다면 스크롤을 조금만 더 내려서 댓글을 남겨주세요. (비로그인도 가능합니다!)
응원이나 피드백이 담긴 댓글은 제가 계속 블로그를 해나갈 수 있는 원동력이 됩니다. 😊

지인에게 보여주고 싶은 글이었다면 URL을 복사해서 메신저나 소셜 미디어에 공유해 주세요.
이전 포스트 다음 포스트

댓글 쓰기

0 Comments

문의하기 양식