Object-Oriented PHP

Reviewed & published by Brayan K

By the end of this lesson you'll be able to model real things as classes — bundling their data and behaviour into objects with constructors, visibility, static members, constants, and readonly properties — the foundation of every modern PHP application.

Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Classes, Properties & Objects

Up to now your data has lived in loose variables. Object-oriented programming (OOP) bundles related data and the code that works on it into one unit. A class is the blueprint: it lists the properties (the data each object holds) and the methods (the things it can do). An object is a single thing built from that blueprint with the new keyword. You reach inside an object with the arrow operator ->. Typing each property (string $name) tells PHP — and your editor — exactly what kind of value belongs there.

<?php
// A class is a BLUEPRINT. It describes what every object made from it
// will hold (properties) and what it can do (methods). Nothing happens
// until you build an actual object from it with 'new'.

class Dog
{
    // Typed properties with visibility:
    //   public    -> readable/writable from anywhere
    //   private   -> only inside THIS class
    public string $name;     // the dog's name
    public int    $age;      // the dog's age in years
}

// 'new' builds an OBJECT (an instance) from the blueprint.
$rex = new Dog();   // $rex is now a Dog object

// Reach inside the object with -> to set its properties.
$rex->name = "Rex";
$rex->age  = 3;

echo "Name: {$rex->name}\n";   // {$rex->name} pulls the value back out
echo "Age:  {$rex->age}\n";

// Each object is independent — a second Dog has its own data.
$buddy = new Dog();
$buddy->name = "Buddy";
$buddy->age  = 7;

echo "Name: {$buddy->name}\n";
echo "Age:  {$buddy->age}\n";

Notice $rex and $buddy are completely separate — each carries its own name and age. That independence is the whole point: one blueprint, many self-contained objects.

2️⃣ The Constructor, $this & Methods

Setting every property by hand is tedious. A constructor — the special method named __construct() — runs automatically the instant you write new, so an object is born ready to use. Inside any method, $this means "this particular object", so $this->name = $name stores the passed-in value on the object. A method is just a function that lives in the class and can use $this.

<?php
// Setting every property by hand (like the last example) is tedious and
// easy to forget. A CONSTRUCTOR fixes that: __construct() runs automatically
// the moment you write 'new', so an object is fully built in one line.

class Dog
{
    public string $name;
    public int    $age;

    // $this means "this particular object". $this->name is the property
    // on the object being built; $name is the value passed in.
    public function __construct(string $name, int $age)
    {
        $this->name = $name;   // store the argument on the object
        $this->age  = $age;
    }

    // A METHOD is a function that belongs to the class and can use $this.
    public function describe(): string
    {
        return "{$this->name} is {$this->age} years old";
    }
}

// The arguments go straight into __construct — no manual setup needed.
$rex   = new Dog("Rex", 3);
$buddy = new Dog("Buddy", 7);

echo $rex->describe() . "\n";     // calls the method with ->
echo $buddy->describe() . "\n";

3️⃣ Visibility & Constructor Promotion

Each property has a visibility that decides who may touch it. public means anyone, anywhere; private means only code inside the same class; protected is like private but also visible to classes that extend it. Hiding data behind private lets a class guarantee it stays valid — outsiders go through methods (a getter like getBalance()) instead of poking the value directly. PHP 8's constructor property promotion declares the property and assigns the argument in one move: put the visibility keyword right in the constructor's parameter list and PHP does the $this->x = $x for you.

<?php
// PHP 8 CONSTRUCTOR PROPERTY PROMOTION: declare the property AND assign it
// in one step by putting the visibility keyword in the constructor signature.
// PHP creates the property and runs $this->x = $x for you. Same result,
// far less boilerplate.

class BankAccount
{
    // 'private' here both DECLARES $balance and assigns the argument to it.
    // 'protected' would let subclasses see it; 'public' anyone.
    public function __construct(
        public string $owner,        // promoted public property
        private float  $balance = 0, // promoted private property, default 0
    ) {}

    public function deposit(float $amount): void
    {
        $this->balance += $amount;   // private is reachable from INSIDE
    }

    public function getBalance(): float
    {
        return $this->balance;       // a 'getter' exposes it safely
    }
}

$acc = new BankAccount("Alice", 100);
$acc->deposit(50);

echo "Owner:   {$acc->owner}\n";              // public -> read directly
echo "Balance: {$acc->getBalance()}\n";       // private -> via the getter

The whole class is just a constructor and two short methods, yet $balance can never be set to a nonsense value from outside — that's encapsulation, the real payoff of private.

