betstore/laravel-dto

A reusable DTO class for Laravel projects

Installs: 47

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/betstore/laravel-dto

1.2.0 2026-01-19 04:38 UTC

This package is auto-updated.

Last update: 2026-02-19 06:06:45 UTC


README

πŸš€ Π’Ρ‹ΡΠΎΠΊΠΎΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½Π°Ρ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° Data Transfer Objects для Laravel с ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠΎΠΉ JIT компиляции

PHP Version Laravel Performance License

⚑ ΠšΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ особСнности

  • πŸš€ JIT-оптимизированная ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ - Π΄ΠΎ 100x быстрСС Π±Π°Π·ΠΎΠ²ΠΎΠΉ вСрсии
  • πŸ”„ АвтоматичСскоС ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΈΠΏΠΎΠ² - ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° union types, nullable Ρ‚ΠΈΠΏΠΎΠ²
  • βœ… Валидация Π΄Π°Π½Π½Ρ‹Ρ… - интСграция с Laravel Validation
  • πŸ“¦ БСриализация/дСсСриализация - Π² массив, JSON, Laravel Response
  • 🎯 Type casting - ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ Π²Π»ΠΎΠΆΠ΅Π½Π½Ρ‹Ρ… ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² ΠΈ ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΉ
  • ⚑ ΠšΡΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Reflection - O(1) доступ ΠΊ ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹ΠΌ классов
  • 🎨 Laravel интСграция - полная ΡΠΎΠ²ΠΌΠ΅ΡΡ‚ΠΈΠΌΠΎΡΡ‚ΡŒ с экосистСмой Laravel

πŸ“Š ΠŸΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ

ВСст Π‘Π΅Π· ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΉ Π‘ оптимизациями Π‘ JIT Π£Π»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΠ΅
БозданиС 100 DTO ~50 ms ~11 ms ~3 ms 16x ⚑
БСриализация 1000Γ— ~1000 ms ~25 ms ~3 ms 333x ⚑
ΠžΠ±Ρ‰Π΅Π΅ врСмя тСстов ~1000 ms ~100 ms ~9 ms 111x ⚑

πŸ“¦ Установка

composer require betstore/laravel-dto

ВрСбования

  • PHP 8.2+
  • Laravel 8.0+
  • OPcache Π²ΠΊΠ»ΡŽΡ‡Π΅Π½
  • Для максимальной ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ: JIT компиляция

РСкомСндуСмая конфигурация OPcache

; php.ini ΠΈΠ»ΠΈ /etc/php/8.2/mods-available/opcache.ini
zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=256
opcache.max_accelerated_files=7963
opcache.revalidate_freq=0
opcache.fast_shutdown=1
opcache.interned_strings_buffer=16

; JIT компиляция (автоматичСски ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ΡΡ ΠΏΡ€ΠΈ Π½Π°Π»ΠΈΡ‡ΠΈΠΈ Xdebug)
opcache.jit=on
opcache.jit_buffer_size=100M

πŸš€ Быстрый старт

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ простого DTO

<?php

namespace App\DTOs;

use Betstore\DTO\DTO;

class UserDTO extends DTO
{
    public string $name;
    public int $age;
    public ?string $email;
    public bool $isActive;
    public array $preferences;
}

ИспользованиС

// БозданиС из массива
$userData = [
    'name' => 'John Doe',
    'age' => 30,
    'email' => 'john@example.com',
    'isActive' => true,
    'preferences' => ['theme' => 'dark', 'notifications' => true]
];

$userDTO = new UserDTO($userData);

// АвтоматичСская сСриализация
$array = $userDTO->toArray();
$json = $userDTO->toJson();

// Laravel Response
return $userDTO->toResponse(); // Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ JsonResponse

🎯 Π Π°ΡΡˆΠΈΡ€Π΅Π½Π½Ρ‹Π΅ возмоТности

Валидация Π΄Π°Π½Π½Ρ‹Ρ…

class CreateUserDTO extends DTO
{
    public string $name;
    public int $age;
    public string $email;

    protected function rules(): array
    {
        return [
            'name' => 'required|string|min:2|max:100',
            'age' => 'required|integer|min:18|max:120',
            'email' => 'required|email|unique:users,email',
        ];
    }

    protected function messages(): array
    {
        return [
            'name.required' => 'Имя ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ для заполнСния',
            'age.min' => 'Возраст Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Π½Π΅ ΠΌΠ΅Π½Π΅Π΅ :min Π»Π΅Ρ‚',
            'email.email' => 'НСкоррСктный Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ email',
        ];
    }
}

// АвтоматичСская валидация ΠΏΡ€ΠΈ создании
try {
    $userDTO = new CreateUserDTO($request->all());
} catch (ValidationException $e) {
    return response()->json(['errors' => $e->errors()], 422);
}

