Content · Programming · PHP

Choosing Arrays vs DTOs for Structured Data

Learn when to use PHP arrays or Data Transfer Objects (DTOs) for structured data. Compare flexibility, type safety, validation, and maintainability with practical examples.

2 views Published Oct 16, 2026 Updated Oct 16, 2026 Admin

PHP arrays are an easy way to organize information. They can store anything from a list of names to complex records containing multiple fields.

However, as applications grow, passing structured data between functions and classes using arrays can become harder to manage.

One alternative is a Data Transfer Object (DTO), which represents structured data using a dedicated PHP class.

In this guide, you'll learn the differences between arrays and DTOs, when to use each approach, and how to build a simple DTO in PHP.

Using Arrays for Structured Data


An associative array stores information using named keys.

For example, an order might contain:

<?php

$order = [
    "customer" => "Alex",
    "quantity" => 2,
    "unit_price_cents" => 2500
];

echo $order["customer"];

Output:

Alex

This approach is simple, flexible, and requires no additional class definitions.

Arrays are particularly useful when:

  • Processing small or temporary collections.
  • Working with data whose structure changes frequently.
  • Handling decoded JSON or database results.
  • Writing short scripts with limited complexity.

However, arrays don't enforce a fixed set of keys or individual field types.

For example, accidentally writing $order["quanity"] instead of $order["quantity"] can produce an undefined array key warning.

These problems become harder to identify when the same data structure is passed through many functions.

What Is a DTO in PHP?


A Data Transfer Object is a class designed primarily to carry structured information between different parts of an application.

Instead of relying on array keys, a DTO defines its fields explicitly using class properties.

For example:

<?php

final class OrderData
{
    public function __construct(
        public readonly string $customer,
        public readonly int $quantity,
        public readonly int $unitPriceCents
    ) {
    }
}

$order = new OrderData(
    "Alex",
    2,
    2500
);

echo $order->customer;

Output:

Alex

This example uses constructor property promotion and readonly properties, both supported in PHP 8.1.

The class defines exactly which fields it expects and their PHP types.

The readonly keyword prevents those properties from being reassigned after initialization.

PHP Arrays vs DTOs: Key Differences


Feature Associative Arrays DTOs
Structure Flexible keys and values Explicitly declared properties
Type enforcement No per-key enforcement Typed properties
IDE assistance May require additional annotations Property names and types are directly available
Validation Requires separate checks Can be performed during construction
Code required Usually less Requires a class definition
Best suited for Flexible or temporary data Stable, predictable data structures

Neither approach is automatically better in every situation. The right choice depends on how the data will be used.

Practical Example: Converting an Array into a DTO


Imagine you're building an application that receives order information from an API.

The incoming data arrives as an associative array, but you want a predictable structure before passing it to other functions.

Let's validate the input and convert it into a DTO.

Create a file named order-dto.php:

<?php

declare(strict_types=1);

final class OrderData
{
    public function __construct(
        public readonly string $customer,
        public readonly int $quantity,
        public readonly int $unitPriceCents
    ) {
        if (trim($customer) === "") {
            throw new InvalidArgumentException(
                "Customer name is required."
            );
        }

        if ($quantity < 1 || $unitPriceCents < 0) {
            throw new InvalidArgumentException(
                "Invalid order values."
            );
        }
    }
}

// Convert validated array data into a DTO.

function createOrderData(array $data): OrderData
{
    $customer = $data["customer"] ?? null;
    $quantity = $data["quantity"] ?? null;
    $price = $data["unit_price_cents"] ?? null;

    if (
        !is_string($customer) ||
        !is_int($quantity) ||
        !is_int($price)
    ) {
        throw new InvalidArgumentException(
            "Invalid order data."
        );
    }

    return new OrderData(
        $customer,
        $quantity,
        $price
    );
}

// Calculate the order total.

function calculateTotal(OrderData $order): int
{
    return $order->quantity
        * $order->unitPriceCents;
}

// Simulated incoming data.

$input = [
    "customer" => "Alex",
    "quantity" => 2,
    "unit_price_cents" => 2500
];

try {

    $order = createOrderData($input);

    $total = calculateTotal($order);

    echo "<p>Customer: "
        . htmlspecialchars(
            $order->customer,
            ENT_QUOTES,
            "UTF-8"
        )
        . "</p>";

    echo "<p>Total: $"
        . number_format($total / 100, 2)
        . "</p>";

} catch (InvalidArgumentException $exception) {

    echo "Invalid order information.";
}

Browser output:

Customer: Alex
Total: $50.00

Here's how the example works:

  1. The application receives structured information in an array.
  2. createOrderData() checks that the required fields have the expected types.
  3. The constructor validates the business constraints and initializes the DTO.
  4. calculateTotal() accepts an OrderData object rather than an unstructured array.
  5. The application displays the result using the DTO's properties.

Separating input validation from data processing makes it easier to understand what each function expects.

The example uses integer cents for prices to avoid common floating-point precision problems. Production applications should also consider integer overflow limits and their monetary calculation requirements.

When Should You Choose a DTO?


DTOs are particularly useful when data has a known structure and moves through multiple application components.

Good examples include:

  • Passing validated API request data to application services.
  • Representing records returned by a repository.
  • Moving information between different application layers.
  • Defining clear inputs and outputs for important operations.

DTOs also work well with IDE autocompletion and static analysis tools because properties have explicit names and types.

However, typed properties alone don't validate every application rule. For example, an integer quantity can still be negative unless you explicitly reject that value.

When Are Arrays the Better Choice?


Arrays remain useful for simple lists, dynamic configuration, temporary calculations, and data with unpredictable keys.

For example:

<?php

$categories = [
    "PHP",
    "JavaScript",
    "Python"
];

foreach ($categories as $category) {
    echo $category . PHP_EOL;
}

Creating a dedicated DTO for a basic list of strings would usually add unnecessary complexity.

Also, don't assume DTOs are always faster or use less memory. PHP arrays and objects have different storage and copying behavior, and performance depends on the workload.

Choose DTOs primarily for clearer contracts and maintainability, not as an automatic performance optimization.

Key Takeaways


  • Associative arrays provide flexible storage for structured data.
  • DTOs define structured data through dedicated PHP classes.
  • Typed DTO properties help enforce expected field types.
  • Use constructors to validate values and establish valid initial state.
  • Use arrays for simple collections and dynamic data.
  • Use DTOs when a stable data structure is shared between application components.
  • Arrays and DTOs can work together: receive data as an array, validate it, and convert it into a DTO.

Choosing between PHP arrays and DTOs is about balancing simplicity with structure. Arrays are convenient for flexible data, while DTOs provide clearer contracts for information that needs to remain consistent across your application.

Share this resourceSend this page to someone who may find it useful.