oki-technologies/laravel-module-test
A Laravel package for testing modular application structure, offering helpers and tooling to validate module behavior within Laravel projects. Useful for developers building module-based architectures who want a straightforward testing layer, though it has limited adoption and community feedback so...
Install and confirm the surface area first.
composer require oki-technologies/laravel-module-test
php artisan vendor:publish --provider="OkiTechnologies\ModuleTest\ModuleTestServiceProvider"
Package discovery should register the service provider automatically; if you keep dont-discover in composer.json, add the provider to config/app.php manually. After publishing, the first thing to read is the published config file — it defines the module root path, namespace convention, and which discovery caches are enabled. Those three values determine almost everything else about how you use the package, so set them before scaffolding anything.
Find the commands before reading the docs. The quickest orientation is:
php artisan list | grep -i module
You will typically see a make/scaffold command, a list command, and a cache/clear pair. Running module:list (or equivalent) on a fresh install confirms discovery is wired correctly — an empty result usually means a path or namespace mismatch, not a broken install.
First use case: extract one existing concern into a module. Do not start by modularising the whole app. Pick a self-contained slice — a reporting feature, an integration client, a set of admin screens — and scaffold it. Keep the new module's routes, migrations, and service provider registered, move the code, and confirm the app boots and the test suite still passes before you add a second module. The package's value is in repeatability, and you only learn the real constraints by doing one clean extraction end to end.
Version note: this is v1.0.0.0, a four-segment version — treat it as the first API-stable line, and pin it exactly ("^1.0" or "1.0.*") until you have seen a second release.
Module-per-bounded-context, not module-per-layer. The pattern that ages well is one module per business capability (Billing, Inventory, Notifications), each owning its own routes, migrations, config, views, and service provider. Modules that mirror Laravel's default folders (Models, Http) reproduce the problem you were trying to solve.
Register the module provider, let the package do the rest. In practice each module has a provider that the package's discovery picks up. Use that provider for the module's own bindings, event listeners, and route/migration loading — not AppServiceProvider. Keeping registration local to the module is what makes modules removable.
// modules/Billing/Providers/BillingServiceProvider.php
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/billing.php', 'billing');
$this->app->singleton(InvoiceGateway::class, fn ($app) =>
new StripeGateway($app['config']->get('billing.stripe'))
);
}
Config namespacing is the integration seam. Publish each module's config under its own key (config/billing.php → config('billing.*')) and reference it only through that key. This keeps modules portable and lets ops override per-environment without touching module source.
Cross-module communication: events first, direct calls last. Two modules calling each other's classes creates the same coupling you had in app/. Use domain events for one-way notification and a small contract/interface (defined in whichever module owns the data) for synchronous needs. Bind the implementation in the owning module's provider.
Migrations need collision-proof names. Multiple modules publishing migrations into one database/migrations directory is a common source of ordering bugs. Either keep each module's migrations inside the module and load them from its provider ($this->loadMigrationsFrom(...)), or prefix timestamps/filenames with the module name. Test php artisan migrate:fresh with all modules enabled before you trust it.
CI and local workflow. Add the module path to composer.json autoload (psr-4) if the package does not do it for you, and include composer dump-autoload in your setup script. Run php artisan module:cache (or the package's equivalent) as a deploy step only if your deployment is read-only at runtime — cached discovery plus a container that writes files is a subtle source of "module works locally, missing in prod".
Testing. Keep each module's tests inside the module and point PHPUnit at the module directories via a second test suite in phpunit.xml. Feature tests that span modules belong in the app-level suite; unit tests stay with the module so extraction remains verifiable.
Discovery caches are the number-one source of confusion. After creating, renaming, or deleting a module, clear the package cache and Laravel's own (php artisan config:clear && php artisan cache:clear && php artisan module:clear if present). Symptoms of a stale cache: new module's routes 404, deleted module's provider still firing, config changes ignored.
Composer autoload beats package discovery. If a module's classes are "not found" while module:list shows it, the package knows about the module but Composer does not. Run composer dump-autoload and verify the PSR-4 mapping in the module's own composer.json or the root one.
Namespace/path drift after renaming. Renaming a module directory without updating the module's metadata (usually a module.json or the provider namespace) leaves discovery half-working. Change the name in one place and re-scaffold if the package offers no rename command.
Four-segment versioning. v1.0.0.0 does not map cleanly onto Composer's default constraints — ^1.0 behaves fine, but ~1.0.0 may not resolve as you expect. Pin explicitly and check composer show oki-technologies/laravel-module-test after updates.
The release notes are effectively a changelog link only. For this version, read the diff between the tagged commits rather than trusting the summary — specifically the service provider, the config file, and any command signatures. Those are the three places a v1 cut is most likely to have shifted from whatever pre-release API you may have used.
Order of provider registration matters. If a module depends on another module's bindings, either declare the order in the package's module manifest (if supported) or defer resolution — resolve from the container inside a method rather than in a constructor. Constructor-injected cross-module dependencies produce "target not instantiable" errors that only appear with a cold cache.
Extension points worth knowing. The published config, the per-module service provider, and the discovery paths are the sanctioned hooks. Prefer overriding config and bindings there over subclassing package classes; the latter breaks on every minor release of a young package.
Deployment checklist. composer install --no-dev → php artisan config:cache → php artisan route:cache → migration step. If route caching fails with a closure error, one of your modules defines routes with closures; convert to controller actions, since this will bite you in production even if it is invisible locally.
How can I help you explore Laravel packages today?