Route.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. <?php
  2. /**
  3. * This file is part of webman.
  4. *
  5. * Licensed under The MIT License
  6. * For full copyright and license information, please see the MIT-LICENSE.txt
  7. * Redistributions of files must retain the above copyright notice.
  8. *
  9. * @author walkor<walkor@workerman.net>
  10. * @copyright walkor<walkor@workerman.net>
  11. * @link http://www.workerman.net/
  12. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  13. */
  14. namespace Webman;
  15. use FastRoute\Dispatcher\GroupCountBased;
  16. use FastRoute\RouteCollector;
  17. use FilesystemIterator;
  18. use RecursiveDirectoryIterator;
  19. use RecursiveIteratorIterator;
  20. use Webman\Route\Route as RouteObject;
  21. use function array_diff;
  22. use function array_values;
  23. use function class_exists;
  24. use function explode;
  25. use function FastRoute\simpleDispatcher;
  26. use function in_array;
  27. use function is_array;
  28. use function is_callable;
  29. use function is_file;
  30. use function is_scalar;
  31. use function is_string;
  32. use function json_encode;
  33. use function method_exists;
  34. use function strpos;
  35. /**
  36. * Class Route
  37. * @package Webman
  38. */
  39. class Route
  40. {
  41. /**
  42. * @var Route
  43. */
  44. protected static $instance = null;
  45. /**
  46. * @var GroupCountBased
  47. */
  48. protected static $dispatcher = null;
  49. /**
  50. * @var RouteCollector
  51. */
  52. protected static $collector = null;
  53. /**
  54. * @var null|callable
  55. */
  56. protected static $fallback = [];
  57. /**
  58. * @var array
  59. */
  60. protected static $nameList = [];
  61. /**
  62. * @var string
  63. */
  64. protected static $groupPrefix = '';
  65. /**
  66. * @var bool
  67. */
  68. protected static $disableDefaultRoute = [];
  69. /**
  70. * @var RouteObject[]
  71. */
  72. protected static $allRoutes = [];
  73. /**
  74. * @var RouteObject[]
  75. */
  76. protected $routes = [];
  77. /**
  78. * @var Route[]
  79. */
  80. protected $children = [];
  81. /**
  82. * @param string $path
  83. * @param callable|mixed $callback
  84. * @return RouteObject
  85. */
  86. public static function get(string $path, $callback): RouteObject
  87. {
  88. return static::addRoute('GET', $path, $callback);
  89. }
  90. /**
  91. * @param string $path
  92. * @param callable|mixed $callback
  93. * @return RouteObject
  94. */
  95. public static function post(string $path, $callback): RouteObject
  96. {
  97. return static::addRoute('POST', $path, $callback);
  98. }
  99. /**
  100. * @param string $path
  101. * @param callable|mixed $callback
  102. * @return RouteObject
  103. */
  104. public static function put(string $path, $callback): RouteObject
  105. {
  106. return static::addRoute('PUT', $path, $callback);
  107. }
  108. /**
  109. * @param string $path
  110. * @param callable|mixed $callback
  111. * @return RouteObject
  112. */
  113. public static function patch(string $path, $callback): RouteObject
  114. {
  115. return static::addRoute('PATCH', $path, $callback);
  116. }
  117. /**
  118. * @param string $path
  119. * @param callable|mixed $callback
  120. * @return RouteObject
  121. */
  122. public static function delete(string $path, $callback): RouteObject
  123. {
  124. return static::addRoute('DELETE', $path, $callback);
  125. }
  126. /**
  127. * @param string $path
  128. * @param callable|mixed $callback
  129. * @return RouteObject
  130. */
  131. public static function head(string $path, $callback): RouteObject
  132. {
  133. return static::addRoute('HEAD', $path, $callback);
  134. }
  135. /**
  136. * @param string $path
  137. * @param callable|mixed $callback
  138. * @return RouteObject
  139. */
  140. public static function options(string $path, $callback): RouteObject
  141. {
  142. return static::addRoute('OPTIONS', $path, $callback);
  143. }
  144. /**
  145. * @param string $path
  146. * @param callable|mixed $callback
  147. * @return RouteObject
  148. */
  149. public static function any(string $path, $callback): RouteObject
  150. {
  151. return static::addRoute(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'], $path, $callback);
  152. }
  153. /**
  154. * @param $method
  155. * @param string $path
  156. * @param callable|mixed $callback
  157. * @return RouteObject
  158. */
  159. public static function add($method, string $path, $callback): RouteObject
  160. {
  161. return static::addRoute($method, $path, $callback);
  162. }
  163. /**
  164. * @param string|callable $path
  165. * @param callable|null $callback
  166. * @return static
  167. */
  168. public static function group($path, callable $callback = null): Route
  169. {
  170. if ($callback === null) {
  171. $callback = $path;
  172. $path = '';
  173. }
  174. $previousGroupPrefix = static::$groupPrefix;
  175. static::$groupPrefix = $previousGroupPrefix . $path;
  176. $previousInstance = static::$instance;
  177. $instance = static::$instance = new static;
  178. static::$collector->addGroup($path, $callback);
  179. static::$groupPrefix = $previousGroupPrefix;
  180. static::$instance = $previousInstance;
  181. if ($previousInstance) {
  182. $previousInstance->addChild($instance);
  183. }
  184. return $instance;
  185. }
  186. /**
  187. * @param string $name
  188. * @param string $controller
  189. * @param array $options
  190. * @return void
  191. */
  192. public static function resource(string $name, string $controller, array $options = [])
  193. {
  194. $name = trim($name, '/');
  195. if (is_array($options) && !empty($options)) {
  196. $diffOptions = array_diff($options, ['index', 'create', 'store', 'update', 'show', 'edit', 'destroy', 'recovery']);
  197. if (!empty($diffOptions)) {
  198. foreach ($diffOptions as $action) {
  199. static::any("/$name/{$action}[/{id}]", [$controller, $action])->name("$name.{$action}");
  200. }
  201. }
  202. // 注册路由 由于顺序不同会导致路由无效 因此不适用循环注册
  203. if (in_array('index', $options)) static::get("/$name", [$controller, 'index'])->name("$name.index");
  204. if (in_array('create', $options)) static::get("/$name/create", [$controller, 'create'])->name("$name.create");
  205. if (in_array('store', $options)) static::post("/$name", [$controller, 'store'])->name("$name.store");
  206. if (in_array('update', $options)) static::put("/$name/{id}", [$controller, 'update'])->name("$name.update");
  207. if (in_array('patch', $options)) static::patch("/$name/{id}", [$controller, 'patch'])->name("$name.patch");
  208. if (in_array('show', $options)) static::get("/$name/{id}", [$controller, 'show'])->name("$name.show");
  209. if (in_array('edit', $options)) static::get("/$name/{id}/edit", [$controller, 'edit'])->name("$name.edit");
  210. if (in_array('destroy', $options)) static::delete("/$name/{id}", [$controller, 'destroy'])->name("$name.destroy");
  211. if (in_array('recovery', $options)) static::put("/$name/{id}/recovery", [$controller, 'recovery'])->name("$name.recovery");
  212. } else {
  213. //为空时自动注册所有常用路由
  214. if (method_exists($controller, 'index')) static::get("/$name", [$controller, 'index'])->name("$name.index");
  215. if (method_exists($controller, 'create')) static::get("/$name/create", [$controller, 'create'])->name("$name.create");
  216. if (method_exists($controller, 'store')) static::post("/$name", [$controller, 'store'])->name("$name.store");
  217. if (method_exists($controller, 'update')) static::put("/$name/{id}", [$controller, 'update'])->name("$name.update");
  218. if (method_exists($controller, 'patch')) static::patch("/$name/{id}", [$controller, 'patch'])->name("$name.patch");
  219. if (method_exists($controller, 'show')) static::get("/$name/{id}", [$controller, 'show'])->name("$name.show");
  220. if (method_exists($controller, 'edit')) static::get("/$name/{id}/edit", [$controller, 'edit'])->name("$name.edit");
  221. if (method_exists($controller, 'destroy')) static::delete("/$name/{id}", [$controller, 'destroy'])->name("$name.destroy");
  222. if (method_exists($controller, 'recovery')) static::put("/$name/{id}/recovery", [$controller, 'recovery'])->name("$name.recovery");
  223. }
  224. }
  225. /**
  226. * @return RouteObject[]
  227. */
  228. public static function getRoutes(): array
  229. {
  230. return static::$allRoutes;
  231. }
  232. /**
  233. * disableDefaultRoute.
  234. *
  235. * @return void
  236. */
  237. public static function disableDefaultRoute($plugin = '')
  238. {
  239. static::$disableDefaultRoute[$plugin] = true;
  240. }
  241. /**
  242. * @param string $plugin
  243. * @return bool
  244. */
  245. public static function hasDisableDefaultRoute(string $plugin = ''): bool
  246. {
  247. return static::$disableDefaultRoute[$plugin] ?? false;
  248. }
  249. /**
  250. * @param $middleware
  251. * @return $this
  252. */
  253. public function middleware($middleware): Route
  254. {
  255. foreach ($this->routes as $route) {
  256. $route->middleware($middleware);
  257. }
  258. foreach ($this->getChildren() as $child) {
  259. $child->middleware($middleware);
  260. }
  261. return $this;
  262. }
  263. /**
  264. * @param RouteObject $route
  265. */
  266. public function collect(RouteObject $route)
  267. {
  268. $this->routes[] = $route;
  269. }
  270. /**
  271. * @param string $name
  272. * @param RouteObject $instance
  273. */
  274. public static function setByName(string $name, RouteObject $instance)
  275. {
  276. static::$nameList[$name] = $instance;
  277. }
  278. /**
  279. * @param string $name
  280. * @return null|RouteObject
  281. */
  282. public static function getByName(string $name): ?RouteObject
  283. {
  284. return static::$nameList[$name] ?? null;
  285. }
  286. /**
  287. * @param Route $route
  288. * @return void
  289. */
  290. public function addChild(Route $route)
  291. {
  292. $this->children[] = $route;
  293. }
  294. /**
  295. * @return Route[]
  296. */
  297. public function getChildren()
  298. {
  299. return $this->children;
  300. }
  301. /**
  302. * @param string $method
  303. * @param string $path
  304. * @return array
  305. */
  306. public static function dispatch(string $method, string $path): array
  307. {
  308. return static::$dispatcher->dispatch($method, $path);
  309. }
  310. /**
  311. * @param string $path
  312. * @param callable|mixed $callback
  313. * @return callable|false|string[]
  314. */
  315. public static function convertToCallable(string $path, $callback)
  316. {
  317. if (is_string($callback) && strpos($callback, '@')) {
  318. $callback = explode('@', $callback, 2);
  319. }
  320. if (!is_array($callback)) {
  321. if (!is_callable($callback)) {
  322. $callStr = is_scalar($callback) ? $callback : 'Closure';
  323. echo "Route $path $callStr is not callable\n";
  324. return false;
  325. }
  326. } else {
  327. $callback = array_values($callback);
  328. if (!isset($callback[1]) || !class_exists($callback[0]) || !method_exists($callback[0], $callback[1])) {
  329. echo "Route $path " . json_encode($callback) . " is not callable\n";
  330. return false;
  331. }
  332. }
  333. return $callback;
  334. }
  335. /**
  336. * @param array|string $methods
  337. * @param string $path
  338. * @param callable|mixed $callback
  339. * @return RouteObject
  340. */
  341. protected static function addRoute($methods, string $path, $callback): RouteObject
  342. {
  343. $route = new RouteObject($methods, static::$groupPrefix . $path, $callback);
  344. static::$allRoutes[] = $route;
  345. if ($callback = static::convertToCallable($path, $callback)) {
  346. static::$collector->addRoute($methods, $path, ['callback' => $callback, 'route' => $route]);
  347. }
  348. if (static::$instance) {
  349. static::$instance->collect($route);
  350. }
  351. return $route;
  352. }
  353. /**
  354. * Load.
  355. * @param mixed $paths
  356. * @return void
  357. */
  358. public static function load($paths)
  359. {
  360. if (!is_array($paths)) {
  361. return;
  362. }
  363. static::$dispatcher = simpleDispatcher(function (RouteCollector $route) use ($paths) {
  364. Route::setCollector($route);
  365. foreach ($paths as $configPath) {
  366. $routeConfigFile = $configPath . '/route.php';
  367. if (is_file($routeConfigFile)) {
  368. require_once $routeConfigFile;
  369. }
  370. if (!is_dir($pluginConfigPath = $configPath . '/plugin')) {
  371. continue;
  372. }
  373. $dirIterator = new RecursiveDirectoryIterator($pluginConfigPath, FilesystemIterator::FOLLOW_SYMLINKS);
  374. $iterator = new RecursiveIteratorIterator($dirIterator);
  375. foreach ($iterator as $file) {
  376. if ($file->getBaseName('.php') !== 'route') {
  377. continue;
  378. }
  379. $appConfigFile = pathinfo($file, PATHINFO_DIRNAME) . '/app.php';
  380. if (!is_file($appConfigFile)) {
  381. continue;
  382. }
  383. $appConfig = include $appConfigFile;
  384. if (empty($appConfig['enable'])) {
  385. continue;
  386. }
  387. require_once $file;
  388. }
  389. }
  390. });
  391. }
  392. /**
  393. * SetCollector.
  394. * @param RouteCollector $route
  395. * @return void
  396. */
  397. public static function setCollector(RouteCollector $route)
  398. {
  399. static::$collector = $route;
  400. }
  401. /**
  402. * Fallback.
  403. * @param callable|mixed $callback
  404. * @param string $plugin
  405. * @return void
  406. */
  407. public static function fallback(callable $callback, string $plugin = '')
  408. {
  409. static::$fallback[$plugin] = $callback;
  410. }
  411. /**
  412. * GetFallBack.
  413. * @param string $plugin
  414. * @return callable|null
  415. */
  416. public static function getFallback(string $plugin = ''): ?callable
  417. {
  418. return static::$fallback[$plugin] ?? null;
  419. }
  420. /**
  421. * @return void
  422. * @deprecated
  423. */
  424. public static function container()
  425. {
  426. }
  427. }