Skip to main content

Course Progress

Loading...

Method Overriding in PHP: Customizing Inherited Behavior

Duration: 30 minutes
Module 2: Object-Oriented PHP

Learning Objectives

  • Master PHP programming concepts
  • Write clean, maintainable code
  • Apply best practices
  • Build dynamic applications

Customizing Inherited Behavior

Welcome to our deep dive into method overriding in PHP! Method overriding is a fundamental concept in object-oriented programming that allows child classes to provide specialized implementations of methods that are already defined in their parent classes.

Why Method Overriding Matters: Method overriding is essential for creating flexible, extensible applications. In WordPress development, overriding methods allows you to customize themes and plugins without modifying core files, making your code more maintainable and update-resistant.

The Art of Method Overriding

Method overriding is like inheriting a family recipe and then adding your own twist to it. The original recipe (parent method) provides a foundation, but your version (overridden method) tailors it to your specific taste while keeping the core essence intact.

Method Overriding Flow

Diagram
Sequence Diagram (Diagram converted to static representation) sequenceDiagram participant Client participant Chi...

Basic Mechanism of Method Overriding

When a child class inherits from a parent class, it can provide its own implementation of a method that already exists in the parent class. When the method is called on an instance of the child class, PHP uses the child's version instead of the parent's.

Simple Method Overriding Example


// Parent class
class Vehicle {
    protected $make;
    protected $model;
    
    public function __construct($make, $model) {
        $this->make = $make;
        $this->model = $model;
    }
    
    public function getInfo() {
        return "Vehicle: {$this->make} {$this->model}";
    }
    
    public function start() {
        return "The vehicle is starting the engine...";
    }
}

// Child class
class Car extends Vehicle {
    protected $doors;
    
    public function __construct($make, $model, $doors) {
        parent::__construct($make, $model);
        $this->doors = $doors;
    }
    
    // Override the getInfo method
    public function getInfo() {
        return "Car: {$this->make} {$this->model} with {$this->doors} doors";
    }
}

// Create instances
$vehicle = new Vehicle("Generic", "Transport");
$car = new Car("Toyota", "Corolla", 4);

// Output
echo $vehicle->getInfo(); // Output: "Vehicle: Generic Transport"
echo "
"; echo $car->getInfo(); // Output: "Car: Toyota Corolla with 4 doors" echo "
"; echo $car->start(); // Output: "The vehicle is starting the engine..." (inherited, not overridden)

In this example, the Car class overrides the getInfo() method to include information about doors, but inherits the start() method unchanged from the Vehicle class.

The Recipe Analogy

Think of method overriding like cooking:

  • The parent class (Grandmother) has a cookie recipe with specific ingredients and instructions.
  • The child class (You) inherits this recipe but wants to modify it slightly – perhaps adding chocolate chips or changing the baking time.
  • You keep the core essence of the recipe (method signature) but customize the implementation to suit your specific needs.
  • Anyone asking you for cookies gets your version of the recipe, not your grandmother's original.

This analogy highlights how overriding preserves the method's contract while allowing customization of behavior.

Rules and Requirements for Method Overriding

Method overriding in PHP must follow several important rules to maintain consistency and expected behavior:

Method Overriding Rules

Rule Description Example
Same Method Name The method in the child class must have the exact same name as the method in the parent class. getInfo() in both parent and child
Compatible Parameter Count The overriding method should accept at least the same number of parameters as the parent method. parent: process($data)
child: process($data, $options = [])
Type Compatibility Parameter types must be compatible (same or less specific) and return types must be compatible (same or more specific). Parameter: parent accepts Animal, child accepts Animal or any parent type.
Return: parent returns Animal, child returns Animal or any child type.
Access Modifier The overriding method can have the same or less restrictive access modifier than the parent method. If parent is protected, child can be protected or public, but not private.
Static Consistency If the parent method is static, the child method must also be static, and vice versa. Cannot override a non-static method with a static method.

Visibility Rules in Detail

One of the most important rules involves visibility (access modifiers). The overriding method can have the same or a less restrictive visibility than the parent method, but it cannot be more restrictive:

Diagram
can be overridden as private private protected protected public public public Warning: Cannot override with more restrictive access

Visibility Override Example


class ParentClass {
    // Private method - only accessible within this class
    private function privateMethod() {
        return "This is private";
    }
    
    // Protected method - accessible within this class and child classes
    protected function protectedMethod() {
        return "This is protected";
    }
    
    // Public method - accessible from anywhere
    public function publicMethod() {
        return "This is public";
    }
    
    // Public method that uses the private method
    public function usePrivateMethod() {
        return $this->privateMethod();
    }
}

class ChildClass extends ParentClass {
    // Cannot override private methods (they're not inherited)
    // This is a new method, not an override
    private function privateMethod() {
        return "Child private method";
    }
    
    // Override protected with protected (same visibility)
    protected function protectedMethod() {
        return "Child protected method";
    }
    
    // Override public with public (same visibility)
    public function publicMethod() {
        return "Child public method";
    }
    
    // This would cause an error - cannot make visibility more restrictive
    // protected function publicMethod() {
    //     return "This will cause an error";
    // }
    
