In PHP, which operations on an undefined class trigger the autoloader, and which ones, such as instanceof, never do?
answer
- the class must actually be needed
- new, static calls, class constants
- class_exists() with $autoload = true default
- instanceof, catch and type checks do not
- ::class is only a string
basics
~10 sAutoloading runs when PHP needs the class itself: new, static method or property access, class constants, extends and implements, and class_exists() by default. instanceof, catch clauses, type declarations and ::class never autoload.
solid answer
~40 sPHP autoloads only when it **needs the class definition**. That covers `new Foo()`, `Foo::create()`, `Foo::$count`, `Foo::VERSION`, a class that `extends` or `implements` something not yet loaded, and `class_exists()`, `interface_exists()`, `trait_exists()` and `enum_exists()`, whose `$autoload` parameter defaults to `true`. It does **not** autoload for checks against an existing value: `$x instanceof Foo`, `catch (Foo $e)` and parameter, return or property **type checks** simply answer "no" when `Foo` is not loaded, because no object of an unloaded class can exist. `Foo::class` is expanded at compile time and loads nothing. Passing `false` as the second argument of `class_exists()` checks only already-declared classes, which is useful inside an autoloader or for optional integrations.
code
php · 12 lines<?php
declare(strict_types=1);
spl_autoload_register(static function (string $class): void {
echo "autoload {$class}\n";
});
$x = new \stdClass();
var_dump($x instanceof Missing\Thing); // bool(false), no autoload
echo Missing\Thing::class, "\n"; // Missing\Thing, no autoload
var_dump(class_exists('Missing\Thing', false)); // bool(false), no autoload
var_dump(class_exists('Missing\Thing')); // autoload Missing\Thing, bool(false)go deeper
Recall the main triggers: new, static calls and class_exists(); and that use statements do not load anything.
Explain why instanceof, catch and type checks never autoload, and how the second argument of class_exists() changes its behaviour.
Use class_exists() deliberately for optional integrations, avoid re-entrant lookups inside autoloaders, and watch for catch clauses that silently never match.
Design extension points around explicit existence checks and registration rather than implicit autoload side effects, so loading stays cheap and predictable.
## The principle The autoloader queue runs when the engine has to **resolve a class name to a class definition** and does not have it yet. If an operation can be answered without the definition, PHP does not load anything. That single rule explains the whole list. ## Operations that trigger autoloading | Operation | Example | Why it needs the class | |---|---|---| | Instantiation | `new Invoice()` | must know the class to build an object | | Static method call | `Invoice::fromArray($a)` | must find the method | | Static property | `Invoice::$count` | must find the property | | Class constant | `Invoice::VAT_RATE` | must read the value | | Inheritance at declaration | `class Pdf extends Document {}` | must link the parent, interfaces and traits | | Existence checks | `class_exists('Invoice')` | `$autoload` defaults to `true` | `interface_exists()`, `trait_exists()` and `enum_exists()` behave like `class_exists()`, each with an `$autoload` parameter that defaults to `true`. Several reflection and class-introspection functions that accept a class name as a string also need the definition and therefore load it. ## Operations that never autoload | Operation | Example | Why no load is needed | |---|---|---| | `instanceof` | `$x instanceof Invoice` | an object of an unloaded class cannot exist, so the answer is `false` | | `catch` | `catch (PaymentFailed $e)` | a thrown object's class is already loaded; an unknown name just never matches | | Type checks | `function f(Invoice $i)` | the argument's class is already loaded; the check compares against it | | `::class` | `Invoice::class` | a compile-time string expansion | | `use` imports | `use App\Billing\Invoice;` | a compile-time alias only | | Existence check with `false` | `class_exists('Invoice', false)` | explicitly asks for declared classes only | This is by design: autoloading costs a file-system lookup and a compilation, and none of these operations benefit from it. ## Consequences in real code 1. **Optional integrations.** `if (class_exists(Redis::class)) { ... }` autoloads if an autoloader knows the class, and returns `false` otherwise, which is the idiom for optional dependencies. 2. **Checks inside an autoloader.** Calling `class_exists($name)` with the default inside an autoloader would ask the queue again for the same name. PHP guards against re-entering the autoloader for a class it is already loading, but the clear form is `class_exists($name, false)`. 3. **Silent mismatches.** Because `catch` and `instanceof` never autoload, a typo or missing import in a `catch` clause is not reported; the clause never matches. 4. **Inheritance chains.** Declaring `class Pdf extends Document` triggers loading of `Document`, and in turn its parents and interfaces, so a single `new Pdf()` can load several files. ## Checks that look similar but differ - `is_a($object, 'Invoice')` works on an object and does not need to load anything; with a class-name string it only considers strings when `$allow_string` is `true`, which is not the default for `is_a()`. - `is_subclass_of()` accepts a class-name string by default, because its `$allow_string` parameter defaults to `true`. - `get_class($object)` and `$object::class` read the class of an existing object and never autoload. - `method_exists()` and similar introspection functions that accept a class name as a string need the definition, so treat them as potential autoload triggers. ## Where the queue runs The engine calls the registered callbacks in order, passing the fully qualified name. If one of them throws, the lookup stops and the exception propagates from the triggering operation, for example from `new` or from `class_exists()`. That is another reason autoloaders should return silently for names they do not handle. ## Summary - **Needs the definition**: `new`, static access, class constants, inheritance, `*_exists()` by default. - **Does not**: `instanceof`, `catch`, type declarations, `::class`, `use`, `*_exists(..., false)`. - The difference follows from whether an object of the class could already exist.
- In PHP, why does class_exists($name, false) appear inside autoloaders and optional-integration checks?The second argument disables autoloading, so the call only reports classes already declared. Inside an autoloader it avoids asking the queue about the very name being loaded; elsewhere it answers "is this already loaded?" without paying for a file lookup. With the default `true`, the call may include and compile a file.
- In PHP, can a type declaration like function pay(Invoice $i) cause Invoice to be autoloaded?No. Checking an argument against a class type compares the object's class with the declared name; if `Invoice` is not loaded, no object can be an `Invoice`, so the check fails with `TypeError` without consulting the autoloaders. Declaring the function does not load the class either.
saying these in an interview costs you the question
- instanceof loads the class so it can compare
- A use statement loads the imported class
- class_exists() never triggers autoloading
- Type declarations autoload every class they mention
- Foo::class autoloads Foo to get its real name