PHP에서 예외(Exception)은 특정한 에러가 발생했을 경우, 스크립트의 일반적인 흐름을 바꾸는데 사용됩니다.


예외란 무엇인가

PHP 5 들어서면서 오류를 처리하는 객체 지향적인 새로운 방법이 등장하게 됩니다.


예외 처리(Exception Handling)은 특정한 오류 (예외적인) 상황이 발생하게 되었을때 코드 실행의 일반적인 흐름을 변경하는데 사용됩니다.


아래는 예외를 발생시켰을때 일반적으로 일어나는 일들 입니다:

  • 현재 코드 상태가 저장되게 됩니다. 
  • 코드 실행의 흐름이 미리 정의된 (커스텀, 사용자 정의) 예외 처리기(=exception handler) 함수로 옮겨집니다.
  • 상황에 따라서, 저장된 코드 상태로부터 다시 실행되거나, 스크립트 실행을 중지시키거나 혹은 코드내의 다른 위치로부터 스크립트를 계속해서 실행시키게 됩니다.


이제부터 오류를 처리하는 방법들을 보여드리겠습니다:

  • 예외의 일반적인 사용법(Basic use of Exceptions)
  • 사용자정의 예외 처리기 만들기(Creating a custom exception handler)
  • 다중 예외(Multiple exceptions)
  • 예외 다시 던지기(Re-throwing an exception)
  • 최상위 레벨 예외 처리기 설정하기(Setting a top level exception handler)

참고: 예외는 오류가 있는 상황에서만 사용되어야지, 특정한 시점에서 코드의 다른 장소로 건너띄기 위해서 사용되어져서는 안됩니다.


예외의 일반적인 사용법(Basic Use of Exceptions)

예외가 던져지면(throw), 이후의 코드는 실행이 되지 않게되고, PHP는 일치하는 "catch" 블럭이 있는지 찾게 됩니다.

(한마디로 예외가 던져지고[throw, thrown] --- 던져진 예외를 잡는다[catch, caught] 고 생각하시면 됩니다^^)


만일 예외를 잡지(caught) 못했을 경우, "Uncaught Exception" 라는 메시지와 함께 치명적인 에러(Fatal Error)가 발생하게 됩니다.

예외를 던지기(throw)만 하고, 잡지는(catch) 않는걸 한번 시도해 봅시다:

<?php
//예외를 포함한 함수 만들기
function checkNum($number)
 {
 if($number>1)
  {
  throw new Exception("값이 1이거나 혹은 더 낮아야 합니다");
  }
 return true;
 }

//예외 발생시키기
checkNum(2);
?>


위의 코드는 아래와 같은 에러를 발생시킬겁니다:

Fatal error: Uncaught exception 'Exception' 
with message 'Value must be 1 or below' in C:\webfolder\test.php:6 
Stack trace: #0 C:\webfolder\test.php(12): 
checkNum(28) #1 {main} thrown in C:\webfolder\test.php on line 6


Try, throw, catch

위의 예제 코드와 같은 오류를 피하기 위해선, 예외(exception)를 처리(handle)할 적절한 코드를 작성해야 합니다.

적절한 예외처리 코드는 아래를 포함해야 합니다:

  1. Try - 예외를 사용하는 함수는 "try" 코드 블럭안에 있어야 합니다. 예외가 발생되지(trigger) 않는다면, 코드는 정상적으로 계속해서 진행되게 됩니다. 하지만 예외가 발생하게 되면, 예외는 "던져지게(thrown)" 됩니다.
  2. Throw - 이걸로 예외(exception)를 발생(trigger)시킵니다. 각각의 "throw" 는 최소한 하나의 "catch" 코드를 가져야 합니다.
  3. Catch - "catch" 블럭에서 던져진(thrown) 예외를 잡고(caught) 예외에 관한 정보를 포함하는 개체를 생성하게 됩니다.

이제 제대로된 코드로 예외를 발생시켜 봅시다:

<?php
//예외를 포함하는 함수 생성
function checkNum($number)
 {
 if($number>1)
  {
  throw new Exception("값이 1이거나 혹은 더 낮아야 합니다");
} return true; } //"try" 블럭에서 예외 발생 시키기 try { checkNum(2); //예외가 던져지게 된다면, 이 텍스트는 표시되지 않습니다 echo '이걸 보게된다면, 숫자값은 1이거나 혹은 더 낮습니다'; } //던져진 예외 잡기(catch) catch(Exception $e) { echo 'Message: ' .$e->getMessage(); } ?>

이제 위의 코드는 오류를 아래처럼 표시하게 됩니다:

Message: 값이 1이거나 혹은 더 낮아야 합니다

예제코드 설명:

