Laravel 4 Packages

A Comprehensive Tutorial

Christopher Pitt
Laravel 4 Tutorials

--

Laravel 4 is a huge step forward for the PHP community. It’s beautifully written, full of features and the community is presently exploding. Let’s contribute to that community by creating reusable packages, with Laravel.

I have spent two full hours getting the code out of a Markdown document and into Medium. Medium really isn’t designed for tutorials such as this, and while much effort has been spent in the pursuit of accuracy; there’s a good chance you could stumble across a curly quote in a code listing. Please make a note and I will fix where needed.

I have also uploaded this code to Github. You need simply follow the configuration instructions in this tutorial, after downloading the source code, and the application should run fine. This assumes, of course, that you know how to do that sort of thing. If not; this shouldn’t be the first place you learn about making PHP applications.

https://github.com/formativ/tutorial-laravel-4-packages

If you spot differences between this tutorial and that source code, please raise it here or as a GitHub issue. Your help is greatly appreciated.

This tutorial follows on from a previous tutorial which covered the basics of creating a deployment process for your applications. It goes into detail about creating a Laravel 4 installation; so you should be familiar with it before spending a great deal of time in this one. You’ll also find the source-code we created there to be a good basis for understanding the source code of this tutorial.

I learned the really technical bits of this tutorial from reading Taylor’s book. If you take away just one thing from this it should be to get and read it.

Composer

While the previous tutorial shows you how to create a Laravel 4 project; we need to do a bit more work in this area if we are to understand one of the fundamental ways in which packages differ from application code.

Laravel 3 had the concept of bundles; which were downloadable folders of source code. Laravel 4 extends this idea, but instead of rolling its own download process, it makes use of Composer. Composer should not only be used to create new Laravel 4 installations; but also as a means of adding packages to existing Laravel 4 applications.

Laravel 4 packages are essentially the same things as normal Composer libraries. They often utilise functionality built into the Laravel 4 framework, but this isn’t a requirement of packages in general.

It follows that the end result of our work today is a stand-alone Composer library including the various deployment tools we created last time. For now, we will be placing the package code inside our application folder to speed up iteration time.

Dependency Injection

One of the darlings of modern PHP development is Dependency Injection. PHP developers have recently grown fond of unit testing and class decoupling. I’ll explain both of these things shortly; but it’s important to know that they are greatly improved by the use of dependency injection.

Let’s look at some non-injected dependencies, and the troubles they create for us.

<?phpclass Archiver
{
protected $database;
public function __construct()
{
$this->database = Database::connect(
"host",
"username",
"password",
"schema"
);
}
public function archive()
{
$entries = $this->database->getEntries();
foreach ($entries as $entry)
{
if ($entry->aggregated)
{
$entry->archive();
}
}
}
}
$archiver = new Archiver();
$archiver->archive();

Assuming, for a moment, that Database has been defined to pull all entries in such a way that they have an aggregated property and an archive() method; this code should be straightforward to follow.

The Archiver constructor initialises a database connection and stores a reference to it within a protected property. The archive() method iterates over the entries and archives them if they are already aggregated. Easy stuff.

Not so easy to test. The problem is that testing the business logic means testing the database connection and the entry instances as well. They are dependencies to any unit test for this class.

Furthermore; the Archiver class needs to know that these things exist and how they work. It’s not enough just to know that the database instance is available or that the getEntries() method returns entries. If we have thirty classes all depending on the database; something small (like a change to the database password) becomes a nightmare.

Dependency injection takes two steps, in PHP. The first is to declare dependencies outside of classes and pass them in:

<?phpclass Archiver
{
protected $database;
public function __construct(Database $database)
{
$this->database = $database;
}
// ...then the archive() method
}
$database = Database::connect(
"host",
"username",
"password",
"schema"
);
$archiver = new Archiver($database);
$archiver->archive();

This may seem like a small, tedious change but it immediately enables independent unit testing of the database and the Archiver class.

The second step to proper dependency injection is to abstract the dependencies in such a way that they provide a minimum of functionality. Another way of looking at it is that we want to reduce the impact of changes or the leaking of details to classes with dependencies:

<?phpinterface EntryProviderInterface
{
public function getEntries();
}
class EntryProvider implements EntryProviderInterface
{
protected $database;
public function __construct(Database $database)
{
$this->database = $database;
}
public function getEntries()
{
// ...get the entries from the database
}
}
class Archiver
{
protected $provider;
public function __construct(EntryProviderInterface $provider)
{
$this->provider = $provider;
}
// ...then the archive() method
}
$database = Database::connect(
"host",
"username",
"password",
"schema"
);
$provider = new EntryProvider($database);$archiver = new Archiver($provider);
$archiver->archive();

