PHP Classes and Objects

Write object-oriented PHP: classes, properties and methods, constructor promotion, visibility, static members, inheritance and interfaces.

Lesson 18 of 86Advanced18 min read

As PHP projects grow, collections of loose functions and arrays become hard to manage. Which functions work with a user array? Which keys does a product array need? Object-oriented programming (OOP) answers these questions by grouping data and the functions that use it into classes.

A class is a blueprint: a Product class describes that every product has a name and a price, and can calculate its price with tax. Each actual product in your shop is an object created from that class. Modern PHP, from version 8 onwards, has excellent OOP features, and every major framework, including Laravel, Symfony and WordPress's newer code, is built from classes.

In this lesson you'll define classes with typed properties and methods, use $this, write constructors (including PHP 8's handy constructor promotion), control access with visibility, use static members, extend classes with inheritance and define contracts with interfaces.

Defining a class

PHP
class Product {
    public string $name;
    public float $price;

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

$pen = new Product();
$pen->name = 'Pen';
$pen->price = 1.20;
echo $pen->priceWithTax();   // 1.44
  • Properties hold an object's data; methods are functions that belong to the class.
  • new Product() creates an object.
  • The arrow -> reaches properties and methods: $pen->name, $pen->priceWithTax().
  • Inside a method, $this means "the current object".

Constructors and constructor promotion

A constructor named __construct runs when an object is created, so it can set up the object properly. PHP 8's constructor promotion declares and assigns properties in one step:

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

$lamp = new Product('Lamp', 24.50);

Putting a visibility keyword in front of a constructor parameter turns it into a property automatically. It's shorter, clearer and the style most modern PHP uses.

Visibility and static members

KeywordAccessible from
publicAnywhere
protectedThe class and classes that extend it
privateOnly inside the class itself

Keep data private and offer public methods that enforce your rules, for example a setPrice() method that refuses negative prices. A readonly property (PHP 8.1) can only be set once, in the constructor.

Static members belong to the class itself rather than to any one object: Product::$count or Product::create(). Inside the class, refer to them with self::. Constants work similarly: const VAT = 0.2; is read as Product::VAT.

Inheritance and interfaces

A class can extend another, inheriting its properties and methods and overriding the ones it needs to change:

PHP
class DigitalProduct extends Product {
    public function shippingCost(): float { return 0; }
}

Call the parent's version of an overridden method with parent::methodName(), and the parent's constructor with parent::__construct(...).

An interface is a contract listing methods a class must provide. A class implements one or more interfaces, which lets different classes be used interchangeably:

PHP
interface Shippable {
    public function shippingCost(): float;
}

In real projects each class lives in its own file, and Composer's autoloader loads them automatically when they're first used, so you never write long lists of require statements.

Objects are shared, not copied

Arrays are copied when you assign them, but objects work differently. A variable holds a handle to an object, so assigning it to another variable gives you two names for the same object:

PHP
class Basket {
    public array $items = [];
}

$a = new Basket();
$b = $a;
$b->items[] = "pen";
echo count($a->items);   // 1: $a and $b are the same basket

The same is true when you pass an object to a function: the function can change the caller's object. When you really need an independent copy, use clone: $c = clone $a;. Comparing objects with == checks whether they're the same class with equal properties, while === checks whether two variables point to the very same object. The object cloning and comparison lesson covers deep copies.

PHP Classes Syntax

Class syntax

interface Describable {
    public function describe(): string;
}

class Name implements Describable {
    const LIMIT = 10;
    private static int $count = 0;

    public function __construct(private string $value) {
        self::$count++;
    }

    public function describe(): string {
        return "Value: {$this->value}";
    }
}

class Child extends Name { }
$obj = new Name('x');
echo $obj->describe();

PHP Classes Summary

  • A class defines properties and methods; new creates objects, and -> uses them.
  • $this refers to the current object inside a method.
  • __construct sets up new objects; constructor promotion declares properties in its parameters.
  • public, protected and private control access; static and const belong to the class.
  • extends reuses a class, parent:: calls its version, and interfaces define contracts.

PHP Classes Examples

Each example shows the code, the output it produces and what each part does.

Example 1

A product class with rules

PHP
<?php
class Product {
    const VAT = 0.2;

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

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

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

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

$lamp = new Product('Desk lamp', 24.50);
$lamp->setPrice(19.99);
echo "{$lamp->name}: {$lamp->price()} (with VAT {$lamp->priceWithVat()})";
Output
Desk lamp: 19.99 (with VAT 23.99)
Line-by-line explanation
  1. Line 3A class constant, read inside the class as self::VAT.
  2. Lines 5 to 8Constructor promotion creates the name and price properties. readonly means the name can't change later.
  3. Lines 12 to 17The only way to change the price is through a method that checks it.
  4. Line 2619.99 x 1.2 = 23.988, rounded to 23.99.
Example 2

Inheritance and parent::

PHP
<?php
class Account {
    public function __construct(
        protected string $owner,
        protected float $balance = 0,
    ) {}

