tommyknocker / struct
Map external arrays and JSON into validated PHP DTOs and explicit outbound payloads
Requires
- php: ^8.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.89
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
External data → validated PHP objects → explicit outbound payloads.
Struct maps arrays and JSON into DTOs and prepares those DTOs for another API, webhook, or queue. Define exchanged fields, validate incoming values, and control outgoing names. Framework-independent, with no external runtime dependencies.
Installation
composer require tommyknocker/struct
Requires PHP 8.1+.
Receive, work with, send
use tommyknocker\struct\CollectionType; use tommyknocker\struct\Field; use tommyknocker\struct\Struct; use tommyknocker\struct\validation\rules\CountRule; use tommyknocker\struct\validation\rules\RangeRule; enum OrderStatus: string { case Paid = 'paid'; } final class OrderItem extends Struct { #[Field] public readonly string $sku; #[Field(validationRules: [new RangeRule(1, 100)])] public readonly int $quantity; } final class Order extends Struct { #[Field(alias: 'order_id', outputAlias: 'id')] public readonly string $orderId; #[Field] public readonly OrderStatus $status; #[Field] public readonly DateTimeImmutable $createdAt; #[Field(OrderItem::class, collection: CollectionType::List, validationRules: [new CountRule(min: 1)])] public readonly array $items; } $order = Order::fromJson('{ "order_id": "ord-42", "status": "paid", "createdAt": "2026-10-25T12:00:00+03:00", "items": [{"sku": "A", "quantity": 2}] }'); $order->items[0]->quantity; // int: 2 $order->status; // OrderStatus::Paid $payload = $order->toArray(); // [ // 'id' => 'ord-42', // 'status' => 'paid', // 'createdAt' => '2026-10-25T12:00:00.000000+03:00', // 'items' => [['sku' => 'A', 'quantity' => 2]], // ] $json = $order->toJson(pretty: true);
Use the same DTO in both directions for the same contract. Use separate DTOs when fields or validation differ: an input DTO containing a password should not be reused as a public response. See executable scenarios, including a partner order → fulfillment integration.
Documentation
Read the documentation index, or go directly to:
- Fields, types, required/null/default semantics
- Lists, maps, and shape preservation
- Validation and all built-in rules
- Transformations and copies
- Arrays, JSON, and outbound contracts
- Error handling
- API reference
- PHPStan, scaffolding, and development checks
The examples are executable integration scenarios. Start with receiving a request, partner-to-fulfillment mapping, or webhook subscription validation.
Development
composer install composer check
Tests target PHP 8.1–8.5; PHPStan runs at level 9. See development checks and CHANGELOG.md.
MIT license. Author: Vasiliy Krivoplyas.