Question

Public vs Private vs Protected in PHP Classes

phpoopprivatepublicprotected

Question

In PHP classes, what is the difference between public, private, and protected methods and properties? When should each one be used, and why?

Examples:

// Public
public $variable;
public function doSomething() {
    // ...
}

// Private
private $variable;
private function doSomething() {
    // ...
}

// Protected
protected $variable;
protected function doSomething() {
    // ...
}

Short Answer

By the end of this page, you will understand how visibility works in PHP classes, what public, private, and protected mean, and how to choose the right one for properties and methods. You will also see practical examples, common mistakes, and how these access levels are used in real projects.

Concept

In PHP, public, private, and protected are visibility keywords. They control where a property or method can be accessed from.

Visibility matters because classes are meant to hide internal details and expose only the parts that other code should use. This helps you:

  • protect important data from accidental changes
  • keep class internals flexible
  • make your code easier to understand and maintain
  • enforce clear boundaries between a class and the outside world

Here is the core idea:

  • public: accessible from anywhere
  • private: accessible only inside the same class
  • protected: accessible inside the same class and its child classes

Why this matters

Imagine a class represents a bank account. You probably want other code to call a method like deposit() or withdraw(), but you do not want external code directly changing the balance in unsafe ways.

That is why visibility is useful: it lets you define a safe public interface and hide the internal implementation.

Quick definition

public

Use public for methods and properties that are intended to be used by other parts of your program.

class User {
    public string $name = "Alice";

    public function greet(): string {
        return "Hello, " . $this->name;
    }
}

$user = new User();
echo $user->name;      // allowed
echo $user->greet();   // allowed

private

Use private for internal details that should only be used inside that exact class.

class User {
    private string $password = "secret";

    private function hashPassword(): string {
        return sha1($this->password);
    }

    public function getPasswordHash(): string {
        return $this->hashPassword();
    }
}

Outside code cannot access $password or hashPassword() directly.

protected

Use protected when something should stay hidden from outside code but still be available to child classes.

class Animal {
    protected string $type = "animal";

    protected function getType(): string {
        return $this->type;
    }
}

class Dog extends Animal {
    public function describe(): string {
        return "This is a " . $this->getType();
    }
}

A Dog object can use inherited protected members, but outside code cannot.

The rule in one table

VisibilitySame classChild classOutside class
publicYesYesYes
protectedYesYesNo
privateYesNoNo

General guideline

A common rule is:

  • make things private by default
  • use protected only when inheritance truly needs it
  • use public for the class API you want others to rely on

This keeps your classes safer and easier to change later.

Mental Model

Think of a class like a building.

  • public is the front door: anyone is allowed in.
  • protected is the staff-only area: the building's workers and approved branches can enter.
  • private is the owner's locked office: only that exact office owner can access it.

Another way to think about it:

  • public = what the class promises to the outside world
  • private = how the class works internally
  • protected = internal tools that subclasses are allowed to reuse

This mental model helps with design:

  • If outside code should use it directly, make it public.
  • If it is an implementation detail, make it private.
  • If subclasses need it but outside code should not, make it protected.
Take Quiz

Syntax and Examples

Basic syntax

class Example {
    public string $publicProperty = 'visible everywhere';
    protected string $protectedProperty = 'visible in class and subclasses';
    private string $privateProperty = 'visible only in this class';

    public function publicMethod(): void {
        echo "Public method\n";
    }

    protected function protectedMethod(): void {
        echo "Protected method\n";
    }

    private function privateMethod(): void {
        echo "Private method\n";
    }
}

Example: safe class design

class BankAccount {
    private float $balance = 0;

    public function deposit(float $amount): void {
        if ($amount > 0) {
            $this->balance += $amount;
        }
    }

    public function getBalance(): float {
        return $this->balance;
    }
}

$account = new BankAccount();
$account->deposit(100);
echo $account->getBalance(); // 100

Why this is good

  • $balance is private, so outside code cannot set it to an invalid value.
  • deposit() is public, because it is part of the class interface.
  • getBalance() is public, because callers need to read the value safely.

Example: protected with inheritance

class Vehicle {
    protected string $brand;

    public function __construct(string $brand) {
        $this->brand = $brand;
    }
}

class Car extends Vehicle {
    public function getDescription(): string {
        return "Car brand: " . $this->brand;
    }
}

$car = new Car("Toyota");
echo $car->getDescription();

Why protected works here

  • Vehicle wants child classes like Car to use $brand.
  • But outside code should not directly modify $brand.

