PHP Constructors

A constructor is a special method that runs automatically every time you create a new object from a class. In PHP, that method is named __construct(). Constructors let you guarantee that an object starts life in a valid, fully-initialized state — instead of creating an empty object and hoping someone remembers to set its properties afterward. Understanding constructors thoroughly is essential to writing reliable object-oriented PHP.

Overview: How Constructors Work

Every PHP class can define at most one constructor, using the reserved method name __construct(). When you call new ClassName(...), PHP’s Zend Engine performs two steps internally: first it allocates a new, empty object (a zend_object structure) with its properties set to their declared defaults; second, if a __construct() method exists, the engine immediately invokes it on that new object, passing along whatever arguments you supplied to new. The object reference is then returned to your code — you never call __construct() yourself in normal usage, and it never has (or needs) a return value, because the newly created object is always what gets returned by new.

Constructors are inherited like any other method. If a child class does not define its own __construct(), it automatically uses its parent’s constructor. If the child does define one, PHP does not automatically call the parent’s constructor for you — you must call it explicitly with parent::__construct() if you still want the parent’s initialization logic to run. This is one of the most common sources of bugs in PHP OOP code, covered in detail below.

PHP does not support constructor overloading (multiple constructors with different signatures, as in Java or C#). Instead, you simulate flexible construction using default parameter values, nullable types, union types, or named arguments. A constructor’s visibility can also be changed: a public constructor (the default) can be called from anywhere; a protected or private constructor prevents direct instantiation from outside the class, which is the foundation of patterns like Singleton and named static factory methods (e.g. DateTime::createFromFormat()-style APIs).

Constructor Property Promotion

Since PHP 8.0, you can declare and initialize properties directly inside the constructor’s parameter list — this is called constructor property promotion. Adding a visibility keyword (public, protected, or private) to a constructor parameter tells PHP to automatically create a property of the same name and assign the incoming argument to it, without you writing $this->property = $property; by hand. It is pure syntax sugar generated at compile time — the resulting object is identical to one built the traditional way — but it removes an enormous amount of boilerplate from typical data-holding classes.

Syntax

The general form of a constructor looks like this:

<?php

class ClassName
{
    public string $property1;
    public int $property2;

    public function __construct(string $property1, int $property2 = 0)
    {
        $this->property1 = $property1;
        $this->property2 = $property2;
    }
}
  • __construct — the fixed, reserved method name; PHP recognizes it automatically, it is case-insensitive but should always be written lowercase by convention.
  • Parameters — typed like any function parameter; may have default values, be nullable, use union types, or be variadic (...$args).
  • Visibility keyword (public/protected/private) — optional on the method itself (defaults to public); required on a parameter only if you want constructor property promotion for that parameter.
  • No return type — constructors never declare a return type; PHP will raise a fatal error if you try to add one such as : void or : self.
  • $this — inside the constructor body, refers to the object currently being built, used to assign incoming values to properties.

Examples

Example 1: A Basic Constructor

<?php

class Product
{
    public string $name;
    public float $price;

    public function __construct(string $name, float $price)
    {
        $this->name = $name;
        $this->price = $price;
    }
}

$p = new Product("Keyboard", 49.99);
echo $p->name . " costs $" . $p->price;

Output:

Keyboard costs $49.99

Here, new Product(...) triggers __construct(), which copies the two arguments into the object’s $name and $price properties. From that point on, every Product object is guaranteed to have both values set — there is no way to create a Product without supplying them, since both constructor parameters are required (no default values).

Example 2: Constructor Property Promotion with Readonly Properties

<?php

class Point
{
    public function __construct(
        public readonly float $x = 0.0,
        public readonly float $y = 0.0
    ) {
    }

    public function __toString(): string
    {
        return "(" . $this->x . ", " . $this->y . ")";
    }
}

$origin = new Point();
$p = new Point(3.5, 4.2);
echo $origin;
echo PHP_EOL;
echo $p;

Output:

(0, 0)
(3.5, 4.2)

The public readonly float $x = 0.0 parameter both declares the $x property and assigns it in one place. Marking it readonly means that once the constructor finishes, $x and $y can never be reassigned — any later attempt throws an Error. This combination (promotion + readonly) is the idiomatic PHP 8+ way to build small, immutable value objects.

Example 3: Inheritance and parent::__construct()

<?php

abstract class Shape
{
    public function __construct(public string $color = "black")
    {
    }

    abstract public function area(): float;
}

class Circle extends Shape
{
    public function __construct(
        private readonly float $radius,
        string $color = "black"
    ) {
        parent::__construct($color);
    }

    public function area(): float
    {
        return round(M_PI * $this->radius ** 2, 2);
    }
}

$circle = new Circle(radius: 5, color: "red");
echo "A " . $circle->color . " circle with area " . $circle->area();

Output:

A red circle with area 78.54

Circle defines its own constructor, so Shape‘s constructor does not run automatically — Circle explicitly forwards $color to it via parent::__construct($color). Note the use of a named argument (radius: 5) when calling new Circle(...), which makes the call self-documenting regardless of parameter order.

How It Works Step by Step

  • 1. You write new ClassName($arg1, $arg2).
  • 2. PHP looks up the class definition and allocates memory for a new object, giving every declared property its default value (or leaving typed properties uninitialized if they have no default).
  • 3. PHP checks whether ClassName (or an ancestor, if not overridden) defines __construct(). If none exists anywhere in the hierarchy, the object is simply returned as-is.
  • 4. If a constructor exists, PHP calls it on the new object, binding $this to that object and passing your arguments positionally or by name.
  • 5. Any promoted constructor parameters are assigned to their matching properties automatically, before the constructor body executes.
  • 6. The constructor body runs top to bottom like any method — validating input, computing derived values, or calling parent::__construct() if needed.
  • 7. When the constructor finishes, new returns the fully-initialized object reference to your code. The constructor’s own return value (if any) is discarded; you cannot make new return something other than the object.

Common Mistakes

Mistake 1: Forgetting to Call parent::__construct()

When a child class defines its own constructor, the parent’s constructor is not called automatically. Forgetting this silently leaves inherited properties at their defaults instead of raising an obvious error:

<?php

class Animal
{
    public string $name = "Unknown";

    public function __construct(string $name)
    {
        $this->name = $name;
    }
}

class Dog extends Animal
{
    public function __construct(public string $breed)
    {
        // Bug: forgot to call parent::__construct()
    }
}

$dog = new Dog("Labrador");
echo "Name: " . $dog->name . ", Breed: " . $dog->breed;

Output:

Name: Unknown, Breed: Labrador

Because Dog::__construct() never calls parent::__construct(), $name keeps its class default of "Unknown" instead of receiving any value derived from construction. This is a silent logic bug, not a crash, which makes it especially dangerous. The fix is to explicitly forward the needed data to the parent constructor:

<?php

class Animal
{
    public string $name = "Unknown";

    public function __construct(string $name)
    {
        $this->name = $name;
    }
}

class Dog extends Animal
{
    public function __construct(public string $breed, string $name = "Dog")
    {
        parent::__construct($name);
    }
}

$dog = new Dog("Labrador", "Rex");
echo "Name: " . $dog->name . ", Breed: " . $dog->breed;

Output:

Name: Rex, Breed: Labrador

Mistake 2: Manually Calling __construct() Again

Because __construct() is, under the hood, just a normal public method, PHP allows you to call it a second time on an already-created object. Doing so silently re-runs the initialization logic and can wipe out the object’s current state:

<?php

class Counter
{
    public int $count;

    public function __construct(int $start = 0)
    {
        $this->count = $start;
    }

    public function increment(): void
    {
        $this->count++;
    }
}

$counter = new Counter(5);
$counter->increment();
$counter->increment();
echo "Before: " . $counter->count;
echo PHP_EOL;

$counter->__construct();
echo "After: " . $counter->count;

Output:

Before: 7
After: 0

Calling $counter->__construct() resets $count back to its default, discarding the two increments. The problem is that nothing in the code makes this reset obvious — readers see a normal-looking method call. The fix is to never rely on re-invoking __construct() and instead expose an explicit, well-named method for any legitimate reset behavior:

<?php

class Counter
{
    public int $count;

    public function __construct(int $start = 0)
    {
        $this->count = $start;
    }

    public function increment(): void
    {
        $this->count++;
    }

    public function reset(int $start = 0): void
    {
        $this->count = $start;
    }
}

$counter = new Counter(5);
$counter->increment();
$counter->increment();
echo "Before: " . $counter->count;
echo PHP_EOL;

$counter->reset();
echo "After: " . $counter->count;

Output:

Before: 7
After: 0

The numeric result is the same, but reset() communicates intent clearly, whereas re-calling __construct() is easy to miss during a code review.

Best Practices

  • Keep constructors focused on assigning and validating incoming data — avoid database calls, file I/O, or other heavy side effects inside __construct(); use a factory method or dependency injection instead.
  • Always call parent::__construct() in a child constructor unless you deliberately intend to skip the parent’s initialization — and if you skip it, add a comment explaining why.
  • Prefer constructor property promotion for simple data-holding classes to cut boilerplate, but fall back to explicit property declarations plus a constructor body when you need extra validation logic.
  • Use readonly properties (PHP 8.1+) for values that should never change after construction, such as IDs, timestamps, or configuration.
  • Use typed parameters with sensible defaults so objects can’t be constructed in an invalid or partially-initialized state.
  • Make constructors private or protected when you want to force object creation through a named static factory method (e.g. User::fromArray($data)) instead of new User(...) directly.
  • Never rely on calling __construct() a second time to “reset” an object — write a dedicated method for that.

Practice Exercises

  • Exercise 1: Write a Rectangle class with a constructor that accepts $width and $height (both promoted, typed float), plus a method area(): float that returns their product.
  • Exercise 2: Create an Employee base class with a constructor that sets $name and $salary, and a Manager subclass with its own constructor that adds a $teamSize parameter but still correctly initializes $name and $salary via parent::__construct().
  • Exercise 3: Build a Temperature class with a private constructor and two static factory methods, fromCelsius(float $c) and fromFahrenheit(float $f), both of which internally call new self(...) to build the object. (Hint: static methods inside the class can call a private constructor because they belong to the same class.)

Summary

  • __construct() is PHP’s special method that runs automatically whenever an object is created with new.
  • A class may have only one constructor; it never declares a return type and its return value is ignored.
  • Constructor property promotion (PHP 8+) lets you declare and assign properties directly in the parameter list, often combined with readonly for immutable objects.
  • Child classes must explicitly call parent::__construct() if they define their own constructor and still need the parent’s initialization logic — PHP will not do this for you.
  • __construct() is a normal public method internally, so it can technically be called again manually — but doing so is a bug magnet, not a feature to rely on.
  • Non-public constructors (private/protected) are the basis for factory methods and the Singleton pattern.