{"id":16078426,"url":"https://github.com/romanzipp/laravel-dto","last_synced_at":"2025-03-17T17:30:31.227Z","repository":{"id":40448028,"uuid":"478586586","full_name":"romanzipp/Laravel-DTO","owner":"romanzipp","description":"Laravel Data-Transfer-Object","archived":false,"fork":false,"pushed_at":"2024-03-05T08:36:47.000Z","size":65,"stargazers_count":10,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-16T13:05:19.426Z","etag":null,"topics":["laravel","php","php8","showcase"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/romanzipp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-06T14:05:15.000Z","updated_at":"2025-02-18T22:00:14.000Z","dependencies_parsed_at":"2024-03-05T08:51:00.711Z","dependency_job_id":null,"html_url":"https://github.com/romanzipp/Laravel-DTO","commit_stats":{"total_commits":42,"total_committers":2,"mean_commits":21.0,"dds":0.04761904761904767,"last_synced_commit":"573a7dbbf474ff0c9a7abac50fcaf36cca4910e0"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romanzipp%2FLaravel-DTO","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romanzipp%2FLaravel-DTO/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romanzipp%2FLaravel-DTO/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romanzipp%2FLaravel-DTO/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/romanzipp","download_url":"https://codeload.github.com/romanzipp/Laravel-DTO/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244077869,"owners_count":20394356,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["laravel","php","php8","showcase"],"created_at":"2024-10-09T10:12:30.794Z","updated_at":"2025-03-17T17:30:30.922Z","avatar_url":"https://github.com/romanzipp.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Laravel DTO\n\n[![Latest Stable Version](https://img.shields.io/packagist/v/romanzipp/Laravel-DTO.svg?style=flat-square)](https://packagist.org/packages/romanzipp/laravel-dto)\n[![Total Downloads](https://img.shields.io/packagist/dt/romanzipp/Laravel-DTO.svg?style=flat-square)](https://packagist.org/packages/romanzipp/laravel-dto)\n[![License](https://img.shields.io/packagist/l/romanzipp/Laravel-DTO.svg?style=flat-square)](https://packagist.org/packages/romanzipp/laravel-dto)\n[![GitHub Build Status](https://img.shields.io/github/actions/workflow/status/romanzipp/Laravel-DTO/tests.yml?label=tests\u0026style=flat-square)](https://github.com/romanzipp/Laravel-DTO/actions)\n\nA strongly typed Data Transfer Object **for Laravel** without magic for PHP 8.0+\n\nThis package extends the functionality of [**romanzipp/DTO**](https://github.com/romanzipp/DTO) to provide more narrow usecases for Laravel applications.\n\nLaravel-DTO serves as an **intermediate and reusable layer** between request input \u0026 validation and model attribute population.\n\n## Contents\n\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Validation](#validation)\n  - [Hydrate models](#hydrate-models)\n  - [**Combined usage**](#combined-usage)\n  - [Validate arrays](#validate-arrays)\n  - [Type casting: Arrays to DTOs](#cast-arrays-to-dtos-nested-data)\n  - [Type casting](#type-casting)\n  - [IDE Support](#ide-support)\n- [Testing](#testing)\n\n## Installation\n\n```\ncomposer require romanzipp/laravel-dto\n```\n\n## Usage\n\nAll data objects must extend the [`romanzipp\\LaravelDTO\\AbstractModelData`](src/AbstractModelData.php) class.\n\n## Validation\n\nWhen 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.\n\n```php\nuse App\\Models\\Person;\nuse App\\Models\\Project;\nuse Illuminate\\Validation\\Rules\\Exists;\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\ForModel;\nuse romanzipp\\LaravelDTO\\Attributes\\ValidationRule;\nuse romanzipp\\LaravelDTO\\Attributes\\ValidationChildrenRule;\n\nclass PersonData extends AbstractModelData\n{\n    #[ValidationRule(['required', 'string', 'min:1', 'max:255'])]\n    public string $name;\n\n    #[ModelAttribute(['sometimes', 'min:18'])] \n    public int $currentAge;\n\n    #[ValidationRule(['nullable', 'string', 'in:de,en'])]\n    public ?string $language;\n\n    #[ValidationRule(['required', 'numeric', new Exists(Project::class, 'id')])]\n    public int $projectId;\n    \n    #[ValidationRule(['required', 'array', 'min:1']), ValidationChildrenRule(['string'], '*.device'), ValidationChildrenRule(['ipv4'], '*.ip')]\n    public array $logins;\n}\n```\n\nThis will throw a `Illuminate\\Validation\\ValidationException` if any rule does not pass.\n\n```php\n$data = new PersonData([\n    'name' =\u003e 'John Doe',\n    'currentAge' =\u003e 25,\n    'language' =\u003e 'de',\n    'projectId' =\u003e 2,\n    'logins' =\u003e [\n        ['device' =\u003e 'PC', 'ip' =\u003e '85.120.61.36'],\n        ['device' =\u003e 'iOS', 'ip' =\u003e '85.120.61.36'],\n    ]\n]);\n```\n\n## Hydrate Models\n\nYou can attach a model to any DTO using the [`#[ForModel(Model::class)]`](src/Attributes/ForModel.php) attribute.\nTo associate DTO properties with Model attributes, you need to attach the [`#[ModelAttribute()]`](src/Attributes/ModelAttribute.php) attribute to each property.\nIf no parameter is passed to the [`#[ModelAttribute]`](src/Attributes/ModelAttribute.php) attribute, DTO uses the property name itself.\n\n```php\nuse App\\Models\\Person;\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\ForModel;\nuse romanzipp\\LaravelDTO\\Attributes\\ModelAttribute;\n\n#[ForModel(Person::class)]\nclass PersonData extends AbstractModelData\n{\n    #[ModelAttribute]                 // The `$name` DTO property will populate the `name` model attribute\n    public string $name;\n\n    #[ModelAttribute('current_age')]  // The `$currentAge` DTO property will populate the `current_age` model attribute\n    public int $currentAge;\n\n    public string $language;          // The `$language` DTO property will be ignored\n}\n\n$data = new PersonData([\n    'name' =\u003e 'John Doe',\n    'currentAge' =\u003e 25,\n    'language' =\u003e 'de',\n]);\n\n$person = $data-\u003etoModel()-\u003esave();\n```\n\n**Attributes saved in `Person` model**\n\n| `name`    | `current_age` |\n|-----------|---------------|\n| John Doe  | 25            |\n\n\n**Note**: You can also pass an existing model to the `toModel()` method.\n\n```php\nuse App\\Models\\Person;\n\n$person = $data-\u003etoModel($person)-\u003esave();\n```\n\n**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.\n\n### Populate DTO from request input data\n\nWhen 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.\n\n```php\nuse App\\Models\\Person;\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\ForModel;\nuse romanzipp\\LaravelDTO\\Attributes\\ModelAttribute;\nuse romanzipp\\LaravelDTO\\Attributes\\RequestAttribute;\n\n#[ForModel(Person::class)]\nclass PersonData extends AbstractModelData\n{\n    #[RequestAttribute]            // The `$name` DTO property will be populated by the `name` request attribute\n    public string $name;\n\n    #[RequestAttribute('my_age')]  // The `$currentAge` DTO property will be populated by `my_age` request attribute\n    public int $currentAge;\n\n    public string $language;       // The `$language` DTO property will not be populated\n}\n```\n\n**The controller**\n\n```php\nuse App\\Data\\PersonData;\nuse Illuminate\\Http\\Request;\n\nclass TestController\n{\n    public function store(Request $request)\n    {\n        $data = PersonData::fromRequest($request);\n    }\n}\n```\n\n**Request input data**\n\n```json\n{\n  \"name\": \"John Doe\",\n  \"my_age\": 25,\n  \"language\": \"de\"\n}\n```\n\n**The `PersonData` DTO instance**\n\n```\nApp\\Data\\PersonData^ {\n  +name: \"John Doe\"\n  +currentAge: 25\n}\n```\n\n## Combined usage\n\nOf 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.\n\nBoth properties in the following example behave exactly the same. Use as you prefer.\n\n```php\nuse App\\Models\\Person;\nuse Illuminate\\Validation\\Rules\\Exists;\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\ForModel;\nuse romanzipp\\LaravelDTO\\Attributes\\ModelAttribute;\nuse romanzipp\\LaravelDTO\\Attributes\\RequestAttribute;\nuse romanzipp\\LaravelDTO\\Attributes\\ValidatedRequestModelAttribute;\nuse romanzipp\\LaravelDTO\\Attributes\\ValidationRule;\n\n#[ForModel(Person::class)]\nclass PersonData extends AbstractModelData\n{\n    // All attributes attached separately (looks disgusting doesn't it?)\n    #[\n        ValidationRule(['required', 'numeric', 'min:18']),\n        RequestAttribute('my_age'),\n        ModelAttribute('current_age')\n    ]\n    public string $currentAge;\n\n    // The `my_age` request attribute will be validated and set to the `current_age` model attribute.\n    //\n    //                                                             RequestAttribute \n    //                                         ValidationRule             │          ModelAttribute\n    //                              ┌────────────────┴──────────────┐  ┌──┴───┐  ┌─────┴─────┐\n   #[ValidatedRequestModelAttribute(['required', 'numeric', 'min:18'], 'my_age', 'current_age')];\n    public string $currentAge;\n}\n```\n\n**Request input data**\n\n```json\n{\n  \"my_age\": 25\n}\n```\n\n**The controller**\n\n```php\nuse App\\Data\\PersonData;\nuse Illuminate\\Http\\Request;\n\nclass TestController\n{\n    public function index(Request $request)\n    {\n        $person = PersonData::fromRequest($request)-\u003etoModel()-\u003esave();\n\n        return $person-\u003eid;\n    }\n}\n```\n\n## Validate arrays\n\nIf you only want to validate an array without casting the children items to another DTO, you can make use of the `ValidationChildrenRule` attribute.\n\nThe 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.\n\n#### Validate a simple array with numeric indexes\n\n```php\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\ValidationChildrenRule;\n\nclass PersonData extends AbstractModelData\n{\n    #[ValidationChildrenRule(['string', 'ipv4'], '*')];\n    public array $logins;\n}\n\n$data = new PersonData([\n    'logins' =\u003e [\n        '127.0.0.1',\n        '127.0.0.1'\n    ]\n]);\n```\n\n#### Validate associative arrays with named keys\n\n```php\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\ValidationChildrenRule;\n\nclass PersonData extends AbstractModelData\n{\n    #[ValidationChildrenRule(['string', 'ipv4'], '*.ip')];\n    public array $logins;\n}\n\n$data = new PersonData([\n    'logins' =\u003e [\n        ['ip' =\u003e '127.0.0.1'],\n        ['ip' =\u003e '127.0.0.1']\n    ]\n]);\n```\n\n#### Multiple validation rules\n\n```php\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\ValidationChildrenRule;\n\nclass PersonData extends AbstractModelData\n{\n    #[\n        ValidationChildrenRule(['string', 'ipv4'], '*.ip'),\n        ValidationChildrenRule(['string'], '*.device')\n    ];\n    public array $logins;\n}\n\n$data = new PersonData([\n    'logins' =\u003e [\n        ['ip' =\u003e '127.0.0.1', 'device' =\u003e 'iOS'],\n        ['ip' =\u003e '127.0.0.1', 'device' =\u003e 'macOS']\n    ]\n]);\n```\n\n## Cast arrays to DTOs (Nested data)\n\nIn 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. \n\nNote 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.\n\nAll attributes attached to the nested DTO will just work as expected.\n\n```php\nuse App\\Models\\Person;\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\ForModel;\nuse romanzipp\\LaravelDTO\\Attributes\\NestedModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\RequestAttribute;\nuse romanzipp\\LaravelDTO\\Attributes\\ValidatedRequestModelAttribute;\nuse romanzipp\\LaravelDTO\\Attributes\\ValidationRule;\n\n#[ForModel(Person::class)]\nclass PersonData extends AbstractModelData\n{\n    #[ValidatedRequestModelAttribute(['required', 'string'])]\n    public string $name;\n\n    /**\n     * @var AddressData[] \n     */\n    #[NestedModelData(AddressData::class), ValidationRule(['required', 'array']), RequestAttribute]\n    public array $adresses;\n}\n```\n\n```php\nuse App\\Models\\Address;\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\ValidatedRequestModelAttribute;\n\n#[ForModel(Address::class)]\nclass AddressData extends AbstractModelData\n{\n    #[ValidatedRequestModelAttribute(['string'])]\n    public string $street;\n\n    #[ValidatedRequestModelAttribute(['nullable', 'int'])]\n    public ?int $apartment = null;\n}\n```\n\n**Request input data**\n\n```json\n{\n  \"name\": \"John Doe\",\n  \"addresses\": [\n    {\n      \"street\": \"Sample Street\"\n    },\n    {\n      \"street\": \"Debugging Alley\",\n      \"apartment\": 43\n    }\n  ]\n}\n```\n\n**The controller**\n\n```php\nuse App\\Data\\PersonData;\nuse Illuminate\\Http\\Request;\n\nclass TestController\n{\n    public function index(Request $request)\n    {\n        $personData = PersonData::fromRequest($request);\n        $person = $personData-\u003etoModel()-\u003esave();\n\n        foreach ($personData-\u003eaddresses as $addressData) {\n            // We assume the `Person` model has a has-many relation with the `Address` model\n            $person-\u003eaddresses()-\u003esave(\n                $addressData-\u003etoModel()\n            );\n        }\n\n        return $person-\u003eid;\n    }\n}\n```\n\n## Type Casting\n\nType casts will convert any given value to a specified type.\n\n### Built-in type casts\n\n#### [`CastToDate`](src/Attributes/Casts/CastToDate.php)\n\nThe [`#[CastToDate]`](src/Attributes/Casts/CastToDate.php) attribute will respect your customly defined date class from `Date::use(...)`.\nYou 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).\n\n```php\nuse Carbon\\Carbon;\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\Casts\\CastToDate;\n\nclass PersonData extends AbstractModelData\n{\n    #[CastToDate]\n    public Carbon $date;\n}\n```\n\n### Custom type casts\n\nYou can declare custom type cast attributes by simply implementing the [`CastInterface`](src/Attributes/Casts/CastInterface.php) interface and attaching an attribute.\n\n```php\nuse Attribute;\nuse romanzipp\\LaravelDTO\\Attributes\\Casts\\CastInterface;\n\n#[Attribute]\nclass MyCast implements CastInterface\n{\n    public function castToType(mixed $value): mixed\n    {\n        return (string) $value;\n    }\n}\n```\n\n## IDE Support\n\nMake sure to add a `@method` PHPDoc comment like shown below to allow IDE and static analyzer support when calling the `toModel()` method.\n\n```php\nuse App\\Models\\Person;\nuse romanzipp\\LaravelDTO\\AbstractModelData;\nuse romanzipp\\LaravelDTO\\Attributes\\ForModel;\nuse romanzipp\\LaravelDTO\\Attributes\\ModelAttribute;\n\n/**\n * @method Person toModel()\n */\n#[ForModel(Person::class)]\nclass PersonData extends AbstractModelData\n{\n    #[ModelAttribute]\n    public string $name;\n}\n```\n\n## Testing\n\n### PHPUnit\n\n```\n./vendor/bin/phpunit\n```\n\n### PHPStan\n\n```\n./vendor/bin/phpstan\n```\n\n## Authors\n\n- [Roman Zipp](https://github.com/romanzipp)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromanzipp%2Flaravel-dto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fromanzipp%2Flaravel-dto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromanzipp%2Flaravel-dto/lists"}