Woah Nelly! That’s a lot of code when compared to the first snippet; but it’s worth it. Firstly; we’re exposing so few details to the Archiver class that the entire entry dependency could be replaced in a heartbeat. This is possible because we’ve moved the database dependency out of the Archiver and into the EntryProvider.

We’re also type-hinting an interface instead of a concrete class; which lets us swap the concrete class out with anything else that implements EntryProviderInterface. The concrete class can fetch the entries from the database, or the filesystem or whatever.

We can test the EntryProvider class by swapping in a fake Database instance. We can test the Archiver class by swapping in a fake EntryProvider instance.

So, to recap the requirements for dependency injection:

  1. Don’t create class instances in other classes (if they are dependencies) — pass them into the class from outside.
  2. Don’t type-hint concrete classes — create interfaces.

“When you go and get things out of the refrigerator for yourself, you can cause problems. You might leave the door open, you might get something Mommy or Daddy doesn’t want you to have. You might even be looking for something we don’t even have or which has expired.

What you should be doing is stating a need, ‘I need something to drink with lunch,’ and then we will make sure you have something when you sit down to eat.” — John Munsch

You can learn more about Dependency Injection, from Taylor’s book, at: https://leanpub.com/laravel.

Inversion Of Control

Inversion of Control (IoC) is the name given to the process that involves assembling class instances (and the resolution of class instances in general) within a container or registry. Where the registry pattern involves defining class instances and then storing them in some global container; IoC involves telling the container how and where to find the instances so that they can be resolved as required.

This naturally aids dependency injection as class instances adhering to abstracted requirements can easily be resolved without first creating. To put it another way; if our classes adhere to certain requirements (via interfaces) and the IoC container can resolve them to class instances, then we don’t have to do it beforehand.

The easiest way to understand this is to look at some code:

<?phpinterface DatabaseInterface
{
// ...
}
class Database implements DatabaseInterface
{
// ...
}

interface EntryProviderInterface
{
// ...
}
class EntryProvider implements EntryProviderInterface
{
protected $database;
public function __construct(DatabaseInterface $database)
{
$this->database = $database;
}
// ...
}
interface ArchiverInterface
{
// ...
}
class Archiver implements ArchiverInterface
{
protected $provider;
public function __construct(EntryProviderInterface $provider)
{
$this->provider = $provider;
}
// ...
}
$database = new Database();
$provider = new EntryProvider($database);
$archiver = new Archiver($provider);

This shallowly represents a dependency (injection) chain. The last three lines are where the problem starts to become clear; the more we abstract our dependencies, the more “bootstrapping” code needs to be done every time we need the Archiver class.

We could abstract this by using Laravel 4's IoC container:

<?php// ...define classesApp::bind("DatabaseInterface", function() {
return new Database();
});
App::bind("EntryProviderInterface", function() {
return new EntryProvider(App::make("DatabaseInterface"));
});
App::bind("ArchiverInterface", function() {
return new Archiver(App::make("EntryProviderInterface"));
});
$archiver = App::make("ArchiverInterface");

These extra nine lines (using the App::bind() and App::make() methods) tell Laravel 4 how to find/make new class instances, so we can get to the business of using them!

You can learn more about IoC container at: http://laravel.com/docs/ioc.

Service Providers

The main purpose of services providers is to collect and organise the bootstrapping requirements of your package. They’re not strictly a requirement of package development; but rather a Really Good Idea™.

There are three big things to service providers. The first is that they are registered in a common configuration array (in app/config/app.php):

'providers' => array( 'Illuminate\Foundation\Providers\ArtisanServiceProvider',
'Illuminate\Auth\AuthServiceProvider',
'Illuminate\Cache\CacheServiceProvider',
// ...
'Illuminate\Validation\ValidationServiceProvider',
'Illuminate\View\ViewServiceProvider',
'Illuminate\Workbench\WorkbenchServiceProvider',
),

This was extracted from app/config/app.php for brevity.

You can also add your own service providers to this list; as many packages will recommend you do. Then there’s the register() method:

