PageRenderTime 57ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Zend/Loader/PrefixPathLoader.php

https://github.com/mfairchild365/zf2
PHP | 501 lines | 261 code | 52 blank | 188 comment | 43 complexity | b389740d87938bc791b2b40127e03008 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Loader
  17. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. */
  20. /**
  21. * @namespace
  22. */
  23. namespace Zend\Loader;
  24. use Zend\Stdlib\ArrayStack,
  25. Zend\Stdlib\SplStack,
  26. SplDoublyLinkedList,
  27. SplFileInfo;
  28. /**
  29. * Prefix/Path plugin loader
  30. *
  31. * @category Zend
  32. * @package Zend_Loader
  33. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  34. * @license http://framework.zend.com/license/new-bsd New BSD License
  35. */
  36. class PrefixPathLoader implements ShortNameLocator, PrefixPathMapper
  37. {
  38. /**
  39. * Map of class names to files
  40. * @var array
  41. */
  42. protected $classMap = array();
  43. /**
  44. * Map of loaded plugins to class names
  45. *
  46. * @var array
  47. */
  48. protected $pluginMap = array();
  49. /**
  50. * Instance registry property
  51. *
  52. * @var ArrayStack
  53. */
  54. protected $prefixPaths;
  55. /**
  56. * Global static overrides to merge at instantiation
  57. *
  58. * @var array
  59. */
  60. protected static $staticPaths = array();
  61. /**
  62. * Constructor
  63. *
  64. * Options are passed to {@link setOptions()}
  65. *
  66. * @param array $options
  67. * @return void
  68. */
  69. public function __construct($options = null)
  70. {
  71. // Allow extending classes to pre-set the prefix paths
  72. if (is_array($this->prefixPaths)) {
  73. // If prefixPaths is an array, pass the array to addPrefixPaths()
  74. // after first setting the property to an ArrayStack
  75. $prefixPaths = $this->prefixPaths;
  76. $this->prefixPaths = new ArrayStack();
  77. $this->addPrefixPaths($prefixPaths);
  78. } elseif (!$this->prefixPaths instanceof ArrayStack) {
  79. // If we don't have an array stack, fix that!
  80. $this->prefixPaths = new ArrayStack();
  81. }
  82. // Merge in static paths
  83. if (!empty(static::$staticPaths)) {
  84. $this->addPrefixPaths(static::$staticPaths);
  85. }
  86. if (null !== $options) {
  87. $this->setOptions($options);
  88. }
  89. }
  90. /**
  91. * Add global static paths to merge at instantiation
  92. *
  93. * @param null|array|Traversable $paths
  94. * @return void
  95. */
  96. public static function addStaticPaths($paths)
  97. {
  98. if (null === $paths) {
  99. static::$staticPaths = array();
  100. return;
  101. }
  102. if (!is_array($paths) && !$paths instanceof \Traversable) {
  103. throw new Exception\InvalidArgumentException(sprintf(
  104. 'Expected a null value, array, or Traversable object, received %s',
  105. (is_object($paths) ? get_class($paths) : gettype($paths))
  106. ));
  107. }
  108. foreach ($paths as $spec) {
  109. if (!is_array($spec) && !is_object($spec)) {
  110. throw new Exception\InvalidArgumentException(sprintf(
  111. 'At least one item in the paths is not an array or object (received %s); aborting population of static prefix path map',
  112. (is_object($spec) ? get_class($spec) : gettype($spec))
  113. ));
  114. }
  115. static::$staticPaths[] = $spec;
  116. }
  117. }
  118. /**
  119. * Configure the prefix path plugin loader
  120. *
  121. * Proxies to {@link addPrefixPaths()}.
  122. *
  123. * @param array|Traversable $options
  124. * @return PrefixPathLoader
  125. */
  126. public function setOptions($options)
  127. {
  128. $this->addPrefixPaths($options);
  129. return $this;
  130. }
  131. /**
  132. * Add prefixed paths to the registry of paths
  133. *
  134. * @param string $prefix
  135. * @param string $path
  136. * @param bool $namespaced Whether the paths are namespaced or prefixed; namespaced by default
  137. * @return \Zend\Loader\PrefixPathLoader
  138. */
  139. public function addPrefixPath($prefix, $path, $namespaced = true)
  140. {
  141. if (!is_string($prefix) || !is_string($path)) {
  142. throw new Exception\InvalidArgumentException(sprintf(
  143. 'Expected strings for prefix and path; received %s and %s, respectively',
  144. (is_object($prefix) ? get_class($prefix) : gettype($prefix)),
  145. (is_object($path) ? get_class($path) : gettype($path))
  146. ));
  147. }
  148. $prefix = $this->formatPrefix($prefix, $namespaced);
  149. $path = $this->formatPath($path);
  150. if (!isset($this->prefixPaths[$prefix])) {
  151. $this->prefixPaths[$prefix] = new SplStack;
  152. }
  153. if (!in_array($path, $this->prefixPaths[$prefix]->toArray())) {
  154. $this->prefixPaths[$prefix][] = $path;
  155. }
  156. return $this;
  157. }
  158. /**
  159. * Add many prefix paths at once
  160. *
  161. * Accepts an array or Traversable object of prefix (or namspace) / path
  162. * pairs. The path may either be a string path, or an array or Traversable
  163. * object with many paths to associate with this prefix. If adding many
  164. * paths at once, please remember that the prefix/path pairs act as a LIFO
  165. * stack, as does the stack of paths associated with any given prefix.
  166. *
  167. * @param array|Traversable $prefixPaths
  168. * @return PrefixPathLoader
  169. */
  170. public function addPrefixPaths($prefixPaths)
  171. {
  172. if (!is_array($prefixPaths) && !$prefixPaths instanceof \Traversable) {
  173. throw new Exception\InvalidArgumentException(sprintf(
  174. 'Expected an array or Traversable object; received %s',
  175. (is_object($prefixPaths) ? get_class($prefixPaths) : gettype($prefixPaths))
  176. ));
  177. }
  178. foreach ($prefixPaths as $prefix => $spec) {
  179. if (is_object($spec)) {
  180. $prefix = $spec->prefix ?: $prefix;
  181. $path = $spec->path ?: false;
  182. $namespaced = isset($spec->namespaced) ? (bool) $spec->namespaced : true;
  183. } elseif (is_array($spec)) {
  184. $prefix = $spec['prefix'] ?: $prefix;
  185. $path = $spec['path'] ?: false;
  186. $namespaced = isset($spec['namespaced']) ? (bool) $spec['namespaced'] : true;
  187. } elseif (is_string($spec)) {
  188. $path = $spec;
  189. $namespaced = strstr($prefix, '_') ? false : true;
  190. } else {
  191. throw new Exception\InvalidArgumentException(
  192. 'Invalid prefix path array specification; must be an array or object'
  193. );
  194. }
  195. if (!$prefix || !$path) {
  196. throw new Exception\InvalidArgumentException(
  197. 'Invalid prefix path object specification; missing either prefix or path'
  198. );
  199. }
  200. $this->addPrefixPath($prefix, $path, $namespaced);
  201. }
  202. return $this;
  203. }
  204. /**
  205. * Get path stack
  206. *
  207. * @param string $prefix
  208. * @return false|ArrayStack|SplStack False if prefix does not exist,
  209. * SplStack otherwise; if no prefix provide, ArrayStack of prefix/SplStack
  210. * pairs
  211. */
  212. public function getPaths($prefix = null)
  213. {
  214. if ((null !== $prefix) && is_string($prefix)) {
  215. $prefix = $this->formatPrefix($prefix);
  216. if (isset($this->prefixPaths[$prefix])) {
  217. return $this->prefixPaths[$prefix];
  218. }
  219. return false;
  220. }
  221. return $this->prefixPaths;
  222. }
  223. /**
  224. * Clear path stack
  225. *
  226. * Clears path stack for a single prefix, or all prefixes.
  227. *
  228. * @param string $prefix
  229. * @return bool False only if $prefix does not exist
  230. */
  231. public function clearPaths($prefix = null)
  232. {
  233. if ((null !== $prefix) && is_string($prefix)) {
  234. $prefix = $this->formatPrefix($prefix);
  235. if (isset($this->prefixPaths[$prefix])) {
  236. unset($this->prefixPaths[$prefix]);
  237. return true;
  238. }
  239. return false;
  240. }
  241. $this->prefixPaths = new ArrayStack();
  242. return true;
  243. }
  244. /**
  245. * Remove a prefix (or prefixed-path) from the registry
  246. *
  247. * @param string $prefix
  248. * @param string $path
  249. * @return Zend\Loader\PrefixPathLoader
  250. */
  251. public function removePrefixPath($prefix, $path)
  252. {
  253. $prefix = $this->formatPrefix($prefix);
  254. $path = $this->formatPath($path);
  255. $registry = $this->prefixPaths;
  256. if (!isset($registry[$prefix])) {
  257. return false;
  258. }
  259. // Find prefix path in stack
  260. $index = false;
  261. $stack = $registry[$prefix];
  262. foreach ($stack as $idx => $test) {
  263. if ($test == $path) {
  264. $index = $idx;
  265. break;
  266. }
  267. }
  268. if (false === $index) {
  269. return false;
  270. }
  271. // Re-calculate index, since this is a stack
  272. $index = count($stack) - $index - 1;
  273. unset($stack[$index]);
  274. // If stack is now empty, remove prefix from ArrayStack
  275. if (0 === count($stack)) {
  276. unset($registry[$prefix]);
  277. }
  278. return true;
  279. }
  280. /**
  281. * Whether or not a Plugin by a specific name is loaded
  282. *
  283. * @param string $name
  284. * @return \Zend\Loader\PrefixPathLoader
  285. */
  286. public function isLoaded($name)
  287. {
  288. $name = $this->formatName($name);
  289. return isset($this->pluginMap[$name]);
  290. }
  291. /**
  292. * Return full class name for a named plugin
  293. *
  294. * @param string $name
  295. * @return string|false False if class not found, class name otherwise
  296. */
  297. public function getClassName($name)
  298. {
  299. $name = $this->formatName($name);
  300. if (isset($this->pluginMap[$name])) {
  301. return $this->pluginMap[$name];
  302. }
  303. return false;
  304. }
  305. /**
  306. * Load a plugin via the name provided
  307. *
  308. * @param string $name
  309. * @return string|false Class name of loaded class; false if no class found
  310. */
  311. public function load($name)
  312. {
  313. $name = $this->formatName($name);
  314. if ($this->isLoaded($name)) {
  315. return $this->getClassName($name);
  316. }
  317. $found = false;
  318. $classFile = str_replace(array('\\', '_'), DIRECTORY_SEPARATOR, $name) . '.php';
  319. foreach ($this->prefixPaths as $prefix => $paths) {
  320. // Initialize file and class variables
  321. $loadFile = false;
  322. $className = $prefix . $name;
  323. if (class_exists($className)) {
  324. // Class already loaded or autoloaded; done
  325. $found = true;
  326. break;
  327. }
  328. // Search path stack
  329. foreach ($paths as $path) {
  330. // Is the class file readable?
  331. $loadFile = new SplFileInfo($path . $classFile);
  332. if ($loadFile->isFile() && $loadFile->isReadable()) {
  333. // File is readable, let's load and check for the class
  334. include_once $loadFile->getPathName();
  335. if (class_exists($className, false)) {
  336. // Found!
  337. $found = true;
  338. break 2;
  339. }
  340. }
  341. // Not found, so reset path holder
  342. $loadFile = false;
  343. }
  344. }
  345. // Plugin class not found -- return early
  346. if (!$found) {
  347. return false;
  348. }
  349. // Get class file for class map
  350. $fileName = null;
  351. if ($loadFile) {
  352. // We have a populated file object from searching
  353. $fileName = $loadFile->getPathName();
  354. } else {
  355. // Class was already loaded or autoloaded
  356. $r = new \ReflectionClass($className);
  357. $fileName = $r->getFileName();
  358. }
  359. // Seed plugin map and class map
  360. $this->pluginMap[$name] = $className;
  361. $this->classMap[$className] = $fileName;
  362. return $className;
  363. }
  364. /**
  365. * Get plugin map
  366. *
  367. * Returns an array of plugin name/class name pairs, suitable for seeding
  368. * a PluginClassLoader instance.
  369. *
  370. * @return array
  371. */
  372. public function getPluginMap()
  373. {
  374. return $this->pluginMap;
  375. }
  376. /**
  377. * Get class map
  378. *
  379. * Returns an array of class name/file name pairs, suitable for seeding
  380. * a ClassMapAutoloader instance. Note: filenames will be absolute paths
  381. * based on the operating system on which the class map is retrieved. You
  382. * may need to alter the paths to be relative to any filesystem.
  383. *
  384. * @return array
  385. */
  386. public function getClassMap()
  387. {
  388. return $this->classMap;
  389. }
  390. /**
  391. * Normalize plugin name
  392. *
  393. * @param string $name
  394. * @return string
  395. */
  396. protected function formatName($name)
  397. {
  398. return ucfirst((string) $name);
  399. }
  400. /**
  401. * Format prefix for internal use
  402. *
  403. * @param string $prefix
  404. * @param bool $namespaced Whether the paths are namespaced or prefixed;
  405. * namespaced by default
  406. * @return string
  407. */
  408. protected function formatPrefix($prefix, $namespaced = true)
  409. {
  410. if($prefix == "") {
  411. return $prefix;
  412. }
  413. switch ((bool) $namespaced) {
  414. case true:
  415. $last = strlen($prefix) - 1;
  416. if ($prefix{$last} == '\\') {
  417. return $prefix;
  418. }
  419. return $prefix . '\\';
  420. case false:
  421. $last = strlen($prefix) - 1;
  422. if ($prefix{$last} == '_') {
  423. return $prefix;
  424. }
  425. return $prefix . '_';
  426. default:
  427. // do nothing; unknown value
  428. }
  429. }
  430. /**
  431. * Format a path for comparisons
  432. *
  433. * Strips trailing directory separator(s), if any, and then appends
  434. * system directory separator.
  435. *
  436. * @param string $path
  437. * @return string
  438. */
  439. protected function formatPath($path)
  440. {
  441. $path = rtrim($path, '/');
  442. $path = rtrim($path, '\\');
  443. $path .= DIRECTORY_SEPARATOR;
  444. return $path;
  445. }
  446. }