Lesson 4.1: Classes and Objects Explained

Object-oriented programming represents a fundamental shift in how you think about code. Instead of writing functions that operate on data, you create objects that contain both data and the functions that work with that data. Think of it like the difference between having a toolbox full of separate tools versus having smart devices that know how to operate themselves.

Imagine you're building a social media application. With procedural programming, you might have separate functions like validateUser(), sendMessage(), uploadPhoto(), and dozens of variables to track user information. With object-oriented programming, you create a User object that contains all user data and knows how to validate itself, send messages, and upload photos. The code becomes more organized, easier to understand, and much more maintainable.

Object-oriented programming solves real problems that emerge as applications grow larger and more complex. When you have thousands of lines of code, dozens of functions, and multiple developers working on the same project, organizing code becomes critical. Objects provide natural boundaries that make complex systems manageable.

Most modern PHP applications—from WordPress and Laravel to e-commerce platforms and business applications—are built using object-oriented principles. Understanding these concepts is essential for working with modern PHP frameworks, contributing to open-source projects, and building scalable applications that can grow with your business needs.

Understanding Classes and Objects

A class is like a blueprint or template that defines what an object will contain and what it can do. An object is a specific instance created from that blueprint. Think of a class as the architectural plans for a house, while objects are the actual houses built from those plans. Each house (object) has the same basic structure but can have different paint colors, furniture, and occupants.

The relationship between classes and objects is fundamental to understanding object-oriented programming. You write the class once, but you can create as many objects from that class as you need, each with its own unique data.

<?php
// Define a User class (the blueprint)
class User {
    // Properties (data that each user object will have)
    public $name;
    public $email;
    public $isActive;

    // Methods (functions that each user object can perform)
    public function login() {
        echo "$this->name has logged in\n";
    }

    public function sendMessage($message) {
        echo "$this->name says: $message\n";
    }
}

// Create objects (actual users) from the class
$user1 = new User();
$user1->name = "Alice Johnson";
$user1->email = "[email protected]";
$user1->isActive = true;

$user2 = new User();
$user2->name = "Bob Smith";
$user2->email = "[email protected]";
$user2->isActive = false;

// Use the objects
$user1->login();
$user1->sendMessage("Hello everyone!");
?>

Notice how $user1 and $user2 are separate objects created from the same User class. They each have their own data but share the same methods. The $this keyword inside the class refers to the current object being used.

Properties: Storing Object Data

Properties are variables that belong to a class. They hold the data that makes each object unique. Think of properties like the characteristics of a person—name, age, email address, preferences. Each person (object) has their own values for these characteristics.

<?php
class Product {
    public $name;
    public $price;
    public $category;
    public $inStock;
    public $description;

    public function getFormattedPrice() {
        return "$" . number_format($this->price, 2);
    }

    public function isAvailable() {
        return $this->inStock && $this->price > 0;
    }
}

// Create different products
$laptop = new Product();
$laptop->name = "Gaming Laptop";
$laptop->price = 1299.99;
$laptop->category = "Electronics";
$laptop->inStock = true;
$laptop->description = "High-performance laptop for gaming";

$coffee = new Product();
$coffee->name = "Colombian Coffee";
$coffee->price = 12.99;
$coffee->category = "Food & Beverage";
$coffee->inStock = false;
$coffee->description = "Premium Colombian coffee beans";

// Use the products
echo $laptop->name . " costs " . $laptop->getFormattedPrice() . "\n";
echo "Available: " . ($laptop->isAvailable() ? "Yes" : "No") . "\n";

echo $coffee->name . " costs " . $coffee->getFormattedPrice() . "\n";
echo "Available: " . ($coffee->isAvailable() ? "Yes" : "No") . "\n";
?>

Properties can hold any type of data—strings, numbers, booleans, arrays, or even other objects. The public keyword means these properties can be accessed from outside the class, which we'll explore more in the next lesson.

Methods: Object Behavior

Methods are functions that belong to a class. They define what an object can do—the actions it can perform or the calculations it can make. Methods often work with the object's properties to provide useful functionality.

<?php
class BankAccount {
    public $accountNumber;
    public $ownerName;
    public $balance;
    public $accountType;

    public function deposit($amount) {
        if ($amount > 0) {
            $this->balance += $amount;
            echo "Deposited $" . number_format($amount, 2) . "\n";
            echo "New balance: " . $this->getFormattedBalance() . "\n";
        } else {
            echo "Deposit amount must be positive\n";
        }
    }

    public function withdraw($amount) {
        if ($amount > 0 && $amount <= $this->balance) {
            $this->balance -= $amount;
            echo "Withdrew $" . number_format($amount, 2) . "\n";
            echo "New balance: " . $this->getFormattedBalance() . "\n";
        } else {
            echo "Invalid withdrawal amount\n";
        }
    }

