Welcome Guest, Not a member yet? Register   Sign In
Simple module system for CI3
#1

(This post was last modified: 03-31-2016, 08:24 AM by josepostiga. Edit Reason: Typo )

Hey devs. So, I've been working on a project that requires the use of multiple CI application folder: one for admins, other for the customers backoffice and a third one for the customers applications. It's a SaaS application and I didn't want to be using different controllers folders for each different scenario because, well, their basically 3 different apps!

During development, I've reached a point where I was repeating myself by copying and pasting the same helpers, configs, models, etc between those application folders. So, naturally, I checked the so called application packages, as described here: https://codeigniter.com/user_guide/libra...n-packages

Unfortunately, that functionality only works inside each application folder and I wanted that outside those folders, so that I could share the code amongst every application. Composer wasn't a valid option here (I know you were thinking that by now, weren't you? Tongue ). I wanted to use CI autoloading for any config, helpers, models, and (inception alert) to autoload other packages.

So I went straight to the CI autoloader method, on the Core Loader class, and made a few changes. Simple changes, that would made possible for CI to handle packages outside the application folders. But enough talking. Let's get dirty.

Step 1. The modules folder structure.
I wanted something along this lines:

/project-root/modules <- The modules base folder
/project-root/modules/module-1 <- A wild module

After this, the structure should be as documented here: https://codeigniter.com/user_guide/libra...n-packages, so CI should expect the config, helpers, models, etc...

But then I though that it would be nice to have a "global" autoload config file to, well, autoload a specific set of modules. I could, in the end, still declare each module to autoload in each CI application instance but it could get messy when I need to remove, edit or update a module that's being autoloaded in X applications at some point down the road. That would be cool, I though, and keep things simple and organised.

So, in addition to the structure shown above, CI should check for an autoload.php file on the modules root folder, like this:

/project-root/modules/autoload.php

The specific of that file is that any declared module to load should not have the folder structure declared. Further explanation on this on the next step.

Step 2. The changes on the Core Loader class.
Inspecting the code was pretty easy. The code is very well written and pretty self explanatory. The block modified was the _ci_autoloader method. The original code is as follow:

