Skip to main content

Course Progress

Loading...

Homework: Extend Your Previous Class with Inheritance

Duration: 60 minutes
Module 2: Object-Oriented PHP

Learning Objectives

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

Understanding the Problem

In this homework assignment, we'll apply George Polya's problem-solving method to extend a class using inheritance. This builds on our previous lessons where we've created basic classes, and now we're taking the next step to leverage the power of inheritance in PHP.

What is Inheritance?

Inheritance allows a class (the child or derived class) to inherit properties and methods from another class (the parent or base class). Think of it like genetic inheritance: a child inherits traits from their parents, but can also have their own unique traits.

Basic Inheritance Structure

Diagram
Class Diagram (Diagram converted to static representation) classDiagram class ParentClass { +parentProperty +...

Assignment Requirements

We need to extend a previously created class using inheritance. This typically involves:

  • Creating a parent class (which we've already done in previous homework)
  • Creating one or more child classes that extend the parent
  • Adding new properties and methods to the child classes
  • Overriding parent methods when necessary
  • Demonstrating polymorphism (using parent and child objects interchangeably)

For this assignment, let's assume we previously created a Product class. We'll now extend it to create specialized product types.

Devising a Plan

Let's break down our approach for extending the Product class with inheritance:

  1. Review the base Product class from our previous homework
  2. Identify what specialized product types we could create (e.g., PhysicalProduct, DigitalProduct)
  3. Determine what unique properties and methods each specialized product type should have
  4. Implement inheritance by extending the Product class
  5. Add constructors in child classes, calling the parent constructor
  6. Override parent methods where needed to provide specialized behavior
  7. Create a test script to demonstrate the inheritance in action

Whiteboard Plan

Here's our simplified plan:

  1. Create folder structure:
    • product_inheritance/ (main folder)
    • product_inheritance/classes/ (for all class files)
    • product_inheritance/index.php (test script)
  2. Define base Product class in classes/Product.php
  3. Create PhysicalProduct class in classes/PhysicalProduct.php
    • Add properties: weight, dimensions, shipping cost
    • Override methods as needed
  4. Create DigitalProduct class in classes/DigitalProduct.php
    • Add properties: download URL, file size
    • Override methods as needed
  5. Create test script in index.php to instantiate and test all classes

Class Hierarchy Design

Diagram
Class Diagram (Diagram converted to static representation) classDiagram class Product { #name: string #price:...

Implementing the Solution

Step 1: Create File Structure

First, let's create our folder structure:


product_inheritance/
├── classes/
│   ├── Product.php
│   ├── PhysicalProduct.php
│   └── DigitalProduct.php
└── index.php
            

Step 2: Define the Base Product Class

Let's start by defining our base Product class from the previous homework:

File: classes/Product.php


<?php
/**
 * Base Product class
 * 
 * This class represents a generic product with basic properties and methods.
 */
class Product {
    // Protected properties (accessible to child classes)
    protected $name;
    protected $price;
    protected $description;
    
    /**
     * Constructor
     * 
     * @param string $name The name of the product
     * @param float $price The price of the product
     * @param string $description The product description
     */
    public function __construct($name, $price, $description = '') {
        $this->name = $name;
        $this->price = $price;
        $this->description = $description;
    }
    
    /**
     * Get the product name
     * 
     * @return string
     */
    public function getName() {
        return $this->name;
    }
    
    /**
     * Get the product price
     * 
     * @return float
     */
    public function getPrice() {
        return $this->price;
    }
    
    /**
     * Get the product description
     * 
     * @return string
     */
    public function getDescription() {
        return $this->description;
    }
    
    /**
     * Get product details as an array
     * 
     * @return array
     */
    public function getDetails() {
        return [
            'name' => $this->name,
            'price' => $this->price,
            'description' => $this->description
        ];
    }
    
    /**
     * Calculate product tax
     * 
     * @param float $rate The tax rate as a decimal (e.g., 0.1 for 10%)
     * @return float The calculated tax amount
     */
    public function calculateTax($rate = 0.1) {
        return $this->price * $rate;
    }
}
            

Step 3: Create the PhysicalProduct Class

Now, let's create our first child class for physical products:

File: classes/PhysicalProduct.php


<?php
// Include the parent class
require_once 'Product.php';

/**
 * PhysicalProduct class
 * 
 * This class extends the base Product class to represent
 * physical products with weight, dimensions, and shipping costs.
 */
class PhysicalProduct extends Product {
    // Additional properties specific to physical products
    protected $weight;
    protected $dimensions;
    
    /**
     * Constructor
     * 
     * @param string $name The name of the product
     * @param float $price The price of the product
     * @param string $description The product description
     * @param float $weight The weight in kilograms
     * @param array $dimensions The dimensions as [length, width, height]
     */
    public function __construct($name, $price, $description = '', $weight = 0, $dimensions = []) {
        // Call the parent constructor first
        parent::__construct($name, $price, $description);
        
        // Initialize our own properties
        $this->weight = $weight;
        $this->dimensions = $dimensions;
    }
    
    /**
     * Get the product weight
     * 
     * @return float
     */
    public function getWeight() {
        return $this->weight;
    }
    
    /**
     * Get the product dimensions
     * 
     * @return array
     */
    public function getDimensions() {
        return $this->dimensions;
    }
    
    /**
     * Calculate shipping cost based on weight and distance
     * 
     * @param float $distance The shipping distance in miles
     * @return float The calculated shipping cost
     */
    public function calculateShippingCost($distance) {
        // Basic formula: $5 base cost + $2 per kg + $0.10 per mile
        return 5 + ($this->weight * 2) + ($distance * 0.1);
    }
    
    /**
     * Get detailed product information
     * 
     * Override the parent method to include physical product details
     * 
     * @return array
     */
    public function getDetails() {
        // Get the basic details from the parent method
        $details = parent::getDetails();
        
        // Add our specific details
        $details['weight'] = $this->weight;
        $details['dimensions'] = $this->dimensions;
        $details['type'] = 'Physical Product';
        
        return $details;
    }
    
    /**
     * Calculate tax for physical products
     * 
     * May have different tax rules than other product types
     * 
     * @param float $rate The tax rate as a decimal
     * @return float The calculated tax amount
     */
    public function calculateTax($rate = 0.1) {
        // For example, physical products might have a different tax calculation
        // Here we're applying the tax rate to the price as in the parent class
        return parent::calculateTax($rate);
        
        // In a real application, you might have different rules, like:
        // return $this->price * $rate * (1 - $this->getDiscountMultiplier());
    }
}
            

Step 4: Create the DigitalProduct Class

Now, let's create our second child class for digital products:

File: classes/DigitalProduct.php


<?php
// Include the parent class
require_once 'Product.php';

/**
 * DigitalProduct class
 * 
 * This class extends the base Product class to represent
 * digital products with download URL and file size.
 */
class DigitalProduct extends Product {
    // Additional properties specific to digital products
    protected $downloadUrl;
    protected $fileSize; // In MB
    
    /**
     * Constructor
     * 
     * @param string $name The name of the product
     * @param float $price The price of the product
     * @param string $description The product description
     * @param string $downloadUrl The product download URL
     * @param float $fileSize The file size in MB
     */
    public function __construct($name, $price, $description = '', $downloadUrl = '', $fileSize = 0) {
        // Call the parent constructor first
        parent::__construct($name, $price, $description);
        
        // Initialize our own properties
        $this->downloadUrl = $downloadUrl;
        $this->fileSize = $fileSize;
    }
    
    /**
     * Get the product download URL
     * 
     * @return string
     */
    public function getDownloadUrl() {
        return $this->downloadUrl;
    }
    
    /**
     * Get the product file size
     * 
     * @return float
     */
    public function getFileSize() {
        return $this->fileSize;
    }
    
    /**
     * Generate a time-limited download link
     * 
     * @param int $validForHours Number of hours the link is valid
     * @return string The generated download link
     */
    public function generateDownloadLink($validForHours = 24) {
        // Generate a temporary token
        $token = md5(uniqid(rand(), true));
        
        // Create an expiry timestamp
        $expires = time() + ($validForHours * 3600);
        
        // In a real app, you'd store this token and expiry in a database
        
        // Return a URL with the token and expiry
        return $this->downloadUrl . '?token=' . $token . '&expires=' . $expires;
    }
    
    /**
     * Get detailed product information
     * 
     * Override the parent method to include digital product details
     * 
     * @return array
     */
    public function getDetails() {
        // Get the basic details from the parent method
        $details = parent::getDetails();
        
        // Add our specific details
        $details['download_url'] = $this->downloadUrl;
        $details['file_size'] = $this->fileSize;
        $details['type'] = 'Digital Product';
        
        return $details;
    }
    
    /**
     * Calculate tax for digital products
     * 
     * Digital products often have different tax rates
     * 
     * @param float $rate The tax rate as a decimal
     * @return float The calculated tax amount
     */
    public function calculateTax($rate = 0.05) {
        // Digital products often have reduced tax rates
        // Note we're using 0.05 (5%) as the default instead of 0.1 (10%)
        return $this->price * $rate;
    }
}
            

Step 5: Create a Test Script

Finally, let's create a test script to demonstrate our inheritance hierarchy:

File: index.php


<?php
// Include our class files
require_once 'classes/Product.php';
require_once 'classes/PhysicalProduct.php';
require_once 'classes/DigitalProduct.php';

// Create an instance of each class
$product = new Product('Generic Product', 19.99, 'A standard product');
$physicalProduct = new PhysicalProduct('Coffee Mug', 12.99, 'A ceramic coffee mug', 0.5, [10, 8, 12]);
$digitalProduct = new DigitalProduct('E-book: PHP Mastery', 24.99, 'Learn PHP from beginner to advanced', 'https://example.com/downloads/php-mastery.pdf', 15.5);

// Display product information
echo '<h2>Base Product:</h2>';
echo 'Name: ' . $product->getName() . '<br>';
echo 'Price: $' . number_format($product->getPrice(), 2) . '<br>';
echo 'Description: ' . $product->getDescription() . '<br>';
echo 'Tax (10%): $' . number_format($product->calculateTax(), 2) . '<br>';

echo '<h2>Physical Product:</h2>';
echo 'Name: ' . $physicalProduct->getName() . '<br>';
echo 'Price: $' . number_format($physicalProduct->getPrice(), 2) . '<br>';
echo 'Description: ' . $physicalProduct->getDescription() . '<br>';
echo 'Weight: ' . $physicalProduct->getWeight() . ' kg<br>';
echo 'Dimensions: ' . implode(' x ', $physicalProduct->getDimensions()) . ' cm<br>';
echo 'Tax (10%): $' . number_format($physicalProduct->calculateTax(), 2) . '<br>';
echo 'Shipping Cost (100 miles): $' . number_format($physicalProduct->calculateShippingCost(100), 2) . '<br>';

echo '<h2>Digital Product:</h2>';
echo 'Name: ' . $digitalProduct->getName() . '<br>';
echo 'Price: $' . number_format($digitalProduct->getPrice(), 2) . '<br>';
echo 'Description: ' . $digitalProduct->getDescription() . '<br>';
echo 'File Size: ' . $digitalProduct->getFileSize() . ' MB<br>';
echo 'Download URL: ' . $digitalProduct->getDownloadUrl() . '<br>';
echo 'Tax (5%): $' . number_format($digitalProduct->calculateTax(), 2) . '<br>';
echo 'Download Link: ' . $digitalProduct->generateDownloadLink() . '<br>';

// Demonstrate polymorphism by processing an array of different product types
echo '<h2>Product Details (Demonstrating Polymorphism):</h2>';
$products = [$product, $physicalProduct, $digitalProduct];

foreach ($products as $index => $prod) {
    echo '<h3>Product ' . ($index + 1) . ':</h3>';
    echo '<pre>';
    print_r($prod->getDetails());
    echo '</pre>';
}
            

Looking Back and Reflecting

Key Concepts Applied

  • Inheritance: We created specialized product classes that inherit from a base class
  • Parent Constructor Calling: Child classes call the parent constructor using parent::__construct()
  • Method Overriding: Child classes override methods like getDetails() and calculateTax() to provide specialized behavior
  • Polymorphism: We demonstrated how objects of different classes can be treated uniformly (through the getDetails() method)
  • Code Reuse: Child classes reuse code from the parent class, avoiding duplication

Expected Output

When you run index.php, you should see output similar to:

Base Product:

Name: Generic Product
Price: $19.99
Description: A standard product
Tax (10%): $2.00

Physical Product:

Name: Coffee Mug
Price: $12.99
Description: A ceramic coffee mug
Weight: 0.5 kg
Dimensions: 10 x 8 x 12 cm
Tax (10%): $1.30
Shipping Cost (100 miles): $16.00

Digital Product:

Name: E-book: PHP Mastery
Price: $24.99
Description: Learn PHP from beginner to advanced
File Size: 15.5 MB
Download URL: https://example.com/downloads/php-mastery.pdf
Tax (5%): $1.25
Download Link: https://example.com/downloads/php-mastery.pdf?token=[random_token]&expires=[timestamp]

Product Details (Demonstrating Polymorphism):

Product 1:

Array
(
    [name] => Generic Product
    [price] => 19.99
    [description] => A standard product
)
                

Product 2:

Array
(
    [name] => Coffee Mug
    [price] => 12.99
    [description] => A ceramic coffee mug
    [weight] => 0.5
    [dimensions] => Array
        (
            [0] => 10
            [1] => 8
            [2] => 12
        )
    [type] => Physical Product
)
                

Product 3:

Array
(
    [name] => E-book: PHP Mastery
    [price] => 24.99
    [description] => Learn PHP from beginner to advanced
    [download_url] => https://example.com/downloads/php-mastery.pdf
    [file_size] => 15.5
    [type] => Digital Product
)
                

Taking It Further

Further Extensions and Improvements

Here are some ways you could extend this solution:

Create a SubscriptionProduct Class

For products with recurring billing, you could create a SubscriptionProduct class:

File: classes/SubscriptionProduct.php


<?php
require_once 'Product.php';

class SubscriptionProduct extends Product {
    protected $billingCycle; // 'monthly', 'yearly', etc.
    protected $billingPeriods; // number of billing periods
    
    public function __construct($name, $price, $description = '', $billingCycle = 'monthly', $billingPeriods = 12) {
        parent::__construct($name, $price, $description);
        $this->billingCycle = $billingCycle;
        $this->billingPeriods = $billingPeriods;
    }
    
    public function getBillingCycle() {
        return $this->billingCycle;
    }
    
    public function getBillingPeriods() {
        return $this->billingPeriods;
    }
    
    public function getTotalPrice() {
        return $this->price * $this->billingPeriods;
    }
    
    public function getDetails() {
        $details = parent::getDetails();
        $details['billing_cycle'] = $this->billingCycle;
        $details['billing_periods'] = $this->billingPeriods;
        $details['total_price'] = $this->getTotalPrice();
        $details['type'] = 'Subscription Product';
        return $details;
    }
    
    public function calculateTax($rate = 0.1) {
        // Calculate tax on the recurring price
        return $this->price * $rate;
    }
}
            

Implement a ProductFactory

A factory class can create different product types based on input:

File: classes/ProductFactory.php


<?php
require_once 'Product.php';
require_once 'PhysicalProduct.php';
require_once 'DigitalProduct.php';
require_once 'SubscriptionProduct.php';

class ProductFactory {
    /**
     * Create a product of the specified type
     * 
     * @param string $type The product type ('physical', 'digital', 'subscription')
     * @param array $data The product data
     * @return Product A product instance
     */
    public static function createProduct($type, $data) {
        switch (strtolower($type)) {
            case 'physical':
                return new PhysicalProduct(
                    $data['name'] ?? '',
                    $data['price'] ?? 0,
                    $data['description'] ?? '',
                    $data['weight'] ?? 0,
                    $data['dimensions'] ?? []
                );
                
            case 'digital':
                return new DigitalProduct(
                    $data['name'] ?? '',
                    $data['price'] ?? 0,
                    $data['description'] ?? '',
                    $data['download_url'] ?? '',
                    $data['file_size'] ?? 0
                );
                
            case 'subscription':
                return new SubscriptionProduct(
                    $data['name'] ?? '',
                    $data['price'] ?? 0,
                    $data['description'] ?? '',
                    $data['billing_cycle'] ?? 'monthly',
                    $data['billing_periods'] ?? 12
                );
                
            default:
                return new Product(
                    $data['name'] ?? '',
                    $data['price'] ?? 0,
                    $data['description'] ?? ''
                );
        }
    }
}
            

Add Abstract Methods and Properties

You could convert the Product class to an abstract class, forcing child classes to implement certain methods:


abstract class Product {
    // Properties...
    
    // Regular methods...
    
    /**
     * Get the product type
     * 
     * @return string
     */
    abstract public function getType();
    
    /**
     * Get product-specific details
     * 
     * @return array
     */
    abstract protected function getSpecificDetails();
    
    /**
     * Get full product details
     * 
     * @return array
     */
    public function getDetails() {
        $details = [
            'name' => $this->name,
            'price' => $this->price,
            'description' => $this->description,
            'type' => $this->getType()
        ];
        
        // Merge with product-specific details
        return array_merge($details, $this->getSpecificDetails());
    }
}
            

Real-World Applications

This product class hierarchy has many real-world applications:

  • E-commerce Systems: Online stores need to handle various product types differently
  • Inventory Management: Physical products require stock tracking, while digital products don't
  • Order Processing: Physical products need shipping, digital products need download links
  • Tax Calculations: Different product types often have different tax rules
  • Content Management Systems: WordPress and similar systems use inheritance for different content types

WordPress Application

In WordPress development, inheritance is used extensively:

  • WooCommerce Products: WooCommerce has a base product class with specialized child classes for simple, variable, grouped, and external products
  • Custom Post Types: You can create specialized post types that inherit from WordPress's base post functionality
  • Widget Development: Custom widgets extend the WP_Widget class
  • Plugin Development: Many plugins use inheritance to create extensible frameworks

Here's a simplified example of a WooCommerce-style product class hierarchy:

WooCommerce-style Product Hierarchy

Diagram
Class Diagram (Diagram converted to static representation) classDiagram class WC_Product { > #id: int #name: ...

Common Mistakes and How to Avoid Them

Forgetting to Call the Parent Constructor

One of the most common mistakes when using inheritance is forgetting to call the parent constructor.


// Incorrect
class ChildClass extends ParentClass {
    public function __construct($param1, $param2, $param3) {
        // Missing parent constructor call
        $this->param3 = $param3;
    }
}

// Correct
class ChildClass extends ParentClass {
    public function __construct($param1, $param2, $param3) {
        // Call parent constructor first
        parent::__construct($param1, $param2);
        $this->param3 = $param3;
    }
}
                

Using Private Instead of Protected

Another common mistake is using private for properties and methods in the parent class that should be accessible to child classes.


// Problematic
class ParentClass {
    private $value; // Child classes cannot access this
    
    private function helper() { // Child classes cannot use this
        // ...
    }
}

// Better
class ParentClass {
    protected $value; // Child classes can access this
    
    protected function helper() { // Child classes can use this
        // ...
    }
}
                

Creating Too Deep Inheritance Hierarchies

Inheritance hierarchies that are too deep can become hard to understand and maintain.


// Too deep (harder to understand and maintain)
class Vehicle { /* ... */ }
class MotorVehicle extends Vehicle { /* ... */ }
class Car extends MotorVehicle { /* ... */ }
class SportsCar extends Car { /* ... */ }
class ConvertibleSportsCar extends SportsCar { /* ... */ }

// Better approach: Flatter hierarchy + composition
class Vehicle { /* ... */ }
class Car extends Vehicle { 
    protected $engine; // Composition
    protected $transmission; // Composition
    
    public function __construct($engine, $transmission) {
        $this->engine = $engine;
        $this->transmission = $transmission;
    }
}
                

Overriding Methods Without Understanding Parent Implementation

Be careful when overriding methods without understanding what the parent method does.


// Problematic
class ParentClass {
    public function process() {
        $this->validate();
        $this->save();
        $this->notify();
    }
    
    protected function validate() { /* ... */ }
    protected function save() { /* ... */ }
    protected function notify() { /* ... */ }
}

class ChildClass extends ParentClass {
    // Completely replacing parent method, losing its functionality
    public function process() {
        // Only doing saving, losing validation and notification
        $this->save();
    }
}

// Better
class ChildClass extends ParentClass {
    // Extending parent method
    public function process() {
        // Call parent method to keep its functionality
        parent::process();
        
        // Add additional functionality
        $this->additionalProcessing();
    }
    
    protected function additionalProcessing() {
        // ...
    }
}
                

Additional Exercises

Exercise 1: Create a Book Product Hierarchy

Create a class hierarchy for different types of books:

  • Create a base Book class with properties for title, author, and price
  • Create child classes for PhysicalBook (with properties for weight, dimensions) and EBook (with properties for file format, download URL)
  • Add appropriate methods for each class
  • Create a test script to demonstrate the hierarchy

Exercise 2: Implement an Abstract Base Class

Modify the product hierarchy to use an abstract base class:

  • Make the Product class abstract
  • Add at least one abstract method that all child classes must implement
  • Update the child classes to implement the abstract method(s)
  • Update the test script to demonstrate the new structure

Exercise 3: Add Interface Implementation

Extend the product hierarchy to include interfaces:

  • Create a Shippable interface for products that can be shipped
  • Create a Downloadable interface for products that can be downloaded
  • Update the appropriate product classes to implement these interfaces
  • Create a test script that demonstrates polymorphism using the interfaces

Additional Resources