    public function getFormattedBalance() {
        return "$" . number_format($this->balance, 2);
    }

    public function getAccountInfo() {
        return [
            'number' => $this->accountNumber,
            'owner' => $this->ownerName,
            'balance' => $this->balance,
            'type' => $this->accountType
        ];
    }
}

// Create and use a bank account
$account = new BankAccount();
$account->accountNumber = "12345678";
$account->ownerName = "Sarah Wilson";
$account->balance = 1000.00;
$account->accountType = "Checking";

echo "Account holder: " . $account->ownerName . "\n";
echo "Starting balance: " . $account->getFormattedBalance() . "\n";

$account->deposit(250.50);
$account->withdraw(75.25);
$account->withdraw(2000); // This should fail
?>

Methods can accept parameters, return values, and modify the object's properties. They encapsulate business logic and provide a clean interface for working with object data.

Constructors: Setting Up Objects

Constructors are special methods that run automatically when you create a new object. They're perfect for setting initial values and ensuring objects start in a valid state. Think of constructors like the setup process when you buy a new phone—certain settings need to be configured before you can use it effectively.

<?php
class User {
    public $id;
    public $username;
    public $email;
    public $registrationDate;
    public $isActive;

    // Constructor runs when new User() is called
    public function __construct($username, $email) {
        $this->id = uniqid();
        $this->username = $username;
        $this->email = $email;
        $this->registrationDate = date('Y-m-d H:i:s');
        $this->isActive = true;

        echo "New user created: $username\n";
    }

    public function getProfile() {
        return [
            'id' => $this->id,
            'username' => $this->username,
            'email' => $this->email,
            'member_since' => $this->registrationDate,
            'status' => $this->isActive ? 'Active' : 'Inactive'
        ];
    }
}

// Constructor is called automatically
$user1 = new User("alice_j", "[email protected]");
$user2 = new User("bob_smith", "[email protected]");

// Objects are ready to use immediately
print_r($user1->getProfile());
?>

Constructors ensure that objects are properly initialized and reduce the chance of using objects in invalid states. They make object creation more convenient and reliable.

Here's a more complex constructor example:

<?php
class ShoppingCart {
    public $items;
    public $customer;
    public $createdAt;
    public $taxRate;

    public function __construct($customerName, $taxRate = 0.08) {
        $this->items = [];
        $this->customer = $customerName;
        $this->createdAt = time();
        $this->taxRate = $taxRate;

        echo "Shopping cart created for $customerName\n";
    }

    public function addItem($productName, $price, $quantity = 1) {
        $this->items[] = [
            'product' => $productName,
            'price' => $price,
            'quantity' => $quantity,
            'subtotal' => $price * $quantity
        ];

        echo "Added $quantity x $productName to cart\n";
    }

    public function getTotal() {
        $subtotal = 0;
        foreach ($this->items as $item) {
            $subtotal += $item['subtotal'];
        }

        $tax = $subtotal * $this->taxRate;
        return $subtotal + $tax;
    }

    public function getItemCount() {
        $count = 0;
        foreach ($this->items as $item) {
            $count += $item['quantity'];
        }
        return $count;
    }
}

$cart = new ShoppingCart("John Doe", 0.075);
$cart->addItem("Laptop", 999.99);
$cart->addItem("Mouse", 29.99, 2);

echo "Items in cart: " . $cart->getItemCount() . "\n";
echo "Total: $" . number_format($cart->getTotal(), 2) . "\n";
?>

Working with Multiple Objects

One of the most powerful aspects of object-oriented programming is how different objects can work together to solve complex problems. Objects can contain other objects, call methods on each other, and collaborate to create sophisticated functionality.

<?php
class Order {
    public $orderId;
    public $customer;
    public $items;
    public $status;
    public $orderDate;

    public function __construct($customer) {
        $this->orderId = "ORD-" . uniqid();
        $this->customer = $customer;
        $this->items = [];
        $this->status = "pending";
        $this->orderDate = date('Y-m-d H:i:s');
    }

    public function addProduct($product, $quantity = 1) {
        $this->items[] = [
            'product' => $product,
            'quantity' => $quantity,
            'price' => $product->price,
            'subtotal' => $product->price * $quantity
        ];
    }

