skip to content

Object-Oriented PHP

How PHP builds objects: typed, readonly and hooked properties, single inheritance plus interfaces and traits, late static binding and enums. Interviewers probe what the 8.x releases changed.

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

explore

questions

page 1 of 2

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.1 and later, why use an enum instead of class constants for a fixed set of values such as order statuses?

level: juniorimportance: must knowfreq 62%

basics

~20 s

An enum is a real type with a closed set of cases, so a parameter typed OrderStatus accepts only those cases and rejects any string. Class constants are plain strings or ints that any typo or foreign value can impersonate.

open as a page

In PHP, what do the abstract and final modifiers mean on a class and on a method, and why can one member not be both?

level: juniorimportance: must knowfreq 62%

basics

~20 s

An abstract class cannot be instantiated and an abstract method has no body, so a concrete child must supply it. A final class cannot be extended and a final method or class constant cannot be overridden. Combining them is contradictory, so PHP rejects it.

open as a page

In PHP, how does a child class override an inherited method, and how does it still run the parent's version with parent::?

level: juniorimportance: must knowfreq 72%

basics

~20 s

A PHP class extends exactly one parent and overrides a method by declaring one with the same name and a compatible signature. Inside it, parent::name() runs the inherited body on the same object; a child constructor must call parent::__construct() itself.

open as a page

In PHP, are objects passed by reference, and what really happens when you assign an object or pass it to a function?

level: juniorimportance: must knowfreq 72%

basics

~20 s

No. A PHP object variable holds an object handle, and assignment, argument passing and return copy that handle, so both sides reach one instance. Rebinding a parameter never affects the caller; only & creates a true alias.

open as a page

In PHP, what is the difference between an interface and an abstract class, and when would you choose each?

level: juniorimportance: must knowfreq 80%

basics

~20 s

A PHP interface is a pure contract: public method signatures, constants and, since 8.4, hooked property declarations, and a class may implement many. An abstract class can also carry state and shared code, but a class extends only one.

open as a page

In PHP, what does declaring a method or property static change, and why can a static method not use $this?

level: juniorimportance: must knowfreq 68%

basics

~20 s

A static method or property belongs to the class, not to an object: it is reached with ClassName:: or self:: and holds one value per class. A static method runs without an instance, so $this does not exist inside it.

open as a page

In PHP, what does use SoftDeletes; inside a class body do, and how is a trait different from a parent class?

level: juniorimportance: must knowfreq 68%

basics

~20 s

Inside a class body, use SoftDeletes; copies the trait's methods, properties and constants into that class as if written there. Unlike a parent class, a trait is not a type, cannot be instantiated, and a class may use many.

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, what is the difference between a pure and a backed enum, and between from() and tryFrom() on a backed enum?

level: middleimportance: must knowfreq 58%

basics

~20 s

A pure enum's cases have only a name; a backed enum gives every case a unique int or string value. On a backed enum, from() returns the matching case or throws ValueError, while tryFrom() returns null when no case matches.

open as a page

In PHP, when exactly are __get, __set, __isset and __unset called, and what breaks if a class implements only __get?

level: middleimportance: must knowfreq 66%

basics

~20 s

They run only for inaccessible properties: undeclared ones, or ones not visible from the caller's scope. __get reads, __set writes, __isset answers isset() and empty(), __unset handles unset(). With only __get, isset() on a magic property returns false.

open as a page

In PHP 8.5, what do readonly properties and readonly classes allow and forbid once an object is initialized?

level: middleimportance: must knowfreq 60%

basics

~20 s

A readonly property accepts exactly one initializing assignment, from its class or since 8.4 a subclass, then any write, unset or reference throws an Error. A readonly class makes every declared property readonly and forbids dynamic properties.

open as a page

In PHP, what is the uninitialized state of a typed property, and how does it differ from a property holding null?

level: middleimportance: must knowfreq 55%

basics

~20 s

A typed property without a default has no value at all until something assigns it. Reading it throws an Error (must not be accessed before initialization), unlike null, which is a real value. Untyped properties default to null instead.

open as a page

In PHP, what is the difference between self:: and static:: inside a parent class method, and what is late static binding?

level: middleimportance: must knowfreq 74%

basics

~20 s

self:: always means the class where the method is written; static:: means the class the call was actually made on, resolved at runtime. That runtime lookup is late static binding, and it lets a parent's method reach a child's override.

open as a page

In PHP, when a class, a trait it uses and its parent class all define touch(), which runs, and what does parent::touch() inside the trait call?

level: middleimportance: must knowfreq 55%

basics

~10 s

The class's own touch() wins, a trait's touch() overrides the inherited one, and the parent's runs only if neither exists. Inside a trait method, parent::touch() calls the using class's parent.

open as a page

