Download the PHP package yorcreative/argonaut-dto without Composer

On this page you can find all versions of the php package yorcreative/argonaut-dto. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package argonaut-dto



Argonaut DTO

GitHub license GitHub stars GitHub Org's stars GitHub issues GitHub forks Packagist Downloads Tests Security

Framework-agnostic Data Transfer Objects for PHP 8.3+. Argonaut DTO provides the useful parts of Laravel Argonaut DTO without requiring Laravel: nested DTO and enum casting, recursive serialization, mutable and immutable DTOs, convention-based assemblers, and lightweight validation.

Requirements

Installation

Install via Composer:

Basic DTO

Unknown input keys are ignored. A setter named set<FieldName> takes precedence over direct property assignment. Declare $prioritizedAttributes when setters must run before the remaining input attributes.

Named constructors

fromArray() and fromJson() are new in 1.1.0. collection() is not new — it has existed since 1.0.0 and is listed here because 1.1.0 makes its return type generic, so static analysis now narrows the elements.

fromJson() throws JsonException on malformed JSON. It also throws JsonException when the JSON is valid but does not decode to an object — for example 'null', '"a string"', '123', or a JSON array such as '[{"fullName":"Ada"}]', '[1,2]' or '[]' — with a message naming the type it found instead, such as ProfileDTO::fromJson() expects a JSON object, null given. The root type is read from the document itself rather than inferred from the decoded value, because associative decoding erases the difference: an empty array and an empty object both decode to [], and a JSON object with numeric keys ('{"0":"a"}') decodes to a PHP list. So '[]' is rejected and '{"0":"a"}' is accepted. All three named constructors are available on both ArgonautDTO and ArgonautImmutableDTO.

Nested casts

Array casts use [SomeDTO::class]. Collection casts use Collection::class . ':' . SomeDTO::class (the shorthand collection:SomeDTO is also supported). Arrays, traversables, and the package Collection can be used as input.

Backed enums and DateTimeInterface implementations can be cast directly:

Nested DTOs and backed enums serialize recursively; enums serialize to their backing values.

Attribute casting

Casts can be declared with attributes instead of the $casts array:

Each attribute is equivalent to the $casts entry it replaces:

Attribute Equivalent $casts value
#[CastTo(LineDTO::class)] LineDTO::class
#[CastTo(LineDTO::class, many: true)] [LineDTO::class]
#[CastCollection(LineDTO::class)] 'collection:'.LineDTO::class
#[CastEnum(Status::class)] Status::class

Attributes and the $casts array can coexist. When both describe the same property, the $casts array wins. Attributes are fixed at the point of property declaration, so a subclass cannot change the attribute on a property it inherits without redeclaring that property — $casts is its lever short of redeclaration, and this rule keeps that override working:

Like $casts, cast attributes only apply on the direct property-assignment path. If the DTO declares a set<Property>() setter for that property, the setter runs instead and is responsible for its own conversion — the attribute is silently never consulted.

Custom cast classes

For a conversion the library doesn't know how to do — a value object, a domain-specific formatter — implement CastsArgonautAttribute:

Declare it the same way as any other cast — as a $casts entry or as a #[CastWith] attribute:

A custom cast works with all three cast container forms used elsewhere in this library, with either declaration:

$casts value #[CastWith] equivalent Behavior
MoneyCast::class #[CastWith(MoneyCast::class)] Applied to the value once
[MoneyCast::class] #[CastWith(MoneyCast::class, many: true)] Value is iterated as an array; applied to each item
'collection:'.MoneyCast::class #[CastWith('collection:'.MoneyCast::class)] Value is iterated as a Collection; applied to each item, result is a Collection

The same three forms via #[CastWith]:

Where a property has both a $casts entry and a #[CastWith] attribute, $casts wins — the same precedence rule as the other cast attributes:

A custom cast never receives null — setAttribute() short-circuits null before casting reaches it, so implementations don't need a null check.

Implementations must be stateless and constructible with no arguments. One instance is created per cast class and reused across every DTO that uses it — a cast that keeps state between calls will leak it, and a cast with a required constructor parameter raises ArgumentCountError.

Custom casts work identically on ArgonautDTO and ArgonautImmutableDTO:

Key mapping

Incoming keys can be renamed to property names before anything else runs, either with a $maps array or with a #[MapFrom] attribute on the property:

Both forms can be used on the same class. Where both describe the same target property, $maps wins and the #[MapFrom] attribute for that property is dropped entirely — even when the two forms name different incoming keys:

