skip to content

Constructors & Promotion

__construct with promoted parameters declares and assigns properties in one signature, and __destruct runs when the last reference goes. Interviewers check modern constructors and cleanup timing.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In PHP, what is __construct, when does it run, and what does PHP 8 no longer treat as a constructor?

level: juniorimportance: must knowfreq 65%

answer

  1. called by new on every instance
  2. arguments come from new Foo(...)
  3. one constructor, no overloading
  4. no return type allowed
  5. same-name methods lost meaning in 8.0

basics

~20 s

__construct is the method new calls on every freshly created object, receiving the arguments written after the class name. A class has at most one. Since PHP 8.0 a method named after the class is no longer a constructor.

solid answer

~50 s

`__construct()` is the **constructor**: `new Account('GB', 100)` allocates the object, fills declared property defaults, and then calls `__construct()` with those arguments so the object is valid before anyone uses it. A class has **one** constructor; PHP has no overloading, so alternative ways to build an object are written as static factory methods. The constructor cannot declare a return type (`Method Account::__construct() cannot declare a return type`), and it is exempt from the usual signature-compatibility rules when a subclass redeclares it. If it throws, `new` produces no object and that object's `__destruct()` never runs. The PHP 8 change: a method with the **same name as the class** used to act as a PHP 4-style constructor in non-namespaced classes, deprecated since 7.0; since **PHP 8.0** it is an ordinary method, so legacy classes relying on it silently lose their initialisation.

code

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

final class Account
{
    private array $entries = [];
    private string $country;

    public function __construct(string $country, int $openingCents)
    {
        if (strlen($country) !== 2) {
            throw new InvalidArgumentException('Country must be two letters');
        }
        $this->country = $country;
        $this->entries[] = $openingCents;
    }

    // PHP 8+: just a method, never called by new
    public function Account(): void {}
}

$a = new Account('GB', 100);

try {
    $b = new Account('GBR', 100);
} catch (InvalidArgumentException $e) {
    var_dump(isset($b)); // bool(false): no object was assigned
}

go deeper

for a junior

Recall that new calls __construct with the arguments in the parentheses, and that a class has only one constructor.

for a middle

Explain the creation order, the no-return-type rule, why same-name constructors broke in PHP 8.0, and what happens when a constructor throws.

for a senior

Keep constructors to validation and assignment, and remember that clone, unserialize and reflection create objects without running them.

for a principal

Set conventions for construction across a codebase: what belongs in a constructor, when to use factories, and how upgrades from legacy constructors are audited.