Example: private does not pass to child classes

class ParentClass {
    private string $secret = 'hidden';
}

class ChildClass extends ParentClass {
    public function reveal(): string {
        return $this->secret; // error
    }
}

This fails because private members belong only to ParentClass, not to ChildClass.

Step by Step Execution

Consider this example:

class Counter {
    private int $count = 0;

    public function increment(): void {
        $this->count++;
    }

    public function getCount(): int {
        return $this->count;
    }
}

$counter = new Counter();
$counter->increment();
$counter->increment();
echo $counter->getCount();

Step by step

1. The class is defined

Counter has:

  • a private property $count
  • a public method increment()
  • a public method getCount()

2. An object is created

$counter = new Counter();

At this point:

  • $counter is an instance of Counter
  • its internal $count starts at 0

3. increment() is called the first time

$counter->increment();

Inside the class:

$this->count++;

Now $count becomes 1.

4. increment() is called again

$counter->increment();

Now $count becomes 2.

5. getCount() is called

echo $counter->getCount();

The method returns the private property value, which is 2.

Important observation

This would not be allowed:

$counter->count = 100;

Why? Because $count is private. Only methods inside Counter can access it directly.

That is the main purpose of visibility: control access to class internals.

Real World Use Cases

1. Protecting data in domain objects

In an e-commerce app, an Order class may store internal state such as total price, status, or discount calculations.

class Order {
    private float $total = 0;

    public function addItem(float $price): void {
        $this->total += $price;
    }

    public function getTotal(): float {
        return $this->total;
    }
}

This prevents outside code from doing something unsafe like:

// Not allowed if $total is private
$order->total = -500;

2. Creating a clean public API

Libraries often expose only a few public methods and keep helper logic private.

class EmailSender {
    public function send(string $to, string $message): void {
        $formatted = $this->formatMessage($message);
        // send email
    }

    private function formatMessage(string $message): string {
        return trim($message);
    }
}

Users of the class only need send(), not formatMessage().

3. Sharing behavior with subclasses

Framework base classes often use protected methods or properties so child classes can customize behavior.

class BaseController {
    protected function json(array $data): string {
        return json_encode($data);
    }
}

class UserController extends BaseController {
    public function index(): string {
        return $this->json(['users' => []]);
    }
}

4. Enforcing business rules

A UserAccount may need to validate state changes through public methods rather than direct property access.

class UserAccount {
    private bool $active = false;

    public function activate(): void {
        $this->active = true;
    }

    public function isActive(): bool {
        return $this->active;
    }
}

This keeps business logic in one place.

Take Quiz

Real Codebase Usage

In real projects, visibility is used to separate a class into two parts:

  • public API: what other code is allowed to call
  • internal implementation: helper methods and internal state

Common patterns

Guarding internal state

Developers often make properties private and expose public getter/setter methods only when needed.

class Product {
    private float $price;

    public function setPrice(float $price): void {
        if ($price < 0) {
            throw new InvalidArgumentException('Price cannot be negative');
        }

        $this->price = $price;
    }

    public function getPrice(): float {
        return $this->price;
    }
}

This allows validation before changing the value.

Helper methods stay private

If a method is only used inside the class, it is usually private.

class ReportService {
    public function generate(): string {
        $data = $this->loadData();
        return $this->format($data);
    }

    private function loadData(): array {
        return ['sales' => 100];
    }

    private function format(array $data): string {
        return json_encode($data);
    }
}

Protected extension points

Frameworks and reusable base classes sometimes use protected to let subclasses customize behavior.

class BaseLogger {
    public function log(string $message): void {
        echo $this->prefix() . $message;
    }

    protected function prefix(): string {
        return '[LOG] ';
    }
}

class ErrorLogger extends BaseLogger {
    protected function prefix(): string {
        return '[ERROR] ';
    }
}

Prefer narrow access

A good design habit is to choose the most restrictive visibility that still works:

  • start with private
  • change to protected only if a subclass really needs access
  • use public only for the intended external interface

This reduces coupling and makes refactoring easier.

Common Mistakes

1. Making everything public

Beginners often mark all properties and methods as public.

class User {
    public string $email;
    public string $password;
}

Why this is a problem

  • outside code can change anything at any time
  • validation becomes harder
  • internal structure becomes part of the public contract

Better approach

class User {
    private string $email;
    private string $password;

    public function setEmail(string $email): void {
        $this->email = $email;
    }

