Classes & Objects

PHP · Lesson 33 of 50 7 min read Updated

So far you have stored data in arrays and passed it to functions. That works, but as an app grows, it becomes hard to remember which array has which keys and which functions may change them. Object-oriented programming (OOP) groups data and the functions that work on it into one unit called an object.

In this lesson you will write classes, create objects from them, use typed properties and methods, write short constructors with property promotion, protect data with private and readonly, and add constants and static members.

Classes and objects

A class is a blueprint. An object is one thing built from that blueprint. The class Student describes what every student has (a name, a city, marks) and can do (add a mark, work out an average). Aarav and Meera are two objects, also called instances, of that class.

Inside a class, variables are called properties and functions are called methods.

PHP
<?php
declare(strict_types=1);

class Student
{
    public string $name;
    public string $city;
    public array $marks = [];

    public function addMark(int $mark): void
    {
        $this->marks[] = $mark;
    }

    public function average(): float
    {
        if ($this->marks === []) {
            return 0.0;
        }
        return array_sum($this->marks) / count($this->marks);
    }
}

$aarav = new Student();
$aarav->name = 'Aarav';
$aarav->city = 'Rohtak';
$aarav->addMark(78);
$aarav->addMark(91);

$meera = new Student();
$meera->name = 'Meera';
$meera->city = 'Kochi';
$meera->addMark(95);

echo "{$aarav->name} ({$aarav->city}): " . $aarav->average() . "\n";
echo "{$meera->name} ({$meera->city}): " . $meera->average() . "\n";
Output
Aarav (Rohtak): 84.5
Meera (Kochi): 95

The important pieces:

  • new Student() creates an object.
  • -> reaches a property or method of an object: $aarav->name, $aarav->addMark(78).
  • Inside a method, $this means "the object this method was called on". When you call $meera->addMark(95), $this is Meera, so the mark goes into Meera's array, not Aarav's.
  • Each object has its own copy of the properties.

Class names use PascalCase (Student, BankAccount), and methods and properties use camelCase (addMark, pricePaise).

Typed properties

Properties can have types, just like function parameters. With declare(strict_types=1);, assigning the wrong type throws a TypeError. A typed property with no default value starts uninitialized, and reading it is an error:

PHP
<?php
declare(strict_types=1);

class Student
{
    public string $name;
}

$s = new Student();
echo $s->name;
Output
Fatal error: Uncaught Error: Typed property Student::$name must not be accessed before initialization

This is a good thing: PHP stops you from using an object that is half set up. The real fix is a constructor that fills every property when the object is created.

Constructors and property promotion

A constructor is a special method named __construct() that runs automatically when you write new. Since PHP 8.0, constructor property promotion lets you declare and fill properties in one place: put a visibility keyword such as public in front of a constructor parameter, and it becomes a property.

PHP
<?php
declare(strict_types=1);

class Product
{
    public function __construct(
        public string $name,
        public int $pricePaise,
        public int $stock = 0,
    ) {
    }

    public function priceLabel(): string
    {
        return '₹' . number_format($this->pricePaise / 100, 2);
    }

    public function isAvailable(): bool
    {
        return $this->stock > 0;
    }
}

$chai = new Product('Masala Chai 250 g', 14950, 12);
$kettle = new Product(name: 'Steel Kettle', pricePaise: 129900);

echo $chai->name . ' - ' . $chai->priceLabel() . "\n";
echo $kettle->name . ' - ' . $kettle->priceLabel() . "\n";
var_dump($kettle->isAvailable());
Output
Masala Chai 250 g - ₹149.50
Steel Kettle - ₹1,299.00
bool(false)

Without promotion, you would declare three properties and then write $this->name = $name; three times. Promotion removes that repetition. The empty body { } is fine because there is nothing else to do. Named arguments, like name: and pricePaise:, make calls with many parameters easier to read.

Notice that the price is stored in paise as an integer. Floats cannot store most decimal fractions exactly, so 0.1 + 0.2 is not exactly 0.3. Money is safer as whole paise.

Visibility: public, private and protected

Visibility decides who can use a property or method:

KeywordWho can use it
publicAny code
privateOnly code inside this class
protectedThis class and classes that extend it (see Inheritance, Interfaces & Traits)

Make properties private when outside code should not change them directly. Then the class can check every change in its methods. This idea is called encapsulation.

Readonly properties

A readonly property (PHP 8.1) can be set only once, from inside the class, usually in the constructor. After that, it cannot change. It is perfect for values like an account number.

PHP
<?php
declare(strict_types=1);

