<?php OOP: Zero to Hero Chapter 06 of 20

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.

๐Ÿง
Real-world analogy

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

KeywordWho can access it?Everyday picture
publicAnyone, anywhereYour name โ€” everyone may use it
privateOnly code inside this same classYour ATM PIN โ€” nobody else, ever
protectedThis 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
Balance: K700 Fatal error: Uncaught Error: Cannot access private property BankAccount::$balance

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
Chanda is 21 Age must be between 15 and 90.
Why not make everything private with getters/setters?

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.

โœ๏ธ Try it yourself

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!
Quick quiz

A private property can be accessed byโ€ฆ

Quick quiz

What is the main benefit of a setter method over a public property?

๐Ÿ”‘ Key points
  • 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.
  • readonly properties are set once in the constructor and frozen forever โ€” perfect for IDs.