Suppose I have classes in same namespaces:
directory :
(folder) a
a.php
(folder) b
b.php
c.php
and we use namespace and __autoload as you see:
in folder b\b.php :
<?php
namespace b;
use b as x;
function __autoload($clsName){
$clsName='../'.str_replace('\\', '/', $clsName).'.php';
require_once $clsName;
}
class b{
function __construct(){
print("b file<hr/>");
}
}
$t=new x\c(); // line 13
?>
and in folder b\c.php :
<?php
namespace b;
class c{
function __construct(){
print("c file<hr/>");
}
}
?>
when we define $t=new x\c, __autoload doesn't call!
please help me :(
error message:
Fatal error: Class 'b\c' not found in C:\xampp\htdocs\project\TEST\b\b.php on line 13
You have not defined autoloader. PHP looks for __autoload (or \__autoload - function defined in global namespace) while you have defined only \b\__autoload (yes, functions are namespaced to!)
How to fix it: move __autoload declaration outside namespace
Better fix: you should use spl_autoload_register
It's hard to see exactly what is going wrong. By the looks of the error, it appears that the __autoload() function isn't being called. If it was I would expect the require_once statement to fail with an error saying file not found, or something like that.
You could try by putting some debugging statements in your __autoload() function to see what's going on, including a var_dump of your $clsName variable.
It might be worth noting the following message that appears in the PHP manual regarding autoloading:
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.
You should also note, that there is a "standard" for PHP autoloading, called PSR-0. Here is a link to an article that gives a good explanation of it.
In both the article and the PSR-0 document mentioned above, are example autoloaders that you can use. I would suggest using one of these than trying to implement your own.
Related
Hi there,
Sorry if the title is rubbish,
I am new to learning PHP, and right now I am trying to make use of the spl_autoload_register() function to handle class includes,
I have made a little project to try and figure out what I am doing wrong, specifically why consts and functions can't be found,
My project folder is as follows:
Site
index.php (include config.php)
config.php (spl_autoload_register())
src
classes
ClassA.php
constants
CONSTANT_A.php
functions
functionA.php
Errors
The main error I keep coming across is:
Fatal error: Uncaught Error: Undefined constant "constants\A"
or
Fatal error: Uncaught Error: Call to undefined function functions\A()
The class is being pulled through just fine it's only the constant and function that are failing,
Page examples
<?php
// index.php
include "config.php";
use const \constants\A;
use function \functions\A;
$my_obj = new \classes\ClassA();
$my_const = \constants\A;
$my_function = \functions\A();
<?php
// config.php
function myAutoLoader($class)
{
$extension = ".php";
$path = "src/" . str_replace("\\", "/", $class) . $extension;
if (!file_exists($path)) {
return;
}
require_once $path;
}
spl_autoload_register("myAutoLoader");
<?php
// ClassA.php
namespace classes;
class ClassA
{
public function __construct()
{
echo "Hello ... " . PHP_EOL;
}
}
<?php
// CONSTANT_A.php
namespace constants;
const A = 1;
<?php
// functionA.php
namespace functions;
function A()
{
echo "Hello ... " . PHP_EOL;
}
My thinking is it has something to do with the constant and function not being a part of a class so the spl_autoload_register() is not including the relevant files,
I have tested the above idea by removeing the spl_autoload_register() and just including the files like normal and that works but I thought the point was to not have to do that?
I have seen other answers around sort of the same problems with constants where people have suggested that you need to use define instead of const and include the namespace with it so:
namespace constants;
define("constants\A", 1);
But that still makes the same error,
Thank you for any response,
The answer is very simple: PHP autoloading does not support functions and constants. As the manual says:
Any class-like construct may be autoloaded the same way. That includes classes, interfaces, traits, and enumerations.
There are some technical reasons why the feature can't be extended to functions and constants, but from a user's point of view, just know that it's not currently supported, and is unlikely to be supported any time soon.
So for now, you have two choices:
Group your functions and constants in files which you include manually in some general "startup" / "bootstrap" code. If you are using Composer (which I highly recommend), it can manage such files alongside your main autoloader, using the files key in composer.json.
Use class constants and static methods, instead of namespaced constants and functions. So instead of \constants\A; you would write \Constants::A; and instead of \functions\A(), you would write Functions::A(). Then the classes (\Constants and \Functions) will be autoloaded for you.
I seem to have some misconceptions on how autoloading works in PHP still, one I simply cannot explain.
I have a class called glue which has a spl_autoload_register within it's main function, here called run like so:
class glue{
public static function run(){
spl_autoload_register(array('glue','autoload'));
}
}
The autoload function works by loading via the PSR-0 standard and works from absolute paths. This is all tested as working etc. Note that glue is not namespaced.
The autoload function covers a namespace called glue. Within this namespace I have a error handler called \glue\ErrorHandler.
When I trigger an error the glue class will autoload \glue\ErrorHandler by PSR-0 notation from the root directory as defined by a stored ROOT constant. This has been tested as working as well in classes such as \glue\User and \glue\Session.
So now for the problem. I cause a Call-time pass-by-reference has been deprecated error within \glue\Validation and it doesn't seem to run my autoload function.
I can see how it is going into my autoload function for everything but when I call this error in that class it just seems to skip my autoloader and bail out saying it can't find my error handler class.
Normally I would say it is something with my programming but I have tried everything. I cannot explain how, for this one error. What compounds my confusion further is that if I cause a:
syntax error, unexpected T_ISSET in /media/server_ws/xxxxxxx/glue/Validation.php on line 47
Error it works. It seems to be for that one error it just will not autoload my error handler.
I thought this might be because my spl_autoload_register is not being binded to that namespace (since the error handler that works is actually called from within glue) and some how, maybe, it is randomly working. So from \glue\Validation I called a class I have never looked at: \glue\util\Crypt but that works and goes into the autoloader correctly.
When I call this error: Call-time pass-by-reference has been deprecated from within glue class it works perfectly.
Can anyone shed some light on this?
Edit
As requested here is a brievated version of Validation.php:
namespace glue;
use glue,
\glue\Exception,
\glue\Collection;
class Validation extends \glue\Component{
private function validateRule($rule){
// This is the line, notice the pass by reference down there?
$valid = $validator($field,$field_value,$params,&$this->model) && $valid;
}
}
The Call-time pass-by-reference has been deprecated error is thrown during script compilation, and auto-loading is disabled during compilation. It's disabled because the compiler cannot start compiling multiple scripts at the same time (i.e. it is not re-entrant), and auto-loading may load some script, which may require compiling it.
Source: https://github.com/php/php-src/blob/76ad52ccc501c02aeb068d2eb4f119ef6f0c2b6a/Zend/zend_execute_API.c#L1058
How can I make PHPUnit respect __autoload functions?
For example, I have these three files:
loader.php
function __autoload($name)
{
echo "foo\n";
require_once("$name.php");
}
test.php
require_once("loader.php");
class FooTest extends PHPUnit_Framework_TestCase
{
function testFoo()
{
new Foo();
}
}
foo.php
require_once("loader.php");
new Foo();
As expected php foo.php errors out, saying that file "Foo.php" doesn't
exist. The testFoo() function however errors out by saying that there is
no such class as Foo, and never echos the "foo\n" line.
This is expected behavior.
See this PHPUnit bugtracker entry: Upgrading to 3.5.10 has broken function of "autoload"
As of PHPUnit 3.5:
PHPUnit now uses an autoloader to load its classes. If the tested code requires an autoloader, use spl_autoload_register() to register it.
Quick fix:
The only change required is to add spl_autoload_register('__autoload') in your bootstrap script.
Longer fix:
If you can I'd suggest you just get rid of __autoload all together and use spl_autoload_register in your application as it is the way to go with PHP 5 code. (If you only have one autoloader you can't use the autoloader of your framework and so on)
Try using spl_autoload_register instead of __autoload. spl_autoload_register allows for multipe autoloaders to work together without clobbering each other.
PHPUnit uses spl_autoload_register which turns off __autoload.
I have a php file which has multiple classes in it. I noticed that __autoload is being called when I instantiate any of the classes, even after the 'package' file has been autoloaded. This is worrying me, because surely if the initial 'package' .php file has been loaded, it is unnecessary overhead for __autoload when the classes have already been loaded.
I probably made a hash of explaining that, so here is an example:
<?php
class Class1{};
class Class2{};
?>
Then in another file:
<?php
new Class1;
new Class2;
?>
__autoload will be used for both Class1 AND Class2's instantiation... even though they are housed in the same file.
Is there a way to get around this?
Sorry if my explanation isn't very good, I'd really appreciate any help or tips.
PHP's autoload should only be called if a class doesn't exist. In other words, for the most basic example, it uses the same logic as:
if( !class_exists("Class1") )
require "path\Class1.php";
If you are finding otherwise, I would check to be sure you are doing everything correctly and report a bug.
From PHP.net/autoload (important documentation highlighted):
In PHP 5, this is no longer necessary.
You may define an __autoload function
which is automatically called in case
you are trying to use a
class/interface which hasn't been
defined yet. By calling this
function the scripting engine is given
a last chance to load the class before
PHP fails with an error.
Bug in formatting, but the emphasis was on "which hasn't been defined yet". "Defined" occurs when a class is compiled (in most cases when a file containing said class is included).
__autoload is definitely NOT called the second time, when Class2 got defined as result of the first call.
First classes.php
<?php
class Class1 {};
class Class2 {};
Now test.php
<?php
function __autoload ($class)
{
print "Autoloading $class\n";
require 'classes.php';
}
$a = new Class1;
$b = new Class2;
print get_class($b);
When you run test.php, the result is:
Autoloading Class1
Class2
If you are getting different result, then there is something that you are not telling us.
How can I "reset" a namespace to the global one? Given the following code:
namespace foo;
include 'myfile.php';
myfile.php will now try to load all of its classes in the foo namespace, even though its classes are in the global namespace. Now it wouldn't be a big deal to swap the order of those lines, but how would I deal with myfile.php having an autoloader? It will try to load the classes in namespace foo.
Namespaces work on a per-file basis. If myfile.php doesn't declare any namespace then everything in it will belong to the global namespace, regardless of the namespace in which include was used.
Long story short, namespace declarations only apply to their own file, not includes.
myfile.php should declare the namespace that it wants to be in. If you want to ensure that myfile.php loads in the global namespace, I believe you can put
namespace // empty namespace means global
{
}
around the body of myfile.php. Make sure that you use the braces, or else you'll redefine the rest of the file in which myfile.php is included to also be in the global namespace.
Maybe I didn't understand your question well ; in that case, could you provide an example of what you get, and what you'd expect ?
Are you sure it will try to load the functions from myfile.php in the foo namespace ?
Considering I have one file (temp-2.php) that looks like this :
<?php
namespace foo;
function my_function_this_file() {
var_dump(__FUNCTION__);
}
my_function_this_file();
include 'my_other_file.php';
my_function_the_other_file();
And another one (my_other_file.php) that looks like this :
<?php
function my_function_the_other_file() {
var_dump(__FUNCTION__);
}
When I call the first one from my browser, I get this output :
string 'foo\my_function_this_file' (length=25)
string 'my_function_the_other_file' (length=26)
This seems to indicate the second function is not inside any namespace, except the global one -- which corresponds to the fact that it's not declared in any namespace.
If I remember correctly, the "namespace" instruction is only valid for the file it is used in, and not for included files.
The Import names cannot conflict with classes defined in the same file page from the namespaces FAQ seems to indicate that too.
Hope this helps, and I understood the question correctly...
EDIT : btw, swapping the order of the to lines, like this :
<?php
include 'my_other_file.php';
namespace foo;
Wouldn't work : the "namespace" instruction must be the first one of the file : you'll get a Fatal Error, if you do that :
Fatal error: Namespace declaration statement has to be the very first statement in the script
I'd just like to chime in on this with my findings. Since PHP cannot handle exceptions in __toString methods, the solution is to do it yourself: something along the lines of
public function __toString()
{
try {
return $this->draw();
} catch ( \Exception $e ) {
die( \error::_getExceptionPage( $e ) );
}
}
However what becomes really weird, the namespace of the call to error::_getExceptionPage($e) becomes the same as the class in which __toString was defined - and I wasn't able to find a way around that except prefixing everything in my exception handling code with \, i.e. every call to any class in getExceptionPage must become \someClass::staticMethod().
Same goes for included files, everything - all code since the thrown exception becomes embedded in the namespace of the faulty class. Tested with PHP 5.3.17