Skip to content

grnsv/lcodegen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4,746 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

lcodegen Go Reference codecov

OpenAPI v3 Code Generator for Laravel.

Generates routes, controllers, form requests, response classes, and DTOs from an OpenAPI v3 specification. Built as a fork of ogen-go/ogen — reuses the battle-tested OpenAPI parsing and IR pipeline, replacing the output layer with Laravel/PHP templates.

Install

go install github.com/grnsv/lcodegen/cmd/lcodegen@latest

Or via the l-codegen Composer package:

composer require --dev grnsv/l-codegen
php artisan l-codegen:install

Usage

Run from your Laravel project root:

vendor/bin/lcodegen openapi.yml

Use --target to specify the Laravel project directory:

vendor/bin/lcodegen --target ./my-laravel-app openapi.yml

Features

  • Full Laravel integration — generates idiomatic Laravel code: routes, controllers, Form Requests, Responsable responses, DTOs
  • Two-class pattern — base classes (always regenerated) + user classes (created once, never overwritten), so re-generation doesn't destroy your code
  • Validation from spec — OpenAPI constraints (required, minLength, maxLength, pattern, minimum, maximum, enum, format) become Laravel validation rules automatically
  • Typed DTOsreadonly classes with JsonSerializable, fromArray() factory, proper optional field handling
  • Optional wrappersOptString, OptInt, etc. to distinguish "not set" from null
  • Operation grouping — operations grouped into controllers via x-ogen-operation-group or path-based inference
  • OpenAPI extensionsx-ogen-name, x-ogen-operation-group, x-ogen-properties for fine-grained control

Generated output

Given the Petstore Expanded spec, lcodegen produces:

routes/openapi.php                                 # API routes
app/Http/Controllers/OpenApi/PetController.php      # Base controller (abstract)
app/Http/Controllers/PetController.php              # User controller (your code here)
app/Http/Requests/OpenApi/AddPetRequest.php         # Base form request with validation
app/Http/Requests/AddPetRequest.php                 # User form request
app/Http/Responses/OpenApi/AddPetResponse.php       # Response class
app/Http/Responses/OpenApi/ErrorResponse.php        # Error response
app/Http/Dto/OpenApi/Pet.php                        # DTO
app/Http/Dto/OpenApi/NewPet.php                     # DTO
app/Http/Dto/OpenApi/OptString.php                  # Optional wrapper
...

Routes

<?php
// Code generated by lcodegen, DO NOT EDIT.

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PetController;

// Pet routes
Route::post('/pets', [PetController::class, 'addPet'])
    ->name('pet.add_pet');
Route::delete('/pets/{id}', [PetController::class, 'deletePet'])
    ->name('pet.delete_pet');
Route::get('/pets/{id}', [PetController::class, 'findPetById'])
    ->name('pet.find_pet_by_id');
Route::get('/pets', [PetController::class, 'findPets'])
    ->name('pet.find_pets');

Base controller

<?php
// Code generated by lcodegen, DO NOT EDIT.

namespace App\Http\Controllers\OpenApi;

use App\Http\Controllers\Controller;
use App\Http\Requests\AddPetRequest;
use App\Http\Requests\FindPetsRequest;
use App\Http\Responses\OpenApi\AddPetResponse;
use App\Http\Responses\OpenApi\DeletePetResponse;
use App\Http\Responses\OpenApi\FindPetByIdResponse;
use App\Http\Responses\OpenApi\FindPetsResponse;
use App\Http\Responses\OpenApi\ErrorResponse;

abstract class PetController extends Controller
{
    /** POST /pets — Creates a new pet in the store. */
    abstract public function addPet(
        AddPetRequest $request,
    ): AddPetResponse|ErrorResponse;

    /** DELETE /pets/{id} — Deletes a single pet based on the ID supplied. */
    abstract public function deletePet(
        int $id,
    ): DeletePetResponse|ErrorResponse;

    /** GET /pets/{id} */
    abstract public function findPetById(
        int $id,
    ): FindPetByIdResponse|ErrorResponse;

    /** GET /pets — Returns all pets from the system. */
    abstract public function findPets(
        FindPetsRequest $request,
    ): FindPetsResponse|ErrorResponse;
}

User controller

Created once, never overwritten — this is where your business logic goes:

