Skip to main content

Course Progress

Loading...

Create a Simple Class with Properties and Methods

Duration: 60 minutes
Module 2: Object-Oriented PHP

Learning Objectives

  • Understand OOP principles in PHP
  • Create and use classes and objects
  • Implement inheritance and polymorphism
  • Apply OOP best practices

What We're Building

Welcome to our tutorial on creating a simple class with properties and methods! In this session, we'll implement the assigned homework by building a Product class that could be used in an e-commerce WordPress website or plugin. This class will demonstrate the fundamental concepts of Object-Oriented Programming (OOP) that we've been learning: properties, methods, constructors, and more.

By the end of this tutorial, you'll have created a fully functional class that can be used to represent products in a store, complete with properties to store product information and methods to perform common operations.

Understanding the Problem

Our homework assignment is to create a simple class with properties and methods. Let's break down what this means:

Diagram
Class Diagram (Diagram converted to static representation) classDiagram class Product { +Properties +Methods ...

We need to create a class that:

  • Has several properties to store information about products
  • Includes a constructor to initialize those properties
  • Provides methods to perform operations on products
  • Incorporates different types of access modifiers (public, protected, private)

For our e-commerce context, we'll need properties to store product details like ID, name, price, etc., and methods to perform operations like checking stock, updating prices, etc.

Planning the Solution

Using George Polya's 4-step problem-solving method, let's develop a plan for our class:

Step 1: Understand the Problem

We need to create a Product class with:

  • Properties to store product information
  • Constructor to initialize these properties
  • Methods to perform operations on the products
  • Different access modifiers for encapsulation

Step 2: Devise a Plan

Here's our simplified whiteboard plan:

  1. Define the class name and its properties with appropriate access modifiers
  2. Create a constructor to initialize the properties
  3. Implement getter and setter methods for accessing private properties
  4. Add methods for common product operations (check stock, update price, etc.)
  5. Implement a method to display product information
  6. Create example usage code to demonstrate the class

Step 3: Implement the Solution

We'll implement the solution according to our plan, creating a Product class with all the necessary components.

Step 4: Review and Refine

After implementation, we'll test the class, verify that it works as expected, and refine it if necessary.

Product Class Properties - private $id - private $name - private $price - private $description - private $stockQuantity - protected $category Methods + __construct() + getInfo() + updatePrice() + reduceStock() + isInStock() - calculateDiscount() Private properties Not accessible from outside Public methods Accessible from anywhere Protected properties Accessible in child classes Private methods Internal use only

Implementing the Solution

Basic Solution

Let's start with a basic implementation of our Product class:

<?php
/**
 * Product class for e-commerce applications
 * 
 * This class represents a product in an online store with
 * properties to store product details and methods to
 * perform common operations.
 */
class Product {
    // Private properties - can only be accessed within this class
    private $id;
    private $name;
    private $price;
    private $description;
    private $stockQuantity;
    
    // Protected property - can be accessed in this class and child classes
    protected $category;
    
    /**
     * Constructor - initializes the product
     * 
     * @param int $id Product ID
     * @param string $name Product name
     * @param float $price Product price
     * @param string $description Product description
     * @param int $stockQuantity Quantity in stock
     * @param string $category Product category
     */
    public function __construct($id, $name, $price, $description, $stockQuantity, $category = 'Uncategorized') {
        // Initialize properties
        $this->id = $id;
        $this->name = $name;
        $this->price = $price;
        $this->description = $description;
        $this->stockQuantity = $stockQuantity;
        $this->category = $category;
    }
    
    /**
     * Get product information as an array
     * 
     * @return array Product details
     */
    public function getInfo() {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'price' => $this->price,
            'description' => $this->description,
            'stock_quantity' => $this->stockQuantity,
            'category' => $this->category,
            'in_stock' => $this->isInStock()
        ];
    }
    
    /**
     * Update the product price
     * 
     * @param float $newPrice New price
     * @return boolean Success status
     */
    public function updatePrice($newPrice) {
        // Validate the new price
        if ($newPrice >= 0) {
            $this->price = $newPrice;
            return true;
        }
        
        return false;
    }
    
    /**
     * Reduce the stock quantity
     * 
     * @param int $quantity Quantity to reduce by
     * @return boolean Success status
     */
    public function reduceStock($quantity = 1) {
        // Check if we have enough stock
        if ($this->stockQuantity >= $quantity) {
            $this->stockQuantity -= $quantity;
            return true;
        }
        
        return false;
    }
    
    /**
     * Check if the product is in stock
     * 
     * @return boolean In stock status
     */
    public function isInStock() {
        return $this->stockQuantity > 0;
    }
    
    /**
     * Calculate discount price
     * 
     * @param float $percentage Discount percentage
     * @return float Discounted price
     */
    private function calculateDiscount($percentage) {
        $discount = $this->price * ($percentage / 100);
        return $this->price - $discount;
    }
    
    /**
     * Apply a discount to the product
     * 
     * @param float $percentage Discount percentage
     * @return boolean Success status
     */
    public function applyDiscount($percentage) {
        if ($percentage > 0 && $percentage <= 100) {
            $newPrice = $this->calculateDiscount($percentage);
            return $this->updatePrice($newPrice);
        }
        
        return false;
    }
    
    // Getter methods
    
    /**
     * Get product ID
     * 
     * @return int Product ID
     */
    public function getId() {
        return $this->id;
    }
    
    /**
     * Get product name
     * 
     * @return string Product name
     */
    public function getName() {
        return $this->name;
    }
    
    /**
     * Get product price
     * 
     * @return float Product price
     */
    public function getPrice() {
        return $this->price;
    }
    
    /**
     * Get product description
     * 
     * @return string Product description
     */
    public function getDescription() {
        return $this->description;
    }
    
    /**
     * Get stock quantity
     * 
     * @return int Stock quantity
     */
    public function getStockQuantity() {
        return $this->stockQuantity;
    }
    
    /**
     * Get product category
     * 
     * @return string Product category
     */
    public function getCategory() {
        return $this->category;
    }
    
    // Setter methods
    
    /**
     * Set product name
     * 
     * @param string $name New product name
     */
    public function setName($name) {
        $this->name = $name;
    }
    
    /**
     * Set product description
     * 
     * @param string $description New product description
     */
    public function setDescription($description) {
        $this->description = $description;
    }
    
    /**
     * Set product category
     * 
     * @param string $category New product category
     */
    public function setCategory($category) {
        $this->category = $category;
    }
}
?>