Key mapping runs before anything else that processes input — before the $prioritizedAttributes pass and before casting. That applies to every input path that takes incoming keys: the constructor, setAttributes(), merge(), with(), and the single-key setMappedAttribute(). setAttribute() is the assignment seam underneath them and takes property names only — it does not map, so an override sees each assignment once, under the name it declared, and may safely reach for other attributes from inside it (use setMappedAttribute() there if you want to name one by its alias). A $casts entry (or cast attribute) for a mapped property is therefore keyed by the property name, not the incoming key:

If both a mapped key and its target property name appear in the same input array, whichever occurs later in the array wins.

with() on ArgonautImmutableDTO does not rely on that rule. It maps the incoming keys to work out which properties are changing, copies every unchanged property to the new instance verbatim, and routes only the given attributes through the normal input path. Unchanged values are never re-cast: a full-state rebuild would run the casting engine over already-cast values, and while a built-in cast survives that on its identity guard, a custom cast is a transformation and would apply twice. Mapped keys work in with() for the same reason they work anywhere else — they are mapped on the way in:

Two output methods reverse the mapping: toMappedArray(?int $depth = null) and toMappedJson(int $options = 0, ?int $depth = null) rename each top-level key back to its incoming key. toArray() and toJson() are unchanged — they still emit property names:

toMappedJson() reports encoding failures exactly as toJson() does — both delegate to the same internal encoder.

Renaming onto an existing property throws. If a mapping renames one property onto the name of another property that also serializes, both land on the same output key and one value would be lost. toMappedArray() and toMappedJson() raise LogicException instead of dropping it:

toArray() is unaffected — it emits property names, which are unique by construction.

Two properties cannot claim the same incoming key. Declaring #[MapFrom('key')] on more than one property is ambiguous — only one could receive the value, and which one would depend on reflection order — so it throws LogicException when the map is first built.

Key mapping is top-level only. toMappedArray() renames this DTO's own keys; it does not reach into nested DTOs and rename their keys too, even if the nested class declares its own $maps or #[MapFrom]:

This is inherent to how toArray() is used: it flattens the whole DTO graph into plain arrays before toMappedArray() ever sees it, and toArray() itself cannot be changed to preserve nested objects instead, because it is declared on ArgonautDTOContract, which consumers implement directly. A consumer with nested DTOs should expect only the outermost keys to be renamed.

Serialization depth

toArray(), toJson(), and jsonSerialize() walk the whole DTO graph:

$depth counts DTO nesting levels and defaults to ArgonautDTO::DEFAULT_MAX_DEPTH (512). Exceeding it throws a RuntimeException rather than silently emitting an empty array, so truncation can never be mistaken for missing data.

Circular references are detected directly, not inferred from the depth limit, and raise YorCreative\ArgonautDTO\CircularReferenceException (a RuntimeException) naming the instance involved. The same DTO appearing in two sibling branches is a shared reference rather than a cycle and serializes normally.

toJson() also accounts for PHP's own json_encode() depth limit. Because each array or collection of DTOs adds a level of its own, a graph well inside $depth can still exceed the encoder's 512 levels; toJson() raises the encoder limit to fit whatever the walk produced. Note that json_decode() has the same 512 default, so decoding very deep payloads needs an explicit depth.

Collection

Collection is a small, dependency-free collection returned by collection: casts and by collection(). It implements ArrayAccess, Countable, IteratorAggregate and JsonSerializable.

Using a different collection

collection: casts produce whatever class collectionClass() returns, so a DTO can use a framework's collection, or your own, instead:

The cast directive may name that collection too — all three of these are recognised:

The package gains no dependency from this — it instantiates the class string you return and nothing more. The contract is:

Requirement Why
__construct(array $items) how the cast builds it
map(callable): self applied per item by collection:<DTO> casts
Traversable so toArray() can walk it back into a nested array

Both are validated when the collection is built, so a class missing either is reported by name rather than failing later inside the casting engine. The checks run outside newCollection(), so overriding that factory does not skip them.

A recognised collection is iterated on output rather than read through all(), so a subclass overriding getIterator() decides what is published.

Only a collection the DTO recognises is walked on output — this package's own, or the one collectionClass() returns. An object that merely happens to be iterable is returned as it is, so json_encode() still calls its jsonSerialize(), a generator is not consumed by being serialized, and an object with an unrelated iterator is still read as a property bag.

Illuminate\Support\Collection satisfies all three. Override newCollection(array $items) instead if construction needs more than new $class($items).

collection() is unaffected and always returns this package's Collection. It is a static factory, so it has no instance to ask, and collectionClass() is deliberately an instance method so two DTOs of different classes can differ. Build the collection yourself if you need another type there.

It is generic over its value type, so static analysis narrows elements:

Available methods:

