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?
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 );
}// 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
) {}
}// 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// 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!// 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 { }// 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.// 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!
);// 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);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
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);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);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"function process_order(Order $order): void {
// $order is guaranteed to be an Order object
// All properties have their declared types
}class Email {
public function __construct(
public readonly string $address
) {
if (!is_email($address)) {
throw new InvalidArgumentException('Invalid email');
}
}
}
// Valid everywhere an Email object existsclass 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;
}
}class Point {
public function __construct(
public readonly float $x,
public readonly float $y
) {}
// Cannot be modified after creation
}// Array - unclear
function search($filters) { }
// Class - clear
function search(ProductSearchFilters $filters): array { }✅ 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
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!