Psr4.php 15 KB

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