ixspx/module-generator

Generate module structure in Laravel

Maintainers

Package info

github.com/saul-paulus/module-generator

pkg:composer/ixspx/module-generator

Transparency log

Statistics

Installs: 331

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 1

v1.1.0 2026-08-03 16:34 UTC

This package is auto-updated.

Last update: 2026-08-04 16:52:09 UTC


README

Latest Version on Packagist PHP Version Laravel Framework License

ixspx/module-generator is an enterprise-grade Laravel developer tooling package designed to accelerate development by scaffolding cleanly layered application modules (Model, Repository Interface, Repository Implementation, Service, Controller, and Service Provider) following PHP 8.2+, Laravel 12, PSR-12, and Clean Architecture standards while establishing a driver-driven, multi-specification REST API foundation supporting Standard REST, JSON:API 1.1, RFC 7807 Problem Details, and custom API drivers.

Table of Contents

1. Project Overview

What is this package?

ixspx/module-generator is a dual-purpose Laravel package:

  1. Modern Module Scaffolder (make:mod): Scaffolds modular domain layers adhering strictly to Clean Architecture, SOLID principles, and Service-Repository patterns using modern PHP 8.2+ features (Constructor Property Promotion, readonly, typed properties, strict_types=1, final classes).
  2. Multi-Specification API Starter (make:api-install & make:api-response): Provisions a specification-aware API response engine, middleware to enforce JSON/JSON:API headers, and a centralized exception registrar. Switch between Standard REST, JSON:API 1.1, or Problem Details instantly via configuration.

2. Key Features

  • πŸ— Modern Full-Stack Module Generator: Scaffolds Model, Interface, Concrete Repository, Service, Controller, and Service Provider via php artisan make:mod {Name}.
  • ⚑ PHP 8.2+ & Laravel 12 Native: Built with declare(strict_types=1), Constructor Property Promotion, private readonly properties, typed attributes, and final classes.
  • πŸ”Œ Driver-Driven API Architecture: Switch between Standard REST, JSON:API 1.1, and RFC 7807 Problem Details simply by changing config('module-generator.api_specification') or .env.
  • βš™οΈ Auto-Registration of Providers & Routes: Automatically registers scaffolded providers in bootstrap/providers.php or config/app.php and appends RESTful routes to routes/api.php.
  • 🎨 Publishable Stubs & Configuration: Fully customizable code templates via php artisan vendor:publish --tag=module-generator-stubs and module-generator-config.
  • πŸ”’ Centralized Exception Handling: Specification-aware exception mapping for database, validation, auth, and domain exceptions.

3. Modern PHP 8.2+ & Clean Architecture Standards

All scaffolded code generated by this package enforces strict modern PHP and Clean Architecture rules:

A. Constructor Property Promotion & Visibility Rules

  • private readonly: Used for all internal class dependencies injected via constructor (Services, Repositories, Models, Filesystem).
  • protected: Reserved strictly for inheritance hooks or Eloquent internal properties ($table, $fillable).
  • public: Reserved exclusively for explicit class API methods.

B. Modern PHP Features Enforced

  • declare(strict_types=1) at the top of every generated file.
  • final class declaration: Controllers, Services, Repositories, Providers, and Middleware are final by default to prevent accidental inheritance coupling.
  • Typed Properties & Explicit Return Types: All properties and methods specify explicit types (e.g. LengthAwarePaginator, JsonResponse, Model, : void).

C. Clean Architecture Principles

  • Dependency Inversion: Services depend on Repository Interfaces ({Name}Interface), never on concrete Eloquent repositories or models directly.
  • No Service Locators in Application Code: Dependencies are injected via Constructor DI.
  • Single Responsibility: Clean separation between Delivery (Controller), Business Orchestration (Service), Data Access (Repository), and Persistence (Model).

4. Architecture Overview

                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚   config/module-generator  β”‚
                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
                                      β–Ό
                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚  ApiSpecificationFactory   β”‚
                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚                         β”‚                         β”‚
            β–Ό                         β–Ό                         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ RestApiSpecification  β”‚ β”‚ JsonApiSpecification  β”‚ β”‚ProblemDetailsSpecifi… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
            β”‚                         β”‚                         β”‚
            β”‚  application/json       β”‚ application/vnd.api+jsonβ”‚ application/problem+json
            β–Ό                         β–Ό                         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Cross-Cutting Services: ApiResponse / ForceJsonResponse / Exception     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

