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

PHP Classes and Objects

Understanding Classes and Objects

Classes and objects are the fundamental building blocks of Object-Oriented Programming in PHP. A class serves as a blueprint or template that defines the structure and behavior of objects, while an object is a specific instance of that class.

Think of a class as a cookie cutter and objects as the individual cookies made from that cutter. Each cookie (object) has the same basic shape (class structure) but can have different characteristics (property values).

The Paradigm Shift

Object-oriented programming represents a fundamental shift from procedural programming. Instead of organizing code as a sequence of functions operating on data, OOP organizes code into objects that contain both data (properties) and the functions (methods) that operate on that data. This encapsulation leads to:

Modularity: Each object is a self-contained unit with clear boundaries Reusability: Classes can be instantiated multiple times and inherited Maintainability: Changes to a class affect all its instances uniformly Real-world Modeling: Objects naturally represent real-world entities

Why Classes and Objects Matter

In procedural programming, data and functions are separate, which can lead to:

  • Functions that depend on global state
  • Difficulty tracking which functions modify which data
  • Complex parameter passing between functions
  • Challenges in organizing large codebases

Classes solve these problems by bundling related data and functionality together, creating a more intuitive and maintainable code structure.

Creating Your First Class

Let's start with a simple example of a PHP class:

<?php
class Book {
    // Properties (attributes)
    public $title;
    public $author;
    public $pages;
    public $isbn;
    
    // Methods (functions)
    public function getInfo() {
        return "'{$this->title}' by {$this->author}";
    }
    
    public function getPageCount() {
        return "This book has {$this->pages} pages";
    }
}
?>

Class Anatomy Explained:

Class Declaration: The class keyword followed by the class name (conventionally in PascalCase) defines a new class. PHP class names are case-insensitive, but using consistent casing is crucial for readability.

Properties: Also called attributes or member variables, these store the object's state. Each property can have:

  • A visibility modifier (public, private, protected)
  • A type declaration (PHP 7.4+)
  • A default value

Methods: Functions defined within a class that operate on the object's data. They have access to the object's properties through the $this pseudo-variable.

The $this Keyword: Inside class methods, $this refers to the current object instance. It's how methods access the object's properties and other methods. Think of it as the object referring to itself.

Creating and Using Objects

Once you have a class, you can create objects (instances) from it:

<?php
// Creating objects
$book1 = new Book();
$book1->title = "The Great Gatsby";
$book1->author = "F. Scott Fitzgerald";
$book1->pages = 180;
$book1->isbn = "978-0-7432-7356-5";

$book2 = new Book();
$book2->title = "To Kill a Mockingbird";
$book2->author = "Harper Lee";
$book2->pages = 281;
$book2->isbn = "978-0-06-112008-4";

// Using object methods
echo $book1->getInfo(); // Output: 'The Great Gatsby' by F. Scott Fitzgerald
echo $book2->getPageCount(); // Output: This book has 281 pages
?>

Object Creation Process:

The new Keyword: Creates a new instance of the class. This process:

  1. Allocates memory for the object
  2. Initializes properties to their default values
  3. Calls the constructor (if defined)
  4. Returns a reference to the new object

Property Access: The arrow operator (->) accesses object properties and methods. Unlike arrays which use square brackets, objects use this operator to navigate their members.

Object Independence: Each object maintains its own set of property values. Changing $book1->title doesn't affect $book2->title. This independence is fundamental to OOP - each object encapsulates its own state.

Method Invocation: When calling a method like $book1->getInfo(), PHP:

  1. Looks up the method in the object's class definition
  2. Sets $this to refer to $book1
  3. Executes the method code
  4. Returns any result

Constructors

Constructors are special methods that automatically run when an object is created. They're perfect for initializing object properties:

<?php
class Book {
    public $title;
    public $author;
    public $pages;
    public $isbn;
    
    // Constructor method
    public function __construct($title, $author, $pages, $isbn) {
        $this->title = $title;
        $this->author = $author;
        $this->pages = $pages;
        $this->isbn = $isbn;
    }
    
    public function getInfo() {
        return "'{$this->title}' by {$this->author}";
    }
    
    public function getSummary() {
        return $this->getInfo() . " ({$this->pages} pages, ISBN: {$this->isbn})";
    }
}

// Now creating objects is more convenient
$book = new Book("1984", "George Orwell", 328, "978-0-452-28423-4");
echo $book->getSummary();
// Output: '1984' by George Orwell (328 pages, ISBN: 978-0-452-28423-4)
?>

