Getters and Setters in PHP OOP

Getters and setters in PHP: how to protect object data, add validation, and write clean, fluent setters - beginner-friendly examples included.

Getters and setters are fundamental concepts in Object-Oriented Programming (OOP) in PHP. They are used for safe reading and modification of an object’s state (the properties and methods you declare in a class) while preserving encapsulation. With them you can:

  • protect data (e.g., validate values before saving),
  • control access (public, private, protected),
  • maintain consistency and business logic in one place.

You will use getters and setters in PHP whenever you want to:

  • hide class implementation (encapsulation),
  • validate input data (e.g., email, price, age),
  • ensure type safety and invariants (e.g., “balance cannot be negative”),
  • prepare a clean class API (clear, understandable methods).

What are getters and setters in PHP?

Definitions

  • Getter: a public method that returns a property value (e.g., getName(): string).
  • Setter: a public method that sets a property value (e.g., setName(string $name): void).

In practice: the real reason to use a setter instead of a public property is validation - a setter is the single place where you can reject a bad value (a negative price, an invalid email) before it corrupts the object's state.

Best practices in PHP OOP:

  • properties should usually be private,
  • use methods (getters and setters) for controlled access,
  • for boolean values, use prefixes is/has (e.g., isActive(): bool).

Why not public properties?

  • You lose validation control,
  • harder to change implementation later,
  • breaks encapsulation,
  • risk of invalid object states.

Naming conventions

  • getX(), setX() - general standard,
  • isX(), hasX() - for boolean values,
  • domain-style names (e.g., price(): Money) - but in this course we stick to classic get/set.

Typing and nullability

  • Use typed properties (PHP 7.4+), union types (PHP 8+).
  • Always enable strict_types.
  • Use nullable types (?string) when needed.

Do you always need setters?

Not always. Sometimes better to have:

  • only a getter (read-only property), often paired with a value assigned once in the constructor,
  • domain-specific methods instead of generic setters (deposit(float $amount) instead of setBalance),
  • immutable objects with withX() methods returning a new instance.

PHP getters and setters examples

Example 1: Simple class with validation in setters

final class User
{
    private string $name;
    private int $age;
    private ?string $email = null;
    private bool $active = true;

    public function __construct(string $name, int $age)
    {
        $this->setName($name);
        $this->setAge($age);
    }

    // GETTERS
    public function getName(): string { return $this->name; }
    public function getAge(): int { return $this->age; }
    public function getEmail(): ?string { return $this->email; }
    public function isActive(): bool { return $this->active; }

    // SETTERS
    public function setName(string $name): void
    {
        $name = trim($name);
        if ($name === '') throw new InvalidArgumentException('Name cannot be empty.');
        $this->name = mb_convert_case($name, MB_CASE_TITLE, 'UTF-8');
    }

    public function setAge(int $age): void
    {
        if ($age < 0 || $age > 130) throw new InvalidArgumentException('Invalid age.');
        $this->age = $age;
    }

    public function setEmail(?string $email): void
    {
        if ($email !== null && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email.');
        }
        $this->email = $email ? strtolower($email) : null;
    }

    public function setActive(bool $active): void { $this->active = $active; }
}

Example 2: Fluent setters (method chaining)

final class Product
{
    private string $name;
    private float $price;

    public function __construct(string $name, float $price)
    {
        $this->setName($name)->setPrice($price);
    }

    public function getName(): string { return $this->name; }

    public function setName(string $name): self
    {
        if (trim($name) === '') throw new InvalidArgumentException('Name cannot be empty.');
        $this->name = $name;
        return $this;
    }

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

    public function setPrice(float $price): self
    {
        if ($price < 0) throw new InvalidArgumentException('Price cannot be negative.');
        $this->price = round($price, 2);
        return $this;
    }

    public function getGrossPrice(float $vat): float
    {
        return round($this->price * (1 + $vat), 2);
    }
}

Example 3: Readonly properties (PHP 8.1+)

final class Config
{
    private readonly string $apiKey;
    private readonly string $env;

