PHP autoloader: ignoring non-existing include - php

I have a problem with my autoloader:
public function loadClass($className) {
$file = str_replace(array('_', '\\'), '/', $className) . '.php';
include_once $file;
}
As you can see, it's quite simple. I just deduce the filename of the class and try to include it. I have a problem though; I get an exception when trying to load a non-existing class (because I have an error handler which throws exceptions). This is inconvenient, because it's also fired when you use class_exists() on a non-existing class. You don't want an exception there, just a "false" returned.
I fixed this earlier by putting an # before the include (supressing all errors). The big drawback with this, though, is that any parser/compiler errors (that are fatal) in this include won't show up (not even in the logs), resulting in a hard to find bug.
What would be the best way to solve both problems at once? The easiest way would be to include something like this in the autoloader (pseudocode):
foreach (path in the include_path) {
if (is_readable(the path + the class name)) readable = true;
}
if (!readable) return;
But I worry about the performance there. Would it hurt a lot?
(Solved) Made it like this:
public function loadClass($className) {
$file = str_replace(array('_', '\\'), '/', $className) . '.php';
$paths = explode(PATH_SEPARATOR, get_include_path());
foreach ($paths as $path) {
if (is_readable($path . '/' . $file)) {
include_once $file;
return;
}
}
}

It will only get called once per class, so performance shouldn't be a problem.

public function loadClass($className) {
$file = str_replace(array('_', '\\'), '/', $className) . '.php';
if(is_readable($file))
include_once $file;
}
is_readable won't make a huge performance difference.

class_exists() has a second parameter autoload which, when set to FALSE, won't trigger the autoloader for a nonexistant class.

(Solved) Made it like this:
public function loadClass($className) {
$file = str_replace(array('_', '\\'), '/', $className) . '.php';
$paths = explode(PATH_SEPARATOR, get_include_path());
foreach ($paths as $path) {
if (is_readable($path . '/' . $file)) {
include_once $file;
return;
}
}
}

Related

Override some function cakephp (Late Static Bindings )

I use CakePHP 2.9.8 to develop an old application that was written 7 years ago and developed until now.
unfortunately the first developer add some codes in CakePHP's library and for migrating to the 3th version of CakePHP I need to transfer the changing in the application.
one of the changing is in the App::load located in ~\lib\Cake\Core\App.php and as it used static::_map($file, $className, $plugin);, I can write a class which extend App.php and rewrite the _map function.
My questions:
Can be override a protected function or property?
if no:
why in CakePHP they were used (or called) like static:: for example: static::_map($file, $className, $plugin); but the definition is protected static function _map($file, $name, $plugin = null)
if yes:
Where in my application should I define class Foo which extend App and for load function that I want to remove the developer changes, where should I write Foo::load?.
I put also App::load function here:
public static function load($className) {
if (!isset(static::$_classMap[$className])) {
return false;
}
if (strpos($className, '..') !== false) {
return false;
}
$parts = explode('.', static::$_classMap[$className], 2);
list($plugin, $package) = count($parts) > 1 ? $parts : array(null, current($parts));
$file = static::_mapped($className, $plugin);
if ($file) {
return include $file;
}
$paths = static::path($package, $plugin);
if (empty($plugin)) {
$appLibs = empty(static::$_packages['Lib']) ? APPLIBS : current(static::$_packages['Lib']);
$paths[] = $appLibs . $package . DS;
$paths[] = APP . $package . DS;
$paths[] = CAKE . $package . DS;
} else {
$pluginPath = CakePlugin::path($plugin);
$paths[] = $pluginPath . 'Lib' . DS . $package . DS;
$paths[] = $pluginPath . $package . DS;
}
$normalizedClassName = str_replace('\\', DS, $className);
// Start point of custom codes
// Load Custom Classes that are usually added during customizations
// This part is for accepting a class like **XControllerCustom** but the name of file is: **XController**
if($package === 'Model'){
foreach ($paths as $path) {
$file = $path . DS . $className . '.php';
$file_custom = $path . 'Custom' . DS . $normalizedClassName . '.php';
if (file_exists($file_custom) && file_exists($file)) {
self::_map($file_custom, $className);
include $file;
return include $file_custom;
}
}
}
// End of custom's code
foreach ($paths as $path) {
$file = $path . $normalizedClassName . '.php';
if (file_exists($file)) {
static::_map($file, $className, $plugin);
return include $file;
}
}
return false;
}
You have some serious lack of knowledge in OOP in php. Read these links carefully, they'll give you the answers and hopefully a complete understandig of both topics.
http://php.net/manual/en/language.oop5.visibility.php
http://php.net/manual/en/language.oop5.late-static-bindings.php
Also there are plenty of questions and answers related to both on SO. Just search for them if the manual is not sufficient.
For number three, there is no fixed place. Just put it in a Lib or Utility folder inside your app. However, I would choose an autoloader over the built in App stuff and just use uses to make my classes available. If your app doesn't use composer yet, just add it and use its autoader and namespaces. The only reason 2.x is still not using them is backward compatibility. App is a crutch to get namespace like functionality done without actually using them.

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');

php autoloader: why does this work?

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...

PHP Autoloading (Efficiently)

I have a problem with autoloading classes that have not been named appropriately. While our developers always use the Java-style file-name-same-as-class approach so autoloading functions are trivial, we suddenly got a brick to the liver when HybridAuth came along.
For the record, their naming system goes like this:
Hybrid_Auth exists in Hybrid/Auth.php
Hybrid_Provider_Adapter exists in *Hybrid/Provider_Adapter.php*
Hybrid_Providers_Facebook exists in Hybrid/Providers/Facebook.php
OAuthRequest (along with several others) exist in Hybrid/thirdparts/OAuth/OAuth.php
And this is what it did to our lovely little, harmonious autoloader:
Original
function autoload($class)
{
$search = array('classes',
'utils',
'config');
foreach($search as $folder)
{
$file = ROOT_DIR.'/'.$folder.'/'.$class.'.php';
if(file_exists($file))
{
require_once $file;
}
}
}
After HybridAuth
function autoload($class)
{
// FIXME: This is horrid.
$search = array('classes',
'utils',
'config',
'utils/hybridauth/Hybrid',
'utils/hybridauth/Hybrid/Providers',
'utils/hybridauth/Hybrid/thirdparty/Facebook',
'utils/hybridauth/Hybrid/thirdparty/LinkedIn',
'utils/hybridauth/Hybrid/thirdparty/OAuth',
'utils/hybridauth/Hybrid/thirdparty/OpenID');
// Stupid Hybrid Auth not using good file naming schemes
if (strstr($class, 'OAuth')) require_once ROOT_DIR.'/'.'utils/hybridauth/Hybrid/thirdparty/OAuth/OAuth.php';
$class = str_replace('Hybrid_', '', $class);
$class = str_replace('Providers_', '', $class);
foreach($search as $folder)
{
$file = ROOT_DIR.'/'.$folder.'/'.$class.'.php';
if(file_exists($file))
{
require_once $file;
}
}
}
It's mullered it! What can I try to make something neater again without modifying HybridAuth's source?

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.

Categories