Himanshu SharmaSoftware engineer
Let’s talk

PHP · Lesson 42 of 54

Classes and objects

Start object-oriented PHP. Define a class with properties and methods, create objects with new, use $this, and protect data with private.

13 minObject-oriented PHPUpdated 22 October 2026

Awaiting technical review. This lesson is readable but not yet listed in search engines.

By the end of this lessonDefine a class with properties and methods, create objects from it and keep its data private.

Why objects

So far, data has lived in arrays and logic in separate functions: a product is an array, and priceWithTax($product) is a function somewhere else. That works, but nothing stops code from putting a negative price into the array, and as programs grow it gets hard to know which functions belong to which data.

Object-oriented programming (OOP) bundles the data and the functions that work on it into one unit, an object. A product object knows its own name and price, and knows how to calculate its own price with tax. Most modern PHP, including Laravel, Symfony and WordPress's newer code, is written this way.

A class and an object

A class is the blueprint. It lists the properties (data) each object will have and the methods (functions) it can perform. new builds an object (an instance) from the class:

php
<?php
class Product
{
    public string $name = '';
    public float $price = 0.0;

    public function priceWithTax(float $rate = 0.18): float
    {
        return round($this->price * (1 + $rate), 2);
    }
}

$keyboard = new Product();
$keyboard->name = 'Mechanical Keyboard';
$keyboard->price = 89.5;

$mug = new Product();
$mug->name = 'Ceramic Mug Set';
$mug->price = 18.75;

echo $keyboard->name . ': ' . $keyboard->priceWithTax() . "\n";
echo $mug->name . ': ' . $mug->priceWithTax() . "\n";
result
Mechanical Keyboard: 105.61
Ceramic Mug Set: 22.13

Points to notice:

  • Class names use PascalCase by convention: Product, OrderLine, ShoppingCart.
  • The arrow -> reaches a property or method of an object: $keyboard->price, $keyboard->priceWithTax(). (No $ before the property name.)
  • Inside a method, $this means "the object this method was called on". The same method returns different results for $keyboard and $mug because $this->price is different.
  • Properties can have types, just like function parameters. Assigning a string to $price would throw a TypeError.

Methods that change the object

Methods can update the object's own properties. Here a cart keeps its lines and offers methods to add items and get the total:

php
<?php
class Cart
{
    public array $lines = [];

    public function add(string $product, float $price, int $qty = 1): void
    {
        $this->lines[] = ['product' => $product, 'price' => $price, 'qty' => $qty];
    }

    public function total(): float
    {
        $total = 0;
        foreach ($this->lines as $line) {
            $total += $line['price'] * $line['qty'];
        }
        return $total;
    }

    public function count(): int
    {
        return array_sum(array_column($this->lines, 'qty'));
    }
}

$cart = new Cart();
$cart->add('Mechanical Keyboard', 89.5);
$cart->add('Ceramic Mug Set', 18.75, 2);

echo $cart->count() . " items, total " . $cart->total() . "\n";
result
3 items, total 127

Code that uses the cart does not need to know how lines are stored. It calls add(), count() and total(), and the class is free to change its insides later.

public and private

The cart above has a problem: $lines is public, so any code can do $cart->lines[] = 'nonsense'; and break total(). Visibility keywords control who can use each member:

Make the data private and offer public methods that enforce the rules. That is called encapsulation:

php
<?php
class Product
{
    private float $price = 0.0;

    public function __construct(public string $name) {}

    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;
    }
}

$mat = new Product('Yoga Mat');
$mat->setPrice(24.99);
echo $mat->name . ' costs ' . $mat->getPrice() . "\n";

try {
    $mat->setPrice(-5);
} catch (InvalidArgumentException $e) {
    echo $e->getMessage() . "\n";
}
result
Yoga Mat costs 24.99
Price cannot be negative.

Trying $mat->price = -5; from outside would now fail with "Cannot access private property". The __construct(public string $name) {} line is a constructor, which runs when the object is created; the next lesson covers it properly.

Objects are shared, not copied

Arrays are copied when you assign them. Objects are not: the variable holds a handle to the object, and assigning it copies the handle. Two variables then point at the same object:

php
<?php
class Counter
{
    public int $value = 0;
}

$a = new Counter();
$b = $a;          // same object
$b->value = 10;
echo $a->value . "\n";

$c = clone $a;    // a separate copy
$c->value = 99;
echo $a->value . " " . $c->value . "\n";
result
10
10 99

The same happens when you pass an object to a function: the function can change the object the caller holds. That is usually what you want with objects, but use clone when you need an independent copy.

Inspecting objects

var_dump() and print_r() work on objects and show their class and properties. instanceof checks whether a value is an object of a given class:

php
<?php
class Product
{
    public function __construct(public string $name, private float $price) {}
}

$p = new Product('Water Bottle', 12.0);
var_dump($p instanceof Product);
print_r($p);
echo get_class($p) . "\n";
result
bool(true)
Product Object
(
    [name] => Water Bottle
    [price:Product:private] => 12
)
Product

Common mistakes

  • Writing $this->$name instead of $this->name. The extra $ looks up a property whose name is the value of $name.
  • Forgetting $this inside a method: return $price; uses a local variable, not the property.
  • Making everything public, so the class cannot protect its own rules.
  • Expecting $b = $a to copy an object. Use clone.

Exercise

Write a class BankAccount with a private balance (starting at 0), and public methods deposit(float $amount), withdraw(float $amount) and getBalance(). Deposits must be positive, and withdrawals may not exceed the balance; throw an exception otherwise. Deposit 500, withdraw 120, try to withdraw 1000, and print the balance.

Show one possible answer
php
<?php
class BankAccount
{
    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 getBalance(): float
    {
        return $this->balance;
    }
}

$account = new BankAccount();
$account->deposit(500);
$account->withdraw(120);
try {
    $account->withdraw(1000);
} catch (RuntimeException $e) {
    echo $e->getMessage() . "\n";
}
echo "Balance: " . $account->getBalance() . "\n";
result
Insufficient funds.
Balance: 380

Key points

  • A class is a blueprint; an object is one thing built from it with new.
  • Properties hold an object's data; methods are functions that belong to the object and use $this.
  • public members can be used from anywhere; private ones only inside the class, which protects the object's rules.
  • Objects are passed around by handle, so two variables can refer to the same object; use clone for a copy.

Check yourself

1. What does new Product() do?
2. Inside a method, what does $this refer to?
3. Where can a private property be read?
4. After $b = $a; $b->name = 'X'; what is $a->name when $a is an object?