    public function summary(): string {
        return sprintf('%s: £%.2f', $this->owner, $this->balance);
    }
}

class SavingsAccount extends Account {
    public function __construct(string $owner, float $balance, private float $rate) {
        parent::__construct($owner, $balance);
    }

    public function addInterest(): void {
        $this->balance += $this->balance * $this->rate;
    }

    public function summary(): string {
        return parent::summary() . sprintf(' at %d%%', $this->rate * 100);
    }
}

$s = new SavingsAccount('Lea', 1000, 0.03);
$s->addInterest();
echo $s->summary();
Output
Lea: £1030.00 at 3%
Line-by-line explanation
  1. Lines 4 to 5protected properties can be used by classes that extend Account.
  2. Line 15parent::__construct runs the parent's set-up code.
  3. Line 23The override reuses the parent's summary and adds the rate.
Example 3

Interfaces and static counters

PHP
<?php
interface Shippable {
    public function shippingCost(): float;
}

class Parcel implements Shippable {
    private static int $created = 0;

    public function __construct(private float $kg) { self::$created++; }

    public function shippingCost(): float { return 3.5 + $this->kg * 1.25; }

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

class Download implements Shippable {
    public function shippingCost(): float { return 0; }
}

$items = [new Parcel(2), new Download(), new Parcel(0.4)];
$total = array_sum(array_map(fn(Shippable $i) => $i->shippingCost(), $items));

echo "Parcels created: " . Parcel::created() . "\n";
echo "Shipping: £" . number_format($total, 2);
Output
Parcels created: 2
Shipping: £10.00
Line-by-line explanation
  1. Lines 2 to 4Any shippable thing must say how much it costs to ship.
  2. Line 7A static property counts parcels across all objects.
  3. Line 9Each new parcel adds one to the shared counter.
  4. Line 21Different classes are treated the same way through the interface: 6.00 + 0 + 4.00 = 10.00.

Common PHP Classes Mistakes

Forgetting $this

Don't
public function priceWithTax(): float {
    return $price * 1.2;      // undefined variable $price
}
Do
public function priceWithTax(): float {
    return $this->price * 1.2;
}

Accessing a private property from outside

Don't
$lamp->price = -5;            // Error: Cannot modify private property
Do
$lamp->setPrice(5);

Not calling the parent constructor

Don't
class SavingsAccount extends Account {
    public function __construct(string $owner, private float $rate) {}
}   // $owner is never stored
Do
class SavingsAccount extends Account {
    public function __construct(string $owner, private float $rate) {
        parent::__construct($owner);
    }
}

PHP Classes in Real Projects

OOP in PHP frameworks

In Laravel, every database table has a model class, such as Product extends Model, and controllers are classes whose methods handle web requests. Symfony services, WordPress plugins written in modern style and Composer packages are all built from classes, interfaces and namespaces, loaded automatically by Composer's autoloader.

PHP Classes FAQ

What is the difference between -> and :: in PHP?

-> works on an object: $pen->name or $pen->priceWithTax(). :: works on the class itself: static methods and properties, such as Product::count(), class constants such as Product::VAT, and parent:: or self:: inside a class.

Should class properties be public?

Usually not. Make properties private or protected and expose public methods that keep the object valid, such as a setPrice() that rejects negative values. A public readonly property is a good option for values that are set once in the constructor and only read afterwards.

How do I check what class an object is?

Use instanceof: if ($payment instanceof CardPayment) is true for that class, its subclasses and classes implementing it as an interface. $object::class or get_class($object) returns the class name as a string, which is useful in log messages.

How do I print an object for debugging?

var_dump($object) and print_r($object) show its class and all its properties, including private ones. To give an object a friendly text form for echo, add a __toString() method that returns a string, such as the product's name and price.

Does every class need its own file?

It isn't required, but it is the standard convention: one class per file, with the file named after the class, such as Product.php. Composer's PSR-4 autoloader relies on that convention to find classes automatically, as the namespaces and Composer lesson shows.

PHP Classes Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the code that creates an object and calls a method.

$pen =  Product('Pen', 1.2);
echo $penpriceWithVat();
Predict the outputeasy

What does this output?

class Counter {
    private int $n = 0;
    public function add(): static { $this->n++; return $this; }
    public function get(): int { return $this->n; }
}
echo (new Counter())->add()->add()->get();
Multiple choicemedium

What does constructor promotion do?

Multiple choicemedium

Which visibility lets child classes use a property but hides it from other code?

Multiple choicehard

How do you call a parent class's version of an overridden method?

PHP Classes Quiz

5 questions to check what you learned.

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

    • The class name
    • The parent class
    • The current object
    • The first property
  2. What is the name of PHP's constructor method?

    • constructor()
    • __construct()
    • init()
    • new()
  3. How do you read a static property inside its own class?

    • self::$count
    • $this->count
    • static->count
    • Class.count
  4. What does a class promise when it implements an interface?

    • To extend another class
    • To have no properties
    • To be abstract
    • To provide every method the interface declares
  5. What does readonly on a property mean?

    • It can never be read
    • It's private
    • It's static
    • It can only be set once, normally in the constructor