PHP Code:
protected function _ci_autoloader()
{
        if (
file_exists(APPPATH.'config/autoload.php'))
        {
            include(
APPPATH.'config/autoload.php');
        }

        if (
file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'))
        {
            include(
APPPATH.'config/'.ENVIRONMENT.'/autoload.php');
        }

        if ( ! isset(
$autoload))
        {
            return;
        }

        
// Autoload packages
        
if (isset($autoload['packages']))
        {
            
$this->add_package_path($package_path);
        }

        
// Load any custom config file
        
if (count($autoload['config']) > 0)
        {
            foreach (
$autoload['config'] as $val)
            {
                
$this->config($val);
            }
        }

        
// Autoload helpers and languages
        
foreach (array('helper''language') as $type)
        {
            if (isset(
$autoload[$type]) && count($autoload[$type]) > 0)
            {
                
$this->$type($autoload[$type]);
            }
        }

        
// Autoload drivers
        
if (isset($autoload['drivers']))
        {
            
$this->driver($autoload['drivers']);
        }

        
// Load libraries
        
if (isset($autoload['libraries']) && count($autoload['libraries']) > 0)
        {
            
// Load the database driver.
            
if (in_array('database'$autoload['libraries']))
            {
                
$this->database();
                
$autoload['libraries'] = array_diff($autoload['libraries'], array('database'));
            }

            
// Load all other libraries
            
$this->library($autoload['libraries']);
        }

        
// Autoload models
        
if (isset($autoload['model']))
        {
            
$this->model($autoload['model']);
        }
    } 

The part about the packages is the one we're interested in, and that's the one I've edited. The final code:

PHP Code:
protected function _ci_autoloader($path '')
    {
 
       $path $path rtrim($path'/').'/'APPPATH;

        if (
file_exists($path.'config/autoload.php'))
        {
            include(
$path.'config/autoload.php');
        }

        if (
file_exists($path.'config/'.ENVIRONMENT.'/autoload.php'))
        {
            include(
$path.'config/'.ENVIRONMENT.'/autoload.php');
        }

        if ( ! isset(
$autoload))
        {
            return;
        }

        
// Autoload packages
        
if (isset($autoload['packages']))
        {
            foreach (
$autoload['packages'] as $package_path)
            {
 
               // checks for a package global autoload file
 
               if (! file_exists($path.'autoload.php')) {
 
                   $this->add_package_path($package_path)->_ci_autoloader($package_path);
 
               }

 
               require_once($package_path.'/autoload.php');

 
               if (isset($autoload['modules']))
 
               {
 
                   // autoloads each module
 
                   foreach ($autoload['modules'] as $module)
 
                   {
 
                       $module_path $package_path.'/'.$module;

 
                       $this->add_package_path($module_path)->_ci_autoloader($module_path);
 
                   }
 
               }
            }
        }

        
// Load any custom config file
        
if (count($autoload['config']) > 0)
        {
            foreach (
$autoload['config'] as $val)
            {
                
$this->config($val);
            }
        }

        
// Autoload helpers and languages
        
foreach (array('helper''language') as $type)
        {
            if (isset(
$autoload[$type]) && count($autoload[$type]) > 0)
            {
                
$this->$type($autoload[$type]);
            }
        }

        
// Autoload drivers
        
if (isset($autoload['drivers']))
        {
            
$this->driver($autoload['drivers']);
        }

        
// Load libraries
        
if (isset($autoload['libraries']) && count($autoload['libraries']) > 0)
        {
            
// Load the database driver.
            
if (in_array('database'$autoload['libraries']))
            {
                
$this->database();
                
$autoload['libraries'] = array_diff($autoload['libraries'], array('database'));
            }

            
// Load all other libraries
            
$this->library($autoload['libraries']);
        }

        
// Autoload models
        
if (isset($autoload['model']))
        {
            
$this->model($autoload['model']);
        }
    } 

As you can see, I've added a recursion loader when checking for the packages. It would check for an autoload.php file, handle that, go to next cycle. Easy!

And here's the autoload.php file:

PHP Code:
<?php

defined
('BASEPATH') OR exit('No direct script access allowed');
/*
| -------------------------------------------------------------------
| AUTO-LOADER
| -------------------------------------------------------------------
| This file specifies which modules should be loaded by default.
|
| -------------------------------------------------------------------
| Instructions
| -------------------------------------------------------------------
| The current modules dir name is used as base dir, so there's no
| need to use it!
|
| Prototype:
|
|  $autoload['modules'] = array('module1_dir_name');
|
*/
$autoload['modules'] = array('); 

Conclusions
Of course this kind of hack to the core is a little messy, and needs careful handle when upgrading versions! But, finally, I could use my modules anywhere on those various CI application instances. I think it deserves the additional 2 minutes on each version upgrade.

Also, I know this solution may have some limitations but we're all devs, and we can make things work to our own needs. I just wanted to share this mod with the community and I'd love to discuss it and know what's your thoughts on this regard. Big Grin
Best regards,
José Postiga
Senior Backend Developer
Reply
#2

I used a custom module feature within APPPATH for TendooCMS, using config.xml file to store all module informations, such as version, namespace, name, description, author, language path, main file. You can test out this free app to see what i'm talking about http://nexo.tendoo.org
NexoPOS 2.6.2 available on CodeCanyon.
Reply
#3

Another approach - just as something else to consider - is if the Backoffice needs something from the Admins - it gets it just like an API would - but in this case its an "internal" API. i've been surprised by how quickly you can wrap some data up in a JSON array and send it off with a few lines of CURL. you can even wrap database result objects in JSON and codeigniter treats them just the same as if they came from a local model. and you don't need a "rest server" - if this is a private API then you just need two lines of code in the receiving application controller method.

PHP Code:
     $request file_get_contents('php://input');
 
   $customer json_decode($request); 

it also opens up a bunch of options in terms of staging, having different applications on different servers, etc etc.
Reply




Theme © iAndrew 2016 - Forum software by © MyBB