Instantiation Of An Undeclared Class
ID |
php.undefined_class |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Php |
Tags |
best-practice, reliability |
Description
Reports a new X(…) whose class is declared nowhere in the scanned project, is not brought in by
a use that resolves to a scanned declaration, and is not a PHP built-in.
Instantiating a class PHP cannot load is a fatal Class "X" not found the first time the line
runs. Because PHP resolves names at run time, nothing before that point says so: a renamed class, a
deleted one, or a typo in a rarely-taken branch reaches production intact.
This rule ships disabled. See Configuration for when it is safe to switch on.
Rationale
namespace App;
use App\Model\Catalog;
class Service
{
public function build(): array
{
return [
new Catalog(), // resolves — declared in App\Model
new \DateTimeImmutable(), // resolves — a PHP built-in
new Missing(), // fatal the first time this line runs
];
}
}
The failure mode is what makes this worth catching. A missing method is a fatal too, but a missing class is typically the residue of a rename or a move: the declaration went to a new namespace, every other reference was updated, and one was left behind in a branch that no test exercises. The code deploys, the happy path works, and the error surfaces on the first request that reaches the branch.
Remediation
Fix the reference, or restore the declaration.
namespace App;
use App\Model\Catalog;
use App\Model\Manifest;
class Service
{
public function build(): array
{
return [
new Catalog(),
new Manifest(),
];
}
}
If the class comes from a library, make sure the scan covers the library, or list its classes in
knownClasses.
Configuration
The rule is disabled by default, and deliberately so. Its verdict is only as complete as the set
of files the scan reads. PHP has no compile-time linking: a class arrives through an autoloader at
run time, and the file that declares it may be anywhere. A project whose dependencies live in an
unscanned vendor/ directory would therefore get a finding for every new of a library class,
which is noise rather than signal.
Enable it on a project that is scanned whole, or on one whose external classes are all listed in
knownClasses.
Nothing about autoloading is guessed at. A name is either matched against a declaration the scan actually read, or it is reported. Name resolution follows PHP’s own rules, widened only where widening can remove findings and never add them:
-
a leading
\pins the name to the global namespace, and it is taken as written; -
a first segment matching a
usealias is replaced by the alias target — plain, aliased and group imports (use A\B\{C, D as E};) all resolve; -
anything else is resolved against the enclosing namespace and against the global namespace. PHP only does the first for classes, but accepting both can only silence a finding.
Shapes with no static name are skipped entirely: new $class(), new self(), new static(),
new parent(), and an anonymous new class { … }.
| Property | Meaning | Default |
|---|---|---|
|
Classes that exist without being declared in the project. Extend it with the classes of any further PHP extension, or of any library the scan does not cover. |
The engine’s own classes, the throwable hierarchy, SPL, reflection, the date and intl classes, and
the classes of the extensions most projects enable ( |