    public function calculateTotal() {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item['subtotal'];
        }
        return $total;
    }

    public function getOrderSummary() {
        $summary = "Order ID: {$this->orderId}\n";
        $summary .= "Customer: {$this->customer->name}\n";
        $summary .= "Date: {$this->orderDate}\n";
        $summary .= "Status: {$this->status}\n";
        $summary .= "Items:\n";

        foreach ($this->items as $item) {
            $summary .= "- {$item['product']->name} x{$item['quantity']} = $" . number_format($item['subtotal'], 2) . "\n";
        }

        $summary .= "Total: $" . number_format($this->calculateTotal(), 2) . "\n";
        return $summary;
    }
}

class Customer {
    public $id;
    public $name;
    public $email;

    public function __construct($name, $email) {
        $this->id = uniqid();
        $this->name = $name;
        $this->email = $email;
    }
}

class Product {
    public $id;
    public $name;
    public $price;
    public $category;

    public function __construct($name, $price, $category) {
        $this->id = uniqid();
        $this->name = $name;
        $this->price = $price;
        $this->category = $category;
    }
}

// Create objects and make them work together
$customer = new Customer("Alice Johnson", "[email protected]");

$laptop = new Product("Gaming Laptop", 1299.99, "Electronics");
$mouse = new Product("Wireless Mouse", 49.99, "Electronics");

$order = new Order($customer);
$order->addProduct($laptop, 1);
$order->addProduct($mouse, 2);

echo $order->getOrderSummary();
?>

This example shows how objects can contain and work with other objects. The Order object contains a Customer object and multiple Product objects, demonstrating how object-oriented programming naturally models real-world relationships.

Why Objects Matter

Object-oriented programming provides several key advantages that become more important as your applications grow in size and complexity.

Organization and Structure: Objects group related data and functions together, making code easier to understand and navigate. Instead of hunting through hundreds of functions to find user-related code, everything is organized in the User class.

Reusability: Once you create a class, you can use it throughout your application. A Product class can be used in inventory management, shopping carts, order processing, and reporting systems without rewriting code.

Maintainability: When you need to change how products work, you only need to modify the Product class. All parts of your application that use products automatically get the updates.

Natural Modeling: Objects let you model real-world concepts directly in code. A BankAccount object behaves like a real bank account, making the code intuitive to understand and work with.

Common Beginner Mistakes

Understanding these common pitfalls helps you avoid frustration and write better object-oriented code from the start.

<?php
// Mistake 1: Forgetting the 'new' keyword
// $user = User(); // Wrong! This tries to call a function
$user = new User(); // Correct

// Mistake 2: Confusing classes and objects
// User->login(); // Wrong! User is a class, not an object
$user->login(); // Correct! $user is an object

// Mistake 3: Trying to use $this outside of a class
function regularFunction() {
    // echo $this->name; // Wrong! $this only works inside class methods
}

// Mistake 4: Not understanding object assignment
$user1 = new User();
$user1->name = "Alice";

$user2 = $user1; // This creates a reference, not a copy
$user2->name = "Bob";

echo $user1->name; // This will output "Bob", not "Alice"!

// To make a real copy, you need to clone
$user3 = clone $user1;
$user3->name = "Charlie";
echo $user1->name; // Still "Bob"
echo $user3->name; // "Charlie"
?>

Best Practices for Getting Started

Following these practices from the beginning helps you develop good object-oriented programming habits.

Choose Meaningful Names: Class names should be nouns that clearly describe what the object represents. User, Product, Order, and BankAccount are much better than Data, Item, or Thing.

Keep Classes Focused: Each class should have a single, clear purpose. A User class should handle user-related functionality, not email sending or file processing. If a class is trying to do too many things, consider splitting it into multiple classes.

Use Constructors Wisely: Constructors should set up objects in valid, usable states. Don't put complex business logic in constructors—save that for dedicated methods.

Think in Objects: Start identifying objects in your problem domain. If you're building a blog, you might have Post, Author, Comment, and Category objects. If you're building a game, you might have Player, Game, Score, and Level objects.

Key Takeaways

Object-oriented programming is a powerful paradigm that helps you build more organized, maintainable, and scalable applications. Classes define templates for objects, and objects are instances that contain both data (properties) and behavior (methods).

Start thinking about your applications in terms of objects that model real-world concepts. A shopping cart application naturally has Product, Customer, Cart, and Order objects. A blog has Post, Author, and Comment objects. This approach makes your code more intuitive and easier to work with.

Constructors help ensure objects start in valid states, and methods provide clean interfaces for working with object data. The $this keyword lets objects refer to themselves, enabling methods to work with the object's own properties.

Practice creating simple classes and objects before moving on to more advanced concepts. Understanding the basics thoroughly will make the advanced features in upcoming lessons much easier to grasp.

In the next lesson, we'll explore properties, methods, and visibility in detail, learning how to control access to object data and create more secure, well-designed classes.