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.
Related
I am learning advanced PHP standards and trying to implement new and useful methods. Earlier I was using __autoload just to escape including multiple files on each page, but recently I have seen a tip on __autoload manual
spl_autoload_register() provides a more flexible alternative for
autoloading classes. For this reason, using __autoload() is
discouraged and may be deprecated or removed in the future.
but I really can't figure out how to implement spl_autoload and spl_autoload_register
spl_autoload_register() allows you to register multiple functions (or static methods from your own Autoload class) that PHP will put into a stack/queue and call sequentially when a "new Class" is declared.
So for example:
spl_autoload_register('myAutoloader');
function myAutoloader($className)
{
$path = '/path/to/class/';
include $path.$className.'.php';
}
//-------------------------------------
$myClass = new MyClass();
In the example above, "MyClass" is the name of the class that you are trying to instantiate, PHP passes this name as a string to spl_autoload_register(), which allows you to pick up the variable and use it to "include" the appropriate class/file. As a result you don't specifically need to include that class via an include/require statement...
Just simply call the class you want to instantiate like in the example above, and since you registered a function (via spl_autoload_register()) of your own that will figure out where all your class are located, PHP will use that function.
The benefit of using spl_autoload_register() is that unlike __autoload() you don't need to implement an autoload function in every file that you create. spl_autoload_register() also allows you to register multiple autoload functions to speed up autoloading and make it even easier.
Example:
spl_autoload_register('MyAutoloader::ClassLoader');
spl_autoload_register('MyAutoloader::LibraryLoader');
spl_autoload_register('MyAutoloader::HelperLoader');
spl_autoload_register('MyAutoloader::DatabaseLoader');
class MyAutoloader
{
public static function ClassLoader($className)
{
//your loading logic here
}
public static function LibraryLoader($className)
{
//your loading logic here
}
With regards to spl_autoload, the manual states:
This function is intended to be used as a default implementation for __autoload(). If nothing else is specified and spl_autoload_register() is called without any parameters then this functions will be used for any later call to __autoload().
In more practical terms, if all your files are located in a single directory and your application uses not only .php files, but custom configuration files with .inc extensions for example, then one strategy you could use would be to add your directory containing all files to PHP's include path (via set_include_path()).
And since you require your configuration files as well, you would use spl_autoload_extensions() to list the extensions that you want PHP to look for.
Example:
set_include_path(get_include_path().PATH_SEPARATOR.'path/to/my/directory/');
spl_autoload_extensions('.php, .inc');
spl_autoload_register();
Since spl_autoload is the default implementation of the __autoload() magic method, PHP will call spl_autoload when you try and instantiate a new class.
Since PHP 5.3, you can use spl_autoload_register() with namespaces, which means that you can organize your project and autoload your php classes without any require or include and without redefining an __autoload() function.
To demonstrate this behaviour, just create a file called index.php :
<?php
spl_autoload_register();
var_dump(new Main\Application);
Then create a folder named Main located right next to the index.php file. Finally, creates a file called Application.php located into Main and paste the following code into it :
<?php namespace Main;
class Application{}
Here is the way I do use Autoload.
In the given example I wanto to load classes form 3 diferent directories.
function namespaceAutoload($rawClass){
$class = str_replace('\\', DIRECTORY_SEPARATOR, $rawClass);
$possiblePaths[] = '..\sys\class\file.php';
$possiblePaths[] = '..\sys\class\lib\file.php';
$possiblePaths[] = '..\sys\class\class.file.inc.php';
foreach ($possiblePaths as $templatePath) {
$path = str_replace(["\\", "file"], [DIRECTORY_SEPARATOR, $class], $templatePath);
if (file_exists($path)) {
require_once "$path";
break;
}
} spl_autoload_register("namespaceAutoload");
I the given example, the PHP will look for the namespace\class in these three directories using these three different filename formats.
Working with OOP in php,
spl_autoload_register() lets you register multiple function, classes, trait etc to your php script.
Heres a use case on /test folder structure
/.htaccess
/index.php
/autoload.php
/controller/test.php
Inside autoload.php file:
spl_autoload_register(function ($classname) {
include_once dirname(__FILE__) . "/" . str_replace("\\", "/", $classname) . '.php';
});
Inside .htaccess
DirectoryIndex index.php
# php_value auto_prepend_file
php_value auto_prepend_file "/xampp/htdocs/test/autoload.php"
Inside controller/test.php
namespace controller;
class test {
public static function sayHello () {
echo "hello";
}
}
Inside index.php
use controller/test;
echo test::sayHello();
The autoload.php can be included or like the example above prepend on all script. It loads the class test and lets you use it directly without having to include the test.php script by pathname.
I got a problem with php and OOP.
I tried to create a object and inside the object I tried to load data from mysql.
My files are build like this.
HTML
|_ php
|__ objects
|_ content
In object folder is the object file "Event". The object created in a script in php folder and the whole script is called from a html file in content.
My Problem is, that i use the object from different locations. And the include_once method wont work.
event.php:
<?php
include_once(ROOT.'php/db.inc.php');
class Pb1_event
{
public $ev1_id;
// do something
}
I also tried it with include_once('./../db.inc.php');.
How should I include it ? Is it a good way to include it in this file or should I include it anywhere else ?
Firstly what I would do is use either __DIR__, or better is $_SERVER['DOCUMENT_ROOT'] for absolute pathing. These are constants that will refer to your server web root. Assuming it refers to the root directory you have given to us, you would do:
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/db.inc.php';
But to gain a better understanding, you should echo it and see how your directory paths. Also, for the "best practices" you should use autoloading, you can read more about it here:
http://php.net/manual/en/language.oop5.autoload.php
Define an autoload function and have it call the file you need, for example, if you need a class called DB your function might look something like this:
function __autoload($class) {
if ($class == 'DB') {
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/db.inc.php';
}
}
Use __FILE__ or __DIR__ magic constants:
include_once(dirname(__FILE__) . '/../db.inc.php');
include_once(__DIR__ . '/../db.inc.php');
My suggestion would be to register an autoloader in the beginning of your scripts using spl_autoload_register():
spl_autoload_register(function ($className) {
include 'path/to/php/objects/' . $className . '.php';
});
When you want to instantiate an object, where ever you are, you just need to do:
$myclass = new MyClass();
The autoloader will load the correct class. All you need to think about is to call the files in "objects" the same as your classes. Example:
class Pb1_event {
}
filename: path/to/php/objects/Pb1_event.php
you can try this for your warning:
include_once($_SERVER["DOCUMENT_ROOT"].'/php/db.inc.php');
Currently I'm recursively looking through my classes folder in order to find the path to my class in order to include it. The problem is, I have many different classes in various different folders that I would like to include. The code that I've created is messy but it works.
The code below is not the recursive function that I've created but it will work for a defined path within the myAutoloader() function.
Is there any way to pass a path to spl_autoload function in PHP?
spl_autoload_register('myAutoloader');
function myAutoloader($className) {
$path = '/classes';
include $path . $className . '.php';
}
//-------------------------------------
// this one will work, as its path is specified in spl_autoload
$myClass1 = new MyClass1();
//this is located in /classes/other so it wont work
$myClass2 = new MyClass2();
There is no way to pass the path to the callback function specified in spl_autoload_register.
You can only create this awareness inside the function:
function myAutoloader($className) {
$paths = array(
'Class1' => '/subpath',
'Class2' => '/subpath/extra',
);
include $paths[$className] . '/'. $className . '.php';
}
That said, I strongly discourage you folliwing this approach. Instead I really suggest you to:
Let composer create your autoload insted doing this manually.
Invest 10 minutes reading PSR-4 Specifications.
If your class "MyClass1" is stored with filename like "MyClass1.php" you can do:
set_include_path('/classes:/classes/other');
spl_autoload_register();
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
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;
}
}