Method Returns Notes
all() array Underlying items, keys preserved
map(callable) Collection Preserves keys
filter(?callable) Collection Callback receives value and key
first(mixed $default = null) TValue\|null
last(mixed $default = null) TValue\|null
reduce(callable, mixed $initial = null) mixed Callback receives carry, value, key
contains(mixed) bool A value compared strictly, or a predicate
pluck(string $value, ?string $key = null) Collection Reads array keys, ArrayAccess offsets, or object properties
groupBy(callable) Collection A Collection of Collections
keyBy(callable) Collection Later duplicates win
values() Collection Reindexes
isEmpty() / isNotEmpty() bool
count() int
validateAll(bool $throw = true) true\|array Validates every item; errors keyed by collection key
isValidAll() bool True when every item validates

Validating a collection of DTOs

Errors are keyed by the item's collection key, so string-keyed collections report which item failed. With $throw = true the first failing item's own ValidationException is raised — use validateAll(false) when you need to know which index failed.

An item that is not an Argonaut DTO, or a DTO whose class declares no rules(), is a programming error and raises LogicException rather than being reported as invalid.

These mirror the names and common calling conventions of Illuminate\Support\Collection so the API is familiar, but deliberately omit Laravel's operator overloads — contains() takes a value or a predicate, not ($key, $operator, $value).

One caveat worth knowing: contains() decides between "value" and "predicate" by testing is_callable(), with one deliberate exception — a string is always a value, never a predicate, so a collection of strings stays searchable for one that happens to name a function (contains('is_int') looks for the string). Every other callable form is invoked as a predicate: a closure, [$object, 'method'], or an object with __invoke(). Predicates receive ($item, $key), so a one-argument function such as is_int(...) is not a valid predicate. A collection whose items are themselves closures cannot be searched by value; Illuminate\Support\Collection has the same limitation. Use in_array($needle, $collection->all(), true) if you need that.

pluck() reads array keys, ArrayAccess offsets, and public object properties. A key that is simply absent yields null, as it does for arrays. A property that exists but is not public throws LogicException rather than yielding null, so a private field is never silently reported as empty and a typo is never mistaken for one.

Immutable DTOs

Declare DTO properties as readonly and extend ArgonautImmutableDTO:

Readonly properties are initialized once during construction. Missing required properties remain uninitialized, allowing PHP's normal typed-property error to identify an incomplete snapshot.

Copying with changes

with() returns a copy with the given attributes applied, leaving the original untouched. It is available on both base classes.

Only the attributes you pass are re-applied, so setter-derived properties recompute from the new values rather than being overwritten with stale ones:

with() is a shallow copy — nested DTOs and other objects are shared with the original, not duplicated. Because ArgonautDTO is mutable, that means mutating a nested DTO reached through the copy also mutates the original:

Rebuild nested values explicitly if you need them independent.

Assemblers

Assemblers resolve to<ClassName> first, then from<ClassName>:

fromArray() and fromCollection() return the package's dependency-free Collection, which supports iteration, array access, count(), first(), all(), map(), and filter().

Instance assembler methods are supported through assembleInstance() or by passing an assembler instance to assemble().

Validation

rules() uses YorCreative DataValidation, including its complete rule set, nested paths, wildcards, and custom closures. Argonaut preserves the convenient int, bool, collection, and sometimes aliases: collections are arrays after serialization, and sometimes omits the rule set when the field is absent.

Custom closures use DataValidation's signature: function (string $field, mixed $value, callable $fail, array $data): bool.

Calling validate() without throw: false raises YorCreative\ArgonautDTO\ValidationException.

isValid() returns false only for validation failure. A missing or broken rules() method is a programming error and surfaces as an exception rather than being reported as invalid data. Pass isValid(throw: true) to raise ValidationException instead of returning false.

Relationship to the Laravel package

yorcreative/argonaut-dto contains no Illuminate dependency. The Laravel package can provide Laravel-specific adapters and continue to expose its existing API while sharing this framework-neutral behavior. Laravel applications that need Laravel's validator or collection implementation can remain on yorcreative/laravel-argonaut-dto.

Testing

Run the test suite:

Run tests with coverage report:

Run static analysis (PHPStan):

Run code style fixer (Pint):

Continuous integration runs the suite on PHP 8.3, 8.4, and 8.5 against locked, lowest, and highest dependency sets, alongside PHPStan, Pint, Composer validation, GitHub dependency review, and scheduled Composer security audits.

Credits

License

This package is open-sourced software licensed under the MIT license.


All versions of argonaut-dto with dependencies

PHP Build Version
Package Version
Requires php Version ^8.3
yorcreative/data-validation Version ^1.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package yorcreative/argonaut-dto contains the following files

Loading the files please wait ...