定制 std-out/simple-data-objects 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

std-out/simple-data-objects

Composer 安装命令:

composer require std-out/simple-data-objects

包简介

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

README 文档

README

Tests Security Latest Version on Packagist Total Downloads PHP License

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

Features

  • Hydrate from anything — array, stdClass, Arrayable, JsonSerializable, JSON string
  • Nested DTOs — deeply nested objects hydrated automatically
  • Enum supportBackedEnum 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

Version
PHP ^8.4
illuminate/contracts ^10.0 | ^11.0 | ^12.0 | ^13.0
illuminate/support ^10.0 | ^11.0 | ^12.0 | ^13.0

Installation

composer require std-out/simple-data-objects

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

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

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

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

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

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

$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

#[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

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

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

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

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

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 4242
FloatCast(2) "9.9876"9.99 9.999.99
BooleanCast "yes"/"1"/"on"/"true"true boolbool
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 → plaintext plaintext → AES-256-CBC + base64

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

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

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

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

MIT — see LICENSE.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-26

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固