Stidges' Blog

Extending The Connection Class In Laravel

Posted on in Development

Sometimes you may wish to extend the database Connection class in Laravel. Maybe you want to customize the internal workings. In my case I needed to alter the database results for every query, whether it's through the Fluent Query Builder or Eloquent. I needed to do this to make my package, Laravel DB Normalizer, work as smoothly as possible.

Researching The Possibilities

The problem with extending the default Connection class is that it is pretty much 'hard wired' into Laravel's database package. And even worse, it has a bunch of sub classes extending it. In my particular use case, I wanted to replace the root Connection class, instead of overriding all of it's sub classes. After some digging around through the database package I finally found the piece of code I was looking for; it is placed in the ConnectionFactory class. In particular, it was this piece of code:

// From Illuminate/Database/Connectors/ConnectionFactory.php
// L211-214

if ($this->container->bound($key = "db.connection.{$driver}"))
{
    return $this->container->make($key, array($connection, $database, $prefix, $config));
}

Great! This part tells us that we can bind a custom implementation of a database driver into the Container, and it will resolve that in favor of the default class! But, does this mean we still have to override all of the sub-classes for this to work? I am afraid the answer is yes. The lucky part is that the code in these sub-classes is pretty thin and not very likely to change.

Extending The Connection Class

But we're getting a little ahead of ourselves here. We initially only wanted to extend or replace the Connection class, so let's do that now! First, let's make an empty class that extends the base Connection class in our namespace:

// From app/src/Stidges/Database/Connection.php

namespace Stidges\Database;

use Illuminate\Database\Connection as BaseConnection;

class Connection extends BaseConnection
{

}

Now we have a place where we can place our own code. I'll use my package as an example here to illustrate what we could do in the class. To 'hijack' all the results in the database, I was particularly interested in the select and selectOne method. These are the two methods that are called on every query to the database. In my case, the methods work fine, I just want the results to work with. So let's set that up.

// From app/src/Stidges/Database/Connection.php

public function select($query, $bindings = array())
{
    // First, let's call the parent method to get the results
    // This method was already doing a fine job collecting
    // the query's results, so why shouldn't we use it?
    $records = parent::select($query, $bindings);

    // Now we can work with the results! In my case, I am
    // working with a Normalizer class that takes in an
    // array, which gets turned into unified object.
    return with(new Normalizer)->normalize($records);
}

public function selectOne($query, $bindings = array())
{
    // This method is pretty much straight forward. Call the
    // parent::select() method. If it returns any results
    // normalize the first result or else return null.
    $records = parent::select($query, $bindings);

    if (count($records) > 0)
    {
        return with(new Normalizer)->normalize(reset($records));
    }

    return null;
}

Cool, the 'easy' part is done. We have extended the Connection class and made it so that it alters the results of any query made to the database. But now, how do we make it work with all those sub classes? Let's dig into that.

Setting Up The Sub Classes

First things first, we have to move all the Connection sub-classes into our own namespace, so that we can customize them. Copy the following classes into your namespace:

  • Illuminate\Database\MySqlConnection.php
  • Illuminate\Database\PostgresConnection.php
  • Illuminate\Database\SQLiteConnection.php
  • Illuminate\Database\SqlServerConnection.php

I have placed these under the same namespace as our custom Connection class. In my case this is Stidges\Database. If you do this, then the classes will automatically extend our custom Connection class, as it is placed in the same namespace! You have to go over the sub-classes, because some of the use relative namespaces to resolve classes. For example, the MySqlConnection has the following code:

// From Illuminate/Database/MySqlConnection.php

protected function getDefaultPostProcessor()
{
    return new Query\Processors\MySqlProcessor;
}

This won't work, because we don't have this class. To fix this, import the class and replace the code like this:

// From app/src/Stidges/Database/MySqlConnection.php

use Illuminate\Database\Query\Processors\MySqlProcessor;

// ...

protected function getDefaultPostProcessor()
{
    return new MySqlProcessor;
}

Great, we have extended the Connection class, imported the sub classes into our own namespace and fixed some namespace issues in these classes. But now, how can we make our app use our custom extended classes? Answer: bind them into the container!

Binding The Sub Classes Into The Container

There are a lot of places that you can set up your bindings. The Laravel documentation suggests a couple of options for this. But as these are quite a few bindings we have to set up, I prefer using a Service Provider to have it all in one place.

From the piece of code I posted at the beginning of this post we can see that it passes along a list of parameters that we can use to set up the classes. I am going to show how to set up the Service Provider class and set up the MySql binding. All the other bindings will be the same as this one, only with different keys and classes!

// From app/src/Stidges/Database/DatabaseServiceProvider.php

namespace Stidges\Database;

use Stidges\Database\MySqlConnection;
use Illuminate\Support\ServiceProvider;

class DbNormalizerServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        // Register the MySql connection class as a singleton
        // because we only want to have one, and only one,
        // MySql database connection at the same time.
        $this->app->singleton('db.connection.mysql', function ($app, $parameters) {
            // First, we list the passes parameters into single
            // variables. I do this because it is far easier
            // to read than using it as eg $parameters[0].
            list($connection, $database, $prefix, $config) = $parameters;

            // Next we can initialize the connection.
            return new MySqlConnection($connection, $database, $prefix, $config);
        });
    }
}

Now you can set up the rest of the connections in exactly the same way, changing only the key and the class inside the closure!

Wrapping Up

The last thing we have to do is register our Service Provider with our application. This can be done using the app/config/app.php file. All we have to do is add it to the providers array:

'providers' => array(
    // ...
    'Stidges\Database\DatabaseServiceProvider.php'
),

And that's it. Now all our Fluent Query Builder results will be intercepted and altered!