Constructor Deep Dive:

Magic Method: __construct() is a "magic method" - PHP calls it automatically at specific times. The double underscore prefix indicates it's a special PHP method.

Initialization Benefits:

  • Ensures objects are properly initialized before use
  • Enforces required parameters
  • Prevents incomplete object states
  • Centralizes initialization logic

Constructor Parameters: Unlike regular methods, constructor parameters define what's needed to create a valid object. This creates a contract: to get a Book object, you must provide these four pieces of information.

Constructor Overloading: PHP doesn't support multiple constructors, but you can simulate overloading using:

  • Default parameter values
  • Variable-length argument lists
  • Static factory methods
  • Builder pattern

Property and Method Visibility

PHP provides three visibility modifiers to control access to class members:

Understanding Visibility

Visibility modifiers are fundamental to encapsulation - one of OOP's core principles. They control which parts of your code can access specific properties and methods, creating clear boundaries and preventing unintended interactions.

Encapsulation Benefits:

  • Data Integrity: Prevents external code from putting objects in invalid states
  • Implementation Hiding: Internal details can change without affecting external code
  • Clear Interfaces: Public methods define how objects should be used
  • Debugging: Fewer access points mean easier troubleshooting

Public Visibility

Public properties and methods can be accessed from anywhere:

<?php
class PublicExample {
    public $publicProperty = "I'm accessible everywhere";
    
    public function publicMethod() {
        return "This method is public";
    }
}

$obj = new PublicExample();
echo $obj->publicProperty; // ✅ Works fine
echo $obj->publicMethod(); // ✅ Works fine
?>

When to Use Public:

Public members form your class's API - the interface other code uses to interact with your objects. Use public for:

  • Methods that provide the class's core functionality
  • Properties that are genuinely meant to be freely accessible
  • Constants that other code needs to reference

Public Visibility Risks:

  • No control over how properties are modified
  • Can't add validation when properties are set
  • Changes to public members can break dependent code
  • Difficult to track where properties are accessed

Private Visibility

Private properties and methods can only be accessed within the same class:

<?php
class PrivateExample {
    private $privateProperty = "Only accessible within this class";
    
    private function privateMethod() {
        return "This is a private method";
    }
    
    public function accessPrivate() {
        // Can access private members within the same class
        return $this->privateProperty . " - " . $this->privateMethod();
    }
}

$obj = new PrivateExample();
echo $obj->accessPrivate(); // ✅ Works fine
// echo $obj->privateProperty; // ❌ Error: Cannot access private property
// echo $obj->privateMethod(); // ❌ Error: Cannot access private method
?>

Private Member Benefits:

Implementation Freedom: Private members can be changed without affecting external code. This is crucial for maintaining large codebases where changing public interfaces is difficult.

Invariant Protection: Private properties ensure object integrity by preventing external code from putting the object in an invalid state.

Helper Methods: Private methods often serve as utilities for public methods, breaking complex operations into manageable pieces without cluttering the public interface.

Protected Visibility

Protected properties and methods can be accessed within the same class and its subclasses:

<?php
class ParentClass {
    protected $protectedProperty = "Accessible in this class and subclasses";
    
    protected function protectedMethod() {
        return "This is a protected method";
    }
}

class ChildClass extends ParentClass {
    public function accessProtected() {
        // Can access protected members in child classes
        return $this->protectedProperty . " - " . $this->protectedMethod();
    }
}

$child = new ChildClass();
echo $child->accessProtected(); // ✅ Works fine
// echo $child->protectedProperty; // ❌ Error: Cannot access protected property
?>

Protected Use Cases:

Protected visibility strikes a balance between public and private:

  • Allows inheritance while maintaining encapsulation
  • Enables child classes to extend functionality
  • Prevents external access while supporting class hierarchies
  • Common in framework and library design

Inheritance Consideration: Protected members form a contract with subclasses. Changing them can break child classes, so they require careful design.

Advanced Class Features

Static Properties and Methods

Static members belong to the class itself rather than any specific instance:

<?php
class Counter {
    private static $count = 0;
    private $instanceId;
    
    public function __construct() {
        self::$count++;
        $this->instanceId = self::$count;
    }
    
    public static function getCount() {
        return self::$count;
    }
    
    public function getInstanceId() {
        return $this->instanceId;
    }
    
    public static function resetCount() {
        self::$count = 0;
    }
}

echo Counter::getCount(); // Output: 0

$counter1 = new Counter();
$counter2 = new Counter();
$counter3 = new Counter();