In PHP, you clone a draft quote and move the copy's DateTime expiry forward, but the original's expiry changes too; why, and how do you fix it?

level: seniorimportance: must knowfreq 55%

basics

~10 s

clone is shallow: an object-valued property is copied as a handle, so both quotes share one DateTime and modify() changes it for both. Deep-copy it in __clone() or store a DateTimeImmutable instead.

open as a page

In PHP, how do == and === behave when comparing two objects, and what traps hide in loose object comparison?

level: juniorimportance: should knowfreq 55%

basics

~20 s

=== on objects is true only for the same instance. == is true when both are instances of exactly the same class and every property compares equal with ==, recursively, so loose juggling applies to nested values.

open as a page

In PHP, what do the __toString and __invoke magic methods let an object do, and when does PHP call each?

level: juniorimportance: should knowfreq 50%

basics

~20 s

__toString lets an object act as a string: PHP calls it for echo, concatenation, interpolation, (string) casts and coercive string parameters. __invoke lets an object be called like a function, $obj($x), so it passes callable checks.

open as a page

In PHP 8.2 and later, what happens when code assigns an undeclared property on an object, and when is #[AllowDynamicProperties] justified?

level: juniorimportance: should knowfreq 45%

basics

~20 s

Since PHP 8.2, assigning an undeclared property creates it but emits E_DEPRECATED: Creation of dynamic property is deprecated. Declare the property instead; #[AllowDynamicProperties] is a last resort for legacy classes that truly need ad-hoc fields.

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, how should enum cases be compared, and why do < and > or array keys not work with them?

level: middleimportance: should knowfreq 40%

basics

~20 s

Compare enum cases with ===, which is identity on singleton objects, or instanceof for the type. Cases are objects, so < and > always return false and using one as an array key throws TypeError; key by ->value instead.

open as a page

In PHP, what can an enum declare besides its cases, and what does the engine forbid inside an enum?

level: middleimportance: should knowfreq 42%

basics

~20 s

An enum may declare methods, static methods, constants, interfaces and traits without properties, plus __call, __callStatic and __invoke. It may not have properties, constructors, destructors, abstract methods or other magic methods, and cannot extend or be extended.

open as a page

In PHP 8.3 and later, what does the #[\Override] attribute check, and which bug does it catch that plain overriding does not?

level: middleimportance: should knowfreq 30%

basics

~20 s

#[\Override] asserts that a method overrides a parent-class method or implements an interface method. If none exists, PHP fails when the class is declared, which catches overrides silently orphaned by a renamed or removed parent method.

open as a page

In PHP, which changes to a method's signature may an overriding method make, and which ones does the engine reject?

level: middleimportance: should knowfreq 48%

basics

~20 s

An override may widen parameter types, narrow the return type, add optional parameters and relax visibility. PHP rejects extra required or removed parameters, narrower parameter types, wider or dropped return types, stricter visibility and by-reference or static changes.

open as a page

In PHP, what is stdClass, and what does an (object) cast produce from an array, a scalar, null or an existing object?

level: middleimportance: should knowfreq 42%

basics

~20 s

stdClass is PHP's empty built-in class for ad-hoc objects; it allows dynamic properties and is not a base class of other objects. (object) turns array keys into properties shallowly, wraps a scalar in ->scalar, and returns objects unchanged.

open as a page

In PHP, what do the Countable and ArrayAccess interfaces let an object do, and where does the array illusion break down?

level: middleimportance: should knowfreq 40%

basics

~20 s

Countable makes count($obj) call the object's count() method. ArrayAccess maps $obj[$k] reads, writes, isset and unset to offsetGet, offsetSet, offsetExists and offsetUnset. The object is still not an array: array_* functions reject it and nested writes need offsetGet by reference.

open as a page

In PHP, what may an interface declare, and what happens when a class implements several interfaces or an interface extends others?

level: middleimportance: should knowfreq 48%

basics

~20 s

A PHP interface declares public method signatures, public constants and, since 8.4, hooked property requirements. An interface may extend several interfaces, a class may implement several, and one method must satisfy every interface that declares it.

open as a page

In PHP, when do __call and __callStatic run, and which one handles self::missing() inside an instance method?

level: middleimportance: should knowfreq 52%

basics

~20 s

__call runs when an inaccessible method is called on an object; __callStatic when one is called statically, like Settings::missing(). A static-style call such as self::missing() from inside an instance method goes to __call, because an object is available.

open as a page

In PHP 8.4 and later, what does asymmetric visibility such as public private(set) do, and how does it differ from readonly?

level: middleimportance: should knowfreq 30%

basics

~20 s

Since PHP 8.4, a property can have separate read and write visibility: public private(set) lets anyone read it but only the class write it, as often as it likes. readonly instead allows a single write ever.

open as a page

showing 1–30 of 47