Unit 5 · 7 teaching hours · 23 min read
Object-Oriented Programming in PHP
What the syllabus asks for 7
- Classes, objects, constructors and destructors
- Visibility and encapsulation
- Inheritance, overriding and polymorphism
- Abstract classes, interfaces and traits
- Static members, constants and class methods
- Exceptions
- Seeing the ideas: classes, objects and contracts
Modern PHP applications and frameworks such as Laravel are built from classes. If you know Java (Semester II) or Python classes (Unit 6 of the Python book), PHP's object model will feel familiar, with its own syntax: -> for members, :: for static members, and $this.
- Classes and objects, properties and methods, constructors and destructors.
- Visibility and encapsulation; inheritance, overriding, polymorphism.
- Static members, abstract classes, interfaces, traits; exceptions.
5.1 Classes, objects, constructors and destructors
<?php
class Student {
public static int $count = 0;
public function __construct(private string $name, private array $marks = []) { // constructor promotion (PHP 8)
self::$count++;
}
public function addMark(int $m): static { $this->marks[] = $m; return $this; }
public function average(): float { return $this->marks ? array_sum($this->marks) / count($this->marks) : 0.0; }
public function getName(): string { return $this->name; }
public function __toString(): string { return sprintf("%s (avg %.1f)", $this->name, $this->average()); }
public function __destruct() { echo "[{$this->name} destroyed]\n"; }
}
$s = new Student("Sita");
$s->addMark(88)->addMark(92)->addMark(75);
echo $s, " | ", Student::$count, "\n";
$t = new Student("Ram", [70, 64]);
echo $t, " | ", Student::$count, " | ", $t instanceof Student ? "is a Student" : "", "\n";
unset($t);
echo "end of script\n";
Sita (avg 85.0) | 1
Ram (avg 67.0) | 2 | is a Student
[Ram destroyed]
end of script
[Sita destroyed]
A class declares properties and methods; new creates an object; $this is the current object; -> accesses members. __construct runs on creation (PHP 8's constructor promotion declares and assigns properties in the parameter list); __destruct runs when the last reference disappears (here at unset, and for $s at script end). __toString lets echo $object work.
Returning $this allows method chaining.
5.2 Visibility and encapsulation
| Modifier | Accessible from |
|---|---|
public |
anywhere |
protected |
the class and its subclasses |
private |
the declaring class only |
Keep properties private/protected and expose behaviour through methods, with validation in setters. readonly properties (PHP 8.1) can be set once.
<?php
class Account {
public function __construct(public readonly string $owner, private float $balance = 0) {}
public function deposit(float $amt): void {
if ($amt <= 0) throw new InvalidArgumentException("deposit must be positive");
$this->balance += $amt;
}
public function getBalance(): float { return $this->balance; }
}
$a = new Account("Ram", 100);
$a->deposit(50);
echo $a->owner, " ", $a->getBalance(), "\n";
try { $a->balance = 1; } catch (Error $e) { echo get_class($e), ": ", $e->getMessage(), "\n"; }
try { $a->owner = "Hari"; } catch (Error $e) { echo get_class($e), ": ", $e->getMessage(), "\n"; }
try { $a->deposit(-5); } catch (InvalidArgumentException $e) { echo "rejected: ", $e->getMessage(), "\n"; }
Ram 150
Error: Cannot access private property Account::$balance
Error: Cannot modify readonly property Account::$owner
rejected: deposit must be positive
5.3 Inheritance, overriding and polymorphism
<?php
class Employee {
public function __construct(protected string $name, protected float $base) {}
public function pay(): float { return $this->base; }
public function describe(): string { return static::class . ": {$this->name} earns " . number_format($this->pay()); }
}
class Manager extends Employee {
public function __construct(string $name, float $base, private float $bonus) { parent::__construct($name, $base); }
public function pay(): float { return parent::pay() + $this->bonus; }
}
final class Intern extends Employee {
public function pay(): float { return $this->base * 0.5; }
}
$staff = [new Employee("Hari", 40000), new Manager("Gita", 60000, 15000), new Intern("Bikash", 20000)];
foreach ($staff as $e) { echo $e->describe(), "\n"; }
echo array_sum(array_map(fn(Employee $e) => $e->pay(), $staff)), " ", $staff[1] instanceof Employee ? "Manager is an Employee" : "", "\n";
Employee: Hari earns 40,000
Manager: Gita earns 75,000
Intern: Bikash earns 10,000
125000 Manager is an Employee
extends inherits; parent:: calls the parent version; overriding replaces a method; polymorphism: the loop calls pay() and each object responds according to its class (late binding; static::class gives the real class).
PHP has single inheritance; final prevents extension or overriding.
5.4 Abstract classes, interfaces and traits
<?php
interface Payable { public function amount(): float; }
abstract class Shape {
abstract public function area(): float;
public function report(): string { return sprintf("%s area %.2f", static::class, $this->area()); }
}
trait Loggable {
private array $log = [];
public function log(string $m): void { $this->log[] = $m; }
public function logs(): array { return $this->log; }
}
class Circle extends Shape implements Payable {
use Loggable;
public function __construct(private float $r) {}
public function area(): float { return M_PI * $this->r ** 2; }
public function amount(): float { return $this->area() * 10; }
}
$c = new Circle(2);
$c->log("created");
echo $c->report(), " | price ", round($c->amount(), 1), " | ", implode(",", $c->logs()), "\n";
try { $x = new Shape(); } catch (Error $e) { echo $e->getMessage(), "\n"; }
var_dump($c instanceof Payable, $c instanceof Shape);
Circle area 12.57 | price 125.7 | created
Cannot instantiate abstract class Shape
bool(true)
bool(true)
An abstract class cannot be instantiated and may declare abstract methods that subclasses must implement. An interface lists methods a class promises to provide; a class may implement many.
A trait is a reusable bundle of methods and properties inserted with use, a workaround for single inheritance.
5.5 Static members, constants and class methods
<?php
class MathUtil {
const PI = 3.14159;
private static array $cache = [];
public static function fib(int $n): int {
return self::$cache[$n] ??= $n < 2 ? $n : self::fib($n - 1) + self::fib($n - 2);
}
public static function circle(float $r): float { return static::PI * $r * $r; }
}
echo MathUtil::fib(40), " ", MathUtil::circle(2), " ", MathUtil::PI, "\n";
class Config {
private static ?Config $instance = null;
private function __construct(public array $data = ["site" => "BCA Portal"]) {}
public static function get(): Config { return self::$instance ??= new Config(); }
}
var_dump(Config::get() === Config::get());
102334155 12.56636 3.14159
bool(true)
static members belong to the class, not to objects (ClassName::member; self:: inside). const defines class constants. The singleton pattern above (private constructor, static accessor) guarantees a single shared object, for example a database connection.
5.6 Exceptions
<?php
class InsufficientFunds extends RuntimeException {
public function __construct(public readonly float $short) { parent::__construct("short by $short"); }
}
function withdraw(float $balance, float $amt): float {
if ($amt <= 0) throw new InvalidArgumentException("amount must be positive");
if ($amt > $balance) throw new InsufficientFunds($amt - $balance);
return $balance - $amt;
}
foreach ([300, 5000, -1] as $amt) {
try {
echo "left: ", withdraw(1000, $amt), "\n";
} catch (InsufficientFunds $e) {
echo "refused: ", $e->getMessage(), "\n";
} catch (InvalidArgumentException | TypeError $e) {
echo "invalid: ", $e->getMessage(), "\n";
} finally {
echo "(checked $amt)\n";
}
}
left: 700
(checked 300)
left: refused: short by 4000
(checked 5000)
left: invalid: amount must be positive
(checked -1)
throw raises an exception object; catch handles it; finally always runs. Custom exceptions extend Exception/RuntimeException. PHP 7+ also throws Error (and TypeError, DivisionByZeroError) for engine problems.
5.7 Seeing the ideas: classes, objects and contracts
A class is a design and an object is one thing made from it, exactly as a cookie cutter is a shape and every biscuit pressed from it is a cookie.
The plate shows this everyday picture beside the PHP version, and the figures then show visibility, inheritance, interfaces and exceptions with the real results of PHP 8.2.35.

5.7.1 Eight advantages of object-oriented programming in PHP
- Data and actions stay together. A
Studentobject holds its marks and has its ownaverage()method. The code that works with marks is found in one place and not scattered across the page. A result system for a college stays easy to read as it grows. - Encapsulation protects data. Private properties can be changed only through methods that check the new value (Fig. 5.1). A mark of 150 can be refused inside
addMark(). No other script can break the rule by accident. - Inheritance avoids repeated code. A
Circleand aRecttake the shareddescribe()fromShapeand write only what is different (Fig. 5.2). A bug fixed in the parent is fixed for every child. A school system can reuse onePersonclass for students and teachers. - Polymorphism removes long
ifchains. The code calls$shape->area()and each object does the right thing. A new shape needs a new class and no change in the old code. This keeps a payment page open to new payment methods. - Interfaces give a contract. A class that implements
Payablepromises to have apay()method, so the rest of the program can use any such class. eSewa-style and bank-card payers can be exchanged without rewriting the checkout. This helps teams who work on different parts. - Traits share code across unrelated classes. A logging trait can be added to a
Wallet, anOrderand aReport(Fig. 5.3). PHP allows only one parent class, and traits remove that limit. The same code is not copied three times. - Exceptions separate errors from normal code. The normal path stays short, and the
catchblock handles the failure in one place (Fig. 5.4). Thefinallyblock closes files and connections in both cases. A failed fee payment can therefore be reported clearly to the user. - Modern frameworks are built on classes. Laravel, Symfony and WordPress plugins are all written with classes, namespaces and interfaces. A student who understands objects can read their code and extend it. This is also the way large Nepali software teams organise their projects.

Formula and fact sheet
class A extends B implements I { use T; };new A();$this->prop;A::STATIC;parent::/self::/static::.- Visibility
public/protected/private;readonly;final. abstract,interface,trait;__construct,__destruct,__toString.try { } catch (A | B $e) { } finally { }.
Chapter summary
- PHP objects bundle state and behaviour, with constructor promotion and visibility modifiers for encapsulation.
- Inheritance, abstract classes and interfaces give polymorphism and contracts; traits reuse code.
- Static members and the singleton pattern share state; exceptions make errors explicit.
Practice
- Write a class
Rectanglewith a constructor,area()andperimeter(). - Explain public, protected and private with an example.
- Demonstrate method overriding and
parent::with classesVehicleandCar. - Differentiate an abstract class and an interface.
- What is a trait and why does PHP need it?
- Write a static method counting how many objects of a class were created.
- Create a custom exception and catch it.
Answers and worked solutions
1.
<?php
class Rectangle {
public function __construct(private float $w, private float $h) {}
public function area(): float { return $this->w * $this->h; }
public function perimeter(): float { return 2 * ($this->w + $this->h); }
}
$r = new Rectangle(3, 5);
echo $r->area(), " ", $r->perimeter(), "\n";
15 16
2. public: anywhere; protected: the class and subclasses; private: only the declaring class (Section 5.2 shows private $balance rejected from outside).
3.
<?php
class Vehicle { public function describe(): string { return "a vehicle"; } }
class Car extends Vehicle {
public function describe(): string { return "a car, which is " . parent::describe(); }
}
echo (new Car)->describe(), "\n";
a car, which is a vehicle
4. An abstract class can have properties, constructor and implemented methods plus abstract ones, and a class extends only one. An interface has only method signatures (and constants), and a class can implement several.
5. A trait is a group of methods/properties that can be inserted into several unrelated classes with use. Because PHP allows only one parent class, traits share code horizontally.
6.
<?php
class Widget {
private static int $made = 0;
public function __construct() { self::$made++; }
public static function made(): int { return self::$made; }
}
new Widget; new Widget; new Widget;
echo Widget::made(), "\n";
3
7. See InsufficientFunds in Section 5.6: extend RuntimeException, throw it, and catch it by class.
Model answers (5 and 10 marks)
Q1. Explain classes, objects, constructors and the visibility modifiers of PHP with a program. (10 marks)
Introduction. Object-oriented programming (OOP) models a program as a set of objects, each having data (properties) and actions (methods).
A class is the design, like a cookie cutter, and an object is one thing made from it with new (Plate 5.1). PHP has supported a full object model since PHP 5, and PHP 8 added constructor promotion and stronger types.
Main parts of a class.
- Properties are the variables of an object, such as
$nameand$marks. They may have a type:private array $marks. - Methods are the functions of the class. Inside a method,
$thismeans the current object. - Constructor
__construct()runs automatically when an object is made and sets the first state. PHP 8 lets the parameters become properties directly (constructor promotion). - Destructor
__destruct()runs when the object is removed, for example byunset()or at the end of the script; it is used to close files. - Static members (
static) belong to the class, not to an object, and are reached withself::orClassName::; one counter is shared by all objects. - Constants are declared with
constand read withClassName::NAME.
Visibility.
| Modifier | Class itself | Subclass | Outside code |
|---|---|---|---|
public |
yes | yes | yes |
protected |
yes | yes | no |
private |
yes | no | no |
Real messages from PHP (Fig. 5.1): outside code gets Cannot access protected property A::$pro and Cannot access private property A::$pri. Hiding data behind methods is encapsulation; it lets the class check every change.
<?php
class Student {
public static int $count = 0;
public function __construct(private string $name, private array $marks = []) {
self::$count++;
}
public function addMark(int $m): static {
if ($m < 0 || $m > 100) throw new InvalidArgumentException("mark $m is out of range");
$this->marks[] = $m;
return $this;
}
public function average(): float {
return $this->marks ? array_sum($this->marks) / count($this->marks) : 0.0;
}
public function __toString(): string { return sprintf("%s (avg %.1f)", $this->name, $this->average()); }
}
$s = new Student("Sita");
$s->addMark(88)->addMark(92)->addMark(75);
$t = new Student("Ram", [70, 64]);
echo $s, " | ", $t, " | objects made: ", Student::$count, "\n";
try { $s->addMark(150); } catch (InvalidArgumentException $e) { echo "refused: ", $e->getMessage(), "\n"; }
try { echo $s->marks; } catch (Error $e) { echo "Error: ", $e->getMessage(), "\n"; }
Sita (avg 85.0) | Ram (avg 67.0) | objects made: 2
refused: mark 150 is out of range
Error: Cannot access private property Student::$marks
Reading the run. Two objects were made, so the shared static counter shows 2. The mark 150 was refused by the method before it could enter the object, and direct access to the private property marks produced an Error.
This is what makes a class a safe unit: its rules are enforced in one place, whoever uses it, and a result system for a college can trust that every stored mark is between 0 and 100.
Conclusion. A class bundles data and behaviour; the constructor builds a valid object; visibility keeps the data safe; static members hold what belongs to the whole class. Together they make larger programs organised, testable and easier to change.
Q2. What is inheritance? Explain method overriding and polymorphism with an example. (5 marks)
Inheritance lets a new class (the child) take the properties and methods of an existing class (the parent) with the keyword extends. PHP supports single inheritance only: a class has one parent.
- Reuse. The child gets all
publicandprotectedmembers of the parent and adds its own (Fig. 5.2). - Overriding. The child may define a method with the same name to replace the parent's version;
parent::method()still calls the old one. - Polymorphism ("many forms"): code that calls
$shape->area()works with any child, and each object runs its own version. finalon a class or method forbids extending or overriding it.instanceoftests whether an object belongs to a class or interface.
<?php
abstract class Shape {
abstract public function area(): float;
public function describe(): string { return sprintf("%s area %.2f", static::class, $this->area()); }
}
class Circle extends Shape {
public function __construct(private float $r) {}
public function area(): float { return M_PI * $this->r ** 2; }
}
class Rect extends Shape {
public function __construct(private float $w, private float $h) {}
public function area(): float { return $this->w * $this->h; }
}
$total = 0;
foreach ([new Circle(7), new Rect(4, 5), new Rect(3, 3)] as $s) { echo $s->describe(), "\n"; $total += $s->area(); }
printf("total %.2f\n", $total);
try { new Shape; } catch (Error $e) { echo $e->getMessage(), "\n"; }
Circle area 153.94
Rect area 20.00
Rect area 9.00
total 182.94
Cannot instantiate abstract class Shape
The child classes keep only what is different (the formula), while the parent keeps what is common (the description). Overriding means a child supplies its own version of a parent's method.
One loop handles three objects of different classes, which is why a new shape can be added without changing the loop. The abstract parent cannot be instantiated, as the last line shows.
Q3. Differentiate between abstract class, interface and trait in PHP. When is each used? Give an example. (10 marks)
Introduction. PHP allows only one parent class, so it offers three tools for sharing design: the abstract class, the interface and the trait.
They answer different questions: what is the common base, what must a class be able to do, and how do unrelated classes share code.
1. Abstract class. Declared with abstract class. It may contain abstract methods (no body) that children must write, finished methods and properties. It cannot be instantiated. A class can extend only one.
Use it when the children are variations of one idea, such as Shape.
2. Interface. Declared with interface. It contains only method signatures and constants. A class that implements it must define every method. A class may implement many interfaces.
Use it for a contract that unrelated classes can satisfy, for example Payable, Countable.
3. Trait. Declared with trait. It holds finished methods and properties that are copied into any class with use. It removes the limit of single inheritance for code reuse, but a trait is not a type, so instanceof does not work for it.
| Point | Abstract class | Interface | Trait |
|---|---|---|---|
| Code inside | partly | none | yes |
| Properties | yes | constants only | yes |
| Per class | one (extends) |
many (implements) |
many (use) |
| Object can be made | no | no | no |
| Purpose | common base | contract | code reuse |
<?php
interface Payable { public function pay(float $a): string; }
trait Logs {
private array $log = [];
public function note(string $m): void { $this->log[] = $m; }
public function count(): int { return count($this->log); }
}
class Wallet implements Payable {
use Logs;
public function pay(float $a): string { $this->note("paid $a"); return "paid $a"; }
}
$w = new Wallet;
echo $w->pay(50), " | ", $w->pay(25.5), " | log size ", $w->count(), "\n";
var_dump($w instanceof Payable);
paid 50 | paid 25.5 | log size 2
bool(true)
Reading the example (Fig. 5.3). Wallet promises to be Payable, and gets its logging code from the trait. Any code that needs a Payable accepts the wallet, a bank card class or a cash class alike.
In a real project the choice is often made by a simple test. If the classes are kinds of the same thing, extend a base class. If different kinds of things must be used in the same way, give them one interface.
If only a few lines of code are repeated, put them in a trait.
Conclusion. Choose an abstract class for a shared base with partly finished code, an interface for a pure promise of behaviour, and a trait to reuse code in unrelated classes. Modern PHP code often uses all three together.
Q4. Explain exception handling in PHP with try, catch, finally and a custom exception. (5 marks)
An exception is an object that signals an error and interrupts the normal flow; it is thrown with throw and caught by a matching catch. It keeps error handling separate from the normal code (Fig. 5.4).
- try holds the code that may fail.
- catch (
Type $e) handles one kind of exception; severalcatchblocks, orA | B, are allowed, and the most specific type must come first. - finally runs in every case, with or without an error, and is used for cleanup.
- Custom exceptions extend
Exception, as inclass InsufficientFunds extends Exception {}; they are alsoThrowable. - Methods:
getMessage(),getCode(),getLine()andgetTraceAsString().
<?php
class InsufficientFunds extends Exception {}
function draw(int $balance, int $amount): string {
try {
if ($amount > $balance) throw new InsufficientFunds("need $amount, have $balance");
echo "ok | ";
return "done";
} catch (InsufficientFunds $e) {
echo "caught: ", $e->getMessage(), " | ";
return "failed";
} finally {
echo "finally runs | ";
}
}
echo draw(100, 40), "\n", draw(100, 400), "\n";
var_dump(new InsufficientFunds instanceof Throwable);
ok | finally runs | done
caught: need 400, have 100 | finally runs | failed
bool(true)
The finally line printed in both runs, even though the function had already decided to return.
A custom exception also makes the code easier to read, because catch (InsufficientFunds $e) says exactly what went wrong, while a bare catch (Exception $e) would hide the difference between a low balance and a broken database.
Errors a script cannot repair should be caught once, near the top, logged, and shown to the visitor as a short polite message.