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
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,
$thismeans "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:
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
| Keyword | Accessible from |
|---|---|
public | Anywhere |
protected | The class and classes that extend it |
private | Only 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:
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:
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.
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;
newcreates objects, and->uses them. $thisrefers to the current object inside a method.__constructsets up new objects; constructor promotion declares properties in its parameters.public,protectedandprivatecontrol access;staticandconstbelong to the class.extendsreuses 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.
A product class with rules
<?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()})";Desk lamp: 19.99 (with VAT 23.99)Line-by-line explanation
- Line 3A class constant, read inside the class as self::VAT.
- Lines 5 to 8Constructor promotion creates the name and price properties. readonly means the name can't change later.
- Lines 12 to 17The only way to change the price is through a method that checks it.
- Line 2619.99 x 1.2 = 23.988, rounded to 23.99.
Inheritance and parent::
<?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();Lea: £1030.00 at 3%Line-by-line explanation
- Lines 4 to 5protected properties can be used by classes that extend Account.
- Line 15parent::__construct runs the parent's set-up code.
- Line 23The override reuses the parent's summary and adds the rate.
Interfaces and static counters
<?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);Parcels created: 2
Shipping: £10.00Line-by-line explanation
- Lines 2 to 4Any shippable thing must say how much it costs to ship.
- Line 7A static property counts parcels across all objects.
- Line 9Each new parcel adds one to the shared counter.
- Line 21Different classes are treated the same way through the interface: 6.00 + 0 + 4.00 = 10.00.
Common PHP Classes Mistakes
Forgetting $this
public function priceWithTax(): float {
return $price * 1.2; // undefined variable $price
}public function priceWithTax(): float {
return $this->price * 1.2;
}Accessing a private property from outside
$lamp->price = -5; // Error: Cannot modify private property$lamp->setPrice(5);Not calling the parent constructor
class SavingsAccount extends Account {
public function __construct(string $owner, private float $rate) {}
} // $owner is never storedclass 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.
Complete the code that creates an object and calls a method.
$pen = Product('Pen', 1.2);
echo $penpriceWithVat();
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();
What does constructor promotion do?
Which visibility lets child classes use a property but hides it from other code?
How do you call a parent class's version of an overridden method?
PHP Classes Quiz
5 questions to check what you learned.
What does
$thisrefer to inside a method?- The class name
- The parent class
- The current object
- The first property
What is the name of PHP's constructor method?
- constructor()
- __construct()
- init()
- new()
How do you read a static property inside its own class?
- self::$count
- $this->count
- static->count
- Class.count
What does a class promise when it
implementsan interface?- To extend another class
- To have no properties
- To be abstract
- To provide every method the interface declares
What does
readonlyon a property mean?- It can never be read
- It's private
- It's static
- It can only be set once, normally in the constructor