4️⃣ Static Members & Class Constants

Most properties belong to an object. A static property or method belongs to the class itself — there's one shared copy and you reach it with Class::member, no new needed. From inside the class you write self:: to mean "this class" (static:: is its close cousin that respects subclasses — handy once you use inheritance). A class constant (const MAX = 100;) is a fixed value attached to the class that can never change, written in UPPER_CASE by convention.

<?php
// STATIC members belong to the CLASS itself, not to any one object.
// There is exactly one copy, shared by everything. Use Class::member to
// reach it — no 'new' required.

class Counter
{
    // A class CONSTANT never changes. By convention it's UPPER_CASE.
    const MAX = 100;

    // A static PROPERTY is shared across every object of the class.
    public static int $count = 0;

    public function __construct()
    {
        self::$count++;          // self:: = "this class" — bumps the shared count
    }

    // A static METHOD is called on the class, not an instance.
    public static function howMany(): string
    {
        // self::MAX reads the constant; self::$count reads the static property.
        return self::$count . " of " . self::MAX . " created";
    }
}

new Counter();   // each 'new' runs the constructor, so $count goes up
new Counter();
new Counter();

echo Counter::howMany() . "\n";   // Class::method() — no object needed
echo "MAX is " . Counter::MAX . "\n";

Now you try. The class below is almost complete — fill in each ___ using the 👉 hint, then run it and check it against the Output panel.

<?php
// 🎯 YOUR TURN — build a Book class, then run it.
// Fill in each blank marked ___ using the 👉 hints.

class Book
{
    // 1) Declare two PUBLIC typed properties: a string $title and an int $pages
    ___ string $title;       // 👉 add the visibility keyword:  public
    public ___ $pages;       // 👉 add the type:  int

    // 2) Finish the constructor so it stores both arguments on the object
    public function __construct(string $title, int $pages)
    {
        $this->title = ___;  // 👉 the argument to save here is  $title
        ___->pages   = $pages;   // 👉 the object refers to itself as  $this
    }

    public function summary(): string
    {
        return "{$this->title} — {$this->pages} pages";
    }
}

$b = new Book("PHP Basics", 240);
echo $b->summary() . "\n";

// ✅ Expected output:
//    PHP Basics — 240 pages
?>

One more — this time the blanks are about static members. Fill them in so the shared counter works.

<?php
// 🎯 YOUR TURN — finish the static counter, then run it.
// Replace each ___ using the 👉 hints.

class Robot
{
    public static int $built = 0;   // shared across ALL robots

    public function __construct()
    {
        // 1) Increase the SHARED counter by one.
        ___::$built++;              // 👉 "this class" keyword is  self
    }
}

new Robot();
new Robot();

// 2) Read the shared count off the CLASS, not an object.
echo Robot::___ . " robots built\n";   // 👉 the static property is  $built

// ✅ Expected output:
//    2 robots built
?>

5️⃣ Readonly Properties

Some values should be set once and then frozen — an id, a created-at timestamp, a primary key. PHP 8.1's readonly keyword does exactly that: the property can be assigned a single time (almost always inside the constructor) and any later attempt to change it throws an error. It pairs perfectly with constructor promotion, giving you tamper-proof objects with almost no code.

<?php
// A READONLY property (PHP 8.1) can be set ONCE — usually in the constructor —
// then never changed again. Perfect for an ID or anything that must stay fixed.

class User
{
    public function __construct(
        public readonly int    $id,     // set once, then locked
        public string          $name,   // a normal, changeable property
    ) {}
}

$u = new User(1, "Alice");

echo "ID:   {$u->id}\n";
echo "Name: {$u->name}\n";

$u->name = "Alicia";   // fine — $name is a normal property
echo "Renamed to: {$u->name}\n";

// $u->id = 2;   // would crash: "Cannot modify readonly property User::\$id"
echo "ID is still: {$u->id}\n";

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — PHP OOP

SyntaxExampleWhat It Does
classclass Dog { }Define a blueprint
newnew Dog("Rex", 3)Build an object (instance)
public/private/protectedprivate int $age;Set who can access a property
__constructfunction __construct(...)Runs automatically on new
$this->$this->nameThis object's property/method
static + self::self::$countClass-level data, one shared copy
constconst MAX = 100;A fixed value on the class
readonlyreadonly int $idSet once, then locked

Mini-Challenge: Rectangle Class

No code is filled in this time — just a brief and an outline. Write the class yourself, run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments. This is exactly the write-run-check loop you'll use on every real class.

