In PHP, what does Foo::class return, when is it resolved, and does it autoload or verify that Foo exists?
answer
- a string, not an object
- compile-time name expansion
- no autoload, no existence check
- $object::class since PHP 8.0
- static::class resolves at run time
basics
~20 sFoo::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
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
Recall that Foo::class gives the full class name as a string and is the preferred way to pass class names around.
Explain that the expansion happens at compile time through imports, so it neither autoloads nor checks existence, and contrast static::class and $object::class.
Rely on static analysis to catch ::class references to missing classes, since the engine happily produces strings for classes that do not exist.
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