Is there a way for the function registered by spl_autoload_register to know the source file/class/method that's calling it? I want to be able to output a useful error when a class is not found so I know which source file needs to be updated. For example:
spl_autoload_register(function($className)
{
$classFile = 'include/' . $className . '.php';
if (!is_readable($classFile))
{
echo 'Could not load ' . $className . ' requested by ' . $source;
// how to figure out $source -----------------------------^^
return false;
}
include $classFile;
return false;
}
That's what a stack trace does. It shows you the chain of events that lead to your error (and can provide details like class, line number, etc)
Try var dumping debug_backtrace() to see the array it returns and if it can helps.
spl_autoload_register(function($className)
{
var_dump(debug_backtrace());
...
Related
I'm working on a project whereby I have the following file structure:
index.php
|---lib
|--|lib|type|class_name.php
|--|lib|size|example_class.php
I'd like to auto load the classes, class_name and example_class (named the same as the PHP classes), so that in index.php the classes would already be instantiated so I could do:
$class_name->getPrivateParam('name');
I've had a look on the net but can't quite find the right answer - can anyone help me out?
EDIT
Thanks for the replies. Let me expand on my scenario. I'm trying to write a WordPress plugin that can be dropped into a project and additional functionality added by dropping a class into a folder 'functionality' for example, inside the plugin. There will never be 1000 classes, at a push maybe 10?
I could write a method to iterate through the folder structure of the 'lib' folder, including every class then assigning it to a variable (of the class name), but didn't think that was a very efficient way to do it but it perhaps seems that's the best way to achieve what I need?
Please, if you need to autoload classes - use the namespaces and class names conventions with SPL autoload, it will save your time for refactoring.
And of course, you will need to instantiate every class as an object.
Thank you.
Like in this thread:
PHP Autoloading in Namespaces
But if you want a complex workaround, please take a look at Symfony's autoload class:
https://github.com/symfony/ClassLoader/blob/master/ClassLoader.php
Or like this (I did it in one of my projects):
<?
spl_autoload_register(function($className)
{
$namespace=str_replace("\\","/",__NAMESPACE__);
$className=str_replace("\\","/",$className);
$class=CORE_PATH."/classes/".(empty($namespace)?"":$namespace."/")."{$className}.class.php";
include_once($class);
});
?>
and then you can instantiate your class like this:
<?
$example=new NS1\NS2\ExampleClass($exampleConstructParam);
?>
and this is your class (found in /NS1/NS2/ExampleClass.class.php):
<?
namespace NS1\NS2
{
class Symbols extends \DB\Table
{
public function __construct($param)
{
echo "hello!";
}
}
}
?>
If you have an access to the command line, you can try it with composer in the classMap section with something like this:
{
"autoload": {
"classmap": ["yourpath/", "anotherpath/"]
}
}
then you have a wordpress plugin to enable composer in the wordpress cli : http://wordpress.org/plugins/composer/
function __autoload($class_name) {
$class_name = strtolower($class_name);
$path = "{$class_name}.php";
if (file_exists($path)) {
require_once($path);
} else {
die("The file {$class_name}.php could not be found!");
}
}
UPDATE:
__autoload() is deprecated as of PHP 7.2
http://php.net/manual/de/function.spl-autoload-register.php
spl_autoload_register(function ($class) {
#require_once('lib/type/' . $class . '.php');
#require_once('lib/size/' . $class . '.php');
});
I have an example here that I use for autoloading and initiliazing.
Basically a better version of spl_autoload_register since it only tries to require the class file whenever you initializes the class.
Here it automatically gets every file inside your class folder, requires the files and initializes it. All you have to do, is name the class the same as the file.
index.php
<?php
require_once __DIR__ . '/app/autoload.php';
$loader = new Loader(false);
User::dump(['hello' => 'test']);
autoload.php
<?php
class Loader
{
public static $library;
protected static $classPath = __DIR__ . "/classes/";
protected static $interfacePath = __DIR__ . "/classes/interfaces/";
public function __construct($requireInterface = true)
{
if(!isset(static::$library)) {
// Get all files inside the class folder
foreach(array_map('basename', glob(static::$classPath . "*.php", GLOB_BRACE)) as $classExt) {
// Make sure the class is not already declared
if(!in_array($classExt, get_declared_classes())) {
// Get rid of php extension easily without pathinfo
$classNoExt = substr($classExt, 0, -4);
$file = static::$path . $classExt;
if($requireInterface) {
// Get interface file
$interface = static::$interfacePath . $classExt;
// Check if interface file exists
if(!file_exists($interface)) {
// Throw exception
die("Unable to load interface file: " . $interface);
}
// Require interface
require_once $interface;
//Check if interface is set
if(!interface_exists("Interface" . $classNoExt)) {
// Throw exception
die("Unable to find interface: " . $interface);
}
}
// Require class
require_once $file;
// Check if class file exists
if(class_exists($classNoExt)) {
// Set class // class.container.php
static::$library[$classNoExt] = new $classNoExt();
} else {
// Throw error
die("Unable to load class: " . $classNoExt);
}
}
}
}
}
/*public function get($class)
{
return (in_array($class, get_declared_classes()) ? static::$library[$class] : die("Class <b>{$class}</b> doesn't exist."));
}*/
}
You can easily manage with a bit of coding, to require classes in different folders too. Hopefully this can be of some use to you.
You can specify a namespaces-friendly autoloading using this autoloader.
<?php
spl_autoload_register(function($className) {
$file = __DIR__ . '\\' . $className . '.php';
$file = str_replace('\\', DIRECTORY_SEPARATOR, $file);
if (file_exists($file)) {
include $file;
}
});
Make sure that you specify the class file's location corretly.
Source
spl_autoload_register(function ($class_name) {
$iterator = new DirectoryIterator(dirname(__FILE__));
$files = $iterator->getPath()."/classes/".$class_name.".class.php";
if (file_exists($files)) {
include($files);
} else {
die("Warning:The file {$files}.class.php could not be found!");
}
});
do this in a file and called it anything like (mr_load.php)
this were u put all your classes
spl_autoload_register(function($class){
$path = '\Applicaton/classes/';
$extension = '.php';
$fileName = $path.$class.$extension;
include $_SERVER['DOCUMENT_ROOT'].$fileName;
})
;
then create another file and include mr_load.php; $load_class = new BusStop(); $load_class->method()
I have an MVC-based application with a basic URL-rewriting rule, which makes the URL look like this: website/controller/action/id. The id is optional.
If a user enters an invalid action, he should get an error which is handled in the class ErrorController.
All of my classes files are required in an autoloader file, so I should not require them every time I want to create an object. I use spl_autoload_register() for autoloading.
The problem occurs when I try to entering a URL with an invalid action. For example, for the URL website/main/inde (instead of index) - an instance of ErrorController should be created.
Instead, I get this two PHP errors:
Warning: require(!core/errorcontroller.php): failed to open stream: No
such file or directory in
D:\Programs\Wamp\www\fanfics\v0.0.2!core\autoloader.php on line 5
And
Fatal error: require(): Failed opening required
'!core/errorcontroller.php' (include_path='.;C:\php\pear') in
D:\Programs\Wamp\www\fanfics\v0.0.2!core\autoloader.php on line 5
Here is a visual of my files (the exclamation mark before the core folder is for keeping it on the top):
index.php:
<?php
require "!core/autoloader.php";
$loader = new Loader();
!core/autoloader.php
<?php
function autoload_core_classes($class)
{
require "!core/" . strtolower($class) . ".php";
}
function autoload_controllers($class)
{
require "controllers/" . str_replace("controller", "", strtolower($class)) . ".php";
}
function autoload_models($class)
{
require "models/" . str_replace("model", "", strtolower($class)) . ".php";
}
spl_autoload_register("autoload_core_classes");
spl_autoload_register("autoload_controllers");
spl_autoload_register("autoload_models");
!core/basecontroller.php
<?php
abstract class BaseController
{
protected $model;
protected $view;
private $action;
public function __construct($action)
{
$this->action = $action;
$this->view = new View(get_class($this), $action);
}
public function executeAction()
{
if (method_exists($this->model, $this->action))
{
$this->view->output($this->model->{$this->action}());
}
else
{
// Here I create an ErrorController object when the action is invalid
$error = new ErrorController("badmodel");
$error->executeAction();
}
}
}
If I try to require controllers/error.php specifically - it works just fine:
.
.
.
else
{
require "controllers/error.php"; // With this line it works just fine
$error = new ErrorController("badmodel");
$error->executeAction();
}
After an online really long search, I understand that there is maybe a problem with the include_path, but I do not quite understand it. How can I solve this problem?
It's a good idea for each autoloader function to check if the file exists before blindly trying to include/require it. Autoloaders are not expected to throw any errors and should fail silently so they can allow the next autoloader in the queue to attempt to autoload the necessary files.
<?php
function autoload_core_classes($class)
{
if (is_readable("!core/" . strtolower($class) . ".php"))
include "!core/" . strtolower($class) . ".php";
}
function autoload_controllers($class)
{
if (is_readable("controllers/" . str_replace("controller", "", strtolower($class)) . ".php"))
include "controllers/" . str_replace("controller", "", strtolower($class)) . ".php";
}
function autoload_models($class)
{
if (is_readable( "models/" . str_replace("model", "", strtolower($class)))
include "models/" . str_replace("model", "", strtolower($class)) . ".php";
}
spl_autoload_register("autoload_core_classes");
spl_autoload_register("autoload_controllers");
spl_autoload_register("autoload_models");
PHP searches for inclusions in paths defined into the property include_path. For command line you can check its value with:
php -i | grep include_path
For web check it with:
phpinfo()
In any case you can modify the value within php.ini.
I used to chdir() in the root of the project in my single entry point and then include files with relative path from root. This is working because include_path usually contains the current directory "."
I am trying to debug some scripts that I've done that don't work.
I want to implement the very basic logging (I mean log files) function that I use in the main page script in my class files.
However it doesn't work, for example these simple lines:
if ($file = fopen('C:/wamp/www/xxxx/Logs/General/' . date('Ymd') . '.log', 'a+') {
fputs($file, "[" . date('d/m/Y - H:i:s') . "]\t" . "[" . $type ."]\t" . "[" . $author . "]\t" . $message . "\r\n");
fclose($file);
}
else
{
return false;
}
Work perfectly if I put them in a php function included at the top of my main page (for example in a log.php file).
Howevr they don't work at all if they are in a class method:
public function __contruct(array $connectionArgs)
{
if ($file = fopen('C:/wamp/www/xxxx/Logs/General/' . date('Ymd') . '.log', 'a')) {
fwrite($file, "test");
fclose($file);
}
else
{
die("fail");
}
I am quite new to OOP so I guess it has something to do with the way of calling such function into a class?
It shoudln't be a different if you're putting your logger in class definition or in function code. I assume that you're doing something wrong or maybe you have some error.
Here this is working example
Class Logger
{
const PATH_TO_LOGS_DIRECTORY = 'C:/wamp/www/xxxx/Logs/General/';
const FILE_DATE_SUFFIX = 'Ymd';
private $handle;
public function log($what) {
$this->openFile();
fwrite($this->handle, $what . PHP_EOL);
}
protected function openFile() {
if ($this->handle === null) {
$this->handle = fopen(self::PATH_TO_LOGS_DIRECTORY . date(self::FILE_DATE_SUFFIX) . '.log', 'a');
if ($this->handle === false) {
throw new RuntimeException('Cannot open log file');
}
}
register_shutdown_function(array($this, 'close'));
}
public function close() {
if($this->handle !== null) {
fclose($this->handle);
}
}
}
Few extra things that you should care of :
don't open file till you want to log something. When you're not logging stuff you don't need to reach the file and seek to end of file.. It's called Lazy Initialiation. When you want to log something they you're opening file
in this demo class I'm using a small trick, normally when you're shuttingdown application you should close the log file, (call fclose()), but then you have remember that, and then if you have exception you have to handle that also. But you can use register_shutdown_function and PHP will always call that function on the end of php script
take a look on PSR-3 (https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) - PHP group is trying to standarize the logging systems so there is no need to to write your own interface to handle
it's good to pass date string (timestamp or DateTime object event better a param to constructor. You should pass dependency, not expect them
I am using spl_autoload_register() function to include all files . What i want that any class having extension .class.php or .php would includes directly. I made below class and register two different function and everything working fine, BUT
I think there is some way so that i need to register only one function to include both extensions together.
please have a look on my function and tell me what i am missing
my folder structure
project
-classes
-alpha.class.php
-beta.class.php
-otherclass.php
-includes
- autoload.php
-config.inc.php // define CLASS_DIR and include 'autoload.php'
autoload.php
var_dump(__DIR__); // 'D:\xampp\htdocs\myproject\includes'
var_dump(CLASS_DIR); // 'D:/xampp/htdocs/myproject/classes/'
spl_autoload_register(null, false);
spl_autoload_extensions(".php, .class.php"); // no use for now
/*** class Loader ***/
class AL
{
public static function autoload($class)
{
$filename = strtolower($class) . '.php';
$filepath = CLASS_DIR.$filename;
if(is_readable($filepath)){
include_once $filepath;
}
// else {
// trigger_error("The class file was not found!", E_USER_ERROR);
// }
}
public static function classLoader($class)
{
$filename = strtolower($class) . '.class.php';
$filepath = CLASS_DIR . $filename;
if(is_readable($filepath)){
include_once $filepath;
}
}
}
spl_autoload_register('AL::autoload');
spl_autoload_register('AL::classLoader');
Note : there is no effect on line spl_autoload_extensions(); . why?
i also read this blog but did not understand how to implement.
There is nothing wrong with the way you do it. Two distinctive autoloaders for two kinds of class files are fine, but I would give them slightly more descriptive names ;)
Note : there is no effect on line spl_autoload_extensions(); . why?
This only affects the builtin-autoloading spl_autoload().
Maybe it's easier to use a single loader after all
public static function autoload($class)
{
if (is_readable(CLASS_DIR.strtolower($class) . '.php')) {
include_once CLASS_DIR.strtolower($class) . '.php';
} else if (is_readable(CLASS_DIR.strtolower($class) . '.class.php')) {
include_once CLASS_DIR.strtolower($class) . '.class.php';
}
}
You may also omit the whole class
spl_autoload_register(function($class) {
if (is_readable(CLASS_DIR.strtolower($class) . '.php')) {
include_once CLASS_DIR.strtolower($class) . '.php';
} else if (is_readable(CLASS_DIR.strtolower($class) . '.class.php')) {
include_once CLASS_DIR.strtolower($class) . '.class.php';
}
});
Maybe this will help:
http://php.net/manual/de/function.spl-autoload-extensions.php
Jeremy Cook 03-Sep-2010 06:46
A quick note for anyone using this function to add their own autoload
extensions. I found that if I included a space in between the
different extensions (i.e. '.php, .class.php') the function would not
work. To get it to work I had to remove the spaces between the
extensions (ie. '.php,.class.php'). This was tested in PHP 5.3.3 on
Windows and I'm using spl_autoload_register() without adding any
custom autoload functions.
Hope that helps somebody.
Here is my problem. I have one file (say, func.inc.php) with autoloader function, registered by spl_autoload_register():
function autoloaderFnc($class) {
global $__CONFIG;
$original = $class_name;
$class_name = mb_strtolower ( $class_name );
foreach ( $__CONFIG ['include_dirs'] as $path ) {
if(file_exists ( $__CONFIG ['root'] . $path . $class_name . '.inc.php' )) {
require ($__CONFIG ['root'] . $path . $class_name . '.inc.php');
$classFound = TRUE;
$foundAt = $__CONFIG ['root'] . $path . $class_name . '.inc.php';
break;
}
}
if( $classFound ) {
if ( class_exists( $original ) ) {
return true;
} else {
$backtrace = debug_backtrace();
$error = "PHP User error: Cannot load class <b>$original</b> included at " . $backtrace[1]['file'] . ":" . $backtrace[1]['line'] . " (searching for <i>$class_name.inc.php</i>), but following file was included: $foundAt";
error_log( $error );
return false;
}
} else {
$backtrace = debug_backtrace();
$error = "PHP User error: Cannot find file with class <b>$original</b> included at " . $backtrace[1]['file'] . ":" . $backtrace[1]['line'] . " (searching for <i>$class_name.model.php</i> and <i>$class_name.inc.php</i>) in none of the following include dirs:<br />" . join ( '<br />', $__CONFIG ['include_dirs'] );
error_log( $error );
}
}
spl_autoload_register('autoloaderFnc');
Then I have a second file (show_me_poi.php), calling some class:
POI::doSmth();
Everything seems OK, but sometimes (and only sometimes!) I receive a following error in log:
[error] PHP User error: Cannot load class POI included at /path_to_dir/php/generators/export.generator.php:156 (searching for poi.inc.php), but following file was included: /path_to_dir/php/scripts/php/incs/poi.inc.php
This is weird, because class POI is defined in properly included file! And I repeat, this situation happens only sometime (10 out of 100 I think). What can cause such behaviour? And how can I fix it?
Thanks in advance!
I've run into this problem as well and I found a solution for my case.
In my situation I had a custom session handler as well which wrote data to the database when closing the session.
When an error occurred the error handler would fire and the error would be handled normally.
But then the session close handler would run and it would encounter and error when trying to write to the database which caused catch statement to fire which, depending on the type of SQL error would throw an Exception of a certain type, but those classes had to be autoloaded. At that point (in a session close handler) you can no longer autoload classes apparently.
Check when this is happening. During normal PHP execution, or in some 'special' PHP mode such as a session handler or a while doing error handling?
The solution in my case was to add class_exists("MyClass", true) before trying to use it. I could throw a normal Exception instead. The Exception will still 'Fatal' as there is nothing left to catch it, but at least it will show the real Exception and not the autoload error.
The problem seems to arise only with APC op-cache turned on. I think that __autoload function can't work properly with caches in some cases. AFAIS only some PHP and APC versions are not compatible with each other.