Serialization
toArray() converts a Data instance back into a plain array, recursively.
$product = ProductData::make(title: 'Widget', reviews: 5, rating: 4.5);
$product->toArray();
// ['title' => 'Widget', 'reviews' => 5, 'rating' => 4.5]
How values are transformed
Each public property is transformed on the way out. Resolution order:
#[WithTransformer]/#[WithCastAndTransformer]— a local transformer wins when present (see Casts & Transformers).- Otherwise the built-in / self-serializing rules below apply.
| Value | Serialized as |
|---|---|
Nested Data | Its own toArray() (recursive) |
BackedEnum | Its scalar ->value |
DateTimeInterface (Carbon, etc.) | ISO-8601 (c), or a custom format via #[DateFormat] |
Lazy | Omitted unless include()d; then the resolved value of its callback |
Sometimes (missing) | Omitted from the output entirely |
Illuminate\Contracts\Support\Arrayable | Its toArray() (when Laravel is present) |
Stringable | Left as-is |
| Arrays | Recursively transformed, with any Sometimes entries filtered out |
Objects implementing DataSerializeable | Their toDataSerializedValue() |
null is left as null and is never passed to a transformer.
Custom transformers
For attribute-based control of how a property serializes, attach a
Transformer. Use
WithCastAndTransformer
when one class handles both hydration and serialization.
Value objects can also self-serialize via
DataSerializeable.
Including/excluding properties
Shape the output per call with fluent, chainable partials. They reference the real
PHP property names (before any #[MapName]
remapping):
$user->only('id', 'name')->toArray(); // allow-list
$user->except('email')->toArray(); // deny-list
$user->exclude('email')->toArray(); // deny-list (companion to include())
except()/exclude() win over only() when a key appears in both.
Lazy properties are omitted from the
output unless you opt them in with include():
$report->include('generatedAt')->toArray();
Wrapping
Nest the payload under a key — handy for API envelopes:
$user->wrap('data')->toArray();
// ['data' => ['id' => 1, 'name' => 'Ada']]
To wrap every instance of a class by default, override defaultWrap():
class UserData extends Data
{
protected function defaultWrap(): ?string
{
return 'data';
}
}
withoutWrapping() disables it for a single call, overriding defaultWrap().
Wrapping is applied after transformToArray() and flows through toJson().
JSON, and returning from controllers
Every Data object implements JsonSerializable, so it encodes directly:
json_encode($product); // uses toArray()
$product->toJson(); // same, as a string
$product->toJson(JSON_PRETTY_PRINT);
On Laravel, return Data objects from controllers via LaravelData or the
ResponsableData trait — see Laravel Integration.
The transformToArray() hook
Override transformToArray() on a Data class for a final say over the payload —
useful for renaming keys, adding derived fields, or reshaping output:
class ProductData extends Data
{
public function __construct(
public string $title,
public float $rating,
) {}
public function transformToArray(array $data): array
{
$data['slug'] = str($data['title'])->slug()->value();
return $data;
}
}