1. php
  2. /object oriented
  3. /abstract-classes

PHP Abstract Classes

Introduction to Abstract Classes

Abstract classes in PHP provide a way to define base classes that cannot be instantiated directly but serve as blueprints for derived classes. They allow you to define common functionality while requiring subclasses to implement specific methods, creating a contract that ensures consistent behavior across related classes.

Understanding abstract classes is essential for creating well-structured object-oriented designs that promote code reuse, enforce consistency, and provide clear inheritance hierarchies.

Why Abstract Classes Matter

Design Contracts: Abstract classes define what methods subclasses must implement, ensuring consistent interfaces across related classes.

Code Reuse: Common functionality can be implemented once in the abstract class and inherited by all subclasses.

Template Method Pattern: Abstract classes enable the template method pattern, where the algorithm structure is defined but specific steps are implemented by subclasses.

Polymorphism: Abstract classes provide a common type that can be used polymorphically with different concrete implementations.

Preventing Instantiation: Abstract classes prevent direct instantiation of incomplete or conceptual classes that should only serve as base classes.

Key Concepts

Abstract Classes: Classes declared with the abstract keyword that cannot be instantiated directly.

Abstract Methods: Methods declared without implementation that must be implemented by concrete subclasses.

Concrete Methods: Fully implemented methods in abstract classes that provide common functionality.

Inheritance Hierarchy: Abstract classes form the foundation of inheritance hierarchies with clear contracts and shared behavior.

Basic Abstract Classes

Creating and Using Abstract Classes

