php autoloader: why does this work? - php

I have been experimenting with autoloader class directory mapping techniques, and it has been a bit of a struggle. I managed to come up with a fairly straightforward solution (on the surface), but I'm totally mystified that it works at all, while other, "more obvious" solutions failed. Below are some code snippets that illustrate my confusion.
Here's the working code:
<?php
spl_autoload_register('my_autoloader');
function my_autoloader($class) {
$classMap = array(
'classes/',
'classes/sites/',
'classes/data/'
);
foreach ($classMap as $location) {
if (!#include_once($location . $class . '.php')) { // # SUPPRESSES include_once WARNINGS
// CLASS DOESN'T EXIST IN THIS DIRECTORY
continue;
} else {
// CLASS IS AUTO-LOADED
break;
}
}
}
?>
Here's a snippet that I felt should work, but doesn't:
<?php
spl_autoload_register('my_autoloader');
function my_autoloader($class) {
$classMap = array(
'classes/',
'classes/sites/',
'classes/data/'
);
foreach ($classMap as $location) {
if (file_exists($location . $class . '.php')) {
require_once ($location . $class . '.php');
}
}
}
?>
The latter makes more sense to me because while these two versions work:
<?php
spl_autoload_register('my_autoloader');
function my_autoloader($class) {
require_once ('classes/sites/' . $class . '.php');
}
?>
<?php
spl_autoload_register('my_autoloader');
function my_autoloader($class) {
$location = 'classes/sites/';
require_once ($location . $class . '.php');
}
?>
This one throws "No such file or directory..." (note the lack of "sites/" in the path.
<?php
spl_autoload_register('my_autoloader');
function my_autoloader($class) {
require_once ('classes/' . $class . '.php');
}
?>
The "No such file or directory..." error made me think I could simply check for a class's supporting file and, if (file_exists()) {require_once(); break;} else {continue;}
Why doesn't that work? And, why DOES the first snippet work? The supporting path/file is never explicitly included or required.

OK, I figured it out. My issue was indeed not setting up the path correctly; using the __DIR__ constant was the ::ahem:: path to success. Here's the working code I'm now using:
<?php
spl_autoload_register('my_autoloader');
function my_autoloader($class) {
$classMap = array(
'classes/',
'classes/sites/',
'classes/data/'
);
foreach ($classMap as $location) {
if (file_exists(__DIR__ . '/' . $location . $class . '.php')) {
require_once(__DIR__ . '/' . $location . $class . '.php');
break;
}
}
}
?>
It turns out __DIR__ returns the directory location of the actual file in which it is called (as opposed to, say, a file that is including it), which means this will work so long as this file remains in the root of the directory with my /classes directory. And, it exists exclusively for defining these kinds of settings...
Hope this helps someone else down the line! And, of course, if anyone can shed light on why this solution is sub-optimal, I'm all eyes...
EDIT: One optimization I will perform will be to convert the $classMap array() to an associative array (e.g,. $classMap = array('root' => 'classes/', 'sites' => 'classes/sites/'); so I can do a lookup rather than loop through all the directories I create over time. But, I suppose that's for another thread.
EDIT2: If anyone's interested, here's the solution I came up with for doing this as an associative array. Basically, I set up an associative array in $GLOBALS and use that to store a map of Classes -> Packages. Then, in the autoloader, I map Packages -> Locations and then call in the file necessary for the instantiated class.
This is the global config file:
<?php
// INITIALIZE GLOBAL Class -> Package map
if (!array_key_exists('packageMap',$GLOBALS)) {
$GLOBALS['packageMap'] = array();
}
spl_autoload_register('my_autoloader');
function my_autoloader($class) {
$classMap = array(
'root'=>'/classes/',
'sites'=>'/classes/sites/',
'data'=>'/classes/data/'
);
// RESOLVE MAPPINGS TO AN ACTUAL LOCATION
$classPackage = $GLOBALS['packageMap'][$class];
$location = $classMap[$classPackage];
if (file_exists(__DIR__ . $location . $class . '.php')) {
require_once(__DIR__ . $location . $class . '.php');
}
}
?>
Here's a specific site config file that uses the autoloader:
<?php
// LOAD GLOBAL CONFIG FILE
require_once('../config/init.php');
// CREATE LOCAL REFERENCE TO GLOBAL PACKAGE MAP (MOSTLY FOR READABILITY)
$packageMap = $GLOBALS['packageMap'];
// ADD Class -> Package MAPPINGS
$packageMap['DAO'] = 'data';
$packageMap['SomeSite'] = 'sites';
$packageMap['PDOQuery'] = 'data';
// INSTANTIATE SOMETHING IN ONE OF THE PACKAGES
$someSite = new SomeSite();
?>
Hopefully this is useful to others...if it raises any red flags for anyone, please chime in. Ultimately, I'd like to replace the use of $GLOBALS with apc, but it's not installed on my hosted server :/. I might consider memcached, but I've heard mixed reviews...

Related

PHP autoloader - $classname includes the entire folder path and not just the class name itself?

I am following WordPress's naming convention where a class My_Class should reside in the file named class-my-class.php. I used this autoloader for WordPress, written by Rarst. If I print out the $class_name variable, I see that the prefix class is appended to the folder name and not the class file. I had the same issue with some other autoloader I used earlier. I can do a little bit of string manipulation and get what I want but I want to know what is the exact issue.
What could be wrong?
I just had a look at this autoloader you linked to, I would say it should be on line 21 something like this :
$class_path = $this->dir . '/class-' . strtolower( str_replace( '_', '-', basename( $class_name ) ) ) . '.php';
basename takes only the file part + file extension of the path.
You need also to check where this autoloader file is, because $this->dir is set to DIR, which is the directory where the autoloader file is.
Use a flexible loader.
try this one.
function TR_Autoloader($className)
{
$assetList = array(
get_stylesheet_directory() . '/vendor/log4php/Logger.php',
// added to fix woocommerce wp_email class not found issue
WP_PLUGIN_DIR . '/woocommerce/includes/libraries/class-emogrifier.php'
// add more paths if needed.
);
// normalized classes first.
$path = get_stylesheet_directory() . '/classes/class-';
$fullPath = $path . $className . '.php';
if (file_exists($fullPath)) {
include_once $fullPath;
}
if (class_exists($className)) {
return;
} else { // read the rest of the asset locations.
foreach ($assetList as $currentAsset) {
if (is_dir($currentAsset)) {
foreach (new DirectoryIterator($currentAsset) as $currentFile) {
if (!($currentFile->isDot() || ($currentFile->getExtension() <> "php")))
require_once $currentAsset . $currentFile->getFilename();
}
} elseif (is_file($currentAsset)) {
require_once $currentAsset;
}
}
}
}
spl_autoload_register('TR_Autoloader');

Swift Mailer ruins autolading

I'm writing simple PHP application which is using Swift Mailer library. My app doesn't use namespaces nor composer.
However, after requiring swift_required.php my (model) classes are not found (Fatal error: Class 'Format' not found is thrown by PHP interpret).
Autolading
define("_DOCUMENT_ROOT", str_replace("//", "/", $_SERVER['DOCUMENT_ROOT'] . "/"));
function __autoload($class_name) {
$file_name = $class_name . '.php';
$include_foleder = array("php/model/", "templates/","cron/crons_tasks/");
foreach ($include_foleder as $folder) {
$abs_path = _DOCUMENT_ROOT . $folder . $file_name;
if (file_exists($abs_path)) {
require_once $abs_path;
}
}
}
Problematic part of function
$bar = Format::bar($foo); //works fine
require_once _DOCUMENT_ROOT . "php/lib/swiftmailer-master/lib/swift_required.php"; //works fine
$bar = Format::bar($foo); //Class not found
Class Format is my custom class, located in _DOCUMENT_ROOT . php/model/Format.php. Also other custom classes (from model folder) after requiring Swift Mailer are not found.
So I guessing that my former autoload is somehow overridden by Swift Mailer, is this possible?
Thank you.
Instead of __autoload(), you should use spl_autoload_register.
If there must be multiple autoload functions, spl_autoload_register()
allows for this. It effectively creates a queue of autoload functions,
and runs through each of them in the order they are defined. By
contrast, __autoload() may only be defined once.
http://php.net/manual/en/function.spl-autoload-register.php
define("_DOCUMENT_ROOT", str_replace("//", "/", $_SERVER['DOCUMENT_ROOT'] . "/"));
spl_autoload_register(function($class_name) {
$file_name = $class_name . '.php';
$include_folder = array("php/model/", "templates/","cron/crons_tasks/");
foreach ($include_folder as $folder) {
$abs_path = _DOCUMENT_ROOT . $folder . $file_name;
if (file_exists($abs_path)) {
require_once $abs_path;
}
}
});

Best Way To Autoload Classes In PHP

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()

what is the usage of `spl_autoload_extension() `with `spl_autoload_register()`?

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.

Automatically choose a file location before loading it in php

I need to create a simple file overloading system like symfony does with php files and templates. I will give an example to explain what I need:
Given this folder structure:
- root_folder
- modules
-module1
-file1.php
-file2.php
-file3.php
- specific_modules
-module1
-file2.php
I would like to find a way that automatically loads a file if it is found inside the specific_modules folder (file2.php) when called, if it is not found, it should load file2.php normally from the modules directory.
I would like to do it unobstrusively for the programmer, but not sure if it's possible!!
Any help or advice is welcome, thanks in advance!
skarvin
If the files contain only objects with the same name, then you can write your own autoloader function and register it with spl_autoload_register(). Perhaps something like
function my_loader($class)
{
// look in specific_modules dir for $class
// if not there, look in modules dir for $class
}
spl_autoload_register('my_loader');
This will allow you to code simply as:
$obj = new Thing();
And if Thing is defined in specific_modules, it will use that one, else the default one.
$normal_dir = 'modules';
$specific_dir = 'specific_modules';
$modules = array('module1' => array('file1.php','file2.php','file3.php'));
foreach($modules as $module => $files)
{
foreach($files as $file)
{
if(!file_exists("$specific_dir/$module/$file"))
{
include("$normal_dir/$module/$file");
}
else
{
include("$specific_dir/$module/$file");
}
}
}
This code will work as simply for you as possible, it makes it easy to add new files to your modules and change the directory names. By "load" I am making the assumption you mean include, but that part is easy enough to change.
Similarly to Alex's answer, you could also define an __autoload function:
function __autoload($class_name) {
if (file_exists(__DIR__ . '/specific_modules/' . $class_name . '.php')) {
require __DIR__ . '/specific_modules/' . $class_name . '.php';
}
elseif (file_exists(__DIR__ . '/modules/' . $class_name . '.php')) {
require __DIR__ . '/modules/' . $class_name . '.php';
}
else {
// Error
}
}
Then if you do $obj = new Thing(); it will try to load Thing.php from those two directories.

Categories