<?php namespace Illuminate\Cookie;use Illuminate\Support\ServiceProvider;class CookieServiceProvider extends ServiceProvider {    /**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app['cookie'] = $this->app->share(function($app)
{
$cookies = new CookieJar($app['request'], $app['encrypter']);
$config = $app['config']['session']; return $cookies->setDefaultPathAndDomain($config['path'], $config['domain']);
});
}
}

This file should be saved as vendor/laravel/framework/src/Illuminate/
Cookie/CookieServiceProvider.php
.

When we take a look at the register() method of the CookieServiceProvider class; we can see a call to $this->app->share() which is similar to theApp::bind() method but instead of creating a new class instance every time it’s resolved in the IoC container; share() wraps a callback so that it’s shared with every resolution.

The name of the register() method explains exactly what it should be used for; registering things with the IoC container (which App extends). If you need to do other bootstrapping stuff then the method you need to use is boot():

public function boot()
{
Model::setConnectionResolver($this->app['db']);
Model::setEventDispatcher($this->app['events']);
}

This was extracted from vendor/laravel/framework/src/Illuminate/
Database/DatabaseServiceProvider.php
for brevity.

This boot() method sets two properties on the Model class. The same class also has a register method but these two settings are pet for the boot method.

You can learn more about service providers at: http://laravel.com/docs/
packages#service-providers

Organising Code

Now that we have some tools to help us create packages; we need to port our code over from being application code to being a package. Laravel 4 provides a useful method for creating some structure to our package:

php artisan workbench Formativ/Deployment

You’ll notice a new folder has been created, called workbench. Within this folder you’ll find an assortment of files arranged in a similar way to those in the vendor/laravel/framework/src/Illuminate/* folders.

We need to break all of the business logic (regarding deployment) into individual classes, making use of the IoC container and dependency injection, and make a public API.

We’re not going to spend a lot of time discussing the intricacies of the deployment classes/scripts we made in the last tutorial. Refer to it if you want more of those details.

To recap; the commands we need to abstract are:

  • asset:combine
  • asset:minify
  • clean
  • copy
  • distribute
  • environment:get
  • environment:remove
  • environment:set
  • watch

Many of these were cluttering up the list of commands which ship with Laravel. Our organisation should collect all of these in a common “namespace”.

The first step is to create a few contracts (interfaces) for the API we want to expose through our package:

<?phpnamespace Formativ\Deployment;interface DistributionInterface
{
public function prepare($source, $target);
public function sync();
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/DistributionInterface.php
.

<?phpnamespace Formativ\Deployment;interface MachineInterface
{
public function getEnvironment();
public function setEnvironment($environment);
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/MachineInterface.php
.

<?phpnamespace Formativ\Deployment\Asset;interface ManagerInterface
{
public function add($source, $target);
public function remove($target);
public function combine();
public function minify();
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Asset/ManagerInterface.php
.

<?phpnamespace Formativ\Deployment\Asset;interface WatcherInterface
{
public function watch();
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Asset/WatcherInterface.php
.

The methods required by these interfaces encompass the functionality our previous commands provided for us. The next step is to fulfil these contracts with concrete class implementations:

<?phpnamespace Formativ\Deployment;use Formativ\Deployment\MachineInterface;
use Illuminate\Filesystem\Filesystem;
class Distribution implements DistributionInterface
{
protected $files;
protected $machine; public function __construct(
Filesystem $files,
MachineInterface $machine
)
{
$this->files = $files;
$this->machine = $machine;
}
public function prepare($source, $target)
{
// ...copy + clean files for distribution
}
public function sync()
{
// ...sync distribution files to remote server
}
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Distribution.php
.

<?phpnamespace Formativ\Deployment;use Illuminate\Filesystem\Filesystem;class Machine implements MachineInterface
{
protected $environment;
protected $files; public function __construct(Filesystem $files)
{
$this->files = $files;
}
public function getEnvironment()
{
// ...get the current environment
}
public function setEnvironment($environment)
{
// ...set the current environment
}
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Machine.php
.

<?phpnamespace Formativ\Deployment\Asset;use Formativ\Deployment\MachineInterface;
use Illuminate\Filesystem\Filesystem;
class Manager implements ManagerInterface
{
protected $files;
protected $machine; protected $assets = []; public function __construct(
Filesystem $files,
MachineInterface $machine
)
{
$this->files = $files;
$this->machine = $machine;
}
public function add($source, $target)
{
// ...add files to $assets
}
public function remove($target)
{
// ...remove files from $assets
}
public function combine()
{
// ...combine assets
}
public function minify()
{
// ...minify assets
}
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Asset/Manager.php
.

<?phpnamespace Formativ\Deployment\Asset;use Formativ\Deployment\MachineInterface;
use Illuminate\Filesystem\Filesystem;
class Watcher implements WatcherInterface
{
protected $files;
protected $machine; public function __construct(
Filesystem $files,
MachineInterface $machine
)
{
$this->files = $files;
$this->machine = $machine;
}
public function watch()
{
// ...watch assets for changes
}
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Asset/Watcher.php
.

The main to note about these implementations is that they use dependency injection instead of creating class instances in their constructors. As I pointed out before; we’re not going to go over the actually implementation code, but feel free to port it over from the application-based commands.

As you can probably tell; I’ve combined related functionality into four main classes. This becomes even more apparent when you consider what the service provider has become:

<?phpnamespace Formativ\Deployment;use App;
use Illuminate\Support\ServiceProvider;
class DeploymentServiceProvider
extends ServiceProvider
{
protected $defer = true;
public function register()
{
App::bind("deployment.asset.manager", function()
{
return new Asset\Manager(
App::make("files"),
App::make("deployment.machine")
);
});
App::bind("deployment.asset.watcher", function()
{
return new Asset\Watcher(
App::make("files"),
App::make("deployment.machine")
);
});
App::bind("deployment.distribution", function()
{
return new Distribution(
App::make("files"),
App::make("deployment.machine")
);
});
App::bind("deployment.machine", function()
{
return new Machine(
App::make("files")
);
});
App::bind("deployment.command.asset.combine", function()
{
return new Command\Asset\Combine(
App::make("deployment.asset.manager")
);
});
App::bind("deployment.command.asset.minify", function()
{
return new Command\Asset\Minify(
App::make("deployment.asset.manager")
);
});
App::bind("deployment.command.asset.watch", function()
{
return new Command\Asset\Watch(
App::make("deployment.asset.manager")
);
});
App::bind("deployment.command.distribute.prepare", function()
{
return new Command\Distribute\Prepare(
App::make("deployment.distribution")
);
});
App::bind("deployment.command.distribute.sync", function()
{
return new Command\Distribute\Sync(
App::make("deployment.distribution")
);
});
App::bind("deployment.command.machine.add", function()
{
return new Command\Machine\Add(
App::make("deployment.machine")
);
});
App::bind("deployment.command.machine.remove", function()
{
return new Command\Machine\Remove(
App::make("deployment.machine")
);
});
$this->commands(
"deployment.command.asset.combine",
"deployment.command.asset.minify",
"deployment.command.asset.watch",
"deployment.command.distribute.prepare",
"deployment.command.distribute.sync",
"deployment.command.machine.add",
"deployment.command.machine.remove"
);
}
public function boot()
{
$this->package("formativ/deployment");
include __DIR__ . "/../../helpers.php";
include __DIR__ . "/../../macros.php";
}
public function provides()
{
return [
"deployment.asset.manager",
"deployment.asset.watcher",
"deployment.distribution",
"deployment.machine",
"deployment.command.asset.combine",
"deployment.command.asset.minify",
"deployment.command.asset.watch",
"deployment.command.distribute.prepare",
"deployment.command.distribute.sync",
"deployment.command.machine.add",
"deployment.command.machine.remove"
];
}
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/DeploymentServiceProvider.php
.

I’ve folded the copy() and clean() methods into a single prepare() method.

The first four bind() calls bind our four main classes to the IoC container. The remaining bind() methods bind the artisan commands we still have to create (to replace those we make last time).

There’s also a call to $this->commands(); which registers commands (bound to the IoC container) with artisan. Finally; we define the provides() method, which coincides with the $defer = true property, to inform Laravel which IoC container bindings are returned by this service provider. By setting $defer = true; we’re instructing Laravel to not immediately load the provided classes and commands, but rather wait until they are required.

<?phpnamespace Formativ\Deployment\Command\Asset;use Formativ\Deployment\Asset\ManagerInterface;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class Combine
extends Command
{
protected $name = "deployment:asset:combine";
protected $description = "Combines multiple resource files."; public function __construct(ManagerInterface $manager)
{
parent::__construct();
$this->manager = $manager;
}
// ...
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Command/Asset/Combine.php
.

<?phpnamespace Formativ\Deployment\Command\Asset;use Formativ\Deployment\Asset\ManagerInterface;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class Minify
extends Command
{
protected $name = "deployment:asset:minify";
protected $description = "Minifies multiple resource files."; public function __construct(ManagerInterface $manager)
{
parent::__construct();
$this->manager = $manager;
}
// ...
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Command/Asset/Minify.php
.

<?phpnamespace Formativ\Deployment\Command\Asset;use Formativ\Deployment\Asset\ManagerInterface;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class Watch
extends Command
{
protected $name = "deployment:asset:watch";
protected $description = "Watches files for changes."; public function __construct(ManagerInterface $manager)
{
parent::__construct();
$this->manager = $manager;
}
// ...
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Command/Asset/Watch.php
.

<?phpnamespace Formativ\Deployment\Command\Distribute;use Formativ\Deployment\DistributionInterface;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class Prepare
extends Command
{
protected $name = "deployment:distribute:prepare";
protected $description = "Prepares the distribution folder."; protected $distribution; public function __construct(
DistributionInterface $distribution
)
{
parent::__construct();
$this->distribution = $distribution;
}
// ...
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Command/Distribute/Prepare.php
.

<?phpnamespace Formativ\Deployment\Command\Distribute;use Formativ\Deployment\DistributionInterface;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class Sync
extends Command
{
protected $name = "deployment:distribute:sync";
protected $description = "Syncs changes to a target."; protected $distribution; public function __construct(
DistributionInterface $distribution
)
{
parent::__construct();
$this->distribution = $distribution;
}
// ...
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Command/Distribute/Sync.php
.

<?phpnamespace Formativ\Deployment\Command\Machine;use Formativ\Deployment\MachineInterface;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class Add
extends Command
{
protected $name = "deployment:machine:add";
protected $description = "Adds the current machine to an environment."; protected $machine; public function __construct(MachineInterface $machine)
{
parent::__construct();
$this->machine = $machine;
}
// ...
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Command/Machine/Add.php
.

<?phpnamespace Formativ\Deployment\Command\Machine;use Formativ\Deployment\MachineInterface;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class Remove
extends Command
{
protected $name = "deployment:machine:remove";
protected $description = "Removes the current machine from an environment."; protected $machine; public function __construct(MachineInterface $machine)
{
parent::__construct();
$this->machine = $machine;
}
// ...
}

This file should be saved as workbench/formativ/deployment/src/
Formativ/Deployment/Command/Machine/Remove.php
.

I’ve also omitted the fire() method from the new commands; consider it an exercise to add these in yourself.

Now, when we run the artisan list command; we should see all of our new package commands neatly grouped. Feel free to remove the old commands, after you’ve ported their functionality over to the new ones.

Publishing Configuration Files

Often you’ll want to add new configuration files to the collection which ship with new Laravel applications. There’s an artisan command to help with this:

php artisan config:publish formativ/deployment --path=
workbench/Formativ/Deployment/src/config

This should copy the package configuration files into the app/config/packages/formativ/deployment directory.

Since we have access to a whole new kind of configuration; we no longer need to override the configuration files for things like environment. As long as our helpers/macros use the package configuration files instead of the the default ones; we can leave the underlying Laravel 4 application structure (and bootstrapping code) untouched.

Creating Composer.json

Before we can publish our package, so that others can use it in their applications; we need to add a few things to the composer.json file that the workbench command created for us:

{
"name" : "formativ/deployment",
"description" : "All sorts of cool things with deployment.",
"authors" : [
{
"name" : "Christopher Pitt",
"email" : "cgpitt@gmail.com"
}
],
"require" : {
"php" : ">=5.4.0",
"illuminate/support" : "4.0.x"
},
"autoload" : {
"psr-0" : {
"Formativ\\Deployment" : "src/"
}
},
"minimum-stability" : "dev"
}

This file should be saved as workbench/formativ/deployment/
composer.json
.

I’ve added a description, my name and email address. I also cleaned up the formatting a bit; but that’s not really a requirement. I’ve also set the minimum PHP version to 5.4.0 as we’re using things like short array syntax in our package.

You should also create a readme.md file, which contains installation instructions, usage instructions, license and contribution information. I can’t understate the import ants of these things — they are your only hope to attract contributors.

You can learn more about the composer.json file at: http://getcomposer.org/doc/01-basic-usage.md
#composer-json-project-setup
.

Submitting A Package To Packagist

To get others using your packages; you need to submit them to packagist.org. Go to http://packagist.org and sign in. If you haven’t already got an account, you should create it and link to your GitHub account.

You’ll find a big “Submit Package” button on the home page. Click it and paste the URL to your GitHub repository in the text field. Submit the form and you should be good to go.

You can learn more about package development at: http://laravel.com/
docs/packages
.

Note On Testing

Those of you who already know about unit testing will doubtlessly wonder where the section on testing is. I ran short on time for that in this tutorial, but it will be the subject of a future tutorial; which will demonstrate the benefits of dependency injection as it relates to unit testing.

Conclusion

Laravel 4 is packed with excellent tools to enable secure deployment. In addition, it also has an excellent ORM, template language, input validator and filter system. And that’s just the tip of the iceberg!

If you found this tutorial helpful, please tell me about it @followchrisp and be sure to recommend it to PHP developers looking to Laravel 4!

This tutorial comes from a book I’m writing. If you like it and want to support future tutorials; please consider buying it. Half of all sales go to Laravel.

--

--