I'm having issues with autoloading classes in PHP's magic __sleep() method. Autoloading doesn't take place, so the class cannot be found. In an attempt to debug this I tried calling spl_autoload_functions() which then causes PHP to segfault...
The example code below demonstrates the problem. Using an instance method or a static method has the same behaviour. This seems to work fine for me using __destruct() instead, which suits my use case fine, but I'm curious as to the reason behind this. Is it a PHP bug, or is there a sensible explanation?
In Foo.php, just as an autoload target
<?php
class Foo {
public static function bar() {
echo __FUNCTION__;
}
}
?>
In testcase.php
<?php
class Autoloader {
public static function register() {
// Switch these calls around to use a static or instance autoload function
spl_autoload_register('Autoloader::staticLoad');
//spl_autoload_register(array(new self, 'instanceLoad'));
}
public function instanceLoad($class) {
require_once dirname(__FILE__) . '/' . $class . '.php';
}
public static function staticLoad($class) {
require_once dirname(__FILE__) . '/' . $class . '.php';
}
}
Autoloader::register();
class Bar {
public function __sleep() {
// Uncomment the next line to segfault php...
// print_r(spl_autoload_functions());
Foo::bar();
}
}
$bar = new Bar;
This can be run by placing both files in a directory and running php testcase.php. This occurs for me with PHP 5.3.3 and 5.2.10.
The problem you describe sounds very similar to this entry in the PHP bug tracker:
http://bugs.php.net/bug.php?id=53141
That bug was fixed in PHP 5.3.4 (search for "53141" on http://php.net/ChangeLog-5.php).
Related
I keep trying to know what the problem is with this very simple class loader script.
The class loader looks like this:
#src/vendors/Autoloading/lib/ClassLoader.php
namespace App\Vendors\Autoloading;
class ClassLoader
{
private $path;
function __construct($path)
{
$this->path = $path;
}
public function load($class)
{
if(file_exists( $class = str_replace(array('\\', '_'), DIRECTORY_SEPARATOR, $this->path) . '.php')){
require $class;
return true;
}
}
public function register()
{
return spl_autoload_register([$this, 'load']);
}
}
The initial class loader had more methods and some functions to validate the file names ...
but, in the process of debugging I had to narrow it to that.
So, that class loader is being required inside an autoload.php file, as you can see below.
#src/vendors/autoload.php
namespace App\Vendors;
require 'Autoloading/lib/ClassLoader.php';
$autoload = new Autoloading\ClassLoader('path/Foo/FooClass');
$autoload->register();
The FooClass.php is located in src/Foo/FooClass.php
namespace App\Foo;
class FooClass{}
and there is actually no problem with the autoloading part, the class gets loaded just fine, but it is done twice which shows me the below error. I am calling it from an index.php file
<?php
use \App\Foo\FooClass;
FooClass::somefunction();
Just using that generates this error.
Fatal error: Cannot redeclare class path\foo\FooClass in /path/to/index.php on line 4
Your autoloading function is wrong:
public function load($class)
{
if (
file_exists(
$class = str_replace(
array('\\', '_'),
DIRECTORY_SEPARATOR,
$this->path)
. '.php')
) {
require $class;
return true;
}
}
The function is calles with the name of the class to be loaded (if impossible, the function should do nothing).
What you do is ignoring the class name, and create a new one based on the path the autoloader is created with. This will always load the same file, with the same class, even if there are different classes to be loaded.
And this explains why you get the error, because no matter which class name gets passed, you always include the one file that is related to the path.
You probably want to use a proper PSR-0 or PSR-4 autoloader. I would recommend using the one that comes with Composer, as you are likely to be using Composer sooner or later yourself. Why not starting today?
Check if the class already exists using class_exists before checking for the file
Try using require_once instead of require. Your autoloader won't keep track of what files have been loaded so far.
Here's how I set mine up. I don't use a class. Maybe this will help
function PlatformAutoloader($classname) {
try {
// Change \ to / so namespacing will work
$classname = strtolower(str_replace('\\', DIRECTORY_SEPARATOR, $classname));
if(!#include_once(DIR_CLASSES . DIRECTORY_SEPARATOR . $classname . '.php')) return false;
} catch(Exception $e) {
return false;
}
}
spl_autoload_register('PlatformAutoloader');
i'm updating my code and trying to use spl_autoload_register but IT SIMPLY DOESN'T WORK!!!
I'm using PHP 5.3.8 - Apache 2.22 on Centos / Ubuntu / Win7 and trying to echo something but i get nothing instead... have been trying to make it work for the last 3 hours with no result... this is driving me mad!!!
class ApplicationInit {
// Constructor
public function __construct() {
spl_autoload_register(array($this, 'classesAutoloader'));
echo 'construct working...!';
}
// Autoloading methods
public function classesAutoloader($class) {
include 'library/' . $class . '.php';
echo 'autoload working...!';
}
}
first echo from __construct works but the "classesAutoloader" doesn't work at all, this class is defined in a php file within a folder and i'm calling it from index.php like follows:
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', getcwd() . DS);
define('APP', ROOT . 'application' . DS);
// Initializing application
require(APP.'appInit.php');
$classAuto = new ApplicationInit();
any help is truly appreciated, thanks in advance!
It seems like you're doing the wrong thing. The function that you pass to spl_autoload_register is what's responsible for loading the class file.
Your code is calling
$classAuto = new ApplicationInit();
but by that time, ApplicationInit is already loaded so the autoload function doesn't get called
A more logical way would be for you to to call
spl_autoload_register(function($class){
include 'library/' . $class . '.php';
});
Then when you call
$something = new MyClass();
And the MyClass is not defined, then it will call your function to load that file and define the class.
What is your problem? Your code is working correctly.
class ApplicationInit {
public function __construct() {
spl_autoload_register(array($this, 'classesAutoloader'));
echo 'construct working...!';
}
public function classesAutoloader($class) {
include 'library/' . $class . '.php';
echo 'autoload working...!';
}
}
$classAuto = new ApplicationInit(); //class already loaded so dont run autoload
$newclass = new testClass(); //class not loaded so run autoload
When I call any function without recursion (class is loaded by SPL) - all fine, but if that function is calling itself (recursion) - nothing works.
If I use function without autoloader - all works great. I think that happens because object of class doesn't exist, like with magic methods: you have to use __callStatic, not a __call with abstract using class, I was trying to make this function static oO but nothing works again.
Any ideas how does it possible to use recursion through autoloader?
For example this function from php.net doesn't work in autoloader mode:
function r_implode($glue, $pieces)
{
foreach ($pieces as $r_pieces)
{
if (is_array( $r_pieces ))
{
$r_pieces = r_implode($glue, $r_pieces);
}
else
{
$retVal[] = $r_pieces;
}
}
return implode($glue, $retVal);
}
class load
{
public static function init()
{
return spl_autoload_register(array(__CLASS__, "hook"));
}
public static function quit()
{
return spl_autoload_unregister(array(__CLASS__, "hook"));
}
public static function hook($class)
{
// echo "CLASS IS:$class<br>";
$lnk=PATH . str_replace("_", "/", $class) . ".php";
ob_start();
require $lnk;
ob_end_clean();
return $class;
}
}
So when I add function into a class tools, and call tools::r_implode($a,$b); function doesn't work, but when I insert this function in the same php and call r_implode($a,$b) works.
From your posted info this is not clear. You didn't describe the actual error. But I surmise your problem is actually this:
class tools {
function r_implode($glue, $pieces)
{
$r_pieces = r_implode($glue, $r_pieces);
}
}
You have packed that function into a class, and the autoloader may even find it. But you didn't adapt the recursive call. If you don't use tools::r_implode for the recursion, then PHP won't find that function. Static methods need to be named explicitly (with class:: prefix). Keeping the plain function name there won't work.
I've read some posts about namespaces and autoload in php 5.3+, but still haven't succeeded in creating one working :x maybe some of you have an idea of what's going wrong about my code ?
Thank you previously.
Autoloader.php class
<?php
namespace my;
class AutoLoader {
private $aExt;
private $sPath;
protected static $instance;
public static function getInstance() {
if(!self::$instance instanceof self ) {
self::$instance = new self();
}
return self::$instance;
}
function __construct($sPath = __DIR__, $exts = 'php') {
// define path and extensions to include
$this->setPath($sPath);
$this->setExtensions($exts);
}
public function getPath() {
return $this->sPath;
}
public function setPath($path){
$this->sPath = $path;
}
public function removePath() {
unset ($this->sPath);
}
public function addExtension($ext) {
// prepends period to extension if none found
$this->aExt[$ext] = (substr($ext, 0, 1) !== '.') ? '.'.$ext : $ext;
}
public function removeExtension($ext) {
unset ($this->aExt[$ext]);
}
public function getExtensions() {
return $this->aExt;
}
public function setExtensions($extensions) {
// convert
if (is_string($extensions)) {
$extensions = array($extensions);
}
// add
foreach($extensions as $ext) {
$this->addExtension($ext);
}
}
public function register() {
set_include_path($this->sPath);
// comma-delimited list of valid source-file extensions
spl_autoload_extensions(implode(',',$this->aExt));
// default behavior without callback
spl_autoload_register(array($this, 'autoload'));
}
public function autoload($sClassName) {
include_once($sClassName.'.php');
return;
}
}
$autoloader = new AutoLoader();
$autoloader->register();
?>
MyClass.php the class i am trying to load dinamically
<?php
namespace my\tools;
class MyClass {
function __construct() {}
function __destruct() {}
function test() {
echo 'ok';
}
}
?>
index.php the caller
<?php
include_once('../Libraries/php/my/AutoLoader.php');
new my\tools\MyClass();
?>
and finally the class structures on my disk
Libraries
|_php
|_my
| |_Autoloader.php
|
|_MyClass.php
That's a tad bit over-engineered, my friend.
You might want to take a look at simply using PSR-0 (PRS-0 is now depreciated, PSR-4 is the new one), an autoloader specification from a large number of PHP projects, like phpBB, Joomla, CakePHP, Zend Framework and lots more. It's built with namespaces in mind, but works well with or without them.
The advantage of PSR-0 (or PSR-4) is that it leads to a clean, simple, obvious directory structure that an increasing number of projects are supporting. This means using one autoloader instead of a single autoloader for every single set of code.
spl_autoload_register() expects a valid callback. You give ... something ^^ But not a callback. A callback is
// a closure
$cb = function ($classname) { /* load class */ }
// object method
$cb = array($object, 'methodName');
// static class method
$cb = array('className', 'methodName');
// function
$cb = 'functionName';
See manual: spl_autoload_register() for further information and examples.
After searching a little on PHP.net website, the solution were really simple :/
In fact php autoload function MUST be on root namespace /, mine was on first level of my package (my/), when i moved the class to root namespace everything worked fine.
I'm trying to make some kind of function that loads and instantiates a class from a given variable. Something like this:
<?php
function loadClass($class) {
$sClassPath = SYSPATH."/classes/{$class}.php";
if (file_exists($sClassPath)) {
require_once($sClassPath);
$class = $class::getInstance();
}
}
?>
If I use it like this:
<?php
loadClass('session');
?>
It should include and instantiate the session class.
BTW: the static getInstance function comes from this code:
<?php
function getCallingClass() {
$backtrace = debug_backtrace();
$method = $backtrace[1]['function'];
$file = file($backtrace[1]['file']);
$line = $file[($backtrace[1]['line'] - 1)];
$class = trim(preg_replace("/^.+?([A-Za-z0-9_]*)::{$method}\(.*$/s", "\\1\\2", $line));
if(! class_exists($class)) {
return false;
} return $class;
}
class Core {
protected static $instances = array();
public static function getInstance() {
$class = getCallingClass();
if (!isset(self::$instances[$class])) {
self::$instances[$class] = new $class();
} return self::$instances[$class];
}
}
?>
The thing is that right now the way to use the functions in a class is this:
<?php
$session = session::getInstance();
?>
But now I want to build that into a function so that I never again have to use that line of code.
I just say loadClass('session');
and than I can use $session->blablablafunction();
Calling static functions on a variable class name is apparently available in PHP 5.3:
Foo::aStaticMethod();
$classname = 'Foo';
$classname::aStaticMethod(); // As of PHP 5.3.0
http://php.net/manual/en/language.oop5.static.php
Could definitely use that right now myself.
Until then you can't really assume that every class you are loading is designed to be a singleton. So long as you are using < 5.3 you'll have to just load the class and instantiate via the constructor:
function loadClass($class) {
$sClassPath = SYSPATH."/classes/{$class}.php";
if (file_exists($sClassPath)) {
require_once($sClassPath);
$class = new $class;
}
}
OR
Just load the class without creating an object from it. Then call "::getInstance()" on those meant to be singletons, and "new" on those that are not, from outside of the loadClass() function.
Although, as others have pointed out earlier, an __autoload() would probably work well for you.
You can use call_user_func():
$class = call_user_func(array($class, 'getInstance'));
The first argument is a callback type containing the classname and method name in this case.
Why not use __autoload() function?
http://www.php.net/autoload
then you just instantiate object when needed.
It looks like you are fighting PHP's current implementation static binding, which is why you are jumping through hoops with getCallingClass. I can tell you from experience, you should probably abandon trying to put instantiation in a parent class through a static method. It will cause you more problems in the end. PHP 5.3 will implement "late static binding" and should solve your problem, but that obviously doesn't help now.
You are probably better off using the autoload functionality mentioned by kodisha combined with a solid Singleton implementation. I'm not sure if your goal is syntactic sugar or not, but it think you will do better in the long run to steer clear of trying to save a few characters.
Off the top of my head, needs testing, validation etc:
<?php
function loadClass($className) {
if (is_object($GLOBALS[$className]))
return;
$sClassPath = SYSPATH."/classes/{$className}.php";
if (file_exists($sClassPath)) {
require_once($sClassPath);
$reflect = new ReflectionClass($className);
$classObj = $reflect->newInstanceArgs();
$GLOBALS[$className] = $classObj;
}
}
?>
Late static bindings will work for you I think. In the construct of each class do:
class ClassName
{
public static $instances = array();
public function __construct()
{
self::$instances[] = $this;
}
}
Then...
Here is an autoloader I created. See if this solves your dilemma.
// Shorten constants for convenience
define ('DS', DIRECTORY_SEPARATOR);
define ('PS', PATH_SEPARATOR);
$template = "default";
// Define an application path constants
define ('APP_ROOT', realpath('.').DS);
define ('VIEW', APP_ROOT . 'Views' . DS);
define ('MODEL', APP_ROOT . 'Models' . DS);
define ('CONTROLLER', APP_ROOT . 'Controllers' . DS);
define ('TEMPLATE', VIEW."templates".DS.$template.DS);
define ('CONTENT', VIEW."content".DS);
define ('HELPERS', MODEL."helpers".DS);
// Check if application is in development stage and set error reporting and
// logging accordingly
error_reporting(E_ALL);
if (defined('DEVELOPMENT')) {
ini_set('display_errors', 1);
} else {
ini_set('display_errors', 0);
ini_set('log_errors', 'On');
ini_set('error_log', APP_ROOT.'error.log');
}
$paths = array(APP_ROOT, VIEW, MODEL, CONTROLLER, TEMPLATE, CONTENT, HELPERS);
// Set the include path from Config Object
set_include_path(implode(PS, $paths));
// Autoloader
function __autoload($class)
{
require_once $class.'.php';
return;
}
Then all you have to do is
$var = new ClassName();
but you have to have a php file in the path with the name
ClassName.php
where ClassName is the same as the name of the class you want to instantiate.