Projectsphp-dataSerialization

PHP Data

Package

Lightweight, framework-agnostic Data Transfer Object (DTO) library with first-class Laravel integration.

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:

  1. #[WithTransformer] / #[WithCastAndTransformer] — a local transformer wins when present (see Casts & Transformers).
  2. Otherwise the built-in / self-serializing rules below apply.
ValueSerialized as
Nested DataIts own toArray() (recursive)
BackedEnumIts scalar ->value
DateTimeInterface (Carbon, etc.)ISO-8601 (c), or a custom format via #[DateFormat]
LazyOmitted unless include()d; then the resolved value of its callback
Sometimes (missing)Omitted from the output entirely
Illuminate\Contracts\Support\ArrayableIts toArray() (when Laravel is present)
StringableLeft as-is
ArraysRecursively transformed, with any Sometimes entries filtered out
Objects implementing DataSerializeableTheir 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;
    }
}