pablodip/module-bundle
Symfony bundle that adds a lightweight module system for organizing application code into self-contained, reusable modules with their own controllers, templates, routing, and configuration.
Read this first: it is not a Laravel package. pablodip/module-bundle is a Symfony2-era bundle. There is no service provider, no extra.laravel package-discovery block, no Illuminate\* dependency, and no code that touches Laravel's container, router, or event loop. Composer will happily install it next to Laravel, but nothing in Laravel will boot it. So "getting started" splits into two tracks: the native track (if you are maintaining a legacy Symfony2 app) and the Laravel track (which is a reimplementation of the idea, not an integration).
Native track — where to look, in order:
composer.json — this pins the Symfony version and the autoload era (likely PSR-0). Do this before anything else; it decides whether the rest is even runnable.PablodipModuleBundle.php — the bundle class; tells you what it registers and on what hook.DependencyInjection/ — the extension's load() method and any Configuration class. This is the real documentation: it tells you the config schema, defaults, and which services get wired.README last. It is thinner than the DI extension.Minimal first use case (native): register new Pablodip\ModuleBundle\PablodipModuleBundle() in AppKernel.php, import the bundle's route loader once in app/config/routing.yml, create one module directory containing a single controller action, one template, and one route, clear app/cache/*, then hit the URL. app/console debug:router is the fastest way to confirm the convention your module layout is actually expected to follow.
Laravel track — first use case: do not install it. Your first use case is a single app/Modules/Billing/ folder containing a controller, a routes.php, a Providers/BillingServiceProvider.php, and a resources/views/ directory, registered from config/app.php (or via discovery from composer.json). That is the same unit of organization the bundle provides, expressed in the framework you actually run. If you want the batteries-included version, evaluate nwidart/laravel-modules instead.
The honest day-to-day pattern for a Laravel developer is pattern translation, not package usage. Take each concept the bundle implements in Symfony2 and map it to its Laravel counterpart:
| Bundle concept (Symfony2) | Laravel equivalent |
|---|---|
Bundle registration in AppKernel |
Service provider in config/app.php or extra.laravel.providers discovery |
DI extension load() + Configuration schema |
register() / boot() plus mergeConfigFrom() |
| Route loader discovering module routes | loadRoutesFrom(__DIR__.'/routes.php') in boot() |
Resources/views resolving per module |
loadViewsFrom(__DIR__.'/resources/views', 'billing') |
| Per-module service YAML | Container bindings inside register() |
| Module folder = feature slice | app/Modules/{Name}/ with a provider per module |
Feature-slice discipline. The bundle's one durable idea: a module owns its controllers, routes, templates, config, and services, and nothing else needs to know about it. Enforce that in Laravel by making each module provider the only registration point for its piece of the app. When a controller needs a service, resolve it through the module's binding — do not reach into app/Services. Portability is the whole point; a module that only depends on the container and framework contracts can be lifted into another app or turned into a standalone package later.
Per-module service providers as the composition root. Keep each provider small and explicit:
class BillingServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/config/billing.php', 'billing');
$this->app->bind(BillingGateway::class, StripeGateway::class);
}
public function boot(): void
{
$this->loadRoutesFrom(__DIR__.'/routes.php');
$this->loadViewsFrom(__DIR__.'/resources/views', 'billing');
$this->loadMigrationsFrom(__DIR__.'/database/migrations');
}
}
Convention-driven routing. The bundle's core value is that routes are discovered from layout rather than hand-maintained in one central file. Reproduce it with one routes/modules.php that iterates module providers, or simply let each module's loadRoutesFrom self-register. Verify with php artisan route:list after every addition — the same way you would run debug:router.
Testing per module. Write a functional test per module route ($this->get('/billing/invoices')->assertOk()), which pins down the implicit conventions. If you are on the native track, $client->request(...) against each module route does the same job and is the only reliable way to catch routing regressions caused by directory renames.
Legacy native maintenance. If you genuinely are on Symfony2 with this bundle: add one module per feature, keep each module's config and routing inside the module folder, and never import module internals from another module — the package has no isolation guarantees, so you enforce them by convention. Clear and rebuild the cache after every structural change; treat it as part of the edit-test loop.
autoload mapping to the root composer.json and composer dump-autoload -o afterwards.rm -rf app/cache/* (or app/console cache:clear) before rebuilding. Routing and container caches will otherwise serve stale definitions, and the failure surfaces as a 404 or a "service not found" rather than a clear "your module isn't loaded" message.debug:router → container:debug → clear cache → read the DI extension's load() again.extra.laravel.providers is a Laravel-only convention and this package does not declare it. If you install it beside Laravel expecting auto-registration, nothing happens and nothing tells you why.load()/Configuration, and any compiler passes are the three places behavior is decided. In Laravel, the analogues are register()/boot(), mergeConfigFrom, and $this->app->extend() / container events. Study the former, implement the latter.composer.lock and note the PHP version in the repo, or the next developer loses a day to it.How can I help you explore Laravel packages today?