Eloquent inside Codeigniter

Ever the diplomat, I’ve been doing my best to use components of the wonderful Laravel in CodeIgniter apps that I’ve been working on for clients. Whilst I’ve switched almost completely to using Laravel as of late, I still work on legacy CI apps (and still work with clients who use CI for new apps, for that matter.) At any rate, Laravel is designed in a modular fashion, allowing me to plug my favourite components into CodeIgniter and use them without too much friction.
The most important of these components is, of course, the wonderful Eloquent ORM, which has provided me with as much joy as ActiveRecord did in the land of Ruby. Thankfully, getting Eloquent running inside CodeIgniter was easy. I’ll assume you have Composer installed – if you don’t, you really should. You’ll need to add Laravel’s database component to your composer.json (which includes Eloquent):
{
    "require": {
        "illuminate/database": "4.2.6"
    }
}
I also use Composer’s autoloader as a broad replacement for CodeIgniter’s CI_Loader, so I’ve added in some folders into my classmap. You don’t need to do this, so don’t worry too much. I use application/classes as a dump-spot for random objects I need to use before I find a more suitable place for them, so don’t worry too much about that, either:
{
    "autoload": {
        "classmap": [
            "application/classes",
            "application/core",
            "application/models",
            "application/presenters",
            "application/libraries"
        ]
    }
}
Install the dependencies and generate the autoload files:
$ composer install
You’ll also need to require the autoloader in order to access anything – I do this right at the top of my index.php file:
require_once '../vendor/autoload.php'; 
I keep the Laravel database component setup code inside my config/database.php config file. Not a particularly good idea, I know, so feel free to move it somewhere more appropriate (perhaps a hook? or potentially a library?). We need to call on the autoloader to give us access to the database component:
use Illuminate\Database\Capsule\Manager as Capsule;
We then create a new instance of the component and add a new connection:
$capsule = new Capsule;

$capsule->addConnection(array(
    'driver'    => 'mysql',
    'host'      => $db['default']['hostname'],
    'database'  => $db['default']['database'],
    'username'  => $db['default']['username'],
    'password'  => $db['default']['password'],
    'charset'   => 'utf8',
    'collation' => 'utf8_unicode_ci',
    'prefix'    => $db['default']['dbprefix'],
));
We want this connection to be used as Eloquent’s connection, so we should set it globally. While we’re there, let’s boot Eloquent as well:
$capsule->setAsGlobal();
$capsule->bootEloquent();
We can now use Eloquent by using the namespace in our model files and extending our models from the class. After that, everything works just as it does in Laravel:
use \Illuminate\Database\Eloquent\Model as Eloquent;

class User extends Eloquent { }

$users = User::all();
One thing that irked me about this setup was the fact that my database queries didn’t show up in my profiler. The CI profiler is an old but trusty tool for debugging and figuring out why pages are loading slowly, or how best to optimise your application and reduce duplicated queries. Back in our config/database.php, under the call to bootEloquent(), we will grab an instance of Laravel’s event dispatcher:
$events = new Illuminate\Events\Dispatcher;
We can now listen to the illuminate.query event, which is triggered every time a query is run. Inside the callback, we want to grab the query text and insert the bindings into the text for display (for more information about bindings, read here):
$events->listen('illuminate.query', function($query, $bindings, $time, $name)
{
    // Format binding data for sql insertion
    foreach ($bindings as $i => $binding)
    {   
        if ($binding instanceof \DateTime)
        {   
            $bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
        }
        else if (is_string($binding))
        {   
            $bindings[$i] = "'$binding'";
        }   
    }       

    // Insert bindings into query
    $query = str_replace(array('%', '?'), array('%%', '%s'), $query);
    $query = vsprintf($query, $bindings); 
Now we’ve got a displayable query (and, incidentally, a runnable query, which is useful for debugging), we can add it to the profiler. The profiler looks to the CI database library object for the query (and its associated running time). Since CI_DB_driver uses the var keyword, we can access it publicly and shove the query onto the end. CodeIgniter’s shitty design may not be particularly robust, but damn it is flexible:
    // Add it into CodeIgniter
    $db =& get_instance()->db;

    $db->query_times[] = $time;
    $db->queries[] = $query;
});
Now we have our listener, we just need to tell the DB component to notify our dispatcher of any events that may occur:
$capsule->setEventDispatcher($events);
And violà, we have a query log.

Eager-loading Eloquent polymorphic associations

Laravel’s Eloquent doesn’t allow for eager loading of polymorphic relationships. While this is a very niche issue, it has come up more than once now and I know that I’ll see it again. Rather than just put up with it and face a lot of redundant queries, I decided to write a small class to handle the fetching of polymorphic associations.
The class works like this: accepting an array of models and the name of the association, pull out all the related types and their IDs and then fetch each related object. After we’ve got the related objects, it’s just a matter of setting them as related objects, as if Eloquent has populated them itself.
The code is simple:
class AssocFetcher
{
    public function __construct($assoc)
    {
        $this->assoc = $assoc;
    }

    public function fetch($models)
    {
        $relationships = array();
        $objects = array();

        $typeCol = "{$this->assoc}_type";
        $idCol = "{$this->assoc}_type";

        foreach ($models as $model)
            $relationships[$model->$typeCol][] = $model->$idCol;

        foreach ($relationships as $class => $ids)
            $objects[$class] = $class::findMany($ids);

        foreach ($models as $model)
            $model->setRelation($this->assoc, $objects[$model->$typeCol]->find($model->$idCol));

        return $models;
    }
}
I can then use it in my controller like so:
$assocs = new AssocFetcher('subject');
$changes = $assocs->fetch(Change::all());
With the above code, each of my Change objects will be populated with a subject relationship, based on the polymorphic subject_type and subject_id columns. This way, I can use my polymorphic related object with ease:
foreach ($changes as $change)
    echo $change->subject->name;
With this class, I’ve gone down from O(1 + n) efficiency (1 query to get the changes, n queries to get each change’s subject) to O(1 + a), where a is the number of different types of associated object. It can be the difference between 4 queries and 400, so it’s well worth investigating ways of reducing the query count on the page by being a bit cleverer and a bit more forward-thinking.

Comments

Popular posts from this blog

Laravel 5.8 Files Folders Permissions and Ownership Setup

Bootstrap Tags Input

20 excellent contact pages