<?php
// 🎯 MINI-CHALLENGE: A Rectangle class.
// No code is filled in — work from the steps below, then run it.
//
// 1. Declare a class called  Rectangle
// 2. Give it a constructor using PROPERTY PROMOTION that takes
//      public float $width  and  public float $height
// 3. Add a method  area(): float  that returns $this->width * $this->height
// 4. Add a method  describe(): string  that returns
//      "Wx H = AREA"   e.g. "4x3 = 12"
// 5. Build  new Rectangle(4, 3)  and echo its describe() on its own line
//
// Tip: inside double quotes, "{$this->width}x{$this->height}" reads both
//      properties; call $this->area() to get the area.
//
// ✅ Expected output:
//    4x3 = 12

// your code here
?>

🎉 Lesson Complete!

Practice quiz

What is the relationship between a class and an object?

  • They are the same thing
  • An object is a blueprint for a class
  • A class is a blueprint; an object is built from it
  • A class is one instance of an object

Answer: A class is a blueprint; an object is built from it. A class is the blueprint, and each object (instance) is a thing built from it with new.

Which keyword builds an object from a class?

  • new
  • create
  • build
  • make

Answer: new. new builds an object (instance) from a class, e.g. new Dog().

Which operator reaches a property or method on an object?

  • ::
  • .
  • =>
  • ->

Answer: ->. The arrow operator -> accesses an object's properties and methods, e.g. $rex->name.

What is the name of the special method that runs automatically on new?

  • __init
  • __construct
  • __new
  • __start

Answer: __construct. __construct() is the constructor; it runs the instant you write new.

What does $this refer to inside a method?

  • The specific object the method is running on
  • The class itself
  • A global variable
  • The parent class

Answer: The specific object the method is running on. $this is a reference to the particular object the method is being called on.

What does private visibility mean for a property?

  • Anyone can read and write it
  • It can never be set
  • Only code inside the same class can access it
  • It is shared across all objects

Answer: Only code inside the same class can access it. private restricts access to code inside the same class; outsiders use methods like getters.

What does constructor property promotion let you do?

  • Make all properties public
  • Declare a property and assign it in one step in the constructor signature
  • Skip the constructor entirely
  • Make a property readonly only

Answer: Declare a property and assign it in one step in the constructor signature. Putting a visibility keyword in the constructor's parameter list declares the property and assigns it.

How do you reach a static member from inside its own class?

  • $this->
  • new
  • ->
  • self::

Answer: self::. Inside the class you use self:: (e.g. self::$count) to reach static members and constants.

What is true of a readonly property (PHP 8.1)?

  • It can never be set
  • It can be set once, then never changed
  • It is always public
  • It is shared across objects

Answer: It can be set once, then never changed. A readonly property is assigned once (usually in the constructor) and locked thereafter.

What does a static property belong to?

  • Each individual object
  • The parent class only
  • The class itself, shared by all
  • A global scope

Answer: The class itself, shared by all. A static property has one shared copy on the class, reachable with Class::member.

Continue this course

Frequently asked questions

What is the difference between a class and an object?

A class is the blueprint and an object is a thing built from it. The class Dog describes that every dog has a name and an age and can bark; each time you write new Dog() you get a separate object with its own name and age. One class can produce as many independent objects as you like — changing one object never affects the others.

When should a property be public, private, or protected?

Default to private and only widen it when you have a reason. private hides a property so only code inside the same class can touch it, which lets the class guarantee its own data stays valid (you expose it through methods like getBalance() instead). protected is like private but also visible to child classes that extend it. public means anyone, anywhere can read and write it — convenient but it removes the safety the other two give you.

What does $this actually mean?

$this is a reference to the specific object a method is running on. When you call $rex->describe(), inside describe() $this IS $rex, so $this->name reads Rex's name; call $buddy->describe() and the same code sees Buddy. You can only use $this inside a non-static method, because static methods belong to the class and have no particular object to point at.

What is constructor property promotion and why use it?

It is PHP 8 shorthand that lets you declare a property and assign its constructor argument in a single line, by writing the visibility keyword (public/private/protected) right in the constructor's parameter list. Instead of declaring private float $balance, listing it as a parameter, and writing $this->balance = $balance, you write just private float $balance in the signature. It is the same behaviour with far less boilerplate, which is why modern PHP code uses it everywhere.

When should I use static instead of a normal property or method?

Use static when the data or behaviour belongs to the class as a whole rather than to one object — for example a shared counter of how many objects exist, or a utility method that does not need any object's data. You reach static members with Class::member (or self:: from inside the class) and they exist without ever calling new. If a method needs $this, it cannot be static.

Related lessons