Книга: Laravel 4 Cookbook
Назад: API
Дальше: Real Time Chat

Packages

Packages are the recommended way of extending the functionality provided by Laravel 4. They’re nothing more than Composer libraries with some particular bootstrapping code.

The code for this chapter can be found at: https://github.com/formativ/tutorial-laravel-4-packages

This chapter follows on from a previous chapter 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 chapter.

I learned the really technical bits of this chapter 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 chapter 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.

 1 <?php  2   3 class Archiver  4 {  5     protected $database;  6   7     public function __construct()  8     {  9         $this->database = Database::connect( 10             "host", 11             "username", 12             "password", 13             "schema" 14         ); 15     } 16  17     public function archive() 18     { 19         $entries = $this->database->getEntries(); 20  21         foreach ($entries as $entry) 22         { 23             if ($entry->aggregated) 24             { 25                 $entry->archive(); 26             } 27         } 28     } 29 } 30  31 $archiver = new Archiver(); 32 $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:

 1 <?php  2   3 class Archiver  4 {  5     protected $database;  6   7     public function __construct(Database $database)  8     {  9         $this->database = $database; 10     } 11  12     // ...then the archive() method 13 } 14  15 $database = Database::connect( 16     "host", 17     "username", 18     "password", 19     "schema" 20 ); 21  22 $archiver = new Archiver($database); 23 $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:

 1 <?php  2   3 interface EntryProviderInterface  4 {  5     public function getEntries();  6 }  7   8 class EntryProvider implements EntryProviderInterface  9 { 10     protected $database; 11  12     public function __construct(Database $database) 13     { 14         $this->database = $database; 15     } 16  17     public function getEntries() 18     { 19         // ...get the entries from the database 20     } 21 } 22  23 class Archiver 24 { 25     protected $provider; 26  27     public function __construct( 28         EntryProviderInterface $provider 29     ) 30     { 31         $this->provider = $provider; 32     } 33  34     // ...then the archive() method 35 } 36  37 $database = Database::connect( 38     "host", 39     "username", 40     "password", 41     "schema" 42 ); 43  44 $provider = new EntryProvider($database); 45 $archiver = new Archiver($provider); 46 $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:

 1 <?php  2   3 interface DatabaseInterface  4 {  5     // ...  6 }  7   8 class Database implements DatabaseInterface  9 { 10     // ... 11 } 12  13 interface EntryProviderInterface 14 { 15     // ... 16 } 17  18 class EntryProvider implements EntryProviderInterface 19 { 20     protected $database; 21  22     public function __construct(DatabaseInterface $database) 23     { 24         $this->database = $database; 25     } 26  27     // ... 28 } 29  30 interface ArchiverInterface 31 { 32     // ... 33 } 34  35 class Archiver implements ArchiverInterface 36 { 37     protected $provider; 38  39     public function __construct( 40         EntryProviderInterface $provider 41     ) 42     { 43         $this->provider = $provider; 44     } 45  46     // ... 47 } 48  49 $database = new Database(); 50 $provider = new EntryProvider($database); 51 $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:

 1 <?php  2   3 // ...define classes  4   5 App::bind("DatabaseInterface", function() {  6     return new Database();  7 });  8   9 App::bind("EntryProviderInterface", function() { 10     return new EntryProvider( 11         App::make("DatabaseInterface") 12     ); 13 }); 14  15 App::bind("ArchiverInterface", function() { 16     return new Archiver( 17         App::make("EntryProviderInterface") 18     ); 19 }); 20  21 $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):

1 'providers' => array( 2     'Illuminate\Foundation\Providers\ArtisanServiceProvider', 3     'Illuminate\Auth\AuthServiceProvider', 4     'Illuminate\Cache\CacheServiceProvider', 5     // ... 6     'Illuminate\Validation\ValidationServiceProvider', 7     'Illuminate\View\ViewServiceProvider', 8     'Illuminate\Workbench\WorkbenchServiceProvider', 9 ), 

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

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

 1 <?php namespace Illuminate\Cookie;  2   3 use Illuminate\Support\ServiceProvider;  4   5 class CookieServiceProvider extends ServiceProvider {  6   7     /**  8     * Register the service provider.  9     * 10     * @return void 11     */ 12     public function register() 13     { 14         $this->app['cookie'] = $this->app->share(function($app) 15         { 16             $cookies = new CookieJar( 17                 $app['request'], 18                 $app['encrypter'] 19             ); 20  21             $config = $app['config']['session']; 22  23             return $cookies->setDefaultPathAndDomain( 24                 $config['path'], 25                 $config['domain'] 26             ); 27         }); 28     } 29  30 } 

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 the App::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():

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

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

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:

1 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:

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:

 1 <?php  2   3 namespace Formativ\Deployment\Asset;  4   5 interface ManagerInterface  6 {  7     public function add($source, $target);  8     public function remove($target);  9     public function combine(); 10     public function minify(); 11 } 

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

1 <?php 2  3 namespace Formativ\Deployment\Asset; 4  5 interface WatcherInterface 6 { 7     public function watch(); 8 } 

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

1 <?php 2  3 namespace Formativ\Deployment; 4  5 interface DistributionInterface 6 { 7     public function prepare($source, $target); 8     public function sync(); 9 } 

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

1 <?php 2  3 namespace Formativ\Deployment; 4  5 interface MachineInterface 6 { 7     public function getEnvironment(); 8     public function setEnvironment($environment); 9 } 

This file should be saved as workbench/formativ/deployment/src/Formativ/Deployment/MachineInterface.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:

 1 <?php  2   3 namespace Formativ\Deployment\Asset;  4   5 use Formativ\Deployment\MachineInterface;  6 use Illuminate\Filesystem\Filesystem;  7   8 class Manager  9 implements ManagerInterface 10 { 11     protected $files; 12  13     protected $machine; 14  15     protected $assets = []; 16  17     public function __construct( 18         Filesystem $files, 19         MachineInterface $machine 20     ) 21     { 22         $this->files = $files; 23         $this->machine = $machine; 24     }    25  26     public function add($source, $target) 27     { 28         // ...add files to $assets 29     } 30  31     public function remove($target) 32     { 33         // ...remove files from $assets 34     } 35  36     public function combine() 37     { 38         // ...combine assets 39     } 40  41     public function minify() 42     { 43         // ...minify assets 44     } 45 } 

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

 1 <?php  2   3 namespace Formativ\Deployment\Asset;  4   5 use Formativ\Deployment\MachineInterface;  6 use Illuminate\Filesystem\Filesystem;  7   8 class Watcher  9 implements WatcherInterface 10 { 11     protected $files; 12  13     protected $machine; 14  15     public function __construct( 16         Filesystem $files, 17         MachineInterface  $machine 18     ) 19     { 20         $this->files = $files; 21         $this->machine = $machine; 22     } 23  24     public function watch() 25     { 26         // ...watch assets for changes 27     } 28 } 

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

 1 <?php  2   3 namespace Formativ\Deployment;  4   5 use Formativ\Deployment\MachineInterface;  6 use Illuminate\Filesystem\Filesystem;  7   8 class Distribution  9 implements DistributionInterface 10 { 11     protected $files; 12  13     protected $machine; 14  15     public function __construct( 16         Filesystem $files, 17         MachineInterface $machine 18     ) 19     { 20         $this->files = $files; 21         $this->machine = $machine; 22     } 23  24     public function prepare($source, $target) 25     { 26         // ...copy + clean files for distribution 27     } 28  29     public function sync() 30     { 31         // ...sync distribution files to remote server 32     } 33 } 

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

 1 <?php  2   3 namespace Formativ\Deployment;  4   5 use Illuminate\Filesystem\Filesystem;  6   7 class Machine  8 implements MachineInterface  9 { 10     protected $environment; 11  12     protected $files; 13  14     public function __construct(Filesystem $files) 15     { 16         $this->files = $files; 17     } 18  19     public function getEnvironment() 20     { 21         // ...get the current environment 22     } 23  24     public function setEnvironment($environment) 25     { 26         // ...set the current environment 27     } 28 } 

This file should be saved as workbench/formativ/deployment/src/Formativ/Deployment/Machine.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:

  1 <?php   2    3 namespace Formativ\Deployment;   4    5 use App;   6 use Illuminate\Support\ServiceProvider;   7    8 class DeploymentServiceProvider   9 extends ServiceProvider  10 {  11     protected $defer = true;  12   13     public function register()  14     {  15         App::bind("deployment.asset.manager", function()  16         {  17             return new Asset\Manager(  18                 App::make("files"),  19                 App::make("deployment.machine")  20             );  21         });  22   23         App::bind("deployment.asset.watcher", function()  24         {  25             return new Asset\Watcher(  26                 App::make("files"),  27                 App::make("deployment.machine")  28             );  29         });  30   31         App::bind("deployment.distribution", function()  32         {  33             return new Distribution(  34                 App::make("files"),  35                 App::make("deployment.machine")  36             );  37         });  38   39         App::bind("deployment.machine", function()  40         {  41             return new Machine(  42                 App::make("files")  43             );  44         });  45   46         App::bind("deployment.command.asset.combine", function()  47         {  48             return new Command\Asset\Combine(  49                 App::make("deployment.asset.manager")  50             );  51         });  52   53         App::bind("deployment.command.asset.minify", function()  54         {  55             return new Command\Asset\Minify(  56                 App::make("deployment.asset.manager")  57             );  58         });  59   60         App::bind("deployment.command.asset.watch", function()  61         {  62             return new Command\Asset\Watch(  63                 App::make("deployment.asset.manager")  64             );  65         });  66   67         App::bind("deployment.command.distribute.prepare", function()  68         {  69             return new Command\Distribute\Prepare(  70                 App::make("deployment.distribution")  71             );  72         });  73   74         App::bind("deployment.command.distribute.sync", function()  75         {  76             return new Command\Distribute\Sync(  77                 App::make("deployment.distribution")  78             );  79         });  80   81         App::bind("deployment.command.machine.add", function()  82         {  83             return new Command\Machine\Add(  84                 App::make("deployment.machine")  85             );  86         });  87   88         App::bind("deployment.command.machine.remove", function()  89         {  90             return new Command\Machine\Remove(  91                 App::make("deployment.machine")  92             );  93         });  94   95         $this->commands(  96             "deployment.command.asset.combine",  97             "deployment.command.asset.minify",  98             "deployment.command.asset.watch",  99             "deployment.command.distribute.prepare", 100             "deployment.command.distribute.sync", 101             "deployment.command.machine.add", 102             "deployment.command.machine.remove" 103         ); 104     } 105  106     public function boot() 107     { 108         $this->package("formativ/deployment"); 109         include __DIR__ . "/../../helpers.php"; 110         include __DIR__ . "/../../macros.php"; 111     } 112  113     public function provides() 114     { 115         return [ 116             "deployment.asset.manager", 117             "deployment.asset.watcher", 118             "deployment.distribution", 119             "deployment.machine", 120             "deployment.command.asset.combine", 121             "deployment.command.asset.minify", 122             "deployment.command.asset.watch", 123             "deployment.command.distribute.prepare", 124             "deployment.command.distribute.sync", 125             "deployment.command.machine.add", 126             "deployment.command.machine.remove" 127        ]; 128     } 129 } 

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.

This is the first time we’re using the boot() method. The call to $this->package() is so that we can have views, language files and other resources mapped to our package. We would be able to call them by prefixing the view names, language keys etc. with the name of the package. We’re not going to be using that this time round; but it’s important to know how.

We also include helpers.php and macros.php in the boot method.

 1 <?php  2   3 namespace Formativ\Deployment\Command\Asset;  4   5 use Formativ\Deployment\Asset\ManagerInterface;  6 use Illuminate\Console\Command;  7 use Symfony\Component\Console\Input\InputArgument;  8 use Symfony\Component\Console\Input\InputOption;  9  10 class Combine 11 extends Command 12 { 13     protected $name = "deployment:asset:combine"; 14  15     protected $description = "Combines multiple resource files."; 16  17     protected $manager; 18  19     public function __construct(ManagerInterface $manager) 20     { 21         parent::__construct(); 22         $this->manager = $manager; 23     } 24  25     // ... 26 } 

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

 1 <?php  2   3 namespace Formativ\Deployment\Command\Asset;  4   5 use Formativ\Deployment\Asset\ManagerInterface;  6 use Illuminate\Console\Command;  7 use Symfony\Component\Console\Input\InputArgument;  8 use Symfony\Component\Console\Input\InputOption;  9  10 class Minify 11 extends Command 12 { 13     protected $name = "deployment:asset:minify"; 14  15     protected $description = "Minifies multiple resource files."; 16  17     protected $manager; 18  19     public function __construct(ManagerInterface $manager) 20     { 21         parent::__construct(); 22         $this->manager = $manager; 23     } 24  25     // ... 26 } 

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

 1 <?php  2   3 namespace Formativ\Deployment\Command\Asset;  4   5 use Formativ\Deployment\Asset\ManagerInterface;  6 use Illuminate\Console\Command;  7 use Symfony\Component\Console\Input\InputArgument;  8 use Symfony\Component\Console\Input\InputOption;  9  10 class Watch 11 extends Command 12 { 13     protected $name = "deployment:asset:watch"; 14  15     protected $description = "Watches files for changes."; 16  17     protected $manager; 18  19     public function __construct(ManagerInterface $manager) 20     { 21        parent::__construct(); 22        $this->manager = $manager; 23     } 24  25     // ... 26 } 

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

 1 <?php  2   3 namespace Formativ\Deployment\Command\Distribute;  4   5 use Formativ\Deployment\DistributionInterface;  6 use Illuminate\Console\Command;  7 use Symfony\Component\Console\Input\InputArgument;  8 use Symfony\Component\Console\Input\InputOption;  9  10 class Prepare 11 extends Command 12 { 13     protected $name = "deployment:distribute:prepare"; 14  15     protected $description = "Prepares the distribution folder."; 16  17     protected $distribution; 18  19     public function __construct( 20         DistributionInterface $distribution 21     ) 22     { 23         parent::__construct(); 24         $this->distribution = $distribution; 25     } 26  27     // ... 28 } 

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

 1 <?php  2   3 namespace Formativ\Deployment\Command\Distribute;  4   5 use Formativ\Deployment\DistributionInterface;  6 use Illuminate\Console\Command;  7 use Symfony\Component\Console\Input\InputArgument;  8 use Symfony\Component\Console\Input\InputOption;  9  10 class Sync 11 extends Command 12 { 13     protected $name = "deployment:distribute:sync"; 14  15     protected $description = "Syncs changes to a target."; 16  17     protected $distribution; 18  19     public function __construct( 20         DistributionInterface $distribution 21     ) 22     { 23         parent::__construct(); 24         $this->distribution = $distribution; 25     } 26  27     // ... 28 } 

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

 1 <?php  2   3 namespace Formativ\Deployment\Command\Machine;  4   5 use Formativ\Deployment\MachineInterface;  6 use Illuminate\Console\Command;  7 use Symfony\Component\Console\Input\InputArgument;  8 use Symfony\Component\Console\Input\InputOption;  9  10 class Add 11 extends Command 12 { 13     protected $name = "deployment:machine:add"; 14  15     protected $description = "Adds the current machine to an environment."; 16  17     protected $machine; 18  19     public function __construct(MachineInterface $machine) 20     { 21         parent::__construct(); 22         $this->machine = $machine; 23     } 24  25     // ... 26 } 

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

 1 <?php  2   3 namespace Formativ\Deployment\Command\Machine;  4   5 use Formativ\Deployment\MachineInterface;  6 use Illuminate\Console\Command;  7 use Symfony\Component\Console\Input\InputArgument;  8 use Symfony\Component\Console\Input\InputOption;  9  10 class Remove 11 extends Command 12 { 13     protected $name = "deployment:machine:remove"; 14  15     protected $description = "Removes the current machine from an environment."; 16  17     protected $machine; 18  19     public function __construct(MachineInterface $machine) 20     { 21         parent::__construct(); 22         $this->machine = $machine; 23     } 24  25     // ... 26 } 

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:

1 php artisan config:publish formativ/deployment --path=workbench/Formativ/Deployme\ 2 nt/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:

 1 {  2     "name"        : "formativ/deployment",  3     "description" : "All sorts of cool things with deployment.",  4     "authors"     : [  5         {  6             "name"  : "Christopher Pitt",  7             "email" : "[email protected]"  8         }  9     ], 10     "require" : { 11         "php"                : ">=5.4.0", 12         "illuminate/support" : "4.0.x" 13     }, 14     "autoload" : { 15         "psr-0" : { 16             "Formativ\\Deployment" : "src/" 17         } 18     }, 19     "minimum-stability" : "dev" 20 } 

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 chapter, but it will be the subject of a future chapter; which will demonstrate the benefits of dependency injection as it relates to unit testing.

Назад: API
Дальше: Real Time Chat