A Small But Troubling Trend With Laravel Packages

Edit: I have updated the “Correct Service Provider” with code provided by William Cahill-Manley in his comments. Thanks for the contribution William!

Watching Laravel 4 grow has been spectacular. Its great to see so many libraries building service providers and facades for Laravel to make them feel more native to the framework. That said, I have noticed small but troubling trend with some packages available for Laravel.

The Problem

The problem lies in how configuration is handled for the package. Configuration should not be done within the service provider. This concept is not unique to Laravel. However, for this post I will be discussing Laravel packages specifically. Below is simple example of one such package.

At a glance, this file doesn’t appear to require configuration. If you look deeper (or refer to the documentation) you will find the developer can change the geocoding provider and adapter used for geocoding services. The ability to pick which geocoding provider and adapter to use is tremendous in allowing developers to build applications to do exactly what they want. The problem lies in the implementation of such configuration.

As you can see above, to use a different geocoding provider and/or adapter the developer must adjust the service provider to instantiate an alternate geocoding provider and/or adapter. Let’s say the package receives an update and the developer pulls down that update via composer. What happens to the “configuration”? It gets wiped out. Now the application is broken and the developer must update the service provider again. Wash. Rinse. Repeat.

The Solution

To solve this issue the package should (and now does) implement a publishable configuration file. The configuration file allows the developer to specify which geocoding provider and adapter to use for geocoding services.

So what do the new service provider and configuration look like? Take a look.

<?php
 
return array(
    'provider' => 'Geocoder\Provider\FreeGeoIpProvider',
    'adapter' => 'Geocoder\HttpAdapter\CurlHttpAdapter'
);
<?php

/**
 * This file is part of the GeocoderLaravel library.
 *
 * (c) Antoine Corcy <contact@sbin.dk>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Toin0u\Geocoder;

use Geocoder\Geocoder;
use Illuminate\Support\ServiceProvider;

/**
 * Geocoder service provider
 *
 * @author Antoine Corcy <contact@sbin.dk>
 */
class GeocoderServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = false;

    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot()
    {
        $this->package('toin0u/geocoder-laravel');
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerAdapter();
        $this->registerProvider();
        $this->registerGeocoder();        
    }

    public function registerAdapter() {
        $this->app->singleton( 'geocoder.adapter',
            $this->app['config']->get('geocoder-laravel::adapter', 'Geocoder\HttpAdapter\CurlHttpAdapter')
        );
    }
    public function registerProvider() {
         $this->app->singleton( 'geocoder.provider',
            $this->app['config']->get('geocoder-laravel::provider', 'Geocoder\Provider\FreeGeoIpProvider')
        );
    }
    public function registerGeocoder() {
        $this->app['geocoder'] = $this->app->share(function($app) {
            $geocoder = new Geocoder;
            $geocoder->registerProvider($app['geocoder.provider']);

            return $geocoder;
        });
    }

    
    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return array('geocoder', 'geocoder.adapter', 'geocoder.provider');
    }
}

Now you may ask, “How do you configure this package?”

Simple. Run the following command after installing the package via composer.

$ php artisan config:publish toin0u/geocoder-laravel

After the configuration has been published the developer can edit it to use the geocoding provider and adapter of their choice. The published configuration file will allow the developer to update via composer without worrying about breaking their application (from a configuration stand point at least).

Summary

Configurable packages are a great thing. That said, let’s wear our “best practice hats” and make sure we keep the configuration out of Composers reach.

6 thoughts on “A Small But Troubling Trend With Laravel Packages

  1. The Geocoder package comes with a lot of providers and adapters so even you configure the package maybe you need to “override” the geocoder.provider :) That’s why in this case it’s was not mandatory IMHO.

    In the other hand, my other package for DigitalOcean lets the user to configure the package and it’s obviously mandatory because you need to set your private credentials.

    But you’re totaly right, it’s a best practice to make configurable packages :)

    • Thanks for reading! Your package may not have been the best example of the point I was trying to make. I apologize for that. That said, I hope the point is still making its way through to others! :)

  2. Hey Sean,

    For the most part I have to agree with you, but there are a few things I would change.

    1) While you are allowing configuration of the provider and adapter, you are still restricting the namespace. This would force any class to be provided with the package itself, if someone wanted to extend them, they would still have to override the service provider. Solution, use the fully namespace’d classes within the configuration.

    2) The singleton method is a lot easier to use than the share method, when you don’t need to do anything besides instantiating the class.

    3) While you’ve gone on to using a config value for these classes, sometimes the service provider still needs to be extended. What if someone wanted to register a custom provider, which they could do using your configuration option, but they need to do something else with it? Keeping everything within the register() method means everything would need to be repeated. Within Laravel’s own service providers, it breaks things up into multiple methods which could each be overridden if need be.

    An example adaptation of this is https://gist.github.com/Kindari/6636837

    Thanks for your contribution.

    • Thanks for sharing this! I agree the singleton method is a lot easier to use, especially in the case of this package. I have updated the examples in this post to reflect the code you provided. Thanks for contributing and im sure that toin0u would love to implement your gist in to this package.

  3. Hi all :)

    @Sean No worries ! I think it’s great that you make blog posts for the Laravel – PHP community!
    @William Thanks for your gist ! I forgot the singleton method in Laravel :S And yes, we need to implement your gist :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Current day month ye@r *