cebpereira / layers
A laravel package to generate files for layered architecture
Requires
- php: ^8.2
- illuminate/console: ^9.0 || ^10.20 || ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^9.0 || ^10.20 || ^11.0 || ^12.0 || ^13.0
- symfony/finder: ^6.3 || ^7.0 || ^8.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-13 14:42:05 UTC
README
A laravel package to generate files for layered architecture and automate interface bindings.
Recommended Laravel version: ^13.0
Go to Laravel Docs to see support policy.
Summary
- Requirements
- Installation
- Configuration
- Usage
- Modular Applications
- Bindings
- Customizing Stubs
- Upgrading to 1.5
- Upgrading to 1.4
Requirements
"php": "^8.2" "symfony/finder": "^6.3 || ^7.0 || ^8.0" "illuminate/support": "^9.0 || ^10.20 || ^11.0 || ^12.0 || ^13.0" "illuminate/console": "^9.0 || ^10.20 || ^11.0 || ^12.0 || ^13.0"
Installation
composer require cebpereira/layers --dev
Configuration
php artisan vendor:publish --tag=layers
This command will copy Layers config to your project config folder
<?php return [ 'models' => [ app_path('Models'), // app_path('Modules/*/Models'), ], 'structure' => [ 'interface' => [ 'path' => 'Repositories/{subpath}', 'class' => '{model}RepositoryInterface', ], 'eloquent' => [ 'path' => 'Repositories/{subpath}', 'class' => '{model}RepositoryEloquent', ], 'service' => [ 'path' => 'Services/{subpath}', 'class' => '{model}Service', ], ], 'property_modifiers' => 'protected', 'auto_bind' => true, ];
- models : directories where your models live. Glob patterns are accepted.
- structure : folder and class name of each layer. The folder is relative to the parent of the models directory, so
app/Modelsgenerates layers insideapp, andapp/Modules/Core/Modelsgenerates layers insideapp/Modules/Core.{subpath}: the model subfolder (Authforapp/Models/Auth/Token.php){model}: the model name (Token)
- property_modifiers : modifiers of the properties promoted in generated constructors (the repository model and the service repositories), e.g.
protected,private readonlyorpublic readonly. - auto_bind : bind every repository interface to its eloquent implementation. See Bindings.
Namespaces are resolved from the PSR-4 mappings in your composer.json.
Usage
Using the layers artisan command, we can be generate files for repositories (interface and eloquent) and services.
php artisan layers + {option} + {model name}
Available options:
- -e or --eloquent : Generate a repository eloquent for the model
- -i or --interface : Generate a repository interface for the model
- -s or --service : Generate a service for the model
- -r or --repository : Generate a repository interface and eloquent for the model
- -a or --all : Generate a service, repository interface and repository eloquent for the model
- --wr : Specify the service's repositories
Subcommands
php artisan layers:repository --eloquent: the same as php artisan layers --eloquentphp artisan layers:repository --interface: the same as php artisan layers --interfacephp artisan layers:service: the same as php artisan layers --servicephp artisan layers:binds: List all repository bindingsphp artisan layers:scaffold: Scaffold repositories and services for all models
Model Names
The model is searched in the configured models directories, and can be written as:
| Name | Model |
|---|---|
User |
app/Models/User.php |
Auth/Token or Auth.Token |
app/Models/Auth/Token.php |
Core/User or Modules/Core/User |
app/Modules/Core/Models/User.php |
App\Modules\Core\Models\User |
app/Modules/Core/Models/User.php |
If a name matches more than one model, the command fails and lists the options. If the model does not exist, the files are generated where it would live and a warning is displayed.
Generate Layers
php artisan layers --all User
This command will generate 3 files:
- app/Repositories/UserRepositoryInterface.php
- app/Repositories/UserRepositoryEloquent.php
- app/Services/UserService.php
UserRepositoryInterface.php
<?php declare(strict_types=1); namespace App\Repositories; use App\Models\User; use Illuminate\Database\Eloquent\Collection; interface UserRepositoryInterface { public function __construct(User $user); /** * Store a new instance of User in the database. * * @param array<string, mixed> $data */ public function store(array $data): User; /** * Get all instances of User from the database. * * @param array<int, string>|string $columns * @param array<array-key, mixed>|null $filters * @return Collection<int, User> */ public function getList(array|string $columns = ['*'], ?array $filters = null): Collection; /** * Get the instance of User with the given id. */ public function get(int|string $id): ?User; /** * Update the data of an instance of User. * * @param array<string, mixed> $data */ public function update(array $data, int|string $id): User; /** * Remove an instance of User from the database. */ public function destroy(int|string $id): bool; }
UserRepositoryEloquent.php
<?php declare(strict_types=1); namespace App\Repositories; use App\Models\User; use Illuminate\Database\Eloquent\Collection; class UserRepositoryEloquent implements UserRepositoryInterface { public function __construct( protected User $user, ) {} /** * Store a new instance of User in the database. * * @param array<string, mixed> $data */ public function store(array $data): User { return $this->user->newQuery()->create($data); } /** * Get all instances of User from the database. * * @param array<int, string>|string $columns * @param array<array-key, mixed>|null $filters * @return Collection<int, User> */ public function getList(array|string $columns = ['*'], ?array $filters = null): Collection { $query = $this->user->newQuery(); if ($filters) { $query->where($filters); } return $query->get($columns); } /** * Get the instance of User with the given id. */ public function get(int|string $id): ?User { return $this->user->newQuery()->find($id); } /** * Update the data of an instance of User. * * @param array<string, mixed> $data */ public function update(array $data, int|string $id): User { $user = $this->user->newQuery()->findOrFail($id); $user->update($data); return $user; } /** * Remove an instance of User from the database. */ public function destroy(int|string $id): bool { return (bool) $this->user->newQuery()->findOrFail($id)->delete(); } }
UserService.php
<?php declare(strict_types=1); namespace App\Services; use App\Repositories\UserRepositoryInterface; class UserService { public function __construct( protected UserRepositoryInterface $repoUser, ) {} // Add your functions here... }
Models in Subfolders
php artisan layers --repository Auth/Token
This command will generate 2 files, mirroring the model subfolder:
- app/Repositories/Auth/TokenRepositoryInterface.php
- app/Repositories/Auth/TokenRepositoryEloquent.php
Both files import the model from its real namespace (use App\Models\Auth\Token;).
Generate Services with more than one repository
php artisan layers --service --wr=Auth/Token --wr=User Person
This command will generate the follow file:
<?php declare(strict_types=1); namespace App\Services; use App\Repositories\Auth\TokenRepositoryInterface; use App\Repositories\UserRepositoryInterface; class PersonService { public function __construct( protected TokenRepositoryInterface $repoToken, protected UserRepositoryInterface $repoUser, ) {} // Add your functions here... }
The repositories must exist before generating the service.
Scaffold Layers from Models
Instead of generating files one by one, you can scaffold repositories for all models at once:
php artisan layers:scaffold
This command scans every configured models directory and generates an Interface and Eloquent pair for every model found.
Example — given the following models:
app/Models/
├── User.php
├── Company.php
└── Auth/
└── Token.php
Running layers:scaffold generates:
app/Repositories/
├── UserRepositoryInterface.php
├── UserRepositoryEloquent.php
├── CompanyRepositoryInterface.php
├── CompanyRepositoryEloquent.php
└── Auth/
├── TokenRepositoryInterface.php
└── TokenRepositoryEloquent.php
To also generate a service for each model, use the --with-service flag:
php artisan layers:scaffold --with-service
If a file already exists, it will be skipped automatically — no files are overwritten.
Modular Applications
Layers are generated next to the models directory, so modules work without extra commands. Given this structure:
app/Modules/
├── Core/
│ └── Models/
│ └── User.php
└── Billing/
└── Models/
└── Invoice.php
And this configuration:
'models' => [ app_path('Modules/*/Models'), ], 'structure' => [ 'interface' => [ 'path' => 'Repositories/Contracts/{subpath}', 'class' => '{model}RepositoryInterface', ], 'eloquent' => [ 'path' => 'Repositories/Eloquent/{subpath}', 'class' => 'Eloquent{model}Repository', ], 'service' => [ 'path' => 'Services/{subpath}', 'class' => '{model}Service', ], ],
Running php artisan layers --repository Billing/Invoice generates:
- app/Modules/Billing/Repositories/Contracts/InvoiceRepositoryInterface.php
- app/Modules/Billing/Repositories/Eloquent/EloquentInvoiceRepository.php
Names without a model can be placed in a module by prefixing it:
php artisan layers:service Core/Report --wr=Core/User --wr=Billing/Invoice
# app/Modules/Core/Services/ReportService.php
Bindings
With auto_bind enabled, every repository interface is bound to its eloquent implementation, following the configured structure.
If you prefer to register bindings in your own service providers (e.g. one provider per module), disable it:
'auto_bind' => false,
php artisan layers:binds lists every interface/implementation pair found and whether it is registered in the container:
+---------------------------------------------------+-------------------------------------------------+------------+
| Interface | Implementation | Registered |
+---------------------------------------------------+-------------------------------------------------+------------+
| App\Repositories\Auth\TokenRepositoryInterface | App\Repositories\Auth\TokenRepositoryEloquent | yes |
| App\Repositories\UserRepositoryInterface | App\Repositories\UserRepositoryEloquent | yes |
+---------------------------------------------------+-------------------------------------------------+------------+
Customizing Stubs
php artisan vendor:publish --tag=layers-stubs
The stubs are copied to stubs/layers and used instead of the package ones.
| Stub | Placeholders |
|---|---|
RepositoryInterface.stub |
namespace, class, imports, model, modelFqcn, modelVariable |
RepositoryEloquent.stub |
namespace, class, imports, model, modelFqcn, modelVariable, interface, interfaceFqcn, propertyModifiers |
Service.stub |
namespace, class, imports, parameters, propertyModifiers |
parameters already includes the configured property_modifiers, so the visibility of service repositories is changed in the config, not in the stub. propertyModifiers is available for properties you add to your own stubs.
The default stubs pass PHPStan level 8 (with Larastan) and Pint with the Laravel preset. With private modifiers, PHPStan reports the repositories of a newly generated service as never read until the service uses them.
Upgrading to 1.5
- Generated repositories accept only
arrayinstoreandupdate. The previousSupportCollection|array|int|stringunion failed at runtime for anything but arrays, because Eloquentcreateandupdateonly accept arrays. Call->all()on collections before passing them. - Existing interfaces and implementations are not changed. Regenerate both files together, or keep the old stubs by publishing them.
- Published
RepositoryEloquent.stubfiles keep their hardcoded visibility. Replaceprotectedwith{{ propertyModifiers }}to follow the config.
Upgrading to 1.4
- Config files published by older versions (
namespaceandpathkeys) still work. Publish the new config to usemodels,structureandauto_bind. - Generated repositories import the model from its real namespace.
php artisan layers --repository User.Addressnow importsApp\Models\User\Addressinstead ofApp\Models\Address. - Services with more than one repository use constructor property promotion, and the
ServiceMultiRepositories.stubwas removed.

