rfc:autoloader_error_handling

This is an old revision of the document!


Request for Comments: Autoloader Error Handling

Terms

I use the following terms in the subsequent examples:

  • project - for every piece of software(library, application, tool, ...)
  • autoloader(Exception) - for an autoloader that throws an exception
  • autoloader(Error) - for an autoloader that triggers a fatal error
  • autoloader(Silent) - for an autoloader that behaves silent
  • policy(Exception) - for a policy that relies on exceptions
  • policy(Error) - for a policy that relies on errors
  • policy(Silent) - for a policy that relies on a silent behavior

Introduction

If working in a multi project environment, sooner or later it comes to incompatibility between the registered autoloader implementations. The problem is that one registered autoloader can throw an exception, an other triggers a fatal error and a third behaves silent. So we need a solution which can handle all these cases based on the policy of the project scope.

Problems

Notes:

  • If an project registers its own autoloader than it uses automatically the same policy. I won't attribute this in the examples.
  • All autoloader implementations aren't compatible. They cannot load classes across projects.
  • The notation autoloader(...)-1 describes the position of the loader in the stack. The first position in this example.

Examples:

  • A third party project registers its own autoloader(Exception)-1. The main project registers its own autoloader(Silent)-2. In this case the autoloader of the main project will never be executed and therefore it cannot load any main project class. A workaround would be to register the main project autoloader as first.
  • PHPUnit registers several autoloaders(Silent)2-10. The test suite uses an autoloader(Exception)-1. Same problem here. None of the PHPUnit classes can be loaded. A workaround would be to register the test suite autoloader as last.
  • A third party project registers its own autoloader(Exception)-1. The main project registers its own autoloader(Error)-2. The same problem as in the first example. But here exists no workaround because the two autoloaders mutually interfere.
  • Every combination of non silent autoloaders shares the same problem as in the example before.

Requirements

As a consequence of the the previous problems now we have the following requirements which have to be fulfilled to work error-free across multiple projects.

  • Autoloaders may not interfere with each other
    • If I have an autoloader(Error) in project A and an autoloader(Exception) in project B then both autoloaders must be processed. Only if both autoloaders fails to load the class, a decision should be made on how the autoloader mechanism behaves.
  • For the main project it should be possible to manipulate the behavior of an autoloader
    • If an autoloader(Silent) fails to load a class from a sub project, it should be possible to change the behavior of the autoloader to the effect that an exception will be thrown. This could be necessary to shutdown the application in an ordered way.
    • On the other side it should be possible to manipulate an autoloader(Exception) or autoloader(Error), so that it behaves silent.
  • Sub projects(libraries, frameworks, ...) must rely on their own policy
    • If I have defined an autoloader(Exception) in my sub project, then it must be guaranteed that I can catch(in this sub project) exceptions thrown by this autoloader. Even if the main project changes the behavior of this autoloader.

Solution

Stop interference between autoloaders

To fulfill the first requirement the engine must catch all errors or exceptions until all autoloaders are executed. If the class could be loaded by one of the autoloaders, the script continues without any error. The solution for the second requirement describes what happens if the class couldn't be loaded.

Allow manipulation of autoloader results

After a class couldn't be loaded the engine must, based on the user settings, neither trigger a fatal error, throw an exception or continue silent. In order to ensure this behavior every autoloader must return true if a class could be loaded. If the class returns false, triggers an error or throws an exception then its a sure sign that a class couldn't be loaded. The engine must save these status(return value, error message and other related data) for all executed autoloaders. Based on the saved status now the engine can manipulate the result of the autoloader.

As example: If the user sets the policy to:

  • POLICY_EXCEPTION, then the engine creates a new autoloader exception, which contains all error related data
  • POLICY_ERROR, then the engine triggers a fatal error and logs all related data
  • POLICY_SILENT, then the engine continues silent

Sub projects must rely on their own policy

A possible solution for this point would be the introduction of a namespace based autoloader. So a subproject can use its own autoloader mechanism which doesn't interfere with other autoloaders. A nice side effect would also that only these autoloader implementations for the registered namespace are executed.

The big picture

It would be a huge BC break to implement all this features. Therefore a new class could be implemented for the spl extension. The next code example describes a possible interface for this class.

interface SplClassAutoloader {
 
   const POLICY_ERROR = 1;
   const POLICY_EXCEPTION = 2;
   const POLICY_SILENT = 3;
 
   /**
    * Creates a new autoloader instance.
    *  
    * @param int $policy The policy for this autoloader.
    */
   public function __construct($policy = self::POLICY_SILENT);
 
   /**
    * Registers a namespace for this autoloader.
    * 
    * @param string $namespace The namespace to register.
    */
   public function registerNamespace($namespace);
 
   /**
    * Unregisters a namespace for this autoloader.
    * 
    * @param string $namespace The namespace to unregister.
    */
   public function unregisterNamespace($namespace);
 
   /**
    * Registers a class loader implementation.
    * 
    * @param SplClassLoader $classLoader The class loader to register.
    */
   public function registerClassLoader(SplClassLoader $classLoader);
 
   /**
    * Unregisters a class loader implementation.
    * 
    * @param SplClassLoader $classLoader The class loader to unregister.
    */
   public function unregisterClassLoader(SplClassLoader $classLoader);
 
   /**
    * Hook the autoloader into the engine.
    */
   public function register();
 
   /**
    * Remove the autoloader from the engine.
    */
   public function unregister();
}

The class loader interface should have only one method.

interface SplClassLoader {
 
   /**
    * Loads the given class.
    *  
    * @param string $className
    * @return boolean True if the class could be loaded, false otherwise.
    */
   public function load($className);
}

Changelog

0.1 - First draft

rfc/autoloader_error_handling.1322171096.txt.gz · Last modified: 2017/09/22 13:28 (external edit)