apie/schema-generator
Generates JSON Schema components from PHP objects with type hints, tailored for Apie entities, value objects, DTOs, enums, lists, and hashmaps. Produces cebe/php-openapi schema objects, focusing on schema sections (not full OpenAPI documents).
Installation
composer require apie/schema-generator
Ensure cebe/php-openapi is also installed (dependency for schema generation):
composer require cebe/php-openapi
First Use Case Generate a schema for a simple DTO or Value Object:
use Apie\SchemaGenerator\ComponentsBuilderFactory;
$factory = ComponentsBuilderFactory::createComponentsBuilderFactory();
$schema = $factory->addCreationSchemaFor(\App\Dto\YourDto::class);
$components = $factory->getComponents()->schemas;
Where to Look First
ComponentsBuilderFactory: Entry point for schema generation.addCreationSchemaFor(): Core method to generate schemas for classes.getComponents(): Retrieve all generated schemas (useful for OpenAPI components section).Leverage DTOs to define API request/response structures with minimal boilerplate:
use Apie\Core\Attributes\Optional;
use Apie\Core\Dto\DtoInterface;
class UserDto implements DtoInterface {
public string $name;
public int $age = 30; // Default value → optional in schema
#[Optional]
public ?string $bio = null;
}
Schema Output:
UserDto-post:
required: ['name']
properties:
name: { type: string }
age: { type: integer }
bio: { type: string, nullable: true }
For Eloquent models or domain entities, use constructor args and set*/with* methods:
class User {
public function __construct(
public string $name,
public ?int $age = null
) {}
public function setEmail(string $email): self {
$this->email = $email;
return $this;
}
}
Schema Output:
User-post:
required: ['name']
properties:
name: { type: string }
age: { type: integer, nullable: true }
email: { type: string }
Handle complex types (e.g., Email, Money) with traits/interfaces:
use Apie\Core\ValueObjects\Interfaces\StringValueObjectInterface;
use Apie\Core\ValueObjects\IsStringValueObject;
class Email implements StringValueObjectInterface {
use IsStringValueObject;
}
Schema Output:
Email-post:
type: string
format: Email
Automatically map enums to enum schemas:
enum Status {
case ACTIVE;
case INACTIVE;
}
Schema Output:
Status-post:
type: string
enum: ['ACTIVE', 'INACTIVE']
Use #[SchemaMethod] for full control:
#[SchemaMethod('getCustomSchema')]
class Password {
public static function getCustomSchema(): array {
return [
'type' => 'string',
'format' => 'password',
'minLength' => 8,
];
}
}
Combine with cebe/php-openapi for full API docs:
use OpenApi\Generator;
$generator = new Generator();
$generator->addSchema($factory->getComponents()->schemas);
$openapi = $generator->generate();
Generate schemas for multiple classes at once:
$factory = ComponentsBuilderFactory::createComponentsBuilderFactory();
$factory->addCreationSchemaFor(UserDto::class);
$factory->addCreationSchemaFor(OrderDto::class);
$components = $factory->getComponents()->schemas;
Circular References
User has Address, Address has User), the generator may fail or produce incomplete schemas.$ref sparingly or restructure your models to avoid cycles.Unsupported Types
array without generics) may not generate valid schemas.#[SchemaMethod] or ensure types are explicitly hinted (e.g., array<string>).Backed Enums
enum Priority {
case HIGH = 'high_priority';
case LOW = 'low_priority';
}
Schema Output:
Priority-post:
type: string
enum: ['high_priority', 'low_priority']
Default Values in DTOs
int $age = 30) make fields optional in the schema, even if they’re required logically.#[Optional] explicitly if you want to enforce this behavior.Composite Value Objects
CompositeValueObject trait maps to objects, but nested properties may not resolve correctly if they’re not properly typed.Inspect Generated Schemas
Use dd($factory->getComponents()->schemas) to debug intermediate outputs.
Validate with OpenAPI Tools Use tools like Swagger Editor to validate generated schemas.
Check for Missing References
If a schema references a non-existent component (e.g., $ref: '#/components/schemas/NonExistent'), verify all classes are processed in the same ComponentsBuilderFactory instance.
Custom Schema Methods
Override schema generation for specific classes using #[SchemaMethod].
Post-Processing
Modify schemas after generation using cebe/php-openapi's Schema class:
$schema = $factory->addCreationSchemaFor(UserDto::class);
$schema->addEnum(['active', 'inactive']); // Extend dynamically
Integration with Laravel
$this->app->singleton(ComponentsBuilderFactory::class, fn() => ComponentsBuilderFactory::createComponentsBuilderFactory());
Schema::generate(UserDto::class);
Performance
ComponentsBuilderFactory instance for multiple schema generations to avoid reprocessing.Testing Test schema generation with PHPUnit:
public function testSchemaGeneration() {
$factory = ComponentsBuilderFactory::createComponentsBuilderFactory();
$schema = $factory->addCreationSchemaFor(UserDto::class);
$this->assertArrayHasKey('required', $schema);
$this->assertContains('name', $schema['required']);
}
How can I help you explore Laravel packages today?