Learn fundamental OOP concepts - classes, objects, properties, methods, and encapsulation to build structured, maintainable applications
Object-Oriented Programming (OOP) is a programming paradigm that organizes code into objects - self-contained units that combine data (properties) and behavior (methods). OOP helps you build more maintainable, reusable, and scalable applications by modeling real-world entities and their relationships.
PHP has robust OOP support with features comparable to languages like Java and C#. Modern PHP development heavily relies on OOP principles, from frameworks like Laravel and Symfony to design patterns and best practices. Understanding OOP is essential for professional PHP development.
In this lesson, you'll learn the foundational OOP concepts: how to create classes and objects, define properties and methods, control access with visibility modifiers, and implement encapsulation. You'll build practical examples that demonstrate how OOP makes code more organized and easier to maintain. These concepts form the foundation for more advanced topics like inheritance, polymorphism, and design patterns.
Classes are blueprints that define the structure and behavior of objects. Objects are instances of classes - concrete realizations with actual data.
Defining a Class:
1class User {
2 // Properties (data)
3 public string $name;
4 public string $email;
5 public int $age;
6
7 // Methods (behavior)
8 public function greet(): string {
9 return "Hello, my name is {$this->name}";
10 }
11
12 public function isAdult(): bool {
13 return $this->age >= 18;
14 }
15}Creating Objects:
Objects are created using the new keyword:
1// Create an object (instance)
2$user = new User();
3
4// Set properties
5$user->name = "John Doe";
6$user->email = "[email protected]";
7$user->age = 30;
8
9// Call methods
10echo $user->greet(); // "Hello, my name is John Doe"
11echo $user->isAdult() ? "Adult" : "Minor"; // "Adult"Multiple Objects from Same Class:
Each object has its own copy of the properties:
1$user1 = new User();
2$user1->name = "Alice";
3$user1->age = 25;
4
5$user2 = new User();
6$user2->name = "Bob";
7$user2->age = 35;
8
9echo $user1->greet(); // "Hello, my name is Alice"
10echo $user2->greet(); // "Hello, my name is Bob"The $this Keyword:
Inside methods, $this refers to the current object:
1class Product {
2 public string $name;
3 public float $price;
4
5 public function applyDiscount(float $percentage): float {
6 // $this->price refers to this object's price
7 return $this->price * (1 - $percentage);
8 }
9
10 public function displayInfo(): string {
11 // Access multiple properties using $this
12 return "{$this->name}: \${$this->price}";
13 }
14}Type Declarations in Classes:
Modern PHP supports typed properties and methods:
1class BankAccount {
2 public string $accountNumber;
3 public float $balance;
4 public bool $isActive;
5
6 public function deposit(float $amount): void {
7 $this->balance += $amount;
8 }
9
10 public function getBalance(): float {
11 return $this->balance;
12 }
13}Class vs Object:
Think of a class as a cookie cutter and objects as the actual cookies made from it.
Properties store object state (data), while methods define object behavior (actions). Together, they create cohesive, self-contained units.
Property Types:
1class Employee {
2 // Typed properties (PHP 7.4+)
3 public string $firstName;
4 public string $lastName;
5 public int $employeeId;
6 public float $salary;
7 public bool $isFullTime;
8 public ?string $department = null; // Nullable
9
10 // Array and object properties
11 public array $skills = [];
12 public DateTime $hireDate;
13}Property Default Values:
1class Configuration {
2 public string $environment = 'development';
3 public int $timeout = 30;
4 public bool $debug = true;
5 public array $options = [];
6}
7
8$config = new Configuration();
9echo $config->environment; // "development"Method Types:
1class Calculator {
2 // Regular method
3 public function add(int $a, int $b): int {
4 return $a + $b;
5 }
6
7 // Method with no return value
8 public function logOperation(string $operation): void {
9 echo "Operation: $operation\n";
10 }
11
12 // Method returning $this for chaining
13 public function reset(): self {
14 // Reset state
15 return $this;
16 }
17
18 // Method with default parameters
19 public function multiply(int $a, int $b = 1): int {
20 return $a * $b;
21 }
22}Method Chaining:
Methods that return $this enable fluent interfaces:
1class QueryBuilder {
2 private string $query = "";
3
4 public function select(string $fields): self {
5 $this->query = "SELECT $fields ";
6 return $this;
7 }
8
9 public function from(string $table): self {
10 $this->query .= "FROM $table ";
11 return $this;
12 }
13
14 public function where(string $condition): self {
15 $this->query .= "WHERE $condition";
16 return $this;
17 }
18
19 public function getQuery(): string {
20 return $this->query;
21 }
22}
23
24// Chained method calls
25$query = (new QueryBuilder())
26 ->select('*')
27 ->from('users')
28 ->where('age > 18')
29 ->getQuery();Static vs Instance:
1class MathHelper {
2 // Static property - shared across all instances
3 public static int $calculationCount = 0;
4
5 // Instance property - unique per object
6 public float $lastResult;
7
8 // Static method - called on class, not object
9 public static function square(float $n): float {
10 self::$calculationCount++;
11 return $n * $n;
12 }
13
14 // Instance method - called on object
15 public function saveResult(float $result): void {
16 $this->lastResult = $result;
17 }
18}
19
20// Static method call
21$squared = MathHelper::square(5); // 25
22echo MathHelper::$calculationCount; // 1
23
24// Instance method call
25$helper = new MathHelper();
26$helper->saveResult(25);
27echo $helper->lastResult; // 25Property and Method Documentation:
1class Article {
2 /**
3 * Article title
4 */
5 public string $title;
6
7 /**
8 * Publication date
9 */
10 public DateTime $publishedAt;
11
12 /**
13 * Check if article is published
14 *
15 * @return bool True if published, false otherwise
16 */
17 public function isPublished(): bool {
18 return $this->publishedAt <= new DateTime();
19 }
20
21 /**
22 * Get article excerpt
23 *
24 * @param int $length Maximum length of excerpt
25 * @return string Truncated content
26 */
27 public function getExcerpt(int $length = 100): string {
28 // Implementation
29 }
30}Visibility modifiers control access to properties and methods, implementing encapsulation - one of OOP's core principles. They protect object integrity by hiding internal implementation details.
Three Visibility Levels:
1class BankAccount {
2 // Public - accessible everywhere
3 public string $accountNumber;
4
5 // Protected - accessible in this class and child classes
6 protected float $balance = 0;
7
8 // Private - accessible only in this class
9 private string $pin;
10
11 public function getBalance(): float {
12 return $this->balance; // OK - same class
13 }
14}
15
16$account = new BankAccount();
17$account->accountNumber = "123456"; // OK - public
18
19// $account->balance = 1000; // ERROR - protected
20// $account->pin = "1234"; // ERROR - privateWhy Use Private/Protected?
Encapsulation prevents direct manipulation of internal state:
1class Temperature {
2 private float $celsius;
3
4 // Controlled access through public methods
5 public function setCelsius(float $temp): void {
6 if ($temp < -273.15) {
7 throw new InvalidArgumentException(
8 "Temperature cannot be below absolute zero"
9 );
10 }
11 $this->celsius = $temp;
12 }
13
14 public function getCelsius(): float {
15 return $this->celsius;
16 }
17
18 public function getFahrenheit(): float {
19 return ($this->celsius * 9/5) + 32;
20 }
21}
22
23$temp = new Temperature();
24$temp->setCelsius(25); // Validated
25echo $temp->getFahrenheit(); // 77
26
27// $temp->celsius = -500; // ERROR - private, validation bypassedGetters and Setters:
Control property access with methods:
1class User {
2 private string $email;
3 private int $age;
4 private array $roles = [];
5
6 // Getter - read access
7 public function getEmail(): string {
8 return $this->email;
9 }
10
11 // Setter with validation
12 public function setEmail(string $email): void {
13 if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
14 throw new InvalidArgumentException("Invalid email");
15 }
16 $this->email = strtolower($email);
17 }
18
19 public function getAge(): int {
20 return $this->age;
21 }
22
23 public function setAge(int $age): void {
24 if ($age < 0 || $age > 150) {
25 throw new InvalidArgumentException("Invalid age");
26 }
27 $this->age = $age;
28 }
29
30 // Getter with computed value
31 public function isAdult(): bool {
32 return $this->age >= 18;
33 }
34
35 // Controlled array access
36 public function addRole(string $role): void {
37 if (!in_array($role, $this->roles)) {
38 $this->roles[] = $role;
39 }
40 }
41
42 public function getRoles(): array {
43 return $this->roles;
44 }
45
46 public function hasRole(string $role): bool {
47 return in_array($role, $this->roles);
48 }
49}Property Promotion (PHP 8+):
Cleaner syntax for constructor properties:
1// Traditional way
2class Point {
3 private float $x;
4 private float $y;
5
6 public function __construct(float $x, float $y) {
7 $this->x = $x;
8 $this->y = $y;
9 }
10}
11
12// PHP 8+ property promotion
13class Point {
14 public function __construct(
15 private float $x,
16 private float $y
17 ) {}
18
19 public function getX(): float {
20 return $this->x;
21 }
22
23 public function getY(): float {
24 return $this->y;
25 }
26}Read-Only Properties (PHP 8.1+):
1class Product {
2 public function __construct(
3 public readonly string $id,
4 public readonly string $name,
5 public float $price // Mutable
6 ) {}
7}
8
9$product = new Product('P001', 'Laptop', 999);
10$product->price = 899; // OK - not readonly
11
12// $product->id = 'P002'; // ERROR - readonly
13// $product->name = 'Desktop'; // ERROR - readonlyVisibility Best Practices:
Constructors initialize objects when they're created. Destructors clean up when objects are destroyed. They manage the complete object lifecycle.
Constructor Basics:
The __construct() method runs automatically when creating objects:
1class User {
2 private string $name;
3 private string $email;
4 private DateTime $createdAt;
5
6 public function __construct(string $name, string $email) {
7 $this->name = $name;
8 $this->email = $email;
9 $this->createdAt = new DateTime();
10 }
11
12 public function getInfo(): string {
13 return "{$this->name} ({$this->email})";
14 }
15}
16
17// Constructor is called automatically
18$user = new User("John Doe", "[email protected]");
19echo $user->getInfo();Constructor with Default Values:
1class Configuration {
2 private string $environment;
3 private bool $debug;
4 private int $timeout;
5
6 public function __construct(
7 string $environment = 'production',
8 bool $debug = false,
9 int $timeout = 30
10 ) {
11 $this->environment = $environment;
12 $this->debug = $debug;
13 $this->timeout = $timeout;
14 }
15}
16
17$config1 = new Configuration(); // All defaults
18$config2 = new Configuration('development', true);
19$config3 = new Configuration(timeout: 60); // Named argumentsConstructor with Validation:
1class BankAccount {
2 private string $accountNumber;
3 private float $balance;
4
5 public function __construct(string $accountNumber, float $initialBalance) {
6 if ($initialBalance < 0) {
7 throw new InvalidArgumentException(
8 "Initial balance cannot be negative"
9 );
10 }
11
12 if (strlen($accountNumber) !== 10) {
13 throw new InvalidArgumentException(
14 "Account number must be 10 digits"
15 );
16 }
17
18 $this->accountNumber = $accountNumber;
19 $this->balance = $initialBalance;
20 }
21}
22
23// Valid
24$account = new BankAccount("1234567890", 1000);
25
26// Throws exception
27// $account = new BankAccount("123", -500);Property Promotion (PHP 8):
Declare and assign properties directly in constructor:
1class Product {
2 public function __construct(
3 private string $id,
4 private string $name,
5 private float $price,
6 private int $stock = 0
7 ) {
8 // Properties are automatically created and assigned
9 // Additional initialization logic can go here
10 if ($price < 0) {
11 throw new InvalidArgumentException("Price cannot be negative");
12 }
13 }
14
15 public function getName(): string {
16 return $this->name;
17 }
18
19 public function getPrice(): float {
20 return $this->price;
21 }
22}Destructor:
The __destruct() method runs when an object is destroyed:
1class DatabaseConnection {
2 private $connection;
3
4 public function __construct(string $host, string $database) {
5 $this->connection = new PDO(
6 "mysql:host=$host;dbname=$database",
7 "username",
8 "password"
9 );
10 echo "Database connected\n";
11 }
12
13 public function __destruct() {
14 $this->connection = null;
15 echo "Database connection closed\n";
16 }
17}
18
19// Constructor called
20$db = new DatabaseConnection("localhost", "mydb");
21
22// ... use connection ...
23
24// Destructor automatically called when:
25// 1. Script ends
26// 2. Object is explicitly unset: unset($db)
27// 3. Object goes out of scopePractical Destructor Use Cases:
1class FileLogger {
2 private $fileHandle;
3
4 public function __construct(string $filename) {
5 $this->fileHandle = fopen($filename, 'a');
6 }
7
8 public function log(string $message): void {
9 fwrite($this->fileHandle, date('Y-m-d H:i:s') . " - $message\n");
10 }
11
12 public function __destruct() {
13 if ($this->fileHandle) {
14 fclose($this->fileHandle);
15 }
16 }
17}
18
19class CacheManager {
20 private array $cache = [];
21 private bool $modified = false;
22
23 public function set(string $key, mixed $value): void {
24 $this->cache[$key] = $value;
25 $this->modified = true;
26 }
27
28 public function __destruct() {
29 // Save cache to disk if modified
30 if ($this->modified) {
31 file_put_contents('cache.json', json_encode($this->cache));
32 }
33 }
34}Constructor Best Practices:
Building a Complete Task Management System
1<?php
2declare(strict_types=1);
3
4/**
5 * Task Management System
6 *
7 * Demonstrates OOP basics:
8 * - Class and object creation
9 * - Properties with visibility modifiers
10 * - Constructor with validation
11 * - Methods for behavior
12 * - Encapsulation with getters/setters
13 * - Object lifecycle management
14 */
15
16class Task {
17 private string $id;
18 private string $title;
19 private string $description;
20 private string $status;
21 private int $priority;
22 private ?DateTime $dueDate;
23 private DateTime $createdAt;
24 private ?DateTime $completedAt;
25
26 private const STATUS_PENDING = 'pending';
27 private const STATUS_IN_PROGRESS = 'in_progress';
28 private const STATUS_COMPLETED = 'completed';
29
30 private const VALID_STATUSES = [
31 self::STATUS_PENDING,
32 self::STATUS_IN_PROGRESS,
33 self::STATUS_COMPLETED
34 ];
35
36 public function __construct(
37 string $title,
38 string $description = '',
39 int $priority = 3,
40 ?DateTime $dueDate = null
41 ) {
42 if (empty(trim($title))) {
43 throw new InvalidArgumentException("Title cannot be empty");
44 }
45
46 if ($priority < 1 || $priority > 5) {
47 throw new InvalidArgumentException("Priority must be 1-5");
48 }
49
50 $this->id = uniqid('task_');
51 $this->title = $title;
52 $this->description = $description;
53 $this->status = self::STATUS_PENDING;
54 $this->priority = $priority;
55 $this->dueDate = $dueDate;
56 $this->createdAt = new DateTime();
57 $this->completedAt = null;
58 }
59
60 // Getters
61 public function getId(): string {
62 return $this->id;
63 }
64
65 public function getTitle(): string {
66 return $this->title;
67 }
68
69 public function getDescription(): string {
70 return $this->description;
71 }
72
73 public function getStatus(): string {
74 return $this->status;
75 }
76
77 public function getPriority(): int {
78 return $this->priority;
79 }
80
81 public function getDueDate(): ?DateTime {
82 return $this->dueDate;
83 }
84
85 // Setters with validation
86 public function setTitle(string $title): void {
87 if (empty(trim($title))) {
88 throw new InvalidArgumentException("Title cannot be empty");
89 }
90 $this->title = $title;
91 }
92
93 public function setDescription(string $description): void {
94 $this->description = $description;
95 }
96
97 public function setPriority(int $priority): void {
98 if ($priority < 1 || $priority > 5) {
99 throw new InvalidArgumentException("Priority must be 1-5");
100 }
101 $this->priority = $priority;
102 }
103
104 // Status management
105 public function start(): void {
106 if ($this->status === self::STATUS_COMPLETED) {
107 throw new LogicException("Cannot start a completed task");
108 }
109 $this->status = self::STATUS_IN_PROGRESS;
110 }
111
112 public function complete(): void {
113 $this->status = self::STATUS_COMPLETED;
114 $this->completedAt = new DateTime();
115 }
116
117 public function reopen(): void {
118 if ($this->status !== self::STATUS_COMPLETED) {
119 throw new LogicException("Can only reopen completed tasks");
120 }
121 $this->status = self::STATUS_PENDING;
122 $this->completedAt = null;
123 }
124
125 // Computed properties
126 public function isOverdue(): bool {
127 if ($this->dueDate === null || $this->status === self::STATUS_COMPLETED) {
128 return false;
129 }
130 return $this->dueDate < new DateTime();
131 }
132
133 public function getDaysUntilDue(): ?int {
134 if ($this->dueDate === null) {
135 return null;
136 }
137 $now = new DateTime();
138 return $now->diff($this->dueDate)->days * ($this->dueDate > $now ? 1 : -1);
139 }
140
141 public function getPriorityLabel(): string {
142 return match($this->priority) {
143 1 => 'Critical',
144 2 => 'High',
145 3 => 'Medium',
146 4 => 'Low',
147 5 => 'Very Low',
148 default => 'Unknown'
149 };
150 }
151
152 // Display methods
153 public function getSummary(): string {
154 $summary = "[{$this->status}] {$this->title}";
155
156 if ($this->isOverdue()) {
157 $summary .= " (OVERDUE)";
158 }
159
160 return $summary;
161 }
162
163 public function getFullInfo(): array {
164 return [
165 'id' => $this->id,
166 'title' => $this->title,
167 'description' => $this->description,
168 'status' => $this->status,
169 'priority' => $this->priority,
170 'priority_label' => $this->getPriorityLabel(),
171 'due_date' => $this->dueDate?->format('Y-m-d'),
172 'days_until_due' => $this->getDaysUntilDue(),
173 'is_overdue' => $this->isOverdue(),
174 'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
175 'completed_at' => $this->completedAt?->format('Y-m-d H:i:s')
176 ];
177 }
178}
179
180class TaskManager {
181 private array $tasks = [];
182
183 public function addTask(Task $task): void {
184 $this->tasks[$task->getId()] = $task;
185 }
186
187 public function removeTask(string $id): bool {
188 if (isset($this->tasks[$id])) {
189 unset($this->tasks[$id]);
190 return true;
191 }
192 return false;
193 }
194
195 public function getTask(string $id): ?Task {
196 return $this->tasks[$id] ?? null;
197 }
198
199 public function getAllTasks(): array {
200 return array_values($this->tasks);
201 }
202
203 public function getTasksByStatus(string $status): array {
204 return array_filter(
205 $this->tasks,
206 fn($task) => $task->getStatus() === $status
207 );
208 }
209
210 public function getOverdueTasks(): array {
211 return array_filter(
212 $this->tasks,
213 fn($task) => $task->isOverdue()
214 );
215 }
216
217 public function getHighPriorityTasks(): array {
218 $high = array_filter(
219 $this->tasks,
220 fn($task) => $task->getPriority() <= 2
221 );
222
223 usort($high, fn($a, $b) => $a->getPriority() <=> $b->getPriority());
224
225 return $high;
226 }
227
228 public function getStatistics(): array {
229 $total = count($this->tasks);
230 $completed = count($this->getTasksByStatus('completed'));
231 $in_progress = count($this->getTasksByStatus('in_progress'));
232 $pending = count($this->getTasksByStatus('pending'));
233 $overdue = count($this->getOverdueTasks());
234
235 return compact('total', 'completed', 'in_progress', 'pending', 'overdue');
236 }
237}
238
239// ==================== DEMONSTRATION ====================
240
241echo "š TASK MANAGEMENT SYSTEM\n";
242echo str_repeat("=", 60) . "\n\n";
243
244$manager = new TaskManager();
245
246// Create tasks
247$task1 = new Task(
248 "Complete project proposal",
249 "Write and submit Q1 project proposal",
250 1, // Critical priority
251 new DateTime('+3 days')
252);
253
254$task2 = new Task(
255 "Review pull requests",
256 "Review pending PRs in main repository",
257 2, // High priority
258 new DateTime('+1 day')
259);
260
261$task3 = new Task(
262 "Update documentation",
263 "Update API documentation with new endpoints",
264 3 // Medium priority
265);
266
267$task4 = new Task(
268 "Fix bug #123",
269 "User login fails on mobile",
270 1, // Critical
271 new DateTime('-1 day') // Overdue!
272);
273
274// Add to manager
275$manager->addTask($task1);
276$manager->addTask($task2);
277$manager->addTask($task3);
278$manager->addTask($task4);
279
280echo "Created 4 tasks\n\n";
281
282// Update task statuses
283$task2->start();
284$task3->start();
285$task3->complete();
286
287echo "š Statistics:\n";
288$stats = $manager->getStatistics();
289foreach ($stats as $key => $value) {
290 echo " " . ucwords(str_replace('_', ' ', $key)) . ": $value\n";
291}
292
293echo "\nā ļø High Priority Tasks:\n";
294foreach ($manager->getHighPriorityTasks() as $task) {
295 echo " - " . $task->getSummary() . "\n";
296}
297
298echo "\nš“ Overdue Tasks:\n";
299foreach ($manager->getOverdueTasks() as $task) {
300 $info = $task->getFullInfo();
301 echo " - {$info['title']} (Due: {$info['due_date']})\n";
302}
303
304echo "\nš All Tasks:\n";
305foreach ($manager->getAllTasks() as $task) {
306 $info = $task->getFullInfo();
307 echo " [{$info['priority_label']}] {$info['title']}\n";
308 echo " Status: {$info['status']}\n";
309 if ($info['due_date']) {
310 echo " Due: {$info['due_date']}";
311 if ($info['is_overdue']) {
312 echo " (OVERDUE!)";
313 }
314 echo "\n";
315 }
316 echo "\n";
317}
318
319echo str_repeat("=", 60) . "\n";
320echo "ā
Demo Complete!\n";
321?>This task management system demonstrates professional OOP design:
Encapsulation:
Constructor Design:
Business Logic in Methods:
start(), complete(), reopen() manage state transitionsComputed Properties:
isOverdue() calculates status dynamicallygetDaysUntilDue() derives value from stored datagetPriorityLabel() provides human-readable labelsInformation Hiding:
Composition:
Type Safety:
Real-World Patterns:
Benefits of This Design:
Your progress is saved in this browser, on this device only.