class BankAccount
{
    private int $balance = 0;

    public function __construct(
        public readonly string $accountNumber,
        public readonly string $holder,
    ) {
    }

    public function deposit(int $amount): void
    {
        if ($amount <= 0) {
            throw new InvalidArgumentException('Deposit must be more than zero.');
        }
        $this->balance += $amount;
    }

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

$account = new BankAccount('SB-40021', 'Priya Sharma');
$account->deposit(5000);
$account->deposit(1200);

echo $account->holder . ' has ₹' . $account->balance() . "\n";

try {
    $account->accountNumber = 'SB-99999';
} catch (Error $e) {
    echo $e->getMessage() . "\n";
}

try {
    $account->balance = 1000000;
} catch (Error $e) {
    echo $e->getMessage() . "\n";
}
Output
Priya Sharma has ₹6200
Cannot modify readonly property BankAccount::$accountNumber
Cannot access private property BankAccount::$balance

Anyone can read the account number, but no one can change it. The balance is private, so the only way to change it is deposit(), which refuses zero and negative amounts. Errors like these are explained in Errors & Exceptions.

Constants and static members

A class constant belongs to the class, not to one object. A static property or method is also shared by the whole class. Use self:: to reach them from inside the class, and ClassName:: from outside.

PHP
<?php
declare(strict_types=1);

class Ticket
{
    public const string PREFIX = 'TKT';
    private static int $issued = 0;

    public readonly string $code;

    public function __construct(public readonly string $passenger)
    {
        self::$issued++;
        $this->code = self::PREFIX . '-' . str_pad((string) self::$issued, 4, '0', STR_PAD_LEFT);
    }

    public static function issuedCount(): int
    {
        return self::$issued;
    }
}

$t1 = new Ticket('Rohan');
$t2 = new Ticket('Ananya');

echo $t1->passenger . ': ' . $t1->code . "\n";
echo $t2->passenger . ': ' . $t2->code . "\n";
echo 'Tickets issued: ' . Ticket::issuedCount() . "\n";
Output
Rohan: TKT-0001
Ananya: TKT-0002
Tickets issued: 2

const string PREFIX is a typed class constant, available since PHP 8.3. Use static members sparingly: shared state makes code harder to test.

Objects are passed by handle

When you assign an object to another variable, both variables point to the same object. $b = $a; $b->items[] = 'Pen'; also changes $a. To get a separate copy, use clone $a. Arrays work the other way: assigning an array copies it.

Common mistakes

  • Forgetting $this->. Inside a method, $name is a local variable; the property is $this->name.
  • Making everything public. Then any code can put an object into an invalid state. Start with private and open up only what is needed.
  • Reading a typed property before it is set. Give it a default or set it in the constructor.
  • Using floats for money. Store paise as integers.

Key points

  • A class is a blueprint; new creates objects (instances) from it.
  • Properties hold data, methods hold behaviour, and $this is the current object.
  • Constructor property promotion declares and sets properties in the constructor's parameter list.
  • private hides data behind methods; readonly properties can be set only once.
  • Class constants and static members belong to the class and are reached with ClassName::.
  • Assigning an object shares it; clone makes a copy.

Exercise

Write a TrainBooking class with readonly promoted properties passenger (string), fromCity (string), toCity (string) and a private seats (int) that starts at 1. Add addSeat(), which adds one seat but never more than 6 (throw an exception if the limit is reached), and summary(), which returns text like Isha: Jaipur to Pune, 2 seat(s).

Show solution
PHP
<?php
declare(strict_types=1);

class TrainBooking
{
    private int $seats = 1;

    public function __construct(
        public readonly string $passenger,
        public readonly string $fromCity,
        public readonly string $toCity,
    ) {
    }

    public function addSeat(): void
    {
        if ($this->seats >= 6) {
            throw new LengthException('A booking can have at most 6 seats.');
        }
        $this->seats++;
    }

    public function summary(): string
    {
        return "{$this->passenger}: {$this->fromCity} to {$this->toCity}, {$this->seats} seat(s)";
    }
}

$booking = new TrainBooking('Isha', 'Jaipur', 'Pune');
$booking->addSeat();
echo $booking->summary() . "\n";
Output
Isha: Jaipur to Pune, 2 seat(s)

The seat count is private, so the six-seat rule in addSeat() cannot be bypassed from outside.

Quick Quiz

1. What does $this refer to inside a method?

2. What does constructor property promotion do?

3. When can a readonly property be assigned?

4. After $b = $a; where $a is an object, what happens when you change $b->name?

Learn PHP with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call