Example Usage

Now let's see how to use our Product class:

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

// Create a new product
$laptop = new Product(
    1,                                          // ID
    'MacBook Pro',                              // Name
    1299.99,                                    // Price
    'Powerful laptop for developers',            // Description
    10,                                         // Stock quantity
    'Electronics'                               // Category
);

// Display product information
echo "<h2>Product Information</h2>";
echo "<pre>";
print_r($laptop->getInfo());
echo "</pre>";

// Check if product is in stock
if ($laptop->isInStock()) {
    echo "<p>The {$laptop->getName()} is in stock with {$laptop->getStockQuantity()} units available.</p>";
} else {
    echo "<p>The {$laptop->getName()} is out of stock.</p>";
}

// Apply a 10% discount
$laptop->applyDiscount(10);

// Show the new price
echo "<p>After 10% discount, the price is $" . number_format($laptop->getPrice(), 2) . "</p>";

// Simulate a purchase
echo "<p>Processing purchase...</p>";
if ($laptop->reduceStock(2)) {
    echo "<p>Purchase successful! Remaining stock: {$laptop->getStockQuantity()}</p>";
} else {
    echo "<p>Purchase failed! Not enough stock.</p>";
}

// Update product information
$laptop->setName('MacBook Pro M3');
$laptop->setDescription('Latest model with M3 chip');

// Display updated information
echo "<h2>Updated Product Information</h2>";
echo "<pre>";
print_r($laptop->getInfo());
echo "</pre>";
?>

This example demonstrates:

  • Creating a new product object
  • Getting product information
  • Checking if the product is in stock
  • Applying a discount
  • Reducing stock (simulating a purchase)
  • Updating product information

Advanced Solution: Adding More Features

Now let's enhance our Product class with more advanced features, such as:

  • Product validation
  • Method chaining
  • Static properties and methods
<?php
/**
 * Enhanced Product class for e-commerce applications
 * 
 * An advanced implementation with additional features like
 * product validation, method chaining, and static methods.
 */