5. API Specification Drivers

A. Standard REST API (Default)

  • Media Type: application/json
{
  "success": true,
  "responseCode": 200,
  "message": "Data retrieved successfully",
  "data": { "id": 1, "name": "John Doe" },
  "meta": { "count": 1 }
}

B. JSON:API 1.1 Specification (API_SPECIFICATION=jsonapi)

  • Media Type: application/vnd.api+json
{
  "jsonapi": { "version": "1.1" },
  "data": {
    "type": "resources",
    "id": "1",
    "attributes": { "name": "John Doe" }
  }
}

C. RFC 7807 Problem Details (API_SPECIFICATION=problem-details)

  • Media Type: application/problem+json for error responses
{
  "type": "http://localhost/errors/validation-error",
  "title": "Validation error",
  "status": 422,
  "detail": "The email field is required.",
  "instance": "http://localhost/api/v1/users",
  "invalid-params": [
    { "name": "email", "reason": "The email field is required." }
  ]
}

6. Generated Structure & Code Examples

Running php artisan make:mod User generates the following clean, modern PHP 8.2+ structure:

1. Controller (App\Http\Controllers\User\UserController.php)

<?php

declare(strict_types=1);

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;
use App\Services\User\UserService;
use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

final class UserController extends Controller
{
    public function __construct(
        private readonly UserService $userService
    ) {}

    public function index(): JsonResponse
    {
        $data = $this->userService->getAll();
        $meta = ['count' => $data->count()];
        return ApiResponse::success($data, 'Data retrieved successfully', 200, $meta);
    }
}

2. Business Service (App\Services\User\UserService.php)

<?php

declare(strict_types=1);

namespace App\Services\User;

use App\Repositories\Interfaces\User\UserInterface;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

final class UserService
{
    public function __construct(
        private readonly UserInterface $repository
    ) {}

    public function getAll(): Collection
    {
        return $this->repository->getAll();
    }

    public function getById(int $id): Model
    {
        return $this->repository->findOrFail($id);
    }

    public function create(array $data): Model
    {
        return DB::transaction(function () use ($data) {
            return $this->repository->create($data);
        });
    }

    public function paginate(int $perPage = 15): LengthAwarePaginator
    {
        return $this->repository->paginate($perPage);
    }
}

3. Repository (App\Repositories\Repository\User\UserRepository.php)

<?php

declare(strict_types=1);

namespace App\Repositories\Repository\User;

use App\Models\User\UserModel;
use App\Repositories\Interfaces\User\UserInterface;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;

final class UserRepository implements UserInterface
{
    public function __construct(
        private readonly UserModel $model
    ) {}

    public function getAll(): Collection
    {
        return $this->model->all();
    }

    public function findOrFail(int $id): Model
    {
        return $this->model->findOrFail($id);
    }

    public function paginate(int $perPage = 15): LengthAwarePaginator
    {
        return $this->model->paginate($perPage);
    }
}

4. Eloquent Model (App\Models\User\UserModel.php)

<?php

declare(strict_types=1);

namespace App\Models\User;

use Illuminate\Database\Eloquent\Model;

class UserModel extends Model
{
    protected string $table = 'users';

    /** @var list<string> */
    protected array $fillable = [
        // Add your fillable fields here
    ];
}

7. Installation

composer require ixspx/module-generator

Publish Configuration & Stubs (Optional)

Publishing the configuration file and stubs is completely optional. The package functions zero-config out of the box with sensible defaults (Standard REST driver, auto-registration enabled).

# Publish configuration file to config/module-generator.php (Optional)
php artisan vendor:publish --tag=module-generator-config

# Publish template stubs to stubs/module-generator/ (Optional)
php artisan vendor:publish --tag=module-generator-stubs

8. Configuration

return [
    /*
    |--------------------------------------------------------------------------
    | Default API Specification Driver
    |--------------------------------------------------------------------------
    | Supported Drivers: 'rest', 'jsonapi', 'problem-details', or custom class
    */
    'api_specification' => env('API_SPECIFICATION', 'rest'),

    'jsonapi' => [
        'version'  => '1.1',
        'base_url' => env('APP_URL', 'http://localhost'),
    ],

    'problem_details' => [
        'type_base_url' => env('APP_URL', 'http://localhost') . '/errors',
    ],

    'table_prefix' => '',
    'controller_style' => 'restful', // 'restful' or 'handler'
    'auto_register_provider' => true,
    'auto_register_route'    => true,
];

