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.
- 1. Project Overview
- 2. Key Features
- 3. Modern PHP 8.2+ & Clean Architecture Standards
- 4. Architecture Overview
- 5. API Specification Drivers
- 6. Generated Structure & Code Examples
- 7. Installation
- 8. Configuration
- 9. Available Artisan Commands
- 10. Extensibility & Custom Drivers
- 11. Best Practices
- 12. License
ixspx/module-generator is a dual-purpose Laravel package:
- 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,finalclasses). - 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.
- 🏗 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 readonlyproperties, typed attributes, andfinalclasses. - 🔌 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.phporconfig/app.phpand appends RESTful routes toroutes/api.php. - 🎨 Publishable Stubs & Configuration: Fully customizable code templates via
php artisan vendor:publish --tag=module-generator-stubsandmodule-generator-config. - 🔒 Centralized Exception Handling: Specification-aware exception mapping for database, validation, auth, and domain exceptions.
All scaffolded code generated by this package enforces strict modern PHP and Clean Architecture 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.
declare(strict_types=1)at the top of every generated file.finalclass declaration: Controllers, Services, Repositories, Providers, and Middleware arefinalby default to prevent accidental inheritance coupling.- Typed Properties & Explicit Return Types: All properties and methods specify explicit types (e.g.
LengthAwarePaginator,JsonResponse,Model,: void).
- 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).
┌────────────────────────────┐
│ config/module-generator │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ ApiSpecificationFactory │
└─────────────┬──────────────┘
│
┌─────────────────────────┼─────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
│ RestApiSpecification │ │ JsonApiSpecification │ │ProblemDetailsSpecifi… │
└───────────┬───────────┘ └───────────┬───────────┘ └───────────┬───────────┘
│ │ │
│ application/json │ application/vnd.api+json│ application/problem+json
▼ ▼ ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ Cross-Cutting Services: ApiResponse / ForceJsonResponse / Exception │
└───────────────────────────────────────────────────────────────────────────┘
- Media Type:
application/json
{
"success": true,
"responseCode": 200,
"message": "Data retrieved successfully",
"data": { "id": 1, "name": "John Doe" },
"meta": { "count": 1 }
}- Media Type:
application/vnd.api+json
{
"jsonapi": { "version": "1.1" },
"data": {
"type": "resources",
"id": "1",
"attributes": { "name": "John Doe" }
}
}- Media Type:
application/problem+jsonfor 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." }
]
}Running php artisan make:mod User generates the following clean, modern PHP 8.2+ structure:
<?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);
}
}<?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);
}
}<?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);
}
}<?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
];
}composer require ixspx/module-generatorPublishing 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-stubsreturn [
/*
|--------------------------------------------------------------------------
| 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,
];| 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 |
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).
ixspx/module-generator supports two distinct controller action naming conventions:
- RESTful Style (Default): Standard Laravel resource action names (
index,show,store,update,destroy) mapped viaRoute::apiResource(). - Handler Style: Explicit handler method names (
handlerGetAll,handlerGetById,handlerCreate,handlerUpdate,handlerDelete) mapped via explicitRoute::controller()->group(). This is particularly intuitive for developers coming from languages like Go, Express/TypeScript, Java, or C#.
To generate a specific module using Handler style:
php artisan make:mod User --style=handlerScaffolded 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');
});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.
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();
});
}- Keep Controllers Thin: Controllers delegate formatting to
ApiResponse::success(), which formats payloads according to the configured driver. - Use Interface Binding: Inject
{Name}Interfaceinto Services to adhere to Dependency Inversion. - Centralize Exception Mapping: Throw domain exceptions inside services;
ApiExceptionRegistrarconverts them to the active specification driver format.
Licensed under the MIT License. See LICENSE for details.