かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

PHP class の例外処理がキャッチできないにハマる

PHP で class を書いていて、try 〜 cache 文で例外処理がキャッチできなくてハマったのでメモ

別クラスの例外がキャッチできなかった例

<?php
namespace MyProject\Package;
class FileLoader {
  public function __construct() {}
  public function loadFile(string $file) {
    if ( !is_file($file) ) {
      throw new InvalidArgumentException("{$file} is not a file");
    }
    $contents = file_get_contents($file);
    if ( $contents === false ) {
      throw new RuntimeException("Error reading file at {$file}.");
    }
    return $contents;
  }
}
<?php
namespace MyProject\Package;
class MyClass {
  public function __construct(string $file) {
    $fileLoader = new FileLoader();
    try {
      $data = $fileLoader->loadFile($file);
      // …
    } catch ( InvalidArgumentException $exception ) {
      // 例外処理 ...
    }
 }
}

FileLoader クラスでファイルが存在しない時 (!is_file($file)) に呼び出し元で例外をキャッチしたかったのですが、例外をキャッチできずに画面に InvalidArgumentException エラーが表示されてしまいました…

namespace がある場合 グローバルなクラスは \ 始まりで呼び出す必要がある

Using \ in front of class name, it mean you call the class from global space. If you don't use \, it will call the class in same namespace with your code. But if you don't use namespace in your code, it will call class from global space.
cf. php - Difference between (Exception $e) and (\Exception $e) in try{}catch(){} - Stack Overflow

namespace のあるクラスでは

  • \ 始まりのクラスは、global から呼び出している
  • \ のないクラスは、今のクラスの namespace から呼び出している
<?php
namespace Module\Example;

class Test
{
  try {
  } catch(Exception $e) { // will look up Module\Example\Exception
  }

  try {
  } catch(\Exception $e) { // will look up Exception from global space
  }
}

先の例では例外を投げている部分 throw new InvalidArgumentException("{$file} is not a file"); のエラークラスが MyProject\Package\InvalidArgumentException クラスのエラーという扱いになっていてうまくキャッチできてなかったのが原因のようででした

👇 コードを修正する

<?php
namespace MyProject\Package;
class FileLoader {
  public function __construct() {}
  public function loadFile(string $file) {
    if ( !is_file($file) ) {
-     throw new InvalidArgumentException("{$file} is not a file");
+     throw new \InvalidArgumentException("{$file} is not a file");
    }
    $contents = file_get_contents($file);
    if ( $contents === false ) {
-     throw new RuntimeException("Error reading file at {$file}.");
+     throw new \RuntimeException("Error reading file at {$file}.");
    }
    return $contents;
  }
}
<?php
namespace MyProject\Package;
class MyClass {
  public function __construct(string $file) {
    $fileLoader = new FileLoader();
    try {
      $data = $fileLoader->loadFile($file);
      // …
-   } catch ( InvalidArgumentException $exception ) {
+   } catch ( \InvalidArgumentException $exception ) {
      // 例外処理 ...
    }
 }
}

これで意図したとおりに例外がキャッチできるようになりました!  
PHP の namespace 未だによく分かってない…


[参考]