PHP OOP — Classes and Objects

Object-Oriented Programming (OOP) organizes code around classes (blueprints) and objects (instances built from those blueprints). Instead of loose functions and arrays floating around, related data and behavior are bundled together. This page covers classes, constructors, visibility, inheritance, interfaces, static members, and traits — with a real class hierarchy executing live on this page.

1) Defining a Class (Properties & Methods)

A class is a blueprint. Properties are variables that belong to the class, and methods are functions that belong to the class.

<?php
  class Car {
    public string $brand;
    public int $speed = 0;

    public function accelerate(int $by) {
      $this->speed += $by;
    }
  }
?>

2) Creating Objects with new

An object is a concrete instance of a class, created with the new keyword.

<?php
  $car1 = new Car();
  $car1->brand = 'Toyota';
  $car1->accelerate(20);
  echo $car1->speed; // 20
?>

3) The Constructor & Property Visibility

__construct() runs automatically when an object is created — the natural place to set up required properties. Visibility keywords control where a property or method can be accessed from:

VisibilityAccessible from
publicAnywhere — inside the class, subclasses, and outside code
protectedInside the class and any subclass, not from outside code
privateOnly inside the exact class that declared it
<?php
  class Animal {
    protected string $name;

    public function __construct(string $name) {
      $this->name = $name;
    }

    public function speak(): string {
      return $this->name . ' makes a sound.';
    }
  }
?>

4) Inheritance (extends)

A class can extends another class to reuse its properties and methods, then override the ones that need to behave differently. parent::__construct() calls the parent class’s constructor from inside the child’s constructor.

<?php
  class Dog extends Animal {
    public function __construct(string $name) {
      parent::__construct($name); // reuse the parent logic
    }

    public function speak(): string {
      return $this->name . ' says: Woof!';
    }
  }
?>

5) Live Demo: Real Inheritance Executing on This Page

The Animal, Dog, and Cat classes shown above are defined for real at the very top of this file, before the <!DOCTYPE html> line. The two lines below are not typed-out text — they are the actual return values of $rex->speak() and $whiskers->speak(), echoed live by PHP:

  • Rex says: Woof!
  • Whiskers says: Meow!
Try it: both Dog and Cat call the same inherited constructor from Animal, but each overrides speak() with its own behavior — that is polymorphism in action.

6) Interfaces (interface / implements)

An interface defines a contract — a list of method names a class promises to implement, without providing the implementation itself. It lets unrelated classes be treated the same way as long as they honor the contract.

<?php
  interface Speaks {
    public function speak(): string;
  }

  class Robot implements Speaks {
    public function speak(): string {
      return 'BEEP BOOP.';
    }
  }
?>

7) Static Properties & Methods

static members belong to the class itself, not to any one object. They are accessed with ClassName::member instead of $object->member, and they keep their value across every call, shared by all instances.

<?php
  class Counter {
    private static int $count = 0;

    public static function increment(): int {
      self::$count++;
      return self::$count;
    }
  }

  Counter::increment();
  Counter::increment();
  echo Counter::increment(); // 3
?>
Live result: the exact Counter class above ran at the top of this file and was called three times. The current count is: 3.

8) Traits (Sharing Methods Across Unrelated Classes)

Sometimes two classes need the same helper method but don’t share a meaningful parent/child relationship. A trait is a chunk of reusable methods that can be mixed into any class with use TraitName;.

<?php
  trait Loggable {
    public function log(string $msg) {
      echo '[LOG] ' . $msg;
    }
  }

  class Invoice {
    use Loggable; // Invoice now has a log() method for free
  }
?>

9) Best Practices & Common Mistakes

IssueProblemFix
Everything publicAny code can corrupt internal stateDefault to private/protected, expose only what’s needed
God classesOne class doing everything is hard to test/maintainSplit responsibilities into smaller classes
Forgetting parent::__construct()Parent setup logic never runsCall it explicitly in the child constructor when needed
Using inheritance where a trait fits betterForces an artificial parent/child relationshipUse a trait to share behavior between unrelated classes