cline / struct
Lean, attribute-driven Laravel data objects with explicit validation and Eloquent casting support
Requires
- php: ^8.5.0
- cline/attribute-reader: ^1.0
- facade/ignition-contracts: ^1.0.2
- laravel/framework: ^10.0 || ^11.0 || ^12.0 || ^13.0
- spatie/laravel-package-tools: ^1.93.0
- spatie/php-structure-discoverer: ^2.4
- symfony/yaml: ^7.4.6 || ^8.0
Requires (Dev)
- beacon-hq/bag: ^2.6.2
- cline/bench: ^1.0.1
- cline/coding-standard: ^2.0.1
- cline/math: ^2.0
- cline/money: ^2.0
- cline/numerus: ^5.0
- cline/phone-number: ^1.0
- cline/postal-code: ^1.0
- cline/semver: ^1.0
- livewire/livewire: ^4.2.1
- orchestra/testbench: ^8.0 || ^9.0 || ^10.9
- ramsey/uuid: ^4.7
- spatie/laravel-data: ^4.20
- symfony/var-dumper: ^6.0 || ^7.4.6 || ^8.0
Suggests
- cline/math: Enable built-in BigNumber, BigInteger, BigDecimal, and BigRational casting for arbitrary-precision DTO fields.
- cline/money: Enable built-in Money, RationalMoney, and MoneyBag casting plus the #[AsMoney(...)] attribute for currency-aware DTO fields.
- cline/numerus: Enable built-in Numerus casting and numeric normalization attributes such as #[Round], #[Clamp], and #[Abs].
- cline/phone-number: Enable built-in PhoneNumber casting plus the #[AsPhoneNumber(...)] attribute for region-aware scalar phone inputs.
- cline/postal-code: Enable built-in PostalCode casting plus the #[AsPostalCode(...)] attribute for country-aware scalar postal inputs.
- cline/semver: Enable built-in Version and Constraint casting for semantic version DTO fields.
- livewire/livewire: Add first-class Livewire Wireable and synthesizer support for data objects.
- ramsey/uuid: Enable the #[Uuid(...)] generated-value attribute for missing UUID identifiers.
README
struct
Lean, attribute-driven Laravel data objects for applications that want the useful parts
of spatie/laravel-data without the magic-heavy surface area.
Requirements
Requires PHP 8.4+
Installation
composer require cline/struct
Optional features:
composer require cline/math composer require cline/money composer require cline/numerus composer require cline/phone-number composer require cline/postal-code composer require ramsey/uuid composer require cline/semver
Install cline/math when you want first-class arbitrary-precision
BigNumber, BigInteger, BigDecimal, or BigRational DTO properties.
composer require cline/money
Install cline/money when you want first-class Money, RationalMoney,
or MoneyBag DTO properties, or the #[AsMoney(...)] attribute for
scalar currency amounts.
composer require cline/numerus
Install cline/numerus when you want first-class Numerus DTO properties or
numeric normalization attributes such as #[Round], #[Clamp], and #[Abs].
composer require cline/phone-number
Install cline/phone-number when you want first-class PhoneNumber DTO
properties, including scalar local-number hydration with
#[AsPhoneNumber(regionCode: ...)].
composer require cline/postal-code
Install cline/postal-code when you want first-class PostalCode DTO
properties, including scalar postal-code hydration with
#[AsPostalCode(country: ...)].
composer require cline/semver
Install cline/semver when you want first-class Version and Constraint
DTO properties for semantic version payloads.
Struct also ships built-in deterministic string transformation attributes such
as #[Trim], #[SnakeCase], #[Slug], #[Limit], #[Replace], and
extraction attributes like #[After], #[Before], and #[Between].
It also ships missing-value generators such as #[Uuid], #[Ulid],
#[Random], and #[Password] for DTO fields that should be created only
when the input key is absent. Install ramsey/uuid if you want to use
#[Uuid(...)]. String transforms and generators now live under
Cline\Struct\Attributes\Strings, while compatibility aliases remain
available at Cline\Struct\Attributes. Collection transforms now live under
Cline\Struct\Attributes\Collections, including helpers such as
#[Collections\\Reverse], #[Collections\\Unique], #[Collections\\Slice],
and #[Collections\\OnlyKeys]. Struct also ships first-class detached
Illuminate\\Support\\Collection property support through
#[AsCollection(...)], separate from DataList and DataCollection,
including callback-based collection attributes such as
#[Collections\\Filter(...)], #[Collections\\Map(...)],
#[Collections\\GroupBy(...)], #[Collections\\UniqueBy(...)], and
#[Collections\\MapInto(...)]. Collection-returning transforms also
include attributes such as #[Collections\\Where(...)],
#[Collections\\Pluck(...)], #[Collections\\Flatten(...)],
#[Collections\\ChunkWhile(...)], #[Collections\\MapToGroups(...)],
#[Collections\\CountBy(...)], #[Collections\\Diff(...)],
#[Collections\\Merge(...)], #[Collections\\Pipe(...)],
#[Collections\\Tap(...)], and conditional wrappers like
#[Collections\\When(...)]. Derived collection results and generated
collection sources live under
Cline\\Struct\\Attributes\\CollectionResults and
Cline\\Struct\\Attributes\\CollectionSources, for methods such as
#[Contains('posts', ...)], #[After('posts', ...)],
#[ToJson('posts')], #[Reduce('totals', ...)],
#[Wrap(source: 'name')], and #[FromJson(source: 'payload')].
For deferred traversal, Struct also ships LazyDataList and
LazyDataCollection with explicit #[AsLazyDataList(...)] and
#[AsLazyDataCollection(...)] attributes. These lazy wrappers keep
Struct-owned collection semantics and typed item hydration, but they are
transport-focused and intentionally reject Attributes\\Collections\\*
transforms in v1. Struct also now supports detached
Illuminate\\Support\\LazyCollection DTO properties through
#[AsLazyCollection(...)], with a strict-lazy transform subset such as
#[Collections\\Map(...)], #[Collections\\Filter(...)],
#[Collections\\Skip(...)], and #[Collections\\Take(...)]. Derived
CollectionResults may read from lazy sources, but eager-only
collection transforms are rejected on LazyCollection properties.
Context-Aware Hydration
Hydration hooks can opt into whole-DTO context when a cast or attribute needs to inspect the raw payload or sibling values that have already been resolved.
use Cline\Struct\Contracts\ContextualCastInterface; use Cline\Struct\Metadata\PropertyMetadata; use Cline\Struct\Support\PropertyHydrationContext; final class SlugCast implements ContextualCastInterface { public function get(PropertyMetadata $property, mixed $value): mixed { return $value; } public function getWithContext( PropertyMetadata $property, mixed $value, PropertyHydrationContext $context, ): mixed { return ($context->resolvedProperties['mode'] ?? null) === 'strict' ? mb_strtolower((string) $value) : (string) $value; } public function set(PropertyMetadata $property, mixed $value): mixed { return $value; } }
PropertyHydrationContext exposes the DTO class, current property, raw
input, and the sibling properties resolved so far. The context is passed
as an immutable argument during hydration; Struct does not mutate shared
cast instances with request-scoped data.
Documentation
- Consumer guide: USAGE.md
- Benchmarking workflows: BENCHMARK.md
Change log
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING and CODE_OF_CONDUCT for details.
Security
If you discover any security related issues, please use the GitHub security reporting form rather than the issue queue.
Credits
License
The MIT License. Please see License File for more information.