class Product {
    // Private properties
    private $id;
    private $name;
    private $price;
    private $description;
    private $stockQuantity;
    protected $category;
    
    // Static properties
    private static $totalProducts = 0;
    private static $validCategories = [
        'Electronics',
        'Clothing',
        'Books',
        'Home',
        'Beauty',
        'Sports',
        'Toys',
        'Food',
        'Other'
    ];
    
    /**
     * Constructor - initializes the product
     * 
     * @param int $id Product ID
     * @param string $name Product name
     * @param float $price Product price
     * @param string $description Product description
     * @param int $stockQuantity Quantity in stock
     * @param string $category Product category
     */
    public function __construct($id, $name, $price, $description, $stockQuantity, $category = 'Other') {
        // Validate inputs
        $this->id = intval($id);
        $this->name = $this->validateName($name) ? $name : 'Unknown Product';
        $this->price = max(0, floatval($price));
        $this->description = $description;
        $this->stockQuantity = max(0, intval($stockQuantity));
        $this->category = $this->validateCategory($category) ? $category : 'Other';
        
        // Increment total products counter
        self::$totalProducts++;
    }
    
    /**
     * Destructor
     */
    public function __destruct() {
        // Decrement counter when object is destroyed
        self::$totalProducts--;
    }
    
    /**
     * Validate product name
     * 
     * @param string $name Name to validate
     * @return boolean Validation result
     */
    private function validateName($name) {
        return !empty($name) && strlen($name) <= 100;
    }
    
    /**
     * Validate product category
     * 
     * @param string $category Category to validate
     * @return boolean Validation result
     */
    private function validateCategory($category) {
        return in_array($category, self::$validCategories);
    }
    
    /**
     * Get total number of product objects
     * 
     * @return int Total products
     */
    public static function getTotalProducts() {
        return self::$totalProducts;
    }
    
    /**
     * Get valid categories
     * 
     * @return array Valid categories
     */
    public static function getValidCategories() {
        return self::$validCategories;
    }
    
    /**
     * Add a new valid category
     * 
     * @param string $category New category
     * @return boolean Success status
     */
    public static function addValidCategory($category) {
        if (!empty($category) && !in_array($category, self::$validCategories)) {
            self::$validCategories[] = $category;
            return true;
        }
        
        return false;
    }
    
