A constructor is a special method that PHP runs automatically when you create an object with new. It is the place to set up the object so it is valid from the start.
<?php
class User
{
public string $name;
public string $email;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
}
$user = new User("Isha", "isha@example.com");
echo $user->name . " <" . $user->email . ">";
Isha <isha@example.com>
The method is always called __construct. The arguments you pass to new User(...) go to it.
Why constructors matter
Without a constructor, you could create a User with no name and no email, and nothing would warn you. With one, a User always has a name and an email, because new User() will not work without them.
<?php
class User
{
public function __construct(public string $name) {}
}
try {
$u = new User();
} catch (ArgumentCountError $e) {
echo $e->getMessage();
}
Too few arguments to function User::__construct(), 0 passed in /path/to/index.php on line 8 and exactly 1 expected
Constructor property promotion (PHP 8.0+)
Writing each property three times (declare, parameter, assign) is repetitive. PHP 8 lets you declare and assign in one step by adding a visibility word to the constructor parameter.
<?php
class User
{
public function __construct(
public string $name,
public string $email,
public bool $active = true,
) {}
}
$user = new User("Isha", "isha@example.com");
echo $user->name . ", active: " . ($user->active ? "yes" : "no");
Isha, active: yes
This is the same as the long version above, in four lines. Constructor Property Promotion goes deeper.
Validate in the constructor
If the data is not valid, the object should not exist. Throw an exception.
<?php
class Email
{
public function __construct(public readonly string $value)
{
if (filter_var($value, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidArgumentException("Invalid email: $value");
}
}
}
try {
new Email("not-an-email");
} catch (InvalidArgumentException $e) {
echo $e->getMessage();
}
Invalid email: not-an-email
An Email object, once created, is always a valid email. Code that receives one does not need to check again.
Default values and named arguments
<?php
class Connection
{
public function __construct(
public string $host = "127.0.0.1",
public int $port = 3306,
public string $charset = "utf8mb4",
) {}
}
$c = new Connection(port: 3307);
echo "{$c->host}:{$c->port}";
127.0.0.1:3307
Do not do heavy work
A constructor should set things up quickly. Do not make network calls or read big files there. A constructor that can fail in complicated ways is hard to test.
Static constructors
A static method can give a clearer name to a creation path.
<?php
class Money
{
private function __construct(public readonly int $cents) {}
public static function fromFloat(float $amount): self
{
return new self((int) round($amount * 100));
}
}
echo Money::fromFloat(19.99)->cents;
1999
The constructor is private, so the only way to build a Money is through the named methods.
One valid state
new, the object is complete and valid. If that is not true, redesign the constructor.