Modules in Laravel 4...

Laravel
written by Boris Strahija on May 31, 2013 in Blog with 119 comments

Update!

While the tutorial below is pretty simple, I needed to have a more generic solution that is easily installed in any project so I created a composer package. It’s still pretty new, so any feedback is appreciated.
Go ahead and try it out: https://github.com/creolab/laravel-modules

I also wrote up a post on this package, so go ahead and check that out: http://creolab.hr/2013/10/modules-laravel-4-part-2/

Laravel 4 is heavily based on composer packages, which is a good thing, but sometimes developers (like myself) like to separate their code into modules. This is especially nice when building larger projects. Now this was fairly easy to do in Laravel 3 with the bundles system, but in Laravel 4 many people just recommend building packages since L4 has a nice workbench feature. This is all good, but sometimes I like to separate my app specific controllers and views into modules, and not have to go through it with the workbench.

This short tutorial will give you just that, a app/modules directory that will contain our modules. I will not go into detail of setting up and installing Laravel, I’ll only show my way of doing this.

So first of all setup a new L4 application and open it in the browser. Make sure you get the welcome screen, so you’ll see everything is running ok.

Since we’re gonna put all our modules inside the app/modules directory, we need to put it in our composer.json file, under autoload/classmap:

"autoload": {
	"classmap": [
		"app/commands",
		"app/controllers",
		"app/models",
		"app/database/migrations",
		"app/database/seeds",
		"app/tests/TestCase.php",
		"app/modules"
	]
}

For this tutorial we’ll create 2 dummy modules from a real world app. Some kind of web shop that will have a simple CMS module and a module for managing the orders. I’ll call them content (app/modules/content) and shop (app/modules/shop). Since you’ll probably put these kind of modules into a admin interface, I will prefix the URL’s with the keyword admin.

Laravel 4 uses service providers to register and boot up the packages, so we can leverage this funcionality to register our modules. For that I’ll create a general ServiceProvider class for all modules that will look something like this:

app/modules/ServiceProvider.php

<?php namespace App\Modules;

abstract class ServiceProvider extends \Illuminate\Support\ServiceProvider {

	public function boot()
	{
		if ($module = $this->getModule(func_get_args()))
		{
			$this->package('app/' . $module, $module, app_path() . '/modules/' . $module);
		}
	}

	public function register()
	{
		if ($module = $this->getModule(func_get_args()))
		{
			$this->app['config']->package('app/' . $module, app_path() . '/modules/' . $module . '/config');

			// Add routes
			$routes = app_path() . '/modules/' . $module . '/routes.php';
			if (file_exists($routes)) require $routes;
		}
	}

	public function getModule($args)
	{
		$module = (isset($args[0]) and is_string($args[0])) ? $args[0] : null;

		return $module;
	}

}

To explain why we need this service provider. Well every module will have it’s own service provider class, but those classes will extend our module service provider and not the Illuminate L4 service provider. And when a service provider for the module is registered in “app/config/app.php“, our global module service provider will then register the module as a package, so you can use it’s routes, configuration etc. It also looks for a routes.php file and includes it, so you can define your module routes there.

So our service providers for the content and shop modules will look like this:

app/modules/content/ServiceProvider.php

<?php namespace App\Modules\Content;

class ServiceProvider extends \App\Modules\ServiceProvider {

	public function register()
	{
		parent::register('content');
	}

	public function boot()
	{
		parent::boot('content');
	}

}

app/modules/shop/ServiceProvider.php

<?php namespace App\Modules\Shop;

class ServiceProvider extends \App\Modules\ServiceProvider {

	public function register()
	{
		parent::register('shop');
	}

	public function boot()
	{
		parent::boot('shop');
	}

}

This looks pretty clean. Of course you can also add the boot() method for the service provider if you need, but this is just a basic example.

The final thing you need to do is register your modules, and this is done like with any other package, we just add the following to our app config in “app/config/app.php” under the providers array:

'App\Modules\Content\ServiceProvider',
'App\Modules\Shop\ServiceProvider',

So now we have out modules fully working. You can add module specific routes, group your controllers/views/models, get module configuration like this:

Config::get('content::channels');

Or get translated phrases like this:

Lang::get('shop::errors.no_items_in_cart');

As with any package you can also put you modules class into the IoC container to make it more testable, but I wont go into that since there are already a bunch of tutorials on this topic.

And lastly a simple example for our modules to actually show return something, so we’ll define some routes.

app/modules/shop/routes.php

Route::get('admin/shop', function() {
	return '<h1>Shop</h1>
'; });

This will simply output the text “Shop” when we hit the route “admin/shop”. This is a very simple example, but in a real app you would define a auth filter, maybe group the routes with a prefix, use resource controllers etc.

Our routes for the content module could pull some stuff from a config file. So maybe we’ll have more content types or as I like to call them, channels. So we’ll add a configuration file to our content module:

app/modules/content/config/channels.php

return array(
	'pages'    => array('title' => 'Pages'),
	'articles' => array('title' => 'Articles'),
	'products' => array('title' => 'Products'),
);

We defined 3 channels, and we’ll create routes for all of them in our modules routes file:

app/modules/content/routes.php

foreach (Config::get('content::channels') as $key => $channel)
{
	Route::get('admin/content/' . $key, function() use ($channel) {
		return "<h1>Channel [{$channel['title']}]</h1>";
	});
}

Route::get('admin/content', function() { return '<h1>Content</h1>'; });

So when we hit the route admin/content, well get the string “Content”, but then we hit the route admin/content/pages we’ll get the string “Channel [Pages]”. This is pulled from a config file.

So there you have it, simple modules in Laravel 4. I hope you find it useful ;)