Test whether a file exists anywhere in the include path - php

I'm writing an autoload function and in the inner logic of it would like to test whether a certain file exists somewhere in the path prior to including it.
This is the logic:
If a file named $className'.specialversion.php' exists anywhere in the include path include it. Otherwise, let other autoloaders take care of including a file for this class.
At the moment I just do: #include($calculatedPath);
I'm not sure if it's a good approach to include and suppress the error. I would rather check if the file exists (somewhere in the include path) prior to including it.
My question is:
Can I test for existence of a file anywhere in the include path?
Is it really problematic to do #include($calculatedPath);?
Edit
An important accent: I don't know where the file should be. I just want to know whether it exists in one of the directories in the include path. So I can't just do file_exists() or something like that.

As of PHP 5.3.2 there is the option to use the stream_resolve_include_path() function whose purpose is to
Resolve [a] filename against the include path according to the same rules as fopen()/include() does.
If the file exists on one of the include paths, then that path (including the file name) will be returned. Otherwise (i.e. the file was not on any of the include paths) it will return FALSE.
Relating this to your needs, your autoloader might look something like:
function my_autoloader($classname) {
$found = stream_resolve_include_path($classname . '.specialversion.php');
if ($found !== FALSE) {
include $found;
}
}

You should avoid the error supressor operator #.
function autoload($class) {
// Build path (here is an example).
$path = DIR_CLASSES .
strtollower(str_replace('_', DIRECTORY_SEPARATOR, $class)) .
'.class.php';
if (file_exists($path)) {
include $path;
}
}
spl_autoload_register('autoload');
$front = new Controller_Front;
// Loads "application/classes/controller/front.class.php" for example.
Update
An important accent: I don't know where the file should be, I just want to know whether it exists in one of the directories in the include path. So I can't just do file_exists or something like this
If your class could be in a number of directories, you could...
Have your autoload function traverse them all, looking for the class. I would not recommend this.
Rename your classes to have a name that easily maps to a file path, like in the example code above.
If you do decide to traverse all folders looking for the class, and it becomes a bottleneck (benchmark it), you could benefit from caching the class name to file location mapping.

I would use file_exists rather than a warnings-suppressed include.
Then you'll have to iterate through the include_path:
$paths = explode(';', get_include_path());
foreach($paths as $p){
if(file_exists($p . '/' . $calculatedPath)){
include $p . '/' . $calculatedPath;
break;
}
}

As a simple resolution, you should a test within the SPL-function file_get_contents() by setting the second argument to TRUE.
--Rolf

I've written a function that can test it nicely
function fileExists($file) {
if(function_exists('stream_resolve_include_path'))
return stream_resolve_include_path($file);
else {
$include_path = explode(PATH_SEPARATOR, get_include_path());
foreach($include_path as $path)
if(file_exists($path.DS.$file))
return true;
return false;
}
}

Related

Moving to PHP5 SPL

After not really working with PhP for a long time, I am moving into php 5 territory. One of the things I am now trying to figure out, is how to use spl autoload functionality. And before I make stupid beginners errors, could you please confirm / advice:
As far as I understand, the SPL_autoload does not mean there is no need for includes anymore; I will still need to include the configuration I want to use manually like this:
require_once("includess/php_ini_settings.php");
In the php_ini_settings.php I subsequently can run an autoloader, to load all the php files in a certain directory, such as my classes directory:
// Directory for classes
define('CLASS_DIR', 'classes/');
// Add classes dir to include path
set_include_path(CLASS_DIR);
spl_autoload_extensions(".php"); // comma-separated list
spl_autoload_register();
Is this indeed the right (and most efficient) way to autoload classes into all my pages?
-- added: --
It is mentioned that unless you use a different naming scheme, the is no need to specify an autoloader. I assume the naming scheme default is using the class name as filename, in non-caps?
You don't really need the spl_autoload_extensions() and spl_autoload_register() part, unless you use a different naming scheme. So you basically just need to add your class path to the include path, like you already do.
I suggest using SPL_autoload_suxx() from http://bugs.php.net/49625 as your __autoload() function though to have more sensible case-sensitivity:
function __autoload($cn) {
$rc = false;
$exts = explode(',', spl_autoload_extensions());
$sep = (substr(PHP_OS, 0, 3) == 'Win') ? ';' : ':';
$paths = explode($sep, ini_get('include_path'));
foreach ($paths as $path) {
foreach ($exts as $ext) {
$file = $path.DIRECTORY_SEPARATOR.$cn.$ext;
if (is_readable($file)) {
require_once $file;
$rc = $file;
break;
}
}
}
return $rc;
}

How to import all classes within filesystem in php?

