rfc:splclassloader

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Next revisionBoth sides next revision
rfc:splclassloader [2011/10/25 02:01] – [Proposal and Patch] guilhermeblancorfc:splclassloader [2012/06/29 06:53] laruence
Line 3: Line 3:
   * Date: 2010-03-22   * Date: 2010-03-22
   * Author: Guilherme Blanco <guilhermeblanco@hotmail.com>   * Author: Guilherme Blanco <guilhermeblanco@hotmail.com>
-  * Status: In the works+  * Status: Voting at https://wiki.php.net/rfc/splclassloader/vote 
   * First Published at: http://wiki.php.net/rfc/splclassloader   * First Published at: http://wiki.php.net/rfc/splclassloader
  
Line 25: Line 25:
   * Each namespace can have as many sub-namespaces as it wishes.   * Each namespace can have as many sub-namespaces as it wishes.
   * Each namespace separator is converted to a DIRECTORY_SEPARATOR when loading from the file system.   * Each namespace separator is converted to a DIRECTORY_SEPARATOR when loading from the file system.
-  * Each "\_" character in the CLASS NAME is converted to a DIRECTORY_SEPARATOR. The "\_" character has no special meaning in the namespace.+  * Each "_" character in the CLASS NAME is converted to a DIRECTORY_SEPARATOR. The "_" character has no special meaning in the namespace.
   * The fully-qualified namespace and class is suffixed with ".php" when loading from the file system.   * The fully-qualified namespace and class is suffixed with ".php" when loading from the file system.
   * Alphabetic characters in vendor names, namespaces, and class names may be of any combination of lower case and upper case.   * Alphabetic characters in vendor names, namespaces, and class names may be of any combination of lower case and upper case.
  
-===== Usage Examples =====+===== Extended Rules for Proposal ===== 
 + 
 +Together with PSR-0 rules, for SplClassLoader we would also include a few valid rules that must be implemented, that addicts to original PSR-0 rules, not affecting any of the projects participants, but also improving the flexibility of autoloader: 
 + 
 +  * Allow a namespace to be mapped through multiple paths. 
 +  * Make SplClassLoader silently fails if class is not found. This one is useful when using multiple instances of SplClassLoader in a single script. 
 +  * Allow SplClassLoader to lookup in include_path. This is useful for projects that define their own on include_path, such as PEAR and Zend Framework. 
 + 
 +===== Flexibility of a Rule ===== 
 + 
 +Even though PSR-0 is strict, PHP cannot force php extension. Ideally, the file extension is customizable, but default approach would be the defined one in PSR-0. 
 + 
 +===== Initial missing support ===== 
 + 
 +PHP does not provide a single approach for any OO based autoloader, so initially a new interface is required, for anyone interested to implement their own Autoloader. From now on, let's name it as SplAutoloader. 
 +The purpose of this interface is to bring the minimum contract that any interested to autoload their resources should follow.  
 +Here is the proposed implementation: 
 + 
 +<code php> 
 +/** 
 + * SplAutoloader defines the contract that any OO based autoloader must follow. 
 + * 
 + * @author Guilherme Blanco <guilhermeblanco@php.net> 
 + */ 
 +interface SplAutoloader 
 +
 +    /** 
 +     * Defines autoloader to work silently if resource is not found. 
 +     * 
 +     * @const 
 +     */ 
 +    const MODE_SILENT = 0; 
 +     
 +    /** 
 +     * Defines autoloader to work normally (requiring an un-existent resource). 
 +     * 
 +     * @const 
 +     */ 
 +    const MODE_NORMAL = 1; 
 +     
 +    /** 
 +     * Defines autoloader to work in debug mode, loading file and validating requested resource. 
 +     * 
 +     * @const 
 +     */ 
 +    const MODE_DEBUG = 2; 
 +     
 +    /** 
 +     * Define the autoloader work mode. 
 +     * 
 +     * @param integer $mode Autoloader work mode. 
 +     */ 
 +    public function setMode($mode); 
 +     
 +    /** 
 +     * Add a new resource lookup path. 
 +     * 
 +     * @param string $resourceName Resource name, namespace or prefix. 
 +     * @param mixed $resourcePath Resource single path or multiple paths (array). 
 +     */ 
 +    public function add($resourceName, $resourcePath = null); 
 +     
 +    /** 
 +     * Load a resource through provided resource name. 
 +     * 
 +     * @param string $resourceName Resource name. 
 +     */ 
 +    public function load($resourceName); 
 +     
 +    /** 
 +     * Register this as an autoloader instance. 
 +     * 
 +     * @param boolean Whether to prepend the autoloader or not in autoloader's list. 
 +     */ 
 +    public function register($prepend = false); 
 +     
 +    /** 
 +     * Unregister this autoloader instance. 
 +     * 
 +     */ 
 +    public function unregister(); 
 +
 +</code> 
 + 
 +===== Examples of Namespace Resolution =====
  
 The standards we set here should be the lowest common denominator for painless autoloader interoperability. You can test that you are following these standards by utilizing this sample SplClassLoader implementation which is able to load PHP 5.3 classes. The standards we set here should be the lowest common denominator for painless autoloader interoperability. You can test that you are following these standards by utilizing this sample SplClassLoader implementation which is able to load PHP 5.3 classes.
