PHP Fatal error: Call to undefined method の修正 — オブジェクトにメソッドが存在しない場合

intermediate🐘 PHP2026-05-22| PHP 7.4以降 / PHP 8.x、任意OS(Linux/Windows/macOS)、フレームワーク:Laravel、Symfony、通常のPHP OOP

Error Message

PHP Fatal error: Uncaught Error: Call to undefined method App\Models\User::findByEmail() in /var/www/app/controllers/AuthController.php:45
#php#oop#メソッド#クラス#fatal-error

エラーの内容

PHP Fatal error: Uncaught Error: Call to undefined method App\Models\User::findByEmail() in /var/www/app/controllers/AuthController.php:45
Stack trace:
#0 /var/www/app/controllers/AuthController.php(45): App\Models\User::findByEmail('admin@example.com')
#1 /var/www/app/index.php(12): AuthController->login()
#2 {main}
  thrown in /var/www/app/controllers/AuthController.php on line 45

深夜2時。デプロイが完了したばかり。ユーザーがログインできない。ログにはこのエラーが連発している。素早く解決する方法を説明する。

原因

PHPはクラス(App\Models\User)自体は見つけられたが、そのクラスにfindByEmail()が存在しない。原因は6つ考えられる。それぞれ対処法が異なるため、正しい原因を先に特定することで、30分の無駄な試行錯誤を省ける。

  • メソッドが定義されていない — 存在しないものを呼び出している。
  • メソッド名のタイポfindByEmailfindByMailgetByEmailなどの混同。
  • 解決されるクラスが違う — オートローダーがベンダーパッケージの別のUserクラスや、キャッシュされたDIバインディングを読み込んでいる。
  • メソッドがトレイトや親クラスに定義されているが、インクルードまたは継承されていない。
  • staticと非staticの不一致public function(非static)として定義されたメソッドをUser::findByEmail()としてstatic呼び出ししている、あるいはその逆。
  • コンパイルキャッシュが古い — LaravelやSymfonyはbootstrap/cache/にクラス定義をキャッシュする。キャッシュが古いと、新しいメソッドを追加する前の古いクラスが使われ続ける。

ステップ1 — 実際に使われているクラスを確認する

コードにはまだ手を加えない。まず、呼び出し箇所で解決されているクラスをダンプする:

// AuthController.php の45行目の直前に追加:
var_dump(get_class($user)); // インスタンスの場合
// staticの場合:
var_dump(User::class);
exit;

意外な結果が出ることがある。名前空間の衝突やDIコンテナの設定ミスにより、findByEmailが最初から存在しないベンダーパッケージのクラスがこっそり差し込まれている場合がある。

次に、PHPが実際に読み込んだファイルを確認する:

$ref = new ReflectionClass(App\Models\User::class);
var_dump($ref->getFileName());
exit;

ステップ2 — クラス定義を確認する

getFileName()が返したファイルを開き、メソッドを検索する:

grep -n 'findByEmail' /var/www/app/Models/User.php

結果が出なかった場合は、以下の原因に応じた修正方法を選ぶ。

修正A — 不足しているメソッドを追加する

最もシンプルなケース:メソッドが実装されていない。追加しよう:

// app/Models/User.php
namespace App\Models;

class User
{
    public static function findByEmail(string $email): ?self
    {
        global $pdo;
        $stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? LIMIT 1');
        $stmt->execute([$email]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) return null;

        $user = new self();
        foreach ($row as $key => $value) {
            $user->$key = $value;
        }
        return $user;
    }
}

Laravelの注意点:findByEmailはEloquentの組み込みメソッドではない。慣用的な代替手段は次のとおり:

// User::findByEmail($email) の代わりに
$user = User::where('email', $email)->first();

または、モデルに適切なstaticメソッドとして追加する:

// app/Models/User.php (Laravel Eloquent)
public static function findByEmail(string $email): ?static
{
    return static::where('email', $email)->first();
}

修正B — タイポを修正する

メソッドは存在するが、名前が少し違う。まず確認しよう:

php -r "print_r(get_class_methods('App\\Models\\User'));"

クラスのすべてのpublicメソッドが一覧表示される。findByEmailに最も近いものを探し、呼び出し箇所を修正する:

// 誤り
$user = User::findByEmail($email);

// 正しい(定義に合わせる)
$user = User::findByMail($email);
// または
$user = User::getByEmail($email);

IDEのオートコンプリートを使えば、コードを実行する前にこのミスを検出できる。PhpStormとIntelephense拡張を導入したVS Codeは、未定義メソッドの呼び出しをインラインでフラグ表示する。まだ設定していない場合は有効にしておく価値がある。

修正C — トレイトをインクルードするか正しい親クラスを継承する

メソッドは定義されているが、クラスから参照できない場所にある。よくある2つのパターン:

トレイトのインクルード漏れ:

// app/Traits/FindableByEmail.php
trait FindableByEmail
{
    public static function findByEmail(string $email): ?static
    {
        return static::where('email', $email)->first();
    }
}

// app/Models/User.php
class User extends Model
{
    use FindableByEmail; // create(['email' => 'test@example.com']);
    $found = User::findByEmail('test@example.com');
    $this->assertNotNull($found);
    $this->assertEquals($user->id, $found->id);
}

Related Error Notes