Autoloading Namespaced Classes - php

I have searched high and low for an answer and tried every example, but this still fails to find my classes. Why do I keep getting Fatal error: Class 'ProjectMorpheus\model\Database' not found in C:\Portables\xampp\htdocs\ProjectMorpheus\config\config.php on line 23
/ProjectMorpheus
/model
Database.class.php
/config
config.php
So my Database class has a namespace like this:
namespace ProjectMorpheus\model;
class Database { ... }
Finally, my config.php has a function autoloader function (Note: __SITE_PATH = 'C:\Portables\xampp\htdocs\ProjectMorpheus\'):
/*** auto load model classes ***/
function __autoload($class){
$parts = explode('\\', $class);
include __SITE_PATH . 'model/' . end($parts) . '.class.php';
}
$dbh = \ProjectMorpheus\model\Database::getInstance($dsn, $username, $password);
Using spl_autoloader appears to work, but why? My only guess is that $class is not the same in both instances. My spl_function looks like:
spl_autoload_register(function($class){
$parts = explode('\\', $class);
include __SITE_PATH . 'model/' . end($parts) . '.class.php';
});

Why don't you use PSR-0 or PSR-4 autoloader standards?
They even have ready-to-use autoloader class on GitHub As long as you will follow the rules, you won't have any issues and your code will be PSR.
Although I wouldn't recommend but if you would like to insist using the code above for autoloading classes (only in models folder) then try to dump what is the value of __SITE_PATH . 'model/' . end($parts) . '.class.php'; and check if you can access it. You can even try to copy and paste the path to your file explorer to see if the location is reachable and file exists in that directory.
P.S. I tried to add this as a comment but I couldn't due to low rep. points (being new around here and all).

Related

PHP autoloader cant find necessary class

So I am learning PHP MVC for the first time, and i came up to a term - autoloading all the required .php files.
So I searched the web for some examples, and made an autoload file with spl_autoload_register method, calling an anonymous method to require_once all needed files. I don't know what is wrong, but it seems not to work.
So the spl_autoload_register method I made is:
spl_autoload_register(function($className){
if(is_file('libraries/' . $className . '.php')){
echo 'libraries/' . $className . '.php';
require_once 'libraries/'. $className . '.php';
}
});
File structure in the project is available here: https://prnt.sc/gPaVkLZRsPpj
If I comment out the spl_autoload_register method and require all the necessary files one by one, it is working perfectly fine.
require_once 'libraries/Core.php';
require_once 'libraries/Controller.php';
require_once 'libraries/Database.php';
The file in which I'm trying to do the autoload looks like this:
<?php
require_once '../app/autoload.php';
// Init Core.php
$init = new Core();
And the error message I get is:
Fatal error: Uncaught Error: Class "Core" not found in C:\xampp\htdocs\farkops-mvc\public\index.php:5 Stack trace: #0 {main} thrown in C:\xampp\htdocs\farkops-mvc\public\index.php on line 5
I have searched trough the internet, tried other implementations of spl_autoload_register but they all don't work.
Can someone please help me to resolve this issue? :)
So the problem was (as #Lawrence Cherone mentioned in comments), that autoloader was called in context to the current working directory, in which the file that was calling that autoloader was located.
So I made a few changes.
Made an constant APPROOT, that contains link to app folder (the root folder for all core files)
define('APPROOT', dirname(dirname(__FILE__)));
In my case, APPROOT is C:\xampp\htdocs\mvc\app
Used that constant to get the right directory with autoloader:
spl_autoload_register(function($className){
if(is_file(APPROOT . '/libraries/' . $className . '.php')){
require_once APPROOT . '/libraries/' . $className . '.php';
}
});
And now, autoloader can access filer in folders like
C:\xampp\htdocs\mvc\app\libraries\Core.php without any errors.

spl_autoload_extensions does not load classes

This code works fine on my dev machine (Windows) but on the server it fails to load the classes:
set_include_path(get_include_path() . PATH_SEPARATOR . 'class/');
spl_autoload_extensions('.class.php');
spl_autoload_register();
I've checked the include path and that seems OK. I also attempted to include a absolute path thusly:
$application_root = $_SERVER['DOCUMENT_ROOT'];
set_include_path(get_include_path() . PATH_SEPARATOR . $application_root.'/class/');
with much the same result: the class is not loaded;
This however works:
$autoloader = function( $class_name )
{
$application_root = $_SERVER['DOCUMENT_ROOT'];
$filename = $application_root . '/class/' . str_replace( '\\', '/', $class_name) . '.class.php';
require_once $filename;
};
spl_autoload_register( $autoloader );
But is not as elegant.
How do I get spl_autoload_extensions working on my production machine?
Summary
not working;
set_include_path(get_include_path() . PATH_SEPARATOR .getenv('DOCUMENT_ROOT').'/class');
spl_autoload_extensions('.class.php');
$filter = new Filter();
With get_include_path() I do get a complete path ".:/usr/share/pear:/usr/share/php:/home/httpd/vhosts/_hidden_/httpsdocs/class". I get Fatal error: Class 'Filter' not found.
working:
include getenv('DOCUMENT_ROOT').'/class/Filter.class.php';
$filter = new Filter();
Solved
What hinted me was the case-insensitivity on Windows OS (the file system is btw.) where linux does not ignore case.
The documentation on php.net is sometimes particularly unclear or incomplete. What it didn't say is that it does a strtolower on the generated file (or even path) name.
I had to rename my class files Filter.class.php and YearFilter.class.php to filter.class.php and yearfilter.class.php and so on and so-fort but at the same time keeping the class names abstract class Filter {} and class Yearfilter{} extends Filter` the classes were loaded.
Also I have to disagree with #WinterSilence. The spl_autoload_register() had to be called at least once or the class files were not loaded.
Renaming the class files
The following code worked after renaming all my class file to have lowercase characters only.
<?php
spl_autoload_register();
set_include_path(get_include_path() . PATH_SEPARATOR . 'class/');
spl_autoload_extensions('.class.php');
$f = new Filter();
I know it works by php complaining about Filter being abstract.
Surpassing the build-in autoloader:
Without renaming the class files (which is silly and will cause future bugs). The spl_autoload_extensions has no effect btw.:
<?php
set_include_path(get_include_path() . PATH_SEPARATOR . 'class/');
spl_autoload_extensions('.class.php');
spl_autoload_register(function($class_name) { require_once( $class_name.'.class.php' );},true);
$f = new Filter();
Btw this is already filed as a bug and marked as will not fix as it will break buggy php code that depends on it. Although a patch exists it seem not to have made it in current PHP releases.
EDIT: Btw the main reason of this behavior not being fixed is that whereas everything else is, in php neither class names nor function names are case-sensitive. class FooBar{} equals class FOOBAR{} equals foorbar{}. Having class names case insensitive it makes no sense to have the filename reflect the contents as it can't, really.

Ignoring autoloader in certain files

I have this function below that autoloads classes before registering them as new classes.
function __autoload($controller){
$ce = explode('\\', $controller);
require ROOT . '/app/base/classes/' .
end($ce) . '/class.' .
end($ce) . '.php';
}
How can I ignore this autoloader for one class? The reason behind this is because I installed a package and the class file is another directory to what my class files are in...
Try to require your package file containing the class you need, before calling new OneClass().
For example, if you have [ROOT]/app/base/classes/OneClass/class.OneClass.php alongside with [ROOT]/app/custom/packages/OneClass.php, you may:
require ROOT . '/app/custom/packages/OneClass.php';
$class = new OneClass(); // will be an instance of /app/custom/packages/OneClass.php
But the best solution is using namespaces, as described in PSR-0 and PSR-4 recommendations.
And when you use namespaces, and you have correct directory/file structure/names, and you have correctly described namespaces inside your classes:
$my_class = new \app\base\OneClass();
// The same class name without any conflicts
$class_from_package = new \app\custom\packages\OneClass();
I think it's a more useful approach. You will have some logical directories/files structure which will reflect namespace structure. And if you want load the same class name from other folder, you just will be able to do it by using fully qualified class name with correspond namespace.
It can be a bit weird, to use namespaces, if you did not use them before. You need to support correlations between file path and namespace definition all the time (when moving class file to another folder, for example), but after some time of using it, you will pay no more attention to namespaces than you are paying to tying your shoes before going outdoor.
You can do something like this:
function __autoload($controller){
$ce = explode('\\', $controller);
$ignore=array('class_1', 'class_2');
if (!in_array(end($ce), $ignore)) {
require ROOT . '/app/base/classes/' .
end($ce) . '/class.' .
end($ce) . '.php';
}
}

multiple spl_autoload_register issue

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

php cannot redeclare _autoload()

I getting more indepth with php and I am creating my own mini mvc framework to learn OOP.
I have a .htaccess file that redirects everything to the index.php. In the index.php I include a file called boootstrap.php to parse the url and load the class php file.
Now that I am adding ActiveRecord http://www.phpactiverecord.org to add database access. I get the error:
Fatal error: Cannot redeclare class AutoLoader in /home/i554246/public_html/mvc/lib/Autoloader.php on line 4
I am not sure how to stop the conflict.
index.php:
include(MVC_CORE_INCLUDE_PATH . DS . 'Bootstrap.php')
include(MVC_CORE_INCLUDE_PATH . DS . 'activerecord/ActiveRecord.php');
autoloader.php which is included in bootstrap.php
<?php
class AutoLoader
{
public static function Load($Class)
{
$File = BASEDIR.$Class.'.php';
if(is_readable($File))
{
require($File);
}
else
{
die('Requested module "'.$Class.'" is missing. Execution stopped.');
}
}
}
spl_autoload_register('AutoLoader::Load');
ActiveRecord.php
if (!defined('PHP_ACTIVERECORD_AUTOLOAD_DISABLE'))
spl_autoload_register('activerecord_autoload',false,PHP_ACTIVERECORD_AUTOLOAD_PREPEND);
function activerecord_autoload($class_name)
{
$path = ActiveRecord\Config::instance()->get_model_directory();
$root = realpath(isset($path) ? $path : '.');
if (($namespaces = ActiveRecord\get_namespaces($class_name)))
{
$class_name = array_pop($namespaces);
$directories = array();
foreach ($namespaces as $directory)
$directories[] = $directory;
$root .= DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR);
}
$file = "$root/$class_name.php";
if (file_exists($file))
require $file;
}
?>
Perhaps change
if (file_exists($file))
require $file;
to
if (file_exists($file))
require_once($file);
You either try to include a file twice (you might want to use require_once) or you have 2 classes (one from a library you use?) with that same name.
if There seem to be 2 classes called AutoLoader, you might want to look into namespaces. I can't recall phpactiverecord having a class called that, but as you might use several libraries you were bound to run into this.
The best way would be to put your own autoloader class in a namespace. Make sure you keep all your calls correct, so calls to the autoloader should have \yournamespace\ in front of it, and calls inside the autoloader might need prepending a \ to (like \Exception for instance)
PHP-ActiveRecord doesn't have any AutoLoader class. What happens here I guess, is that you have two loaders that are loading the file.
Since it's PSR-0 compliant, you can load it using your own loading utility (assuming it's embracing that convention). If you do so just disable PHP-AR autoloading utility.
define('PHP_ACTIVERECORD_AUTOLOAD_DISABLE', true);
The vanilla loader is moslty useful for finding model classes which won't be needed if you're putting them in their own namespace. As your framework might not follow PHP-AR convention regarding where the models are, it seems correct to disable that autoloader.
Check that example of PHP-AR integration with lithium framework: li3_activerecord
I moved the line
$Bootstrap = new Bootstrap();
under the
include(MVC_CORE_INCLUDE_PATH . DS . 'Bootstrap.php');
include(MVC_CORE_INCLUDE_PATH . DS . 'Controller.php');
but above
require_once 'lib/activerecord/ActiveRecord.php'; the ActiveRecord seemed to want to load it again

Categories