PHPackages                             callismart/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. callismart/dto

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

callismart/dto
==============

A flexible, extendable foundation for Data Transfer Objects (DTOs) in PHP.

v1.0.1(2w ago)091MITPHPPHP &gt;=8.1

Since May 13Pushed 2w agoCompare

[ Source](https://github.com/CallismartLtd/CallismartDTO)[ Packagist](https://packagist.org/packages/callismart/dto)[ RSS](/packages/callismart-dto/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (1)Versions (3)Used By (1)

Callismart DTO
==============

[](#callismart-dto)

A flexible, extensible foundation for Data Transfer Objects (DTOs) in PHP 8.1+. Build type-safe, immutable data containers with zero dependencies.

Why Callismart DTO?
-------------------

[](#why-callismart-dto)

DTOs are fundamental building blocks for clean architecture, but creating them usually means writing repetitive boilerplate. Callismart DTO eliminates that by providing a powerful, extensible base class that handles:

- **Dynamic Properties** — Add any data without declaring fields
- **Type Coercion** — Automatically cast values to expected types
- **Validation** — Enforce which keys are allowed
- **Security** — Mask sensitive values in debugging and serialization
- **Fluent API** — Chain methods for readable configuration
- **Zero Dependencies** — No external packages required

Perfect for APIs, configuration objects, domain models, and any data transfer scenario.

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

[](#installation)

Install via Composer:

```
composer require callismart/dto
```

**Requirements**: PHP 8.1 or higher

Quick Start
-----------

[](#quick-start)

### Basic Usage

[](#basic-usage)

```
use Callismart\DTO\DTO;

// Create a DTO with initial data
$dto = new DTO([
    'name'  => 'Alice',
    'email' => 'alice@example.com',
    'age'   => 30,
]);

// Access via magic property
echo $dto->name;           // Alice

// Access via array syntax
echo $dto['email'];        // alice@example.com

// Access via get() method
echo $dto->get( 'age' );   // 30

// Check if key exists
if ( $dto->has( 'name' ) ) {
    echo "Name is set";
}

// Set a value
$dto->phone = '555-1234';

// Remove a key
$dto->remove( 'age' );

// Convert to array
$array = $dto->to_array();

// Convert to JSON
$json = $dto->to_json();
```

### Creating Type-Safe Subclasses

[](#creating-type-safe-subclasses)

Restrict allowed keys and apply type casting:

```
use Callismart\DTO\DTO;

class UserDTO extends DTO {
    protected function allowed_keys(): array {
        return [ 'id', 'name', 'email', 'role', 'active' ];
    }

    protected function sensitive_keys(): array {
        return [ 'email' ];
    }

    protected function cast( string $key, mixed $value ): mixed {
        return match ( $key ) {
            'id'     => (int) $value,
            'role'   => strtolower( (string) $value ),
            'active' => (bool) $value,
            default  => $value,
        };
    }
}

$user = new UserDTO([
    'id'     => '42',
    'name'   => 'Bob',
    'email'  => 'bob@example.com',
    'role'   => 'ADMIN',  // Cast to 'admin'
    'active' => 1,        // Cast to true
]);

// This throws InvalidArgumentException
$user->phone = '555-1234';  // 'phone' is not allowed
```

Core Features
-------------

[](#core-features)

### Dynamic Properties

[](#dynamic-properties)

Access properties using any of three syntaxes:

```
$dto = new DTO( [ 'name' => 'Alice' ] );

// Magic property access
$dto->name;

// Array access
$dto['name'];

// Explicit get() method
$dto->get( 'name' );

// With default value
$dto->get( 'missing', 'default' );
```

### Type Casting &amp; Validation

[](#type-casting--validation)

Override `cast()` to enforce types and validate values:

```
class ProductDTO extends DTO {
    protected function cast( string $key, mixed $value ): mixed {
        return match ( $key ) {
            'price'     => (float) $value,
            'quantity'  => (int) $value,
            'category'  => $this->validate_category( $value ),
            'in_stock'  => (bool) $value,
            default     => $value,
        };
    }

    private function validate_category( mixed $value ): string {
        $allowed = [ 'electronics', 'clothing', 'books' ];
        $value = (string) $value;

        if ( ! in_array( $value, $allowed, true ) ) {
            throw new InvalidArgumentException(
                "Invalid category: {$value}"
            );
        }

        return $value;
    }
}

$product = new ProductDTO([
    'price'    => '19.99',      // Cast to float: 19.99
    'quantity' => '5',          // Cast to int: 5
    'category' => 'ELECTRONICS', // Validated: 'electronics'
    'in_stock' => 'yes',        // Cast to bool: true
]);
```

### Key Whitelisting

[](#key-whitelisting)

Restrict which keys are allowed:

```
class ConfigDTO extends DTO {
    protected function allowed_keys(): array {
        return [ 'host', 'port', 'username', 'password' ];
    }
}

$config = new ConfigDTO([
    'host'     => 'localhost',
    'port'     => 3306,
    'username' => 'user',
    'password' => 'secret',
]);

// This throws InvalidArgumentException
$config->api_key = 'xyz';  // Not in allowed_keys
```

### Sensitive Value Masking

[](#sensitive-value-masking)

Protect sensitive data in debugging and serialization:

```
class APIKeyDTO extends DTO {
    protected function sensitive_keys(): array {
        return [ 'api_key', 'secret' ];
    }
}

$creds = new APIKeyDTO([
    'app_id'  => 'my-app',
    'api_key' => 'sk_live_abc123xyz789',
    'secret'  => 'super_secret_value',
]);

// Direct access works
echo $creds->api_key;  // sk_live_abc123xyz789

// But in output, it's masked
var_dump( $creds );                 // api_key: '***', secret: '***'
echo $creds->to_array()['api_key']; // *** (masked)
echo json_encode( $creds );         // {"api_key":"***", ...}
```

### Fluent API

[](#fluent-api)

Chain methods for readable code:

```
$user = new UserDTO()
    ->set( 'id', 1 )
    ->set( 'name', 'Alice' )
    ->set( 'email', 'alice@example.com' )
    ->set( 'role', 'admin' );

// Or fill at once
$user = new UserDTO()
    ->fill([
        'id'    => 1,
        'name'  => 'Alice',
        'email' => 'alice@example.com',
    ]);

// Or merge additional data
$user = new UserDTO( [ 'id' => 1 ] )
    ->merge([
        'name'  => 'Alice',
        'email' => 'alice@example.com',
    ]);
```

### Advanced Operations

[](#advanced-operations)

```
$dto = new DTO([
    'id'     => 1,
    'name'   => 'Product',
    'price'  => 19.99,
    'secret' => 'hidden',
]);

// Get all keys
$keys = $dto->keys();        // ['id', 'name', 'price', 'secret']

// Get all values
$values = $dto->values();    // [1, 'Product', 19.99, 'hidden']

// Select only certain keys
$public = $dto->only( ['id', 'name', 'price'] );
// ['id' => 1, 'name' => 'Product', 'price' => 19.99]

// Get all except certain keys
$filtered = $dto->except( ['secret'] );
// ['id' => 1, 'name' => 'Product', 'price' => 19.99]

// Check if empty
if ( $dto->is_empty() ) {
    echo "No data";
}

// Count properties
echo count( $dto );         // 4

// Clear all data
$dto->clear();
```

### Serialization

[](#serialization)

DTOs work with standard PHP serialization interfaces:

```
$dto = new DTO([
    'name'     => 'Test',
    'password' => 'secret',
]);

// JSON serialization (automatically masks sensitive values)
$json = json_encode( $dto );

// Iteration (foreach loops)
foreach ( $dto as $key => $value ) {
    echo "{$key}: {$value}";
}

// Countable
echo count( $dto );  // Returns number of properties

// Convert to array
$array = $dto->to_array();

// Convert to JSON string
$json_str = $dto->to_json();

// Populate from JSON
$dto->from_json( '{"name":"Alice","age":30}' );
```

### Debugging

[](#debugging)

```
$dto = new UserDTO([
    'id'       => 1,
    'name'     => 'Alice',
    'email'    => 'alice@example.com',
    'password' => 'secret123',
]);

// var_dump respects __debugInfo()
var_dump( $dto );
// Shows password as '***' (masked)

// Get debug information
$debug = $dto->dump();
// Returns array with class name, count, allowed keys, and masked properties
```

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

[](#api-reference)

### Reading

[](#reading)

```
$dto->get( string $key, mixed $default = null ): mixed
$dto['key']                                    // ArrayAccess
$dto->key                                      // Magic property
$dto->has( string $key ): bool
$dto->keys(): array
$dto->values(): array
$dto->to_array(): array
$dto->is_empty(): bool
$dto->count(): int
$dto->dump(): array
```

### Writing

[](#writing)

```
$dto->set( string $key, mixed $value ): static  // Fluent, returns $this
$dto->fill( array $data ): static               // Replace all, fluent
$dto->merge( array $data ): static              // Merge in, fluent
$dto->remove( string $key ): static             // Delete one, fluent
$dto->clear(): static                           // Delete all, fluent
$dto[$key] = $value                             // ArrayAccess
$dto->key = $value                              // Magic property
```

### Selection

[](#selection)

```
$dto->only( array $keys ): array                // Only these keys
$dto->except( array $keys ): array              // All except these
```

### Serialization

[](#serialization-1)

```
$dto->to_json( int $flags = 0 ): string
$dto->from_json( string $json ): static
json_encode( $dto )                             // Works (JsonSerializable)
foreach ( $dto as $key => $value ) {}           // Works (IteratorAggregate)
count( $dto )                                   // Works (Countable)
```

Extension Points
----------------

[](#extension-points)

Override these methods in subclasses to customize behavior:

### allowed\_keys()

[](#allowed_keys)

Restrict which properties can be set:

```
protected function allowed_keys(): array {
    return [ 'key1', 'key2', 'key3' ];
}
```

Return empty array (default) to allow any key.

### sensitive\_keys()

[](#sensitive_keys)

Mark values that should be masked in output:

```
protected function sensitive_keys(): array {
    return [ 'password', 'api_key', 'token' ];
}
```

Sensitive values are masked as `***` in:

- `to_array()` output
- JSON serialization
- `var_dump()` output
- `dump()` method

### cast()

[](#cast)

Transform or validate values before storage:

```
protected function cast( string $key, mixed $value ): mixed {
    return match ( $key ) {
        'age'    => (int) $value,
        'email'  => strtolower( (string) $value ),
        'active' => (bool) $value,
        default  => $value,
    };
}
```

Can throw `InvalidArgumentException` for invalid values.

Examples
--------

[](#examples)

### API Request/Response

[](#api-requestresponse)

```
class CreateUserRequest extends DTO {
    protected function allowed_keys(): array {
        return [ 'name', 'email', 'password' ];
    }

    protected function cast( string $key, mixed $value ): mixed {
        return match ( $key ) {
            'email' => strtolower( (string) $value ),
            'name'  => trim( (string) $value ),
            default => $value,
        };
    }
}

// Handle request
$request = new CreateUserRequest( $_POST );
$user = create_user( $request );
```

### Configuration Object

[](#configuration-object)

```
class DatabaseConfig extends DTO {
    protected function allowed_keys(): array {
        return [ 'host', 'port', 'database', 'username', 'password' ];
    }

    protected function sensitive_keys(): array {
        return [ 'password' ];
    }

    protected function cast( string $key, mixed $value ): mixed {
        return match ( $key ) {
            'port' => (int) $value,
            default => $value,
        };
    }
}

$config = new DatabaseConfig([
    'host'     => 'localhost',
    'port'     => '3306',
    'database' => 'myapp',
    'username' => 'user',
    'password' => 'secret',
]);

echo json_encode( $config );  // password is masked
```

### Domain Entity

[](#domain-entity)

```
class Product extends DTO {
    protected function allowed_keys(): array {
        return [ 'id', 'name', 'sku', 'price', 'quantity', 'created_at' ];
    }

    protected function cast( string $key, mixed $value ): mixed {
        return match ( $key ) {
            'id'       => (int) $value,
            'price'    => (float) $value,
            'quantity' => (int) $value,
            'created_at' => new DateTime( $value ),
            default    => $value,
        };
    }

    public function is_in_stock(): bool {
        return $this->quantity > 0;
    }
}

$product = new Product([
    'id'       => '123',
    'name'     => 'Widget',
    'sku'      => 'WDG-001',
    'price'    => '19.99',
    'quantity' => '50',
    'created_at' => '2025-05-13 10:00:00',
]);

echo $product->is_in_stock() ? 'In Stock' : 'Out of Stock';
```

### Nested DTOs

[](#nested-dtos)

```
class Address extends DTO {
    protected function allowed_keys(): array {
        return [ 'street', 'city', 'state', 'zip', 'country' ];
    }
}

class User extends DTO {
    protected function allowed_keys(): array {
        return [ 'id', 'name', 'email', 'address' ];
    }

    protected function cast( string $key, mixed $value ): mixed {
        if ( $key === 'address' && is_array( $value ) ) {
            return new Address( $value );
        }
        return $value;
    }
}

$user = new User([
    'id'    => 1,
    'name'  => 'Alice',
    'email' => 'alice@example.com',
    'address' => [
        'street'  => '123 Main St',
        'city'    => 'Springfield',
        'state'   => 'IL',
        'zip'     => '62701',
        'country' => 'USA',
    ],
]);

echo $user->address->city;  // Springfield
```

Testing
-------

[](#testing)

Run the test suite with:

```
composer test
```

Or with PHPUnit directly:

```
./vendor/bin/phpunit
```

Best Practices
--------------

[](#best-practices)

1. **Create Typed Subclasses** — Always subclass DTO for domain objects, not raw instances
2. **Define allowed\_keys()** — Restrict to prevent typos and unexpected properties
3. **Implement cast()** — Enforce types, trim strings, validate values
4. **Mark sensitive\_keys()** — Protect passwords, tokens, API keys, etc.
5. **Use Fluent API** — Chain methods for readable initialization
6. **Immutability** — Once created, avoid mutating DTOs (clone if needed)
7. **Safe Logging** — Use `dump()` or `to_array()` for safe debug output

Performance
-----------

[](#performance)

The DTO class is designed for performance:

- **Zero Reflection** — No reflection or magic used beyond `__get/__set`
- **Direct Array Access** — Internal storage is a simple array
- **Minimal Overhead** — Single method call for get/set operations
- **Type Hints** — Full type hints enable JIT compilation optimizations

Security
--------

[](#security)

Sensitive value protection:

- Automatic masking in serialization and debugging
- Prevent accidental credential exposure in logs
- Safe for storing in configuration objects
- Works with any sensitive key name you define

Requirements
------------

[](#requirements)

- **PHP**: 8.1 or higher
- **Extensions**: None required

License
-------

[](#license)

MIT License. See LICENSE file for details.

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

[](#contributing)

Contributions are welcome! Please ensure:

- Code follows WordPress PHP Coding Standards (K&amp;R braces, tab indentation)
- All public methods have PHPDoc comments
- Full type hints on parameters and returns
- Tests pass with `composer test`

Support
-------

[](#support)

- **Issues**: Report bugs on GitHub
- **Documentation**: Check examples and API reference above
- **Questions**: Create a discussion on GitHub

Related Packages
----------------

[](#related-packages)

- **callismart/http** — Lightweight HTTP client with multi-adapter support
- **callismart/database** — Multi-adapter database abstraction (uses this DTO for configuration)

Changelog
---------

[](#changelog)

### v1.0.0

[](#v100)

- Initial release
- Full DTO functionality with extension hooks
- Magic property access, array access, iteration
- JSON serialization with sensitive value masking
- Fluent API with chaining
- Zero dependencies

Author
------

[](#author)

Callistus Nwachukwu -

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance96

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity43

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

Every ~6 days

Total

2

Last Release

20d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/ceb96fc601f945fd7ad524e6c485c3d57898d0487e24fe4b448908a64a26bbdd?d=identicon)[CallismartLtd](/maintainers/CallismartLtd)

---

Top Contributors

[![CallismartLtd](https://avatars.githubusercontent.com/u/142177518?v=4)](https://github.com/CallismartLtd "CallismartLtd (3 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

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

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

###  Alternatives

[simonhamp/laravel-nova-csv-import

A fully-fledged CSV import tool for Laravel Nova.

168469.9k](/packages/simonhamp-laravel-nova-csv-import)[magefan/module-conflict-detector

Detect extension conflicts

4875.4k2](/packages/magefan-module-conflict-detector)

PHPackages © 2026

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