Line 46: Line 130:
     * /path/to/project/lib/vendor/namespace/package_name/Class/Name.php     * /path/to/project/lib/vendor/namespace/package_name/Class/Name.php
  
-===== Example implementation =====+===== Examples of usage ===== 
 + 
 +Autoloading Doctrine 2: 
 + 
 +<code php> 
 +$classLoader = new \SplClassLoader(); // $mode is "normal" 
 +$classLoader->add('Doctrine', array( 
 +    '/path/to/doctrine-common', '/path/to/doctrine-dbal', '/path/to/doctrine-orm' 
 +)); 
 +$classLoader->register(true); // Autoloader is prepended 
 +</code>  
 + 
 +Autoloading PEAR1: 
 + 
 +<code php> 
 +$classLoader = new \SplClassLoader(); 
 +$classLoader->setMode(\SplClassLoader::MODE_SILENT); 
 +$classLoader->setIncludePathLookup(true); 
 +$classLoader->add('PEAR'); 
 +$classLoader->register(); 
 +</code> 
 + 
 +Autoloading in debug mode: 
 + 
 +<code php> 
 +$classLoader = new \SplClassLoader(); 
 +$classLoader->setMode(\SplClassLoader::MODE_NORMAL | \SplClassLoader::MODE_DEBUG); 
 +$classLoader->add('Symfony', '/path/to/symfony'); 
 +$classLoader->add('Zend', '/path/to/zf'); 
 +$classLoader->register(); 
 +</code> 
 + 
 +===== Example of simplest implementation =====
  
 Below is an example function to simply demonstrate how the above proposed standards are autoloaded. Below is an example function to simply demonstrate how the above proposed standards are autoloaded.
Line 56: Line 172:
     $fileName  = '';     $fileName  = '';
     $namespace = '';     $namespace = '';
 +    
     if ($lastNsPos = strripos($className, '\\')) {     if ($lastNsPos = strripos($className, '\\')) {
         $namespace = substr($className, 0, $lastNsPos);         $namespace = substr($className, 0, $lastNsPos);
Line 61: Line 178:
         $fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;         $fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
     }     }
 +    
     $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';     $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
  
Line 72: Line 190:
  
 <code php> <code php>
