~/teardowns/TD-030.md
←Back to teardowns
# TD-030MediumRuntime

The Repository Method That Never Existed

How a Spring Data habit became a Doctrine fatal error

Published Oct 29, 2026

Executive Summary

A login check calls findByEmailAndActive() on a Doctrine repository — a magic finder that only exists in Spring Data JPA, where the framework generates the query from the method name. Doctrine has no such convention, so the method is undefined and the very first login crashes with a fatal error. The fix is Doctrine's real API: findOneBy() with a criteria array.

The Prompt

“Add a method to the UserRepository that looks up a user by email and active status for the login check.”

The Flaw

findByEmailAndActive() is a Spring Data JPA idiom — in that world, the framework derives the query from the method name and implements it for you. Doctrine has no such convention: repository methods must be written by hand. Because PHP resolves method calls at runtime, the file passes php -l, the class autoloads cleanly, and nothing fails until the login check actually runs — at which point PHP throws 'Call to undefined method App\Repository\UserRepository::findByEmailAndActive()'.

Risk & Impact

[Availability] The very first production login crashes with a fatal 'Call to undefined method' error. The method name looked exactly like the kind of thing a framework would auto-implement, so nobody questioned it — until a real user hit it.

Cost: Login feature dead on arrival; the error only surfaces with real traffic because no test or static check ever executed that code path.

Code Comparison

❌ Vulnerable (AI generated)

src/Repository/UserRepository.phpphp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
// src/Repository/UserRepository.php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function findActiveUserByEmail(string $email): ?User
{
// Spring Data JPA magic finder — Doctrine has no such method.
return $this->findByEmailAndActive($email, true);
}
}
src/Controller/LoginController.phpphp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
// src/Controller/LoginController.php
namespace App\Controller;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class LoginController extends AbstractController
{
public function check(UserRepository $users, string $email): Response
{
$user = $users->findActiveUserByEmail($email); // fatal here
if (!$user) {
return $this->json(['error' => 'invalid credentials'], 401);
}
return $this->json(['ok' => true]);
}
}

The Fix

Use Doctrine's actual API — findOneBy()/findBy() with a criteria array, or a QueryBuilder — instead of inventing Spring Data-style magic finder names the ORM does not implement.

Prevention

  • [linter] PHPStan or Psalm at level max flags 'Call to an undefined method' at CI time, so the hallucinated finder never reaches production.
  • [ci] One smoke test that instantiates the repository and calls the finder fails fast, before any deploy.
  • [manual] Name the ORM and version in the prompt (rule 1), and point the agent at an existing repository method to mirror instead of letting it guess the data-access API.

Why This Happened

Root cause: the prompt omitted Rule 1, Rule 7, and Rule 9 — the guardrails that would have caught this. See the full guide →

Sources

Is your codebase vulnerable to this?

Run Diagnostic on My Project →