Encapsulation means an object keeps its data private and offers a small set of methods to work with it. Outside code cannot put the object into an invalid state.
You already know the tools: private properties and public methods. Here is how to use them well.
The problem: open data
<?php
class BadAccount
{
public float $balance = 0;
}
$a = new BadAccount();
$a->balance = -99999; // nothing stops this
echo $a->balance;
-99999
The solution: protect the rules
<?php
class Account
{
private float $balance = 0;
public function deposit(float $amount): void
{
if ($amount <= 0) {
throw new InvalidArgumentException("Deposit must be positive.");
}
$this->balance += $amount;
}
public function withdraw(float $amount): void
{
if ($amount > $this->balance) {
throw new RuntimeException("Insufficient funds.");
}
$this->balance -= $amount;
}
public function balance(): float
{
return $this->balance;
}
}
$account = new Account();
$account->deposit(100);
try {
$account->withdraw(500);
} catch (RuntimeException $e) {
echo $e->getMessage() . "\n";
}
echo $account->balance();
Insufficient funds.
100
The rule “the balance can never go negative” lives in one place, the class. No code anywhere can break it.
Invariants
A condition that is always true for an object is an invariant. A Percentage is always between 0 and 100. An Email is always a valid address. Encapsulation protects invariants.
Immutable value objects
A value object represents a value (money, a date range, a coordinate). It is often immutable: instead of changing it, you create a new one.
<?php
final class Money
{
public function __construct(
public readonly int $cents,
public readonly string $currency,
) {}
public function add(Money $other): static
{
if ($other->currency !== $this->currency) {
throw new InvalidArgumentException("Currency mismatch.");
}
return new static($this->cents + $other->cents, $this->currency);
}
}
$a = new Money(500, "INR");
$b = $a->add(new Money(250, "INR"));
echo $a->cents . " and " . $b->cents;
500 and 750
$a did not change. Immutable objects cannot be changed by accident, and you can share them freely.
Tell, don’t ask
Instead of asking an object for its data and deciding yourself, tell it what to do.
// Ask: the caller makes the decision and knows the internals
if ($account->getBalance() >= $amount) {
$account->setBalance($account->getBalance() - $amount);
}
// Tell: the object decides
$account->withdraw($amount);
The second version cannot be forgotten or done wrongly. The rule is part of the class.
Do not add a getter and setter for everything
A setter for every property puts you back where you started: anyone can set anything. Add a method only when outside code needs it, and give it a meaningful name (withdraw, not setBalance).
Encapsulation vs security
Private properties protect your code from other code. They are not about hiding secrets from users. The data is still in memory, and a debugger or serialization can show it.
Why this matters in Laravel
$fillable and $guarded, which is encapsulation applied to database fields: the model decides what can be set from outside.