Выбрасывать исключения в автозагрузчик SPL?

Есть ли способ генерировать исключения из автозагрузчика SPL в PHP в случае сбоя? Кажется, он не работает под PHP 5.2.11.

class SPLAutoLoader{

    public static function autoloadDomain($className) {
        if(file_exists('test/'.$className.'.class.php')){
            require_once('test/'.$className.'.class.php');
            return true;
        }       

        throw new Exception('File not found');
    }

} //end class

//start
spl_autoload_register( array('SPLAutoLoader', 'autoloadDomain') );

try{
    $domain = new foobarDomain();
}catch(Exception $c){
    echo 'File not found';
}

Когда вызывается приведенный выше код, нет никаких признаков исключения, вместо этого я получаю стандартную «Неустранимая ошибка: класс« foobarDomain »не найден в bla». И выполнение скрипта прекращается.


person clops    schedule 16.10.2009    source источник
comment
Что именно происходит? Вы только сказали, что это терпит неудачу, но не как это терпит неудачу.   -  person Charles    schedule 16.10.2009
comment
Когда вызывается приведенный выше код, нет никаких признаков исключения, вместо этого я получаю стандартную фатальную ошибку: класс «foobarDomain» не найден в bla. И выполнение скрипта прекращается.   -  person clops    schedule 16.10.2009
comment
Большое спасибо. Что происходит, когда вы выбрасываете исключение первым делом в функции, до включения?   -  person Charles    schedule 16.10.2009
comment
Кажется, я обнаружил еще одну ошибку PHP   -  person clops    schedule 19.10.2009


Ответы (3)


Это не ошибка, это дизайнерское решение:

Примечание. Исключения, возникающие в функции __autoload, не могут быть перехвачены в блоке catch, что приводит к фатальной ошибке.

Причина в том, что может быть более одного обработчика автозагрузки, и в этом случае вы не хотите, чтобы первый обработчик выдавал исключение и обходил второй обработчик. Вы хотите, чтобы ваш второй обработчик имел возможность автоматически загружать свои классы. Если вы используете библиотеку, которая использует функцию автозагрузки, вы не хотите, чтобы она обходила ваш обработчик автозагрузки, потому что они генерируют исключения внутри своего автозагрузчика.

Если вы хотите проверить, можете ли вы создать экземпляр класса, используйте class_exists и передайте true в качестве второго аргумента (или оставьте его, true используется по умолчанию):

if (class_exists('foobarDomain', $autoload = true)) {
    $domain = new foobarDomain();
} else {
    echo 'Class not found';
}
person Ionuț G. Stan    schedule 19.10.2009
comment
Большое спасибо - вы спасли день! - person clops; 19.10.2009
comment
Это поведение изменилось в PHP 5.3 — теперь исключения могут быть выброшены и перехвачены в автозагрузчике. Однако вам нужно быть осторожным, если у вас зарегистрировано несколько автозагрузчиков. - person MrWhite; 20.02.2014

Согласно комментариям в документации для spl_autoload_register, возможно вызвать другую функцию из автозагрузчика, которая, в свою очередь, вызовет исключение.

class SPLAutoLoader{

    public static function autoloadDomain($className) {
        if(file_exists('test/'.$className.'.class.php')){
            require_once('test/'.$className.'.class.php');
            return true;
        }       
        self::throwFileNotFoundException();
    }

    public static function throwFileNotFoundException()
    {
        throw new Exception('File not found');
    }

} //end class

//start
spl_autoload_register( array('SPLAutoLoader', 'autoloadDomain') );

try{
    $domain = new foobarDomain();
}catch(Exception $c){
    echo 'File not found';
}
person Ignas R    schedule 16.10.2009
comment
К сожалению, это не работает. Та же ошибка и никаких исключений :( - person clops; 17.10.2009

Вот полноценный фабричный объект, который демонстрирует автоматическую загрузку, поддержку пространств имен, вызовы из нестатических экземпляров (с переменными путями), обработку ошибок загрузки и пользовательских исключений.

abstract class AbstractFactory implements \ArrayAccess
{
    protected $manifest;
    function __construct($manifest)
    {
        $this->manifest = $manifest;
    }

    abstract function produce($name);

    public function offsetExists($offset)
    {
        return isset($this->manifest[$offset]);
    }

    public function offsetGet($offset)
    {
        return $this->produce($offset);
    }
    //implement stubs for other ArrayAccess funcs
}


abstract class SimpleFactory extends AbstractFactory {

    protected $description;
    protected $path;
    protected $namespace;

    function __construct($manifest, $path, $namespace = "jj\\") {
        parent::__construct($manifest);
        $this->path = $path;
        $this->namespace = $namespace;
        if (! spl_autoload_register(array($this, 'autoload'), false)) //throws exceptions on its own, but we want a custom one
            throw new \RuntimeException(get_class($this)." failed to register autoload.");
    }

    function __destruct()
    {
        spl_autoload_unregister(array($this, 'autoload'));
    }

    public function autoload($class_name) {
        $file = str_replace($this->namespace, '', $class_name);
        $filename = $this->path.$file.'.php';
        if (file_exists($filename))
            try {
                require $filename; //TODO add global set_error_handler and try clause to catch parse errors
            } catch (Exception $e) {} //autoload exceptions are not passed by design, nothing to do
    }

    function produce($name) {
        if (isset($this->manifest[$name])) {
            $class = $this->namespace.$this->manifest[$name];
            if (class_exists($class, $autoload = true)) {
                return new $class();
            } else throw new \jj\SystemConfigurationException('Factory '.get_class($this)." was unable to produce a new class {$class}", 'SYSTEM_ERROR', $this);
//an example of a custom exception with a string code and data container

        } else throw new LogicException("Unknown {$this->description} {$name}.");
    }

    function __toString() //description function if custom exception class wants a string explanation for its container
    {
        return $this->description." factory ".get_class($this)."(path={$this->path}, namespace={$this->namespace}, map: ".json_encode($this->manifest).")";
    }

}

и наконец пример:

namespace jj;
require_once('lib/AbstractFactory.php');
require_once('lib/CurrenciesProvider.php'); //base abstract class for all banking objects that are created

class CurrencyProviders extends SimpleFactory
{
    function __construct()
    {
        $manifest = array(
          'Germany' => 'GermanBankCurrencies',
          'Switzerland' => 'SwissBankCurrencies'
        );

        parent::__construct($manifest, __DIR__.'/CurrencyProviders/', //you have total control over relative or absolute paths here
       'banks\');
        $this->description = 'currency provider country name';
    }


}

теперь сделай

$currencies_cache = (new \jj\CurrencyProviders())['Germany'];

or

$currencies_cache = (new \jj\CurrencyProviders())['Ukraine'];

LogicException("Неизвестное название страны-поставщика валюты Украина")

Если в /CurrencyProviders/ нет файла SwissCurrencies.php,

\jj\SystemConfigurationException('Factory jj\CurrencyProviders не удалось создать новый классbanks\SwissCurrencies. Данные отладки: имя страны-поставщика валюты factory jj\CurrencyProviders(path=/var/www/hosted/site/.../CurrencyProviders/ , namespace=banks\, map: {"Германия": "GermanBankCurrencies", "Швейцария": "SwissBankCurrencies"}')

Приложив достаточные усилия, эту фабрику можно расширить для обнаружения ошибок синтаксического анализа (Как отловить ошибку require() или include() в PHP?) и передать аргументы конструкторам.

person ArticIceJuice    schedule 01.10.2015