echo Counter::getCount(); // Output: 3
echo $counter2->getInstanceId(); // Output: 2

Counter::resetCount();
echo Counter::getCount(); // Output: 0
?>

Static Members Explained:

Class-Level State: Static properties are shared across all instances. They're useful for:

  • Tracking information about all objects (like count)
  • Storing configuration that applies to all instances
  • Implementing singleton patterns
  • Caching shared data

self vs $this: Inside a class:

  • $this-> accesses instance members (specific to each object)
  • self:: accesses static members (shared by all objects)
  • Static methods can't use $this because they're not associated with any instance

Static Method Limitations: Since static methods don't have access to instance data, they:

  • Can't access non-static properties or methods
  • Are essentially namespaced functions
  • Should be used sparingly to maintain OOP principles

Class Constants

Constants in classes are values that never change:

<?php
class MathHelper {
    const PI = 3.14159;
    const E = 2.71828;
    
    public static function circleArea($radius) {
        return self::PI * $radius * $radius;
    }
    
    public static function circleCircumference($radius) {
        return 2 * self::PI * $radius;
    }
}

echo MathHelper::PI; // Output: 3.14159
echo MathHelper::circleArea(5); // Output: 78.53975
?>

Class Constants Benefits:

Immutability: Constants can't be changed after declaration, preventing accidental modifications and ensuring consistent values throughout your application.

No $ Sign: Unlike properties, constants don't use the $ prefix, making them visually distinct and indicating their immutable nature.

Access Methods:

  • Inside the class: self::CONSTANT_NAME
  • Outside the class: ClassName::CONSTANT_NAME
  • Through objects: $object::CONSTANT_NAME (PHP 5.3+)

Use Cases:

  • Configuration values
  • Status codes
  • Mathematical constants
  • API endpoints
  • Default values

Real-World Example: User Management System

Let's create a more practical example that demonstrates classes and objects in a real-world scenario:

<?php
class User {
    private $id;
    private $username;
    private $email;
    private $password;
    private $createdAt;
    private $isActive;
    
    // Constructor
    public function __construct($username, $email, $password) {
        $this->id = uniqid();
        $this->username = $username;
        $this->email = $email;
        $this->password = password_hash($password, PASSWORD_DEFAULT);
        $this->createdAt = new DateTime();
        $this->isActive = true;
    }
    
    // Getters
    public function getId() {
        return $this->id;
    }
    
    public function getUsername() {
        return $this->username;
    }
    
    public function getEmail() {
        return $this->email;
    }
    
    public function getCreatedAt() {
        return $this->createdAt->format('Y-m-d H:i:s');
    }
    
    public function isActive() {
        return $this->isActive;
    }
    
    // Setters
    public function setEmail($email) {
        if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->email = $email;
            return true;
        }
        return false;
    }
    
    public function setPassword($newPassword) {
        $this->password = password_hash($newPassword, PASSWORD_DEFAULT);
    }
    
    // Methods
    public function verifyPassword($password) {
        return password_verify($password, $this->password);
    }
    
    public function deactivate() {
        $this->isActive = false;
    }
    
    public function activate() {
        $this->isActive = true;
    }
    
    public function getProfile() {
        return [
            'id' => $this->id,
            'username' => $this->username,
            'email' => $this->email,
            'created_at' => $this->getCreatedAt(),
            'is_active' => $this->isActive
        ];
    }
    
    public function __toString() {
        return "User: {$this->username} ({$this->email})";
    }
}

// Usage example
$user1 = new User("johndoe", "[email protected]", "secretpassword");
$user2 = new User("janedoe", "[email protected]", "anothersecret");

echo $user1; // Output: User: johndoe ([email protected])
echo "\n";

// Check password
if ($user1->verifyPassword("secretpassword")) {
    echo "Password is correct!";
} else {
    echo "Invalid password!";
}
echo "\n";

// Update email
if ($user1->setEmail("[email protected]")) {
    echo "Email updated successfully!";
} else {
    echo "Invalid email format!";
}
echo "\n";

// Get user profile
print_r($user1->getProfile());
?>

Real-World Design Principles Demonstrated:

Data Encapsulation: All properties are private, accessed only through controlled methods. This ensures:

  • Email validation before updates
  • Password hashing is always applied
  • ID generation is consistent
  • Created date can't be modified

Security Considerations:

  • Passwords are never stored in plain text
  • Password verification doesn't expose the hash
  • Sensitive data isn't included in __toString()
  • No direct access to security-critical properties

