Menu

PHP 예외: throw와 사용자 정의 예외 클래스

PHP에서는 throw new Exception('message');로 예외를 던지고, 호출한 쪽은 catch 블록에서 $e->getMessage()로 읽습니다. 내장 예외 클래스, 사용자 정의 예외 만들기, getCode(), getLine(), getPrevious(), 표현식으로서의 throw, ValueError와 TypeError처럼 PHP가 직접 던지는 에러를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

PHP에서는 throw new Exception('message');로 예외를 던집니다. 실행은 그 줄에서 멈추고 예외의 타입을 받아들이는 가장 가까운 catch 블록으로 넘어가며, 그곳에서 $e->getMessage()가 메시지를 반환합니다. 호출한 쪽이 문제를 구별할 수 있도록 InvalidArgumentException 같은 구체적인 클래스를 쓰세요.

예외는 객체입니다. throw는 그것을 호출 스택 위로 올려 보내 일치하는 catch가 있는 첫 번째 호출자에게 전달하고, 그 호출자가 무엇을 할지 정합니다. 문제를 발견한 함수는 그것을 알 필요가 없습니다. 잡는 방법, finally, 여러 타입 잡기는 try catch 페이지에 있습니다.

예외 객체에 담긴 것

모든 예외는 메시지, 정수 코드, 만들어진 파일과 줄, 스택 트레이스를 가집니다. 생성자는 ($message, $code, $previous)를 받으며 모두 선택 사항입니다.

