エラーの内容
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分の無駄な試行錯誤を省ける。
- メソッドが定義されていない — 存在しないものを呼び出している。
- メソッド名のタイポ —
findByEmail、findByMail、getByEmailなどの混同。 - 解決されるクラスが違う — オートローダーがベンダーパッケージの別の
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);
}