## What the constructor is for A **constructor** is the method that turns a freshly allocated object into a valid one. In PHP it is the magic method `__construct()`. Its job is to receive the data the object needs, check it, and store it in properties, so that no code ever sees a half-initialised instance. ## What happens on `new` 1. `new Account('GB', 100)` resolves the class (autoloading it if needed). 2. The engine allocates the object and applies **declared property defaults**, for example `private array $entries = [];`. Typed properties without a default stay *uninitialized*. 3. If the class has a `__construct()`, the engine calls it with the arguments in the parentheses. Named arguments work too. 4. When the constructor returns normally, `new` evaluates to the object handle. If the class has no constructor, or its constructor has no required parameters, the parentheses may be omitted: `new Ledger` is legal. ## Rules that surprise people - **One per class.** PHP has no constructor overloading. Different ways to build an object become static creation methods (`Money::fromCents()`), often with a private constructor. - **No return type.** Declaring `: void` or anything else is a compile error: `Method Account::__construct() cannot declare a return type`. A `return` statement's value is ignored by `new`. - **Not static.** A static `__construct()` is rejected by the engine. - **Exempt from signature compatibility.** A subclass may declare a constructor with completely different parameters; the substitution checks that apply to other methods do not apply here. - **Exceptions abort construction.** If `__construct()` throws, the assignment `$a = new Account(...)` never happens, and PHP marks the half-built object so its `__destruct()` is **not** called. - **Not called by `clone` or by unserializing.** Those paths create objects without running the constructor, which is why invariants cannot rely on the constructor alone when objects are cloned or restored. ## Old-style constructors | Version | `class Account { function Account() {} }` in the global namespace | |---|---| | PHP 5 | acts as the constructor when there is no `__construct()` | | PHP 7.x | still the constructor, with an `E_DEPRECATED` notice | | PHP 8.0+ | an ordinary method with no special meaning | In **namespaced** classes the same-name method never had special meaning. When both existed, `__construct()` always won. The trap in upgrades is silent: code that relied on a same-name constructor still compiles on PHP 8, but the method is no longer called by `new`, so properties stay at their defaults and typed ones stay uninitialized until something reads them and throws `Error: Typed property ... must not be accessed before initialization`. ## A modern constructor Modern PHP rarely writes the assignment boilerplate by hand. **Constructor property promotion** (PHP 8.0) declares and assigns a property from a parameter in one step, and PHP 8.1 lets a parameter default be an object (`private Clock $clock = new SystemClock()`). The body then only contains real logic, typically validation: - check invariants and throw `InvalidArgumentException` (or a domain exception) early; - do not perform I/O or call services that may fail for reasons unrelated to the object's validity; - keep it cheap, because every `new` pays for it. ## Reviewing a constructor A short checklist that interviewers like to hear: - Does every required dependency arrive as a parameter, rather than being fetched from a global or a static registry? - Does the constructor reject invalid input with an exception instead of leaving a flag for later checks? - Are all typed properties initialised on every path through the body? - Is there any I/O (queries, HTTP calls, file reads) that would make `new` slow or fail for unrelated reasons? - Would the class need a second way to be built? Then add a named static creation method instead of optional parameters that change meaning. ## Summary `__construct()` runs once per `new`, receives the constructor arguments, cannot declare a return type, and is the only constructor a class can have. A same-name method has not been a constructor since PHP 8.0.

  • If `__construct()` throws, does `__destruct()` run for the half-built object?
    No. When the constructor fails, the engine flags the object as already destructed, so `__destruct()` is skipped, and the variable on the left of `new` is never assigned. Resources the constructor opened before throwing must be released in the constructor itself, for example in a `finally` block or before the `throw`.
  • How do you offer several ways to create an object when PHP allows only one constructor?
    Write public static creation methods, sometimes called named constructors, such as `Money::fromCents()` and `Money::zero()`. Each prepares its input and calls the single constructor with `new self(...)`. Making the constructor private forces every caller through those methods.

saying these in an interview costs you the question

  • A method with the same name as the class is still a constructor in PHP 8.
  • You can declare several __construct methods with different parameter lists.
  • A constructor should declare : void as its return type.
  • clone and unserialize call the constructor on the new object.
  • If the constructor throws, __destruct still runs to clean up.
open as a page

In PHP 8, what does constructor property promotion do, and which rules limit what a promoted parameter may declare?

level: middleimportance: must knowfreq 65%

basics

~20 s

A constructor parameter with a modifier such as private declares a property of the same name and type and assigns the argument to it before the body runs. Promotion is not allowed for callable types, variadics or abstract constructors.

open as a page

In PHP 8.1 and later, where may a new expression be used as an initializer, and what did PHP 8.4 change about chaining on new?

level: middleimportance: should knowfreq 30%

basics

~10 s

Since PHP 8.1, new may appear in parameter defaults, static variable initializers, global constants and attribute arguments, but not in property defaults or class constants. PHP 8.4 lets new Foo()->m() chain without wrapping parentheses.

open as a page

In PHP, how do a private constructor and static named constructors like Money::fromCents() work together, and what does the private constructor enforce?

level: middleimportance: should knowfreq 40%

basics

~20 s

Make __construct private and expose public static methods such as Money::fromCents() that call new self(...). Outside code can then only create objects through those named entry points, while the private constructor keeps a single validation path.

open as a page

In PHP, when exactly does __destruct run, and why is it risky to rely on a destructor to flush or commit work?

level: seniorimportance: should knowfreq 40%

basics

~20 s

__destruct runs as soon as the last handle to an object disappears, or at shutdown if the object is still alive. Fatal errors skip it, cycles delay it and shutdown order is undefined, so commit work explicitly.

open as a page