-<?php 
- 
 /** /**
  * SplClassLoader implementation that implements the technical interoperability  * SplClassLoader implementation that implements the technical interoperability
  * standards for PHP 5.3 namespaces and class names.  * standards for PHP 5.3 namespaces and class names.
  *  *
- http://groups.google.com/group/php-standards/web/final-proposal+ https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md 
 + * 
 + * Example usage: 
 + * 
 +     $classLoader = new \SplClassLoader(); 
 + * 
 +     // Configure the SplClassLoader to act normally or silently 
 +     $classLoader->setMode(\SplClassLoader::MODE_NORMAL); 
 + * 
 +     // Add a namespace of classes 
 +     $classLoader->add('Doctrine', array( 
 +         '/path/to/doctrine-common', '/path/to/doctrine-dbal', '/path/to/doctrine-orm' 
 +     )); 
 + * 
 +     // Add a prefix 
 +     $classLoader->add('Swift', '/path/to/swift'); 
 + * 
 +     // Add a prefix through PEAR1 convention, requiring include_path lookup 
 +     $classLoader->add('PEAR'); 
 + * 
 +     // Allow to PHP use the include_path for file path lookup 
 +     $classLoader->setIncludePathLookup(true); 
 + * 
 +     // Possibility to change the default php file extension 
 +     $classLoader->setFileExtension('.php');
  *  *
- * // Example which loads classes for the Doctrine Common package in the +     // Register the autoloader, prepending it in the stack 
- // Doctrine\Common namespace. +     $classLoader->register(true);
- * $classLoader = new SplClassLoader('Doctrine\Common', '/path/to/doctrine'); +
- $classLoader->register();+
  *  *
 + * @author Guilherme Blanco <guilhermeblanco@php.net>
  * @author Jonathan H. Wage <jonwage@gmail.com>  * @author Jonathan H. Wage <jonwage@gmail.com>
  * @author Roman S. Borschel <roman@code-factory.org>  * @author Roman S. Borschel <roman@code-factory.org>
Line 91: Line 230:
  * @author Fabien Potencier <fabien.potencier@symfony-project.org>  * @author Fabien Potencier <fabien.potencier@symfony-project.org>
  */  */