Getter/Setter Pattern:

  • Getters provide read access to private properties
  • Setters add validation and business logic
  • Not all properties have setters (like ID and createdAt)
  • Return values indicate success/failure

Practical Methods: Beyond basic CRUD:

  • verifyPassword(): Secure authentication
  • activate()/deactivate(): State management
  • getProfile(): Filtered data for external use
  • __toString(): Convenient string representation

Best Practices for Classes and Objects

1. Use Meaningful Names

<?php
// Bad
class C {
    public $n;
    public function d() {}
}

// Good
class Customer {
    public $name;
    public function delete() {}
}
?>

Naming Principles:

  • Class names should be nouns (Customer, Order, Product)
  • Method names should be verbs or verb phrases (calculateTotal, sendEmail)
  • Property names should describe what they store
  • Avoid abbreviations and single letters
  • Use consistent naming conventions throughout your codebase

2. Keep Classes Focused (Single Responsibility Principle)

<?php
// Bad - Too many responsibilities
class User {
    public function saveToDatabase() {}
    public function sendEmail() {}
    public function validateInput() {}
    public function logActivity() {}
}

// Good - Focused responsibility
class User {
    public function getProfile() {}
    public function updateProfile() {}
    public function isActive() {}
}

class UserRepository {
    public function save(User $user) {}
    public function find($id) {}
}

class EmailService {
    public function sendEmail($to, $subject, $body) {}
}
?>

Single Responsibility Benefits:

  • Easier Testing: Each class has one reason to change
  • Better Organization: Clear where functionality belongs
  • Improved Reusability: Focused classes are more versatile
  • Simpler Maintenance: Changes affect smaller code areas

3. Use Type Hints and Return Types

<?php
class OrderService {
    public function calculateTotal(array $items): float {
        $total = 0.0;
        foreach ($items as $item) {
            $total += $item['price'] * $item['quantity'];
        }
        return $total;
    }
    
    public function createOrder(Customer $customer, array $items): Order {
        $total = $this->calculateTotal($items);
        return new Order($customer, $items, $total);
    }
}
?>

Type Safety Advantages:

  • Early Error Detection: Type mismatches caught immediately
  • Self-Documenting: Parameter and return types clarify usage
  • IDE Support: Better autocomplete and error detection
  • Refactoring Safety: Changes that break contracts are obvious

4. Implement Proper Encapsulation

<?php
class BankAccount {
    private $balance;
    private $accountNumber;
    
    public function __construct($initialBalance, $accountNumber) {
        $this->balance = max(0, $initialBalance); // Ensure non-negative
        $this->accountNumber = $accountNumber;
    }
    
    public function getBalance(): float {
        return $this->balance;
    }
    
    public function deposit(float $amount): bool {
        if ($amount > 0) {
            $this->balance += $amount;
            return true;
        }
        return false;
    }
    
    public function withdraw(float $amount): bool {
        if ($amount > 0 && $amount <= $this->balance) {
            $this->balance -= $amount;
            return true;
        }
        return false;
    }
}
?>

Encapsulation Principles Applied:

  • Private Properties: Balance can't be directly modified
  • Validated Operations: All modifications go through methods with checks
  • Consistent State: Object can never have negative balance
  • Clear Interface: Public methods define allowed operations

Common Patterns and Use Cases

1. Factory Pattern

<?php
class DatabaseConnection {
    private $host;
    private $database;
    private $username;
    private $password;
    
    private function __construct($host, $database, $username, $password) {
        $this->host = $host;
        $this->database = $database;
        $this->username = $username;
        $this->password = $password;
    }
    
    public static function create($host, $database, $username, $password) {
        return new self($host, $database, $username, $password);
    }
    
    public static function createFromConfig(array $config) {
        return new self(
            $config['host'],
            $config['database'],
            $config['username'],
            $config['password']
        );
    }
}

// Usage
$db1 = DatabaseConnection::create("localhost", "mydb", "user", "pass");

Factory Pattern Benefits:

Multiple Creation Methods: Static factory methods provide different ways to create objects, accommodating various use cases without constructor overloading.

Private Constructor: Prevents direct instantiation, ensuring objects are created through factory methods that can:

  • Add validation
  • Return cached instances
  • Choose different implementations
  • Handle complex initialization

Named Constructors: Factory method names describe what they create, making code more readable than constructor parameters alone.

Understanding classes and objects is fundamental to PHP development. They provide structure, reusability, and maintainability that procedural code can't match. Master these concepts, and you'll write more professional, scalable PHP applications.