Skip to content

ndcorder/php-datapack

Repository files navigation

php-datapack

Framework-agnostic DTOs with hydration, validation, and serialization for PHP 8.2+.

Why?

Spatie's laravel-data is excellent but tightly coupled to Laravel. datapack gives you the same power — hydration from arrays/JSON, attribute-based transformers, typed collections, validation adapters — without any framework dependency. It works with Laravel, Symfony, Slim, or vanilla PHP.

Installation

composer require kexxt/datapack

Requires PHP 8.2+.

Quick Start

use Kexxt\Datapack\Data;
use Kexxt\Datapack\Attributes\CastToDate;
use Kexxt\Datapack\Attributes\Trim;

class UserData extends Data
{
    public function __construct(
        #[Trim]
        public readonly string $name,
        #[CastToDate]
        public readonly DateTimeImmutable $birthDate,
        public readonly Role $role,        // backed enum — auto-cast
        public readonly AddressData $address, // nested DTO — auto-hydrated
    ) {}
}

// Hydrate from array
$user = UserData::from([
    'name' => '  Alice  ',
    'birthDate' => '1990-01-15',
    'role' => 'admin',
    'address' => ['street' => '123 Main', 'city' => 'Springfield', 'zip' => '62701'],
]);

$user->name;        // "Alice" (trimmed)
$user->role;        // Role::Admin (enum)
$user->toArray();   // serialized array
$user->toJson();    // JSON string

Hydration

Create DTOs from arrays, JSON strings, stdClass objects, or anything with a toArray() method:

$user = UserData::from(['name' => 'Alice', 'age' => 30]);
$user = UserData::from('{"name":"Alice","age":30}');
$user = UserData::from($stdClassObj);

Features:

  • Nested DTOs are hydrated automatically
  • Backed enums are cast automatically
  • Nullable properties and defaults are handled
  • Type coercion for scalars (string "30" → int 30)

Attributes

Attribute Target Description
#[CastToDate] Property Cast string to DateTimeImmutable
#[CastWith(CasterClass)] Property Use a custom caster
#[Trim] Property Trim whitespace
#[Uppercase] Property Convert to uppercase
#[Lowercase] Property Convert to lowercase
#[Hidden] Property Exclude from serialization
#[Computed] Property Exclude from hydration
#[MapFrom('key')] Property Map from different input key
#[MapTo('key')] Property Map to different output key

Custom Casters

Implement CastInterface:

use Kexxt\Datapack\Contracts\CastInterface;

class MoneyCast implements CastInterface
{
    public function cast(mixed $value, string $propertyName, array $context = []): Money
    {
        return new Money((int) ($value * 100));
    }
}

class OrderData extends Data
{
    public function __construct(
        #[CastWith(MoneyCast::class)]
        public readonly Money $total,
    ) {}
}

Serialization

$user->toArray();  // associative array
$user->toJson();   // JSON string
json_encode($user); // implements JsonSerializable
  • #[Hidden] fields are excluded
  • #[MapTo] renames keys in output
  • Nested DTOs, enums, and DateTimes are serialized recursively

Immutable Updates

$updated = $user->with('name', 'Bob');
// $user->name is still "Alice"
// $updated->name is "Bob"

Collections

$users = UserData::collection([
    ['name' => 'Alice', 'age' => 30],
    ['name' => 'Bob', 'age' => 25],
]);

$users->count();      // 2
$users->first();      // UserData instance
$users->toArray();    // list of arrays
$users->toJson();     // JSON array

// Filter and map
$seniors = $users->filter(fn(UserData $u) => $u->age >= 30);
$names = $users->map(fn(UserData $u) => $u->name); // ['Alice', 'Bob']

// Iterable
foreach ($users as $user) { ... }

Validation

Bring your own validator by implementing ValidatorInterface:

use Kexxt\Datapack\Contracts\ValidatorInterface;
use Kexxt\Datapack\Data;

class MyValidator implements ValidatorInterface
{
    public function validate(Data $data): array
    {
        $errors = [];
        // your validation logic
        return $errors; // ['field' => ['error message']]
    }
}

$errors = $user->validate(new MyValidator());
$user->validateOrFail(new MyValidator()); // throws ValidationException

Development

composer test      # Run tests
composer analyse   # Run PHPStan (level 8)
composer lint      # Check code style
composer fix       # Fix code style

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for your changes
  4. Ensure composer test && composer analyse && composer lint all pass
  5. Submit a pull request

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages