File indexing completed on 2024-12-22 05:36:49

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Loader
0017  * @subpackage PluginLoader
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /** Zend_Loader_PluginLoader_Interface */
0024 // require_once 'Zend/Loader/PluginLoader/Interface.php';
0025 
0026 /** Zend_Loader */
0027 // require_once 'Zend/Loader.php';
0028 
0029 /**
0030  * Generic plugin class loader
0031  *
0032  * @category   Zend
0033  * @package    Zend_Loader
0034  * @subpackage PluginLoader
0035  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0036  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0037  */
0038 class Zend_Loader_PluginLoader implements Zend_Loader_PluginLoader_Interface
0039 {
0040     /**
0041      * Class map cache file
0042      * @var string
0043      */
0044     protected static $_includeFileCache;
0045 
0046     /**
0047      * Class map cache file handler
0048      * @var resource
0049      */
0050     protected static $_includeFileCacheHandler;
0051 
0052     /**
0053      * Instance loaded plugin paths
0054      *
0055      * @var array
0056      */
0057     protected $_loadedPluginPaths = array();
0058 
0059     /**
0060      * Instance loaded plugins
0061      *
0062      * @var array
0063      */
0064     protected $_loadedPlugins = array();
0065 
0066     /**
0067      * Instance registry property
0068      *
0069      * @var array
0070      */
0071     protected $_prefixToPaths = array();
0072 
0073     /**
0074      * Statically loaded plugin path mappings
0075      *
0076      * @var array
0077      */
0078     protected static $_staticLoadedPluginPaths = array();
0079 
0080     /**
0081      * Statically loaded plugins
0082      *
0083      * @var array
0084      */
0085     protected static $_staticLoadedPlugins = array();
0086 
0087     /**
0088      * Static registry property
0089      *
0090      * @var array
0091      */
0092     protected static $_staticPrefixToPaths = array();
0093 
0094     /**
0095      * Whether to use a statically named registry for loading plugins
0096      *
0097      * @var string|null
0098      */
0099     protected $_useStaticRegistry = null;
0100 
0101     /**
0102      * Constructor
0103      *
0104      * @param array $prefixToPaths
0105      * @param string $staticRegistryName OPTIONAL
0106      */
0107     public function __construct(Array $prefixToPaths = array(), $staticRegistryName = null)
0108     {
0109         if (is_string($staticRegistryName) && !empty($staticRegistryName)) {
0110             $this->_useStaticRegistry = $staticRegistryName;
0111             if(!isset(self::$_staticPrefixToPaths[$staticRegistryName])) {
0112                 self::$_staticPrefixToPaths[$staticRegistryName] = array();
0113             }
0114             if(!isset(self::$_staticLoadedPlugins[$staticRegistryName])) {
0115                 self::$_staticLoadedPlugins[$staticRegistryName] = array();
0116             }
0117         }
0118 
0119         foreach ($prefixToPaths as $prefix => $path) {
0120             $this->addPrefixPath($prefix, $path);
0121         }
0122     }
0123 
0124     /**
0125      * Format prefix for internal use
0126      *
0127      * @param  string $prefix
0128      * @return string
0129      */
0130     protected function _formatPrefix($prefix)
0131     {
0132         if($prefix == "") {
0133             return $prefix;
0134         }
0135 
0136         $nsSeparator = (false !== strpos($prefix, '\\'))?'\\':'_';
0137         $prefix = rtrim($prefix, $nsSeparator) . $nsSeparator;
0138         //if $nsSeprator == "\" and the prefix ends in "_\" remove trailing \
0139         //https://github.com/zendframework/zf1/issues/152
0140         if(($nsSeparator == "\\") && (substr($prefix,-2) == "_\\")) {
0141             $prefix = substr($prefix, 0, -1);
0142         }
0143         return $prefix;
0144     }
0145 
0146     /**
0147      * Add prefixed paths to the registry of paths
0148      *
0149      * @param string $prefix
0150      * @param string $path
0151      * @return Zend_Loader_PluginLoader
0152      */
0153     public function addPrefixPath($prefix, $path)
0154     {
0155         if (!is_string($prefix) || !is_string($path)) {
0156             // require_once 'Zend/Loader/PluginLoader/Exception.php';
0157             throw new Zend_Loader_PluginLoader_Exception('Zend_Loader_PluginLoader::addPrefixPath() method only takes strings for prefix and path.');
0158         }
0159 
0160         $prefix = $this->_formatPrefix($prefix);
0161         $path   = rtrim($path, '/\\') . '/';
0162 
0163         if ($this->_useStaticRegistry) {
0164             self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix][] = $path;
0165         } else {
0166             if (!isset($this->_prefixToPaths[$prefix])) {
0167                 $this->_prefixToPaths[$prefix] = array();
0168             }
0169             if (!in_array($path, $this->_prefixToPaths[$prefix])) {
0170                 $this->_prefixToPaths[$prefix][] = $path;
0171             }
0172         }
0173         return $this;
0174     }
0175 
0176     /**
0177      * Get path stack
0178      *
0179      * @param  string $prefix
0180      * @return false|array False if prefix does not exist, array otherwise
0181      */
0182     public function getPaths($prefix = null)
0183     {
0184         if ((null !== $prefix) && is_string($prefix)) {
0185             $prefix = $this->_formatPrefix($prefix);
0186             if ($this->_useStaticRegistry) {
0187                 if (isset(self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix])) {
0188                     return self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix];
0189                 }
0190 
0191                 return false;
0192             }
0193 
0194             if (isset($this->_prefixToPaths[$prefix])) {
0195                 return $this->_prefixToPaths[$prefix];
0196             }
0197 
0198             return false;
0199         }
0200 
0201         if ($this->_useStaticRegistry) {
0202             return self::$_staticPrefixToPaths[$this->_useStaticRegistry];
0203         }
0204 
0205         return $this->_prefixToPaths;
0206     }
0207 
0208     /**
0209      * Clear path stack
0210      *
0211      * @param  string $prefix
0212      * @return bool False only if $prefix does not exist
0213      */
0214     public function clearPaths($prefix = null)
0215     {
0216         if ((null !== $prefix) && is_string($prefix)) {
0217             $prefix = $this->_formatPrefix($prefix);
0218             if ($this->_useStaticRegistry) {
0219                 if (isset(self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix])) {
0220                     unset(self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix]);
0221                     return true;
0222                 }
0223 
0224                 return false;
0225             }
0226 
0227             if (isset($this->_prefixToPaths[$prefix])) {
0228                 unset($this->_prefixToPaths[$prefix]);
0229                 return true;
0230             }
0231 
0232             return false;
0233         }
0234 
0235         if ($this->_useStaticRegistry) {
0236             self::$_staticPrefixToPaths[$this->_useStaticRegistry] = array();
0237         } else {
0238             $this->_prefixToPaths = array();
0239         }
0240 
0241         return true;
0242     }
0243 
0244     /**
0245      * Remove a prefix (or prefixed-path) from the registry
0246      *
0247      * @param string $prefix
0248      * @param string $path OPTIONAL
0249      * @return Zend_Loader_PluginLoader
0250      */
0251     public function removePrefixPath($prefix, $path = null)
0252     {
0253         $prefix = $this->_formatPrefix($prefix);
0254         if ($this->_useStaticRegistry) {
0255             $registry =& self::$_staticPrefixToPaths[$this->_useStaticRegistry];
0256         } else {
0257             $registry =& $this->_prefixToPaths;
0258         }
0259 
0260         if (!isset($registry[$prefix])) {
0261             // require_once 'Zend/Loader/PluginLoader/Exception.php';
0262             throw new Zend_Loader_PluginLoader_Exception('Prefix ' . $prefix . ' was not found in the PluginLoader.');
0263         }
0264 
0265         if ($path != null) {
0266             $pos = array_search($path, $registry[$prefix]);
0267             if (false === $pos) {
0268                 // require_once 'Zend/Loader/PluginLoader/Exception.php';
0269                 throw new Zend_Loader_PluginLoader_Exception('Prefix ' . $prefix . ' / Path ' . $path . ' was not found in the PluginLoader.');
0270             }
0271             unset($registry[$prefix][$pos]);
0272         } else {
0273             unset($registry[$prefix]);
0274         }
0275 
0276         return $this;
0277     }
0278 
0279     /**
0280      * Normalize plugin name
0281      *
0282      * @param  string $name
0283      * @return string
0284      */
0285     protected function _formatName($name)
0286     {
0287         return ucfirst((string) $name);
0288     }
0289 
0290     /**
0291      * Whether or not a Plugin by a specific name is loaded
0292      *
0293      * @param string $name
0294      * @return Zend_Loader_PluginLoader
0295      */
0296     public function isLoaded($name)
0297     {
0298         $name = $this->_formatName($name);
0299         if ($this->_useStaticRegistry) {
0300             return isset(self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name]);
0301         }
0302 
0303         return isset($this->_loadedPlugins[$name]);
0304     }
0305 
0306     /**
0307      * Return full class name for a named plugin
0308      *
0309      * @param string $name
0310      * @return string|false False if class not found, class name otherwise
0311      */
0312     public function getClassName($name)
0313     {
0314         $name = $this->_formatName($name);
0315         if ($this->_useStaticRegistry
0316             && isset(self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name])
0317         ) {
0318             return self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name];
0319         } elseif (isset($this->_loadedPlugins[$name])) {
0320             return $this->_loadedPlugins[$name];
0321         }
0322 
0323         return false;
0324     }
0325 
0326     /**
0327      * Get path to plugin class
0328      *
0329      * @param  mixed $name
0330      * @return string|false False if not found
0331      */
0332     public function getClassPath($name)
0333     {
0334         $name = $this->_formatName($name);
0335         if ($this->_useStaticRegistry
0336             && !empty(self::$_staticLoadedPluginPaths[$this->_useStaticRegistry][$name])
0337         ) {
0338             return self::$_staticLoadedPluginPaths[$this->_useStaticRegistry][$name];
0339         } elseif (!empty($this->_loadedPluginPaths[$name])) {
0340             return $this->_loadedPluginPaths[$name];
0341         }
0342 
0343         if ($this->isLoaded($name)) {
0344             $class = $this->getClassName($name);
0345             $r     = new ReflectionClass($class);
0346             $path  = $r->getFileName();
0347             if ($this->_useStaticRegistry) {
0348                 self::$_staticLoadedPluginPaths[$this->_useStaticRegistry][$name] = $path;
0349             } else {
0350                 $this->_loadedPluginPaths[$name] = $path;
0351             }
0352             return $path;
0353         }
0354 
0355         return false;
0356     }
0357 
0358     /**
0359      * Load a plugin via the name provided
0360      *
0361      * @param  string $name
0362      * @param  bool $throwExceptions Whether or not to throw exceptions if the
0363      * class is not resolved
0364      * @return string|false Class name of loaded class; false if $throwExceptions
0365      * if false and no class found
0366      * @throws Zend_Loader_Exception if class not found
0367      */
0368     public function load($name, $throwExceptions = true)
0369     {
0370         $name = $this->_formatName($name);
0371         if ($this->isLoaded($name)) {
0372             return $this->getClassName($name);
0373         }
0374 
0375         if ($this->_useStaticRegistry) {
0376             $registry = self::$_staticPrefixToPaths[$this->_useStaticRegistry];
0377         } else {
0378             $registry = $this->_prefixToPaths;
0379         }
0380 
0381         $registry  = array_reverse($registry, true);
0382         $found     = false;
0383         if (false !== strpos($name, '\\')) {
0384             $classFile = str_replace('\\', DIRECTORY_SEPARATOR, $name) . '.php';
0385         } else {
0386             $classFile = str_replace('_', DIRECTORY_SEPARATOR, $name) . '.php';
0387         }
0388         $incFile   = self::getIncludeFileCache();
0389         foreach ($registry as $prefix => $paths) {
0390             $className = $prefix . $name;
0391 
0392             if (class_exists($className, false)) {
0393                 $found = true;
0394                 break;
0395             }
0396 
0397             $paths     = array_reverse($paths, true);
0398 
0399             foreach ($paths as $path) {
0400                 $loadFile = $path . $classFile;
0401                 if (Zend_Loader::isReadable($loadFile)) {
0402                     include_once $loadFile;
0403                     if (class_exists($className, false)) {
0404                         if (null !== $incFile) {
0405                             self::_appendIncFile($loadFile);
0406                         }
0407                         $found = true;
0408                         break 2;
0409                     }
0410                 }
0411             }
0412         }
0413 
0414         if (!$found) {
0415             if (!$throwExceptions) {
0416                 return false;
0417             }
0418 
0419             $message = "Plugin by name '$name' was not found in the registry; used paths:";
0420             foreach ($registry as $prefix => $paths) {
0421                 $message .= "\n$prefix: " . implode(PATH_SEPARATOR, $paths);
0422             }
0423             // require_once 'Zend/Loader/PluginLoader/Exception.php';
0424             throw new Zend_Loader_PluginLoader_Exception($message);
0425        }
0426 
0427         if ($this->_useStaticRegistry) {
0428             self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name]     = $className;
0429         } else {
0430             $this->_loadedPlugins[$name]     = $className;
0431         }
0432         return $className;
0433     }
0434 
0435     /**
0436      * Set path to class file cache
0437      *
0438      * Specify a path to a file that will add include_once statements for each
0439      * plugin class loaded. This is an opt-in feature for performance purposes.
0440      *
0441      * @param  string $file
0442      * @return void
0443      * @throws Zend_Loader_PluginLoader_Exception if file is not writeable or path does not exist
0444      */
0445     public static function setIncludeFileCache($file)
0446     {
0447         if (!empty(self::$_includeFileCacheHandler)) {
0448             flock(self::$_includeFileCacheHandler, LOCK_UN);
0449             fclose(self::$_includeFileCacheHandler);
0450         }
0451 
0452         self::$_includeFileCacheHandler = null;
0453 
0454         if (null === $file) {
0455             self::$_includeFileCache = null;
0456             return;
0457         }
0458 
0459         if (!file_exists($file) && !file_exists(dirname($file))) {
0460             // require_once 'Zend/Loader/PluginLoader/Exception.php';
0461             throw new Zend_Loader_PluginLoader_Exception('Specified file does not exist and/or directory does not exist (' . $file . ')');
0462         }
0463         if (file_exists($file) && !is_writable($file)) {
0464             // require_once 'Zend/Loader/PluginLoader/Exception.php';
0465             throw new Zend_Loader_PluginLoader_Exception('Specified file is not writeable (' . $file . ')');
0466         }
0467         if (!file_exists($file) && file_exists(dirname($file)) && !is_writable(dirname($file))) {
0468             // require_once 'Zend/Loader/PluginLoader/Exception.php';
0469             throw new Zend_Loader_PluginLoader_Exception('Specified file is not writeable (' . $file . ')');
0470         }
0471 
0472         self::$_includeFileCache = $file;
0473     }
0474 
0475     /**
0476      * Retrieve class file cache path
0477      *
0478      * @return string|null
0479      */
0480     public static function getIncludeFileCache()
0481     {
0482         return self::$_includeFileCache;
0483     }
0484 
0485     /**
0486      * Append an include_once statement to the class file cache
0487      *
0488      * @param  string $incFile
0489      * @return void
0490      */
0491     protected static function _appendIncFile($incFile)
0492     {
0493         if (!isset(self::$_includeFileCacheHandler)) {
0494             self::$_includeFileCacheHandler = fopen(self::$_includeFileCache, 'ab');
0495 
0496             if (!flock(self::$_includeFileCacheHandler, LOCK_EX | LOCK_NB, $wouldBlock) || $wouldBlock) {
0497                 self::$_includeFileCacheHandler = false;
0498             }
0499         }
0500 
0501         if (false !== self::$_includeFileCacheHandler) {
0502             $line = "<?php include_once '$incFile'?>\n";
0503             fwrite(self::$_includeFileCacheHandler, $line, strlen($line));
0504         }
0505     }
0506 }