Argonaut is a lightweight Data Transfer Object (DTO) package for Laravel that supports nested casting, recursive serialization, and validation out of the box. Ideal for service layers, APIs, and clean architecture workflows.
Laravel Argonaut DTO is a lightweight, highly composable package for transforming arrays, objects, or collections into
structured DTOs (Data Transfer Objects), with built-in support for:
toArray
, toJson
)Install via Composer:
composer require yorcreative/laravel-argonaut-dto
DTOs extend ArgonautDTO
, and define your expected structure via public properties, casting rules, and validation.
class UserDTO extends ArgonautDTO
{
public string $username;
public string $email;
protected array $casts = [
'username' => 'string',
'email' => 'string',
];
public function rules(): array
{
return [
'username' => ['required', 'string'],
'email' => ['required', 'email'],
];
}
}
This defines a strongly typed DTO with both validation rules and simple type casting.
Assemblers are responsible for mapping raw inputs (arrays or objects) into your DTOs.
// static usage example
class UserDTOAssembler extends ArgonautAssembler
{
public static function toUserDTO(object $input): UserDTO
{
return new UserDTO([
'username' => $input->display_name,
'email' => $input->email,
]);
}
}
// instance usage example
class UserDTOAssembler extends ArgonautAssembler
{
public function __construct(protected UserFormattingService $formattingService)
{
//
}
public static function toUserDTO(object $input): UserDTO
{
return new UserDTO([
'username' => $formatingService->userName($input->display_name),
'email' => $formatingService->email($input->email),
]);
}
}
Assembler method names must follow the format
to<ClassName>
orfrom<ClassName>
, and are resolved automatically usingclass_basename
.
Use the assembler to transform raw data into structured, casted DTO instances.
// static usage example
$dto = UserDTOAssembler::assemble([
'display_name' => 'jdoe',
'email' => '[email protected]',
], UserDTO::class);
// instance usage example
$dto = $userDTOAssemblerInstance->assembleInstance([
'display_name' => 'jdoe',
'email' => '[email protected]',
], UserDTO::class);
You can also batch transform arrays or collections:
// static usage
UserDTOAssembler::fromArray($userArray, UserDTO::class);
UserDTOAssembler::fromCollection($userCollection, UserDTO::class);
// instance usage
UserDTOAssembler::fromArray($userArray, UserDTO::class, $userDTOAssemblerInstance);
UserDTOAssembler::fromCollection($userCollection, UserDTO::class, $userDTOAssemblerInstance);
// or using the assembler instance's static methods
$userDTOAssemblerInstance::fromArray($userArray, UserDTO::class, $userDTOAssemblerInstance);
$userDTOAssemblerInstance::fromCollection($userCollection, UserDTO::class, $userDTOAssemblerInstance);
This example demonstrates nested relationships and complex type casting in action.
class ProductDTO extends ArgonautDTO
{
public string $title;
public array $features;
public Collection $reviews;
public ?UserDTO $user = null;
protected array $casts = [
'features' => [ProductFeatureDTO::class],
'reviews' => Collection::class . ':' . ProductReviewDTO::class,
'user' => UserDTO::class,
];
public function rules(): array
{
return [
'title' => ['required', 'string'],
'reviews' => ['sometimes', 'required', 'collection', 'min:1'],
];
}
}
class ProductDTOAssembler extends ArgonautAssembler
{
public static function toProductDTO(object $input): ProductDTO
{
return new ProductDTO([
'title' => $input->product_name,
'user' => $input->user,
'features' => $input->features ?? [],
'reviews' => $input->reviews ?? [],
]);
}
public static function toProductFeatureDTO(object $input): ProductFeatureDTO
{
return new ProductFeatureDTO([
'name' => $input->name ?? 'Unnamed Feature',
'description' => $input->description ?? null,
]);
}
public static function toProductReviewDTO(object $input): ProductReviewDTO
{
return new ProductReviewDTO([
'rating' => (int) ($input->rating ?? 0),
'comment' => $input->comment ?? '',
]);
}
}
ArgonautAssembler offers enhanced flexibility for your Assembler logic by supporting dependency injection. This allows
you to leverage services or custom logic, whether defined in static or non-static methods, during the DTO assembly
process. This is particularly powerful when integrating with Laravel’s service container.
This feature enables you to:
ArgonautAssembler
supports dependency injection in non-static transformation methods (e.g., toUserDTO
or
fromUserDTO
) by leveraging Laravel’s service container. When you call ArgonautAssembler::assemble()
,
fromCollection()
, fromArray()
, or assembleInstance()
with an instance of the assembler, the transformation method
is invoked on that instance. Laravel’s container automatically resolves and injects any dependencies declared in the
method’s signature.
public static function toUserDTO($input)
) do not supportpublic function toUserDTO($input)
) are called on anBelow is an example of an assembler with a non-static transformation method that uses dependency injection to format a
user’s name via an injected service.
<?php
namespace App\Assemblers;
use App\DTOs\UserDTO;
use App\Services\UserFormattingService;
use YorCreative\LaravelArgonautDTO\ArgonautAssembler;
class UserAssembler extends ArgonautAssembler
{
public function __construct(protected UserFormattingService $formattingService)
{
//
}
/**
* Transform input data into a UserDTO with dependency injection.
*
* @param object $input Input data (e.g., from a model or array cast to object).
* @param UserFormattingService $formatter Injected service for formatting user data.
* @return UserDTO
*/
public function toUserDTO(object $input): UserDTO
{
return new UserDTO([
'full_name' => $formattingService->formatName($input->first_name, $input->last_name),
'email' => $input->email,
'created_at' => $input->created_at,
]);
}
}
// ServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class YourServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(FormattingServiceInterface::class, function($app) {
return new FormattingService();
})
$this->app->bind(YourArgonautAssembler::clas, function ($app) {
return new YourArgonautAssembler($app->get(FormattingServiceInterface::class));
});
}
public function provides()
{
return [
YourArgonautAssembler::class,
FormattingServiceInterface::class
]
}
}
To use the assembler with dependency injection, you need to provide an instance of the assembler to the assemble
method or related methods (fromCollection
, fromArray
, or assembleInstance
). Laravel’s container will resolve the
dependencies when the method is invoked.
<?php
use App\Assemblers\UserAssembler;
use App\DTOs\UserDTO;
// Example input (e.g., a model or object)
$input = (object) [
'first_name' => 'John',
'last_name' => 'Doe',
'email' => '[email protected]',
'created_at' => now(),
];
// Creating an assembler instance
$formattingService = new UserFormattingService();
$assembler = new UserAssembler($formattingService);
// or using the container instance
$assembler = resolve(YourArgonautAssembler::class);
// Pass the $assembler instance
$userDTO = UserAssembler::assemble($input, UserDTO::class, $assembler);
// Or use the instance method
$userDTO = $assembler->assembleInstance($input, UserDTO::class);
// Transform a collection passing the $assembler instance
$array = [$input, $input];
$collection = collect($array);
$userDTOs = UserAssembler::fromCollection($collection, UserDTO::class, $assembler);
$userDTOs = $assembler::fromArray($array, UserDTO::class, $assembler)
In this example:
toUserDTO
method requires a UserFormattingService
dependency.$assembler
) is passed to assemble
, fromArray
or fromCollection
, ensuring thetoUserDTO
method is invoked on the instance.ArgonautDTO allows you to prioritize the assignment of specific fields using $prioritizedAttributes
, which is critical
for cases where one field influences others.
class UserDTO extends ArgonautDTO
{
public ?string $firstName = null;
public ?string $lastName = null;
public string $username;
public string $email;
public ?string $fullName = null;
protected array $prioritizedAttributes = ['firstName', 'lastName'];
protected array $casts = [
'firstName' => 'string',
'lastName' => 'string',
'username' => 'string',
'email' => 'string',
'fullName' => 'string',
];
public function setFirstName($value)
{
$this->firstName = $value;
$this->fullName = $this->firstName . ' ' . $this->lastName;
}
public function setLastName($value)
{
$this->lastName = $value;
$this->fullName = $this->firstName . ' ' . $this->lastName;
}
public function rules(): array
{
return [
'firstName' => ['nullable', 'string', 'max:32'],
'lastName' => ['nullable', 'string', 'max:32'],
'username' => ['required', 'string', 'max:64'],
'email' => ['required', 'email', 'max:255'],
];
}
}
Casting allows you to automatically transform values into other DTOs, Laravel Collections, arrays, dates, and more.
protected array $casts = [
'registeredAt' => \Illuminate\Support\Carbon::class,
'profile' => ProfileDTO::class,
'roles' => [RoleDTO::class],
'permissions' => Collection::class . ':' . PermissionDTO::class,
];
Cast Type | Example | Description |
---|---|---|
Scalar | 'string' , 'int' , etc. |
Native PHP type cast |
Single DTO | ProfileDTO::class |
Cast an array to a DTO instance |
Array of DTOs | [RoleDTO::class] |
Cast to array of DTOs |
Collection of DTOs | Collection::class . ':' . CommentDTO::class |
Cast to a Laravel Collection |
Date casting | Carbon::class |
Cast to Carbon/DateTime instance |
Validate DTOs with Laravel’s validator:
$userDTO->validate(); // Throws ValidationException
$userDTO->validate(false); // Returns array of errors (non-throwing)
$userDTO->isValid(); // Returns true/false
Serialize DTOs for output, API responses, etc.
$userDTO->toArray(); // Recursively converts nested DTOs
$userDTO->toJson(); // JSON output (throws on encoding errors)
Create DTO collections directly:
UserDTO::collection([
['username' => 'john', 'email' => '[email protected]'],
]);
Run the test suite using:
composer test
This package is open-sourced software licensed under the MIT license.