예외
예외 (Exceptions)
PHP에도 다른 프로그래밍 언어와 비슷한 예외 모델이 있어요. PHP 안에서 예외를 throw로 던질 수도 있고 catch로 잡을 수도 있죠. 잠재적인 예외를 잡기 쉽도록 코드를 try 블록으로 감쌀 수 있어요. try 하나에는 반드시 catch나 finally 블록이 하나 이상 대응해야 해요.
참고:
Exception클래스와 함께 보세요.
예외가 던져졌는데 현재 함수 스코프에 catch 블록이 없으면, 그 예외는 호출 스택을 타고 거슬러 올라가며(bubble up) 일치하는 catch 블록을 만날 때까지 호출한 함수로 전파돼요. 그 과정에서 만나는 finally 블록은 모두 실행되고요. 호출 스택이 전역 스코프까지 풀렸는데도 일치하는 catch 블록을 못 만나면, 전역 예외 처리기가 설정돼 있지 않은 한 프로그램은 치명적 오류(fatal error)로 종료돼요.
던져지는 객체는 반드시 Throwable의 instanceof여야 해요. 그렇지 않은 객체를 던지려 하면 PHP 치명적 오류가 발생해요.
PHP 8.0.0부터 throw 키워드는 표현식(expression)이라서 어떤 표현식 자리에서든 쓸 수 있어요. 이전 버전에서는 문(statement)이라서 단독 줄에 써야 했죠.
catch
catch 블록은 던져진 예외에 어떻게 응답할지를 정의해요. catch 블록은 처리할 수 있는 예외나 오류의 타입을 하나 이상 지정하고, 선택적으로 예외를 할당할 변수를 지정할 수 있어요. (이 변수는 PHP 8.0.0 이전에는 필수였어요.) 던져진 예외나 오류가 만나는 catch 블록 중에서, 그 객체의 타입과 일치하는 첫 번째 블록이 그 객체를 처리해요.
catch 블록을 여러 개 쓰면 서로 다른 클래스의 예외를 각각 잡을 수 있어요. 정상 실행(try 블록 안에서 예외가 발생하지 않은 경우)은 순서대로 정의된 마지막 catch 블록 다음부터 이어져요. catch 블록 안에서도 예외를 던지거나 다시 던질(re-thrown) 수 있는데, 그러지 않으면 실행은 발동된 catch 블록 다음부터 계속돼요.
예외가 던져지면 그 문장 다음의 코드는 실행되지 않고, PHP는 첫 번째로 일치하는 catch 블록을 찾아요. 예외가 잡히지 않으면 set_exception_handler()로 처리기를 정의하지 않은 한 "Uncaught Exception ..." 메시지와 함께 PHP 치명적 오류가 발생해요.
PHP 7.1.0부터 catch 블록은 파이프(|) 문자를 사용해 예외를 여러 개 지정할 수 있어요. 서로 다른 클래스 계층의 예외를 같은 방식으로 처리할 때 유용하죠.
PHP 8.0.0부터 잡은 예외의 변수 이름은 선택 사항이에요. 지정하지 않으면 catch 블록은 여전히 실행되지만 던져진 객체에는 접근할 수 없어요.
finally
finally 블록은 catch 블록 뒤나 그 대신에 지정할 수 있어요. finally 블록 안의 코드는 예외가 던져졌는지와 무관하게 try와 catch 블록이 끝난 뒤, 정상 실행이 재개되기 전에 항상 실행돼요.
주목할 상호작용이 하나 있는데, 바로 finally 블록과 return 문 사이예요. try나 catch 블록 안에서 return 문을 만나도 finally 블록은 여전히 실행돼요. 게다가 return 문은 만난 시점에 평가되지만, 그 결과는 finally 블록이 실행된 뒤에 반환돼요. 그리고 finally 블록에도 return 문이 있으면, finally 블록의 값이 반환돼요.
또 하나 주목할 상호작용은 try 블록 안에서 던져진 예외와 finally 블록 안에서 던져진 예외 사이의 관계예요. 두 블록이 모두 예외를 던지면 finally 블록에서 던져진 예외가 전파되고, try 블록에서 던져진 예외는 그 이전 예외(previous exception)로 사용돼요.
전역 예외 처리기 (Global exception handler)
예외가 전역 스코프까지 거슬러 올라가면, 설정되어 있다면 전역 예외 처리기가 잡을 수 있어요. set_exception_handler() 함수로 다른 블록이 호출되지 않을 때 catch 블록 대신 호출될 함수를 지정할 수 있어요. 그 효과는 본질적으로 프로그램 전체를 try-catch 블록으로 감싸고 그 함수를 catch로 둔 것과 같아요.
참고 (Notes)
참고: 내장 PHP 함수는 주로 오류 보고를 사용하고, 최신 객체 지향 확장만 예외를 사용해요. 하지만 오류는 ErrorException으로 쉽게 예외로 바꿀 수 있어요. 다만 이 기법은 치명적이지 않은 오류에서만 동작해요.
예제 #1 오류 보고를 예외로 변환하기
<?php
function exceptions_error_handler($severity, $message, $filename, $lineno) {
throw new ErrorException($message, 0, $severity, $filename, $lineno);
}
set_error_handler('exceptions_error_handler');
표준 PHP 라이브러리(SPL)는 꽤 많은 내장 예외를 제공해요.
예제 (Examples)
예제 #2 예외 던지기
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Division by zero.');
}
return 1/$x;
}
try {
echo inverse(5) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
// Continue execution
echo "Hello World\n";
위 예제는 다음과 같이 출력해요:
0.2
Caught exception: Division by zero.
Hello World
예제 #3 finally 블록을 사용한 예외 처리
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Division by zero.');
}
return 1/$x;
}
try {
echo inverse(5) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
} finally {
echo "First finally.\n";
}
try {
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
} finally {
echo "Second finally.\n";
}
// Continue execution
echo "Hello World\n";
위 예제는 다음과 같이 출력해요:
0.2
First finally.
Caught exception: Division by zero.
Second finally.
Hello World
예제 #4 finally 블록과 return 사이의 상호작용
<?php
function test() {
try {
throw new Exception('foo');
} catch (Exception $e) {
return 'catch';
} finally {
return 'finally';
}
}
echo test();
위 예제는 다음과 같이 출력해요:
finally
예제 #5 중첩 예외 (Nested Exception)
<?php
class MyException extends Exception { }
class Test {
public function testing() {
try {
try {
throw new MyException('foo!');
} catch (MyException $e) {
// rethrow it
throw $e;
}
} catch (Exception $e) {
var_dump($e->getMessage());
}
}
}
$foo = new Test;
$foo->testing();
위 예제는 다음과 같이 출력해요:
string(4) "foo!"
예제 #6 여러 타입을 잡는 catch (Multi catch)
<?php
class MyException extends Exception { }
class MyOtherException extends Exception { }
class Test {
public function testing() {
try {
throw new MyException();
} catch (MyException | MyOtherException $e) {
var_dump(get_class($e));
}
}
}
$foo = new Test;
$foo->testing();
위 예제는 다음과 같이 출력해요:
string(11) "MyException"
예제 #7 잡은 변수 생략하기
PHP 8.0.0 이상에서만 허용돼요.
<?php
class SpecificException extends Exception {}
function test() {
throw new SpecificException('Oopsie');
}
try {
test();
} catch (SpecificException) {
print "A SpecificException was thrown, but we don't care about the details.";
}
위 예제는 다음과 같이 출력해요:
A SpecificException was thrown, but we don't care about the details.
예제 #8 표현식으로서의 throw
PHP 8.0.0 이상에서만 허용돼요.
<?php
function test() {
do_something_risky() or throw new Exception('It did not work');
}
function do_something_risky() {
return false; // Simulate failure
}
try {
test();
} catch (Exception $e) {
print $e->getMessage();
}
위 예제는 다음과 같이 출력해요:
It did not work
예제 #9 try와 finally에서 모두 예외 발생
<?php
try {
try {
throw new Exception(message: 'Third', previous: new Exception('Fourth'));
} finally {
throw new Exception(message: 'First', previous: new Exception('Second'));
}
} catch (Exception $e) {
var_dump(
$e->getMessage(),
$e->getPrevious()->getMessage(),
$e->getPrevious()->getPrevious()->getMessage(),
$e->getPrevious()->getPrevious()->getPrevious()->getMessage(),
);
}
위 예제는 다음과 같이 출력해요:
string(5) "First"
string(6) "Second"
string(5) "Third"
string(6) "Fourth"