<?php /**  * Basic Abstract Class Implementation  *   * Abstract classes provide blueprints for related classes  * with shared functionality and enforced contracts.  */  /**  * Abstract Vehicle class defining common vehicle behavior  */ abstract class Vehicle {  protected string $make;  protected string $model;  protected int $year;  protected float $fuelLevel = 0.0;  protected bool $isRunning = false;    public function __construct(string $make, string $model, int $year)  {  $this->make = $make;  $this->model = $model;  $this->year = $year;  }    /**  * Concrete method - implemented in abstract class  */  public function getInfo(): string  {  return "{$this->year} {$this->make} {$this->model}";  }    /**  * Concrete method with shared logic  */  public function refuel(float $amount): void  {  if ($amount <= 0) {  throw new InvalidArgumentException('Fuel amount must be positive');  }    $this->fuelLevel += $amount;  echo "Refueled {$this->getInfo()} with {$amount} units\n";  }    /**  * Concrete method that uses abstract method  */  public function start(): void  {  if ($this->fuelLevel <= 0) {  throw new RuntimeException('Cannot start: no fuel');  }    if ($this->isRunning) {  echo "{$this->getInfo()} is already running\n";  return;  }    $this->performStartSequence();  $this->isRunning = true;  echo "{$this->getInfo()} started successfully\n";  }    public function stop(): void  {  if (!$this->isRunning) {  echo "{$this->getInfo()} is already stopped\n";  return;  }    $this->isRunning = false;  echo "{$this->getInfo()} stopped\n";  }    /**  * Abstract method - must be implemented by subclasses  */  abstract protected function performStartSequence(): void;    /**  * Abstract method - each vehicle type moves differently  */  abstract public function move(float $distance): void;    /**  * Abstract method - different fuel consumption rates  */  abstract public function getFuelConsumption(): float;    // Getters  public function getMake(): string { return $this->make; }  public function getModel(): string { return $this->model; }  public function getYear(): int { return $this->year; }  public function getFuelLevel(): float { return $this->fuelLevel; }  public function isRunning(): bool { return $this->isRunning; } }  /**  * Concrete Car class implementing abstract methods  */ class Car extends Vehicle {  private int $doors;  private string $transmission;    public function __construct(string $make, string $model, int $year, int $doors = 4, string $transmission = 'automatic')  {  parent::__construct($make, $model, $year);  $this->doors = $doors;  $this->transmission = $transmission;  }    /**  * Implementation of abstract method  */  protected function performStartSequence(): void  {  echo "Turning ignition key for {$this->getInfo()}\n";  echo "Engine warming up...\n";  }    /**  * Implementation of abstract method  */  public function move(float $distance): void  {  if (!$this->isRunning) {  throw new RuntimeException('Cannot move: vehicle not running');  }    $fuelNeeded = $distance * $this->getFuelConsumption();    if ($this->fuelLevel < $fuelNeeded) {  throw new RuntimeException('Insufficient fuel for distance');  }    $this->fuelLevel -= $fuelNeeded;  echo "Car drove {$distance} km, fuel remaining: {$this->fuelLevel}\n";  }    /**  * Implementation of abstract method  */  public function getFuelConsumption(): float  {  // Cars consume 0.08 fuel units per km  return 0.08;  }    /**  * Car-specific method  */  public function openTrunk(): void  {  echo "Trunk opened\n";  }    public function getDoors(): int { return $this->doors; }  public function getTransmission(): string { return $this->transmission; } }  /**  * Concrete Motorcycle class implementing abstract methods  */ class Motorcycle extends Vehicle {  private int $engineCC;  private bool $hasSidecar;    public function __construct(string $make, string $model, int $year, int $engineCC, bool $hasSidecar = false)  {  parent::__construct($make, $model, $year);  $this->engineCC = $engineCC;  $this->hasSidecar = $hasSidecar;  }    /**  * Implementation of abstract method  */  protected function performStartSequence(): void  {  echo "Kickstarting {$this->getInfo()}\n";  echo "Engine revving...\n";  }    /**  * Implementation of abstract method  */  public function move(float $distance): void  {  if (!$this->isRunning) {  throw new RuntimeException('Cannot move: motorcycle not running');  }    $fuelNeeded = $distance * $this->getFuelConsumption();    if ($this->fuelLevel < $fuelNeeded) {  throw new RuntimeException('Insufficient fuel for distance');  }    $this->fuelLevel -= $fuelNeeded;  echo "Motorcycle rode {$distance} km, fuel remaining: {$this->fuelLevel}\n";  }    /**  * Implementation of abstract method  */  public function getFuelConsumption(): float  {  // Base consumption: 0.04 fuel units per km  $consumption = 0.04;    // Larger engines consume more  if ($this->engineCC > 600) {  $consumption *= 1.5;  }    // Sidecar increases consumption  if ($this->hasSidecar) {  $consumption *= 1.3;  }    return $consumption;  }    /**  * Motorcycle-specific method  */  public function wheelie(): void  {  if (!$this->isRunning) {  throw new RuntimeException('Cannot perform wheelie: motorcycle not running');  }  echo "Performing wheelie!\n";  }    public function getEngineCC(): int { return $this->engineCC; }  public function hasSidecar(): bool { return $this->hasSidecar; } }  // Usage examples echo "=== Abstract Class Examples ===\n";  // Cannot instantiate abstract class // $vehicle = new Vehicle('Generic', 'Vehicle', 2024); // This would cause an error  // Create concrete vehicles $car = new Car('Toyota', 'Camry', 2024, 4, 'automatic'); $motorcycle = new Motorcycle('Harley-Davidson', 'Street 750', 2024, 750, false);  // Use inherited methods echo "Car: " . $car->getInfo() . "\n"; echo "Motorcycle: " . $motorcycle->getInfo() . "\n";  // Refuel vehicles $car->refuel(50.0); $motorcycle->refuel(15.0);  // Start and move vehicles $car->start(); $car->move(100.0);  $motorcycle->start(); $motorcycle->move(80.0); $motorcycle->wheelie();  // Polymorphic usage function displayVehicleInfo(Vehicle $vehicle): void {  echo "Vehicle Info: " . $vehicle->getInfo() . "\n";  echo "Fuel Level: " . $vehicle->getFuelLevel() . "\n";  echo "Fuel Consumption: " . $vehicle->getFuelConsumption() . " units/km\n";  echo "Running: " . ($vehicle->isRunning() ? 'Yes' : 'No') . "\n\n"; }  displayVehicleInfo($car); displayVehicleInfo($motorcycle); ?> 

Template Method Pattern

Implementing Template Method with Abstract Classes

<?php /**  * Template Method Pattern Implementation  *   * The template method pattern defines the skeleton of an algorithm  * in an abstract class and lets subclasses override specific steps.  */  /**  * Abstract data processor defining the processing template  */ abstract class DataProcessor {  protected array $data = [];  protected array $errors = [];    /**  * Template method defining the algorithm structure  * This method cannot be overridden by subclasses  */  final public function process(array $rawData): array  {  $this->validateInput($rawData);  $this->loadData($rawData);  $this->preprocessData();  $this->transformData();  $this->validateOutput();  $this->saveResults();    return $this->getResults();  }    /**  * Concrete method with common validation  */  protected function validateInput(array $rawData): void  {  if (empty($rawData)) {  throw new InvalidArgumentException('Input data cannot be empty');  }    echo "Input validation passed\n";  }    /**  * Concrete method with common data loading  */  protected function loadData(array $rawData): void  {  $this->data = $rawData;  echo "Data loaded: " . count($this->data) . " records\n";  }    /**  * Abstract method - preprocessing varies by data type  */  abstract protected function preprocessData(): void;    /**  * Abstract method - transformation logic varies by processor type  */  abstract protected function transformData(): void;    /**  * Hook method - can be overridden but has default implementation  */  protected function validateOutput(): void  {  if (empty($this->data)) {  $this->errors[] = 'No data after processing';  }    echo "Output validation completed\n";  }    /**  * Abstract method - saving mechanism varies  */  abstract protected function saveResults(): void;    /**  * Concrete method for result retrieval  */  protected function getResults(): array  {  return [  'data' => $this->data,  'errors' => $this->errors,  'processed_at' => date('Y-m-d H:i:s')  ];  } }  /**  * Concrete processor for user data  */ class UserDataProcessor extends DataProcessor {  /**  * User-specific preprocessing  */  protected function preprocessData(): void  {  foreach ($this->data as &$record) {  // Normalize email addresses  if (isset($record['email'])) {  $record['email'] = strtolower(trim($record['email']));  }    // Standardize phone numbers  if (isset($record['phone'])) {  $record['phone'] = preg_replace('/[^\d]/', '', $record['phone']);  }    // Trim names  if (isset($record['name'])) {  $record['name'] = trim($record['name']);  }  }    echo "User data preprocessed\n";  }    /**  * User-specific transformation  */  protected function transformData(): void  {  foreach ($this->data as &$record) {  // Add full name if first and last name exist  if (isset($record['first_name']) && isset($record['last_name'])) {  $record['full_name'] = $record['first_name'] . ' ' . $record['last_name'];  }    // Calculate age from birth date  if (isset($record['birth_date'])) {  $birthDate = new DateTime($record['birth_date']);  $today = new DateTime();  $record['age'] = $today->diff($birthDate)->y;  }    // Validate email format  if (isset($record['email']) && !filter_var($record['email'], FILTER_VALIDATE_EMAIL)) {  $this->errors[] = "Invalid email: {$record['email']}";  }  }    echo "User data transformed\n";  }    /**  * Override validation for user-specific rules  */  protected function validateOutput(): void  {  parent::validateOutput();    foreach ($this->data as $record) {  if (!isset($record['email']) || !isset($record['name'])) {  $this->errors[] = 'Missing required fields in user record';  }  }    echo "User-specific validation completed\n";  }    /**  * User-specific saving logic  */  protected function saveResults(): void  {  // Simulate saving to user database  echo "Saving " . count($this->data) . " user records to database\n";    foreach ($this->data as $record) {  if (isset($record['email'])) {  echo "Saved user: {$record['email']}\n";  }  }  } }  /**  * Concrete processor for product data  */ class ProductDataProcessor extends DataProcessor {  /**  * Product-specific preprocessing  */  protected function preprocessData(): void  {  foreach ($this->data as &$record) {  // Normalize product names  if (isset($record['name'])) {  $record['name'] = ucwords(strtolower(trim($record['name'])));  }    // Ensure price is numeric  if (isset($record['price'])) {  $record['price'] = (float) $record['price'];  }    // Normalize SKU  if (isset($record['sku'])) {  $record['sku'] = strtoupper(trim($record['sku']));  }  }    echo "Product data preprocessed\n";  }    /**  * Product-specific transformation  */  protected function transformData(): void  {  foreach ($this->data as &$record) {  // Calculate discounted price  if (isset($record['price']) && isset($record['discount_percent'])) {  $discountAmount = $record['price'] * ($record['discount_percent'] / 100);  $record['discounted_price'] = $record['price'] - $discountAmount;  }    // Generate slug from name  if (isset($record['name'])) {  $record['slug'] = strtolower(preg_replace('/[^a-zA-Z0-9]/', '-', $record['name']));  $record['slug'] = preg_replace('/-+/', '-', trim($record['slug'], '-'));  }    // Validate price  if (isset($record['price']) && $record['price'] <= 0) {  $this->errors[] = "Invalid price for product: {$record['name']}";  }  }    echo "Product data transformed\n";  }    /**  * Product-specific saving logic  */  protected function saveResults(): void  {  // Simulate saving to product catalog  echo "Saving " . count($this->data) . " products to catalog\n";    foreach ($this->data as $record) {  if (isset($record['sku'])) {  echo "Saved product: {$record['sku']} - {$record['name']}\n";  }  }  } }  // Usage examples echo "\n=== Template Method Pattern Examples ===\n";  // Process user data $userProcessor = new UserDataProcessor(); $userData = [  [  'first_name' => 'john',  'last_name' => 'doe',  'email' => ' [email protected] ',  'phone' => '(555) 123-4567',  'birth_date' => '1990-01-15'  ],  [  'first_name' => 'jane',  'last_name' => 'smith',  'email' => '[email protected]',  'phone' => '555.987.6543',  'birth_date' => '1985-05-20'  ] ];  echo "Processing user data:\n"; $userResults = $userProcessor->process($userData);  echo "\nUser processing results:\n"; print_r($userResults);  // Process product data $productProcessor = new ProductDataProcessor(); $productData = [  [  'name' => ' wireless headphones ',  'price' => '99.99',  'sku' => 'wh-001',  'discount_percent' => 10  ],  [  'name' => 'BLUETOOTH SPEAKER',  'price' => '49.99',  'sku' => ' bs-002 ',  'discount_percent' => 15  ] ];  echo "\n" . str_repeat('-', 50) . "\n"; echo "Processing product data:\n"; $productResults = $productProcessor->process($productData);  echo "\nProduct processing results:\n"; print_r($productResults); ?> 

Advanced Abstract Class Patterns

Abstract Factory Pattern and Complex Hierarchies

<?php /**  * Advanced Abstract Class Patterns  *   * Complex inheritance hierarchies and factory patterns  * using abstract classes for structured design.  */  /**  * Abstract notification system  */ abstract class NotificationChannel {  protected array $config;  protected array $recipients = [];    public function __construct(array $config = [])  {  $this->config = $config;  $this->validateConfig();  }    /**  * Template method for sending notifications  */  final public function send(string $message, array $recipients = []): bool  {  if (!empty($recipients)) {  $this->setRecipients($recipients);  }    if (empty($this->recipients)) {  throw new RuntimeException('No recipients specified');  }    $formattedMessage = $this->formatMessage($message);  $this->validateMessage($formattedMessage);    $success = $this->deliverMessage($formattedMessage);  $this->logDelivery($success);    return $success;  }    /**  * Abstract method for configuration validation  */  abstract protected function validateConfig(): void;    /**  * Abstract method for message formatting  */  abstract protected function formatMessage(string $message): string;    /**  * Abstract method for message delivery  */  abstract protected function deliverMessage(string $message): bool;    /**  * Hook method for message validation  */  protected function validateMessage(string $message): void  {  if (empty($message)) {  throw new InvalidArgumentException('Message cannot be empty');  }  }    /**  * Concrete method for recipient management  */  public function setRecipients(array $recipients): void  {  $this->recipients = array_filter($recipients, [$this, 'isValidRecipient']);  }    /**  * Abstract method for recipient validation  */  abstract protected function isValidRecipient($recipient): bool;    /**  * Concrete method for logging  */  protected function logDelivery(bool $success): void  {  $status = $success ? 'SUCCESS' : 'FAILED';  $timestamp = date('Y-m-d H:i:s');  $channelType = static::class;    echo "[$timestamp] $channelType: $status\n";  } }  /**  * Email notification implementation  */ class EmailNotification extends NotificationChannel {  protected function validateConfig(): void  {  $required = ['smtp_host', 'smtp_port', 'username', 'password'];    foreach ($required as $key) {  if (!isset($this->config[$key])) {  throw new InvalidArgumentException("Missing email config: $key");  }  }  }    protected function formatMessage(string $message): string  {  $html = "<html><body>";  $html .= "<h2>Notification</h2>";  $html .= "<p>" . nl2br(htmlspecialchars($message)) . "</p>";  $html .= "<p><small>Sent via automated system</small></p>";  $html .= "</body></html>";    return $html;  }    protected function deliverMessage(string $message): bool  {  // Simulate email sending  foreach ($this->recipients as $email) {  echo "Sending email to: $email\n";  // In real implementation, use PHPMailer or similar  }    return true;  }    protected function isValidRecipient($recipient): bool  {  return filter_var($recipient, FILTER_VALIDATE_EMAIL) !== false;  }    protected function validateMessage(string $message): void  {  parent::validateMessage($message);    if (strlen($message) > 100000) {  throw new InvalidArgumentException('Email message too long');  }  } }  /**  * SMS notification implementation  */ class SmsNotification extends NotificationChannel {  protected function validateConfig(): void  {  $required = ['api_key', 'sender_id'];    foreach ($required as $key) {  if (!isset($this->config[$key])) {  throw new InvalidArgumentException("Missing SMS config: $key");  }  }  }    protected function formatMessage(string $message): string  {  // SMS has character limits  if (strlen($message) > 160) {  $message = substr($message, 0, 157) . '...';  }    return $message;  }    protected function deliverMessage(string $message): bool  {  // Simulate SMS sending  foreach ($this->recipients as $phone) {  echo "Sending SMS to: $phone\n";  // In real implementation, use Twilio or similar API  }    return true;  }    protected function isValidRecipient($recipient): bool  {  // Basic phone number validation  return preg_match('/^\+?[1-9]\d{1,14}$/', $recipient);  }    protected function validateMessage(string $message): void  {  parent::validateMessage($message);    // SMS-specific validation  if (strlen($message) > 160) {  echo "Warning: SMS message will be truncated\n";  }  } }  /**  * Push notification implementation  */ class PushNotification extends NotificationChannel {  protected function validateConfig(): void  {  $required = ['app_id', 'api_key'];    foreach ($required as $key) {  if (!isset($this->config[$key])) {  throw new InvalidArgumentException("Missing push config: $key");  }  }  }    protected function formatMessage(string $message): string  {  return json_encode([  'title' => 'Notification',  'body' => $message,  'timestamp' => time(),  'priority' => 'high'  ]);  }    protected function deliverMessage(string $message): bool  {  // Simulate push notification  foreach ($this->recipients as $deviceToken) {  echo "Sending push notification to device: " . substr($deviceToken, 0, 10) . "...\n";  // In real implementation, use Firebase Cloud Messaging or similar  }    return true;  }    protected function isValidRecipient($recipient): bool  {  // Basic device token validation (simplified)  return strlen($recipient) > 20 && ctype_alnum($recipient);  } }  /**  * Abstract notification factory  */ abstract class NotificationFactory {  /**  * Factory method for creating notification channels  */  abstract public function createChannel(array $config): NotificationChannel;    /**  * Template method for sending notifications  */  public function sendNotification(string $type, string $message, array $recipients, array $config = []): bool  {  $channel = $this->createChannel($config);  return $channel->send($message, $recipients);  } }  /**  * Concrete email factory  */ class EmailNotificationFactory extends NotificationFactory {  public function createChannel(array $config): NotificationChannel  {  $defaultConfig = [  'smtp_host' => 'smtp.example.com',  'smtp_port' => 587,  'username' => '[email protected]',  'password' => 'secret'  ];    return new EmailNotification(array_merge($defaultConfig, $config));  } }  /**  * Concrete SMS factory  */ class SmsNotificationFactory extends NotificationFactory {  public function createChannel(array $config): NotificationChannel  {  $defaultConfig = [  'api_key' => 'sms_api_key',  'sender_id' => 'MyApp'  ];    return new SmsNotification(array_merge($defaultConfig, $config));  } }  // Usage examples echo "\n=== Advanced Abstract Class Examples ===\n";  // Email notifications $emailFactory = new EmailNotificationFactory(); $emailNotification = $emailFactory->createChannel([]);  $emailRecipients = ['[email protected]', '[email protected]', 'invalid-email']; $emailNotification->send('Welcome to our service!', $emailRecipients);  // SMS notifications $smsFactory = new SmsNotificationFactory(); $smsNotification = $smsFactory->createChannel([]);  $smsRecipients = ['+1234567890', '+0987654321', 'invalid-phone']; $smsNotification->send('Your verification code is: 123456', $smsRecipients);  // Push notifications $pushNotification = new PushNotification([  'app_id' => 'myapp123',  'api_key' => 'push_api_key' ]);  $deviceTokens = ['abcdef1234567890abcdef1234567890', 'fedcba0987654321fedcba0987654321']; $pushNotification->send('You have a new message!', $deviceTokens);  // Polymorphic usage function broadcastMessage(string $message, array $channels): void {  foreach ($channels as $channel) {  if ($channel instanceof NotificationChannel) {  try {  $channel->send($message, []);  } catch (Exception $e) {  echo "Failed to send via " . get_class($channel) . ": " . $e->getMessage() . "\n";  }  }  } }  // Would need to set recipients first in real usage echo "\nBroadcasting message across channels:\n"; // broadcastMessage('System maintenance tonight at 2 AM', [$emailNotification, $smsNotification, $pushNotification]); ?> 

For more PHP object-oriented programming topics:

Summary

Abstract classes provide powerful tools for creating structured object-oriented designs:

Design Contracts: Define common interfaces while requiring specific implementations from subclasses for consistent behavior.

Code Reuse: Implement shared functionality once in abstract classes and inherit across multiple concrete implementations.

Template Method Pattern: Define algorithm structure in abstract classes while allowing subclasses to customize specific steps.

Inheritance Hierarchies: Create clear, well-structured inheritance trees with abstract base classes defining common behavior.

Factory Patterns: Use abstract classes as foundations for factory patterns that create related object families.

Best Practices: Balance abstract and concrete methods, use final methods to prevent overriding critical logic, and provide clear documentation for abstract method requirements.

Abstract classes enable sophisticated object-oriented designs that promote maintainability, consistency, and proper separation of concerns in PHP applications.