-class SplClassLoader+class SplClassLoader implements SplAutoloader
 { {
-    private $_fileExtension = '.php'; +    /** 
-    private $_namespace+     * @var string 
-    private $_includePath+     */ 
-    private $_namespaceSeparator '\\';+    private $fileExtension = '.php'; 
 +     
 +    /** 
 +     * @var boolean 
 +     */ 
 +    private $includePathLookup = false; 
 +     
 +    /** 
 +     * @var array 
 +     */ 
 +    private $resources = array(); 
 +     
 +    /** 
 +     * @var integer 
 +     */ 
 +    private $mode self::MODE_NORMAL;
  
     /**     /**
-     Creates a new <tt>SplClassLoader</tt> that loads classes of the +     {@inheritdoc}
-     * specified namespace. +
-     * +
-     @param string $ns The namespace to use.+
      */      */
-    public function __construct($ns = null, $includePath = null)+    public function setMode($mode)
     {     {
-        $this->_namespace = $ns+    if ($mode & self::MODE_SILENT && $mode & self::MODE_NORMAL) { 
-        $this->_includePath = $includePath;+        throw new \InvalidArgumentException( 
 +            sprintf('Cannot have %s working normally and silently at the same time!', __CLASS__) 
 +        ); 
 +    } 
 +     
 +        $this->mode = $mode;
     }     }
 + 
     /**     /**
-     Sets the namespace separator used by classes in the namespace of this class loader.+     Define the file extension of resource files in the path of this class loader.
      *      *
-     * @param string $sep The separator to use.+     * @param string $fileExtension
      */      */
-    public function setNamespaceSeparator($sep)+    public function setFileExtension($fileExtension)
     {     {
-        $this->_namespaceSeparator = $sep;+        $this->fileExtension = $fileExtension;
     }     }
 + 
     /**     /**
-     Gets the namespace seperator used by classes in the namespace of this class loader.+     Retrieve the file extension of resource files in the path of this class loader.
      *      *
-     * @return void+     * @return string
      */      */
-    public function getNamespaceSeparator()+    public function getFileExtension()
     {     {
-        return $this->_namespaceSeparator;+        return $this->fileExtension;
     }     }
 + 
     /**     /**
-     Sets the base include path for all class files in the namespace of this class loader.+     Turns on searching the include for class files. Allows easy loading installed PEAR packages.
      *      *
-     * @param string $includePath+     * @param boolean $includePathLookup
      */      */
-    public function setIncludePath($includePath)+    public function setIncludePathLookup($includePathLookup)
     {     {
-        $this->_includePath = $includePath;+        $this->includePathLookup = $includePathLookup;
     }     }
 + 
     /**     /**
      * Gets the base include path for all class files in the namespace of this class loader.      * Gets the base include path for all class files in the namespace of this class loader.
      *      *
-     * @return string $includePath+     * @return boolean
      */      */
-    public function getIncludePath()+    public function getIncludePathLookup()
     {     {
-        return $this->_includePath;+        return $this->includePathLookup;
     }     }
 + 
     /**     /**
-     Sets the file extension of class files in the namespace of this class loader. +     {@inheritdoc}
-     * +
-     @param string $fileExtension+
      */      */
-    public function setFileExtension($fileExtension)+    public function register($prepend = false)
     {     {
-        $this->_fileExtension = $fileExtension;+        spl_autoload_register(array($this, 'load'), true, $prepend);
     }     }
 + 
     /**     /**
-     Gets the file extension of class files in the namespace of this class loader. +     {@inheritdoc}
-     * +
-     @return string $fileExtension+
      */      */
-    public function getFileExtension()+    public function unregister()
     {     {
-        return $this->_fileExtension;+        spl_autoload_unregister(array($this, 'load'));
     }     }
 +    
     /**     /**
-     Installs this class loader on the SPL autoload stack.+     {@inheritdoc}
      */      */
-    public function register()+    public function add($resource, $resourcePath = null)
     {     {
-        spl_autoload_register(array($this, 'loadClass'));+        $this->resources[$resource] = (array$resourcePath;
     }     }
 +    
     /**     /**
-     Uninstalls this class loader from the SPL autoloader stack.+     {@inheritdoc}
      */      */
-    public function unregister()+    public function load($resourceName)
     {     {
-        spl_autoload_unregister(array($this'loadClass'));+        $resourceAbsolutePath = $this->getResourceAbsolutePath($resourceName); 
 +         
 +        switch (true) { 
 +            case ($this->mode & self::MODE_SILENT): 
 +                if ($resourceAbsolutePath !== false) { 
 +                    require $resourceAbsolutePath; 
 +                } 
 +                break; 
 +         
 +            case ($this->mode & self::MODE_NORMAL): 
 +            default: 
 +                require $resourceAbsolutePath; 
 +                break; 
 +        } 
 +         
 +        if ($this->mode & self::MODE_DEBUG && ! $this->isResourceDeclared($resourceName)) { 
 +            throw new \RuntimeException( 
 +                sprintf('Autoloader expected resource "%s" to be declared in file "%s".', $resourceName, $resourceAbsolutePath) 
 +            ); 
 +        }
     }     }
 +    
     /**     /**
-     Loads the given class or interface.+     Transform resource name into its absolute resource path representation. 
 +     * 
 +     * @params string $resourceName
      *      *
-     * @param string $className The name of the class to load. +     * @return string Resource absolute path.
-     * @return void+
      */      */
-    public function loadClass($className)+    private function getResourceAbsolutePath($resourceName)
     {     {
-        if (null === $this->_namespace || $this->_namespace . $this->_namespaceSeparator === substr($className, 0, strlen($this->_namespace . $this->_namespaceSeparator))) { +        $resourceRelativePath = $this->getResourceRelativePath($resourceName); 
-            $fileName = ''; +         
-            $namespace = ''; +        foreach ($this->resources as $resource =$resourcesPath) { 
-            if (false !== ($lastNsPos = strripos($className, $this->_namespaceSeparator))) { +            if (strpos($resourceName, $resource!== 0) { 
-                $namespace = substr($className, 0, $lastNsPos); +                continue; 
-                $className substr($className, $lastNsPos + 1)+            } 
-                $fileName = str_replace($this->_namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;+             
 +            foreach ($resourcesPath as $resourcePath{ 
 +                $resourceAbsolutePath = $resourcePath . DIRECTORY_SEPARATOR . $resourceRelativePath
 +                 
 +                if (is_file($resourceAbsolutePath)) { 
 +                    return $resourceAbsolutePath; 
 +                }
             }             }
-            $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension; 
- 
-            require ($this->_includePath !== null ? $this->_includePath . DIRECTORY_SEPARATOR : '') . $fileName; 
         }         }
 +        
 +        if ($this->includePathLookup && ($resourceAbsolutePath = stream_resolve_include_path($resourceRelativePath)) !== false) {
 +            return $resourceAbsolutePath;
 +        }
 +        
 +        return false;
 +    }
 +    
 +    /**
 +     * Transform resource name into its relative resource path representation.
 +     *
 +     * @params string $resourceName
 +     *
 +     * @return string Resource relative path.
 +     */
 +    private function getResourceRelativePath($resourceName)
 +    {
 +        // We always work with FQCN in this context
 +        $resourceName = ltrim($resourceName, '\\');
 +        $resourcePath = '';
 +        
 +        if (($lastNamespacePosition = strrpos($resourceName, '\\')) !== false) {
 +            // Namespaced resource name
 +            $resourceNamespace = substr($resourceName, 0, $lastNamespacePosition);
 +            $resourceName      = substr($resourceName, $lastNamespacePosition + 1);
 +            $resourcePath      =  str_replace('\\', DIRECTORY_SEPARATOR, $resourceNamespace) . DIRECTORY_SEPARATOR;
 +        }
 +        
 +        return $resourcePath . str_replace('_', DIRECTORY_SEPARATOR . $resourceName) . $this->fileExtension;
 +    }
 +    
 +    /**
 +     * Check if resource is declared in user space.
 +     *
 +     * @params string $resourceName
 +     *
 +     * @return boolean
 +     */
 +    private function isResourceDeclared($resourceName)
 +    {
 +    return class_exists($resourceName, false) 
 +        || interface_exists($resourceName, false) 
 +        || (function_exists('trait_exists') && trait_exists($resourceName, false));
     }     }
 } }
 </code> </code>
  
-===== Example usage ===== +If any interested wants to customize the public methodslike caching through APC to reduce I/O, it should be possible to extend SplClassLoader and overwrite the public methods.
- +
-<code php> +
-$classLoader = new SplClassLoader('Doctrine\Common''/path/to/doctrine'); +
-$classLoader->register(); +
-</code>+
  
 ===== Proposal and Patch ===== ===== Proposal and Patch =====
Line 221: Line 434:
 The final release version of PSR-0 is available at: [[https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md]] The final release version of PSR-0 is available at: [[https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md]]
  
-A C extension is already available for usage, and can be grabbed at [[http://github.com/metagoto/splclassloader]]+<del>A C extension is already available for usage, and can be grabbed at [[http://github.com/metagoto/splclassloader]]</del> 
 + 
 +An extension to SPL has been created from the original C extension and a [[https://bugs.php.net/bug.php?id=60128|feature request]] has been filled for documentation purposes. The new SPL extension preliminary patch can be found at: [[https://gist.github.com/1310352]]. The provided patch is a minimum working version of SplClassLoader, so it may still require some updates to address minimum issues highlighted after a deep code review.
  
-Main purpose of this proposal is to suport both PEAR style directory organization and also Namespace directory organization.+Main purpose of this proposal is to support both PEAR style directory organization and also Namespace directory organization.
  
 ===== Changelog ===== ===== Changelog =====
  
-2011-10-24 Guilherme Blanco: Expanded RFC documentation. +  * 2011-11-09 Christian Kaps: Update examples to use the new interface(setMode) 
-2010-03-22 Guilherme Blanco: Initial RFC creation.+  * 2011-11-08 Guilherme Blanco: Removed constructor prototype and created setMode. 
 +  * 2011-11-08 Guilherme Blanco: Updated SplClassLoader implementation. 
 +  * 2011-11-07 Guilherme Blanco: Expanded extended rules. 
 +  * 2011-10-25 David Coallier: Added the new SPL patch information and feature request link. 
 +  * 2011-10-24 Guilherme Blanco: Expanded RFC documentation. Put it as ready for review
 +  2010-03-22 Guilherme Blanco: Initial RFC creation.
  
 +===== Comments =====
 +  * laruence : I have already got a similar loader implemented in Yaf, called Yaf_Loader, you can found the source here: [[http://svn.php.net/viewvc/pecl/yaf/trunk/yaf_loader.c?view=markup]]
rfc/splclassloader.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1