    public function __construct(string $apiKey, string $env = 'prod')
    {
        if ($apiKey === '') throw new InvalidArgumentException('API key cannot be empty.');
        $this->apiKey = $apiKey;
        $this->env = $env;
    }

    public function getApiKey(): string { return $this->apiKey; }
    public function getEnv(): string { return $this->env; }
}

Example 4: Immutable object with withX() methods

final class Email
{
    private function __construct(private string $value)
    {
        self::assertValid($value);
        $this->value = strtolower($value);
    }

    public static function fromString(string $email): self { return new self($email); }
    public function asString(): string { return $this->value; }

    public function withDomain(string $domain): self
    {
        [$local] = explode('@', $this->value);
        return new self($local . '@' . ltrim($domain, '@'));
    }

    private static function assertValid(string $email): void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) throw new InvalidArgumentException('Invalid email.');
    }
}

Example 5: Domain methods instead of setters

final class BankAccount
{
    private float $balance = 0.0;

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

    public function deposit(float $amount): void
    {
        if ($amount <= 0) throw new InvalidArgumentException('Deposit must be positive.');
        $this->balance = round($this->balance + $amount, 2);
    }

    public function withdraw(float $amount): void
    {
        if ($amount <= 0) throw new InvalidArgumentException('Withdrawal must be positive.');
        if ($amount > $this->balance) throw new RuntimeException('Insufficient funds.');
        $this->balance = round($this->balance - $amount, 2);
    }
}

Example 6: Magic methods __get and __set (use cautiously)

class DataBag
{
    private array $data = [];

    public function __get(string $name): mixed { return $this->data[$name] ?? null; }
    public function __set(string $name, mixed $value): void { $this->data[$name] = $value; }
}

⚠️ Downsides:

  • No static typing, validation skipped.
  • IDE and analyzers struggle to detect errors.
  • From PHP 8.2, dynamic properties are forbidden by default (different from __get/__set).
  • Prefer explicit getters/setters.

Best Practices and Common Mistakes

Best Practices

  • Keep properties private; expose controlled access.
  • Validate in setters.
  • Use typed properties and strict types.
  • Naming: get/set for general, is/has for boolean.
  • Getters must not cause side effects.
  • Prefer domain methods (deposit, withdraw) over generic setters.
  • Use immutable objects and readonly when appropriate - if you want to go deeper, see readonly properties in PHP, and PHP 8.4 property hooks for a modern alternative to hand-written getters and setters.
  • For collections, use add/remove instead of setCollection(array).
  • Use PHPDoc when validation/normalization logic matters.
  • Be consistent when returning $this in fluent setters.

Common Mistakes

  • Public properties without validation.
  • Setters without validation.
  • Anemic models (only get/set, no domain logic).
  • Getters with side effects.
  • Overusing __get/__set.
  • Returning mutable internals without cloning.
  • Not handling exceptions from setters.

Summary

  • Getters = safe read, Setters = controlled write with validation.
  • Prefer private properties, controlled access, and strong typing.
  • Not every property needs a setter.
  • Domain logic often beats generic setters.
  • Avoid public properties and magic method overuse.

Later in this chapter, the lesson on encapsulation as a key OOP principle shows how getters, setters, and validation fit together in a complete class design.


FAQ

What are getters and setters in PHP?

A getter is a method that returns a private property's value (getName()), and a setter is a method that changes it (setName($name)), usually after validating the new value. Together they give controlled access to an object's data instead of exposing it directly.

Why use getters and setters instead of public properties?

They let you validate input and keep invalid values out (a negative price, an empty name), hide how the class stores data, and change that implementation later without breaking the code that uses the class.

What is a fluent setter in PHP?

A setter that returns $this, so calls can be chained: $product->setName('Book')->setPrice(20). It reads cleanly when you configure an object in several steps.

Do all properties need a setter in PHP?

No. Use a getter alone for read-only values, a readonly property for write-once data, or a domain method (deposit(), withdraw()) instead of a generic setter when real business rules apply.