Constructors
A constructor runs automatically when an object is created with new. It receives the object's starting data, validates it, and sets up properties, so no half-initialised object ever exists. PHP's constructor property promotion makes simple constructors a single line.
The __construct method
<?php class BankAccount { private string $owner; private float $balance; public function __construct(string $owner, float $openingBalance = 0) { $this->owner = $owner; $this->balance = $openingBalance; } public function summary(): string { return sprintf("%s: %.2f", $this->owner, $this->balance); } } $acct = new BankAccount("Rosa Diaz", 250); echo $acct->summary(), "\n"; echo (new BankAccount("Ben"))->summary(), "\n";
Rosa Diaz: 250.00 Ben: 0.00
The arguments to new are passed to __construct. Leaving out a required argument throws an ArgumentCountError — an account simply can't be created without an owner.
Constructor property promotion
Most constructors just copy their arguments into properties. With promotion, adding a visibility keyword to a constructor parameter declares the property and assigns it in one go:
<?php class Product { public function __construct( public string $name, public float $price, public int $stock = 0, ) {} public function value(): float { return $this->price * $this->stock; } } $lamp = new Product("Desk lamp", 34.5, 4); echo "{$lamp->name}: {$lamp->value()}\n";
Desk lamp: 138
That class is equivalent to declaring three properties and assigning each one in the constructor body — but with a third of the code. You can mix promoted and ordinary parameters.
Validating in the constructor
The constructor is the place to reject bad data. If it throws, the object is never created:
<?php class Temperature { public function __construct(public readonly float $celsius) { if ($celsius < -273.15) { throw new InvalidArgumentException("Below absolute zero: $celsius"); } } } $ok = new Temperature(21.5); echo $ok->celsius, "\n"; $bad = new Temperature(-300);
21.5
Fatal error: Uncaught InvalidArgumentException: Below absolute zero: -300 in main.php:8
Stack trace:
#0 main.php(15): Temperature->__construct(-300.0)
#1 {main}
thrown in main.php on line 8readonly (lesson 41) means the property can be set once, in the constructor, and never changed — so a Temperature object stays valid forever.
Named arguments with new
Named arguments work for constructors too, which makes object creation with several parameters self-documenting:
<?php class Booking { public function __construct( public string $guest, public int $nights = 1, public bool $breakfast = false, public ?string $notes = null, ) {} } $b = new Booking(guest: "Lin", breakfast: true, notes: "Late arrival"); echo "{$b->guest}, {$b->nights} night(s), breakfast: ", $b->breakfast ? "yes" : "no", ", {$b->notes}\n";
Lin, 1 night(s), breakfast: yes, Late arrival
Static factory methods
PHP allows only one constructor per class. When there are several ways to create an object, add static factory methods with descriptive names (static methods are covered in lesson 36):
<?php class Money { private function __construct(public readonly int $cents) {} public static function fromCents(int $cents): self { return new self($cents); } public static function fromString(string $amount): self { return new self((int) round((float) ltrim($amount, '$') * 100)); } public function format(): string { return '$' . number_format($this->cents / 100, 2); } } echo Money::fromCents(1999)->format(), "\n"; echo Money::fromString('$5.25')->format(), "\n";
$19.99 $5.25
Making the constructor private forces everyone to use the factories.
Dependency injection
Objects often need other objects to do their work — a mailer, a logger, a database connection. Rather than creating them inside the class, pass them into the constructor. This is called dependency injection, and it makes classes easy to configure and test:
<?php class ConsoleLogger { public function log(string $message): void { echo "[log] $message\n"; } } class OrderService { public function __construct(private ConsoleLogger $logger) {} public function place(string $item, int $qty): void { $this->logger->log("Placed order: $qty x $item"); } } $service = new OrderService(new ConsoleLogger()); $service->place("Notebook", 3);
[log] Placed order: 3 x Notebook
Frameworks like Laravel and Symfony have a "service container" that creates and injects these objects automatically. Lesson 38 shows how interfaces make it possible to swap the logger for a different implementation.
Destructors
A __destruct method runs when an object is no longer referenced (or when the script ends). It's rarely needed in PHP, since resources are cleaned up at the end of each request anyway:
<?php class Connection { public function __construct(private string $name) { echo "open {$this->name}\n"; } public function __destruct() { echo "close {$this->name}\n"; } } $c = new Connection("db"); unset($c); echo "done\n";
open db close db done
Try it yourself
Give Rgb a static factory that accepts a hex string such as "#FF8800".
rgb(255, 136, 0) rgb(0, 128, 128)