    /**
     * Get product information as an array
     * 
     * @return array Product details
     */
    public function getInfo() {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'price' => $this->price,
            'description' => $this->description,
            'stock_quantity' => $this->stockQuantity,
            'category' => $this->category,
            'in_stock' => $this->isInStock()
        ];
    }
    
    /**
     * Update the product price with method chaining
     * 
     * @param float $newPrice New price
     * @return Product The product object for chaining
     */
    public function updatePrice($newPrice) {
        // Validate the new price
        if ($newPrice >= 0) {
            $this->price = $newPrice;
        }
        
        return $this; // Return $this for method chaining
    }
    
    /**
     * Reduce the stock quantity with method chaining
     * 
     * @param int $quantity Quantity to reduce by
     * @return Product The product object for chaining
     */
    public function reduceStock($quantity = 1) {
        // Check if we have enough stock
        if ($this->stockQuantity >= $quantity) {
            $this->stockQuantity -= $quantity;
        }
        
        return $this; // Return $this for method chaining
    }
    
    /**
     * Increase the stock quantity with method chaining
     * 
     * @param int $quantity Quantity to increase by
     * @return Product The product object for chaining
     */
    public function increaseStock($quantity = 1) {
        if ($quantity > 0) {
            $this->stockQuantity += $quantity;
        }
        
        return $this; // Return $this for method chaining
    }
    
    /**
     * Check if the product is in stock
     * 
     * @return boolean In stock status
     */
    public function isInStock() {
        return $this->stockQuantity > 0;
    }
    
    /**
     * Check if the product is low on stock
     * 
     * @param int $threshold Low stock threshold
     * @return boolean Low stock status
     */
    public function isLowStock($threshold = 5) {
        return $this->isInStock() && $this->stockQuantity <= $threshold;
    }
    
    /**
     * Calculate discount price
     * 
     * @param float $percentage Discount percentage
     * @return float Discounted price
     */
    private function calculateDiscount($percentage) {
        $discount = $this->price * ($percentage / 100);
        return round($this->price - $discount, 2);
    }
    
    /**
     * Apply a discount to the product with method chaining
     * 
     * @param float $percentage Discount percentage
     * @return Product The product object for chaining
     */
    public function applyDiscount($percentage) {
        if ($percentage > 0 && $percentage <= 100) {
            $newPrice = $this->calculateDiscount($percentage);
            $this->price = $newPrice;
        }
        
        return $this; // Return $this for method chaining
    }
    
    /**
     * Format price with currency symbol
     * 
     * @param string $currencySymbol Currency symbol
     * @return string Formatted price
     */
    public function getFormattedPrice($currencySymbol = '$') {
        return $currencySymbol . number_format($this->price, 2);
    }
    
    /**
     * Create a product from an array of data
     * 
     * @param array $data Product data
     * @return Product New product object
     */
    public static function createFromArray($data) {
        return new self(
            $data['id'] ?? 0,
            $data['name'] ?? 'Unknown Product',
            $data['price'] ?? 0,
            $data['description'] ?? '',
            $data['stock_quantity'] ?? 0,
            $data['category'] ?? 'Other'
        );
    }
    
    // Getter and setter methods with method chaining
    
    public function getId() {
        return $this->id;
    }
    
    public function getName() {
        return $this->name;
    }
    
    public function setName($name) {
        if ($this->validateName($name)) {
            $this->name = $name;
        }
        return $this;
    }
    
    public function getPrice() {
        return $this->price;
    }
    
    public function getDescription() {
        return $this->description;
    }
    
    public function setDescription($description) {
        $this->description = $description;
        return $this;
    }
    
    public function getStockQuantity() {
        return $this->stockQuantity;
    }
    
    public function setStockQuantity($quantity) {
        $this->stockQuantity = max(0, intval($quantity));
        return $this;
    }
    
    public function getCategory() {
        return $this->category;
    }
    
    public function setCategory($category) {
        if ($this->validateCategory($category)) {
            $this->category = $category;
        }
        return $this;
    }
}
?>

Advanced Usage Example

Here's how to use the enhanced Product class with its advanced features:

<?php
// Include the enhanced Product class
require_once 'class-product-advanced.php';

// Display valid categories
echo "<h2>Valid Product Categories</h2>";
echo "<ul>";
foreach (Product::getValidCategories() as $category) {
    echo "<li>{$category}</li>";
}
echo "</ul>";

// Add a new category
Product::addValidCategory('Automotive');
echo "<p>Added new category: Automotive</p>";

// Create a product from array data
$productData = [
    'id' => 1,
    'name' => 'MacBook Pro',
    'price' => 1299.99,
    'description' => 'Powerful laptop for developers',
    'stock_quantity' => 10,
    'category' => 'Electronics'
];

$laptop = Product::createFromArray($productData);

// Display current product count
echo "<p>Total products: " . Product::getTotalProducts() . "</p>";

// Create another product
$phone = new Product(
    2,
    'iPhone 14',
    999.99,
    'Latest smartphone with advanced features',
    15,
    'Electronics'
);

// Display updated product count
echo "<p>Total products after creating iPhone: " . Product::getTotalProducts() . "</p>";

// Using method chaining
$laptop->setName('MacBook Pro M3')
       ->setDescription('Latest model with M3 chip')
       ->updatePrice(1499.99)
       ->increaseStock(5);

// Display product information
echo "<h2>Product Information (After Chaining)</h2>";
echo "<pre>";
print_r($laptop->getInfo());
echo "</pre>";

// Format price
echo "<p>Formatted price: " . $laptop->getFormattedPrice() . "</p>";

// Check low stock
if ($laptop->isLowStock(20)) {
    echo "<p><strong>Warning:</strong> This product is running low on stock!</p>";
}

// Simulate multiple purchases with chaining
echo "<p>Processing multiple purchases...</p>";
$laptop->reduceStock(2)->applyDiscount(5);

echo "<p>After purchases and 5% discount:</p>";
echo "<p>New price: " . $laptop->getFormattedPrice() . "</p>";
echo "<p>Remaining stock: " . $laptop->getStockQuantity() . "</p>";

