php-serialize is a powerful attribute-based serialization library for PHP (requires PHP ≥ 8.1).
It allows you to map objects to arrays/JSON and automatically generate OpenAPI documentation based on the same attributes.
🚀 Unified solution for API data serialization and documentation generation.
- 🏷️ Property aliasing with
- 🔄 Automatic type casting (e.g. DateTime ↔ string)
- 🔁 Deep object nesting support
- ❌ Skip/exclude fields with
- 🧩 Recursive DTO serialization
- 🧬 Auto-generate OpenAPI schema using object definitions
- ⚙️ Framework-agnostic — works with Laravel, Symfony, etc.
    Run vendor/bin/phpbench run benchmarks/ --bootstrap=vendor/autoload.php
    PHPBench (1.4.1) running benchmarks... #standwithukraine
    with PHP version 8.4.8, xdebug ❌, opcache ❌
    benchObjectCreation.....................I4 ✔ Mo117.841μs (±3.09%)
    benchObjectCreationWithoutCache.........I4 ✔ Mo284.568μs (±0.71%)
    benchObjectToArray......................I4 ✔ Mo62.883μs (±0.56%)
    benchObjectToArrayWithoutCache..........I4 ✔ Mo211.700μs (±0.84%)Install using Composer:
composer require astral/php-serializeuse Astral\Serialize\Serialize;
class User extends Serialize {
    public string $name,
    public int $age
}
// Create object from array
$user = User::from([
    'name' => 'John Doe',
    'age' => 30
]);
// Access object properties
echo $user->name;  // Output: John Doe
echo $user->age;   // Output: 30
// Convert to array
$userArray = $user->toArray();
// $userArray contents:
// [
//     'name' => 'John Doe',
//     'age' => 30
// ]- Immutability: Read-only properties cannot be modified after construction
use Astral\Serialize\Serialize;
class User extends Serialize {
    public function __construct(
        public readonly string $name,
        public readonly int $age
    ) {}
}
$user = User::from([
    'name' => 'John Doe',
    'age' => 30
]);
try {
    $user->name = 'Jane Doe';  // Compile-time error: cannot modify read-only property
} catch (Error $e) {
    echo "Read-only properties cannot be reassigned";
}- Type-Safe Initialization
$user = User::from([
    'name' => 123,       // Integer will be converted to string
    'age' => '35'        // String will be converted to integer
]);
echo $user->name;  // Output: "123"
echo $user->age;   // Output: 35- Constructor Initialization
use Astral\Serialize\Serialize;
class User extends Serialize {
    public function __construct(
        public readonly string $name,
        public readonly int $age
    ) {
        // Can add additional validation or processing logic in the constructor
        if (strlen($name) < 2) {
            throw new \InvalidArgumentException('Name is too short');
        }
    }
}use Astral\Serialize\Serialize;
class UserAddRequest extends Serialize {
    public string $name;
    public int $id;
}
class UserDetailRequest extends Serialize {
    public int $id;
}use Astral\Serialize\Serialize;
class UserDto extends Serialize {
    public string $name,
    public int $id;
}use Astral\Serialize\Serialize;
use Astral\Serialize\OpenApi\Enum\MethodEnum;
#[\Astral\Serialize\OpenApi\Annotations\Tag('User Module Management')]
class UserController {
    #[\Astral\Serialize\OpenApi\Annotations\Summary('Create User')]
    #[\Astral\Serialize\OpenApi\Annotations\Route('/user/create')]
    #[\Astral\Serialize\OpenApi\Annotations\RequestBody(UserAddRequest::class)]
     #[\Astral\Serialize\OpenApi\Annotations\Response(UserDto::class)]
    public function create() 
    {
        return new UserDto(); 
    }
    
    #[\Astral\Serialize\OpenApi\Annotations\Summary('User Detail')]
    #[\Astral\Serialize\OpenApi\Annotations\Route(route:'/user/detail', method: MethodEnum::GET)]
    public function detail(UserDetailRequest $request): UserDto  
    {
        return new UserDto();
    }
}Navigate to the project root directory first:
docker run  -v $PWD/vendor/astral/php-serialize/src/OpenApi/Frankenphp/Caddyfile:/etc/frankenphp/Caddyfile -v $PWD:/app -p 8089:80 dunglas/frankenphpAccess http://127.0.0.1:8089/docs to view the documentation.
