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):
              
              
              
            
            
            
            
            
            
            
            
            
            
              
            
                
        
          
        
          
            
              
                
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:
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
Post a Comment