shreifelagamy/laravel-service-modules
Laravel Service Modules helps organize Laravel applications into self-contained service modules, keeping code modular, maintainable, and easy to scale. It provides a structured way to group related functionality and register module components within a Laravel app.
shreifelagamy/laravel-service-modules is a small, opinionated scaffolding package: it gives you an Artisan-driven way to carve a Laravel app into self-contained service modules (each with its own routes, migrations, views, providers) instead of letting everything pile into app/.
Install (v2.x):
composer require shreifelagamy/laravel-service-modules
php artisan vendor:publish --provider="Shreifelagamy\ServiceModules\ServiceModulesServiceProvider"
Where to look first, in order:
composer.json of the package — confirm the illuminate/support constraint matches your Laravel version. v2.0.0 is the Laravel 12-compatible line; if you're on 10 or 11, stay on ^1.2.config/service-modules.php or similar) — this is where the module base path and namespace are defined. Everything else derives from it.php artisan list filtered to the package's namespace — the generator commands are the whole product surface; the runtime footprint is thin.src/ directory: read the service provider and the generator commands. Both are short, and reading them is faster than guessing at conventions from the README.First use case — the one that justifies the install: pick a genuinely separable slice of your app (billing, notifications, an internal admin panel) and scaffold it as a module rather than a app/Services/Billing folder. If you can't name two or three such slices, you don't need this package yet.
Module-per-bounded-context. The value shows up when a module owns all of its layers — controllers, models, migrations, routes, views, tests — under one directory. Half-migrating (module has controllers, models stay in app/Models) is the failure mode that makes these packages feel like overhead.
Register providers, don't hardcode paths. Each module should expose its own service provider, registered from the package's config or a bootstrap/providers.php entry. This is what lets you delete or disable a module cleanly:
// config/service-modules.php
'modules' => [
'Billing' => ['enabled' => true],
'Notifications'=> ['enabled' => env('MODULE_NOTIFICATIONS', true)],
],
Feature-flagging modules per environment is the highest-leverage pattern here — it's how you keep a half-finished module deployed but inert.
Route and migration isolation. Point each module's provider at its own routes/web.php and database/migrations directory with a distinct migration path. Load migrations with loadMigrationsFrom() and name them with a module prefix (2024_01_01_000000_billing_create_invoices_table.php) so php artisan migrate output stays readable and rollbacks are traceable.
Cross-module communication via events, not calls. The temptation is app(NotificationsService::class) from inside Billing. Resolve to firing a domain event and letting the other module subscribe. This keeps modules deletable and is the difference between "modular" as a folder convention and modular as an architectural property.
Composer autoload for module namespaces. Add a PSR-4 mapping so module classes resolve without per-module requires:
"autoload": { "psr-4": { "Modules\\": "modules/" } }
Then composer dump-autoload after adding a module. Wire this into a post-autoload-dump script if you scaffold modules often.
Pair with a test suite per module. Modules that can't be tested in isolation aren't modules. Put modules/Billing/tests/ in phpunit.xml's testsuite list; Pest users can add it as a separate suite with its own TestCase.
v2.0.0 is a major bump for a reason. The changelog lists only "add support for laravel 12," but the 1.x → 2.x jump almost certainly drops Laravel 10 (and possibly 11) from the illuminate/* constraints. Before upgrading, check the actual composer.json diff on the tag — a semver-major for a support-matrix change means your CI matrix is the thing that breaks, not your application code. If you're mid-cycle on Laravel 11, pin ^1.2 and revisit at your next upgrade window.
Config cache is the classic footgun. Module paths and enabled-flags read from config will be stale after deploy if you don't php artisan config:cache (or, conversely, will ignore your local edits until you config:clear). Same for route caching with per-module route files — route:cache needs the module providers to have registered first, so verify provider order in bootstrap/providers.php if routes 404 in production but work locally.
Namespace mismatches after renaming a module. Generators bake the module name into namespaces, provider class names, and sometimes migration filenames. Renaming a directory by hand leaves dangling references. Prefer deleting and re-scaffolding, or grep for the old namespace across modules/, config/, and composer.json — all three, not just the first.
Migration ordering across modules. Filesystem ordering means modules migrate in directory-name alphabetical order. If Billing has a foreign key into Accounts' table, that will fail on a fresh database. Resolve it by (a) naming modules so dependency order matches alphabetical, (b) merging cross-module FKs into a later migration, or (c) using Schema::withoutForeignKeyConstraints(). Document the order — it's invisible until someone runs migrate:fresh on a clean machine.
Generator output is a starting point, not gospel. These packages emit boilerplate that reflects the author's conventions. Read the stubs once, then publish/override them if the package allows — otherwise every new module ships with a directory structure or namespace style you'll fight later. If stubs aren't publishable, that's a legitimate reason to fork or wrap the commands.
Don't over-modularize on day one. The overhead (extra providers, autoload entries, cross-module imports) is real and only pays off past a certain size. Start with one module you'd genuinely extract, prove the ergonomics, then expand. Retrofitting an existing app wholesale is the highest-risk way to adopt this.
Small package, small bus factor. Single-maintainer projects with infrequent releases deserve a pinned version in composer.json ("^2.0" is fine, but avoid dev-main) and a note in your upgrade runbook. The runtime surface is thin enough that forking is cheap if maintenance stalls — keep that in mind before letting module structure become load-bearing across a large codebase.
How can I help you explore Laravel packages today?