Chapter 06 ยท OOP Foundations
Visibility & Encapsulation
So far our properties have all been public โ anyone can reach in and
change them, even to nonsense values. Real objects protect themselves. This chapter teaches the
three visibility keywords and the famous getter/setter pattern.
The danger of public everything
class BankAccount {
public float $balance = 0;
}
$acc = new BankAccount();
$acc->balance = -5000000; // ๐ฑ anyone can do this! The bank now owes... wait, what?
Nothing stops that line. In a big project with many programmers (or just tired-you at 2 a.m.), somebody will write a line like that eventually. We need locks.
Think of an ATM. The cash is locked inside โ you cannot open the machine and grab notes. You interact only through the buttons on the front: withdraw, deposit, check balance. Each button has rules built in ("you can't withdraw more than you have"). That's encapsulation: private data inside, controlled public buttons outside.
The three visibility keywords
| Keyword | Who can access it? | Everyday picture |
|---|---|---|
public | Anyone, anywhere | Your name โ everyone may use it |
private | Only code inside this same class | Your ATM PIN โ nobody else, ever |
protected | This class and its children (next chapter!) | A family recipe โ shared with your children, not with strangers |
Locking the vault with private
<?php
class BankAccount {
private float $balance = 0; // ๐ locked inside
public function deposit(float $amount): void {
if ($amount <= 0) {
echo "Deposit must be positive.<br>";
return;
}
$this->balance += $amount;
}
public function withdraw(float $amount): void {
if ($amount > $this->balance) {
echo "Insufficient funds.<br>";
return;
}
$this->balance -= $amount;
}
public function getBalance(): float {
return $this->balance;
}
}
$acc = new BankAccount();
$acc->deposit(1000);
$acc->withdraw(300);
echo "Balance: K" . $acc->getBalance() . "<br>";
$acc->balance = -5000000; // ๐ฅ Fatal error: Cannot access private property
Beautiful! The dangerous line now crashes immediately instead of silently
corrupting our data. Inside the class, methods still use $this->balance freely โ
the lock only keeps outsiders out. Every coin now enters through deposit()
and leaves through withdraw(), and both doors have rules.
Getters and setters โ the official doors
The pattern you just saw has a name. A getter is a method that reads a
private property (getBalance()). A setter is a method that
changes one โ after checking the new value makes sense:
class Student {
private string $name;
private int $age;
public function __construct(string $name, int $age) {
$this->setName($name); // even the constructor uses the setter โ one door, one rule
$this->setAge($age);
}
// ---- getters ----
public function getName(): string { return $this->name; }
public function getAge(): int { return $this->age; }
// ---- setters with rules ----
public function setName(string $name): void {
$name = trim($name);
if ($name === "") {
die("Name cannot be empty.");
}
$this->name = $name;
}
public function setAge(int $age): void {
if ($age < 15 || $age > 90) {
die("Age must be between 15 and 90.");
}
$this->age = $age;
}
}
$s = new Student("Chanda", 21);
echo $s->getName() . " is " . $s->getAge();
$s->setAge(250); // the setter's rule blocks nonsense
Good question โ and developers argue about it! The practical rule of thumb:
start private. Add a getter only if outsiders genuinely need to read the value,
and a setter only if they genuinely need to change it โ with validation inside. If a property
needs no rules at all and is freely changeable, public is honest and fine.
Encapsulation is a tool, not a religion.
Read-only properties โ set once, never change
Some things should never change after creation โ a student number, an invoice number, a date of birth. Modern PHP has a keyword for exactly that:
class Student {
public function __construct(
public readonly string $studentNumber, // set once at birth, frozen forever
public string $name
) {}
}
$s = new Student("LGU2026-001", "Chanda");
echo $s->studentNumber; // reading is fine
$s->name = "Chanda Mwila"; // normal property, fine
$s->studentNumber = "HACKED"; // ๐ฅ Error: Cannot modify readonly property
readonly gives you the safety of private-with-a-getter but with less typing:
publicly readable, never writable after the constructor.
Build a class Fridge with a private property $temperature starting at
4. Add getTemperature() and setTemperature(), where the setter only
accepts values between 1 and 8 (otherwise print "Unsafe temperature!"). Test it with 6 and
then 20.
Show solution
<?php
class Fridge {
private int $temperature = 4;
public function getTemperature(): int {
return $this->temperature;
}
public function setTemperature(int $t): void {
if ($t < 1 || $t > 8) {
echo "Unsafe temperature!<br>";
return;
}
$this->temperature = $t;
}
}
$f = new Fridge();
$f->setTemperature(6);
echo $f->getTemperature() . "<br>"; // 6
$f->setTemperature(20); // Unsafe temperature!
echo $f->getTemperature(); // still 6 โ protected!
A private property can be accessed byโฆ
What is the main benefit of a setter method over a public property?
- Encapsulation = lock data inside, interact through approved methods (the ATM).
public: anyone.private: this class only.protected: this class + its children.- Getters read private data; setters change it after validating.
- Accessing a private property from outside causes an immediate, loud error โ bugs die young.
readonlyproperties are set once in the constructor and frozen forever โ perfect for IDs.