PHPのお勉強!

PHP TOP

例外(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 文が trycatch ブロックの内部に存在した場合でも、 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

例4 finally ブロックと return の相互作用

<?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();
}
?>
add a note

User Contributed Notes 13 notes

up
125
ask at nilpo dot com
15 years ago
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}
up
12
tianyiw at vip dot qq dot com
1 year ago
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
up
80
Johan
13 years ago
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());
}
?>
up
6
jlherren
11 months ago
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
up
24
Shot (Piotr Szotkowski)
16 years ago
‘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).
up
15
christof+php[AT]insypro.com
7 years ago
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();
}
?>
up
15
daviddlowe dot flimm at gmail dot com
7 years ago
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 );
}

?>
up
25
Edu
11 years ago
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
up
15
Simo
9 years ago
#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.
up
16
telefoontoestel at nospam dot org
10 years ago
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
?>
up
11
mlaopane at gmail dot com
6 years ago
<?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 !"