Skip to content

Latest commit

 

History

History
455 lines (369 loc) · 9.99 KB

File metadata and controls

455 lines (369 loc) · 9.99 KB

Associative Arrays vs Classes

The Problem

Using associative arrays instead of classes:

  • No type safety - Values can be anything
  • Endless validation - Check every key in every function
  • Easy typos - $data['emai'] vs $data['email']
  • No IDE support - No autocomplete, no refactoring
  • Runtime errors - Missing keys cause PHP Notices
  • Unclear API - What keys are required?

The Pain of Arrays

Example: User Data

With Array:

function create_user($user_data) {
    // Endless validation
    if (!isset($user_data['name'])) {
        return false;
    }
    
    if (!isset($user_data['email'])) {
        return false;
    }
    
    // Easy to forget validation
    $phone = $user_data['phone']; // Notice if not set!
    
    // Typo: 'emai' instead of 'email' - silent bug
    return wp_create_user(
        $user_data['name'],
        'pass',
        $user_data['emai']
    );
}

With Class:

class UserData {
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly ?string $phone = null
    ) {
        // Validation once, in constructor
        if ( empty( $name ) ) {
            throw new InvalidArgumentException( 'Name required' );
        }
        
        if ( ! is_email( $email ) ) {
            throw new InvalidArgumentException( 'Invalid email' );
        }
    }
}

function create_user( UserData $user_data ): int {
    // No validation needed!
    // No typos possible!
    // IDE autocomplete works!
    return wp_create_user( $user_data->name, 'pass', $user_data->email );
}

Problems with Arrays

1. No Type Safety

// Array - anything goes
$product = [
    'price' => '10.99',  // String? Number? Who knows!
    'name' => 123,       // Name as number? Sure!
    'active' => 'yes',   // Boolean as string? Why not!
];

// Class - guaranteed types
class Product {
    public function __construct(
        public readonly float $price,   // Always float
        public readonly string $name,   // Always string
        public readonly bool $active    // Always bool
    ) {}
}

2. Missing Key Errors

// Array - runtime error
$user = ['name' => 'John'];
echo $user['email']; // PHP Notice: Undefined index

// Need defensive checks everywhere
echo $user['email'] ?? 'No email';

// Class - compile time safety
class User {
    public function __construct(
        public readonly string $name,
        public readonly ?string $email = null
    ) {}
}

$user = new User('John');
echo $user->email; // null - no error, intentional

3. Typo Hell

// Array - silent bugs from typos
$data['email'] = 'test@example.com';
echo $data['emai'];  // Notice, or worse: undefined behavior

// Different spellings in same codebase
$user['first_name']  // snake_case
$user['lastName']    // camelCase
$user['EMail']       // PascalCase

// Class - IDE catches typos immediately
class User {
    public string $email;
}

$user = new User();
$user->emai; // IDE error before you even run it!

4. Validation Repetition

// Array - validate everywhere
function create_order($order) {
    if (!isset($order['total'])) {
        return false;
    }
    if (!is_numeric($order['total'])) {
        return false;
    }
    if ($order['total'] < 0) {
        return false;
    }
    // Process...
}

function update_order($order) {
    // Same validation again!
    if (!isset($order['total'])) {
        return false;
    }
    if (!is_numeric($order['total'])) {
        return false;
    }
    if ($order['total'] < 0) {
        return false;
    }
    // Process...
}

// Class - validate once
class Order {
    public function __construct(
        public readonly float $total
    ) {
        if ( $total < 0 ) {
            throw new InvalidArgumentException( 'Total cannot be negative' );
        }
    }
}

// No validation needed anywhere else!
function create_order( Order $order ): void { }
function update_order( Order $order ): void { }

5. No IDE Support

// Array - no autocomplete
$user = get_user_data();
$user[''] // IDE shows nothing - have to remember/guess

// Class - full IDE support
$user = get_user_data();
$user-> // IDE shows: name, email, phone, etc.

6. Unclear API

// Array - what does this function need?
function send_email($params) {
    // to? from? subject? body? cc? bcc?
    // No way to know without reading implementation
}

// Have to read docs or source code
send_email([
    'to' => 'user@example.com',
    'subject' => 'Hi',
    // Forgot 'body' - runtime error!
]);

// Class - crystal clear API
class EmailMessage {
    public function __construct(
        public readonly string $to,
        public readonly string $subject,
        public readonly string $body
    ) {}
}