위의 코드에서는 예외를 던지게 되고(throw), 던져진 예외를 잡게(catch) 됩니다:

  1. checkNum() 함수가 생성됩니다. 숫자가 1보다 큰지를 확인해서, 크다면 에러가 던져지게 됩니다.
  2. checkNum() 함수가 "try" 블럭에서 호출됩니다.
  3. checkNum() 함수내의 예외가 던져집니다.
  4. "catch" 블럭이 예외를 잡아낸 다음, 예외에 관한 정보를 포함하는 개체 ($e)를 생성 합니다.
  5. 예외로부터의 오류메시지가, 예외 개체로부터 호출 ( $e->getMessage()  ) 하는걸 통해서 표시됩니다.

하지만, "모든 throw는 반드시 하나의 catch"를 가져야 한다는 규칙을 피하는 한가지 방법이 있는데 그건 바로 최상위 레벨 예외 처리기를 설정하는 것입니다.



사용자정의 예외(Custom Exception) 클래스(Class)

사용자정의 예외 처리기(custom exception handler)를 만드는것은 의외로 간단합니다.

간단하게 PHP에서 예외가 발생했을때 호출될 수 있는 함수를 가진 특별한 클래스를 만들면 됩니다. 

클래스는  예외 클래스의 확장이어야 합니다.


사용자정의 예외 클래스는 PHP의 예외 클래스로부터 속성을 상속받으며, 

사용할때에 사용자정의 함수를 추가할 수 있습니다.

예외 클래스를 만들어 봅시다:


<?php
class customException extends Exception
 {
 public function errorMessage()
  {
  //error message
  $errorMsg = 'Error on line '.$this->getLine().' in '.$this->getFile()
  .': <b>'.$this->getMessage().'</b> is not a valid E-Mail address';
  return $errorMsg;
  }
 }
$email = "someone@example...com";
try
 {
 //check if 
 if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
  {
  //throw exception if email is not valid
  throw new customException($email);
  }
 }
catch (customException $e)
 {
 //display custom message
 echo $e->errorMessage();
 }
?>

새 클래스는, errorMessage() 함수가 추가된, 이전 예외 클래스의 복사본 입니다. 

이전 클래스의 복사본이기 때문에, 이전 클래스로부터 속성들과 메소드들을 상속받게 됩니다.

그리고 이점 덕분에 getLine(), getFile(), getMessage() 같은 예외 클래스 메소드들을 사용할 수 있게 됩니다.


예제코드 설명:

위의 코드는 예외를 던진다음(throw), 사용자정의 예외 클래스로 던져진 예외를 잡습니다(catch):

  1. customException() 클래스가, 이전 예외 클래스의 확장으로서 생성됩니다. 이를 통해, 이전 예외 클래스로부터 모든 메소드들과 속성들을 상속받게 됩니다.
  2. errorMessage() 함수가 생성됩니다. 이 함수는 e-mail 주소가 올바르지 않을경우 에러 메시지를 리턴합니다.
  3. $email 변수가 올바르지 않은 e-mail 주소 문자열로 설정됩니다.
  4. "try" 블럭이 실행되고, e-mail 주소가 올바르지 않기 때문에 예외가 던져집니다.
  5. "catch" 블럭이 예외를 잡은다음, 오류 메시지를 출력합니다.

다중 예외(Multiple Exceptions)

여러 상황에 대해서 확인하기 위해서 스크립트 내에서 다중 예외를 사용하는것이 가능합니다.

if..else 블럭, switch, nest 다중 예외를 사용할수도 있습니다.

이 예외들은 다른 예외 클래스들을 사용할 수 있고, 다른 에러 메시지들을 리턴하게 됩니다:


<?php
class customException extends Exception
{
public function errorMessage()
{
//error message
$errorMsg = 'Error on line '.$this->getLine().' in '.$this->getFile()
.': <b>'.$this->getMessage().'</b> is not a valid E-Mail address';
return $errorMsg;
}
}

$email = "someone@example.com";

try
 {
 //check if 
 if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
  {
  //throw exception if email is not valid
  throw new customException($email);
  }
 //check for "example" in mail address
 if(strpos($email, "example") !== FALSE)
  {
  throw new Exception("$email is an example e-mail");
  }
 }

catch (customException $e)
 {
 echo $e->errorMessage();
 }
catch(Exception $e)
 {
 echo $e->getMessage();
 }
?>

예제코드 설명:

