Free & open source — no account required

v1.2.5-PRICING-19
Web & Frontend • Engineering Documentation

PHP 8+ Mastery: Automated DTO Generation

This technical guide provides an in-depth analysis of the json to php dto engine, best practices for implementation, and data security standards.

JSON to PHP DTO: Generating Typed Data Transfer Objects from JSON Payloads

PHP 8's constructor promotion and readonly properties make DTOs (Data Transfer Objects) significantly cleaner than the old getter/setter pattern. When you receive a JSON payload from an API or client request, generating the DTO class structure from the JSON shape gives you a typed, immutable value object that makes your controllers and services easier to reason about.

Live Example: E-Commerce Order DTO

// Input JSON (API request body)
{
  "order_id": "ord_8821",
  "customer_email": "buyer@example.com",
  "total_amount": 149.99,
  "currency": "USD",
  "item_count": 3,
  "is_gift": false,
  "gift_message": null
}

// Generated PHP DTO
<?php

// PHP version 8.1+ required

class Root
{
    public function __construct(
        private string $order_id,
        private string $customer_email,
        private float $total_amount,
        private string $currency,
        private float $item_count,
        private bool $is_gift,
        private ?string $gift_message,
    ) {}

    public function getOrderId(): string { return $this->order_id; }
    public function getCustomerEmail(): string { return $this->customer_email; }
    public function getTotalAmount(): float { return $this->total_amount; }
    public function getCurrency(): string { return $this->currency; }
    public function getItemCount(): float { return $this->item_count; }
    public function getIsGift(): bool { return $this->is_gift; }
    public function getGiftMessage(): ?string { return $this->gift_message; }
}

PHP 8.2 Readonly Classes: The Modern Pattern

PHP 8.2 added readonly at the class level, making all promoted properties automatically readonly. This is the cleanest DTO pattern available:

<?php

declare(strict_types=1);

// PHP 8.2+: readonly class makes all properties immutable automatically
readonly class OrderDto
{
    public function __construct(
        public string $orderId,
        public string $customerEmail,
        public float $totalAmount,
        public string $currency,
        public int $itemCount,        // int not float for counts
        public bool $isGift,
        public ?string $giftMessage,
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            orderId:       $data['order_id'],
            customerEmail: $data['customer_email'],
            totalAmount:   (float) $data['total_amount'],
            currency:      $data['currency'],
            itemCount:     (int) $data['item_count'],
            isGift:        (bool) $data['is_gift'],
            giftMessage:   $data['gift_message'] ?? null,
        );
    }
}

// Usage in a controller
$dto = OrderDto::fromArray(json_decode($request->getContent(), true));
echo $dto->customerEmail;     // public readonly — readable, not writable
// $dto->customerEmail = 'x'; // Fatal error: Cannot modify readonly property

Laravel Integration: Form Requests and Spatie Laravel-Data

<?php

// Option 1: Laravel Form Request (built-in validation + DTO in one)
use Illuminate\Foundation\Http\FormRequest;

class StoreOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'order_id'       => ['required', 'string'],
            'customer_email' => ['required', 'email'],
            'total_amount'   => ['required', 'numeric', 'min:0'],
            'currency'       => ['required', 'string', 'size:3'],
            'item_count'     => ['required', 'integer', 'min:1'],
            'is_gift'        => ['boolean'],
            'gift_message'   => ['nullable', 'string', 'max:500'],
        ];
    }
}

// In controller: Laravel injects the validated DTO automatically
class OrderController extends Controller
{
    public function store(StoreOrderRequest $request): JsonResponse
    {
        $validated = $request->validated(); // array, already validated
        $order = $this->orderService->create($validated);
        return response()->json($order, 201);
    }
}

// Option 2: Spatie Laravel-Data (generates DTOs with validation + casting)
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\Validation\Email;

class OrderData extends Data
{
    public function __construct(
        public readonly string $orderId,
        #[Email]
        public readonly string $customerEmail,
        public readonly float $totalAmount,
        public readonly string $currency,
        public readonly int $itemCount,
        public readonly bool $isGift = false,
        public readonly ?string $giftMessage = null,
    ) {}
}

// Auto-maps from request: OrderData::from($request)

JSON Deserialization: fromJson() Factory

<?php

readonly class OrderDto
{
    public function __construct(
        public string $orderId,
        public string $customerEmail,
        public float $totalAmount,
        public string $currency,
        public int $itemCount,
        public bool $isGift,
        public ?string $giftMessage,
    ) {}

    public static function fromJson(string $json): self
    {
        $data = json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR);
        return self::fromArray($data);
    }

    public static function fromArray(array $data): self
    {
        return new self(
            orderId:       $data['order_id'] ?? throw new \InvalidArgumentException('order_id required'),
            customerEmail: filter_var($data['customer_email'], FILTER_VALIDATE_EMAIL)
                               ?: throw new \InvalidArgumentException('invalid email'),
            totalAmount:   (float) ($data['total_amount'] ?? 0),
            currency:      strtoupper($data['currency'] ?? 'USD'),
            itemCount:     (int) ($data['item_count'] ?? 0),
            isGift:        (bool) ($data['is_gift'] ?? false),
            giftMessage:   $data['gift_message'] ?? null,
        );
    }

    public function toArray(): array
    {
        return [
            'order_id'       => $this->orderId,
            'customer_email' => $this->customerEmail,
            'total_amount'   => $this->totalAmount,
            'currency'       => $this->currency,
            'item_count'     => $this->itemCount,
            'is_gift'        => $this->isGift,
            'gift_message'   => $this->giftMessage,
        ];
    }
}

Frequently Asked Questions

What's the difference between constructor promotion and regular properties? Constructor promotion (public function __construct(private string $name)) declares the property and assigns it in one step — no separate private string $name; declaration needed. Available since PHP 8.0. Combined with readonly (PHP 8.1) or readonly class (PHP 8.2), it's the most concise DTO pattern.

Should I use float or string for monetary values? Use string or a Money value object for monetary amounts. Floating-point arithmetic on currency values causes rounding errors (0.1 + 0.2 !== 0.3 in floating-point). Store and compute money as integers (cents) or use bcmath functions with string representations.

Does this work with PHP 7? Constructor promotion is PHP 8.0+; readonly properties are PHP 8.1+; readonly classes are PHP 8.2+. For PHP 7, you need explicit property declarations, constructor assignment, and getters.

Is my JSON sent to a server? No. TypeMorph runs entirely in your browser — none of your data leaves your machine.

Developer FAQ

Is the processing local-only?

Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.

Can I use this for enterprise projects?

Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.