I'm a bit new to OOP and I'm working on a 'framework' for my own application. I have my own autoload function which looks like below.. as well as an exception handling object. I won't be using any 3rd party plugins (at least I don't think).
First question: Should I bother with exception handling in my autoload or am I just overdoing things?
Second question: My exceptionHandler class is a public function...since it's something that will be used by many other applications is this the right? Thank you.
Thanks..any input is greatly appreciated.
function __autoload( $class ){
// Define filename pattern to include
$filename = $_SERVER['DOCUMENT_ROOT'] . '/../app/core/models/' . $class . '.class.php';
// Require class if it exists
try {
if ( is_readable( $filename ) ) {
require_once ( $filename );
}
else {
throw new Exception( "Class filename doesn't exist or isn't named correctly: $filename" );
}
}
catch ( Exception $e ) {
// Send to exceptionHandler Class for logging/handling.
$err = new exceptionHandler( $e, 3 );
}
}
Regarding your first question: No, you shouldn't.
First reason: You should always be able to chain multiple autoloaders (see spl_autoload_register), so if one autoloader fails, a second one might be able to load that class. Because of this, your autoloader should not throw any errors unless you know exactly what you're doing.
Second reason: If all autoloading attempts fail, php will trigger an error anyway. Log your php errors properly and you won't need to log this in your autoloader.
Third reason: Your autoloader will be a piece of code that will be executed a lot. You don't want a try/catch block here.
Apart from that: What happens, if autoloading your exceptionHandler fails...?
Any autoload function is really not meant to throw exceptions, because it's not clear where that exception should rise and you would have to either wrap the entire code in a try catch block or define a exception handler.
You should trigger an error via trigger_error or even better let PHP tells you what's the class that is not able to load in the real line where it happens (therefore just ignoring files that doesn't exists).
Also you shouldn't use __autoload but spl_autoload_register.
Thank you for the feedback Jeffrey and Alexander. You guys got me thinking.. I ended up removing my home grown autoloader for an easier (and faster?) method...
In my config file I added the include path for my core application classes as well as the module classes:
set_include_path( get_include_path() . PATH_SEPARATOR . APPLICATION_ROOT . 'core' . DIRECTORY_SEPARATOR . PATH_SEPARATOR . APPLICATION_ROOT . 'module' . DIRECTORY_SEPARATOR );
spl_autoload_extensions( '.class.php' );
spl_autoload_register();
So far it works :)
Related
Recently I decided to upgrade some projects from my own (...) autoloader solution to a Composer PSR-4 autoloader. My code already followed PSR-4 so, no big deal there.
On a specific case, I had the following code:
public static function isAutoLoadable($className)
{
$className = ltrim($className, "\\");
$file = $GLOBALS["Path"] . "/src/" . str_replace("\\", "/", $className) . ".php";
if (false !== stream_resolve_include_path($file))
return $file;
return false;
}
It allowed me to check whatever a given class name could be loaded, without actually trying to load it and result on a PHP Class * not found error.
Use case:
I'm currently using it to replace controllers that by some reason couldn't be found with a generic error one, avoiding an App crash and just telling the user something went wrong... then extra internal logging is done.
A function would be good since I can call it before trying to load a controller... I don't want this behavior to spread to any other classes on the app
So my question is: Is there a equivalent way to check if Composer is able to autoload some class without forcing it to load it and cause an error? -- So I can take further actions in case the class wasn't found?
I'm not a PHP expert; am trying to use this TextRank Library to help with a project.
I seem to be running into a bizarre issue: even after adding the autoload function, executing on the command line still results in "class not found" error. So here's the layout:
Code that calls the other classes (the "main" code):
echo realpath (__DIR__);
function __autoload($class_name) {
if(file_exists(__DIR__ . "/lib/TextRank/" . $class_name . '.php')) {
require_once(__DIR__ . "/lib/TextRank/" . $class_name . '.php');
} else {
throw new Exception("Unable to load $class_name.");
}
}
$config = new Config;
$textrank = new TextRank($config);
$keywords = $textrank->getKeywords("The only asynchronous, one-on-four game in Nintendo’s booth came from the “Wait, they’re still making that?” franchise that is Mario Party, and its buried presence didn’t bode well. Thankfully, Mario Party 10’s demo didn’t waste time with the series’ slowest crawl-around-a-board-game moments, instead jumping straight into four mini-games.");
var_dump($keywords);
Here's my directory structure:
/test.php (the above file)
/lib
/lib/TextRank (contains all the classes referenced by the above code
/lib/TextRank/Config.php
Yet, I still get:
Fatal error: Class 'Config' not found in /path/to/test.php
This means that:
The autoload is working, as no exceptions were thrown.
But somehow, PHP still isn't finding the required classes??
Does this have anything to do with the namespace conventions used in the classes, such as:
(in /lib/TextRank/Config.php)
namespace crodas\TextRank;
class Config
{
....
Yes it does. You need to do
$config = new crodas\TextRank\Config();
But that will not get catched by your autoloader. You need to look for an PSR-0 compatible autoloader.
I have a strange problem I can't figure out. I'm using PEAR to send mail from a PHP page. The Send_Mail class is working (sending mail using SMTP), but I'm getting this strange warning associated with my autoloader.
Warning: include(classes/LOGIN.php)
[<a href='function.include'>function.include</a>]:
failed to open stream: No such file or directory in
C:\xampp\htdocs\mysite\initialize.php on line 46`
In my initialize.php file, I have this:
function autoloader($class) {
include 'classes/' . $class . '.php';
}
spl_autoload_register('autoloader');
And in my header.php file I am loading several PHP classes for the site:
// autoload PHP classes for site
autoloader('Navigation');
autoloader('Validation');
The error referes to a LOGIN class which I don't have. But I searched my entire site folder, including php and found these two lines in C:\xampp\php\PEAR\Net\SMTP.php:
/* These standard authentication methods are always available. */
$this->setAuthMethod('LOGIN', array($this, '_authLogin'), false);
$this->setAuthMethod('PLAIN', array($this, '_authPlain'), false);
When I comment out the line containing LOGIN I get the same warning but for PLAIN and when I comment out both lines, the warnings go away (but then SMTP authentication fails).
Why is this happening?
UPDATE
Here is my new autoloader:
function autoloader($class) {
if (file_exists('classes' . $class . '.php')) {
include 'classes' . $class . '.php';
}
}
When I echo 'classes' . $class . '.php', though, I get this:
classes/.php
And then if I change it back to not use the file_exists it works, but the echo still shows classes/.php
I'm not sure what version of Net_SMTP you're on, but the setAuthMethod function takes as a parameter various different types of structures - method names, classes, objects, callables, etc.
Since PHP is dynamically typed, SMTP.php in Net_SMTP has to do a lot of checking on that object in order to determine what type of object it is. As part of that, it tries to determine if 'LOGIN' is a class name, which is invoking your autoloader.
The solution is to perform a file_exists in your autoloader before trying the include, but of course if your include path is complex in any way you're basically setting yourself up for headaches. Anyone who has dealt with writing a comprehensive autoloader knows your pain.
I reverted back to a simple autoloader and things seem to be working fine:
function autoloader($class) {
include 'classes/' . $class . '.php';
}
spl_autoload_register('autoloader');
Thanks for suggestions.
I'm working on the development of a custom framework.
And I have encountered an issue when I tried to dynamise the calling of my classes.
This is a visual of my files :
So I decided to create a different function for each folder (libs, controllers et modeles):
function autoloadLibs($class) {
//require the general classes
require 'libs/' . $class . '.php';
}
function autoloadModels($class) {
//require the models classes
require 'models/' . $class . '.php';
}
function autoloadControllers($class) {
//require the controllers classes
require 'controllers/' . $class . '.php';
}
spl_autoload_register ('autoloadLibs');
spl_autoload_register ('autoloadControllers');
spl_autoload_register ('autoloadModels');
Nevertheless I have this message : Warning: require(libs/admin.php): failed to open stream, of cours it's not the good folder. But I don't know how to fix that. Is there a good way to optimise my classes calls ?
After few tests, I found this solution for my case :
set_include_path(implode(PATH_SEPARATOR, array(get_include_path(), './libs', './controllers', './models')));
spl_autoload_register();
You need to check the file exists first with is_file() before you attempt to require it.
When using spl_autoload_register(), I've found it's generally better to register one method to include the files. The fact that you can bing multiple functions I believe is to make interoperability with different libraries easy (so they don't clobber __autoload()). It will also save you having to write the code out multiple times to check for the file's existent, map _ to directory separator (if you do that), etc.
So, assuming you change your filenames to suit the convention of Underscore_Separated_Name, e.g. Controller_Admin_Dashboard, you could use...
function autoload($className) {
$path = SYSPATH .
str_replace("_", DIRECTORY_SEPARATOR, strtolower($className)) .
".php";
if (is_file($path)) {
require $path;
}
}
The first time you instantiate Controller_Admin_Dashboard, PHP may include a file such as /app/controller/admin/dashboard.php.
If you have multiple spl_autoload_register calls you need to make sure you don't use the require keyword to include the files, because this means "include the file or die if it can't".
Personally I disagree with others about only having one autoload function, especially if you are including classes from different locations, such as a controllers versus some library directory. I also check if the file exists first, then include it.
tl;dr version: Don't allow spl_autoload_register calls to block each other.
your answer is here.
when you are registering multiple autoloaders, php try to load a class by anyone of those autoloaders, Then, php calls those autoloaders from the first registered to the last.
Then, in anyone of those autoloaders you should check that file_exists or not, else, php try to include it and throws an error if that file does not exist. Then, before inclusion, check existance of file.
Change Your Autoloaders To:
function autoloadLibs($class)
{
#require the general classes
$file = 'libs/' . $class . '.php';
if(file_exists($file))
require $file;
}
function autoloadModels($class)
{
#require the models classes
$file = 'models/' . $class . '.php';
if(file_exists($file))
require $file;
}
function autoloadControllers($class)
{
#require the controllers classes
$file = 'controllers/' . $class . '.php';
if(file_exists($file))
require $file;
}
You should check class names before requiring the file, for example:
function autoloadControllers($class) {
//require the controllers classes
if( substr( $class, -10) == 'Controller')){
require 'controllers/' . $class . '.php';
}
}
I find it correct to cause error if class cannot be loaded, but you should make sure that require is called only on correct path.
Note that spl_autoload_register provides a third parameter (prepend). You can set this to true if you wish to place a specific autoload function on top of the autoload stack. This means that this specific function will then be called first.
Example:
spl_autoload_register(array('My_Class', 'My_Method'), true, true);
http://www.php.net/manual/en/function.spl-autoload-register.php
I use this simple autoloader code:
function __autoload( $class_name ) { include 'class.' . $class_name . '.php'; }
and I make this call new SDB();
SDB actually inherits from SDBOne which I never include...yet the auto-loader loads it.
This means it knows to load modules nested / included in other modules.
So from the looks of it I never need to require or include anything if I use the auto-loader.
However, if I use a stand alone module where there is no auto-loader, I then need to include the class it inherits from.
Should I use
require,
require_once,
include,
include_once.
I would guess to go with require_once because I want an error not a warning...plus when I use the code with the autoloader I don't want it loaded twice.
Basically I have 2 types of use for SDB : one with the autoloader present and one with out.
Reference
http://php.net/manual/en/language.oop5.autoload.php
You are correct, require_once would be the correct way to include a parent class or dependent class in one file. That way if its included multiple times, the require_once prevents errors that would arise from redeclaring a class.
The autoloader is autoloading SDBOne automatically because it needs that class defined before it can extend SDB from it. The same thing is happening to autoload the parent class on demand as happens when you try to load the inherited class.
Also, you should consider switching to spl_autoload_register so that your code will work well with other code that may use an autoloader. The SPL autoloader supports multiple autoloaders and creates a stack in the order they are registered. This way if the first autoloader doesn't satisfy the requirement, it keeps trying subsequently registered autoloaders until the class is loaded or cannot be found.
Another note on preventing errors, you may want to change your autoload function as follows:
function __autoload( $class_name ) {
$file = 'class.' . $class_name . '.php';
if (file_exists($file)) {
include $file;
}
}
Because if the class being autoloaded doesn't exist, you will get errors about including a non-existent file. This is especially important when using spl_autoload_register; you don't want your autoloader to emit unnecessary warnings about missing files if another autoloader will be ultimately responsible for loading the class in question.
Hope that helps.