skip to content

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.