In my day job, I maintain a fairly large-ish Laravel application. It started out as a few separate vanilla PHP web apps, which I consolidated onto Laravel 4, then upgraded to Laravel 5. I’ve added a few new apps to the suite as business needs arose.
In the Laravel 4 version, I used creolab/laravel-modules to, well, modularize, the various apps under one Laravel instance. It worked well with a few gotchas, but the package wasn’t updated for Laravel 5. At that point I looked at a couple other module packages, but in the end decided it was simple enough to add modules to Laravel.
Assume our namespace is “CorpName”.
Add your namespace to composer.json
... "psr-4": { "App\\": "app/", "CorpName\\": "app/CorpName/" } ...
Create a directory structure like the following:
Add your Service Provider to the ‘providers’ array in config/app.php
... 'CorpName\Core\Providers\CorpNameServiceProvider', ...
Create “app/CorpName/Core/Providers/CorpNameServiceProvider.php”:
<?php namespace CorpName\Core\Providers; use Illuminate\Support\ServiceProvider; use CorpName\Core\Bootstrap; /** * Load the CorpName Bootstrap helper, which in turn loads the modules. * Wired up in app/config/app.php providers array */ class CorpNameServiceProvider extends ServiceProvider { /** * Bootstrap the application services. * * @return void */ public function boot() { $this->app['CorpName']->boot(); } /** * Register the application services. * * @return void */ public function register() { // $this->app->singleton('CorpName', function($app) { return new Bootstrap($app); }); } }
Create “app/CorpName/Core/Bootstrap.php”:
<?php namespace CorpName\Core; use Illuminate\Foundation\Application; /** * Bootstrap the modules. Called from the ServiceProvider */ class Bootstrap { /** * IoC * @var \Illuminate\Foundation\Application */ protected $app; public function __construct(Application $app) { $this->app = $app; } public function boot() { $this->loadModules(); } /** * Scan the CorpName/Modules directory for Modules, and load them (routes, views, includes) */ public function loadModules() { /** * @var \Illuminate\Contracts\Filesystem\Filesystem $disk */ $disk = \Storage::disk('modules'); $modules = $disk->directories(); $modules_path = config('filesystems.disks.modules.root'); foreach ($modules as $module) { $module_path = $modules_path . '/' . $module; //register routes if($disk->exists("{$module}/routes.php")) { include $module_path . '/routes.php'; } //set up views if($disk->exists("{$module}/views")) { $this->app['view']->addNamespace($module, $module_path . '/views'); } //include files if($disk->exists("{$module}/includes")) { $includes = $disk->files("{$module}/includes"); foreach ($includes as $include) { if(substr($include, -4) == '.php') { include_once $modules_path . '/' . $include; } } } } } }
Note that I’m using a “modules” storage “disk” configured in “config/filesystem.php”:
... 'modules' => [ 'driver' => 'local', 'root' => app_path().'/CorpName/Modules', ], ...
And…
That’s basically it. You’ll probably want a routes.php file in each module, and a views directory, but you can structure your Modules as you see fit. If you use migrations, you’ll need to add your module/migrations directory to the autoload classmap in composer.json:
... "autoload": { "classmap": [ "database", "app/CorpName/Modules/ModuleOne/migrations", "app/CorpName/Modules/ModuleTwo/migrations", "app/CorpName/Modules/ZeeAppModule/migrations" ], ...
Also, views can be referenced by module name in your controller and blade templates:
Controller:
... return view('ModuleOne::index', $this->data); ...
Blade:
... @extends('ModuleOne::layouts.master') @section('apphead') @include('ModuleTwo::partials.head') @stop ...
This setup has been running in Laravel 5.0 and 5.1 for a year or so without problems. It could probably use some improvements, like caching, but it works as is.