例外(exceptions)
目次
PHP は、他のプログラミング言語に似た例外モデルを持っています。
PHP 内で例外がスローされ (throw
され)、それが
捕捉され (catch
され) ます。発生した例外を
捕捉するには、コードを try
ブロックで囲みます。
各 try
ブロックには、対応する catch
ブロックあるいは finally
ブロックが存在する必要があります。
例外がスローされ、現在の関数スコープに catch
ブロックがなかった場合、
その例外は、マッチする catch
ブロックが見つかるまで関数のコールスタックを "遡って" いきます。
その途中で見つかった全ての finally
ブロックが実行されます。
グローバルスコープに遡るまで全てのコールスタックを探しても、
マッチする catch
ブロックが見つからない場合は、
グローバルな例外ハンドラが設定されていない限り fatal error となり、
プログラムが終了します。
スローされるオブジェクトは、 Throwable のインスタンスでなければなりません。 それ以外のオブジェクトをスローしようとすると PHP の fatal error が発生します。
PHP 8.0.0 以降では、throw
キーワードは式として扱えるようになり、
様々なコンテクストで使えるようになりました。
これより前のバージョンでは、throw
は文であり、
それが現れる行でだけでしか使えませんでした。
catch
catch
ブロックは、スローされた例外にどのように反応するかを定義します。
catch
ブロックは、扱えるひとつ以上の例外またはエラー型を定義します。
そして、オプションで例外を代入できる変数も定義できます。
(PHP 8.0.0 より前のバージョンでは、この変数定義は必須でした)
スローされた例外またはエラーにマッチする最初の catch
ブロックが、そのオブジェクトを処理します。
さまざまな型の例外を捕捉するために
複数の catch
ブロックを使用することができます。
通常の実行時 (try
ブロック内で例外がスローされなかった
場合) は、catch
ブロック内は処理されず、それ以降から処理が続けられます。
catch
ブロックの中から例外を throw
する
(あるいは throw
しなおす) こともできます。
throw
し直さない場合、その catch
ブロックの後から処理が続けられます。
例外がスローされた場合、その命令に続くコードは実行されず、
PHP は最初にマッチする catch
ブロックを探します。
例外が捕捉されない場合、PHP は "Uncaught Exception ...
"
というメッセージとともに
致命的なエラー(fatal error)を発生させます。
ただし、set_exception_handler() でハンドラが
定義されている場合を除きます。
PHP 7.1.0 以降では、catch
ブロック で 複数の例外を
パイプ文字 (|
) を使って指定できるようになりました。
これは、異なるクラス階層からの例外を同時に扱う必要がある場合に有用です。
PHP 8.0.0 以降では、キャッチされた例外に対応する変数はオプションになりました。
指定されない場合、catch
ブロックは実行されるものの、
スローされたオブジェクトへアクセスすることは出来ません。
finally
catch
ブロックの後、または catch
ブロックの代わりに、
finally
ブロックも指定できます。
finally
ブロックの中に書いたコードは、
try
および catch
ブロックの後で、
かつ通常のコードの実行が再開される前に常に実行されます。
例外がスローされたかどうかは関係ありません。
finally
ブロックと return
文の間には注意すべき相互作用があります。
return
文が try
や catch
ブロックの内部に存在した場合でも、
finally
ブロックは実行されます。
さらに、return
文は出現した時に評価されますが、
結果は finally
ブロックが実行された後に返されます。
さらに、finally
ブロックにも return
文が存在した場合は、
finally
ブロックから値が返されます。
グローバルな例外ハンドラ
例外がグローバルスコープにまで遡った場合、
設定されていれば、グローバルな例外ハンドラがそれをキャッチすることができます。
他の catch
ブロックが呼び出されなかった場合に、
catch
の代わりに呼び出される関数を
set_exception_handler() 関数で設定できます。
その効果は、プログラム全体を try
-catch
ブロックで囲むことと同じです。
注意
注意:
PHP の内部関数の多くは エラー報告(error_reporting) を使っており、例外を使っているのは新しい オブジェクト指向 の拡張モジュールのみです。 しかし、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');
?>
Standard PHP Library (SPL) には 組み込みの例外 が数多く用意されています。
例
例2 例外をスローする
<?php
function inverse($x) {
if (!$x) {
throw new Exception('ゼロによる除算。');
}
return 1/$x;
}
try {
echo inverse(5) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo '捕捉した例外: ', $e->getMessage(), "\n";
}
// 実行は継続される
echo "Hello World\n";
?>
上の例の出力は以下となります。
0.2 捕捉した例外: ゼロによる除算。 Hello World
例3 例外処理での finally
ブロック
<?php
function inverse($x) {
if (!$x) {
throw new Exception('ゼロによる除算。');
}
return 1/$x;
}
try {
echo inverse(5) . "\n";
} catch (Exception $e) {
echo '捕捉した例外: ', $e->getMessage(), "\n";
} finally {
echo "First finally.\n";
}
try {
echo inverse(0) . "\n";
} catch (Exception $e) {
echo '捕捉した例外: ', $e->getMessage(), "\n";
} finally {
echo "Second finally.\n";
}
// 実行は継続される
echo "Hello World\n";
?>
上の例の出力は以下となります。
0.2 First finally. 捕捉した例外: ゼロによる除算。 Second finally. Hello World
<?php
function test() {
try {
throw new Exception('foo');
} catch (Exception $e) {
return 'catch';
} finally {
return 'finally';
}
}
echo test();
?>
上の例の出力は以下となります。
finally
例5 ネストした例外
<?php
class MyException extends Exception { }
class Test {
public function testing() {
try {
try {
throw new MyException('foo!');
} catch (MyException $e) {
// 改めてスロー
throw $e;
}
} catch (Exception $e) {
var_dump($e->getMessage());
}
}
}
$foo = new Test;
$foo->testing();
?>
上の例の出力は以下となります。
string(4) "foo!"
例6 複数の例外ハンドリングをひとつの 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
function test() {
throw new SpecificException('Oopsie');
}
try {
test();
} catch (SpecificException) {
print "A SpecificException was thrown, but we don't care about the details.";
}
?>
例8 throw を 式として扱う
PHP 8.0.0 以降でのみ許されます
<?php
class SpecificException extends Exception {}
function test() {
do_something_risky() or throw new Exception('It did not work');
}
try {
test();
} catch (Exception $e) {
print $e->getMessage();
}
?>
User Contributed Notes 13 notes
If you intend on creating a lot of custom exceptions, you may find this code useful. I've created an interface and an abstract exception class that ensures that all parts of the built-in Exception class are preserved in child classes. It also properly pushes all information back to the parent constructor ensuring that nothing is lost. This allows you to quickly create new exceptions on the fly. It also overrides the default __toString method with a more thorough one.
<?php
interface IException
{
/* Protected methods inherited from Exception class */
public function getMessage(); // Exception message
public function getCode(); // User-defined Exception code
public function getFile(); // Source filename
public function getLine(); // Source line
public function getTrace(); // An array of the backtrace()
public function getTraceAsString(); // Formated string of trace
/* Overrideable methods inherited from Exception class */
public function __toString(); // formated string for display
public function __construct($message = null, $code = 0);
}
abstract class CustomException extends Exception implements IException
{
protected $message = 'Unknown exception'; // Exception message
private $string; // Unknown
protected $code = 0; // User-defined exception code
protected $file; // Source filename of exception
protected $line; // Source line of exception
private $trace; // Unknown
public function __construct($message = null, $code = 0)
{
if (!$message) {
throw new $this('Unknown '. get_class($this));
}
parent::__construct($message, $code);
}
public function __toString()
{
return get_class($this) . " '{$this->message}' in {$this->file}({$this->line})\n"
. "{$this->getTraceAsString()}";
}
}
?>
Now you can create new exceptions in one line:
<?php
class TestException extends CustomException {}
?>
Here's a test that shows that all information is properly preserved throughout the backtrace.
<?php
function exceptionTest()
{
try {
throw new TestException();
}
catch (TestException $e) {
echo "Caught TestException ('{$e->getMessage()}')\n{$e}\n";
}
catch (Exception $e) {
echo "Caught Exception ('{$e->getMessage()}')\n{$e}\n";
}
}
echo '<pre>' . exceptionTest() . '</pre>';
?>
Here's a sample output:
Caught TestException ('Unknown TestException')
TestException 'Unknown TestException' in C:\xampp\htdocs\CustomException\CustomException.php(31)
#0 C:\xampp\htdocs\CustomException\ExceptionTest.php(19): CustomException->__construct()
#1 C:\xampp\htdocs\CustomException\ExceptionTest.php(43): exceptionTest()
#2 {main}
Easy to understand `finally`.
<?php
try {
try {
echo "before\n";
1 / 0;
echo "after\n";
} finally {
echo "finally\n";
}
} catch (\Throwable) {
echo "exception\n";
}
?>
# Print:
before
finally
exception
Custom error handling on entire pages can avoid half rendered pages for the users:
<?php
ob_start();
try {
/*contains all page logic
and throws error if needed*/
...
} catch (Exception $e) {
ob_end_clean();
displayErrorPage($e->getMessage());
}
?>
As noted elsewhere, throwing an exception from the `finally` block will replace a previously thrown exception. But the original exception is magically available from the new exception's `getPrevious()`.
<?php
try {
try {
throw new RuntimeException('Exception A');
} finally {
throw new RuntimeException('Exception B');
}
}
catch (Throwable $exception) {
echo $exception->getMessage(), "\n";
// 'previous' is magically available!
echo $exception->getPrevious()->getMessage(), "\n";
}
?>
Will print:
Exception B
Exception A
‘Normal execution (when no exception is thrown within the try block, *or when a catch matching the thrown exception’s class is not present*) will continue after that last catch block defined in sequence.’
‘If an exception is not caught, a PHP Fatal Error will be issued with an “Uncaught Exception …” message, unless a handler has been defined with set_exception_handler().’
These two sentences seem a bit contradicting about what happens ‘when a catch matching the thrown exception’s class is not present’ (and the second sentence is actually correct).
In case your E_WARNING type of errors aren't catchable with try/catch you can change them to another type of error like this:
<?php
set_error_handler(function($errno, $errstr, $errfile, $errline){
if($errno === E_WARNING){
// make it more serious than a warning so it can be caught
trigger_error($errstr, E_ERROR);
return true;
} else {
// fallback to default php error handler
return false;
}
});
try {
// code that might result in a E_WARNING
} catch(Exception $e){
// code to handle the E_WARNING (it's actually changed to E_ERROR at this point)
} finally {
restore_error_handler();
}
?>
Starting in PHP 7, the classes Exception and Error both implement the Throwable interface. This means, if you want to catch both Error instances and Exception instances, you should catch Throwable objects, like this:
<?php
try {
throw new Error( "foobar" );
// or:
// throw new Exception( "foobar" );
}
catch (Throwable $e) {
var_export( $e );
}
?>
The "finally" block can change the exception that has been throw by the catch block.
<?php
try{
try {
throw new \Exception("Hello");
} catch(\Exception $e) {
echo $e->getMessage()." catch in\n";
throw $e;
} finally {
echo $e->getMessage()." finally \n";
throw new \Exception("Bye");
}
} catch (\Exception $e) {
echo $e->getMessage()." catch out\n";
}
?>
The output is:
Hello catch in
Hello finally
Bye catch out
#3 is not a good example. inverse("0a") would not be caught since (bool) "0a" returns true, yet 1/"0a" casts the string to integer zero and attempts to perform the calculation.
When using finally keep in mind that when a exit/die statement is used in the catch block it will NOT go through the finally block.
<?php
try {
echo "try block<br />";
throw new Exception("test");
} catch (Exception $ex) {
echo "catch block<br />";
} finally {
echo "finally block<br />";
}
// try block
// catch block
// finally block
?>
<?php
try {
echo "try block<br />";
throw new Exception("test");
} catch (Exception $ex) {
echo "catch block<br />";
exit(1);
} finally {
echo "finally block<br />";
}
// try block
// catch block
?>
<?php
/**
* You can catch exceptions thrown in a deep level function
*/
function employee()
{
throw new \Exception("I am just an employee !");
}
function manager()
{
employee();
}
function boss()
{
try {
manager();
} catch (\Exception $e) {
echo $e->getMessage();
}
}
boss(); // output: "I am just an employee !"