skip to content

In PHP, what does Foo::class return, when is it resolved, and does it autoload or verify that Foo exists?

level: middleimportance: should knowfreq 42%

answer

  1. a string, not an object
  2. compile-time name expansion
  3. no autoload, no existence check
  4. $object::class since PHP 8.0
  5. static::class resolves at run time

basics

~20 s

Foo::class is a string holding the fully qualified class name, expanded at compile time through the file's imports and namespace; it neither autoloads nor checks that Foo exists, so a typo still yields a string.

solid answer

~40 s

`Foo::class` evaluates to the **fully qualified name** of `Foo` as a plain string, for example `'App\Model\Foo'`. The compiler produces it by applying the file's `use` table and namespace, exactly as it would for `new Foo`, so it is a safe replacement for hand-written class-name strings in container bindings, test doubles and attribute arguments. Because it is only a name expansion, it **does not trigger autoloading** and does **not** check that the class exists: `Does\Not\Exist::class` returns `'Does\Not\Exist'` without error. `static::class` is the exception resolved at run time (late static binding). Since PHP 8.0, `$object::class` also works on an object and returns the same as `get_class($object)`; on a non-object it throws `TypeError`.

code

php · 18 lines
php
<?php
declare(strict_types=1);

namespace App;

echo Missing\Thing::class, PHP_EOL; // App\Missing\Thing, no error
echo DateTime::class, PHP_EOL;      // App\DateTime (import forgotten)
echo \DateTime::class, PHP_EOL;     // DateTime

$now = new \DateTimeImmutable();
echo $now::class, PHP_EOL;          // DateTimeImmutable (PHP 8.0+)

$name = 'DateTimeImmutable';
try {
    echo $name::class;
} catch (\TypeError $e) {
    echo $e->getMessage();          // Cannot use "::class" on string
}

go deeper

for a junior

Recall that Foo::class gives the full class name as a string and is the preferred way to pass class names around.

for a middle

Explain that the expansion happens at compile time through imports, so it neither autoloads nor checks existence, and contrast static::class and $object::class.

for a senior

Rely on static analysis to catch ::class references to missing classes, since the engine happily produces strings for classes that do not exist.

for a principal

Push string class names toward ::class across a codebase: it turns refactors from text search into tool-checked edits, at the cost of relying on analysers for existence.

## What ::class is `::class` is a special constant available on any class name. It evaluates to a **string** containing the class's fully qualified name, without a leading backslash: ```php <?php namespace App\Model; use Psr\Log\LoggerInterface; echo User::class; // App\Model\User echo LoggerInterface::class; // Psr\Log\LoggerInterface echo \DateTime::class; // DateTime ``` It exists because class names are often needed as strings: dependency-injection container keys, `is_a()` and `is_subclass_of()` checks, test doubles such as a mock created from a class name, attribute arguments, event maps and `match` arms keyed by class. ## Resolved at compile time For a literal class name, `Foo::class` is expanded by the **compiler**, using the same rules as any other class reference in that file: 1. a fully qualified name (`\Foo`) is taken as is; 2. an imported short name or an imported first segment is translated through the `use` table; 3. anything else gets the current namespace prepended. This has two consequences interviewers like to probe: - **No autoloading.** Nothing is loaded; the string is produced before the class is ever needed. - **No existence check.** `Does\Not\Exist::class` compiles and returns `'Does\Not\Exist'`. A typo or a missing import produces a wrong string silently, and the failure surfaces later, wherever the string is used to instantiate or look up the class. Static analysis catches this; the engine does not. Because the result is a compile-time constant, it can be used in constant expressions: class constants, property and parameter defaults, and attribute arguments. ## The forms of ::class | Expression | Resolved | Result | |---|---|---| | `Foo::class` | compile time | FQCN of `Foo` via imports/namespace | | `self::class` | compile time in a class body (run time in a trait or closure) | the class the code is written in | | `static::class` | **run time** | the class the method was called on (late static binding) | | `parent::class` | compile time | the parent class name | | `$object::class` | **run time**, PHP 8.0+ | same as `get_class($object)` | `static::class` cannot appear in a constant expression; PHP rejects it with "static::class cannot be used for compile-time class name resolution". The same goes for `(expression)::class`. ## ::class on objects (PHP 8.0) Before PHP 8.0, `::class` required a literal class name, and code wrote `get_class($object)`. PHP 8.0 allowed `$object::class`, resolved at run time. Applied to something that is not an object, such as a string held in a variable, it throws `TypeError` with a message like `Cannot use "::class" on string`. So `$name::class` is not a way to normalise a class-name string; for that you still need the string itself. ## Why it beats string literals - **Refactoring safety.** Renaming or moving a class updates `Foo::class` references through IDE and analyser tooling; a string `'App\Model\Foo'` is just text. - **Import awareness.** `Foo::class` follows the file's `use` statements, so aliases resolve correctly: after `use Beta\Logging\Logger as BetaLogger;`, `BetaLogger::class` is `'Beta\Logging\Logger'`. - **No escaping traps.** A class name in a double-quoted string can collide with escape sequences such as `\n` or `\t`; `::class` never involves quoting. ## Where ::class shows up in modern PHP Typical uses in a PHP 8.5 codebase: - **Container bindings and factories**: a service identifier such as `LoggerInterface::class` rather than a hand-typed string. - **Test doubles**: a PHPUnit mock is created from `Mailer::class`, so renaming `Mailer` updates the test. - **Attributes**: arguments such as `#[CoversClass(Invoice::class)]` accept class names as constant expressions. - **Dispatch tables**: an array mapping `OrderPlaced::class` to a handler, or a `match (true)` over `instanceof` checks. - **Exception messages and logs**: `sprintf('%s failed', static::class)` names the real subclass at run time. In each case the benefit is the same: the name is checked by the same resolution rules as the rest of the file, and tools can follow it. ## Pitfalls - Treating `Foo::class` as proof that `Foo` exists or is loaded. Use `class_exists()` for that, which by default does trigger autoloading. - Forgetting an import: inside `namespace App;`, `DateTime::class` is `'App\DateTime'`, a string that names nothing. - Expecting `::class` to return an object or a reflection handle; it is only a string.

  • In PHP, can Foo::class be used as a class constant value or an attribute argument?
    Yes. A literal `Foo::class` (and `self::class`, `parent::class`) is resolved at compile time, so it is valid in constant expressions: class constants, property and parameter defaults, and attribute arguments. `static::class` and `(expression)::class` are resolved at run time and are rejected there with a compile error.
  • In PHP, does class_exists(Foo::class) trigger autoloading when Foo::class alone does not?
    Yes. `Foo::class` just produces the string. `class_exists()` then looks the class up and, with its second parameter `$autoload` left at its default `true`, asks the registered autoloaders to load it. Passing `false` checks only classes that are already declared.

saying these in an interview costs you the question

  • Foo::class triggers the autoloader to load Foo
  • Foo::class throws an error when the class does not exist
  • Foo::class returns a ReflectionClass or a Closure
  • $object::class has always worked, even before PHP 8.0
  • $name::class turns a class-name string into its normalised form