objects namespaces and application architecture
Visibility
Visibility is not about secrecy. It is about boundaries. Public members are part of the class's interface. Private members are implementation details the class controls. Protected members are available to child classes, which makes them a wider contract than private members.
Public members are the outside interface
Public methods are what other code is allowed to call.
<?php
declare(strict_types=1);
class OrderLine
{
public function __construct(
private int $unitPricePennies,
private int $quantity,
) {
if ($unitPricePennies < 0 || $quantity < 1) {
throw new InvalidArgumentException('Invalid order line.');
}
}
public function lineTotalPennies(): int
{
return $this->unitPricePennies * $this->quantity;
}
}
$line = new OrderLine(2499, 2);
echo $line->lineTotalPennies() . PHP_EOL;
// Prints:
// 4998
Calling code can ask for the line total, but it cannot directly change the internal price or quantity.
Private properties protect state
Private properties force changes to go through methods that can enforce rules.
<?php
declare(strict_types=1);
class Basket
{
private int $itemCount = 0;
public function addItem(): void
{
$this->itemCount++;
}
public function itemCount(): int
{
return $this->itemCount;
}
}
$basket = new Basket();
$basket->addItem();
$basket->addItem();
echo $basket->itemCount() . PHP_EOL;
// Prints:
// 2
There is no way for outside code to set $itemCount to -10.
Private methods hide internal steps
Private methods are useful when a public method has a smaller internal step that should not be called from outside.
<?php
declare(strict_types=1);
class SlugGenerator
{
public function fromTitle(string $title): string
{
return $this->collapseDashes(strtolower(trim($title)));
}
private function collapseDashes(string $value): string
{
return trim(preg_replace('/[^a-z0-9]+/', '-', $value) ?? '', '-');
}
}
$generator = new SlugGenerator();
echo $generator->fromTitle(' New Product Launch! ') . PHP_EOL;
// Prints:
// new-product-launch
The public method explains the use case. The private method is just an implementation detail.
Protected is for inheritance contracts
Protected members can be used by the class and its child classes. Use them carefully because child classes can become coupled to internal details.
<?php
declare(strict_types=1);
class Report
{
protected function heading(string $title): string
{
return strtoupper($title);
}
}
class SalesReport extends Report
{
public function title(): string
{
return $this->heading('sales report');
}
}
$report = new SalesReport();
echo $report->title() . PHP_EOL;
// Prints:
// SALES REPORT
If you are not intentionally designing for inheritance, prefer private.
Public properties are a commitment
Once other code reads or writes a public property, changing how that property works becomes harder.
<?php
declare(strict_types=1);
class CustomerProfile
{
public function __construct(private string $email)
{
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidArgumentException('Email is invalid.');
}
}
public function email(): string
{
return $this->email;
}
}
$profile = new CustomerProfile('nia@example.com');
echo $profile->email() . PHP_EOL;
// Prints:
// nia@example.com
The getter exposes the value without letting outside code replace it with an invalid email.
What to remember
Make public what other code genuinely needs. Keep implementation details private. Use protected only when child classes are part of the design. Visibility helps you preserve object rules and change internals without breaking every caller.
Practice
Task: Protect account balance state
Create a small account balance class using visibility.
Requirements
- Use
declare(strict_types=1);. - Store the balance in a private property.
- Start the balance through the constructor.
- Reject a negative starting balance.
- Add a public
deposit()method. - Reject deposits less than
1. - Add a public
balancePennies()method. - Print one valid balance.
- Show one invalid deposit by catching the exception.
- Include the expected output as comments in the same PHP code block.
The class should not expose a public writable balance property.
Show solution
<?php
declare(strict_types=1);
class AccountBalance
{
public function __construct(private int $balancePennies)
{
if ($balancePennies < 0) {
throw new InvalidArgumentException('Starting balance cannot be negative.');
}
}
public function deposit(int $pennies): void
{
if ($pennies < 1) {
throw new InvalidArgumentException('Deposit must be at least 1 penny.');
}
$this->balancePennies += $pennies;
}
public function balancePennies(): int
{
return $this->balancePennies;
}
}
$balance = new AccountBalance(1000);
$balance->deposit(250);
echo $balance->balancePennies() . PHP_EOL;
try {
$balance->deposit(0);
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// 1250
// Deposit must be at least 1 penny.
The balance is private, so outside code cannot set it directly. All changes go through methods that enforce the object's rules.