The folder structure is like so:
/main.php
/lib/class1.php
/lib/class2.php
/lib/class3.php
I want to have main.php make available all the classes in lib without doing a ton of require/include. Is this possible? Is it possible to just include all files within a directory?
Create an autoloading function to load the class directly from your URL
function __autoload($class_name) {
include "/lib/".$class_name . '.php'; //Add your folder structure like this
// ^ Change the path to your specific need
}
//Then Simply
$class1_object = new Class1();
Yes, yes it is entirely possible and quite easy to do. Best way to do so would be to utilize http://uk1.php.net/autoload
I would suggest you use an autoload function like #Puciek suggested.
If you are curious about doing it yourself though, you can do something like:
$path = "lib/";
foreach(new \DirectoryIterator($path) as $lib){
if(strstr($lib->getFilename(), ".php")){
require_once $path.$lib->getFilename();
}
}
The above will use DirectoryIterator which is part of the PHP SPL.
$path = '/lib/';
$dir = new DirectoryIterator($path);
foreach($dir as $spl){
if($spl->getExtension() != 'php') continue;
include($spl);
}
Basically I looped through the directory's files, filtered out the not php files, then included them. You can feel free to add more filters like files starting with "class" as filenames.
You can also have a look to glob() function to php, which returns an array with all the files within a directory:
http://it2.php.net/manual/en/function.glob.php
The good thing about the directory iterator is that you can use a RecursiveDirectoryIterator alongside with a RecursiveIteratorIterator to iterate through a directory tree in a similar way if you want, to get all the files as well.
Autoloading would be a good option for this but that assumes there is a way to map class names to file names, if that is an option you should follow #puciek advise.
Other options would be to create a single include that includes all of the files you want for example in /lib create a file called all.php and define it as
include("class1.php");
include("class2.php");
include("class3.php");
then in your script you could just include("lib/all.php");
Another option would be to glob the files in a directory then loop through the resultant array and issue an include.
$includes = glob("lib/*.php");
foreach($inclues as $inc){
include($inc);
}
Use lazy loading rather than importing everything - http://php.net/manual/en/function.spl-autoload-register.php
function autoload($sClass) {
$sPath = str_replace('_', DIRECTORY_SEPARATOR, $sClass) . '.php';
require_once($sPath);
}
spl_autoload_register('autoload');
new My_Class();
This is a very stripped back example but it's something to build on. Ideally you would check that the file exists on the include path (http://php.net/manual/en/function.stream-resolve-include-path.php), check that the file is readable, that the require/include was successful and that the class exists at the end of the autoload

__DIR__ VS using Reflection

In Symfony2, I saw the code like below:
if (null === $this->rootDir) {
$r = new \ReflectionObject($this);
$this->rootDir = dirname($r->getFileName());
}
why not just use the __DIR__?
if (null === $this->rootDir) {
$this->rootDir = __DIR__;
}
What is difference between them?
__DIR__ returns the directory of the file where it is called. The Symphony2 code returns the directory of where the class is defined, which most likely is a different file.
As PHP manual states:
DIR returns the directory of the file. If used inside an include, the directory of the included file is returned
FILE returns the full path and filename of the file. If used inside an include, the name of the included file is returned.
So these constants always returns paths of the file where there are used. However, I suppose that it is not the expected behaviour in the code snippet you cited. Possibly the code resides in some base class, while it can be invoked from extending classes. If we want to get the path to the current class, the first way is the correct one.
__DIR__ exists only in PHP 5.3. Before 5.3, we had to use dirname(__FILE__) or something similar.
I think it is because __DIR__ will return the directory of the script that was initially invoked. In the code example, you would get the directory of the object's class. I may be wrong though have not tried this yet please correct me anyone if I am.

include path and the __autoload function in php