    // This is valid - making visibility less restrictive
    public function protectedMethod2() {
        // Call the parent's protected method
        return "Public wrapper: " . parent::protectedMethod();
    }
}

$child = new ChildClass();
echo $child->publicMethod(); // "Child public method"
echo $child->usePrivateMethod(); // "This is private" (uses parent's privateMethod)
echo $child->protectedMethod2(); // "Public wrapper: Child protected method"
                

Type Declarations and Covariance/Contravariance

Since PHP 7.4, method overriding supports return type covariance and parameter type contravariance. These concepts allow more flexibility when overriding methods:

  • Return Type Covariance: The overriding method can return a more specific type than the parent method.
  • Parameter Type Contravariance: The overriding method can accept a less specific type than the parent method.

// Example of return type covariance
class Animal {}
class Dog extends Animal {}

class AnimalShelter {
    public function getAnimal(): Animal {
        return new Animal();
    }
}

class DogShelter extends AnimalShelter {
    // Return type is more specific (covariance)
    public function getAnimal(): Dog {
        return new Dog();
    }
}

// Example of parameter type contravariance
class Feed {
    public function feedAnimal(Dog $animal) {
        // Feed only dogs
    }
}

class SuperFeed extends Feed {
    // Parameter type is less specific (contravariance)
    public function feedAnimal(Animal $animal) {
        // Can feed any animal
    }
}
                    

Extending Parent Methods with parent::

Often, you don't want to completely replace the parent method's behavior, but rather extend it. The parent:: keyword lets you call the parent's version of a method from within the overriding method.

Extending vs. Replacing Parent Methods

Diagram
Then adds its own behavior Complete Replacement New Implementation Extension Parent Implementation Child's Additional Behavior

Using parent:: to Extend Functionality


class Logger {
    public function log($message) {
        $timestamp = date('Y-m-d H:i:s');
        return "[{$timestamp}] {$message}";
    }
}

class DetailedLogger extends Logger {
    protected $logLevel;
    
    public function __construct($logLevel = 'INFO') {
        $this->logLevel = $logLevel;
    }
    
    // Override with extension, not replacement
    public function log($message) {
        // First, get the parent's log format
        $parentLog = parent::log($message);
        
        // Then enhance it with additional information
        return "{$parentLog} [Level: {$this->logLevel}]";
    }
}

// Usage
$logger = new Logger();
$detailedLogger = new DetailedLogger('WARNING');

echo $logger->log("System started"); 
// Output: "[2025-04-28 10:15:22] System started"

echo "
"; echo $detailedLogger->log("Low memory detected"); // Output: "[2025-04-28 10:15:22] Low memory detected [Level: WARNING]"

Common Use Cases for parent::

  • Constructors: Calling the parent constructor before adding child-specific initialization
  • Adding functionality: Extending the parent's method with additional behaviors
  • Pre/post processing: Running code before or after the parent's implementation
  • Filtering results: Modifying the return value of the parent's method
  • Validation: Adding additional validation before calling the parent method

Constructor Overriding Example


class Product {
    protected $name;
    protected $price;
    
    public function __construct($name, $price) {
        $this->name = $name;
        $this->price = $price;
    }
    
    public function getDetails() {
        return [
            'name' => $this->name,
            'price' => $this->price
        ];
    }
}

class DigitalProduct extends Product {
    protected $downloadUrl;
    protected $fileSize;
    
    public function __construct($name, $price, $downloadUrl, $fileSize) {
        // Call parent constructor first
        parent::__construct($name, $price);
        
        // Then add child-specific properties
        $this->downloadUrl = $downloadUrl;
        $this->fileSize = $fileSize;
    }
    
    public function getDetails() {
        // Get the parent's details
        $details = parent::getDetails();
        
        // Add child-specific details
        $details['download_url'] = $this->downloadUrl;
        $details['file_size'] = $this->fileSize;
        $details['type'] = 'digital';
        
        return $details;
    }
}

$ebook = new DigitalProduct(
    'PHP Mastery', 
    29.99, 
    'https://example.com/downloads/php-mastery.pdf', 
    '15MB'
);

print_r($ebook->getDetails());
/* Output:
Array
(
    [name] => PHP Mastery
    [price] => 29.99
    [download_url] => https://example.com/downloads/php-mastery.pdf
    [file_size] => 15MB
    [type] => digital
)
*/
                

Best Practices for Calling parent::

  • Always call parent::__construct() when overriding constructors
  • Call parent:: at the beginning of the method for pre-processing (validation)
  • Call parent:: at the end of the method for post-processing (augmenting results)
  • Be careful with conditional calls to parent:: as it can make code harder to follow
  • Document why you're overriding and what parent functionality you're preserving

Method Overriding in the Real World

Example 1: WordPress Widgets

WordPress widgets use method overriding extensively. Each widget type extends the WP_Widget base class and overrides specific methods to customize behavior.

Custom WordPress Widget Example


class WP_Widget {
    protected $id_base;
    protected $name;
    protected $options;
    
    public function __construct($id_base, $name, $options = []) {
        $this->id_base = $id_base;
        $this->name = $name;
        $this->options = $options;
    }
    
