Constructors and Visibility
A vending machine has a glass front and a few buttons. You can see the snacks and press the buttons, but you can't reach in and rearrange the coils. That's on purpose.
In Classes and Objects, you made objects and filled in their properties one by one. That had two problems: it was easy to forget a property, and anyone could set any property to anything, like a book with -50 borrowed copies. This lesson fixes both.
The constructor: setup on arrival
A constructor is a special method named __construct (with two underscores). PHP calls it automatically when you use new, and passes along the arguments:
<?php
declare(strict_types=1);
class Pet
{
public string $name;
public string $sound;
public function __construct(string $name, string $sound)
{
$this->name = $name;
$this->sound = $sound;
}
}
$dog = new Pet("Bantay", "Woof");
echo "$dog->name says $dog->sound!\n";Bantay says Woof!Now every Pet gets its name and sound the moment it's created, in one line. Forget one, and PHP stops with a "Too few arguments" error straight away, instead of leaving a half-built object around.
Constructor promotion: the shortcut
Writing each property three times (declare it, take it as a parameter, assign it) gets old. PHP 8 has a shortcut, called constructor promotion: put the visibility word before a constructor parameter, and PHP creates and fills the property for you:
<?php
declare(strict_types=1);
class Pet
{
public function __construct(
public string $name,
public string $sound,
) {
}
}
$cat = new Pet("Mingming", "Meow");
echo "$cat->name says $cat->sound!\n";Mingming says Meow!This does exactly the same as the longer version. You'll see this style everywhere in modern PHP.
Visibility: public and private
Every property and method has a visibility:
public: usable from anywhere.private: usable only by code inside the class.
(There's also protected, which you'll meet with inheritance in the next lesson.)
Here's why private matters. A bank account's balance shouldn't be something anyone can overwrite:
<?php
declare(strict_types=1);
class BankAccount
{
private int $balance = 0;
public function deposit(int $amount): void
{
if ($amount <= 0) {
echo "Deposits must be positive.\n";
return;
}
$this->balance += $amount;
}
public function getBalance(): int
{
return $this->balance;
}
}
$account = new BankAccount();
$account->deposit(500);
$account->deposit(-9999);
echo "Balance: " . $account->getBalance() . "\n";Deposits must be positive.
Balance: 500And trying to reach in from outside fails:
<?php
class BankAccount
{
private int $balance = 0;
}
$account = new BankAccount();
$account->balance = 1000000;
// error: Cannot access private property BankAccount::$balanceThat's the vending machine: the public methods are the buttons, and the private properties are behind the glass. The only way to change the balance is through deposit, which checks every amount. Hiding an object's data behind methods like this is called encapsulation.
A method like getBalance, which just returns a private property, is called a getter. A good default is: make properties private, and add public methods only for what the outside world really needs.
readonly: set once, never change
Some values should be set when the object is created, and then never change, like a student's ID number. Mark them readonly:
<?php
declare(strict_types=1);
class Student
{
public function __construct(
public readonly string $id,
public string $name,
) {
}
}
$s = new Student("2026-0042", "Maria");
$s->name = "Maria Santos";
echo "$s->id: $s->name\n";
$s->id = "9999";
// error: Cannot modify readonly property Student::$idThe name can change, but the ID is locked after the constructor sets it. public readonly is a nice combination: everyone can read the ID directly, so you don't need a getter, but nobody can change it.
Class constants
A value that belongs to the class itself and never changes can be a class constant, written with const inside the class, and read with :::
<?php
declare(strict_types=1);
class Ticket
{
public const MAX_PER_ORDER = 6;
public function __construct(private int $quantity)
{
if ($quantity > self::MAX_PER_ORDER) {
$this->quantity = self::MAX_PER_ORDER;
}
}
public function getQuantity(): int
{
return $this->quantity;
}
}
echo "Limit: " . Ticket::MAX_PER_ORDER . "\n";
$order = new Ticket(10);
echo "Booked: " . $order->getQuantity() . "\n";Limit: 6
Booked: 6Outside the class, it's Ticket::MAX_PER_ORDER. Inside, self:: means "this class."
Try it
A game character with protected health. Predict each line.
Running PHP here
Running PHP right in the browser is coming to Syntaxia soon. For now, you can read and edit the code here. If you have PHP installed on your computer (see Setting Up), save it as index.php and run it with php index.php.
Check your prediction
Lakan: 55 HP, 2 potion(s)
Lakan drinks a potion.
Lakan drinks a potion.
Lakan has no potions left!
Lakan: 100 HP, 0 potion(s)
Lakan: 0 HP, 0 potion(s)The hero starts with 100 health and the default 2 potions. After 45 damage, it's 55. The first potion brings it to 85, and the second would give 115, but min caps it at 100. The third attempt finds no potions. max(0, ...) stops health going below zero.
Try it yourself
- Create a second hero,
new Hero("Dayang", 5). How many potions does she have? - Add the line
$hero->health = 9999;at the end. What does PHP say? - Add a public method
isAlive(): bool, and print whether the hero is alive after the big hit.
Check your understanding
Up next
What if you need several classes that are almost the same, like a Dog and a Cat that are both pets? Rather than copying code, they can share it, in Inheritance and Interfaces.