I am trying to convert several php scripts to use the __autoload function. Right now I can use the include and require functions like this:
require_once('path/to/script.php');
But inside of the __autoload function, I can't use the line above. I have to use this:
require_once('absolute/path/to/script.php');
Why does it seem as though the __autoload function doesn't use the include path I have specified in php.ini?
Don't use __autoload... It has a few drawbacks (including limiting yourself to one per execution). Use instead spl_autoload_register if you're on 5.2+.
So what I typically do, is have a class:
class AutoLoader {
protected static $paths = array(
PATH_TO_LIBRARIES,
);
public static function addPath($path) {
$path = realpath($path);
if ($path) {
self::$paths[] = $path;
}
}
public static function load($class) {
$classPath = $class; // Do whatever logic here
foreach (self::$paths as $path) {
if (is_file($path . $classPath)) {
require_once $path . $classPath;
return;
}
}
}
}
spl_autoload_register(array('AutoLoader', 'load'));
That way, if you add a library set, you can just "add it" to your paths by calling AutoLoader::AddPath($path);. This makes testing with your autoloader a LOT easier (IMHO).
One other note. Don't throw exceptions from the autoload class unless absolutely necessary. The reason is that you can install multiple autoloaders, so if you don't know how to load the file, another one may exist to load it. But if you throw an exception, it'll skip the other one...
Personally, I don't ever like to use relative paths with includes. Especially with multiple include directories (like pear), it makes it very difficult to know exactly which file is being imported when you see require 'foo/bar.php';. I prefer to define the absolute path in the beginning of the file set define('PATH_ROOT', dirname(__FILE__));, and then define all my other useful paths off of that directory (PATH_LIBRARIES, PATH_TEMPLATES, etc...). That way, everything is absolutely defined... And no need to deal with relative paths (like the issue you're having now)...
I suspect your __autoload() function is in a separate file then the code which calls it. The path to the included files will be relative to the file which the __autoload() function declaration resides.
It seems like . is not in your include path. So add it use:
set_include_path('.' . PATH_SEPARATOR . get_include_path());
Now PHP should look relative to the executed scripts directory, too. (Executed script here is something like index.php, not autoload.php.
But why don't use simply use a normal relative path like ./path/to/class.php?
Not sure without seeing the whole set-up. My autoload function is within my global functions file, and looks like this:
function __autoload($class) {
if (file_exists("includes/{$class}.php")) {
require_once("includes/{$class}.php");
}
/**
* Add any additional directories to search in within an else if statement here
*/
else {
// handle error gracefully
}
}
I use a relative path because the script is included in my index.php file and all HTTP requests are passed through it.

autoloader with upper and lowercase classname

I am using this class in php for autoloading.
http://pastebin.com/m75f95c3b
But when I have somewhere
class Bar extends Foo
And I have a file called foo.class.php it won't find the class.
But when i chagne the filename to Foo.class.php it will find the class.
I am trying to add some functionallity to my class to always find the file if its there no matter wether the filename starts with a capital or not. But so far I haven't scuceeded.
Anyone?
If all of your file have lowercase names, apply strtolower to the $className variable value in each method of your class:
$className = strtolower($className);
But I suggest that you draft some coding guidelines that every developer has to stick to. Otherwise you will have to test each of the 2<length of file name> possibilities of writing a file name using uppercase and lowercase letters.
Gumbo's solution is the best one and that's what almost everyone uses. I do not understand why you do not like. Of course you can first check whether a file with the class name capitalized exists or not, and if not then check whether lowercased version exists or not. That's definitely not better than Gumbo's solution. After all "your" programmers have to follow some conventions/rules.
I'd do it more or less like this
function __autoload($class_name) {
$upperName = strtoupper(substr($class_name,0,1)) . substr($class_name, 1);
$lowerName = strtolower(substr($class_name,0,1)) . substr($class_name, 1);
//Check to see if the class file exists as-is
if(file_exists($class_name . '.php')) {
require_once($class_name . '.php');
}elseif(file_exists($upperName . '.php')) {
require_once($upperName . '.php');
}elseif(file_exists($lowerName . '.php')) {
require_once($lowerName . '.php');
}else {
throw new Exception("The class $class_name could not be found.");
}
}
It's easy to enough to add other conditionals as different naming conventions arise.
I use an __autoload() function that takes into account several different file naming conventions to provide backwards compatibility with previous developers.
It does a simple loop over each convention and checks to see if that file exists. If the file exists, then it loads it.
For my function, I do this for different file extensions, such as .inc, .class or .inc.php . You could do the same, but search for upper and lower first characters.
I would put this in the searchForClassFile() method, in the else part with the comment 'Found a file'.
EDIT (more information):
Rather than recursively descend into a class directory looking for the correct file, I map the class name to a specific location. This is a common practice. For example, foo_bar is mapped to [CLASS_DIRECTORY]/foo/bar.[EXTENSION]. In our case, we check several different extensions.
In your case, you have to make a design decision about how you want to search for the class file, but modifying your code:
} else {
// Found a file
if ($f == $className . self::$classFileSuffix ||
$f == strtolower($className) . self::classFileSuffix) {
return $subPath;
}
}
Instead of strtolower() you could write a function that only lowers the first character, or if using PHP > 5.3.0 (not officially released) use the lcfirst() function.
You can use an autoloader that scans the directories for .php files, and actually looks into the contents of each file for patterns such as "class Foo" using a regular expression. You will need to cache this, of course, preferably by using an array of the form class => directory. You also need to make sure you invalidate the cache correctly, e.g. when there are new .php files in the folder.
I do this for my website, and it works great.

Categories