Psr4.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. <?php
  2. namespace Qii\Autoloader;
  3. /**
  4. * Psr4 规范
  5. *
  6. */
  7. class Psr4
  8. {
  9. /**
  10. * 将查找过的文件放入缓存
  11. */
  12. protected static $cachedFiles = array();
  13. /**
  14. * 是否使用namespace
  15. */
  16. protected $useNamespace = array();
  17. /**
  18. * 添加namespace前缀对应的目录,只要是以这个前缀开头的文件都在指定目录中去查找
  19. * 前缀可以对应多个目录,找的时候会去遍历数组
  20. * @var array
  21. */
  22. protected $prefixes = array();
  23. /**
  24. * 当前class的初始化
  25. */
  26. private static $_instance = null;
  27. /**
  28. * @var APP_LOAD_PREFIX 保存类到以APP_LOAD_PREFIX开头的key中
  29. */
  30. const APP_LOAD_PREFIX = '__qii_psr4_instance';
  31. /**
  32. * @var $_loadedClass 保存加载过的类
  33. */
  34. protected static $_loadedClass = array();
  35. /**
  36. * @var $_realpath 将转换后的路径存放到此变量中
  37. */
  38. protected static $_realpath = array();
  39. /**
  40. * 最后一次没有加载到文件的错误路径
  41. * @var array $lastErrorLoadedFile
  42. */
  43. protected static $lastErrorLoadedFile = array();
  44. /**
  45. * 注册自动加载类
  46. *
  47. */
  48. private function __construct()
  49. {
  50. }
  51. /**
  52. * 单例模式
  53. */
  54. public static function getInstance()
  55. {
  56. if (self::$_instance == null) {
  57. self::$_instance = new self();
  58. }
  59. return self::$_instance;
  60. }
  61. /**
  62. * 注册自动加载类
  63. *
  64. * @return $this
  65. */
  66. public function register()
  67. {
  68. spl_autoload_register(array($this, 'loadFileByClass'));
  69. return $this;
  70. }
  71. /**
  72. * Setting is use namespaces for class
  73. *
  74. * @param $prefix 以prefix前缀开头的使用namespace
  75. * @param bool $useNamespace
  76. * @return object $this
  77. */
  78. public function setUseNamespaces($arr)
  79. {
  80. if(!is_array($arr))
  81. {
  82. return $this;
  83. }
  84. foreach($arr as $namespace)
  85. {
  86. call_user_func_array(array($this, 'setUseNamespace'), $namespace);
  87. }
  88. return $this;
  89. }
  90. /**
  91. * Setting is use namespace for class
  92. *
  93. * @param $prefix 以prefix前缀开头的使用namespace
  94. * @param bool $useNamespace
  95. * @return object $this
  96. */
  97. public function setUseNamespace($prefix, $useNamespace = true)
  98. {
  99. $this->useNamespace[$prefix] = $useNamespace;
  100. return $this;
  101. }
  102. /**
  103. * Adds a base directory for namespace prefix
  104. * @param $arr
  105. * @return $this
  106. */
  107. public function addNamespaces($arr)
  108. {
  109. if(!is_array($arr))
  110. {
  111. return $this;
  112. }
  113. foreach ($arr as $namespace)
  114. {
  115. call_user_func_array(array($this, 'addNamespace'), $namespace);
  116. }
  117. return $this;
  118. }
  119. /**
  120. * Adds a base directory for a namespace prefix.
  121. *
  122. * @param string $prefix The namespace prefix.
  123. * @param string $baseDir A base directory for class files in the
  124. * namespace.
  125. * @param bool $prepend If true, prepend the base directory to the stack
  126. * instead of appending it; this causes it to be searched first rather
  127. * than last.
  128. * @return void
  129. */
  130. public function addNamespace($prefix, $baseDir, $prepend = false)
  131. {
  132. // normalize namespace prefix
  133. $prefix = trim($prefix, '\\') . '\\';
  134. // normalize the base directory with a trailing separator
  135. $baseDir = rtrim($baseDir, '/') . DIRECTORY_SEPARATOR;
  136. $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';
  137. // initialize the namespace prefix array
  138. if (isset($this->prefixes[$prefix]) === false) {
  139. $this->prefixes[$prefix] = array();
  140. }
  141. if (in_array($baseDir, $this->prefixes[$prefix])) {
  142. return $this;
  143. }
  144. // retain the base directory for the namespace prefix
  145. if ($prepend) {
  146. array_unshift($this->prefixes[$prefix], $baseDir);
  147. } else {
  148. array_push($this->prefixes[$prefix], $baseDir);
  149. }
  150. return $this;
  151. }
  152. /**
  153. * 移除某一个namespace下的指定路径
  154. * @param string $prefix 前缀
  155. * @param string $baseDir 路径
  156. * @return array
  157. */
  158. public function removeNameSpace($prefix, $baseDir)
  159. {
  160. // normalize namespace prefix
  161. $prefix = trim($prefix, '\\') . '\\';
  162. // normalize the base directory with a trailing separator
  163. $baseDir = rtrim($baseDir, '/') . DIRECTORY_SEPARATOR;
  164. $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';
  165. // initialize the namespace prefix array
  166. if (isset($this->prefixes[$prefix]) === false) {
  167. return false;
  168. }
  169. foreach ($this->prefixes[$prefix] AS $key => $dir) {
  170. if ($dir == $baseDir) unset($this->prefixes[$prefix][$key]);
  171. }
  172. return $this->prefixes;
  173. }
  174. /**
  175. * 返回namespace路径
  176. */
  177. public function getNamespace($prefix)
  178. {
  179. // normalize namespace prefix
  180. $prefix = trim($prefix, '\\') . '\\';
  181. if (isset($this->prefixes[$prefix])) return $this->prefixes[$prefix];
  182. return '';
  183. }
  184. /**
  185. * 通过文件名返回路径
  186. * @param string $fileName 文件名
  187. * @return string
  188. */
  189. public function getFileByPrefix($fileName)
  190. {
  191. $fileName = str_replace(array('/', '\\'), DS, $fileName);
  192. $prefixes = explode(DS, $fileName, 2);
  193. $dirs = isset($this->prefixes['workspace\\']) ? $this->prefixes['workspace\\'] : array();
  194. if (count($prefixes) == 2) {
  195. if (isset($this->prefixes[$prefixes[0]])) $dirs = $this->prefixes[$prefixes[0]];
  196. }
  197. foreach ($dirs as $baseDir) {
  198. if (is_file($baseDir . DS . $fileName)) {
  199. return $baseDir . DS . $fileName;
  200. }
  201. }
  202. return $fileName;
  203. }
  204. /**
  205. * 获取指定文件夹路径
  206. * @param string $folder 路径
  207. * @return string 路径
  208. */
  209. public function getFolderByPrefix($folder)
  210. {
  211. $fileName = str_replace(array('/', '\\'), DS, $folder);
  212. $prefixes = explode(DS, $fileName, 2);
  213. $dirs = isset($this->prefixes['workspace\\']) ? $this->prefixes['workspace\\'] : array();
  214. if (count($prefixes) == 2) {
  215. if (isset($this->prefixes[$prefixes[0]])) $dirs = $this->prefixes[$prefixes[0]];
  216. }
  217. foreach ($dirs as $baseDir) {
  218. return $baseDir . DS . $folder;
  219. }
  220. return $folder;
  221. }
  222. /**
  223. * 从Map中获取文件
  224. */
  225. public function searchMappedFile($class)
  226. {
  227. $prefix = $class;
  228. // work backwards through the namespace names of the fully-qualified
  229. // class name to find a mapped file name
  230. while (false !== $pos = strrpos($prefix, '\\')) {
  231. // retain the trailing namespace separator in the prefix
  232. $prefix = substr($class, 0, $pos + 1);
  233. // the rest is the relative class name
  234. $relativeClass = substr($class, $pos + 1);
  235. // try to load a mapped file for the prefix and relative class
  236. $mappedFile = $this->loadMappedFile($prefix, $relativeClass);
  237. if ($mappedFile) {
  238. return $mappedFile;
  239. }
  240. $prefix = rtrim($prefix, '\\');
  241. };
  242. //如果没有找到就在workspace中去找对应的文件 额外添加的方法
  243. $mappedFile = $this->loadMappedFile('workspace\\', $class);
  244. if ($mappedFile) {
  245. return $mappedFile;
  246. }
  247. return false;
  248. }
  249. /**
  250. * 通过类名加载文件
  251. * @param string $class 类名
  252. * @return string 文件路径
  253. */
  254. public function loadFileByClass($class)
  255. {
  256. // the current namespace prefix
  257. //replace "_" to "\" use common method to load class
  258. $class = str_replace("_", "\\", $class);
  259. if(!$this->searchMappedFile($class))
  260. {
  261. $notLoaded = isset(self::$lastErrorLoadedFile[$class]) ? self::$lastErrorLoadedFile[$class] : self::getClassName($class);
  262. throw new \Qii\Exceptions\FileNotFound(\Qii::i(1405, $notLoaded), 404);
  263. }
  264. }
  265. /**
  266. * loadClass返回真正的类名
  267. *
  268. * @param string $class 类名
  269. */
  270. public function getClassName($class)
  271. {
  272. // the current namespace prefix
  273. //replace "_" to "\" use common method to load class
  274. $class = str_replace("_", "\\", $class);
  275. $class = str_replace("/", "\\", $class);
  276. if($this->searchMappedFile($class))
  277. {
  278. return $class;
  279. }
  280. return str_replace('\\', '_', $class);
  281. }
  282. /**
  283. * Loads the class file for a given class name.
  284. *
  285. * @param string $class The fully-qualified class name.
  286. * @return mixed The mapped file name on success, or boolean false on
  287. * failure.
  288. */
  289. public function loadClass($class)
  290. {
  291. $args = func_get_args();
  292. //去掉第一个斜杠
  293. $class = array_shift($args);
  294. $class = preg_replace("/^\\\\/", "", $class);
  295. $class = $this->getClassName($class);
  296. array_unshift($args, $class);
  297. if (class_exists($class, false)) {
  298. return call_user_func_array(array($this, 'instance'), $args);
  299. }
  300. if ($this->loadFileByClass($class)) {
  301. return call_user_func_array(array($this, 'instance'), $args);
  302. }
  303. throw new \Qii\Exceptions\ClassNotFound(\Qii::i(1103, $class), __LINE__);
  304. }
  305. /**
  306. * 调用静态的方法
  307. * @param string $class 类名
  308. * @param string $method 方法名
  309. * @return mixed
  310. */
  311. public static function loadStatic($class, $method)
  312. {
  313. $args = func_get_args();
  314. $class = \Qii\Autoloader\Psr4::getInstance()->getClassName(array_shift($args));
  315. $method = array_shift($args);
  316. return call_user_func_array(array($class, $method), $args);
  317. }
  318. /**
  319. * 获取文件的绝对路径
  320. * @param string $path
  321. * @param bool $exists 是否使用realpath
  322. * @return string 真实路径
  323. */
  324. public static function realpath($path)
  325. {
  326. if (isset(self::$_realpath[$path])) return self::$_realpath[$path];
  327. $drive = '';
  328. if (OS === 'WIN') {
  329. $path = preg_replace('/[\\\\\/]/', DIRECTORY_SEPARATOR, $path);
  330. if (preg_match('/(phar\:\\\\|[a-zA-Z]\:)(.*)/', $path, $matches)) {
  331. list(, $drive, $path) = $matches;
  332. } else {
  333. $cwd = getcwd();
  334. $drive = substr($cwd, 0, 2);
  335. if (substr($path, 0, 1) != DIRECTORY_SEPARATOR) {
  336. $path = substr($cwd, 3) . DIRECTORY_SEPARATOR . $path;
  337. }
  338. }
  339. } elseif (substr($path, 0, 1) != DIRECTORY_SEPARATOR) {
  340. $path = getcwd() . DIRECTORY_SEPARATOR . $path;
  341. }
  342. $stack = array();
  343. $parts = explode(DIRECTORY_SEPARATOR, $path);
  344. foreach ($parts as $dir) {
  345. if (strlen($dir) && $dir !== '.') {
  346. if ($dir == '..') {
  347. array_pop($stack);
  348. } else {
  349. array_push($stack, $dir);
  350. }
  351. }
  352. }
  353. $realPath = str_replace(DIRECTORY_SEPARATOR, '/', $drive . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $stack));
  354. self::$_realpath[$path] = $realPath;
  355. return $realPath;
  356. }
  357. /**
  358. * Load the mapped file for a namespace prefix and relative class.
  359. *
  360. * @param string $prefix The namespace prefix.
  361. * @param string $relativeClass The relative class name.
  362. * @return mixed Boolean false if no mapped file can be loaded, or the
  363. * name of the mapped file that was loaded.
  364. */
  365. protected function loadMappedFile($prefix, $relativeClass)
  366. {
  367. if (isset(self::$cachedFiles[$prefix . '_' . $relativeClass])) return self::$cachedFiles[$prefix . '_' . $relativeClass];
  368. // are there any base directories for this namespace prefix?
  369. if (isset($this->prefixes[$prefix]) === false) {
  370. //if there any base directories , add self to prefix
  371. $this->addNamespace($prefix, $prefix);
  372. //return false;
  373. }
  374. $prefix = trim($prefix, '\\') . '\\';
  375. $file = '';
  376. // look through base directories for this namespace prefix
  377. foreach ($this->prefixes[$prefix] as $baseDir) {
  378. $path = $baseDir . $relativeClass. '.php';
  379. $file = $this->replaceDash($path);
  380. // if the mapped file exists, require it
  381. if ($this->requireFile($file)) {
  382. self::$cachedFiles[$prefix . '_' . $relativeClass] = $file;
  383. return $file;
  384. }
  385. }
  386. self::$lastErrorLoadedFile[$relativeClass] = $file;
  387. // never found it
  388. return false;
  389. }
  390. /**
  391. * 替换路径中多余的
  392. * @param string $path 路径
  393. * @return mixed
  394. */
  395. protected function replaceDash($path)
  396. {
  397. $path = str_replace('\\', DS, $path);
  398. $path = str_replace('\\\\', DS, $path);
  399. $path = str_replace('/', DS, $path);
  400. $path = str_replace('//', DS, $path);
  401. return $path;
  402. }
  403. /**
  404. * If a file exists, require it from the file system.
  405. *
  406. * @param string $file The file to require.
  407. * @return bool True if the file exists, false if not.
  408. */
  409. protected function requireFile($file)
  410. {
  411. return \Qii\Autoloader\Import::requires($file);
  412. }
  413. /**
  414. * instance class
  415. * @param string $class
  416. * @return object
  417. */
  418. public function instance()
  419. {
  420. $args = func_get_args();
  421. $class = array_shift($args);
  422. $className = $this->getClassName($class);
  423. if (isset(self::$_loadedClass[$className])) return self::$_loadedClass[$className];
  424. if (!class_exists($className, false)) {
  425. throw new \Qii\Exceptions\CallUndefinedClass(\Qii::i('1105', $className), __LINE__);
  426. }
  427. $refClass = new \ReflectionClass($className);
  428. self::$_loadedClass[$className] = $instance = $refClass->newInstanceArgs($args);
  429. //如果有_initialize方法就自动调用_initialize方法,并将参数传递给_initialize方法
  430. if ($refClass->hasMethod('_initialize')) {
  431. call_user_func_array(array($instance, '_initialize'), $args);
  432. }
  433. return $instance;
  434. }
  435. }