// Remove a product and check count
unset($phone);
echo "<p>Total products after removing iPhone: " . Product::getTotalProducts() . "</p>";
?>

This advanced example demonstrates:

  • Using static properties and methods
  • Creating products from array data
  • Method chaining for a more fluent interface
  • Input validation
  • Price formatting
  • Stock status checking

Step by Step Instructions

Setting Up the Project

  1. Create a new folder for your project, for example product-class
  2. Inside this folder, create a file named class-product.php for the basic implementation
  3. If you want to try the advanced version, create a file named class-product-advanced.php
  4. Create example usage files: example-basic.php and example-advanced.php

Implementing the Basic Class

  1. Open class-product.php in your code editor
  2. Define the Product class with private properties for product details
  3. Create a constructor to initialize these properties
  4. Implement basic methods like getInfo(), updatePrice(), etc.
  5. Add getter and setter methods for accessing private properties
  6. Save the file

Testing the Basic Implementation

  1. Open example-basic.php in your code editor
  2. Include the class-product.php file using require_once
  3. Create a new product object with sample data
  4. Test each method to ensure it works correctly
  5. Save the file
  6. Run the example in your browser or command line to see the results

Extending to the Advanced Version

  1. Open class-product-advanced.php in your code editor
  2. Copy the basic implementation and enhance it with:
  3. Static properties and methods for tracking all products
  4. Input validation in the constructor and setter methods
  5. Method chaining by returning $this from setter methods
  6. Additional helper methods for formatting and validation
  7. Save the file

Testing the Advanced Implementation

  1. Open example-advanced.php in your code editor
  2. Include the class-product-advanced.php file
  3. Test the advanced features, including static methods and method chaining
  4. Save the file
  5. Run the example to see the enhanced functionality

Code Explanations and Concepts

Properties and Access Modifiers

In our Product class, we used different access modifiers:

  • Private properties ($id, $name, etc.): These can only be accessed within the class itself. This provides encapsulation, preventing direct manipulation from outside the class.
  • Protected properties ($category): These can be accessed within the class and any child classes that extend it. This is useful when creating inheritance hierarchies.
  • Public methods (getInfo(), updatePrice(), etc.): These form the class's public API, allowing external code to interact with objects.

Access Modifiers Analogy

Think of a class like a house:

  • Private members are like your bedroom - only you (the class itself) can access them
  • Protected members are like the family room - only family members (the class and its descendants) can access them
  • Public members are like your front yard - anyone passing by can see and interact with them

Constructor and Initialization

The constructor __construct() is a special method that runs automatically when an object is created. In our class, it:

  • Takes parameters for all necessary product information
  • Initializes the object's properties with these values
  • In the advanced version, it also validates inputs and increments a counter

Getters and Setters

Since our properties are private, we provide public methods to access them:

  • Getter methods (getName(), getPrice(), etc.) allow reading property values
  • Setter methods (setName(), setDescription(), etc.) allow changing property values

This pattern gives us control over how properties are accessed and modified, allowing for validation and other logic.

Method Chaining

In the advanced version, we implemented method chaining by returning $this from methods that modify the object:

public function setName($name) {
    if ($this->validateName($name)) {
        $this->name = $name;
    }
    return $this; // Return $this for method chaining
}

This allows for more concise and readable code:

$product->setName('New Name')
        ->setDescription('New description')
        ->updatePrice(99.99);

Static Properties and Methods

Static members belong to the class itself, not to individual objects:

  • Static properties (like $totalProducts) store information shared by all instances
  • Static methods (like getTotalProducts()) provide functionality that doesn't depend on individual object state

We access these using the self:: keyword within the class and ClassName:: from outside.

Real-World Applications

WordPress Integration

Our Product class could easily be integrated into a WordPress plugin for an e-commerce site:

<?php
/**
 * Plugin Name: Simple Product Manager
 * Description: A lightweight product management system
 * Version: 1.0.0
 * Author: Your Name
 */

// Include the Product class
require_once plugin_dir_path(__FILE__) . 'includes/class-product.php';

/**
 * Register custom post type for products
 */
