PHPackages                             gpalyan/dto-forge - 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. [Validation &amp; Sanitization](/categories/validation)
4. /
5. gpalyan/dto-forge

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

gpalyan/dto-forge
=================

Advanced DTO system with casting, validation, masks, nested DTOs

v1.0.0(2mo ago)240MITPHPPHP ^8.4CI passing

Since Apr 10Pushed 1w ago1 watchersCompare

[ Source](https://github.com/GaiPalyan/dtoforge)[ Packagist](https://packagist.org/packages/gpalyan/dto-forge)[ RSS](/packages/gpalyan-dto-forge/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (6)Versions (3)Used By (0)

PHP DTO Package
===============

[](#php-dto-package)

[![Tests](https://github.com/GaiPalyan/dtoforge/actions/workflows/tests.yml/badge.svg)](https://github.com/GaiPalyan/dtoforge/actions/workflows/tests.yml)[![codecov](https://camo.githubusercontent.com/f406c7a62a06b68b509a477b0d2a9963dc19505ce3c00d202154434b2889a70b/68747470733a2f2f636f6465636f762e696f2f67682f47616950616c79616e2f64746f666f7267652f6272616e63682f6d61696e2f67726170682f62616467652e737667)](https://codecov.io/gh/GaiPalyan/dtoforge)[![Latest Stable Version](https://camo.githubusercontent.com/eed3c397ec54825aa7ae1ef8925a6d8f06bb869c8903459c2a2b4346b0d9d60f/68747470733a2f2f706f7365722e707567782e6f72672f6770616c79616e2f64746f2d666f7267652f762f737461626c65)](https://packagist.org/packages/gpalyan/dto-forge)[![License](https://camo.githubusercontent.com/ff01dc860f2b08e9a55c2ee1d984d089e2ca807d0b7af9a4f19181d722ce92bc/68747470733a2f2f706f7365722e707567782e6f72672f6770616c79616e2f64746f2d666f7267652f6c6963656e7365)](https://packagist.org/packages/gpalyan/dto-forge)

A flexible and powerful Data Transfer Object (DTO) library for PHP that provides validation, type casting, default value generation, and convenient data manipulation methods.

Contents
--------

[](#contents)

- [Features](#features)
- [Installation](#installation)
- [Basic Usage](#basic-usage)
    - [Creating DTOs](#creating-dtos)
    - [Magic Methods](#magic-methods)
    - [Filling Data](#filling-data)
    - [Merging DTOs](#merging-dtos)
    - [Comparing DTOs](#comparing-dtos)
    - [Cloning DTOs](#cloning-dtos)
    - [Serialization](#serialization)
- [Nested DTOs](#nested-dtos)
- [Validation](#validation)
    - [Using Built-in Validators](#using-built-in-validators)
    - [Validation Errors](#validation-errors)
    - [Creating Custom Validators](#creating-custom-validators)
- [Default Value Generation](#default-value-generation)
    - [Creating Custom Generators](#creating-custom-generators)
- [Type Casting](#type-casting)
    - [Casting Collections](#casting-collections)
- [Masking](#masking)
- [API Reference](#api-reference)
- [Contributing](#contributing)

Features
--------

[](#features)

- **Easy DTO Creation** - Create DTOs from arrays, JSON, or use fluent setters
- **Built-in Validation** - Validate data with custom rules (required fields, INN validation, etc.)
- **Type Casting** - Automatic type conversion for scalar values
- **Data Manipulation** - Merge, diff, fill, and clone DTOs
- **Nested DTOs** - Full support for nested DTO structures
- **Default Values** - Generate default values automatically when needed
- **Serialization** - Convert to arrays and JSON easily
- **Masking** - Redact sensitive fields on serialization without mutating the DTO

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

[](#installation)

```
composer require gpalyan/dto-forge
```

Basic Usage
-----------

[](#basic-usage)

### Creating DTOs

[](#creating-dtos)

All DTOs must extend `BaseDto`:

```
use Forge\Dto\Support\BaseDto;

final class SimpleDto extends BaseDto
{
    public ?string $name = null;
    public ?string $age = null;
    public ?string $address = null;
}

// From array
$dto = new SimpleDto([
    'name' => 'John Doe',
    'age' => '25',
    'address' => 'Some address'
]);

// From JSON
$dto = new SimpleDto(json_encode($data));

// Using setters
$dto = new SimpleDto()
    ->setName('John Doe')
    ->setAge('25')
    ->setAddress('Some address');

// Access via properties or getters
echo $dto->name;           // John Doe
echo $dto->getName();      // John Doe

if ($dto->hasName()) {
    echo "Name is set";
}
```

### Magic Methods

[](#magic-methods)

DTOs automatically provide getters, setters, and checkers for all properties without explicit method definitions. You don't need to manually create getter/setter methods - they are generated automatically based on property names using camelCase convention.

### Filling Data

[](#filling-data)

```
$dto = new SimpleDto();
$dto->fill([
    'name' => 'John Doe',
    'age' => '25'
]);

// Overwrite specific fields
$dto->fill(['name' => 'Jane Doe']);
echo $dto->name; // Jane Doe
```

### Merging DTOs

[](#merging-dtos)

```
$dto1 = new SimpleDto()->setName('John Doe');
$dto2 = new SimpleDto()
    ->setAge('25')
    ->setAddress('Some address');

$dto3 = $dto1->merge($dto2);

// $dto3 now contains all fields from both DTOs
echo $dto3->getName();    // John Doe
echo $dto3->getAge();     // 25
echo $dto3->getAddress(); // Some address
```

### Comparing DTOs

[](#comparing-dtos)

```
$dto1 = new SimpleDto([
    'name' => 'John Doe',
    'age' => '25',
    'address' => 'Some address'
]);

$dto2 = new SimpleDto(['name' => 'Jane Doe']);

$diff = $dto1->diff($dto2);

/*
[
    'name' => ['old' => 'John Doe', 'new' => 'Jane Doe'],
    'age' => ['old' => '25', 'new' => null],
    'address' => ['old' => 'Some address', 'new' => null]
]
*/
```

### Cloning DTOs

[](#cloning-dtos)

```
$dto = new SimpleDto(['name' => 'John Doe']);
$clone = $dto->clone();

$diff = $dto->diff($clone); // Empty array - perfect copy
```

### Serialization

[](#serialization)

```
$dto = new SimpleDto([
    'name' => 'John Doe',
    'age' => '25'
]);

// To array
$array = $dto->toArray();

// To JSON
$json = $dto->toJson();
```

Nested DTOs
-----------

[](#nested-dtos)

```
use YourVendor\Dto\NestedDto;
use YourVendor\Dto\SimpleDto;

$dto = new NestedDto([
    'children' => new SimpleDto([
        'name' => 'John Doe',
        'age' => '25',
        'address' => 'Some address'
    ]),
    'companyAddress' => 'Some address',
    'companyName' => 'google'
]);

// Access nested properties
$children = $dto->getChildren(); // Returns SimpleDto instance
echo $children->getName(); // John Doe

// Serialize nested DTOs
$array = $dto->toArray();
/*
[
    'children' => [
        'name' => 'John Doe',
        'age' => '25',
        'address' => 'Some address'
    ],
    'companyAddress' => 'Some address',
    'companyName' => 'google'
]
*/

// Merge nested DTOs
$dto1 = new NestedDto($data);
$dto2 = new NestedDto();
$dto2->setChildren(
    (new SimpleDto())
        ->setName('Changed name')
        ->setAge(null)
);

$merged = $dto1->merge($dto2);
echo $merged->getChildren()->getName(); // Changed name
```

Validation
----------

[](#validation)

Validation is applied via PHP attributes on DTO properties.

### Using Built-in Validators

[](#using-built-in-validators)

```
use Forge\Dto\Support\BaseDto;
use Forge\Dto\Support\Validation\Uuid;
use Forge\Dto\Support\Validation\Required;
use Forge\Dto\Support\Validation\Inn;

final class UserDto extends BaseDto
{
    #[Uuid]
    public ?string $id = null;

    #[Required]
    public ?string $name = null;

    #[Inn]
    public ?string $inn = null;
}

// Validation happens automatically on property assignment
$dto = new UserDto();
$dto->setId('invalid-uuid'); // Throws DtoValidationException
$dto->setId('550e8400-e29b-41d4-a716-446655440000'); // ✅

$dto->setInn('627708638650'); // ✅ Valid individual INN
$dto->setInn('4404380820');   // ✅ Valid legal entity INN
```

### Validation Errors

[](#validation-errors)

Get all validation errors that occurred during DTO population:

```
$dto = new UserDto();

try {
    $dto->setId('invalid-uuid');
} catch (DtoValidationException $e) {
    // Exception thrown
}

// Validation errors are used by default value generators
if (empty($dto->getValidationErrors())) {
    // Safe to generate defaults
}
```

### Creating Custom Validators

[](#creating-custom-validators)

Implement `PropertyValidatorInterface` to create your own validators:

```
use Attribute;
use Forge\Dto\Contracts\PropertyValidatorInterface;
use Forge\Dto\Support\Validation\Traits\HasLaravelValidation;

#[Attribute(Attribute::TARGET_PROPERTY)]
readonly class Email implements PropertyValidatorInterface
{
    use HasLaravelValidation;

    public function validate(mixed $value, string $propertyName): void
    {
        $this->performValidation(
            value: $value,
            rules: ['nullable', 'email'],
            field: $propertyName
        );
    }
}

// Usage
final class ContactDto extends BaseDto
{
    #[Email]
    public ?string $email = null;
}
```

**Requirements for custom validators:**

- Must be a PHP 8 Attribute with `Attribute::TARGET_PROPERTY`
- Must implement `PropertyValidatorInterface`
- Throw `DtoValidationException` on validation failure

Default Value Generation
------------------------

[](#default-value-generation)

Generate default values for properties using attributes.

### Creating Custom Generators

[](#creating-custom-generators)

Implement `DefaultValueGeneratorInterface` to create your own generators:

```
use Attribute;
use Forge\Dto\Contracts\DefaultValueGeneratorInterface;
use Forge\Dto\Support\BaseDto;

#[Attribute(Attribute::TARGET_PROPERTY)]
class UuidGenerator implements DefaultValueGeneratorInterface
{
    public function generate(BaseDto $dto): mixed
    {
        // return the generated value for the property
    }

    public function supports(BaseDto $dto, string $propertyName): bool
    {
        // return true if generation should occur (e.g. property is empty and DTO has no errors)
    }
}

// Usage
final class EntityDto extends BaseDto
{
    #[UuidGenerator]
    public ?string $id = null;
}
```

**Requirements for custom generators:**

- Must be a PHP 8 Attribute with `Attribute::TARGET_PROPERTY`
- Must implement `DefaultValueGeneratorInterface`
- Implement `generate(BaseDto $dto): mixed` - returns the generated value
- Implement `supports(BaseDto $dto, string $propertyName): bool` - determines if generation should occur

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

[](#type-casting)

The package automatically casts values to appropriate types:

```
final class UserDto extends BaseDto
{
    public ?string $age = null; // declared as string
}

$dto = new UserDto()->setAge(25); // passing int
echo gettype($dto->getAge()); // "string" — automatically cast to match property type
```

### Casting Collections

[](#casting-collections)

An `array` property does not know what its items are. To cast each element into a DTO, declare the item type explicitly with the `#[CastEachTo]` attribute:

```
use Forge\Dto\Support\BaseDto;
use Forge\Dto\Support\Casting\CastEachTo;

final class OrderDto extends BaseDto
{
    #[CastEachTo(LineItemDto::class)]
    public ?array $items = null;
}

$order = new OrderDto([
    'items' => [
        ['sku' => 'A-1', 'qty' => 2],
        ['sku' => 'B-7', 'qty' => 1],
    ],
]);

$order->items[0]; // LineItemDto instance
```

Each raw array (or JSON object) is constructed into the given class; values that are already instances of that class are passed through unchanged. Casting is **opt-in via this attribute only** — docblock `@var Item[]` annotations are treated as documentation and never drive casting.

> **Note:** `#[CastEachTo]` and `#[ArrayOf]` are not meant to be combined on the same property. `CastEachTo` *constructs* items from raw data; `#[ArrayOf]` *validates* a collection of already-built (e.g. polymorphic) objects without constructing them. In strict mode `#[ArrayOf]` runs before casting and will reject raw input — pick the one that matches your intent.

Masking
-------

[](#masking)

Masking redacts property values **on serialization only** — the stored DTO value is never changed. It is opt-in per call via the `masking` flag on `toArray()` / `toJson()`.

A mask is a PHP attribute implementing `PropertyMaskInterface`:

```
use Attribute;
use Forge\Dto\Contracts\PropertyMaskInterface;

#[Attribute(Attribute::TARGET_PROPERTY)]
readonly class MaskCard implements PropertyMaskInterface
{
    public function apply(string $value): string
    {
        return str_repeat('*', max(0, strlen($value) - 4)) . substr($value, -4);
    }
}

final class PaymentDto extends BaseDto
{
    #[MaskCard]
    public ?string $cardNumber = null;
}

$dto = new PaymentDto(['cardNumber' => '4111111111111111']);

$dto->toArray();              // ['cardNumber' => '4111111111111111']
$dto->toArray(masking: true); // ['cardNumber' => '************1111']
echo $dto->cardNumber;        // '4111111111111111' — untouched
```

**Requirements / behavior:**

- Must be a PHP 8 Attribute with `Attribute::TARGET_PROPERTY` implementing `PropertyMaskInterface`.
- `apply(string $value): string` is called only for **string** values, and only when `masking` is enabled.
- One mask per property — the first attribute wins.
- Nested DTOs are masked recursively; masking never mutates the DTO, only its serialized output.

API Reference
-------------

[](#api-reference)

### Core Methods

[](#core-methods)

- `fill(array $data): self` - Fill DTO with data from array
- `merge(DtoInterface $dto): self` - Merge another DTO into this one
- `diff(DtoInterface $dto): array` - Get differences between DTOs
- `clone(): self` - Create a deep copy of the DTO
- `toArray(): array` - Convert DTO to array
- `toJson(): string` - Convert DTO to JSON string
- `generateDefaultsIfAllowed(): void` - Generate default values for null fields

Contributing
------------

[](#contributing)

Contributions are welcome! Please feel free to submit a Pull Request.

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance94

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 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

Unknown

Total

1

Last Release

60d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/79068666?v=4)[Gai Palyan](/maintainers/GaiPalyan)[@GaiPalyan](https://github.com/GaiPalyan)

---

Top Contributors

[![GaiPalyan](https://avatars.githubusercontent.com/u/79068666?v=4)](https://github.com/GaiPalyan "GaiPalyan (12 commits)")

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/gpalyan-dto-forge/health.svg)

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

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[illuminate/validation

The Illuminate Validation package.

18837.7M1.6k](/packages/illuminate-validation)[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[moonshine/moonshine

Laravel administration panel

1.3k239.9k72](/packages/moonshine-moonshine)[tallstackui/tallstackui

TallStackUI is a powerful suite of Blade components that elevate your workflow of Livewire applications.

719160.4k12](/packages/tallstackui-tallstackui)[illuminate/pipeline

The Illuminate Pipeline package.

9348.3M264](/packages/illuminate-pipeline)

PHPackages © 2026

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