    // Default widget output method
    public function widget($args, $instance) {
        echo "Default widget output";
    }
    
    // Default form for widget settings
    public function form($instance) {
        echo "Default form";
    }
    
    // Default method to save widget settings
    public function update($new_instance, $old_instance) {
        return $new_instance;
    }
}

class Latest_Posts_Widget extends WP_Widget {
    public function __construct() {
        // Call parent constructor with specific widget details
        parent::__construct(
            'latest_posts',
            'Latest Posts Widget',
            ['description' => 'Display the most recent posts']
        );
    }
    
    // Override widget output
    public function widget($args, $instance) {
        // Extract arguments provided by WordPress
        extract($args);
        
        // Get the widget settings
        $title = !empty($instance['title']) ? $instance['title'] : 'Latest Posts';
        $count = !empty($instance['count']) ? (int) $instance['count'] : 5;
        
        // Widget start (from theme)
        echo $before_widget;
        echo $before_title . $title . $after_title;
        
        // Widget custom content
        echo "
    "; for ($i = 1; $i <= $count; $i++) { echo "
  • Example Post {$i}
  • "; } echo "
"; // Widget end (from theme) echo $after_widget; } // Override settings form public function form($instance) { // Get saved settings or defaults $title = !empty($instance['title']) ? $instance['title'] : 'Latest Posts'; $count = !empty($instance['count']) ? (int) $instance['count'] : 5; // Output form fields echo "

"; echo ""; echo ""; echo "

"; echo "

"; echo ""; echo ""; echo "

"; } // Override update method public function update($new_instance, $old_instance) { // Start with parent's update method $instance = parent::update($new_instance, $old_instance); // Add our custom sanitization $instance['title'] = sanitize_text_field($new_instance['title']); $instance['count'] = max(1, min(10, (int) $new_instance['count'])); return $instance; } }

Example 2: WooCommerce Product Types

WooCommerce, the popular e-commerce plugin for WordPress, uses method overriding to handle different product types with varying pricing, shipping, and tax calculations.

Simplified WooCommerce Product Classes


abstract class WC_Product {
    protected $id;
    protected $name;
    protected $price;
    protected $stock_quantity;
    
    public function __construct($id, $name, $price, $stock_quantity) {
        $this->id = $id;
        $this->name = $name;
        $this->price = $price;
        $this->stock_quantity = $stock_quantity;
    }
    
    // Base methods
    public function get_price() {
        return $this->price;
    }
    
    public function is_in_stock() {
        return $this->stock_quantity > 0;
    }
    
    // Abstract method all product types must implement
    abstract public function calculate_tax($tax_rate);
}

// Simple physical product
class WC_Product_Simple extends WC_Product {
    protected $weight;
    protected $dimensions;
    
    public function __construct($id, $name, $price, $stock_quantity, $weight, $dimensions) {
        parent::__construct($id, $name, $price, $stock_quantity);
        $this->weight = $weight;
        $this->dimensions = $dimensions;
    }
    
    // Override to include tax
    public function get_price() {
        $base_price = parent::get_price();
        return $base_price + $this->calculate_tax(0.1); // 10% tax rate
    }
    
    public function calculate_tax($tax_rate) {
        return $this->price * $tax_rate;
    }
    
    // Shipping-specific method
    public function get_shipping_cost($zone) {
        return $this->weight * $zone->rate_per_kg;
    }
}

// Digital product (no shipping)
class WC_Product_Digital extends WC_Product {
    protected $download_url;
    protected $file_size;
    
    public function __construct($id, $name, $price, $stock_quantity, $download_url, $file_size) {
        parent::__construct($id, $name, $price, $stock_quantity);
        $this->download_url = $download_url;
        $this->file_size = $file_size;
    }
    
    // Digital products are always in stock
    public function is_in_stock() {
        return true;
    }
    
    // Different tax calculation for digital goods
    public function calculate_tax($tax_rate) {
        return $this->price * ($tax_rate * 0.5); // Half tax rate for digital goods
    }
    
    // Digital-specific method
    public function get_download_link($order_id) {
        return $this->download_url . "?order={$order_id}&time=" . time();
    }
}

// Usage
$physical = new WC_Product_Simple(1, 'Coffee Mug', 14.99, 10, 0.5, ['height' => 10, 'width' => 8, 'depth' => 8]);
$digital = new WC_Product_Digital(2, 'E-book', 9.99, 0, 'https://example.com/downloads/ebook.pdf', '2.5MB');

echo "Physical product price: $" . $physical->get_price() . "
"; // Includes tax echo "Digital product price: $" . $digital->get_price() . "
"; echo "Physical product in stock: " . ($physical->is_in_stock() ? 'Yes' : 'No') . "
"; echo "Digital product in stock: " . ($digital->is_in_stock() ? 'Yes' : 'No') . "
"; // Always Yes

Example 3: WordPress Theme Customizer

WordPress theme customization relies heavily on method overriding to customize how different control types are rendered.

WordPress Customizer Control Classes


class WP_Customize_Control {
    protected $id;
    protected $label;
    protected $type;
    protected $settings;
    
    public function __construct($id, $label, $settings, $args = []) {
        $this->id = $id;
        $this->label = $label;
        $this->settings = $settings;
        $this->type = isset($args['type']) ? $args['type'] : 'text';
    }
    
    // Render the control's content
    public function render_content() {
        echo "{$this->label}";
        echo "";
    }
}

// Color picker control
class WP_Customize_Color_Control extends WP_Customize_Control {
    // Override to render a color picker instead of a text input
    public function render_content() {
        // Call parent for the label
        parent::render_content();
        
        // Replace input with color picker
        echo "";
        echo "Select a color";
    }
}

// Image upload control
class WP_Customize_Image_Control extends WP_Customize_Control {
    // Completely override the render method
    public function render_content() {
        echo "{$this->label}";
        echo "
"; echo "Current image"; echo "
"; echo ""; echo ""; echo ""; } } // Usage in a theme function theme_customizer_setup($wp_customize) { // Add a text setting $wp_customize->add_setting('site_title_color', [ 'default' => '#000000', 'transport' => 'refresh', ]); // Add a color control $wp_customize->add_control(new WP_Customize_Color_Control( 'site_title_color', 'Site Title Color', 'site_title_color', ['section' => 'title_tagline'] )); // Add an image setting $wp_customize->add_setting('header_image', [ 'default' => '', 'transport' => 'refresh', ]); // Add an image control $wp_customize->add_control(new WP_Customize_Image_Control( 'header_image', 'Header Image', 'header_image', ['section' => 'header_settings'] )); }

Advanced Topics in Method Overriding

Final Methods: Preventing Overrides

Sometimes, you want to prevent a method from being overridden in child classes. PHP provides the final keyword for this purpose.

Final Method Example


class PaymentProcessor {
    public function processPayment($amount) {
        $this->validateAmount($amount);
        $this->logTransaction($amount);
        return $this->sendPaymentRequest($amount);
    }
    
    // This method can be overridden
    protected function validateAmount($amount) {
        if ($amount <= 0) {
            throw new Exception("Amount must be positive");
        }
    }
    
    // Critical security method - cannot be overridden
    final protected function logTransaction($amount) {
        // Secure logging implementation
        $timestamp = date('Y-m-d H:i:s');
        $transaction_id = uniqid('txn_');
        echo "LOGGED: [{$timestamp}] Transaction {$transaction_id} for \${$amount} initiated.
"; } // This method should be overridden by specific payment providers protected function sendPaymentRequest($amount) { throw new Exception("Payment method not implemented"); } } class StripePaymentProcessor extends PaymentProcessor { // Override validation to add Stripe-specific rules protected function validateAmount($amount) { // Call parent validation first parent::validateAmount($amount); // Add Stripe-specific validation if ($amount < 0.50) { throw new Exception("Stripe requires a minimum charge of $0.50"); } } // Cannot override final method // This would cause a fatal error // protected function logTransaction($amount) { // // Custom implementation // } // Implement required method protected function sendPaymentRequest($amount) { // Stripe-specific implementation echo "Processing \${$amount} payment through Stripe.
"; return "stripe_" . uniqid(); } } // Usage $processor = new StripePaymentProcessor(); $processor->processPayment(25.99);

Private Methods and Overriding

It's important to understand that private methods are not inherited by child classes, so they cannot be overridden in the traditional sense. If a child class defines a method with the same name as a private method in the parent, it's creating a new method, not overriding the parent's method.

Private Methods vs. Overriding


class ParentClass {
    public function publicMethod() {
        echo "Public method in parent
"; $this->privateMethod(); } private function privateMethod() { echo "Private method in parent
"; } } class ChildClass extends ParentClass { // This is NOT overriding - it's a completely separate method private function privateMethod() { echo "Private method in child
"; } } $parent = new ParentClass(); $child = new ChildClass(); $parent->publicMethod(); // Output: // Public method in parent // Private method in parent $child->publicMethod(); // Output: // Public method in parent // Private method in parent (not child!)

Notice that when $child->publicMethod() is called, it still uses the parent's private method, not the child's. This is because the child's method with the same name is a completely different method that is not accessible to the inherited public method.

Design Considerations

  • Use final methods when:
    • A method implements critical security functionality
    • Overriding would break the class's internal logic
    • You want to enforce a specific algorithm
  • Use private methods when:
    • The functionality is purely internal to the class
    • The method should not be part of the class's API
    • You want to prevent any form of external access or modification
  • Use protected methods when:
    • You want child classes to be able to override behavior
    • The method is part of the inheritance API but not the public API
    • You're designing extension points for child classes

Common Method Overriding Patterns

Method overriding is used in several common design patterns. Understanding these patterns can help you structure your code more effectively.

The Template Method Pattern

The Template Method pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. It lets subclasses redefine certain steps without changing the algorithm's structure.

Template Method Pattern

Diagram
Class Diagram (Diagram converted to static representation) classDiagram class AbstractClass { +templateMethod...

Template Method Pattern Example


abstract class ReportGenerator {
    // The template method defines the algorithm structure
    public function generateReport($data) {
        $this->validateData($data);
        $processedData = $this->processData($data);
        $formattedReport = $this->formatReport($processedData);
        $this->deliverReport($formattedReport);
        
        return $formattedReport;
    }
    
    // Common validation logic
    protected function validateData($data) {
        if (empty($data)) {
            throw new Exception("Data cannot be empty");
        }
    }
    
    // Abstract methods that must be implemented by subclasses
    abstract protected function processData($data);
    abstract protected function formatReport($data);
    
    // Hook method with default implementation
    protected function deliverReport($report) {
        echo "Report generated and ready for delivery.
"; } } class PDFReportGenerator extends ReportGenerator { protected function processData($data) { echo "Processing data for PDF report...
"; // PDF-specific data processing return array_map(function($item) { return strtoupper($item); // Convert to uppercase for PDF }, $data); } protected function formatReport($data) { echo "Formatting as PDF...
"; // PDF-specific formatting return "PDF_REPORT: " . implode(", ", $data); } // Override hook method protected function deliverReport($report) { parent::deliverReport($report); echo "Saving PDF to disk...
"; } } class CSVReportGenerator extends ReportGenerator { protected function processData($data) { echo "Processing data for CSV report...
"; // CSV-specific data processing return $data; // No special processing for CSV } protected function formatReport($data) { echo "Formatting as CSV...
"; // CSV-specific formatting return "CSV_REPORT: " . implode(",", $data); } // Override hook method protected function deliverReport($report) { parent::deliverReport($report); echo "Emailing CSV report...
"; } } // Usage $data = ['sales' => 1000, 'expenses' => 700, 'profit' => 300]; $pdfGenerator = new PDFReportGenerator(); $pdfReport = $pdfGenerator->generateReport($data); echo "Result: {$pdfReport}

"; $csvGenerator = new CSVReportGenerator(); $csvReport = $csvGenerator->generateReport($data); echo "Result: {$csvReport}
";

The Strategy Pattern

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.

Strategy Pattern Example


// Strategy interface
interface PaymentStrategy {
    public function pay($amount);
    public function getPaymentDetails();
}

// Concrete strategies
class CreditCardPayment implements PaymentStrategy {
    private $cardNumber;
    private $expiryDate;
    private $cvv;
    
    public function __construct($cardNumber, $expiryDate, $cvv) {
        $this->cardNumber = $cardNumber;
        $this->expiryDate = $expiryDate;
        $this->cvv = $cvv;
    }
    
    public function pay($amount) {
        // Credit card payment processing logic
        echo "Processing credit card payment of ${$amount}...
"; return "cc_transaction_" . rand(10000, 99999); } public function getPaymentDetails() { return "Credit Card: " . substr($this->cardNumber, -4); } } class PayPalPayment implements PaymentStrategy { private $email; private $password; public function __construct($email, $password) { $this->email = $email; $this->password = $password; } public function pay($amount) { // PayPal payment processing logic echo "Processing PayPal payment of ${$amount}...
"; return "pp_transaction_" . rand(10000, 99999); } public function getPaymentDetails() { return "PayPal: " . $this->email; } } // Context class class ShoppingCart { private $paymentStrategy; private $items = []; public function setPaymentStrategy(PaymentStrategy $paymentStrategy) { $this->paymentStrategy = $paymentStrategy; } public function addItem($item, $price) { $this->items[$item] = $price; } public function calculateTotal() { return array_sum($this->items); } public function checkout() { $amount = $this->calculateTotal(); if (!$this->paymentStrategy) { throw new Exception("Payment method not set"); } echo "Checking out with " . $this->paymentStrategy->getPaymentDetails() . "
"; echo "Cart total: ${$amount}
"; $transactionId = $this->paymentStrategy->pay($amount); echo "Transaction complete: {$transactionId}
"; } } // Usage $cart = new ShoppingCart(); $cart->addItem("PHP Book", 29.99); $cart->addItem("Hosting (1 month)", 9.99); // Pay with credit card $cart->setPaymentStrategy(new CreditCardPayment("1234567890123456", "12/26", "123")); $cart->checkout(); echo "
"; // Pay with PayPal $cart->setPaymentStrategy(new PayPalPayment("user@example.com", "password")); $cart->checkout();

The Factory Method Pattern

The Factory Method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. It lets a class defer instantiation to subclasses.

Factory Method Pattern Example


// Product interface
interface Logger {
    public function log($message);
}

// Concrete products
class FileLogger implements Logger {
    private $filePath;
    
    public function __construct($filePath) {
        $this->filePath = $filePath;
        echo "Creating FileLogger to {$filePath}
"; } public function log($message) { echo "FileLogger: Writing message to {$this->filePath}: {$message}
"; } } class DatabaseLogger implements Logger { private $connection; public function __construct($connection) { $this->connection = $connection; echo "Creating DatabaseLogger with connection {$connection}
"; } public function log($message) { echo "DatabaseLogger: Saving message to database ({$this->connection}): {$message}
"; } } // Creator (abstract factory) abstract class LoggerFactory { // The factory method abstract protected function createLogger(); // Template method that uses the factory method public function logMessage($message) { $logger = $this->createLogger(); $logger->log($message); } } // Concrete creators class FileLoggerFactory extends LoggerFactory { protected function createLogger() { return new FileLogger("/var/log/app.log"); } } class DatabaseLoggerFactory extends LoggerFactory { protected function createLogger() { return new DatabaseLogger("mysql://localhost/logs"); } } // Client code function clientCode(LoggerFactory $factory) { $factory->logMessage("System started"); $factory->logMessage("User logged in"); } // Usage echo "Using FileLoggerFactory:
"; clientCode(new FileLoggerFactory()); echo "
Using DatabaseLoggerFactory:
"; clientCode(new DatabaseLoggerFactory());

Best Practices for Method Overriding

Follow the Liskov Substitution Principle

The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. When overriding methods, make sure your subclass behavior doesn't violate the expectations set by the parent class.

Bad Example


class Rectangle {
    protected $width;
    protected $height;
    
    public function setWidth($width) {
        $this->width = $width;
    }
    
    public function setHeight($height) {
        $this->height = $height;
    }
    
    public function getArea() {
        return $this->width * $this->height;
    }
}

// Violates Liskov Substitution Principle
class Square extends Rectangle {
    // Override to maintain square properties
    public function setWidth($width) {
        $this->width = $width;
        $this->height = $width; // Side effect!
    }
    
    // Override to maintain square properties
    public function setHeight($height) {
        $this->width = $height; // Side effect!
        $this->height = $height;
    }
}

// This code breaks with Square
function resizeRectangle(Rectangle $rectangle) {
    $rectangle->setWidth(5);
    $rectangle->setHeight(4);
    
    // With Rectangle: 5 * 4 = 20
    // With Square: 4 * 4 = 16 (Unexpected!)
    return $rectangle->getArea();
}

$rectangle = new Rectangle();
$area1 = resizeRectangle($rectangle);
echo "Rectangle area: {$area1}
"; // 20 $square = new Square(); $area2 = resizeRectangle($square); echo "Square area: {$area2}
"; // 16 (Unexpected!)

Document Your Overrides

Always use PHPDoc comments to document your method overrides. This helps other developers understand your intentions and any differences from the parent method.


class ParentClass {
    /**
     * Process the given data.
     *
     * @param array $data The data to process
     * @return array Processed data
     */
    public function processData(array $data) {
        // Processing logic
        return $data;
    }
}

class ChildClass extends ParentClass {
    /**
     * {@inheritdoc}
     *
     * Overrides parent method to add additional validation
     * and format the output as JSON strings.
     *
     * @param array $data The data to process
     * @return array Processed data with JSON-encoded values
     */
    public function processData(array $data) {
        // Call parent implementation
        $processed = parent::processData($data);
        
        // Add additional processing
        foreach ($processed as &$value) {
            $value = json_encode($value);
        }
        
        return $processed;
    }
}
                        

Be Consistent with Method Signatures

Keep method signatures consistent to avoid unexpected behavior. Use type declarations to enforce type compatibility.


class ParentClass {
    /**
     * Find a user by ID.
     *
     * @param int $id User ID
     * @return array|null User data or null if not found
     */
    public function findUser(int $id): ?array {
        // Implementation
    }
}

class ChildClass extends ParentClass {
    /**
     * {@inheritdoc}
     */
    public function findUser(int $id): ?array {
        // Implementation with same signature
    }
}
                        

Consider Performance Implications

Method overriding can have performance implications, especially when calling parent methods. Be mindful of performance-critical sections.

Performance-Aware Overriding


class CacheableContent {
    protected $cache = [];
    
    public function getData($key) {
        if (isset($this->cache[$key])) {
            return $this->cache[$key];
        }
        
        $data = $this->fetchData($key);
        $this->cache[$key] = $data;
        
        return $data;
    }
    
    protected function fetchData($key) {
        // Expensive operation
        sleep(1); // Simulate slow operation
        return "Original data for {$key}";
    }
}

class EnhancedCacheableContent extends CacheableContent {
    private $enhancedCache = [];
    
    // Override with enhanced caching
    public function getData($key) {
        // Enhanced caching layer
        $cacheKey = "enhanced_{$key}";
        
        if (isset($this->enhancedCache[$cacheKey])) {
            return $this->enhancedCache[$cacheKey];
        }
        
        // Don't call parent::getData() which would use another cache layer
        // Instead, call fetchData directly if needed
        if (!isset($this->cache[$key])) {
            $this->cache[$key] = $this->fetchData($key);
        }
        
        // Enhance the data
        $enhancedData = $this->enhance($this->cache[$key]);
        $this->enhancedCache[$cacheKey] = $enhancedData;
        
        return $enhancedData;
    }
    
    protected function enhance($data) {
        return "Enhanced: " . $data;
    }
}
                        

Use Method Overriding for Extension, Not Modification

Method overriding is best used to extend functionality, not to drastically change behavior. If you need to change behavior substantially, consider composition over inheritance.

Good Use of Overriding


class BasicAuthenticator {
    public function authenticate($username, $password) {
        // Basic validation
        if (empty($username) || empty($password)) {
            return false;
        }
        
        // Check credentials
        $valid = $this->checkCredentials($username, $password);
        
        if ($valid) {
            $this->logSuccess($username);
        } else {
            $this->logFailure($username);
        }
        
        return $valid;
    }
    
    protected function checkCredentials($username, $password) {
        // Simple check (in a real app, this would check a database)
        return ($username === 'admin' && $password === 'password');
    }
    
    protected function logSuccess($username) {
        echo "User {$username} logged in successfully
"; } protected function logFailure($username) { echo "Failed login attempt for user {$username}
"; } } class EnhancedAuthenticator extends BasicAuthenticator { private $maxAttempts = 3; private $attemptCount = []; // Override to add rate limiting protected function logFailure($username) { parent::logFailure($username); // Add rate limiting logic if (!isset($this->attemptCount[$username])) { $this->attemptCount[$username] = 0; } $this->attemptCount[$username]++; if ($this->attemptCount[$username] >= $this->maxAttempts) { echo "Account {$username} locked due to too many failed attempts
"; } } // Override to add secure credential checking protected function checkCredentials($username, $password) { // Check if account is locked if (isset($this->attemptCount[$username]) && $this->attemptCount[$username] >= $this->maxAttempts) { return false; } // Use parent method for actual credential checking return parent::checkCredentials($username, $password); } } // Usage $auth = new EnhancedAuthenticator(); // Test with wrong password $auth->authenticate('admin', 'wrong1'); $auth->authenticate('admin', 'wrong2'); $auth->authenticate('admin', 'wrong3'); // This attempt fails because account is locked $auth->authenticate('admin', 'password');

Homework: Extend Your Previous Class with Inheritance

Assignment: Method Overriding Practice

For this assignment, you'll take the class you created in the previous session and extend it with proper method overriding. The goal is to practice creating child classes that override parent methods in various ways.

Requirements:

  1. Start with the simple class you created in the previous homework, or use the example class below.
  2. Create at least two child classes that extend your original class.
  3. In each child class, override at least two methods from the parent class.
  4. Use different overriding approaches:
    • Complete replacement (new implementation)
    • Extension with parent:: call at the beginning
    • Extension with parent:: call at the end
    • Conditional parent:: call
  5. Include proper documentation (PHPDoc comments) for each overridden method.
  6. Create a test script that demonstrates the different behaviors of the parent and child classes.

Example Starting Class:


/**
 * Product class representing a basic product in an e-commerce system.
 */
class Product {
    protected $name;
    protected $price;
    protected $sku;
    protected $description;
    
    /**
     * Create a new product instance.
     *
     * @param string $name Product name
     * @param float $price Product price
     * @param string $sku Stock keeping unit
     * @param string $description Product description
     */
    public function __construct($name, $price, $sku, $description = '') {
        $this->name = $name;
        $this->price = $price;
        $this->sku = $sku;
        $this->description = $description;
    }
    
    /**
     * Get the product details as an array.
     *
     * @return array
     */
    public function getDetails() {
        return [
            'name' => $this->name,
            'price' => $this->price,
            'sku' => $this->sku,
            'description' => $this->description
        ];
    }
    
    /**
     * Calculate the final price including tax.
     *
     * @param float $taxRate The tax rate (0.1 for 10%)
     * @return float
     */
    public function calculatePriceWithTax($taxRate = 0.1) {
        return $this->price * (1 + $taxRate);
    }
    
    /**
     * Get formatted product information.
     *
     * @return string
     */
    public function getFormattedInfo() {
        return "Product: {$this->name} (SKU: {$this->sku}) - \${$this->price}";
    }
    
    /**
     * Check if the product is in a specific price range.
     *
     * @param float $minPrice Minimum price
     * @param float $maxPrice Maximum price
     * @return bool
     */
    public function isInPriceRange($minPrice, $maxPrice) {
        return $this->price >= $minPrice && $this->price <= $maxPrice;
    }
}
                

Starter Code for Child Classes:


/**
 * DigitalProduct extends the basic Product with digital-specific features.
 */
class DigitalProduct extends Product {
    protected $downloadUrl;
    protected $fileSizeInMB;
    
    /**
     * Create a new digital product instance.
     *
     * @param string $name Product name
     * @param float $price Product price
     * @param string $sku Stock keeping unit
     * @param string $description Product description
     * @param string $downloadUrl Download URL
     * @param float $fileSizeInMB File size in megabytes
     */
    public function __construct($name, $price, $sku, $description, $downloadUrl, $fileSizeInMB) {
        // Call parent constructor
        parent::__construct($name, $price, $sku, $description);
        
        // Set digital-specific properties
        $this->downloadUrl = $downloadUrl;
        $this->fileSizeInMB = $fileSizeInMB;
    }
    
    /**
     * {@inheritdoc}
     *
     * Override to include digital product specific details.
     */
    public function getDetails() {
        // TODO: Override this method to add digital-specific details
    }
    
    /**
     * {@inheritdoc}
     *
     * Override to use a different tax rate for digital products.
     */
    public function calculatePriceWithTax($taxRate = 0.05) {
        // TODO: Override this method to use digital-specific tax calculation
    }
}

/**
 * PhysicalProduct extends the basic Product with physical-specific features.
 */
class PhysicalProduct extends Product {
    protected $weight;
    protected $dimensions;
    protected $inStock;
    
    /**
     * Create a new physical product instance.
     *
     * @param string $name Product name
     * @param float $price Product price
     * @param string $sku Stock keeping unit
     * @param string $description Product description
     * @param float $weight Weight in kg
     * @param array $dimensions Array with width, height, depth
     * @param int $inStock Number of items in stock
     */
    public function __construct($name, $price, $sku, $description, $weight, $dimensions, $inStock) {
        // TODO: Call parent constructor
        
        // TODO: Set physical-specific properties
    }
    
    /**
     * {@inheritdoc}
     *
     * Override to include physical product specific details.
     */
    public function getFormattedInfo() {
        // TODO: Override this method to add physical-specific formatting
    }
    
    /**
     * {@inheritdoc}
     *
     * Override to also check stock availability.
     */
    public function isInPriceRange($minPrice, $maxPrice) {
        // TODO: Override this method to include stock check
    }
    
    /**
     * Calculate shipping cost based on weight and distance.
     *
     * @param float $distance Shipping distance in miles
     * @return float
     */
    public function calculateShippingCost($distance) {
        // Base rate + weight factor + distance factor
        return 5 + ($this->weight * 2) + ($distance * 0.1);
    }
}
                

Test Script Template:


// Include your class files
// require_once 'Product.php';
// require_once 'DigitalProduct.php';
// require_once 'PhysicalProduct.php';

// Create instances
$basicProduct = new Product('Basic Widget', 19.99, 'BW-001', 'A simple widget');
$ebook = new DigitalProduct('PHP Mastery', 29.99, 'E-BOOK-123', 'Learn PHP from scratch', 
                          'https://example.com/downloads/php-mastery.pdf', 15.5);
$chair = new PhysicalProduct('Office Chair', 149.99, 'CHAIR-456', 'Ergonomic office chair',
                           12.5, ['width' => 60, 'height' => 120, 'depth' => 65], 5);

// Test method overriding
echo "

Product Details Comparison:

"; echo "
";
print_r($basicProduct->getDetails());
echo "

";
print_r($ebook->getDetails());
echo "

";
print_r($chair->getDetails());
echo "
"; echo "

Price with Tax Comparison:

"; echo "Basic product: $" . number_format($basicProduct->calculatePriceWithTax(), 2) . "
"; echo "Digital product: $" . number_format($ebook->calculatePriceWithTax(), 2) . "
"; echo "Physical product: $" . number_format($chair->calculatePriceWithTax(), 2) . "
"; echo "

Formatted Info Comparison:

"; echo $basicProduct->getFormattedInfo() . "
"; echo $ebook->getFormattedInfo() . "
"; echo $chair->getFormattedInfo() . "
"; echo "

Price Range Check Comparison:

"; $minPrice = 20; $maxPrice = 100; echo "Basic product in range {$minPrice}-{$maxPrice}: " . ($basicProduct->isInPriceRange($minPrice, $maxPrice) ? 'Yes' : 'No') . "
"; echo "Digital product in range {$minPrice}-{$maxPrice}: " . ($ebook->isInPriceRange($minPrice, $maxPrice) ? 'Yes' : 'No') . "
"; echo "Physical product in range {$minPrice}-{$maxPrice}: " . ($chair->isInPriceRange($minPrice, $maxPrice) ? 'Yes' : 'No') . "
"; // Test product-specific methods echo "

Product-Specific Methods:

"; echo "Physical product shipping cost (100 miles): $" . number_format($chair->calculateShippingCost(100), 2) . "
";

Bonus Challenges:

  • Add a third child class SubscriptionProduct with a recurring billing feature
  • Add a method to the parent class that calculates a discount, then override it in each child class with different discount strategies
  • Add error checking and exception handling to the overridden methods
  • Implement a final method in the parent class and explain why it shouldn't be overridden

Key Takeaways

Method Overriding Fundamentals

Method overriding allows child classes to provide specific implementations of methods defined in parent classes, customizing inherited behavior while maintaining the same method signature.

Rules and Requirements

Overriding methods must follow specific rules: same name, compatible parameters, compatible return types, and visibility that is the same or less restrictive than the parent method.

parent:: Keyword

The parent:: keyword allows child classes to extend parent behavior rather than completely replace it, enabling you to call the parent method and then add additional functionality.

Design Patterns

Method overriding is a key component in several design patterns like Template Method, Strategy, and Factory Method, enabling flexible and extensible code structures.

Best Practices

Follow the Liskov Substitution Principle, document your overrides with PHPDoc comments, maintain consistent method signatures, and use overriding for extension rather than drastic modification.

Next Steps

Now that you understand method overriding, we'll build on this foundation by exploring abstract classes, interfaces, namespaces, and traits. These concepts, combined with method overriding, will give you the tools to build robust, maintainable PHP applications and effectively work with WordPress's object-oriented architecture.

Additional Resources