Codeigniter 3 - Third_party and controllers / loading ressources - php

I'd like to use the controllers from the /third_party/myapp/controllers folder without having to move them to the /application/controllers one
It doesn't seem possible (at least not without hacking the core). Though the question concerns loading model, limitations are mentioned here
I don't feel like using HMVC - as stated in the solution proposed there, cause I don't need any other functionality than avoiding to multiply file transfer in CI base folders (i don't really need the 'hierarchical' part of it)
But I don't really get it... if one declares its third_party app, CI will load resources from those folders (as stated by the doc)
config/
helpers/
language/
libraries/
models/
[metaphysical] Why wouldn't it be possible to simply load controllers as well ?
[pragmatic] is it a good idea to try to hack the core system or should I stay on the "HMVC or do copy your files" choice ?
[optimistic] do someone have a solution ?
EDIT
While looking further to trajchevska tip in another installation of CI, I tried to implement DFriend example of spl_register. I read several times the manual and this answer as well :
So far I understood that spl_autoload_register is triggered when a class is called, which allows to skip their manual loading
So I've tried simply adding the spl_autoload_register at the end of the config file in application/config/config.php, expecting it to print Auto load ClassName when a first class is called - a bit naïve, I know.
but from what I understood (thanks to DFriend), this spl_autoload_register would NOT work with controllers, as CI will always check if the file exists (in its own path) before trying to declare the controller.
It seems on the other hand that it could work with other classes (core, model ... see DFriend answer below)
Conclusion
So, after a few hairs pulled out of my head, I decided to try the HMVC extension for CI - MX from WireDesign. It solved my problem of loading any resources from different folder, along with bringing a brand new scope of shared classes and their attached issues (what is available where ?).
As it's the beginning the project I had the opportunity to switch to the "hierarchical" side, otherwise I'd followed Dfriend's advice of
going with the flow and putting controllers for third-party packages
where CI expects them to be

You could use PHP's spl_autoload_register to implement an autoloader. A google search will reveal multiple approaches for using one within the CI framework.
One that works and is often described is to register the autoloader in config.php or maybe a site-specific constants file. Such a solution is to put something like this in one of the mentioned files.
spl_autoload_register(function($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.`/third_party/myapp/controllers/`.$class.'.php'))
{
require_once $file;
}
}
});
You can use hooks if you want to avoid hacking config.php or some other file. Here's one way to do that.
application/config/config.php
$config['enable_hooks'] = TRUE;
application/config/hooks.php
$hook['pre_system'][] = array(
'class' => '',
'function' => 'register_autoloader',
'filename' => 'Auto_load.php',
'filepath' => 'hooks'
);
application/hooks/Auto_load.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
function register_autoloader()
{
spl_autoload_register('my_autoloader');
}
function my_autoloader($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.`/third_party/myapp/controllers/`.$class.'.php'))
{
require_once $file;
}
}
}
Addendum
On further digging and consideration the above will not work because CI insists that Controllers be in the application/controllers directory or a subdirectory thereof.
PHP will run a registered autoloader when you try to use a class/interface which hasn’t been defined yet. Controllers are php classes so you might expect an autoloader to come to the rescue. But CI is very explicit about where it looks for controller files. If the file is not found then it never attempts any calls to the class. Instead it abandons all hope and immediately issues an 404 error. The autoloader never gets a chance to look for the file.
The answer to the metaphysical question is that CI's routing and URL parsing are tightly coupled to the file storage structure making the desired functionality impossible. (without some serious core hacking)
The answer to the pragmatic question is very subjective. I'm not fond of the HMVC approach and personally would opt for going with the flow and putting controllers for third-party packages where CI expects them to be.
Start: Serious diversion from question at hand
So, what good is registering an autoloader in the CI environment?
Controllers are defined like so.
class Home extends CI_Controller{ ...
The class being extended, CI_Controller, is a core class. If you want to extend this core class for use by multiple other controllers you can use the CI convention of prepending the class name with "MY_". e.g.
class MY_Controller extends CI_Controller
(
//class implementation
}
and then use it to define actual controllers using the extended core class
class Home extends MY_Controller{ ...
The problem is that you can only extend the core CI_Controller once using this convention. What if you have a need for more than one extension to the CI_Controller core class? One solution is registering an autoloader. (A good discussion of this and other ways to overcome the MY_Controller monopoly is HERE.)
An autoloader for these purposes could look this.
spl_autoload_register(function($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.'libraries/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'models/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'core/'.$class.'.php'))
{
require_once $file;
}
}
}
Note that the above also allows you to extend models from other models.
End: Serious diversion from question at hand