function spm_register_post_types() {
    register_post_type('spm_product', [
        'labels' => [
            'name' => 'Products',
            'singular_name' => 'Product'
        ],
        'public' => true,
        'has_archive' => true,
        'menu_icon' => 'dashicons-cart',
        'supports' => ['title', 'editor', 'thumbnail', 'custom-fields']
    ]);
}
add_action('init', 'spm_register_post_types');

/**
 * Convert WordPress post to Product object
 * 
 * @param int $post_id Post ID
 * @return Product Product object
 */
function spm_get_product($post_id) {
    $post = get_post($post_id);
    
    if (!$post || $post->post_type !== 'spm_product') {
        return null;
    }
    
    // Get product meta data
    $price = get_post_meta($post_id, '_product_price', true);
    $stock = get_post_meta($post_id, '_product_stock', true);
    $category = get_post_meta($post_id, '_product_category', true);
    
    // Create a Product object
    return new Product(
        $post_id,
        $post->post_title,
        floatval($price),
        $post->post_content,
        intval($stock),
        $category
    );
}

/**
 * Save product data when post is saved
 * 
 * @param int $post_id Post ID
 */
function spm_save_product_data($post_id) {
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    
    if (!current_user_can('edit_post', $post_id)) {
        return;
    }
    
    if (isset($_POST['_product_price'])) {
        update_post_meta($post_id, '_product_price', sanitize_text_field($_POST['_product_price']));
    }
    
    if (isset($_POST['_product_stock'])) {
        update_post_meta($post_id, '_product_stock', intval($_POST['_product_stock']));
    }
    
    if (isset($_POST['_product_category'])) {
        update_post_meta($post_id, '_product_category', sanitize_text_field($_POST['_product_category']));
    }
}
add_action('save_post_spm_product', 'spm_save_product_data');

/**
 * Display product information on the product single page
 * 
 * @param string $content Post content
 * @return string Modified content
 */
function spm_display_product_info($content) {
    if (!is_singular('spm_product')) {
        return $content;
    }
    
    $product = spm_get_product(get_the_ID());
    
    if (!$product) {
        return $content;
    }
    
    $output = $content;
    
    $output .= '<div class="product-info">';
    $output .= '<p class="product-price">Price: $' . number_format($product->getPrice(), 2) . '</p>';
    
    if ($product->isInStock()) {
        $output .= '<p class="in-stock">In Stock: ' . $product->getStockQuantity() . ' available</p>';
    } else {
        $output .= '<p class="out-of-stock">Out of Stock</p>';
    }
    
    $output .= '<p class="product-category">Category: ' . $product->getCategory() . '</p>';
    $output .= '</div>';
    
    return $output;
}
add_filter('the_content', 'spm_display_product_info');
?>

This example shows how the Product class could be used in a WordPress plugin to:

  • Create a custom post type for products
  • Convert WordPress posts to Product objects
  • Save product data as post meta
  • Display product information on the front-end

Other Real-World Applications

The concepts demonstrated in this class can be applied to many other scenarios:

  • User Management: Create a User class to handle user accounts
  • Content Management: Create classes for different types of content
  • Payment Processing: Create classes to handle payment methods and transactions
  • Data Validation: Create utility classes for validating different types of data

Further Learning

Next Steps

To build on what you've learned, consider exploring these topics:

  • Inheritance: Create child classes like PhysicalProduct and DigitalProduct that extend the Product class
  • Interfaces: Define interfaces that classes can implement, such as Shippable or Downloadable
  • Namespaces: Organize your classes in namespaces to avoid naming conflicts
  • Autoloading: Set up autoloading so you don't need to manually include class files
  • Design Patterns: Learn common OOP design patterns like Singleton, Factory, and Observer

Resources

Conclusion

Congratulations! You've successfully created a Product class with properties and methods, and explored how it can be used in real-world applications. Let's recap what we've learned:

  • How to define a class with properties and methods
  • How to use access modifiers (private, protected, public) for encapsulation
  • How to create a constructor to initialize objects
  • How to implement getter and setter methods
  • How to add functionality through instance methods
  • How to use static properties and methods for class-level features
  • How to enable method chaining for a more fluent interface

These object-oriented programming concepts are fundamental to building maintainable, reusable, and organized code, especially in larger applications like WordPress plugins and themes.