    public function getEmail(): string {
        return $this->email;
    }
}

2. Expecting child classes to access private members

Broken example:

class ParentClass {
    private string $name = 'Parent';
}

class ChildClass extends ParentClass {
    public function getName(): string {
        return $this->name; // error
    }
}

Fix

If the child must access it, use protected or provide a public/protected method.

class ParentClass {
    protected string $name = 'Parent';
}

3. Using protected when private is enough

protected is sometimes overused.

If a property or method does not need to be accessed by subclasses, private is safer.

4. Accessing private/protected members from outside the class

Broken example:

class Test {
    private string $secret = 'abc';
}

$test = new Test();
echo $test->secret; // error

Fix

Expose controlled access through a public method.

class Test {
    private string $secret = 'abc';

    public function getSecret(): string {
        return $this->secret;
    }
}

5. Treating getters and setters as automatically required

Not every property needs a getter or setter. Sometimes a property should stay completely internal.

Use public methods only when they support real class behavior or needed access.

6. Exposing implementation details too early

If helper methods are public, other code may start relying on them. Later, changing them becomes risky.

Keep helper methods private unless they are meant to be part of the class interface.

Comparisons

Visibility comparison

KeywordAccessible inside same classAccessible in child classesAccessible from outsideTypical use
publicYesYesYesClass API
protectedYesYesNoShared internals for inheritance
privateYesNoNoInternal implementation details

private vs protected

ChoiceUse when
privateOnly the current class should access it
protectedSubclasses also need access

public vs getter/setter methods

ApproachProsCons
Public propertySimple to read/writeNo control over changes
Private property + public methodsValidation and flexibilitySlightly more code

Which one should you choose?

  • Choose public for methods that define what the class does.
  • Choose private for internal state and helper logic.
  • Choose protected only when designing for inheritance.

A useful default is:

  1. Start with private
  2. Move to protected only if a child class genuinely needs access
  3. Use public for the external contract
Take Quiz

Cheat Sheet

Quick rules

  • public = accessible everywhere
  • protected = accessible in the class and subclasses
  • private = accessible only in the same class

Access table

From where?publicprotectedprivate
Same classYesYesYes
Child classYesYesNo
Outside object usageYesNoNo

Syntax

class Example {
    public $a;
    protected $b;
    private $c;

    public function one() {}
    protected function two() {}
    private function three() {}
}

Best practice

  • Prefer private by default
  • Use public for the intended API
  • Use protected for inheritance-based extension
  • Keep internal helpers non-public

Common edge case

private members are not inherited in an accessible way by child classes.

class A {
    private $x = 1;
}

class B extends A {
    public function test() {
        return $this->x; // not allowed
    }
}

Good design pattern

class Account {
    private float $balance = 0;

    public function deposit(float $amount): void {
        if ($amount > 0) {
            $this->balance += $amount;
        }
    }

    public function getBalance(): float {
        return $this->balance;
    }
}

FAQ

What does public mean in PHP?

public means a property or method can be accessed from anywhere: inside the class, from child classes, and from outside objects.

What does private mean in PHP?

private means only the same class can access that property or method directly.

What does protected mean in PHP?

protected means the current class and its child classes can access it, but outside code cannot.

Should class properties be private in PHP?

Usually, yes. Making properties private helps protect data and lets you control access through methods.

Can a child class access private properties in PHP?

No. A child class cannot directly access a parent's private properties or methods.

When should I use protected instead of private?

Use protected when subclasses genuinely need access to a property or method. Otherwise, prefer private.

Is it bad to make everything public?

In most cases, yes. It exposes internal details, makes validation harder, and increases coupling between parts of your code.

Can protected members be accessed outside the class?

No. protected members are not accessible through an object from outside the class hierarchy.

Take Quiz

Mini Project

Description

Build a simple BankAccount class to practice choosing the right visibility for properties and methods. This project demonstrates how to protect internal data, expose safe public methods, and optionally use a protected helper for subclasses.

Goal

Create a PHP class that allows deposits and withdrawals through public methods while keeping the balance protected from direct external modification.

Requirements

  1. Create a BankAccount class with a balance that cannot be accessed directly from outside the class.
  2. Add public methods to deposit money, withdraw money, and read the current balance.
  3. Prevent invalid deposits or withdrawals, such as negative amounts or withdrawing more than the balance.
  4. Add one internal helper method for validation.
  5. Show the class being used with a few example operations.
Take Quiz

Keep learning

Related questions

View more