<?php
// This file can be edited. It will not be overwritten by the generator.

namespace App\Http\Controllers;

use App\Http\Controllers\OpenApi\PetController as BasePetController;
use App\Http\Requests\AddPetRequest;
use App\Http\Responses\OpenApi\AddPetResponse;
use App\Http\Responses\OpenApi\ErrorResponse;

final class PetController extends BasePetController
{
    public function addPet(
        AddPetRequest $request,
    ): AddPetResponse|ErrorResponse
    {
        // TODO: Implement AddPet
        throw new \BadMethodCallException('Not implemented');
    }

    // ...
}

Form request with validation

<?php
// Code generated by lcodegen, DO NOT EDIT.

namespace App\Http\Requests\OpenApi;

use App\Http\Dto\OpenApi\NewPet;
use Illuminate\Foundation\Http\FormRequest;

abstract class AddPetRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name' => ['required', 'string'],
            'tag' => ['sometimes', 'string'],
        ];
    }

    public function validated($key = null, $default = null): mixed
    {
        $validated = parent::validated($key, $default);
        if ($key === null) {
            return NewPet::fromArray($validated);
        }

        return $validated;
    }
}

DTO

<?php
// Code generated by lcodegen, DO NOT EDIT.

namespace App\Http\Dto\OpenApi;

use JsonSerializable;

final readonly class Pet implements JsonSerializable
{
    public function __construct(
        public string $name,
        public OptString $tag,
        public int $id,
    ) {}

    public function jsonSerialize(): array
    {
        $result = [];
        $result['name'] = $this->name;
        if ($this->tag->set) {
            $result['tag'] = $this->tag->value;
        }
        $result['id'] = $this->id;

        return $result;
    }

    public static function fromArray(array $data): self
    {
        return new self(
            name: $data['name'],
            tag: \array_key_exists('tag', $data)
                ? OptString::some($data['tag'])
                : OptString::none(),
            id: $data['id'],
        );
    }
}

Response

<?php
// Code generated by lcodegen, DO NOT EDIT.

namespace App\Http\Responses\OpenApi;

use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\JsonResponse;
use App\Http\Dto\OpenApi as Dto;

final readonly class AddPetResponse implements Responsable
{
    private const HTTP_STATUS = 200;

    public function __construct(
        private Dto\Pet $data,
    ) {}

    public function toResponse($request): JsonResponse
    {
        return response()->json($this->data, self::HTTP_STATUS);
    }
}

Optional wrapper

<?php
// Code generated by lcodegen, DO NOT EDIT.

namespace App\Http\Dto\OpenApi;

final readonly class OptString
{
    public function __construct(
        public ?string $value,
        public bool $set,
    ) {}

    public static function none(): self
    {
        return new self(null, false);
    }

    public static function some(string $value): self
    {
        return new self($value, true);
    }
}

Two-class pattern

The generator separates generated code from user code:

Layer Base class (OpenApi/ subdir) User class (parent dir)
Controllers Abstract, defines method signatures final, extends base, contains your logic
Form Requests Validation rules from spec, validated() returns typed DTO Authorization, custom rules

Base classes are always regenerated from the spec. User classes are created once and never overwritten — safe to edit.

Configuration

Config file (ogen.yml, ogen.yaml, .ogen.yml, .ogen.yaml):

generator:
  ignore_not_implemented: ["all"]
parser:
  infer_types: true
  allow_remote: true

OpenAPI extensions

Extension Scope Description
x-ogen-operation-group path / operation Group operations into controllers
x-ogen-name schema Custom type name
x-ogen-properties schema Custom field names
x-ogen-server-name server Custom server name

Example — grouping operations into controllers:

paths:
  /pets:
    x-ogen-operation-group: Pet
    get:
      operationId: findPets
    post:
      operationId: addPet
  /users:
    x-ogen-operation-group: User
    get:
      operationId: listUsers

This generates PetController and UserController instead of a single controller.

Architecture

OpenAPI YAML/JSON
  → ogen.Parse()           # Parse & validate spec, resolve $ref
  → gen.NewGenerator()     # Build IR
  → g.WriteSource()        # Render PHP via Go text/template
  → Laravel PHP files

The intermediate representation drives code generation. PHP/Laravel specifics live in templates and supporting generator code.

License

Apache-2.0