Type Casting

class OrderDTO extends DTO
{
    public string $orderNumber;
    public UserDTO $customer;
    public Collection $items;
    public array $shippingAddresses;

    protected function casts(): array
    {
        return [
            // ΠŸΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ Π² ΠΎΠ±ΡŠΠ΅ΠΊΡ‚
            'customer' => [null, UserDTO::class],

            // ΠŸΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ Π² ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΡŽ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ²
            'items' => ['collection', OrderItemDTO::class],

            // ΠŸΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ Π² массив ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ²
            'shippingAddresses' => ['array', AddressDTO::class],
        ];
    }
}

Union Types ΠΈ Nullable Types

class ProductDTO extends DTO
{
    public string $name;
    public string|int $sku; // Union type
    public ?string $description; // Nullable
    public ?CategoryDTO $category; // Nullable object
    public array $tags;
    public float|bool $price; // Union type с Ρ€Π°Π·Π½Ρ‹ΠΌΠΈ Ρ‚ΠΈΠΏΠ°ΠΌΠΈ
}

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΈΠ· ΠΌΠΎΠ΄Π΅Π»ΠΈ Eloquent

class User extends Model
{
    protected $fillable = ['name', 'email', 'age'];
}

// АвтоматичСскоС ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ snake_case -> camelCase
$user = User::find(1);
$userDTO = UserDTO::fromModel($user);

НаслСдованиС DTO классов

class BaseUserDTO extends DTO
{
    public string $name;
    public int $age;

    protected function rules(): array
    {
        return [
            'name' => 'required|string|min:2',
            'age' => 'required|integer|min:18',
        ];
    }
}

class ExtendedUserDTO extends BaseUserDTO
{
    public string $email;
    public ?string $phone;

    protected function rules(): array
    {
        return array_merge(parent::rules(), [
            'email' => 'required|email',
            'phone' => 'nullable|string|min:10',
        ]);
    }
}

// ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ класс ΠΈΠΌΠ΅Π΅Ρ‚ свой ΠΈΠ·ΠΎΠ»ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ кэш Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ
$baseUser = new BaseUserDTO(['name' => 'John', 'age' => 25]);
$extendedUser = new ExtendedUserDTO([
    'name' => 'Jane',
    'age' => 30,
    'email' => 'jane@example.com',
    'phone' => '+1234567890'
]);

Π€Π°Π±Ρ€ΠΈΡ‡Π½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹

// Из массива
$dto = UserDTO::fromArray($data);

// Из Eloquent модСли
$dto = UserDTO::fromModel($userModel);

// Из ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ
$dtos = $userModels->map(fn($model) => UserDTO::fromModel($model));

πŸ”§ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ

Настройка casts()

protected function casts(): array
{
    return [
        // ΠŸΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ Π² ΠΎΠ±ΡŠΠ΅ΠΊΡ‚
        'user' => [null, UserDTO::class],

        // ΠŸΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ Π² массив ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ²
        'comments' => ['array', CommentDTO::class],

        // ΠŸΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ Π² ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΡŽ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ²
        'posts' => ['collection', PostDTO::class],
    ];
}

БСриализация с Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΠ΅ΠΉ

// Π˜ΡΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ null значСния
$array = $dto->toArray(true);

// ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹Π΅ поля
$publicData = $dto->toArray();
unset($publicData['password'], $publicData['secretKey']);

πŸ§ͺ ВСстированиС

# Запуск всСх тСстов
composer test

# Π‘ Π·Π°ΠΌΠ΅Ρ€Π°ΠΌΠΈ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ
php vendor/bin/phpunit tests/ --verbose

# ВСсты ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΉ
php vendor/bin/phpunit tests/PerformanceOptimizationTest.php

Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° тСстов

tests/
β”œβ”€β”€ DTOTest.php              # ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ тСсты Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ
β”œβ”€β”€ TypecastingDTOTest.php   # ВСсты прСобразования Ρ‚ΠΈΠΏΠΎΠ²
β”œβ”€β”€ ValidationDTOTest.php    # ВСсты Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ
β”œβ”€β”€ FactoryMethodsDTOTest.php # ВСсты Ρ„Π°Π±Ρ€ΠΈΡ‡Π½Ρ‹Ρ… ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ²
β”œβ”€β”€ SerializationDTOTest.php  # ВСсты сСриализации
β”œβ”€β”€ PerformanceOptimizationTest.php # ВСсты ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ

πŸš€ ΠŸΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ

ΠžΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΈ

  1. ΠšΡΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Reflection Π΄Π°Π½Π½Ρ‹Ρ… - статичСский кэш ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Ρ… классов
  2. ΠžΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΡ поиска свойств - O(1) доступ вмСсто Π»ΠΈΠ½Π΅ΠΉΠ½ΠΎΠ³ΠΎ поиска
  3. Π˜Π·ΠΎΠ»ΠΈΡ€ΠΎΠ²Π°Π½Π½ΠΎΠ΅ ΠΊΡΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ - ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΉ кэш для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ DTO класса
  4. JIT-компиляция - Π΄ΠΎ 100x ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΡ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ

Π’Π°ΠΆΠ½Ρ‹Π΅ исправлСния

  • πŸ”§ Π˜ΡΠΏΡ€Π°Π²Π»Π΅Π½Π° критичСская ошибка ΠΊΡΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΡ Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ - ΠΏΡ€Π°Π²ΠΈΠ»Π° Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ Ρ‚Π΅ΠΏΠ΅Ρ€ΡŒ ΠΈΠ·ΠΎΠ»ΠΈΡ€ΠΎΠ²Π°Π½Ρ‹ для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ DTO класса
  • πŸ‘¨β€πŸ‘¦ ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° наслСдования - Π΄ΠΎΡ‡Π΅Ρ€Π½ΠΈΠ΅ DTO классы ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎ Π½Π°ΡΠ»Π΅Π΄ΡƒΡŽΡ‚ ΠΈ Ρ€Π°ΡΡˆΠΈΡ€ΡΡŽΡ‚ ΠΏΡ€Π°Π²ΠΈΠ»Π° Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ
  • ⚑ Π£Π»ΡƒΡ‡ΡˆΠ΅Π½Π° ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° union types - Π±ΠΎΠ»Π΅Π΅ эффСктивная Π»ΠΎΠ³ΠΈΠΊΠ° прСобразования Ρ‚ΠΈΠΏΠΎΠ²
  • 🎯 ΠžΠΏΡ‚ΠΈΠΌΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½Π° сСриализация - ΠΊΡΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Reflection для ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² toArray/toJson

Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ тСстирования

Π‘Π΅Π· ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΉ:   ~1000 ms для комплСксных ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ
Π‘ оптимизациями:    ~100 ms (10x ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΠ΅)
Π‘ OPcache:          ~90 ms (11x ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΠ΅)
Π‘ JIT:              ~9 ms (111x ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΠ΅)

Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ†ΠΈΠΈ ΠΏΠΎ ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΈ

  • Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅ OPcache для ΠΊΡΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΡ Π±Π°ΠΉΡ‚-ΠΊΠΎΠ΄Π°
  • Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ JIT Π² production для максимальной ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ
  • ΠžΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅ Xdebug Π² production (JIT ΠΊΠΎΠ½Ρ„Π»ΠΈΠΊΡ‚ΡƒΠ΅Ρ‚ с Xdebug)
  • ΠœΠΎΠ½ΠΈΡ‚ΠΎΡ€ΡŒΡ‚Π΅ hit rate OPcache (>95% ΠΆΠ΅Π»Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ)

πŸ“š API Reference

ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹

// ΠšΠΎΠ½ΡΡ‚Ρ€ΡƒΠΊΡ‚ΠΎΡ€ с автоматичСской Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠ΅ΠΉ ΠΈ ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ
new DTOClass(array $data)

// БСриализация
$dto->toArray(bool $unsetNulls = false): array
$dto->toJson(int $options = 0): string
$dto->toResponse(Request $request = null, int $status = 200): JsonResponse

// Π€Π°Π±Ρ€ΠΈΡ‡Π½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹
DTOClass::fromArray(array $data): static
DTOClass::fromModel(Model $model): static

Π—Π°Ρ‰ΠΈΡ‰Π΅Π½Π½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ для пСрСопрСдСлСния

protected function rules(): array // ΠŸΡ€Π°Π²ΠΈΠ»Π° Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ
protected function messages(): array // БообщСния ΠΎΠ± ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ…
protected function casts(): array // ΠŸΡ€Π°Π²ΠΈΠ»Π° прСобразования Ρ‚ΠΈΠΏΠΎΠ²

🀝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

  • Laravel Framework Π·Π° Π²Π΄ΠΎΡ…Π½ΠΎΠ²Π΅Π½ΠΈΠ΅
  • PHP JIT компиляция Π·Π° Π½Π΅Π²Π΅Ρ€ΠΎΡΡ‚Π½ΡƒΡŽ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ
  • Open source сообщСство Π·Π° Π²ΠΊΠ»Π°Π΄ Π² Ρ€Π°Π·Π²ΠΈΡ‚ΠΈΠ΅ PHP

Π‘ΠΎΠ·Π΄Π°Π½ΠΎ с ❀️ для Π²Ρ‹ΡΠΎΠΊΠΎΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Laravel ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ

πŸ“§ Email β€’ 🌐 Website β€’ πŸ› Issues