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 topublic); 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
: voidor: 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
$thisto 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,
newreturns the fully-initialized object reference to your code. The constructor’s ownreturnvalue (if any) is discarded; you cannot makenewreturn 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
readonlyproperties (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
privateorprotectedwhen you want to force object creation through a named static factory method (e.g.User::fromArray($data)) instead ofnew 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
Rectangleclass with a constructor that accepts$widthand$height(both promoted, typedfloat), plus a methodarea(): floatthat returns their product. - Exercise 2: Create an
Employeebase class with a constructor that sets$nameand$salary, and aManagersubclass with its own constructor that adds a$teamSizeparameter but still correctly initializes$nameand$salaryviaparent::__construct(). - Exercise 3: Build a
Temperatureclass with aprivateconstructor and two static factory methods,fromCelsius(float $c)andfromFahrenheit(float $f), both of which internally callnew 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 withnew.- 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
readonlyfor 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.
