PHPackages                             romanzipp/laravel-dto - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. romanzipp/laravel-dto

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

romanzipp/laravel-dto
=====================

A strongly typed Data Transfer Object integration for Laravel

1.1.1(8mo ago)115.4k2[1 issues](https://github.com/romanzipp/Laravel-DTO/issues)[1 PRs](https://github.com/romanzipp/Laravel-DTO/pulls)MITPHPPHP ^8.1CI passing

Since Apr 6Pushed 8mo ago1 watchersCompare

[ Source](https://github.com/romanzipp/Laravel-DTO)[ Packagist](https://packagist.org/packages/romanzipp/laravel-dto)[ RSS](/packages/romanzipp-laravel-dto/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (7)Versions (23)Used By (0)

Laravel DTO
===========

[](#laravel-dto)

[![Latest Stable Version](https://camo.githubusercontent.com/4879a155e507a561dcbb4060b4f3c112f1678496a3a3df32a2b9e2435a941464/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f726f6d616e7a6970702f4c61726176656c2d44544f2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/romanzipp/laravel-dto)[![Total Downloads](https://camo.githubusercontent.com/93e9c5dfa92cc32af27309247b7e495bf493a0c6b180bfa74986add277cc1f39/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f726f6d616e7a6970702f4c61726176656c2d44544f2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/romanzipp/laravel-dto)[![License](https://camo.githubusercontent.com/06511c693f40a14da41c0957a0d07da53d027995a9d75abafe4b412216f5322a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f726f6d616e7a6970702f4c61726176656c2d44544f2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/romanzipp/laravel-dto)[![GitHub Build Status](https://camo.githubusercontent.com/ffd2b510f61471047609a876c5715ec4042f6d84424dd431ccb73da7d54beb02/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f726f6d616e7a6970702f4c61726176656c2d44544f2f74657374732e796d6c3f6c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/romanzipp/Laravel-DTO/actions)

A strongly typed Data Transfer Object **for Laravel** without magic for PHP 8.0+

This package extends the functionality of [**romanzipp/DTO**](https://github.com/romanzipp/DTO) to provide more narrow usecases for Laravel applications.

Laravel-DTO serves as an **intermediate and reusable layer** between request input &amp; validation and model attribute population.

Contents
--------

[](#contents)

- [Installation](#installation)
- [Usage](#usage)
    - [Validation](#validation)
    - [Hydrate models](#hydrate-models)
    - [**Combined usage**](#combined-usage)
    - [Validate arrays](#validate-arrays)
    - [Type casting: Arrays to DTOs](#cast-arrays-to-dtos-nested-data)
    - [Type casting](#type-casting)
    - [IDE Support](#ide-support)
- [Testing](#testing)

Installation
------------

[](#installation)

```
composer require romanzipp/laravel-dto

```

Usage
-----

[](#usage)

All data objects must extend the [`romanzipp\LaravelDTO\AbstractModelData`](src/AbstractModelData.php) class.

Validation
----------

[](#validation)

When attaching the [`#[ValidationRule]`](src/Attributes/ValidationRule.php) any given data will be passed to the Laravel Validator so you can make use of all [available validation rules](https://laravel.com/docs/9.x/validation#available-validation-rules) and even built-in rules instances.

```
use App\Models\Person;
use App\Models\Project;
use Illuminate\Validation\Rules\Exists;
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\ForModel;
use romanzipp\LaravelDTO\Attributes\ValidationRule;
use romanzipp\LaravelDTO\Attributes\ValidationChildrenRule;

class PersonData extends AbstractModelData
{
    #[ValidationRule(['required', 'string', 'min:1', 'max:255'])]
    public string $name;

    #[ModelAttribute(['sometimes', 'min:18'])]
    public int $currentAge;

    #[ValidationRule(['nullable', 'string', 'in:de,en'])]
    public ?string $language;

    #[ValidationRule(['required', 'numeric', new Exists(Project::class, 'id')])]
    public int $projectId;

    #[ValidationRule(['required', 'array', 'min:1']), ValidationChildrenRule(['string'], '*.device'), ValidationChildrenRule(['ipv4'], '*.ip')]
    public array $logins;
}
```

This will throw a `Illuminate\Validation\ValidationException` if any rule does not pass.

```
$data = new PersonData([
    'name' => 'John Doe',
    'currentAge' => 25,
    'language' => 'de',
    'projectId' => 2,
    'logins' => [
        ['device' => 'PC', 'ip' => '85.120.61.36'],
        ['device' => 'iOS', 'ip' => '85.120.61.36'],
    ]
]);
```

Hydrate Models
--------------

[](#hydrate-models)

You can attach a model to any DTO using the [`#[ForModel(Model::class)]`](src/Attributes/ForModel.php) attribute. To associate DTO properties with Model attributes, you need to attach the [`#[ModelAttribute()]`](src/Attributes/ModelAttribute.php) attribute to each property. If no parameter is passed to the [`#[ModelAttribute]`](src/Attributes/ModelAttribute.php) attribute, DTO uses the property name itself.

```
use App\Models\Person;
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\ForModel;
use romanzipp\LaravelDTO\Attributes\ModelAttribute;

#[ForModel(Person::class)]
class PersonData extends AbstractModelData
{
    #[ModelAttribute]                 // The `$name` DTO property will populate the `name` model attribute
    public string $name;

    #[ModelAttribute('current_age')]  // The `$currentAge` DTO property will populate the `current_age` model attribute
    public int $currentAge;

    public string $language;          // The `$language` DTO property will be ignored
}

$data = new PersonData([
    'name' => 'John Doe',
    'currentAge' => 25,
    'language' => 'de',
]);

$person = $data->toModel()->save();
```

**Attributes saved in `Person` model**

`name``current_age`John Doe25**Note**: You can also pass an existing model to the `toModel()` method.

```
use App\Models\Person;

$person = $data->toModel($person)->save();
```

**Note**: When passing **no** existing model to the `toModel()` method, default values declared in the DTO will be populated. If a model is passed as argument `toModel($model)` default values will not override existing model attributes.

### Populate DTO from request input data

[](#populate-dto-from-request-input-data)

When attaching the [`#[RequestAttribute]`](src/Attributes/RequestAttribute.php) and creating a DTO instance via the `fromRequest(Request $request)` method all matching attributes will be populated by the input data. If no parameter is passed to the [`#[RequestAttribute]`](src/Attributes/RequestAttribute.php) attribute, DTO uses the property name itself.

```
use App\Models\Person;
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\ForModel;
use romanzipp\LaravelDTO\Attributes\ModelAttribute;
use romanzipp\LaravelDTO\Attributes\RequestAttribute;

#[ForModel(Person::class)]
class PersonData extends AbstractModelData
{
    #[RequestAttribute]            // The `$name` DTO property will be populated by the `name` request attribute
    public string $name;

    #[RequestAttribute('my_age')]  // The `$currentAge` DTO property will be populated by `my_age` request attribute
    public int $currentAge;

    public string $language;       // The `$language` DTO property will not be populated
}
```

**The controller**

```
use App\Data\PersonData;
use Illuminate\Http\Request;

class TestController
{
    public function store(Request $request)
    {
        $data = PersonData::fromRequest($request);
    }
}
```

**Request input data**

```
{
  "name": "John Doe",
  "my_age": 25,
  "language": "de"
}
```

**The `PersonData` DTO instance**

```
App\Data\PersonData^ {
  +name: "John Doe"
  +currentAge: 25
}

```

Combined usage
--------------

[](#combined-usage)

Of course all those attributes start to make sense if used together. You can attach all attributes separately of make use of the [`#[ValidatedRequestModelAttribute]`](src/Attributes/ValidatedRequestModelAttribute.php) attribute which combines the functionality of all [`#[RequestAttribute]`](src/Attributes/RequestAttribute.php), [`#[ModelAttribute]`](src/Attributes/ModelAttribute.php) and [`#[ValidationRule]`](src/Attributes/ValidationRule.php) attributes.

Both properties in the following example behave exactly the same. Use as you prefer.

```
use App\Models\Person;
use Illuminate\Validation\Rules\Exists;
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\ForModel;
use romanzipp\LaravelDTO\Attributes\ModelAttribute;
use romanzipp\LaravelDTO\Attributes\RequestAttribute;
use romanzipp\LaravelDTO\Attributes\ValidatedRequestModelAttribute;
use romanzipp\LaravelDTO\Attributes\ValidationRule;

#[ForModel(Person::class)]
class PersonData extends AbstractModelData
{
    // All attributes attached separately (looks disgusting doesn't it?)
    #[
        ValidationRule(['required', 'numeric', 'min:18']),
        RequestAttribute('my_age'),
        ModelAttribute('current_age')
    ]
    public string $currentAge;

    // The `my_age` request attribute will be validated and set to the `current_age` model attribute.
    //
    //                                                             RequestAttribute
    //                                         ValidationRule             │          ModelAttribute
    //                              ┌────────────────┴──────────────┐  ┌──┴───┐  ┌─────┴─────┐
   #[ValidatedRequestModelAttribute(['required', 'numeric', 'min:18'], 'my_age', 'current_age')];
    public string $currentAge;
}
```

**Request input data**

```
{
  "my_age": 25
}
```

**The controller**

```
use App\Data\PersonData;
use Illuminate\Http\Request;

class TestController
{
    public function index(Request $request)
    {
        $person = PersonData::fromRequest($request)->toModel()->save();

        return $person->id;
    }
}
```

Validate arrays
---------------

[](#validate-arrays)

If you only want to validate an array without casting the children items to another DTO, you can make use of the `ValidationChildrenRule` attribute.

The first parameter to the `ValidationChildrenRule` attribute is the validation rule for the children items. The second parameter is the validator path to access the children key to validate.

#### Validate a simple array with numeric indexes

[](#validate-a-simple-array-with-numeric-indexes)

```
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\ValidationChildrenRule;

class PersonData extends AbstractModelData
{
    #[ValidationChildrenRule(['string', 'ipv4'], '*')];
    public array $logins;
}

$data = new PersonData([
    'logins' => [
        '127.0.0.1',
        '127.0.0.1'
    ]
]);
```

#### Validate associative arrays with named keys

[](#validate-associative-arrays-with-named-keys)

```
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\ValidationChildrenRule;

class PersonData extends AbstractModelData
{
    #[ValidationChildrenRule(['string', 'ipv4'], '*.ip')];
    public array $logins;
}

$data = new PersonData([
    'logins' => [
        ['ip' => '127.0.0.1'],
        ['ip' => '127.0.0.1']
    ]
]);
```

#### Multiple validation rules

[](#multiple-validation-rules)

```
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\ValidationChildrenRule;

class PersonData extends AbstractModelData
{
    #[
        ValidationChildrenRule(['string', 'ipv4'], '*.ip'),
        ValidationChildrenRule(['string'], '*.device')
    ];
    public array $logins;
}

$data = new PersonData([
    'logins' => [
        ['ip' => '127.0.0.1', 'device' => 'iOS'],
        ['ip' => '127.0.0.1', 'device' => 'macOS']
    ]
]);
```

Cast arrays to DTOs (Nested data)
---------------------------------

[](#cast-arrays-to-dtos-nested-data)

In some cases you also want to create realted models with a single HTTP call. In this case you can make use of the [`#[NestedModelData(NestedData::class)]`](src/Attributes/NestedModelData.php) which will populate the DTO property with n instances of the defined DTO.

Note that we will not attach an [`#[ModelAttribute]`](src/Attributes/ModelAttribute.php) attribute to the `$address` DTO property since it should not be set to a model attribute.

All attributes attached to the nested DTO will just work as expected.

```
use App\Models\Person;
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\ForModel;
use romanzipp\LaravelDTO\Attributes\NestedModelData;
use romanzipp\LaravelDTO\Attributes\RequestAttribute;
use romanzipp\LaravelDTO\Attributes\ValidatedRequestModelAttribute;
use romanzipp\LaravelDTO\Attributes\ValidationRule;

#[ForModel(Person::class)]
class PersonData extends AbstractModelData
{
    #[ValidatedRequestModelAttribute(['required', 'string'])]
    public string $name;

    /**
     * @var AddressData[]
     */
    #[NestedModelData(AddressData::class), ValidationRule(['required', 'array']), RequestAttribute]
    public array $adresses;
}
```

```
use App\Models\Address;
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\ValidatedRequestModelAttribute;

#[ForModel(Address::class)]
class AddressData extends AbstractModelData
{
    #[ValidatedRequestModelAttribute(['string'])]
    public string $street;

    #[ValidatedRequestModelAttribute(['nullable', 'int'])]
    public ?int $apartment = null;
}
```

**Request input data**

```
{
  "name": "John Doe",
  "addresses": [
    {
      "street": "Sample Street"
    },
    {
      "street": "Debugging Alley",
      "apartment": 43
    }
  ]
}
```

**The controller**

```
use App\Data\PersonData;
use Illuminate\Http\Request;

class TestController
{
    public function index(Request $request)
    {
        $personData = PersonData::fromRequest($request);
        $person = $personData->toModel()->save();

        foreach ($personData->addresses as $addressData) {
            // We assume the `Person` model has a has-many relation with the `Address` model
            $person->addresses()->save(
                $addressData->toModel()
            );
        }

        return $person->id;
    }
}
```

Type Casting
------------

[](#type-casting)

Type casts will convert any given value to a specified type.

### Built-in type casts

[](#built-in-type-casts)

#### [`CastToDate`](src/Attributes/Casts/CastToDate.php)

[](#casttodate)

The [`#[CastToDate]`](src/Attributes/Casts/CastToDate.php) attribute will respect your customly defined date class from `Date::use(...)`. You can also specify a custom date class to be used by passing the date class name as single argument [`#[CastToDate(MyDateClass::class)]`](src/Attributes/Casts/CastToDate.php).

```
use Carbon\Carbon;
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\Casts\CastToDate;

class PersonData extends AbstractModelData
{
    #[CastToDate]
    public Carbon $date;
}
```

### Custom type casts

[](#custom-type-casts)

You can declare custom type cast attributes by simply implementing the [`CastInterface`](src/Attributes/Casts/CastInterface.php) interface and attaching an attribute.

```
use Attribute;
use romanzipp\LaravelDTO\Attributes\Casts\CastInterface;

#[Attribute]
class MyCast implements CastInterface
{
    public function castToType(mixed $value): mixed
    {
        return (string) $value;
    }
}
```

IDE Support
-----------

[](#ide-support)

Make sure to add a `@method` PHPDoc comment like shown below to allow IDE and static analyzer support when calling the `toModel()` method.

```
use App\Models\Person;
use romanzipp\LaravelDTO\AbstractModelData;
use romanzipp\LaravelDTO\Attributes\ForModel;
use romanzipp\LaravelDTO\Attributes\ModelAttribute;

/**
 * @method Person toModel()
 */
#[ForModel(Person::class)]
class PersonData extends AbstractModelData
{
    #[ModelAttribute]
    public string $name;
}
```

Testing
-------

[](#testing)

### PHPUnit

[](#phpunit)

```
./vendor/bin/phpunit

```

### PHPStan

[](#phpstan)

```
./vendor/bin/phpstan

```

Authors
-------

[](#authors)

- [Roman Zipp](https://github.com/romanzipp)

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance59

Moderate activity, may be stable

Popularity29

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity67

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~65 days

Recently: every ~223 days

Total

21

Last Release

247d ago

Major Versions

0.0.18 → 1.0.02024-03-05

PHP version history (2 changes)0.0.1PHP ^8.0

0.0.15PHP ^8.1

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/11266773?v=4)[Roman Zipp](/maintainers/romanzipp)[@romanzipp](https://github.com/romanzipp)

---

Top Contributors

[![romanzipp](https://avatars.githubusercontent.com/u/11266773?v=4)](https://github.com/romanzipp "romanzipp (58 commits)")

---

Tags

laravelphpphp8showcase

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/romanzipp-laravel-dto/health.svg)

```
[![Health](https://phpackages.com/badges/romanzipp-laravel-dto/health.svg)](https://phpackages.com/packages/romanzipp-laravel-dto)
```

###  Alternatives

[brinley/jsignature

Signature For Javascript

70033.1k1](/packages/brinley-jsignature)[webimpress/composer-extra-dependency

Composer plugin to require extra dependencies

12705.8k1](/packages/webimpress-composer-extra-dependency)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
