flyokai/data-mate

Data/Dto helpers and base definitions

Maintainers

Package info

github.com/flyokai/data-mate

pkg:composer/flyokai/data-mate

Statistics

Installs: 791

Dependents: 2

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.1 2026-04-25 18:01 UTC

This package is auto-updated.

Last update: 2026-04-29 03:08:42 UTC


README

User docs → README.md · Agent quick-ref → CLAUDE.md · Agent deep dive → AGENTS.md

The DTO foundation of the Flyokai framework — Solid/Draft/GreyData states, identity, enums, and collections.

data-mate defines the contract every Data Transfer Object in Flyokai obeys. It pairs the safety of constructor-validated immutables (Solid) with the flexibility of dynamic schemas (Draft, GreyData), and it standardises identity (HasId / HasAltId), enum helpers, and item collections so the rest of the framework can treat data uniformly.

It is built on top of cuyz/valinor for type-safe deserialization.

Features

  • Dto interfacetoArray(), toDbRow(), with(), cloneWith(), fromArgs()
  • Three data statesSolid (immutable, validated), Draft (flexible), GreyData (key/value bag)
  • Reflection-cached DtoTrait — automatic Valinor mapping with undefined-vs-null tracking
  • Identity systemHasId, HasAltId, HasIdDto with primary + alternative/composite keys
  • Enum helpersStringEnumTrait, IntEnumTrait, LCStringEnumTrait, UCStringEnumTrait
  • CollectionsItemJar interface and JarTrait for type-safe iterables
  • Built-in DTOsConfigJar, DdlTable, DdlTableColumn, DdlTableJar
  • ValidationAssertions, ValidationException, InvalidEntity factories

Installation

composer require flyokai/data-mate

Quick start

A Solid DTO (strict, immutable)

use Flyokai\DataMate\Dto;
use Flyokai\DataMate\DtoTrait;
use Flyokai\DataMate\Solid;

final class UserSolid implements Dto, Solid
{
    use DtoTrait;

    public function __construct(
        public readonly int $userId,
        public readonly string $username,
        public readonly string $email,
        public readonly ?string $firstName = null,
    ) {}
}

$user = UserSolid::fromArray([
    'userId' => 1,
    'username' => 'alice',
    'email' => 'alice@example.com',
]);

$row    = $user->toDbRow();             // ['user_id' => 1, 'username' => 'alice', ...]
$copy   = $user->cloneWith(email: 'a@b.com');
$loose  = $user->with(email: 'a@b.com'); // Valinor-mapped, accepts loose types

A Draft / Solid pairing

use Flyokai\DataMate\Draft;
use Flyokai\DataMate\DtoTrait;

final class User implements Dto, Draft
{
    use DtoTrait;
    protected static string $solidClassName = UserSolid::class;
    // …mutable, flexible properties for input/staging
}

Identity

use Flyokai\DataMate\HasId;
use Flyokai\DataMate\HasAltId;
use Flyokai\DataMate\HasIdDto;

final class UserSolid implements HasIdDto, Solid
{
    use DtoTrait;

    protected static string $type      = 'user';
    protected static string $idKey     = 'user_id';
    protected static array  $altIdKeys = ['username', 'email'];

    public function __construct(
        public readonly int $userId,
        public readonly string $username,
        public readonly string $email,
    ) {}
}

$user->id();                   // 1
$user->idKey();                // 'user_id'
$user->altId('username');      // 'alice'
$user->altId(['username', 'email']);
$user->extractIdentity();      // 1, with fallback to alt IDs

The data-state model

Marker Mutability Construction Use it for
Solid Immutable, public readonly props Constructor, validated Domain objects, repositories return these
Draft Mutable, flexible Same DTO class as Solid but relaxed Input staging, partial updates
GreyData Dynamic key/value Backed by $data array API payloads, third-party blobs

fromArray() uses Valinor for tolerant deserialisation; cloneWith() goes through the constructor for strict validation.

toArray() skips properties that were never assigned (undefined ≠ explicit null). toDbRow() builds on top of it and JSON-encodes any remaining array values — so ['data' => ['k' => 'v']] becomes ['data' => '{"k":"v"}'].

GreyData

GreyDataTrait implements a path-aware key/value bag for objects whose schema is dynamic:

$config->get('db/connection/default/host');     // path-based
$config->set('feature/flag/x', true);
$config->hasFeatureX();                         // magic getter (snake_case)
$config->withFeatureX(true);                    // immutable variant

Enums

use Flyokai\DataMate\StringEnumTrait;

enum Status: string
{
    use StringEnumTrait;

    case Active   = 'active';
    case Disabled = 'disabled';
}

Status::allowedValues();   // ['active', 'disabled']
Status::fromName('Active');
Status::tryFromName('xxx');
Status::validate('active');
Status::normalize('ACTIVE');

LCStringEnumTrait lower-cases input before lookup; UCStringEnumTrait upper-cases.

Collections (ItemJar)

use Flyokai\DataMate\ItemJar;
use Flyokai\DataMate\JarTrait;

final class UserJar implements ItemJar
{
    use JarTrait;
}

$jar = new UserJar();
$jar->add($user1);
$jar->get($user1->id());
foreach ($jar as $u) { /* … */ }

API surface

Class / Trait Purpose
Dto Core interface
DtoTrait Reflection-based Valinor implementation
Solid / Draft / GreyData State markers
HasId / HasAltId / HasIdDto Identity
EnumTrait, StringEnumTrait, IntEnumTrait, LCStringEnumTrait, UCStringEnumTrait Enum helpers
ItemJar, JarTrait Type-safe collections
ConfigJar, DdlTable, DdlTableJar Built-in DTOs
Assertions assertNotNull, assertPositiveInt, assertPositiveFloat
ValidationException, InvalidEntity Errors
dtoMapper(...) Cached configured Valinor TreeMapper

Gotchas

  • with() is Valinor-flexible (accepts loose types, slower). cloneWith() is constructor-strict (faster).
  • toDbRow() JSON-encodes arrays — be aware when reading rows back.
  • A constructor parameter that was never supplied is "undefined" and is skipped in toArray(). An explicit null is included.
  • GreyData does not track undefined — every key in $data is preserved.
  • __sleep() only serializes constructor params — implement __wakeup() to reinitialize derived state.
  • Reflection caches are global static and never cleared (intentional, for performance).
  • Alt-ID keys are strings; composite keys are arrays of strings, normalised via sorting.

See also

License

MIT