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 use alias 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

knownClasses

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 (PDO, mysqli, DOM, ZipArchive, Redis, …)

© 2026 Xygeni. All rights reserved