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!
Related
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.
I want to try Yii, but I don't want use it as my main framework. In other words, I want to use my own framework while also using some of Yii's features. I figured that in order to be able to instantiate Yii's classes from my application, I'd just need to register Yii's autoloader from my application, probably in a way similar to this:
spl_autoload_register
(
function ($classname)
{
YiiBase::autoload($className);
}
);
Of course, I'm gonna need to require or include the YiiBase class, so before I call the previous function, I do this:
$yiiBase = $_SERVER['DOCUMENT_ROOT'].'/yii/framework/YiiBase.php';
require_once($yiiBase);
But I get a "Cannot redeclare class YiiBase" error. What am I missing?
1) Do not include YiiBase.php direcly, include yii.php. Because yii.php contains a class Yii which is used in all over framework code (even in YiiBase methods).
$yii = $_SERVER['DOCUMENT_ROOT'].'/yii/framework/yii.php';
require_once($yii);
( and YiiBase.php is included in yii.php by default)
2) register your autoload handler in this way.
(Yii has built-in functionality to add custom autoload handlers ).
$my_autoload = function($class) { ... };
// OR
// $my_autoload = array('MyClass', 'my_autoload')
YiiBase::registerAutoloader($my_autoload, true);
The second parameter true tells whether to append/prepend the new autoloader after/before the default Yii autoloader
if the YiiBase.php included, then Yii's default autoloader will also gets included. No need to call YiiBase::autoload() explicitly in you code. Ref: check the last line in YiiBase.php file
You can take a look at some approaches of people integrating Yii with wordpress, you may not need to do ->run() the application, unless you need the controllers/routing, it also depends on what parts of the framework you pretend to use.
To do it for Yii2, this article explains it under the heading "Using Yii in Third-Party Systems" in the middle of the page.
Here is the relevant part to include on startup of your external application:
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
$yiiConfig = require(__DIR__ . '/../config/yii/web.php');
new yii\web\Application($yiiConfig); // Do NOT call run() here
Obviously, __DIR__ . '/../ may need to be adjusted to fit your directory layout.
I am trying to use the SwiftMailer php library with a program that I wrote. I have been using the spl_autoload_register() function just fine before including this library. However, prior to using this library I was explicitly defining the class extensions and locations using the spl functions:
set_include_path(get_include_path().[my include path]);
spl_autoload_extensions('.class.php');
spl_autoload_register();
session_start();
The problem I'm running into, is that now I'm trying to use a library that does not follow along the same naming conventions. Their own autoload class (built by the initial call to the library) looks like this.
public static function autoload($class)
{
//Don't interfere with other autoloaders
if (0 !== strpos($class, 'Swift_'))
{
return;
}
$path = dirname(__FILE__).'/'.str_replace('_', '/', $class).'.php';
if (!file_exists($path))
{
return;
}
if (self::$initPath && !self::$initialized)
{
self::$initialized = true;
require self::$initPath;
}
require $path;
}
When I try to simply run the program after calling their class I get:
Fatal error: spl_autoload() [<a href='function.spl-autoload'>
function.spl-autoload</a>]:Class Swift_MailTransport could not
be loaded in [my file] on line 30
Line 30:
$transport = Swift_MailTransport::newInstance();
I have tried using a custom autoload class modeled after theirs, however, all I get when I try:
var_dump(spl_autoload_functions());
results:
bool(false);
I know this has to be a fairly simple issue, something that I'm overlooking, but I can't find it.
Any help would be greatly appreciated.
Try removing this:
spl_autoload_register();
From the documentation:
[if] spl_autoload_register() is called without any parameters then
[spl_autoload(...)] functions will be used
Knowing that, it's only logical to think that spl_autoload does not know where to load your SwiftMailer classes because the errors you get say so. It then follows that SwiftMailer is not in your include path because spl_autoload tries to load from there.
Next step is to put your SwiftMailer classes in one of the include paths.
Ok, after knocking my head against the wall all day and getting nowhere, I got a great piece of feedback from my brother who is also a programmer.
The whole problem, stemmed from this one line:
require_once(SITE_ROOT.'/classes/lib/swift_required.php');
The SITE_ROOT variable was actually referencing the web location (i.e. http://), with my current host, this does not work, it needs to use the physical file location instead. After making this change, the included autoloader works as advertised.
According to the PCR-0 proposal, all the autoloader needs is autoload() function. Zend has a few autoloading classes
Zend_Loader()
Zend_Loader_Autoloader()
Zend_Loader_Autoloader_Resource()
Zend_Loader_Autoloader_Interface()
I'm guessing it has all these classes because it's a framework, so it needs to load its own classes, as well as library classes for any libraries that the developer may add and which don't have their own autoloader.
I have a library (normal library, not a framework). Right now it doesn't have an autoloader so I use Zend's Zend_Loader_Autoloader_Resource ->addResourceType(). If I write an autoloader for it, which autoloader will be used: Zend's or the library? and do I have to implement an autoloader as complex as Zend's or a simple one like the PCR-0 example.
What happens in these cases
both framework and library have their own autoloader
framework has autoloader, but the library doesn't
framework has NO autoloader, and library has
I'd suggest sticking to direct path->class mapping and implementing your own autoloader like this:
class MyLib_Autoloader
{
public function __construct() {
spl_autoload_register(array($this, 'autoload'));
}
public function autoload($className)
{
if (substr($className, 0, 6) == 'MyLib_') {
include dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR strtr($className, '_', DIRECTORY_SEPARATOR) . '.php';
return true;
}
return false;
}
}
This will check if the class is from your namespace and load the file. Assumes your autoloader is in /some/path/MyLib/ -> the include path will be /some/path/MyLib/../MyLib/ClassName.php. You can also add more logic to strip the initial MyLib from the class name and get rid of that '..'.
Make sure to use only require. It's faster and the autoload function is not called more than once for each classname! :) No need to include_once/require_once/etc. Go with the fastest.
Usage is as simple as:
include '/some/path/MyLib/Autploader.php';
$loader = new MyLib_Autoloader();
The main question is : who/how will your library be used ?
If that is by you only, then use Zend's autoloader, this will save you time. You don't need to reinvent the wheel.
If you have to make you library public and used on different projects, then that may be a problem because it will force the users of your library to have Zend Framework too. So in that case, either you make your own autoloader, either you pick one of a framework/library but include it in your library (watch out for the licenses).
Now regarding the utilisation of the autoloaders : only the autoloaders that are registered will be called. If you have your own autoloader, but you didn't mention how to setup it in your documentation, the users of your code will never think of set it up, and then it won't be used.
So the basic answer is to say : use many autoloaders, register them all, and they will be all called and everything will work fine. But that may lead to conflicts, because one autoloader will try to load something that is supposed to be handled by another autoloader, so you have to be careful with that, and not abuse of them.
So I have an idea, but I'm thinking I need to run it by StackOverflow before I do something stupid.
I want to have an associative array of class names => filepaths. If PHP ever runs into a fatal error where the class is not defined, it will check if the key exists in my array and then require_once the class. This will prevent unnecessary bulk loading of classes that may never be used.
Bad idea?
How about trying PHP's built in autoloading.
Autoloading is the right way to do it, but spl_autoload_register is a cleaner way than __autoload, because it allows multiple autoloaders. Function __autoload also AFAIK stops working when spl_autoload_register is called, unless __autoload is also registered.
You can write your own autoload or use an existing one. For example, Zend Framework has an autoloader that uses conventions (Foo_Bar is in Foo/Bar.php). Nette Framework has RobotLoader, that indexes your classes and uses the index when neccessary. However, unless you use other things from the framework, it is probably too large.
see: http://www.php.net/manual/en/function.spl-autoload-register.php
If you are on PHP5, you can use __autoload().
makes your code a bit more manageable , although performance-wise, it's a bad choice. But I wouldn't worry it unless I'm building a Facebook.
What you are trying to do is already handled by the php __autoload function. You can read all about it here: http://php.net/manual/en/language.oop5.autoload.php
So, not a bad idea at all ;)
you should use autoloading with specified clas name structure, here is an example
the class names should should be only alpha and _ case-insensitive.
Lets take this directory structure and files
/classes/class.php
/classes/input/input.php
/classes/output/output.php
/classes/regex/regex.php
/classes/interface/parser/interface_parser.php
/classes/parser/parser.php
/classes/parser/xml/parser_xml.php
/classes/parser/html/parser_html.php
having the structure like this is good as it encourages you to code better when it comes to OOP.
Now if we take a look at the /classes/parser/html/html_parser.php file:
class Parser_Html extends Parser implements Interface_Parser
{
//looks nice in here
}
usually you would have to make sure the interface and the extended class is loaded, but these get autoloaded as well if they have not already.
creating the auto load system for this is not that complex, its just 1 function.
function __autoload($name)
{
//Classes
$parts = explode('_',strtolower($name));
$path = '/classes/';
foreach($parts as $p)
{
$path .= $p;
}
$path .= '/' . $name . '.php';
if(file_exists($path))
{
require_once $path;
}
}
so instead of including the class file first just run the class initiation.
$HtmlParser = new Parser_Html();
as the file has not been include the __autoload is run with a param of the class name, the autoload then looks in the directory that's relevant to the class name to try and load it.
also as your using the extend keyword in the class file shown above the class that is to be the parent gets run threw trhe autoloader aswell so you do not need to pre-load interfaces and classes etc.
Hope this helps you.
Note:
All code provided is untested and written for informational purposes, I would recommend you research the techniques more in detail before any implementation is done.