Skip to content

Doubling a final class

A final class you do not own, no interface, and no way to change it — that is what bypassFinals() is for:

php
// In your test bootstrap, before the class is autoloaded.
Understudy::bypassFinals(FinalGate::class);   // one class
Understudy::bypassFinals();                   // every class this process loads

It is opt-in because the technique has limits that are better met knowingly than discovered.

The limits

Order mattersit works only for a class not yet read from disk; a class is read once per process
The process is changedthe class really is not final any more, so reflection in your test sees something production does not
final methods staya final method cannot be overridden either way, so a class carrying one is still refused
PHAR and preloaded classestheir source arrives as phar://, or before any bootstrap ran, so it never passes through the file:// wrapper
The opcode cache is not a way backhowever warm the cache is, and whether or not it holds the bypassed file — Linux keeps it out, Windows does not — the class stays open. Not being cached is a cost where it happens, not a guarantee to rely on
Another source transformerif something else is already rewriting PHP source, understudy refuses rather than replacing it silently; a wrapper that leaves source alone composes and is accepted

The refusal tells you which one it was

When a class is still final at for(), the message says which case applies — bypass never asked for, asked for other classes but not this one, or asked for and out of reach — rather than sending you to check the thing that is already right.

Prefer these first

In order:

  1. Double an interface the class implements. The usual answer, and the one that leaves the process alone.
  2. For a value object, build a real one. A double of a thing with no collaborators buys nothing.
  3. Introduce an interface. Where the class is yours to change.

Bypass is the answer when none of those is available.