Search by

cebpereira / layers

cebpereira

A laravel package to generate files for layered architecture

Package info

github.com/cebpereira/Layers

pkg:composer/cebpereira/layers

Statistics

Installs: 224

Dependents: 0

Suggesters: 0

Stars: 0

v1.4.1 2026-09-13 14:41 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

"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/Models generates layers inside app, and app/Modules/Core/Models generates layers inside app/Modules/Core.
    • {subpath} : the model subfolder (Auth for app/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 readonly or public 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 --eloquent
  • php artisan layers:repository --interface : the same as php artisan layers --interface
  • php artisan layers:service : the same as php artisan layers --service
  • php artisan layers:binds : List all repository bindings
  • php 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

Structure Folder

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;).

Structure Folder with Subfolders

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 array in store and update. The previous SupportCollection|array|int|string union failed at runtime for anything but arrays, because Eloquent create and update only 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.stub files keep their hardcoded visibility. Replace protected with {{ propertyModifiers }} to follow the config.

Upgrading to 1.4

  • Config files published by older versions (namespace and path keys) still work. Publish the new config to use models, structure and auto_bind.
  • Generated repositories import the model from its real namespace. php artisan layers --repository User.Address now imports App\Models\User\Address instead of App\Models\Address.
  • Services with more than one repository use constructor property promotion, and the ServiceMultiRepositories.stub was removed.