I assume it's because of the routing process - the CI Router checks whether a corresponding controller exists in the application folder, or a respective route is defined in the config. If none of them is found, 404 is returned.
You can override the Router file to enable loading controllers from other locations. In the core folder, create a MY_Router file that will extend the CI_Router. Then extend the _validate_request function, to allow loading controller files from different path than the default.
UPDATE: So, I've spent some time on this and didn't manage to extend the Router to get the controller file from the third party folder. It's easy to do when it comes to subfolders of the default controllers directory, but setting up a custom directory for reading the controllers is not that straightforward and unfortunately, I couldn't find any documentation on that.

Related

Curiosity: Will too much autoload in codeigniter make application slower?

I'm using php and codeigniter, It has autoload class.
My first question is,
If I use autoload class to load all model will it make my application slower? Or is there no effect?
My second question :
Which one is better and faster, loading all model you need using autoload class, Or load only some models you need in a class function?
1) Autoload class will obviously make your application slower. because it uses php4 require function to load files. there are some hacks to use php 5 autoloading feature. Hope, new owner of codeigniter will add add support for autoloading.
2) It is best to use load specific models rather than autoload. in previous point I stated the reason behind this. basically it is good practice to load only the needed model, helper, library and assets. it assures that you use minimum time and memory.
I use autoload. and its working like a charm with no significant impact on loading time.
Method
Add this peace of Code on top of any libraries/models that you use in you CI autoload.php
example for me my config/autoload.php looks like
$autoload['libraries'] = array('database','door','acl','form_validation','notify');
and in libraries/Door.php i added
<?php//libraries/Door.php
function multi_auto_require($class) {
#var_dump("requesting $class");
if(stripos($class, 'CI') === FALSE && stripos($class, 'PEAR') === FALSE) {
foreach (array('objects','core') as $folder){//array of folders to look inside
if (is_file(APPPATH."{$folder}/{$class}.php")){
include_once APPPATH."{$folder}/{$class}.php";
}
}
}
}
spl_autoload_register('multi_auto_require');
class Door {
i added this snippet just above class Door{ in this way this snippet will run on every time codeigniter loads door library.
Testing and benchmarking
now For benchmarking i tested this code in a page with 17 DB queries from 8 Autoloaded Objects from 2 different folder with array of 3 folders to look in.
Result
using above mentioned method Vs include_once 'classlocation.php' for all project classes,
the average of 10 page refreshs for both methods where around 0.6sec;
so you can see there is no significant difference between both methods.
yet although i didn't test it on pages that doesn't use all class's, yet im sure autoloading makes my CI life much better and i'm happy with it.

Where can I put my global code in Kohana 3.3?

Say I want a constant, a function or a class to be available in all my models, views and controllers code (within a particular web site (application)). Where can I put them? I don't mind importing them explicitly in every file I need them in but I don't know how (simple require_once does not seem to work).
You can put them in the vendor folder (in application/vendor or modules/MOD/vendor). Then you can load it like this:
require Kohana::find_file('vendor', 'folder/file','ext');
You can read up more on this in the user guide
Now, it should be stated you should in general not use functions or globals.
I declare Constants in Bootstrap.php and create my own Helpers for general functions under application/classes/Helpers.
If you need to integrate a third party library into Kohana or want to make code available to other Kohana users consider creating a module instead.
You can define all your constants in new php file and place it in the application/classes directory. In your Template controller or in the main controller like Welcome or Website, just place the code in the __constructor()
require_once Kohana::find_file( 'classes', 'filename' );
I suggest you to put your constant variables in the bootstrap.php file because this is loaded by the framework before every request.
You can simply put your classes in the root of the classes directory, the framework will find.
This worked well for me...
In application/config/constants.php:
define('SOME_COOL_CONSTANT', 'foo');
return array();
In index.php:
Kohana::$config->load('constants');
SOME_COOL_CONSTANT should then be available globally.

PHP AWS SDK autoloader conflicts with Codeigniter

I'm using Codeigniter 2.1.0.
I'm including the Amazon Web Services SDK in a custom model using require_once APPPATH . "/libraries/aws_sdk/sdk.class.php";
This works well. However when CI decides it later needs to load it's db cache class, it calls (via CI_DB_driver::_cache_init):
if ( ! class_exists('CI_DB_Cache'))
{
if ( ! #include(BASEPATH.'database/DB_cache.php'))
{
return $this->cache_off();
}
}
This triggers the autoload of the AWS SDK (the exact method being CFLoader::autoloader).
I can get around this by telling class_exists not to autoload, as it seems that DB_cache.php is included anyway if class_exists returns false:
if ( ! class_exists('CI_DB_Cache', false))
{
if ( ! #include(BASEPATH.'database/DB_cache.php'))
{
return $this->cache_off();
}
}
This dirty fix works, but obviously only fixes the immediate problem. In general, what is the best way to ensure that CodeIgniter doesn't get confused with the autoload from other libraries?
Please note:
I've read bits and pieces about using spl_autoload_register. It seems that Codeigniter doesn't use this and I'm not sure how I should implement this safely. I would find a solid example of how and where I should implement this most useful (if, of course, this is the solution).
It appears that the AWS SDK already uses spl_autoload_register: spl_autoload_register(array('CFLoader', 'autoloader'));
As you probably know, you don't really want to be modifying the core methods in CI. So, to prevent CI from conflicting with your class, you want to do something very similar to the following:
Leave your folder structure as is, but create a new file outside your aws_sdk folder. Name it something like *Aws_loader* or something that makes sense to you. If you want to autoload the sdk, then in the CI autoloader file, add:
CI Autoload file (application/config/autoload.php):
$autoload['libraries'] = array('Aws_loader');
Your init file:
class CI_Aws_sdk{
// for use with PHP < version 5
/*public function CI_Aws_sdk(){
require dirname(__FILE__) . DIRECTORY_SEPARATOR . "aws_sdk" .DIRECTORY_SEPARATOR . 'sdk.class.php';
}*/
// for use with PHP >= 5
public function __construct(){
require dirname(__FILE__) . DIRECTORY_SEPARATOR . "aws_sdk" .DIRECTORY_SEPARATOR . 'sdk.class.php';
}
}
So your directory structure looks like this now:
application --
config --
...
libraries --
Aws_loader.php
aws_sdk --
sdk.class.php
If you aren't autoloading the sdk, then in your controller, you can do this:
$this->load->library('Aws_loader');
Either way, CI with load the class for you and effectively separate any methods within it and now you can operate within that class just like any other library or model that you've loaded previously, without interfering with CI's methods, similar to this:
$this->Aws_loader->do_something();
You can use the same method for any third party class library or even one that you wrote yourself. A very similar arrangement can be used for models, libraries, helpers and the like.
Hope this helps!

Can I extend the codeigniter core system, to use my custom functions on multiple sites?

I am currently using CodeIgniter to support one site, whose main purpose is to display multiple editable tablekit tables, and handle the AJAX that arrives when you edit them. A kind of PHPMyAdmin [VERY] Lite. It has a number of core helpers and controllers, which run the main workings of the site, mixed in with some site specific controllers and helpers.
I would like to restructure my site so that I can reuse the core code-base in another site. However, I would still like to be have some default controller functions and some cutsom functions in the same controller; i.e. in a system file somewhere:
class My_core extends Controller{
/*
Lots of base functions
*/
}
and on one site:
class site_1 extends My_Core{
/*
Site specific functions
*/
}
Then on the other site:
class site_2 extends My_Core{
/*
Site specific functions
*/
}
Does anyone have any guidance on how I can do this?
Thanks,
Lemiant
If you are using CodeIgniter 2.0 you can achieve most of this with with packages. They will let you load helpers, libraries and models from anywhere, so in each application simply configure a package to be loaded from that shared folder.
As for core libraries (which MY_Controller will be) you will have to implement your own __autoload() function:
http://php.net/manual/en/language.oop5.autoload.php
You can put an autoloader at the bottom of config.php. As long as it is checking the correct folders (local first, then the shared folder structure) it should all work pretty nicely.
I don't know if this is still helpful to you but heres what I've done.
say I have 2 websites, palladium.com and osmium.com.
my file tree looks like this
/var/www/system/ (the CI system folder)
/var/www/palladium/application
/var/www/palladium/public/index.php
/var/www/osmium/application
/var/www/osmium/public/index.php
inside those index.php files are lines that define where /system/ is stored. I've got that set to
$system_folder = "../../system";
Now inside /var/www/system/libraries i have a file named MY_TestClass
<?php
class MY_TestClass {
public function MY_TestClass() {
echo "this is a test of the emergency broadcast system";
}
}
From anywhere inside BOTH palladium.com and osmium.com i can call
$this->load->library('MY_TestClass');
and "this is a test of the emergency broadcast system" will show up.

How to handle including needed classes in PHP

I'm wondering what the best practice is for handling the problem with having to "include" so many files in my PHP scripts in order to ensure that all the classes I need to use are accessible to my script.
Currently, I'm just using include_once to include the classes I access directly. Each of those would include_once the classes that they access.
I've looked into using the __autoload function, but hat doesn't seem to work well if you plan to have your class files organized in a directory tree. If you did this, it seems like you'd end up walking the directory tree until you found the class you were looking for. Also, I'm not sure how this effects classes with the same name in different namespaces.
Is there an easier way to handle this?
Or is PHP just not suited to "enterprisey" type applications with lots of different objects all located in separate files that can be in many different directories.
I my applications I usually have setup.php file that includes all core classes (i.e. framework and accompanying libraries). My custom classes are loaded using autoloader aided by directory layout map.
Each time new class is added I run command line builder script that scans whole directory tree in search for model classes then builds associative array with class names as keys and paths as values. Then, __autoload function looks up class name in that array and gets include path. Here's the code:
autobuild.php
define('MAP', 'var/cache/autoload.map');
error_reporting(E_ALL);
require 'setup.php';
print(buildAutoloaderMap() . " classes mapped\n");
function buildAutoloaderMap() {
$dirs = array('lib', 'view', 'model');
$cache = array();
$n = 0;
foreach ($dirs as $dir) {
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)) as $entry) {
$fn = $entry->getFilename();
if (!preg_match('/\.class\.php$/', $fn))
continue;
$c = str_replace('.class.php', '', $fn);
if (!class_exists($c)) {
$cache[$c] = ($pn = $entry->getPathname());
++$n;
}
}
}
ksort($cache);
file_put_contents(MAP, serialize($cache));
return $n;
}
autoload.php
define('MAP', 'var/cache/autoload.map');
function __autoload($className) {
static $map;
$map or ($map = unserialize(file_get_contents(MAP)));
$fn = array_key_exists($className, $map) ? $map[$className] : null;
if ($fn and file_exists($fn)) {
include $fn;
unset($map[$className]);
}
}
Note that file naming convention must be [class_name].class.php. Alter the directories classes will be looked in autobuild.php. You can also run autobuilder from autoload function when class not found, but that may get your program into infinite loop.
Serialized arrays are darn fast.
#JasonMichael: PHP 4 is dead. Get over it.
You can define multiple autoloading functions with spl_autoload_register:
spl_autoload_register('load_controllers');
spl_autoload_register('load_models');
function load_models($class){
if( !file_exists("models/$class.php") )
return false;
include "models/$class.php";
return true;
}
function load_controllers($class){
if( !file_exists("controllers/$class.php") )
return false;
include "controllers/$class.php";
return true;
}
You can also programmatically determine the location of the class file by using structured naming conventions that map to physical directories. This is how Zend do it in Zend Framework. So when you call Zend_Loader::loadClass("Zend_Db_Table"); it explodes the classname into an array of directories by splitting on the underscores, and then the Zend_Loader class goes to load the required file.
Like all the Zend modules, I would expect you can use just the loader on its own with your own classes but I have only used it as part of a site using Zend's MVC.
But there have been concerns about performance under load when you use any sort of dynamic class loading, for example see this blog post comparing Zend_Loader with hard loading of class files.
As well as the performance penalty of having to search the PHP include path, it defeats opcode caching. From a comment on that post:
When using ANY Dynamic class loader APC can’t cache those files fully as its not sure which files will load on any single request. By hard loading the files APC can cache them in full.
__autoload works well if you have a consistent naming convention for your classes that tell the function where they're found inside the directory tree. MVC lends itself particularly well for this kind of thing because you can easily split the classes into models, views and controllers.
Alternatively, keep an associative array of names to file locations for your class and let __autoload query this array.
Of the suggestions so far, I'm partial to Kevin's, but it doesn't need to be absolute. I see a couple different options to use with __autoload.
Put all class files into a single directory. Name the file after the class, ie, classes/User.php or classes/User.class.php.
Kevin's idea of putting models into one directory, controllers into another, etc. Works well if all of your classes fit nicely into the MVC framework, but sometimes, things get messy.
Include the directory in the classname. For example, a class called Model_User would actually be located at classes/Model/User.php. Your __autoload function would know to translate an underscore into a directory separator to find the file.
Just parse the whole directory structure once. Either in the __autoload function, or even just in the same PHP file where it's defined, loop over the contents of the classes directory and cache what files are where. So, if you try to load the User class, it doesn't matter if it's in classes/User.php or classes/Models/User.php or classes/Utility/User.php. Once it finds User.php somewhere in the classes directory, it will know what file to include when the User class needs to be autoloaded.
#Kevin:
I was just trying to point out that spl_autoload_register is a better alternative to __autoload since you can define multiple loaders, and they won't conflict with each other. Handy if you have to include libraries that define an __autoload function as well.
Are you sure? The documentation says differently:
If your code has an existing __autoload function then this function must be explicitly registered on the __autoload stack. This is because spl_autoload_register() will effectively replace the engine cache for the __autoload function by either spl_autoload() or spl_autoload_call().
=> you have to explicitly register any library's __autoload as well. But apart from that you're of course right, this function is the better alternative.
__autoload will work, but only in PHP 5.

Categories