9. Available Artisan Commands

Command Signature Description Key Options Example Usage
Module Generator make:mod {name} Scaffolds complete 6-layer PHP 8.2+ module structure. --table=, --table-prefix=, --style=, --no-provider, --no-route, --force php artisan make:mod OrderPayment
API Installer make:api-install Installs standard API foundation infrastructure. --force php artisan make:api-install --force
API Response Helper make:api-response Scaffolds ApiResponse support class. None php artisan make:api-response

βš™οΈ Setting Up API Route Prefixing (api/v1) & Exception Handling in Laravel 11 & 12

After running php artisan make:api-install, configure both the API route prefixing and the centralized ApiExceptionRegistrar inside your application's bootstrap/app.php to ensure all API exceptions are returned in formatted JSON:

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
        then: function ($router) {
            Route::prefix('api/v1')
                ->group(base_path('routes/api.php'));
        },
    )
    ->withMiddleware(function (Middleware $middleware) {
        // ...
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        $exceptions->shouldRenderJsonWhen(
            fn(Request $request) => $request->is('api/*'),
        );
        \App\Exceptions\ApiExceptionRegistrar::register($exceptions);
    })->create();

Tip

Why ApiExceptionRegistrar is required: Without registering ApiExceptionRegistrar, unhandled API exceptions (e.g. 404 Not Found, 422 Validation Error, 500 Internal Error) will fall back to default unformatted HTML or generic Laravel exception pages. Calling \App\Exceptions\ApiExceptionRegistrar::register($exceptions) ensures all exceptions on api/* routes are consistently formatted according to your active API specification driver (rest, jsonapi, or problem-details).

πŸ”€ Controller Action Naming Styles (RESTful vs. Handler Style)

ixspx/module-generator supports two distinct controller action naming conventions:

  1. RESTful Style (Default): Standard Laravel resource action names (index, show, store, update, destroy) mapped via Route::apiResource().
  2. Handler Style: Explicit handler method names (handlerGetAll, handlerGetById, handlerCreate, handlerUpdate, handlerDelete) mapped via explicit Route::controller()->group(). This is particularly intuitive for developers coming from languages like Go, Express/TypeScript, Java, or C#.

Option A: Per-Module Generation via --style=handler

To generate a specific module using Handler style:

php artisan make:mod User --style=handler

Scaffolded Controller (app/Http/Controllers/User/UserController.php):

final class UserController extends Controller
{
    public function handlerGetAll(): JsonResponse { ... }
    public function handlerGetById(int $id): JsonResponse { ... }
    public function handlerCreate(Request $request): JsonResponse { ... }
    public function handlerUpdate(Request $request, int $id): JsonResponse { ... }
    public function handlerDelete(int $id): JsonResponse { ... }
}

Scaffolded Route (routes/api.php):

Route::controller(\App\Http\Controllers\User\UserController::class)->group(function () {
    Route::get('/users', 'handlerGetAll');
    Route::get('/users/{id}', 'handlerGetById');
    Route::post('/users', 'handlerCreate');
    Route::put('/users/{id}', 'handlerUpdate');
    Route::delete('/users/{id}', 'handlerDelete');
});

Option B: Application-Wide Default Configuration

To set Handler style as the default for all scaffolded modules across your project, set controller_style in config/module-generator.php:

return [
    'controller_style' => env('MODULE_CONTROLLER_STYLE', 'handler'),
];

Now, running php artisan make:mod User will automatically scaffold using Handler style.

10. Extensibility & Custom Drivers

Extend the factory with custom API drivers in your AppServiceProvider:

use Ixspx\ModuleGenerator\Contracts\ApiSpecificationInterface;
use Ixspx\ModuleGenerator\Factories\ApiSpecificationFactory;

public function boot(ApiSpecificationFactory $factory): void
{
    $factory->extend('company-api', function ($app) {
        return new CustomCompanyApiSpecification();
    });
}

11. Best Practices

  1. Keep Controllers Thin: Controllers delegate formatting to ApiResponse::success(), which formats payloads according to the configured driver.
  2. Use Interface Binding: Inject {Name}Interface into Services to adhere to Dependency Inversion.
  3. Centralize Exception Mapping: Throw domain exceptions inside services; ApiExceptionRegistrar converts them to the active specification driver format.

12. License

Licensed under the MIT License. See LICENSE for details.