PageRenderTime 46ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/classes/Fuel/Kernel/Loader/Package.php

http://github.com/fuelphp/kernel
PHP | 574 lines | 287 code | 45 blank | 242 comment | 16 complexity | 470eab52b517b3b489ab794e2c7030c4 MD5 | raw file
  1. <?php
  2. /**
  3. * Part of the FuelPHP framework.
  4. *
  5. * @package Fuel\Kernel
  6. * @version 2.0.0
  7. * @license MIT License
  8. * @copyright 2010 - 2012 Fuel Development Team
  9. */
  10. namespace Fuel\Kernel\Loader;
  11. use Fuel\Kernel\Environment;
  12. /**
  13. * Package Loader
  14. *
  15. * Default Fuel Package loader class that allows loading files & classes.
  16. *
  17. * @package Fuel\Kernel
  18. *
  19. * @since 2.0.0
  20. */
  21. class Package implements Loadable
  22. {
  23. /**
  24. * @var string name of this loader
  25. *
  26. * @since 2.0.0
  27. */
  28. public $name;
  29. /**
  30. * @var \Fuel\Kernel\Environment
  31. */
  32. protected $env;
  33. /**
  34. * @var string basepath for the package
  35. *
  36. * @since 2.0.0
  37. */
  38. protected $path = '';
  39. /**
  40. * @var string base namespace for the package (with trailing backslash when not empty)
  41. *
  42. * @since 2.0.0
  43. */
  44. protected $namespace = '';
  45. /**
  46. * @var string string to prefix the Controller classname with, will be relative to the base namespace
  47. *
  48. * @since 2.0.0
  49. */
  50. protected $classPrefixes = array(
  51. 'application' => 'Application\\',
  52. 'controller' => 'Controller\\',
  53. 'model' => 'Model\\',
  54. 'presenter' => 'Presenter\\',
  55. 'task' => 'Task\\',
  56. );
  57. /**
  58. * @var array package modules with array(relative path => relative subnamespace) (with trailing backslash)
  59. *
  60. * @since 2.0.0
  61. */
  62. protected $modules = array();
  63. /**
  64. * @var array registered classes, without the base namespace
  65. *
  66. * @since 2.0.0
  67. */
  68. protected $classes = array();
  69. /**
  70. * @var array classes that are aliased: classname => actual class
  71. *
  72. * @since 2.0.0
  73. */
  74. protected $classAliases = array();
  75. /**
  76. * @var bool whether the class's path is relative to the main namespace or fully PSR-0 compliant
  77. *
  78. * @since 2.0.0
  79. */
  80. protected $relativeClassLoad = false;
  81. /**
  82. * @var bool whether this package is routable
  83. *
  84. * @since 2.0.0
  85. */
  86. protected $routable = false;
  87. /**
  88. * @var string a first segment required for find_file() & find_class() that will be stripped
  89. *
  90. * @since 2.0.0
  91. */
  92. protected $findTrigger;
  93. /**
  94. * Magic Fuel method that is the setter for the current Environment
  95. *
  96. * @param \Fuel\Kernel\Environment $env
  97. * @return void
  98. *
  99. * @since 2.0.0
  100. */
  101. public function _setEnv(Environment $env)
  102. {
  103. $this->env = $env;
  104. // Show package loads inside application
  105. ($app = $env->activeApplication())
  106. and $app->notify('packageLoaderCreated', $this, __METHOD__);
  107. }
  108. /**
  109. * Assigns a name to this package
  110. *
  111. * @param string $name
  112. * @return Loadable
  113. *
  114. * @since 2.0.0
  115. */
  116. public function setName($name)
  117. {
  118. $this->name = $name;
  119. return $this;
  120. }
  121. /**
  122. * Returns the base namespace for this package
  123. *
  124. * @return string
  125. *
  126. * @since 2.0.0
  127. */
  128. public function getNamespace()
  129. {
  130. return $this->namespace;
  131. }
  132. /**
  133. * Returns the base path for this package
  134. *
  135. * @return string
  136. *
  137. * @since 2.0.0
  138. */
  139. public function getPath()
  140. {
  141. return $this->path;
  142. }
  143. /**
  144. * Attempt to load a class from the package
  145. *
  146. * @param string $class
  147. * @return bool
  148. *
  149. * @since 2.0.0
  150. */
  151. public function loadClass($class)
  152. {
  153. // Save the original classname
  154. $original = $class;
  155. // Check if the class path was registered with the Package
  156. if (isset($this->classes[$class]))
  157. {
  158. require $this->classes[$class];
  159. return true;
  160. }
  161. // Check if the request class is an alias registered with the Package
  162. elseif (isset($this->classAliases[$class]))
  163. {
  164. class_alias($this->classAliases[$class], $class);
  165. return true;
  166. }
  167. // If a base namespace was set and doesn't match the class: fail
  168. if ($this->namespace === false
  169. or ($this->namespace and strpos($class, $this->namespace) !== 0))
  170. {
  171. return false;
  172. }
  173. // Anything further will be relative to the base namespace
  174. $class = substr($class, strlen($this->namespace));
  175. // Check if any of the modules' namespaces matches the class and make it relative on such a match
  176. $path = $this->path;
  177. foreach ($this->modules as $mPath => $mNamespace)
  178. {
  179. if (strpos($class, $mNamespace) === 0)
  180. {
  181. $class = substr($class, strlen($mNamespace));
  182. $path .= 'modules/'.$mPath.'/';
  183. break;
  184. }
  185. }
  186. $path = $this->classToPath($original, $class, $path.'classes/');
  187. // When found include the file and return success
  188. if (is_file($path))
  189. {
  190. require $path;
  191. return true;
  192. }
  193. // ... still here? Failure.
  194. return false;
  195. }
  196. /**
  197. * Converts a classname to a path using PSR-0 conventions
  198. *
  199. * NOTE: using the base namespace setting and usage of modules break PSR-0 convention. The paths are expected
  200. * relative to the base namespace when used and optionally relative to the module's (sub)namespace.
  201. *
  202. * @param string $fullName full classname
  203. * @param string $class classname relative to base/module namespace
  204. * @param string $basePath
  205. * @return string
  206. *
  207. * @since 2.0.0
  208. */
  209. protected function classToPath($fullName, $class, $basePath)
  210. {
  211. return $basePath.$this->env->loader->psrClassToPath($this->relativeClassLoad ? $class : $fullName);
  212. }
  213. /**
  214. * Set a base path for the package
  215. *
  216. * @param string $path
  217. * @return Package
  218. *
  219. * @since 2.0.0
  220. */
  221. public function setPath($path)
  222. {
  223. $this->path = rtrim($path, '/\\').'/';
  224. return $this;
  225. }
  226. /**
  227. * Set a base namespace for the package, only classes from that namespace are loaded
  228. *
  229. * @param string $namespace
  230. * @return Package
  231. *
  232. * @since 2.0.0
  233. */
  234. public function setNamespace($namespace)
  235. {
  236. $this->namespace = $namespace ? trim($namespace, '\\').'\\' : $namespace;
  237. return $this;
  238. }
  239. /**
  240. * Add a module with path & namespace
  241. *
  242. * @param string $path
  243. * @param string $namespace
  244. * @return Package
  245. *
  246. * @since 2.0.0
  247. */
  248. public function addModule($path, $namespace)
  249. {
  250. $this->modules = array(trim($path, '/\\').'/' => trim($namespace, '\\').'\\') + $this->modules;
  251. return $this;
  252. }
  253. /**
  254. * Remove a module from the package
  255. *
  256. * @param string $path
  257. * @return Package
  258. *
  259. * @since 2.0.0
  260. */
  261. public function removeModule($path)
  262. {
  263. unset($this->modules[trim($path, '/\\').'/']);
  264. return $this;
  265. }
  266. /**
  267. * Adds a class to the Package that doesn't need to be found
  268. *
  269. * @param string $class
  270. * @param string $path
  271. * @return Package
  272. *
  273. * @since 2.0.0
  274. */
  275. public function addClass($class, $path)
  276. {
  277. return $this->addClasses(array($class => $path));
  278. }
  279. /**
  280. * Adds classes to the Package that don't need to be found
  281. *
  282. * @param array $classes
  283. * @return Package
  284. *
  285. * @since 2.0.0
  286. */
  287. public function addClasses(array $classes)
  288. {
  289. foreach ($classes as $class => $path)
  290. {
  291. $this->classes[$class] = $path;
  292. }
  293. return $this;
  294. }
  295. /**
  296. * Add an alias and the actual classname
  297. *
  298. * @param string $alias
  299. * @param string $actual
  300. * @return Package for method chaining
  301. *
  302. * @since 2.0.0
  303. */
  304. public function addClassAlias($alias, $actual)
  305. {
  306. return $this->addClassAliases(array($alias => $actual));
  307. }
  308. /**
  309. * Add multiple classes with their aliases
  310. *
  311. * @param array $classes
  312. * @return Package for method chaining
  313. *
  314. * @since 2.0.0
  315. */
  316. public function addClassAliases(array $classes = array())
  317. {
  318. foreach ($classes as $alias => $actual)
  319. {
  320. $this->classAliases[$alias] = $actual;
  321. }
  322. return $this;
  323. }
  324. /**
  325. * Removes a class from the package
  326. *
  327. * @param string $class
  328. * @return Package
  329. *
  330. * @since 2.0.0
  331. */
  332. public function removeClass($class)
  333. {
  334. unset($this->classes[$class]);
  335. return $this;
  336. }
  337. /**
  338. * Sets routability of this package
  339. *
  340. * @param bool $routable
  341. * @return Loadable
  342. *
  343. * @since 2.0.0
  344. */
  345. public function setRoutable($routable)
  346. {
  347. $this->routable = (bool) $routable;
  348. return $this;
  349. }
  350. /**
  351. * Sets whether a class's path is relative to the main namespace of this
  352. * package (true) or normal PSR-0 (false)
  353. *
  354. * @param bool $compliance
  355. * @return Loadable
  356. *
  357. * @since 2.0.0
  358. */
  359. public function setRelativeClassLoad($compliance)
  360. {
  361. $this->relativeClassLoad = (bool) $compliance;
  362. return $this;
  363. }
  364. /**
  365. * Sets the find trigger: a firgst segment required for find_file() and find_class()
  366. *
  367. * @param bool $trigger
  368. * @return Package
  369. *
  370. * @since 2.0.0
  371. */
  372. public function setFindTrigger($trigger)
  373. {
  374. $this->findTrigger = trim(strval($trigger), '\\/');
  375. return $this;
  376. }
  377. /**
  378. * Change a special class type prefix
  379. *
  380. * @param string $type
  381. * @param string $prefix
  382. * @return Package
  383. *
  384. * @since 2.0.0
  385. */
  386. public function setClassTypePrefix($type, $prefix)
  387. {
  388. $this->classPrefixes[strtolower($type)] = $prefix;
  389. return $this;
  390. }
  391. /**
  392. * Get the class prefix for a specific type
  393. *
  394. * @param string $type
  395. * @return string
  396. *
  397. * @since 2.0.0
  398. */
  399. public function classTypePrefix($type)
  400. {
  401. $type = strtolower($type);
  402. return isset($this->classPrefixes[$type]) ? $this->classPrefixes[$type] : '';
  403. }
  404. /**
  405. * Attempts to find a controller, loads the class and returns the classname if found
  406. *
  407. * @param string $type for example: controller or task
  408. * @param string $path
  409. * @return bool|string
  410. *
  411. * @since 2.0.0
  412. */
  413. public function findClass($type, $path)
  414. {
  415. // If the routable property is a string then this requires a trigger segment to be findable
  416. if (is_string($this->findTrigger))
  417. {
  418. // If string trigger isn't found at the beginning return false
  419. if (strpos(strtolower($path), strtolower($this->findTrigger).'/') !== 0)
  420. {
  421. return false;
  422. }
  423. // Strip trigger from classname
  424. $path = substr($path, strlen($this->findTrigger) + 1);
  425. }
  426. // Build the namespace for the controller
  427. $namespace = $this->namespace;
  428. if ($pos = strpos($path, '/'))
  429. {
  430. $module = substr($path, 0, $pos).'/';
  431. if (isset($this->modules[$module]))
  432. {
  433. $namespace .= $this->modules[$module];
  434. $path = substr($path, $pos + 1);
  435. }
  436. }
  437. $path = $namespace.$this->classTypePrefix($type).str_replace('/', '_', $path);
  438. if ($this->loadClass($path))
  439. {
  440. return $path;
  441. }
  442. return false;
  443. }
  444. /**
  445. * Attempts to find a specific file
  446. *
  447. * @param string $file
  448. * @return bool|string
  449. *
  450. * @since 2.0.0
  451. */
  452. public function findFile($file)
  453. {
  454. // if the routable property is a string then this requires a trigger segment to be findable
  455. if (is_string($this->findTrigger))
  456. {
  457. // if string trigger isn't found at the beginning return false
  458. $fileSegments = explode('/', $file);
  459. if (count($fileSegments) <= 2 or $fileSegments[1] !== $this->findTrigger)
  460. {
  461. return false;
  462. }
  463. // strip trigger from file path
  464. unset($fileSegments[1]);
  465. $file = implode('/', $fileSegments);
  466. }
  467. // if given attempt specific module load
  468. if (($pos = strpos($file, ':')) !== false)
  469. {
  470. $module = substr($file, 0, $pos).'/';
  471. if (isset($this->modules[$module]))
  472. {
  473. if (is_file($path = $this->path.$module.substr($file, $pos + 1)))
  474. {
  475. return $path;
  476. }
  477. }
  478. return false;
  479. }
  480. // attempt fetch from base
  481. if (file_exists($path = $this->path.$file))
  482. {
  483. return $path;
  484. }
  485. // attempt to find in modules
  486. foreach ($this->modules as $path => $ns)
  487. {
  488. if (file_exists($path = $this->path.'modules/'.$path.$file))
  489. {
  490. return $path;
  491. }
  492. }
  493. // all is lost
  494. return false;
  495. }
  496. /**
  497. * Glob package and module dirs
  498. * Returns result of each glob indexed by full path to the dir
  499. *
  500. * @param string $location
  501. * @param string $filter
  502. * @param int $flags
  503. * @return array combined glob result
  504. *
  505. * @since 2.0.0
  506. */
  507. public function glob($location, $filter, $flags = 0)
  508. {
  509. $location = trim($location, '\\/').'/';
  510. $glob = array();
  511. // First add the main dir
  512. $path = $this->path.$location;
  513. $glob[$path] = glob($path, $flags);
  514. // Add the modules
  515. foreach ($this->modules as $path => $ns)
  516. {
  517. $path = $this->path.'modules/'.$path.$location;
  518. $glob[$path] = glob($path.$filter, $flags);
  519. }
  520. return $glob;
  521. }
  522. }