In PHP, how do you create an object when its class name is only known at run time, and when is ReflectionClass::newInstanceArgs() actually needed?
answer
- new works with a string variable
- argument unpacking with ...$args
- string keys become named arguments
- inspect before you construct
- newInstanceWithoutConstructor() skips the constructor
basics
~20 sPlain new already accepts a variable: new $class(...$args), with string keys passed as named arguments. ReflectionClass::newInstanceArgs() does the same; reflection is needed when you must inspect the class first, or build it without its constructor or lazily.
solid answer
~40 s`new $className($a, $b)` and `new $className(...$args)` work with no reflection at all; since PHP 8.0 string keys in the unpacked array become named arguments. `ReflectionClass::newInstanceArgs(array $args = [])` does the same job, returning the object (or `null` on failure) and throwing `ReflectionException` for a non-public constructor. Reflection earns its place when you need **information before construction**: `isInstantiable()` to reject interfaces and abstract classes, `getConstructor()` (which is `null` when there is none) and its `getParameters()` to decide what to pass. It is also the only route to `newInstanceWithoutConstructor()` for hydrating objects from storage, and to `newLazyGhost()`/`newLazyProxy()`. A missing class gives `Error: Class "X" not found` with `new`, and `ReflectionException: Class "X" does not exist` with `new ReflectionClass('X')`.
code
php · 18 lines<?php
declare(strict_types=1);
final class SmtpTransport
{
public function __construct(public string $host, public int $port = 25) {}
}
$class = SmtpTransport::class;
$args = ['port' => 587, 'host' => 'smtp.example.test'];
$a = new $class(...$args); // no reflection needed
$b = (new ReflectionClass($class))->newInstanceArgs($args); // same result
var_dump($a == $b); // bool(true)
$raw = (new ReflectionClass($class))->newInstanceWithoutConstructor();
// $raw->host is uninitialized: the constructor never rango deeper
Know that new $class(...$args) works with a class name in a variable and that the name must be fully qualified.
Explain string-key unpacking as named arguments, what newInstanceArgs() adds, and the errors for missing or abstract classes.
Choose reflection only where it adds information or bypasses construction, and never let request input pick the class to instantiate.
Decide where dynamic instantiation belongs in an architecture, typically confined to a container or plugin loader with an allow-list of types.
## Dynamic instantiation without reflection PHP's `new` operator accepts a **class name held in a variable**, so most dynamic creation needs no Reflection API at all: ```php $class = App\Mail\SmtpTransport::class; $transport = new $class('smtp.example.test', 587); $transport = new $class(...$args); // positional from a list $transport = new $class(...['port' => 587, 'host' => 'smtp.example.test']); ``` The `...` operator **unpacks** an array (or any `Traversable`) into arguments. Integer keys become positional arguments; **string keys become named arguments**, a behaviour that arrived with named arguments in PHP 8.0. The name must be a fully qualified class name string — `use` imports do not apply to strings — which is why code writes `Foo::class` rather than a literal. Failure modes of plain `new`: - an unknown class throws `Error` with `Class "X" not found`; - an interface, trait, enum or abstract class throws `Error`, such as `Cannot instantiate abstract class X`; - a wrong argument throws `TypeError` or `ArgumentCountError`. ## What ReflectionClass adds `ReflectionClass` describes a class; three of its methods create instances: | Method | Behaviour | |---|---| | `newInstance(mixed ...$args)` | calls the constructor with the given arguments | | `newInstanceArgs(array $args = [])` | same, with an array; string keys are named arguments since 8.0 | | `newInstanceWithoutConstructor()` | allocates the object with default property values, constructor **not** called | `newInstanceArgs()` is documented as returning the object or `null` on failure, and throws `ReflectionException` when the constructor is not public, or when arguments are given to a class that has no constructor. An `Error` is thrown if a string key names no constructor parameter. So `newInstanceArgs($args)` and `new $class(...$args)` are equivalent for the common case. Choosing reflection only to call a constructor adds nothing. ## When reflection is actually needed 1. **Inspect first.** A plugin loader or container must know whether the class *can* be built and what it needs: `isInstantiable()` is `false` for interfaces, traits, abstract classes and classes with a non-public constructor; `getConstructor()` returns a `ReflectionMethod` or `null`; `getParameters()` lists what the constructor wants. 2. **Skip the constructor.** Hydrators and deserialisers that restore an object from a database row call `newInstanceWithoutConstructor()`, then set properties, because the constructor may validate input or have side effects. 3. **Create lazily.** Since PHP 8.4, `newLazyGhost()` and `newLazyProxy()` build objects whose initialisation is deferred. 4. **Report precisely.** `new ReflectionClass('X')` throws `ReflectionException` with `Class "X" does not exist`, which a loader can catch separately from constructor errors. ## Error behaviour side by side | Situation | `new $class(...)` | `ReflectionClass` route | |---|---|---| | Class does not exist | `Error`: `Class "X" not found` | `ReflectionException`: `Class "X" does not exist` | | Interface or abstract class | `Error`: `Cannot instantiate ...` | `isInstantiable()` is `false` beforehand | | Non-public constructor | `Error` from outside the class | `ReflectionException` from `newInstanceArgs()` | | Unknown named argument | `Error` | `Error` | | Wrong argument type | `TypeError` | `TypeError` | The useful difference is not the exception class but the ability to **ask first**: a loader can check `isInstantiable()` and inspect the constructor's parameters, then report "plugin X needs a Y" instead of surfacing a raw `ArgumentCountError` to the user. ## Checking a name before using it - `class_exists($class)` triggers the autoloader and returns a `bool` — enough for a quick guard. - `is_subclass_of($class, Plugin::class)` or `is_a($class, Plugin::class, true)` checks the type without creating an object. - Never build class names from raw request input: combined with autoloading, that lets a caller choose which class your code instantiates. ## Summary - Everyday dynamic creation: `new $class(...$args)`. - Reflection: inspecting constructors, bypassing them, or laziness.
- Why would a PHP hydrator use newInstanceWithoutConstructor() instead of new?The object already existed once and its state is being restored from storage, so running the constructor again could re-validate, generate new IDs or trigger side effects. The hydrator allocates the object without the constructor and then writes the stored property values, typically through `ReflectionProperty::setValue()`.
- What does ReflectionClass::isInstantiable() return for a class with a private constructor?`false`. It is `true` only for concrete classes whose constructor, if any, is public. Such a class, a singleton for instance, cannot be built with `new` from outside or with `newInstanceArgs()`, which throws `ReflectionException` for a non-public constructor.
saying these in an interview costs you the question
- You need ReflectionClass to instantiate a class whose name is in a variable.
- String keys in an unpacked argument array are ignored.
- use imports apply to class names stored in strings.
- newInstanceWithoutConstructor() still runs the constructor, just without arguments.
- new with an unknown class name returns null instead of throwing.