PHPackages                             std-out/simple-data-objects - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. std-out/simple-data-objects

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

std-out/simple-data-objects
===========================

Lightweight typed Data Transfer Object library for PHP 8.1+ with attribute-driven hydration

v0.1.0(today)00MITPHPPHP ^8.4CI passing

Since Jun 26Pushed todayCompare

[ Source](https://github.com/std-out/simple-data-objects)[ Packagist](https://packagist.org/packages/std-out/simple-data-objects)[ RSS](/packages/std-out-simple-data-objects/feed)WikiDiscussions main Synced today

READMEChangelog (3)Dependencies (6)Versions (2)Used By (0)

Simple Data Objects
===================

[](#simple-data-objects)

[![Tests](https://github.com/std-out/simple-data-objects/actions/workflows/tests.yml/badge.svg)](https://github.com/std-out/simple-data-objects/actions/workflows/tests.yml)[![Security](https://github.com/std-out/simple-data-objects/actions/workflows/security.yml/badge.svg)](https://github.com/std-out/simple-data-objects/actions/workflows/security.yml)[![Latest Version on Packagist](https://camo.githubusercontent.com/0ba1c6bbf85e5274c0bcf80c7270578198feee9cdbc7789de5ec2a014f1f85db/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7374642d6f75742f73696d706c652d646174612d6f626a656374732e737667)](https://packagist.org/packages/std-out/simple-data-objects)[![Total Downloads](https://camo.githubusercontent.com/e0123238d0052b235478d7a0f78594c7096fccbed0f276d68dd57b20e4716582/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7374642d6f75742f73696d706c652d646174612d6f626a656374732e737667)](https://packagist.org/packages/std-out/simple-data-objects)[![PHP](https://camo.githubusercontent.com/e9b4fc8e2edfe2ad83ab5e32546edefd37388dfa099b72dfe95f1a4820360f55/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d253545382e342d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://packagist.org/packages/std-out/simple-data-objects)[![License](https://camo.githubusercontent.com/8bb50fd2278f18fc326bf71f6e88ca8f884f72f179d3e555e20ed30157190d0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e2e737667)](LICENSE)

Lightweight, attribute-driven Data Transfer Objects for PHP 8.4+. Works standalone or inside Laravel 10–13.

---

Features
--------

[](#features)

- **Hydrate from anything** — array, `stdClass`, `Arrayable`, `JsonSerializable`, JSON string
- **Nested DTOs** — deeply nested objects hydrated automatically
- **Enum support** — `BackedEnum` cast by value, `UnitEnum` passed through
- **Typed collections** — `#[DataCollection(UserData::class)]` produces a typed `TypedDataCollection`
- **Cast system** — `#[Cast(...)]` for dates, booleans, JSON, integers, floats, strings, enums with fallback, encryption
- **Key mapping** — `#[MapPropertyName]` per property, or `#[TransformKeys]` at class level
- **Hidden fields** — `#[Hidden]` excludes a property from `toArray()` / JSON output
- **Null omission** — `#[IgnoreIfNull]` skips a null field from serialization output
- **Reflection cache** — metadata built once per class, all derived sets computed at cache time
- **Laravel integration** — optional trait adds `fromRequest()`, `fromModel()`, `toResponse()`

---

Requirements
------------

[](#requirements)

VersionPHP^8.4`illuminate/contracts`^10.0 | ^11.0 | ^12.0 | ^13.0`illuminate/support`^10.0 | ^11.0 | ^12.0 | ^13.0---

Installation
------------

[](#installation)

```
composer require std-out/simple-data-objects
```

---

Quick Start
-----------

[](#quick-start)

```
use StdOut\SimpleDataObjects\BaseData;

class UserData extends BaseData
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly ?string $phone = null,
    ) {}
}

$user = UserData::from(['name' => 'Alice', 'email' => 'alice@example.com']);

$user->name;       // 'Alice'
$user->toArray();  // ['name' => 'Alice', 'email' => 'alice@example.com', 'phone' => null]
$user->toJson();   // '{"name":"Alice","email":"alice@example.com","phone":null}'
```

---

Hydration
---------

[](#hydration)

### From array, stdClass, Arrayable, JsonSerializable

[](#from-array-stdclass-arrayable-jsonserializable)

```
UserData::from(['name' => 'Alice', 'email' => 'alice@example.com']);
UserData::from((object) ['name' => 'Alice', 'email' => 'alice@example.com']);
UserData::from(collect(['name' => 'Alice', 'email' => 'alice@example.com']));
```

### From JSON string

[](#from-json-string)

```
UserData::fromJson('{"name":"Alice","email":"alice@example.com"}');
```

### Nested DTOs

[](#nested-dtos)

```
class ProfileData extends BaseData
{
    public function __construct(
        public readonly UserData $user,
        public readonly string $bio,
    ) {}
}

$profile = ProfileData::from([
    'user' => ['name' => 'Alice', 'email' => 'alice@example.com'],
    'bio'  => 'Software Engineer',
]);

$profile->user->name; // 'Alice'
```

### Enums

[](#enums)

```
enum Status: string
{
    case Active   = 'active';
    case Inactive = 'inactive';
}

class OrderData extends BaseData
{
    public function __construct(
        public readonly int    $id,
        public readonly Status $status,
    ) {}
}

$order = OrderData::from(['id' => 1, 'status' => 'active']);
$order->status; // Status::Active
```

### Collections

[](#collections)

```
use StdOut\SimpleDataObjects\Attributes\DataCollection;
use StdOut\SimpleDataObjects\TypedDataCollection;

class TeamData extends BaseData
{
    public function __construct(
        public readonly string $name,
        #[DataCollection(UserData::class)]
        public readonly TypedDataCollection $members,
    ) {}
}

$team = TeamData::from([
    'name'    => 'Engineering',
    'members' => [
        ['name' => 'Alice', 'email' => 'alice@example.com'],
        ['name' => 'Bob',   'email' => 'bob@example.com'],
    ],
]);

$team->members->count(); // 2
$team->members->first(); // UserData instance
```

Static factory:

```
$collection = UserData::collection([
    ['name' => 'Alice', 'email' => 'alice@example.com'],
    ['name' => 'Bob',   'email' => 'bob@example.com'],
]);
```

---

Serialization
-------------

[](#serialization)

```
$user->toArray();        // ['name' => 'Alice', 'email' => 'alice@example.com', 'phone' => null]
$user->toJson();         // JSON string
(string) $user;          // same as toJson()
$user->only('name');     // ['name' => 'Alice']
$user->except('phone');  // ['name' => 'Alice', 'email' => 'alice@example.com']
json_encode($user);      // works via JsonSerializable
```

---

Attributes
----------

[](#attributes)

### `#[Hidden]` — exclude from output

[](#hidden--exclude-from-output)

```
use StdOut\SimpleDataObjects\Attributes\Hidden;

class AuthData extends BaseData
{
    public function __construct(
        public readonly string $username,
        #[Hidden]
        public readonly string $password,
    ) {}
}

AuthData::from(['username' => 'alice', 'password' => 'secret'])->toArray();
// ['username' => 'alice']
```

### `#[IgnoreIfNull]` — omit field when null

[](#ignoreifnull--omit-field-when-null)

```
use StdOut\SimpleDataObjects\Attributes\IgnoreIfNull;

class ArticleData extends BaseData
{
    public function __construct(
        public readonly string  $title,
        #[IgnoreIfNull]
        public readonly ?string $subtitle = null,
    ) {}
}

ArticleData::from(['title' => 'Hello'])->toArray();
// ['title' => 'Hello']  — 'subtitle' omitted because null
```

### `#[MapPropertyName]` — remap a single input key

[](#mappropertyname--remap-a-single-input-key)

```
use StdOut\SimpleDataObjects\Attributes\MapPropertyName;

class UserData extends BaseData
{
    public function __construct(
        #[MapPropertyName('user_name')]
        public readonly string $userName,
    ) {}
}

UserData::from(['user_name' => 'alice']); // $userName = 'alice'
```

### `#[TransformKeys]` — remap all keys at class level

[](#transformkeys--remap-all-keys-at-class-level)

```
use StdOut\SimpleDataObjects\Attributes\TransformKeys;

#[TransformKeys(TransformKeys::SNAKE_CASE)]
class UserData extends BaseData
{
    public function __construct(
        public readonly string $firstName,  // reads 'first_name'
        public readonly string $lastName,   // reads 'last_name'
    ) {}
}

UserData::from(['first_name' => 'Alice', 'last_name' => 'Smith']);
```

Available strategies: `TransformKeys::SNAKE_CASE`, `TransformKeys::CAMEL_CASE`.

> `#[MapPropertyName]` always takes priority over a class-level `#[TransformKeys]`.

---

Cast System
-----------

[](#cast-system)

Apply any cast with `#[Cast(new SomeCast(...))]` on a constructor parameter.

### Built-in casts

[](#built-in-casts)

Cast`get()` — hydration`set()` — serialization`DateTimeCast($format)`string → `DateTime``DateTime` → formatted string`DateTimeImmutableCast($format)`string → `DateTimeImmutable``DateTimeImmutable` → formatted string`EnumCast(Status::class, Status::Unknown)`string → `Status` (falls back to default)`Status` → value`IntegerCast``"42"` → `42``42` → `42``FloatCast(2)``"9.9876"` → `9.99``9.99` → `9.99``BooleanCast``"yes"/"1"/"on"/"true"` → `true``bool` → `bool``TrimCast``" hello "` → `"hello"`same`TrimCast(TrimCast::LOWERCASE)``" ABC "` → `"abc"`same`TrimCast(TrimCast::UPPERCASE)``" abc "` → `"ABC"`same`JsonCast``'{"k":"v"}'` → `['k' => 'v']``['k' => 'v']` → `'{"k":"v"}'``EncryptedCast('key')`base64 → plaintextplaintext → AES-256-CBC + base64### Examples

[](#examples)

```
use StdOut\SimpleDataObjects\Attributes\Cast;
use StdOut\SimpleDataObjects\Casts\BooleanCast;
use StdOut\SimpleDataObjects\Casts\DateTimeCast;
use StdOut\SimpleDataObjects\Casts\DateTimeImmutableCast;
use StdOut\SimpleDataObjects\Casts\EncryptedCast;
use StdOut\SimpleDataObjects\Casts\EnumCast;
use StdOut\SimpleDataObjects\Casts\FloatCast;
use StdOut\SimpleDataObjects\Casts\IntegerCast;
use StdOut\SimpleDataObjects\Casts\JsonCast;
use StdOut\SimpleDataObjects\Casts\TrimCast;

class ProductData extends BaseData
{
    public function __construct(
        #[Cast(new TrimCast(TrimCast::LOWERCASE))]
        public readonly string           $sku,

        #[Cast(new IntegerCast)]
        public readonly int              $quantity,

        #[Cast(new FloatCast(2))]
        public readonly float            $price,

        #[Cast(new BooleanCast)]
        public readonly bool             $available,

        #[Cast(new JsonCast)]
        public readonly array            $meta,

        #[Cast(new DateTimeCast('Y-m-d'))]
        public readonly DateTime         $createdAt,

        #[Cast(new DateTimeImmutableCast(DateTimeInterface::ATOM))]
        public readonly ?DateTimeImmutable $publishedAt = null,

        #[Cast(new EnumCast(Status::class, Status::Inactive))]
        public readonly Status           $status = Status::Inactive,
    ) {}
}
```

### Custom casts

[](#custom-casts)

Implement `CastsValue` to create your own cast:

```
use StdOut\SimpleDataObjects\Contracts\CastsValue;

final class MoneyCast implements CastsValue
{
    public function __construct(private readonly string $currency = 'USD') {}

    public function get(mixed $value): ?int
    {
        return $value === null ? null : (int) round((float) $value * 100);
    }

    public function set(mixed $value): ?string
    {
        return $value === null ? null : number_format($value / 100, 2);
    }
}

// Usage
#[Cast(new MoneyCast('EUR'))]
public readonly int $price,
```

---

Laravel Integration
-------------------

[](#laravel-integration)

Add the `HasLaravelIntegration` trait to unlock `fromRequest()`, `fromModel()`, and `toResponse()`.

> **Requires:** `illuminate/http` and `illuminate/database` (available if using full Laravel).

```
use StdOut\SimpleDataObjects\BaseData;
use StdOut\SimpleDataObjects\Concerns\HasLaravelIntegration;

abstract class AppData extends BaseData
{
    use HasLaravelIntegration;
}
```

```
class CreateUserData extends AppData
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
    ) {}
}

// In a controller
public function store(Request $request): JsonResponse
{
    $data = CreateUserData::fromRequest($request); // uses $request->validated() if available
    return $data->toResponse($request);            // JsonResponse
}

// From an Eloquent model
$data = UserData::fromModel($user);
```

---

Running Tests
-------------

[](#running-tests)

```
make test        # run PHPUnit in Docker
make lint        # fix code style with Pint
make lint-check  # check style without changes (CI)
make shell       # open shell in container
```

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/fd5e28b0992ba48b1577e4864c49893b84cf6f7ae302dff2fedf265c0f9882f5?d=identicon)[yuriizee](/maintainers/yuriizee)

---

Top Contributors

[![yuriizee](https://avatars.githubusercontent.com/u/24149807?v=4)](https://github.com/yuriizee "yuriizee (7 commits)")

---

Tags

laravelSimpledata-transfer-objectdtohydrationdata object

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/std-out-simple-data-objects/health.svg)

```
[![Health](https://phpackages.com/badges/std-out-simple-data-objects/health.svg)](https://phpackages.com/packages/std-out-simple-data-objects)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[laravel/ai

The official AI SDK for Laravel.

9782.1M162](/packages/laravel-ai)[moonshine/moonshine

Laravel administration panel

1.3k239.9k76](/packages/moonshine-moonshine)[linkxtr/laravel-qrcode

A clean, modern, and easy-to-use QR code generator for Laravel

3614.9k](/packages/linkxtr-laravel-qrcode)[tomshaw/electricgrid

A feature-rich Livewire package designed for projects that require dynamic, interactive data tables.

119.2k](/packages/tomshaw-electricgrid)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
