Inheritance and Polymorphism in PHP
Inheritance and polymorphism are fundamental pillars of object-oriented programming that enable code reuse, extensibility, and flexible design. This lesson explores how PHP implements these concepts and how to use them effectively in modern applications.
PHP supports single inheritance (a class can extend one parent class) and multiple interface implementation (a class can implement many interfaces). This combination provides powerful mechanisms for creating flexible, maintainable code hierarchies.
Inheritance allows a class to inherit properties and methods from a parent class, promoting code reuse and establishing hierarchical relationships.
Basic Inheritance:
1<?php
2declare(strict_types=1);
3
4// Parent class
5class Animal {
6 protected string $name;
7 protected int $age;
8
9 public function __construct(string $name, int $age) {
10 $this->name = $name;
11 $this->age = $age;
12 }
13
14 public function eat(): void {
15 echo "{$this->name} is eating.\n";
16 }
17
18 public function sleep(): void {
19 echo "{$this->name} is sleeping.\n";
20 }
21
22 public function getInfo(): string {
23 return "{$this->name} ({$this->age} years old)";
24 }
25}
26
27// Child class inheriting from Animal
28class Dog extends Animal {
29 private string $breed;
30
31 public function __construct(string $name, int $age, string $breed) {
32 parent::__construct($name, $age); // Call parent constructor
33 $this->breed = $breed;
34 }
35
36 public function bark(): void {
37 echo "{$this->name} says: Woof! Woof!\n";
38 }
39
40 public function getInfo(): string {
41 return parent::getInfo() . ", Breed: {$this->breed}";
42 }
43}
44
45$dog = new Dog("Max", 3, "Golden Retriever");
46$dog->eat(); // Inherited method
47$dog->bark(); // Own method
48echo $dog->getInfo(); // Overridden method
49?>Key Concepts:
The parent Keyword:
1<?php
2class Product {
3 protected float $basePrice;
4 protected float $taxRate = 0.08;
5
6 public function __construct(float $price) {
7 $this->basePrice = $price;
8 }
9
10 public function getPrice(): float {
11 return $this->basePrice * (1 + $this->taxRate);
12 }
13}
14
15class DigitalProduct extends Product {
16 private float $downloadFee = 2.99;
17
18 public function getPrice(): float {
19 // Call parent implementation and add download fee
20 return parent::getPrice() + $this->downloadFee;
21 }
22}
23
24$ebook = new DigitalProduct(19.99);
25echo "Total price: $" . number_format($ebook->getPrice(), 2);
26// Output: Total price: $24.58 (19.99 * 1.08 + 2.99)
27?>Constructor Inheritance:
1<?php
2class User {
3 protected string $name;
4 protected string $email;
5
6 public function __construct(string $name, string $email) {
7 $this->name = $name;
8 $this->email = $email;
9 }
10}
11
12class AdminUser extends User {
13 private array $permissions;
14
15 public function __construct(
16 string $name,
17 string $email,
18 array $permissions = []
19 ) {
20 parent::__construct($name, $email);
21 $this->permissions = $permissions;
22 }
23
24 public function hasPermission(string $permission): bool {
25 return in_array($permission, $this->permissions);
26 }
27}
28?>Property Promotion with Inheritance (PHP 8):
1<?php
2class Shape {
3 public function __construct(
4 protected string $color
5 ) {}
6
7 public function getColor(): string {
8 return $this->color;
9 }
10}
11
12class Circle extends Shape {
13 public function __construct(
14 string $color,
15 private float $radius
16 ) {
17 parent::__construct($color);
18 }
19
20 public function getArea(): float {
21 return pi() * $this->radius ** 2;
22 }
23}
24?>Method overriding allows child classes to provide specific implementations of inherited methods, enabling polymorphic behavior.
Basic Method Overriding:
1<?php
2declare(strict_types=1);
3
4class PaymentMethod {
5 protected string $name;
6
7 public function __construct(string $name) {
8 $this->name = $name;
9 }
10
11 public function process(float $amount): string {
12 return "Processing ${amount} payment via {$this->name}";
13 }
14
15 public function getFeePe rcentage(): float {
16 return 0.0;
17 }
18}
19
20class CreditCard extends PaymentMethod {
21 public function __construct(
22 private string $cardNumber,
23 private string $expiryDate
24 ) {
25 parent::__construct('Credit Card');
26 }
27
28 public function process(float $amount): string {
29 $fee = $amount * $this->getFeePercentage();
30 $total = $amount + $fee;
31
32 return parent::process($total) . " (Card: " .
33 $this->maskCardNumber() . ")";
34 }
35
36 public function getFeePercentage(): float {
37 return 0.029; // 2.9% fee
38 }
39
40 private function maskCardNumber(): string {
41 return "****-****-****-" . substr($this->cardNumber, -4);
42 }
43}
44
45class PayPal extends PaymentMethod {
46 public function __construct(private string $email) {
47 parent::__construct('PayPal');
48 }
49
50 public function process(float $amount): string {
51 $fee = $amount * $this->getFeePercentage();
52 $total = $amount + $fee;
53
54 return parent::process($total) . " (PayPal: {$this->email})";
55 }
56
57 public function getFeePercentage(): float {
58 return 0.034; // 3.4% fee
59 }
60}
61
62// Polymorphism in action
63function processPayment(PaymentMethod $method, float $amount): void {
64 echo $method->process($amount) . "\n";
65 echo "Fee: " . ($method->getFeePercentage() * 100) . "%\n\n";
66}
67
68$creditCard = new CreditCard('1234567890123456', '12/25');
69$paypal = new PayPal('[email protected]');
70
71processPayment($creditCard, 100.00);
72processPayment($paypal, 100.00);
73?>Type Hinting and Polymorphism:
1<?php
2class Logger {
3 public function log(string $message): void {
4 echo "[LOG] $message\n";
5 }
6}
7
8class FileLogger extends Logger {
9 public function __construct(private string $filename) {}
10
11 public function log(string $message): void {
12 // Override to write to file
13 $timestamp = date('Y-m-d H:i:s');
14 $logEntry = "[$timestamp] $message\n";
15 file_put_contents($this->filename, $logEntry, FILE_APPEND);
16 }
17}
18
19class DatabaseLogger extends Logger {
20 public function __construct(private $db) {}
21
22 public function log(string $message): void {
23 // Override to write to database
24 $stmt = $this->db->prepare(
25 "INSERT INTO logs (message, created_at) VALUES (?, NOW())"
26 );
27 $stmt->execute([$message]);
28 }
29}
30
31class Application {
32 public function __construct(private Logger $logger) {}
33
34 public function run(): void {
35 $this->logger->log("Application started");
36 // Doesn't care which logger implementation is used
37 }
38}
39
40// Can inject any Logger subclass
41$app1 = new Application(new Logger());
42$app2 = new Application(new FileLogger('app.log'));
43$app3 = new Application(new DatabaseLogger($pdo));
44?>Covariant Return Types (PHP 7.4+):
1<?php
2class Factory {
3 public function create(): Product {
4 return new Product();
5 }
6}
7
8class SpecializedFactory extends Factory {
9 // Covariant return type - can return more specific type
10 public function create(): SpecialProduct {
11 return new SpecialProduct();
12 }
13}
14
15class Product {}
16class SpecialProduct extends Product {}
17?>Abstract classes provide templates for derived classes, defining methods that must be implemented while optionally providing concrete implementations for shared behavior.
Basic Abstract Class:
1<?php
2declare(strict_types=1);
3
4abstract class Database {
5 protected string $host;
6 protected string $dbname;
7 protected $connection;
8
9 public function __construct(string $host, string $dbname) {
10 $this->host = $host;
11 $this->dbname = $dbname;
12 }
13
14 // Abstract methods must be implemented by child classes
15 abstract public function connect(): void;
16 abstract public function query(string $sql): array;
17 abstract public function disconnect(): void;
18
19 // Concrete method available to all children
20 public function isConnected(): bool {
21 return $this->connection !== null;
22 }
23
24 public function getDatabaseName(): string {
25 return $this->dbname;
26 }
27}
28
29class MySQLDatabase extends Database {
30 public function connect(): void {
31 $this->connection = new \PDO(
32 "mysql:host={$this->host};dbname={$this->dbname}",
33 'username',
34 'password'
35 );
36 echo "Connected to MySQL database\n";
37 }
38
39 public function query(string $sql): array {
40 $stmt = $this->connection->query($sql);
41 return $stmt->fetchAll();
42 }
43
44 public function disconnect(): void {
45 $this->connection = null;
46 echo "Disconnected from MySQL\n";
47 }
48}
49
50class PostgreSQLDatabase extends Database {
51 public function connect(): void {
52 $this->connection = new \PDO(
53 "pgsql:host={$this->host};dbname={$this->dbname}",
54 'username',
55 'password'
56 );
57 echo "Connected to PostgreSQL database\n";
58 }
59
60 public function query(string $sql): array {
61 $stmt = $this->connection->query($sql);
62 return $stmt->fetchAll();
63 }
64
65 public function disconnect(): void {
66 $this->connection = null;
67 echo "Disconnected from PostgreSQL\n";
68 }
69}
70
71// Cannot instantiate abstract class
72// $db = new Database('localhost', 'test'); // ERROR!
73
74$mysql = new MySQLDatabase('localhost', 'myapp');
75$mysql->connect();
76?>Template Method Pattern:
1<?php
2abstract class DataImporter {
3 // Template method defines the algorithm
4 final public function import(string $filename): void {
5 $this->validate($filename);
6 $data = $this->read($filename);
7 $processed = $this->process($data);
8 $this->save($processed);
9 $this->cleanup();
10 }
11
12 // Steps that subclasses must implement
13 abstract protected function read(string $filename): array;
14 abstract protected function process(array $data): array;
15 abstract protected function save(array $data): void;
16
17 // Optional hook methods with default implementation
18 protected function validate(string $filename): void {
19 if (!file_exists($filename)) {
20 throw new \Exception("File not found: $filename");
21 }
22 }
23
24 protected function cleanup(): void {
25 // Default: do nothing
26 }
27}
28
29class CSVImporter extends DataImporter {
30 protected function read(string $filename): array {
31 $data = [];
32 $file = fopen($filename, 'r');
33 while (($row = fgetcsv($file)) !== false) {
34 $data[] = $row;
35 }
36 fclose($file);
37 return $data;
38 }
39
40 protected function process(array $data): array {
41 // Remove header row
42 array_shift($data);
43 return $data;
44 }
45
46 protected function save(array $data): void {
47 foreach ($data as $row) {
48 // Save to database
49 echo "Saving: " . implode(', ', $row) . "\n";
50 }
51 }
52}
53
54$importer = new CSVImporter();
55$importer->import('data.csv');
56?>Abstract Classes with Constructors:
1<?php
2abstract class Vehicle {
3 protected int $wheels;
4 protected string $fuelType;
5
6 public function __construct(int $wheels, string $fuelType) {
7 $this->wheels = $wheels;
8 $this->fuelType = $fuelType;
9 }
10
11 abstract public function start(): void;
12 abstract public function stop(): void;
13
14 public function getInfo(): string {
15 return "Wheels: {$this->wheels}, Fuel: {$this->fuelType}";
16 }
17}
18
19class Car extends Vehicle {
20 public function __construct(string $fuelType = 'gasoline') {
21 parent::__construct(4, $fuelType);
22 }
23
24 public function start(): void {
25 echo "Car engine started\n";
26 }
27
28 public function stop(): void {
29 echo "Car engine stopped\n";
30 }
31}
32
33class Motorcycle extends Vehicle {
34 public function __construct() {
35 parent::__construct(2, 'gasoline');
36 }
37
38 public function start(): void {
39 echo "Motorcycle engine started\n";
40 }
41
42 public function stop(): void {
43 echo "Motorcycle engine stopped\n";
44 }
45}
46?>Interfaces define contracts that classes must fulfill, enabling polymorphism without inheritance and allowing multiple interface implementation.
Basic Interface:
1<?php
2declare(strict_types=1);
3
4interface Renderable {
5 public function render(): string;
6 public function getType(): string;
7}
8
9class Button implements Renderable {
10 public function __construct(
11 private string $label,
12 private string $class = 'btn'
13 ) {}
14
15 public function render(): string {
16 return "<button class=\"{$this->class}\">{$this->label}</button>";
17 }
18
19 public function getType(): string {
20 return 'button';
21 }
22}
23
24class Link implements Renderable {
25 public function __construct(
26 private string $text,
27 private string $url
28 ) {}
29
30 public function render(): string {
31 return "<a href=\"{$this->url}\">{$this->text}</a>";
32 }
33
34 public function getType(): string {
35 return 'link';
36 }
37}
38
39function displayElement(Renderable $element): void {
40 echo "Type: " . $element->getType() . "\n";
41 echo "HTML: " . $element->render() . "\n\n";
42}
43
44displayElement(new Button('Click Me'));
45displayElement(new Link('Home', '/home'));
46?>Multiple Interface Implementation:
1<?php
2interface Loggable {
3 public function getLogMessage(): string;
4}
5
6interface Serializable {
7 public function serialize(): string;
8 public function unserialize(string $data): void;
9}
10
11interface Cacheable {
12 public function getCacheKey(): string;
13 public function getCacheDuration(): int;
14}
15
16class User implements Loggable, Serializable, Cacheable {
17 public function __construct(
18 private int $id,
19 private string $name,
20 private string $email
21 ) {}
22
23 // Loggable implementation
24 public function getLogMessage(): string {
25 return "User {$this->id}: {$this->name}";
26 }
27
28 // Serializable implementation
29 public function serialize(): string {
30 return json_encode([
31 'id' => $this->id,
32 'name' => $this->name,
33 'email' => $this->email
34 ]);
35 }
36
37 public function unserialize(string $data): void {
38 $decoded = json_decode($data, true);
39 $this->id = $decoded['id'];
40 $this->name = $decoded['name'];
41 $this->email = $decoded['email'];
42 }
43
44 // Cacheable implementation
45 public function getCacheKey(): string {
46 return "user:{$this->id}";
47 }
48
49 public function getCacheDuration(): int {
50 return 3600; // 1 hour
51 }
52}
53
54$user = new User(1, 'John Doe', '[email protected]');
55echo $user->getLogMessage() . "\n";
56echo "Cache key: " . $user->getCacheKey() . "\n";
57echo "Serialized: " . $user->serialize() . "\n";
58?>Interface Inheritance:
1<?php
2interface Readable {
3 public function read(): string;
4}
5
6interface Writable {
7 public function write(string $data): void;
8}
9
10// Interface can extend multiple interfaces
11interface ReadWritable extends Readable, Writable {
12 public function flush(): void;
13}
14
15class File implements ReadWritable {
16 private array $buffer = [];
17
18 public function read(): string {
19 return implode("\n", $this->buffer);
20 }
21
22 public function write(string $data): void {
23 $this->buffer[] = $data;
24 }
25
26 public function flush(): void {
27 $this->buffer = [];
28 }
29}
30?>Interface Constants:
1<?php
2interface Status {
3 public const PENDING = 'pending';
4 public const APPROVED = 'approved';
5 public const REJECTED = 'rejected';
6}
7
8class Order implements Status {
9 private string $status = self::PENDING;
10
11 public function approve(): void {
12 $this->status = self::APPROVED;
13 }
14
15 public function reject(): void {
16 $this->status = self::REJECTED;
17 }
18
19 public function getStatus(): string {
20 return $this->status;
21 }
22}
23
24echo "Pending status: " . Status::PENDING . "\n";
25?>Type Hinting with Interfaces:
1<?php
2interface Notifiable {
3 public function notify(string $message): void;
4}
5
6class EmailNotifier implements Notifiable {
7 public function __construct(private string $email) {}
8
9 public function notify(string $message): void {
10 echo "Email sent to {$this->email}: $message\n";
11 }
12}
13
14class SMSNotifier implements Notifiable {
15 public function __construct(private string $phone) {}
16
17 public function notify(string $message): void {
18 echo "SMS sent to {$this->phone}: $message\n";
19 }
20}
21
22class NotificationService {
23 /** @var Notifiable[] */
24 private array $notifiers = [];
25
26 public function addNotifier(Notifiable $notifier): void {
27 $this->notifiers[] = $notifier;
28 }
29
30 public function sendToAll(string $message): void {
31 foreach ($this->notifiers as $notifier) {
32 $notifier->notify($message);
33 }
34 }
35}
36
37$service = new NotificationService();
38$service->addNotifier(new EmailNotifier('[email protected]'));
39$service->addNotifier(new SMSNotifier('+1234567890'));
40$service->sendToAll("Your order has shipped!");
41?>Explore advanced patterns and best practices for using inheritance effectively in complex applications.
Final Classes and Methods:
1<?php
2declare(strict_types=1);
3
4class BaseValidator {
5 // Final method cannot be overridden
6 final public function validate(array $data): bool {
7 $this->beforeValidation();
8 $result = $this->performValidation($data);
9 $this->afterValidation($result);
10 return $result;
11 }
12
13 protected function beforeValidation(): void {
14 // Hook for subclasses
15 }
16
17 protected function performValidation(array $data): bool {
18 return true; // Override in subclasses
19 }
20
21 protected function afterValidation(bool $result): void {
22 // Hook for subclasses
23 }
24}
25
26// Final class cannot be extended
27final class EmailValidator extends BaseValidator {
28 protected function performValidation(array $data): bool {
29 return isset($data['email']) &&
30 filter_var($data['email'], FILTER_VALIDATE_EMAIL);
31 }
32}
33
34// This would cause an error:
35// class SpecialEmailValidator extends EmailValidator {} // ERROR!
36?>Traits with Inheritance:
1<?php
2trait Timestampable {
3 protected \DateTime $createdAt;
4 protected \DateTime $updatedAt;
5
6 public function initializeTimestamps(): void {
7 $this->createdAt = new \DateTime();
8 $this->updatedAt = new \DateTime();
9 }
10
11 public function touch(): void {
12 $this->updatedAt = new \DateTime();
13 }
14
15 public function getCreatedAt(): \DateTime {
16 return $this->createdAt;
17 }
18}
19
20trait SoftDeletable {
21 protected ?\DateTime $deletedAt = null;
22
23 public function delete(): void {
24 $this->deletedAt = new \DateTime();
25 }
26
27 public function restore(): void {
28 $this->deletedAt = null;
29 }
30
31 public function isDeleted(): bool {
32 return $this->deletedAt !== null;
33 }
34}
35
36abstract class Model {
37 protected int $id;
38
39 abstract public function save(): void;
40 abstract public function find(int $id): ?self;
41}
42
43class Post extends Model {
44 use Timestampable, SoftDeletable;
45
46 public function __construct(private string $title) {
47 $this->initializeTimestamps();
48 }
49
50 public function save(): void {
51 $this->touch();
52 // Save to database
53 }
54
55 public function find(int $id): ?self {
56 // Find in database
57 return null;
58 }
59}
60?>Composition Over Inheritance:
1<?php
2// Instead of deep inheritance hierarchies, use composition
3
4// BAD: Deep inheritance
5class Vehicle {}
6class MotorVehicle extends Vehicle {}
7class Car extends MotorVehicle {}
8class SportsCar extends Car {}
9
10// GOOD: Composition
11interface Engine {
12 public function start(): void;
13 public function stop(): void;
14}
15
16class GasolineEngine implements Engine {
17 public function start(): void {
18 echo "Gasoline engine started\n";
19 }
20
21 public function stop(): void {
22 echo "Gasoline engine stopped\n";
23 }
24}
25
26class ElectricEngine implements Engine {
27 public function start(): void {
28 echo "Electric motor activated\n";
29 }
30
31 public function stop(): void {
32 echo "Electric motor deactivated\n";
33 }
34}
35
36class ModernCar {
37 public function __construct(
38 private Engine $engine,
39 private int $wheels = 4
40 ) {}
41
42 public function start(): void {
43 $this->engine->start();
44 }
45
46 public function stop(): void {
47 $this->engine->stop();
48 }
49}
50
51// Flexible composition
52$gasCar = new ModernCar(new GasolineEngine());
53$electricCar = new ModernCar(new ElectricEngine());
54?>instanceof and Type Checking:
1<?php
2abstract class Shape {
3 abstract public function getArea(): float;
4}
5
6class Circle extends Shape {
7 public function __construct(private float $radius) {}
8
9 public function getArea(): float {
10 return pi() * $this->radius ** 2;
11 }
12
13 public function getCircumference(): float {
14 return 2 * pi() * $this->radius;
15 }
16}
17
18class Rectangle extends Shape {
19 public function __construct(
20 private float $width,
21 private float $height
22 ) {}
23
24 public function getArea(): float {
25 return $this->width * $this->height;
26 }
27
28 public function getPerimeter(): float {
29 return 2 * ($this->width + $this->height);
30 }
31}
32
33function processShape(Shape $shape): void {
34 echo "Area: " . $shape->getArea() . "\n";
35
36 if ($shape instanceof Circle) {
37 echo "Circumference: " . $shape->getCircumference() . "\n";
38 } elseif ($shape instanceof Rectangle) {
39 echo "Perimeter: " . $shape->getPerimeter() . "\n";
40 }
41}
42
43processShape(new Circle(5));
44processShape(new Rectangle(4, 6));
45?>Building a Content Management System with Inheritance
1<?php
2declare(strict_types=1);
3
4/**
5 * Complete Content Management System using Inheritance and Polymorphism
6 *
7 * Demonstrates:
8 * - Abstract base classes
9 * - Method overriding
10 * - Polymorphic behavior
11 * - Multiple interface implementation
12 * - Type hinting
13 * - Real-world CMS architecture
14 */
15
16// ==================== INTERFACES ====================
17
18interface Publishable {
19 public function publish(): void;
20 public function unpublish(): void;
21 public function isPublished(): bool;
22}
23
24interface Commentable {
25 public function addComment(Comment $comment): void;
26 public function getComments(): array;
27 public function getCommentCount(): int;
28}
29
30interface Taggable {
31 public function addTag(string $tag): void;
32 public function removeTag(string $tag): void;
33 public function getTags(): array;
34}
35
36// ==================== ABSTRACT BASE CLASS ====================
37
38abstract class Content implements Publishable {
39 protected int $id;
40 protected string $title;
41 protected string $author;
42 protected \DateTime $createdAt;
43 protected ?\DateTime $publishedAt = null;
44 protected array $metadata = [];
45
46 public function __construct(string $title, string $author) {
47 $this->id = random_int(1000, 9999);
48 $this->title = $title;
49 $this->author = $author;
50 $this->createdAt = new \DateTime();
51 }
52
53 // Abstract methods that children must implement
54 abstract public function getType(): string;
55 abstract public function getPreview(): string;
56 abstract public function render(): string;
57
58 // Concrete implementation from Publishable
59 public function publish(): void {
60 $this->publishedAt = new \DateTime();
61 echo "✅ {$this->getType()} published: {$this->title}\n";
62 }
63
64 public function unpublish(): void {
65 $this->publishedAt = null;
66 echo "❌ {$this->getType()} unpublished: {$this->title}\n";
67 }
68
69 public function isPublished(): bool {
70 return $this->publishedAt !== null;
71 }
72
73 // Common methods available to all content
74 public function getId(): int {
75 return $this->id;
76 }
77
78 public function getTitle(): string {
79 return $this->title;
80 }
81
82 public function getAuthor(): string {
83 return $this->author;
84 }
85
86 public function setMetadata(string $key, mixed $value): void {
87 $this->metadata[$key] = $value;
88 }
89
90 public function getMetadata(string $key): mixed {
91 return $this->metadata[$key] ?? null;
92 }
93
94 public function getInfo(): string {
95 $status = $this->isPublished() ? '✅ Published' : '⏳ Draft';
96 $published = $this->publishedAt
97 ? $this->publishedAt->format('Y-m-d H:i:s')
98 : 'N/A';
99
100 return <<<INFO
101 [{$this->getType()}] {$this->title}
102 Author: {$this->author}
103 Status: $status
104 Created: {$this->createdAt->format('Y-m-d H:i:s')}
105 Published: $published
106 INFO;
107 }
108}
109
110// ==================== COMMENT CLASS ====================
111
112class Comment {
113 public function __construct(
114 private string $author,
115 private string $content,
116 private \DateTime $createdAt = new \DateTime()
117 ) {}
118
119 public function getAuthor(): string {
120 return $this->author;
121 }
122
123 public function getContent(): string {
124 return $this->content;
125 }
126
127 public function render(): string {
128 $time = $this->createdAt->format('Y-m-d H:i:s');
129 return "💬 {$this->author} ({$time}): {$this->content}";
130 }
131}
132
133// ==================== ARTICLE CLASS ====================
134
135class Article extends Content implements Commentable, Taggable {
136 private string $body;
137 private array $comments = [];
138 private array $tags = [];
139 private int $readingTime;
140
141 public function __construct(
142 string $title,
143 string $author,
144 string $body
145 ) {
146 parent::__construct($title, $author);
147 $this->body = $body;
148 $this->readingTime = $this->calculateReadingTime();
149 }
150
151 public function getType(): string {
152 return 'Article';
153 }
154
155 public function getPreview(): string {
156 return substr($this->body, 0, 150) . '...';
157 }
158
159 public function render(): string {
160 $tags = implode(', ', $this->tags);
161 return <<<HTML
162 <article>
163 <h1>{$this->title}</h1>
164 <p class="meta">By {$this->author} • {$this->readingTime} min read</p>
165 <div class="tags">$tags</div>
166 <div class="content">{$this->body}</div>
167 <div class="comments">Comments: {$this->getCommentCount()}</div>
168 </article>
169 HTML;
170 }
171
172 private function calculateReadingTime(): int {
173 $wordCount = str_word_count($this->body);
174 return max(1, (int)ceil($wordCount / 200));
175 }
176
177 // Commentable implementation
178 public function addComment(Comment $comment): void {
179 $this->comments[] = $comment;
180 }
181
182 public function getComments(): array {
183 return $this->comments;
184 }
185
186 public function getCommentCount(): int {
187 return count($this->comments);
188 }
189
190 // Taggable implementation
191 public function addTag(string $tag): void {
192 if (!in_array($tag, $this->tags)) {
193 $this->tags[] = $tag;
194 }
195 }
196
197 public function removeTag(string $tag): void {
198 $this->tags = array_filter(
199 $this->tags,
200 fn($t) => $t !== $tag
201 );
202 }
203
204 public function getTags(): array {
205 return $this->tags;
206 }
207}
208
209// ==================== VIDEO CLASS ====================
210
211class Video extends Content implements Commentable {
212 private array $comments = [];
213
214 public function __construct(
215 string $title,
216 string $author,
217 private string $url,
218 private int $duration // in seconds
219 ) {
220 parent::__construct($title, $author);
221 }
222
223 public function getType(): string {
224 return 'Video';
225 }
226
227 public function getPreview(): string {
228 $mins = floor($this->duration / 60);
229 $secs = $this->duration % 60;
230 return "Video: {$this->title} ({$mins}:{$secs:02d})";
231 }
232
233 public function render(): string {
234 $formatted = $this->formatDuration();
235 return <<<HTML
236 <div class="video">
237 <h2>{$this->title}</h2>
238 <p>By {$this->author} • Duration: $formatted</p>
239 <video src="{$this->url}" controls></video>
240 <div class="comments">Comments: {$this->getCommentCount()}</div>
241 </div>
242 HTML;
243 }
244
245 private function formatDuration(): string {
246 $hours = floor($this->duration / 3600);
247 $minutes = floor(($this->duration % 3600) / 60);
248 $seconds = $this->duration % 60;
249
250 if ($hours > 0) {
251 return sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
252 }
253 return sprintf('%02d:%02d', $minutes, $seconds);
254 }
255
256 // Commentable implementation
257 public function addComment(Comment $comment): void {
258 $this->comments[] = $comment;
259 }
260
261 public function getComments(): array {
262 return $this->comments;
263 }
264
265 public function getCommentCount(): int {
266 return count($this->comments);
267 }
268}
269
270// ==================== PAGE CLASS ====================
271
272class Page extends Content {
273 private string $content;
274 private ?Page $parent = null;
275 private array $children = [];
276
277 public function __construct(
278 string $title,
279 string $author,
280 string $content
281 ) {
282 parent::__construct($title, $author);
283 $this->content = $content;
284 }
285
286 public function getType(): string {
287 return 'Page';
288 }
289
290 public function getPreview(): string {
291 return "Static page: {$this->title}";
292 }
293
294 public function render(): string {
295 $breadcrumb = $this->getBreadcrumb();
296 return <<<HTML
297 <div class="page">
298 <nav>$breadcrumb</nav>
299 <h1>{$this->title}</h1>
300 <div class="content">{$this->content}</div>
301 </div>
302 HTML;
303 }
304
305 public function setParent(Page $parent): void {
306 $this->parent = $parent;
307 $parent->children[] = $this;
308 }
309
310 public function getChildren(): array {
311 return $this->children;
312 }
313
314 private function getBreadcrumb(): string {
315 $parts = [$this->title];
316 $current = $this->parent;
317
318 while ($current !== null) {
319 array_unshift($parts, $current->getTitle());
320 $current = $current->parent;
321 }
322
323 return implode(' > ', $parts);
324 }
325}
326
327// ==================== CMS CLASS ====================
328
329class ContentManagementSystem {
330 /** @var Content[] */
331 private array $contents = [];
332
333 public function addContent(Content $content): void {
334 $this->contents[$content->getId()] = $content;
335 echo "➕ Added {$content->getType()}: {$content->getTitle()}\n";
336 }
337
338 public function publishAll(): void {
339 echo "\n📢 Publishing all content...\n";
340 foreach ($this->contents as $content) {
341 if (!$content->isPublished()) {
342 $content->publish();
343 }
344 }
345 }
346
347 public function getPublishedContent(): array {
348 return array_filter(
349 $this->contents,
350 fn($c) => $c->isPublished()
351 );
352 }
353
354 public function getContentByType(string $type): array {
355 return array_filter(
356 $this->contents,
357 fn($c) => $c->getType() === $type
358 );
359 }
360
361 public function displayAllContent(): void {
362 echo "\n📋 All Content:\n";
363 echo str_repeat('=', 60) . "\n";
364
365 foreach ($this->contents as $content) {
366 echo $content->getInfo() . "\n";
367 echo str_repeat('-', 60) . "\n";
368 }
369 }
370}
371
372// ==================== DEMO ====================
373
374echo str_repeat('=', 60) . "\n";
375echo " 📰 CONTENT MANAGEMENT SYSTEM DEMO\n";
376echo str_repeat('=', 60) . "\n\n";
377
378$cms = new ContentManagementSystem();
379
380// Create article
381$article = new Article(
382 'Getting Started with PHP',
383 'John Doe',
384 'This is a comprehensive guide to getting started with PHP. ' .
385 'Learn about variables, functions, OOP, and more. ' .
386 'PHP is a powerful server-side scripting language.'
387);
388$article->addTag('PHP');
389$article->addTag('Tutorial');
390$article->addTag('Beginner');
391$article->addComment(new Comment('Alice', 'Great article!'));
392$article->addComment(new Comment('Bob', 'Very helpful, thanks!'));
393$cms->addContent($article);
394
395// Create video
396$video = new Video(
397 'PHP OOP Tutorial',
398 'Jane Smith',
399 'https://example.com/video.mp4',
400 1845 // 30 minutes 45 seconds
401);
402$video->addComment(new Comment('Charlie', 'Excellent explanation!'));
403$cms->addContent($video);
404
405// Create pages with hierarchy
406$home = new Page('Home', 'Admin', 'Welcome to our website');
407$about = new Page('About', 'Admin', 'Learn about our company');
408$about->setParent($home);
409$contact = new Page('Contact', 'Admin', 'Get in touch with us');
410$contact->setParent($about);
411
412$cms->addContent($home);
413$cms->addContent($about);
414$cms->addContent($contact);
415
416// Publish all content
417$cms->publishAll();
418
419// Display all content
420$cms->displayAllContent();
421
422// Display rendered article
423echo "\n📄 Rendered Article:\n";
424echo str_repeat('=', 60) . "\n";
425echo $article->render() . "\n";
426
427echo "\n💬 Article Comments:\n";
428foreach ($article->getComments() as $comment) {
429 echo " " . $comment->render() . "\n";
430}
431
432echo "\n🏷️ Article Tags: " . implode(', ', $article->getTags()) . "\n";
433
434echo "\n" . str_repeat('=', 60) . "\n";
435echo "✅ CMS DEMO COMPLETE!\n";
436echo str_repeat('=', 60) . "\n";
437echo "\n🎯 Key Concepts Demonstrated:\n";
438echo " • Abstract base class (Content)\n";
439echo " • Multiple interfaces (Publishable, Commentable, Taggable)\n";
440echo " • Method overriding (getType, getPreview, render)\n";
441echo " • Polymorphism (different content types, same interface)\n";
442echo " • Inheritance hierarchies (Page with parent/children)\n";
443echo " • Type hinting with abstracts and interfaces\n";
444?>This comprehensive CMS demonstrates all key inheritance and polymorphism concepts:
1. Abstract Base Class:
Content serves as abstract base for all content typesPublishable interface2. Polymorphism:
ContentContent type3. Multiple Interface Implementation:
Article implements both Commentable and TaggableVideo implements Commentable4. Method Overriding:
getType(), getPreview(), render()5. Real-World Architecture:
Try modifying the code to add new content types like Podcast or Gallery!
Your progress is saved in this browser, on this device only.