getLine()은 catch의 줄이 아니라 throw new의 줄입니다. 트레이스는 그곳에 이르기까지의 호출을 나열하며(#0 /home/index.php(8): findOrder()), 로그에 에러가 나타났을 때 보통 가장 먼저 읽는 것입니다. 예외를 문자열로 바꾸면(echo $e; 또는 (string) $e) PHP의 표준 형식으로 전부 출력됩니다.

내장 예외 클래스

PHP는 표준 PHP 라이브러리(SPL)에 예외 클래스 가족을 담고 있습니다. 문제를 이름으로 말해 주는 것을 던지면 코드가 더 잘 읽히고 호출한 쪽이 좁게 잡을 수 있습니다. 모두 Exception을 상속합니다.

클래스던질 때
InvalidArgumentException인수의 형태가 잘못되었을 때: 빈 이름, 알 수 없는 옵션
DomainException값이 말이 되는 집합 밖일 때: 음수 가격
OutOfRangeException코드가 절대 존재할 수 없는 인덱스를 요청했을 때(호출한 쪽의 버그)
OutOfBoundsException실행 중에만 알 수 있는 데이터에 키나 인덱스가 없을 때
RangeException실행 중 계산 결과가 유효 범위를 벗어날 때
LengthException무언가가 너무 길거나 짧을 때
RuntimeException실행 중에만 감지할 수 있는 문제: 디스크 가득 참, 시간 초과
UnexpectedValueException함수가 예상하지 못한 타입의 값을 반환하거나 받았을 때
LogicException잘못된 입력이 아니라 코드 자체가 틀렸을 때, 즉 버그
JsonExceptionJSON_THROW_ON_ERROR와 함께 쓴 json_encode()/json_decode()가 던짐

InvalidArgumentException, DomainException, LengthException, OutOfRangeException은 LogicException을 상속하고, OutOfBoundsException, RangeException, UnexpectedValueException은 RuntimeException을 상속합니다. 어떤 클래스의 부모든 직접 확인할 수 있습니다.

TypeError의 부모는 Exception이 아니라 Error입니다. 아래의 두 번째 가족에 속합니다.

PHP가 직접 던지는 에러

PHP 7부터, 그리고 PHP 8부터는 훨씬 더, PHP 자체의 함수와 연산자는 예전에 경고였던 문제에 대해 Error 하위 클래스를 던집니다. 같은 방식으로 잡지만 catch (Exception $e)는 일치하지 않습니다.

DivisionByZeroError, TypeError, ValueError, ArgumentCountError, UnhandledMatchError, 그리고 그냥 Error는 모두 Error를 상속합니다. 각각은 잘못된 데이터보다 호출하는 코드의 버그를 가리키는 경우가 많으며, 그래서 Exception 트리 밖에 있습니다. str_repeat(5, 2)를 TypeError로 만드는 것은 declare(strict_types=1)입니다. 없으면 PHP가 5를 "5"로 변환합니다.

사용자 정의 예외 클래스 만들기

한 줄짜리 하위 클래스로 충분한 경우가 많습니다. 클래스 이름 자체가 의미를 담고, 호출한 쪽은 정확히 그 타입을 잡을 수 있습니다. 잡는 쪽에 데이터(어떤 주문, 어떤 금액)가 필요하면 속성을 추가하고 parent::__construct()로 메시지를 넘기세요.

부모는 신중하게 고르세요. 그냥 Exception이 아니라 RuntimeException을 상속하면 RuntimeException을 잡는 코드가 여러분의 예외도 처리합니다. 라이브러리나 모듈마다 기반 예외 하나와 그 아래 구체적인 하위 클래스를 둔 작은 계층 구조는 호출한 쪽이 "결제 관련은 모두 잡기"와 "이 경우만 잡기" 중에서 고르게 해 줍니다.

previous로 예외 연결하기

저수준 예외를 잡고 상위 수준 예외를 던질 때, 원래 예외를 세 번째 인수로 넘기세요. 아무것도 잃지 않습니다. getPrevious()가 근본 원인까지 거슬러 올라갑니다.

사용자는 "customers.csv could not be imported"를 보고, 체인을 따라가는 로그는 "row 2 has 2 columns"도 얻습니다. 연결된 예외가 끝내 잡히지 않으면 PHP의 치명적 에러가 체인 전체를 출력합니다. 원래 원인이 먼저, 그다음 각 감싼 예외가 Next 줄 아래에 나옵니다.

표현식으로서의 throw

PHP 8.0부터 throw는 표현식이므로 값이 와야 하는 곳 어디에나 둘 수 있습니다. ?? 뒤, 삼항 연산자 안, 화살표 함수 안입니다. "이것을 얻거나 실패하기"를 한 줄로 만듭니다.

예외를 던지지 말아야 할 때

예외는 현재 함수가 처리할 수 없고 정상 흐름이 예상하지 않는 상황을 위한 것입니다. 아무것도 찾지 못한 검색, 비어 있는 선택적 필드, 로그인하지 않은 사용자는 평범한 결과입니다. null, false, 빈 배열을 반환해 호출한 쪽이 확인하게 하고, 예외는 계속 진행하면 잘못되는 경우를 위해 남겨 두세요.

예외 코드를 읽기 좋게 유지하는 습관이 두 가지 더 있습니다. 예외를 무시하려고 잡지 마세요(빈 catch (Exception $e) {}는 다음 진짜 버그를 숨깁니다). 그리고 그냥 Exception이 아니라 구체적인 클래스를 던져서 호출한 쪽이 한 경우를 처리하려고 모든 것을 잡지 않아도 되게 하세요.

자주 묻는 질문

PHP에서 예외를 던지려면 어떻게 하나요?

예외 객체를 만들어 던지세요: throw new InvalidArgumentException('Quantity must be positive');. 실행은 그 줄에서 멈추고 가장 가까운 일치하는 catch 블록으로 넘어가며, 없으면 스크립트가 "Uncaught" 치명적 에러로 끝납니다.

PHP에서 사용자 정의 예외를 만들려면 어떻게 하나요?

Exception이나 그 하위 클래스를 상속하세요: class PaymentFailedException extends RuntimeException {}. 그 한 줄이면 자신의 타입으로 던지고 잡을 수 있습니다. 잡는 쪽에 추가 데이터가 필요하면 속성과 생성자를 추가하고 parent::__construct($message, $code, $previous)를 호출하세요.

getMessage와 getCode의 차이는 무엇인가요?

getMessage()는 생성자의 첫 번째 인수로 넘긴 텍스트를 반환하며, 사람과 로그를 위한 것입니다. getCode()는 두 번째 인수로 넘긴 정수(기본값 0)를 반환하며, 에러를 HTTP 상태 코드에 대응시키는 것 같은 기계적 확인에 유용합니다.

PHP에서 어떤 예외를 던져야 하나요?

문제에 맞는 내장 SPL 클래스를 쓰세요. 잘못된 인수에는 InvalidArgumentException, 허용 범위 밖의 값에는 DomainException, 실행 중에만 보이는 실패(디스크 가득 참, 시간 초과)에는 RuntimeException, 프로그래밍 실수에는 LogicException입니다. 호출한 쪽이 구별해야 하는 에러라면 직접 하위 클래스를 만드세요.

PHP의 예외 체이닝은 무엇인가요?

새 예외를 던질 때 원래 예외를 생성자의 세 번째 인수로 넘기는 것입니다: throw new ImportException('Import failed', 0, $e);. 새 예외는 맥락을 주고, 그것의 $e->getPrevious()는 원래 원인을 반환하므로 세부 사항을 잃지 않습니다.

Coddy 프로그래밍 언어 일러스트

Coddy로 코딩 배우기

시작하기