function send_email( EmailMessage $email ): void {
    // IDE shows exactly what's required!
}

// Named parameters make it even clearer
$email = new EmailMessage(
    to: 'user@example.com',
    subject: 'Hi',
    body: 'Hello!'  // Can't forget - constructor requires it!
);

7. Refactoring Nightmare

// Array - change 'email' to 'email_address'
// Have to find and replace in 50 files
// Easy to miss one
// No compiler help

$user['email'] = 'test@example.com';
echo $user['email'];
validate_email($user['email']);

// Class - rename in one place
class User {
    public string $email_address; // IDE renames all usages!
}

$user->email_address = 'test@example.com';
echo $user->email_address;
validate_email($user->email_address);

When Arrays Make Sense

Arrays are fine for:

Simple lists - ['apple', 'banana', 'orange']
Database results - Direct mapping from DB
JSON encoding - Temporary data structures
Simple key-value configs - Small, flat configurations

Arrays are NOT good for:

Complex data structures - Use classes
Function parameters - Use classes or named params
Return values - Use classes or value objects
Business entities - Use domain models

Migration Strategy

Before (Array):

function create_product($data) {
    if (!isset($data['name'])) {
        return false;
    }
    if (!isset($data['price'])) {
        return false;
    }
    if (!is_numeric($data['price'])) {
        return false;
    }
    
    // Process...
}

$product = [
    'name' => 'Widget',
    'price' => 19.99,
];

create_product($product);

After (Class):

class Product {
    public function __construct(
        public readonly string $name,
        public readonly float $price
    ) {
        if (empty($name)) {
            throw new InvalidArgumentException('Name required');
        }
        
        if ($price < 0) {
            throw new InvalidArgumentException('Price cannot be negative');
        }
    }
}

function create_product(Product $product): int {
    // No validation needed!
    // Type hints guarantee correct data
    return save_to_database($product);
}

$product = new Product(
    name: 'Widget',
    price: 19.99
);

create_product($product);

Value Objects

Perfect use case for classes:

class Money {
    public function __construct(
        public readonly float $amount,
        public readonly string $currency = 'USD'
    ) {
        if ($amount < 0) {
            throw new InvalidArgumentException('Amount cannot be negative');
        }
    }
    
    public function add(Money $other): Money {
        if ($this->currency !== $other->currency) {
            throw new InvalidArgumentException('Currency mismatch');
        }
        
        return new Money($this->amount + $other->amount, $this->currency);
    }
    
    public function format(): string {
        return $this->currency . ' ' . number_format($this->amount, 2);
    }
}

// Usage
$price = new Money(19.99);
$tax = new Money(2.00);
$total = $price->add($tax);
echo $total->format(); // "USD 21.99"

Benefits of Classes

1. Type Safety

function process_order(Order $order): void {
    // $order is guaranteed to be an Order object
    // All properties have their declared types
}

2. Validation in One Place

class Email {
    public function __construct(
        public readonly string $address
    ) {
        if (!is_email($address)) {
            throw new InvalidArgumentException('Invalid email');
        }
    }
}

// Valid everywhere an Email object exists

3. Behavior with Data

class DateRange {
    public function get_days(): int {
        return $this->start->diff($this->end)->days;
    }
    
    public function contains(DateTime $date): bool {
        return $date >= $this->start && $date <= $this->end;
    }
}

4. Immutability

class Point {
    public function __construct(
        public readonly float $x,
        public readonly float $y
    ) {}
    
    // Cannot be modified after creation
}

5. Clear Intent

// Array - unclear
function search($filters) { }

// Class - clear
function search(ProductSearchFilters $filters): array { }

Key Takeaways

Use classes for structured data
Validation in constructor - once and done
Type hints everywhere - catch errors early
Readonly properties - prevent mutations
Named parameters - self-documenting code
IDE support - autocomplete, refactoring
Value objects - encapsulate behavior

❌ Don't use arrays for complex structures
❌ Don't repeat validation everywhere
❌ Don't rely on documentation for structure
❌ Don't sacrifice type safety for convenience
❌ Don't make typos possible
❌ Don't create unclear APIs

The Bottom Line

Arrays are for collections. Classes are for structures.

// Collection - array is fine
$fruits = ['apple', 'banana', 'orange'];

// Structure - use a class
class User {
    public function __construct(
        public readonly string $name,
        public readonly string $email
    ) {}
}

If you're writing isset($array['key']) more than once, you need a class.

Your IDE, your teammates, and your future self will thank you!