위의 코드는 두 개의 조건을 테스트한 다음, 조건들중 어떤것이라도 일치하지 않으면 예외를 던집니다:

  1. customException() 클래스가 이전 예외 클래스의 확장으로서 생성됩니다. 이를 통해, 이전 예외 클래스로부터 모든 메소드들과 속성들을 상속받게 됩니다.
  2. errorMessage() 함수가 생성됩니다. 이 함수는 e-mail 주소가 올바르지 않다면 에러를 리턴합니다.
  3. $email 변수가 올바른 e-mail 주소로 설정되지만, "example"이란 문자열을 포함하고 있습니다.
  4. "try" 블럭이 실행되고, 첫번째 조건에서는 예외가 던져지지 않습니다.
  5. 두번째 조건에서는 e-mail이 "example"이란 문자열을 포함하고 있기 때문에 예외가 발생되게 됩니다.
  6. "catch" 블럭이 예외를 잡아낸 다음, 알맞은 에러 메시지를 표시합니다.
만일 customException catch가 없고, 기본 예외 catch만 있었다면, 예외는 거기에서 처리되었을 겁니다^_^



예외 다시 던지기(Re-throwing Exceptions)

때때로, 예외가 던져졌을때, 표준적인 방법과는 다르게 그걸 처리하고 싶을 경우가 있을겁니다.

"catch" 블럭내에서 예외를 두번이나 던지는 것이 가능합니다.


스크립트는 사용자로부터 시스템 에러를 숨겨야 합니다. 시스템 에러는 프로그래머에게는 중요할지도 모르지만,

사용자에게는 관심의 대상이 아닙니다. 사용자가 이해하기 쉽게 하려면, 좀 더 메시지가 쉬운 형태가 되도록 

예외를 다시 던질 수 있습니다:


<?php
class customException extends Exception
 {
 public function errorMessage()
  {
  //error message
  $errorMsg = $this->getMessage().' is not a valid E-Mail address.';
  return $errorMsg;
  }
 }
$email = "someone@example.com";
try
 {
 try
  {
  //check for "example" in mail address
  if(strpos($email, "example") !== FALSE)
   {
   //throw exception if email is not valid
   throw new Exception($email);
   }
  }
 catch(Exception $e)
  {
  //re-throw exception
  throw new customException($email);
  }
 }
catch (customException $e)
 {
 //display custom message
 echo $e->errorMessage();
 }
?>

예제코드 설명:

위의 코드는 이메일 주소가 "example"이란 문자열을 포함하고 있는지 테스트한 다음, 포함되어 있다면 예외가 다시 던져지게 됩니다:

  1. customException() 클래스가 이전 예외 클래스의 확장으로서 생성되게 됩니다. 이를 통해, 이전 예외 클래스의 모든 메소드와 속성들을 상속받게 됩니다.
  2. errorMessage() 함수가 생성됩니다. 이 함수는 이메일 주소가 올바르지 않다면 오류 메시지를 리턴합니다.
  3. $email 변수가 올바른 이메일 주소를 가진 문자열로 초기화 되지만, "example"이란 문자열을 포함하게 됩니다.
  4. "try" 블럭이, 예외를 다시 던지는것이 가능하도록, 또 다른 "try" 블럭을 포함하고 있습니다.
  5. 이메일 주소에 "example" 문자열이 포함되어 있기 때문에, 예외가 발생되게 됩니다.
  6. "catch" 블럭이 예외를 잡아낸 다음, "customException"을 다시 던집니다.
  7. "customException" 이 잡히게 되고, 에러 메시지가 표시됩니다.

만일 예외가 현재 "try" 블럭에서 잡히지 않을 경우, "더 높은 단계들에서" catch 블럭을 검색하게 됩니다.



최상위 예외 처리기 설정하기(Set a Top Level Exception Handler)

set_exception_handler() 함수가 잡혀지지 않은 모든 예외들을 처리하기 위한 사용자 정의된 함수를 설정합니다.


<?php
function myException($exception)
{
echo "<b>Exception:</b> " , $exception->getMessage();
}
set_exception_handler('myException');
throw new Exception('Uncaught Exception occurred');
?>

위 코드의 출력 결과는 아래와 비슷한 형태가 될겁니다:

Exception: Uncaught Exception occurred

위의 코드에서는 "catch" 블럭이 없었습니다. 대신에, 최상위 예외 처리기(Top Level Exception Handler)가 발생되었습니다. 이 함수는 모든 잡혀지지 않은 예외들을 잡기위해 사용되어야 합니다.



예외에 대한 규칙(Rules for exceptions)

  • 코드는 잠재적인 예외들을 잡아내는데 유용하도록 try 블럭으로 감싸져야 합니다.
  • 각각의 try 블럭 혹은 "throw"는 최소한 하나의 상응하는 catch 블럭을 가져야만 합니다.
  • 다중 catch 블럭은 예외의 다른 클래스들을 잡아내기 위해서 사용될 수 있습니다.
  • 예외는 try 블럭안의 catch 블럭내에서 던져질 수(혹은 다시 던져질 수) 있습니다.

단순한 규칙 하나: 무언가 하나를 던졌다면, 그걸 잡아야만 합니다.