├── .gitignore ├── src ├── start.php ├── Annotation │ ├── DisableDefaultRoute.php │ └── Middleware.php ├── support │ ├── annotation │ │ ├── DisableDefaultRoute.php │ │ └── Middleware.php │ ├── exception │ │ ├── NotFoundException.php │ │ ├── InputValueException.php │ │ ├── InputTypeException.php │ │ ├── BusinessException.php │ │ ├── Handler.php │ │ ├── MissingInputException.php │ │ └── PageNotFoundException.php │ ├── Request.php │ ├── Context.php │ ├── Response.php │ ├── View.php │ ├── Container.php │ ├── bootstrap │ │ └── Session.php │ ├── view │ │ ├── Raw.php │ │ ├── Blade.php │ │ ├── ThinkPHP.php │ │ └── Twig.php │ ├── Plugin.php │ ├── Translation.php │ ├── bootstrap.php │ ├── Log.php │ ├── App.php │ └── helpers.php ├── Context.php ├── Exception │ ├── FileException.php │ ├── NotFoundException.php │ ├── ExceptionHandlerInterface.php │ ├── BusinessException.php │ └── ExceptionHandler.php ├── Session │ ├── RedisClusterSessionHandler.php │ ├── FileSessionHandler.php │ └── RedisSessionHandler.php ├── Bootstrap.php ├── View.php ├── MiddlewareInterface.php ├── Util.php ├── File.php ├── Install.php ├── Container.php ├── Http │ ├── Response.php │ ├── UploadFile.php │ └── Request.php ├── windows.php ├── Route │ └── Route.php ├── Middleware.php ├── Config.php ├── Route.php └── App.php ├── README.md └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | vendor/ 4 | .idea 5 | .idea/ -------------------------------------------------------------------------------- /src/start.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support\exception; 16 | 17 | class NotFoundException extends BusinessException 18 | { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/support/Request.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support; 16 | 17 | /** 18 | * Class Request 19 | * @package support 20 | */ 21 | class Request extends \Webman\Http\Request 22 | { 23 | 24 | } -------------------------------------------------------------------------------- /src/Annotation/Middleware.php: -------------------------------------------------------------------------------- 1 | middlewares = $middlewares; 15 | } 16 | 17 | public function getMiddlewares(): array 18 | { 19 | $middlewares = []; 20 | foreach ($this->middlewares as $middleware) { 21 | $middlewares[] = [$middleware, 'process']; 22 | } 23 | return $middlewares; 24 | } 25 | } -------------------------------------------------------------------------------- /src/support/Context.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright walkor 12 | * @link http://www.workerman.net/ 13 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | 16 | namespace support; 17 | 18 | /** 19 | * Class Context 20 | * @package Webman 21 | */ 22 | class Context extends \Webman\Context 23 | { 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/support/Response.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support; 16 | 17 | /** 18 | * Class Response 19 | * @package support 20 | */ 21 | class Response extends \Webman\Http\Response 22 | { 23 | 24 | } -------------------------------------------------------------------------------- /src/support/exception/InputValueException.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Exception; 16 | 17 | use RuntimeException; 18 | 19 | /** 20 | * Class FileException 21 | * @package Webman\Exception 22 | */ 23 | class FileException extends RuntimeException 24 | { 25 | } -------------------------------------------------------------------------------- /src/support/exception/BusinessException.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support\exception; 16 | 17 | /** 18 | * Class BusinessException 19 | * @package support\exception 20 | */ 21 | class BusinessException extends \Webman\Exception\BusinessException 22 | { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/Session/RedisClusterSessionHandler.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Session; 16 | 17 | use Workerman\Protocols\Http\Session\RedisClusterSessionHandler as RedisClusterHandler; 18 | 19 | class RedisClusterSessionHandler extends RedisClusterHandler 20 | { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/Session/FileSessionHandler.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Session; 16 | 17 | use Workerman\Protocols\Http\Session\FileSessionHandler as FileHandler; 18 | 19 | /** 20 | * Class FileSessionHandler 21 | * @package Webman 22 | */ 23 | class FileSessionHandler extends FileHandler 24 | { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/Bootstrap.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman; 16 | 17 | use Workerman\Worker; 18 | 19 | interface Bootstrap 20 | { 21 | /** 22 | * onWorkerStart 23 | * 24 | * @param Worker|null $worker 25 | * @return mixed 26 | */ 27 | public static function start(?Worker $worker); 28 | } 29 | -------------------------------------------------------------------------------- /src/Session/RedisSessionHandler.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Session; 16 | 17 | use Workerman\Protocols\Http\Session\RedisSessionHandler as RedisHandler; 18 | 19 | /** 20 | * Class FileSessionHandler 21 | * @package Webman 22 | */ 23 | class RedisSessionHandler extends RedisHandler 24 | { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/Exception/NotFoundException.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Exception; 16 | 17 | use Psr\Container\NotFoundExceptionInterface; 18 | 19 | /** 20 | * Class NotFoundException 21 | * @package Webman\Exception 22 | */ 23 | class NotFoundException extends \Exception implements NotFoundExceptionInterface 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /src/View.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman; 16 | 17 | interface View 18 | { 19 | /** 20 | * Render. 21 | * @param string $template 22 | * @param array $vars 23 | * @param string|null $app 24 | * @return string 25 | */ 26 | public static function render(string $template, array $vars, ?string $app = null): string; 27 | } 28 | -------------------------------------------------------------------------------- /src/MiddlewareInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman; 16 | 17 | use Webman\Http\Request; 18 | use Webman\Http\Response; 19 | 20 | interface MiddlewareInterface 21 | { 22 | /** 23 | * Process an incoming server request. 24 | * 25 | * Processes an incoming server request in order to produce a response. 26 | * If unable to produce the response itself, it may delegate to the provided 27 | * request handler to do so. 28 | */ 29 | public function process(Request $request, callable $handler): Response; 30 | } 31 | -------------------------------------------------------------------------------- /src/support/View.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support; 16 | 17 | use function config; 18 | use function request; 19 | 20 | class View 21 | { 22 | /** 23 | * Assign. 24 | * @param mixed $name 25 | * @param mixed $value 26 | * @return void 27 | */ 28 | public static function assign($name, mixed $value = null) 29 | { 30 | $request = request(); 31 | $plugin = $request->plugin ?? ''; 32 | $handler = config($plugin ? "plugin.$plugin.view.handler" : 'view.handler'); 33 | $handler::assign($name, $value); 34 | } 35 | } -------------------------------------------------------------------------------- /src/Exception/ExceptionHandlerInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Exception; 16 | 17 | use Throwable; 18 | use Webman\Http\Request; 19 | use Webman\Http\Response; 20 | 21 | interface ExceptionHandlerInterface 22 | { 23 | /** 24 | * @param Throwable $exception 25 | * @return mixed 26 | */ 27 | public function report(Throwable $exception); 28 | 29 | /** 30 | * @param Request $request 31 | * @param Throwable $exception 32 | * @return Response 33 | */ 34 | public function render(Request $request, Throwable $exception): Response; 35 | } -------------------------------------------------------------------------------- /src/support/exception/Handler.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support\exception; 16 | 17 | use Throwable; 18 | use Webman\Exception\ExceptionHandler; 19 | use Webman\Http\Request; 20 | use Webman\Http\Response; 21 | use Webman\Exception\BusinessException; 22 | 23 | /** 24 | * Class Handler 25 | * @package support\exception 26 | */ 27 | class Handler extends ExceptionHandler 28 | { 29 | public $dontReport = [ 30 | BusinessException::class, 31 | ]; 32 | 33 | public function report(Throwable $exception) 34 | { 35 | parent::report($exception); 36 | } 37 | 38 | public function render(Request $request, Throwable $exception): Response 39 | { 40 | return parent::render($request, $exception); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/Util.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman; 16 | 17 | use function array_diff; 18 | use function array_map; 19 | use function scandir; 20 | 21 | /** 22 | * Class Util 23 | * @package Webman 24 | */ 25 | class Util 26 | { 27 | /** 28 | * ScanDir. 29 | * @param string $basePath 30 | * @param bool $withBasePath 31 | * @return array 32 | */ 33 | public static function scanDir(string $basePath, bool $withBasePath = true): array 34 | { 35 | if (!is_dir($basePath)) { 36 | return []; 37 | } 38 | $paths = array_diff(scandir($basePath), array('.', '..')) ?: []; 39 | return $withBasePath ? array_map(static function ($path) use ($basePath) { 40 | return $basePath . DIRECTORY_SEPARATOR . $path; 41 | }, $paths) : $paths; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/support/Container.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support; 16 | 17 | use Webman\Config; 18 | 19 | /** 20 | * Class Container 21 | * @package support 22 | * @method static mixed get($name) 23 | * @method static mixed make($name, array $parameters) 24 | * @method static bool has($name) 25 | */ 26 | class Container 27 | { 28 | /** 29 | * Instance 30 | * @param string $plugin 31 | * @return array|mixed|void|null 32 | */ 33 | public static function instance(string $plugin = '') 34 | { 35 | return Config::get($plugin ? "plugin.$plugin.container" : 'container'); 36 | } 37 | 38 | /** 39 | * @param string $name 40 | * @param array $arguments 41 | * @return mixed 42 | */ 43 | public static function __callStatic(string $name, array $arguments) 44 | { 45 | $plugin = \Webman\App::getPluginByClass($name); 46 | return static::instance($plugin)->{$name}(... $arguments); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workerman/webman-framework", 3 | "type": "library", 4 | "keywords": [ 5 | "high performance", 6 | "http service" 7 | ], 8 | "homepage": "https://www.workerman.net", 9 | "license": "MIT", 10 | "description": "High performance HTTP Service Framework.", 11 | "authors": [ 12 | { 13 | "name": "walkor", 14 | "email": "walkor@workerman.net", 15 | "homepage": "https://www.workerman.net", 16 | "role": "Developer" 17 | } 18 | ], 19 | "support": { 20 | "email": "walkor@workerman.net", 21 | "issues": "https://github.com/walkor/webman/issues", 22 | "forum": "https://wenda.workerman.net/", 23 | "wiki": "https://doc.workerman.net/", 24 | "source": "https://github.com/walkor/webman-framework" 25 | }, 26 | "require": { 27 | "php": ">=8.1", 28 | "ext-json": "*", 29 | "workerman/workerman": "^5.1 || dev-master", 30 | "nikic/fast-route": "^1.3", 31 | "psr/container": ">=1.0", 32 | "psr/log": "^2.0 || ^3.0" 33 | }, 34 | "suggest": { 35 | "ext-event": "For better performance. " 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Webman\\": "./src", 40 | "support\\": "./src/support", 41 | "Support\\": "./src/support", 42 | "Support\\Bootstrap\\": "./src/support/bootstrap", 43 | "Support\\Exception\\": "./src/support/exception", 44 | "Support\\View\\": "./src/support/view" 45 | }, 46 | "files": [ 47 | "./src/support/helpers.php" 48 | ] 49 | }, 50 | "minimum-stability": "dev", 51 | "prefer-stable": true 52 | } 53 | -------------------------------------------------------------------------------- /src/support/exception/MissingInputException.php: -------------------------------------------------------------------------------- 1 | getCode() ?: 404; 35 | $debug = config($request->plugin ? "plugin.$request->plugin.app.debug" : 'app.debug'); 36 | $data = $debug ? $this->data : ['parameter' => '']; 37 | $message = $this->trans($this->getMessage(), $data); 38 | if ($request->expectsJson()) { 39 | $json = ['code' => $code, 'msg' => $message, 'data' => $data]; 40 | return new Response(200, ['Content-Type' => 'application/json'], 41 | json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 42 | } 43 | return new Response($code, [], $this->html($message)); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/File.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman; 16 | 17 | use SplFileInfo; 18 | use Webman\Exception\FileException; 19 | use function chmod; 20 | use function is_dir; 21 | use function mkdir; 22 | use function pathinfo; 23 | use function restore_error_handler; 24 | use function set_error_handler; 25 | use function sprintf; 26 | use function strip_tags; 27 | use function umask; 28 | 29 | class File extends SplFileInfo 30 | { 31 | 32 | /** 33 | * Move. 34 | * @param string $destination 35 | * @return File 36 | */ 37 | public function move(string $destination): File 38 | { 39 | set_error_handler(function ($type, $msg) use (&$error) { 40 | $error = $msg; 41 | }); 42 | $path = pathinfo($destination, PATHINFO_DIRNAME); 43 | if (!is_dir($path) && !mkdir($path, 0777, true)) { 44 | restore_error_handler(); 45 | throw new FileException(sprintf('Unable to create the "%s" directory (%s)', $path, strip_tags($error))); 46 | } 47 | if (!rename($this->getPathname(), $destination)) { 48 | restore_error_handler(); 49 | throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $destination, strip_tags($error))); 50 | } 51 | restore_error_handler(); 52 | @chmod($destination, 0666 & ~umask()); 53 | return new self($destination); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/support/bootstrap/Session.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support\bootstrap; 16 | 17 | use Webman\Bootstrap; 18 | use Workerman\Protocols\Http; 19 | use Workerman\Protocols\Http\Session as SessionBase; 20 | use Workerman\Worker; 21 | use function config; 22 | use function property_exists; 23 | 24 | /** 25 | * Class Session 26 | * @package support 27 | */ 28 | class Session implements Bootstrap 29 | { 30 | 31 | /** 32 | * @param Worker|null $worker 33 | * @return void 34 | */ 35 | public static function start(?Worker $worker) 36 | { 37 | $config = config('session'); 38 | if (property_exists(SessionBase::class, 'name')) { 39 | SessionBase::$name = $config['session_name']; 40 | } else { 41 | Http::sessionName($config['session_name']); 42 | } 43 | SessionBase::handlerClass($config['handler'], $config['config'][$config['type']]); 44 | $map = [ 45 | 'auto_update_timestamp' => 'autoUpdateTimestamp', 46 | 'cookie_lifetime' => 'cookieLifetime', 47 | 'gc_probability' => 'gcProbability', 48 | 'cookie_path' => 'cookiePath', 49 | 'http_only' => 'httpOnly', 50 | 'same_site' => 'sameSite', 51 | 'lifetime' => 'lifetime', 52 | 'domain' => 'domain', 53 | 'secure' => 'secure', 54 | ]; 55 | foreach ($map as $key => $name) { 56 | if (isset($config[$key]) && property_exists(SessionBase::class, $name)) { 57 | SessionBase::${$name} = $config[$key]; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Install.php: -------------------------------------------------------------------------------- 1 | 'start.php', 14 | 'windows.php' => 'windows.php', 15 | 'support/bootstrap.php' => 'support/bootstrap.php', 16 | ]; 17 | 18 | /** 19 | * Install 20 | * @return void 21 | */ 22 | public static function install() 23 | { 24 | static::installByRelation(); 25 | static::removeLaravelDbFromBootstrap(); 26 | } 27 | 28 | /** 29 | * Uninstall 30 | * @return void 31 | */ 32 | public static function uninstall() 33 | { 34 | 35 | } 36 | 37 | /** 38 | * InstallByRelation 39 | * @return void 40 | */ 41 | public static function installByRelation() 42 | { 43 | foreach (static::$pathRelation as $source => $dest) { 44 | $parentDir = base_path(dirname($dest)); 45 | if (!is_dir($parentDir)) { 46 | mkdir($parentDir, 0777, true); 47 | } 48 | $sourceFile = __DIR__ . "/$source"; 49 | copy_dir($sourceFile, base_path($dest), true); 50 | echo "Create $dest\r\n"; 51 | if (is_file($sourceFile)) { 52 | @unlink($sourceFile); 53 | } 54 | } 55 | if (is_file($file = base_path('support/helpers.php'))) { 56 | file_put_contents($file, "instances[$name])) { 35 | if (isset($this->definitions[$name])) { 36 | $this->instances[$name] = call_user_func($this->definitions[$name], $this); 37 | } else { 38 | if (!class_exists($name)) { 39 | throw new NotFoundException("Class '$name' not found"); 40 | } 41 | $this->instances[$name] = new $name(); 42 | } 43 | } 44 | return $this->instances[$name]; 45 | } 46 | 47 | /** 48 | * Has. 49 | * @param string $name 50 | * @return bool 51 | */ 52 | public function has(string $name): bool 53 | { 54 | return array_key_exists($name, $this->instances) 55 | || array_key_exists($name, $this->definitions); 56 | } 57 | 58 | /** 59 | * Make. 60 | * @param string $name 61 | * @param array $constructor 62 | * @return mixed 63 | * @throws NotFoundException 64 | */ 65 | public function make(string $name, array $constructor = []) 66 | { 67 | if (!class_exists($name)) { 68 | throw new NotFoundException("Class '$name' not found"); 69 | } 70 | return new $name(... array_values($constructor)); 71 | } 72 | 73 | /** 74 | * AddDefinitions. 75 | * @param array $definitions 76 | * @return $this 77 | */ 78 | public function addDefinitions(array $definitions): Container 79 | { 80 | $this->definitions = array_merge($this->definitions, $definitions); 81 | return $this; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/Http/Response.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Http; 16 | 17 | use Throwable; 18 | use Webman\App; 19 | use function filemtime; 20 | use function gmdate; 21 | 22 | /** 23 | * Class Response 24 | * @package Webman\Http 25 | */ 26 | class Response extends \Workerman\Protocols\Http\Response 27 | { 28 | /** 29 | * @var Throwable 30 | */ 31 | protected $exception = null; 32 | 33 | /** 34 | * File 35 | * @param string $file 36 | * @return $this 37 | */ 38 | public function file(string $file): Response 39 | { 40 | if ($this->notModifiedSince($file)) { 41 | return $this->withStatus(304); 42 | } 43 | return $this->withFile($file); 44 | } 45 | 46 | /** 47 | * Download 48 | * @param string $file 49 | * @param string $downloadName 50 | * @return $this 51 | */ 52 | public function download(string $file, string $downloadName = ''): Response 53 | { 54 | $this->withFile($file); 55 | if ($downloadName) { 56 | $this->header('Content-Disposition', "attachment; filename=\"$downloadName\""); 57 | } 58 | return $this; 59 | } 60 | 61 | /** 62 | * NotModifiedSince 63 | * @param string $file 64 | * @return bool 65 | */ 66 | protected function notModifiedSince(string $file): bool 67 | { 68 | $ifModifiedSince = App::request()->header('if-modified-since'); 69 | if ($ifModifiedSince === null || !is_file($file) || !($mtime = filemtime($file))) { 70 | return false; 71 | } 72 | return $ifModifiedSince === gmdate('D, d M Y H:i:s', $mtime) . ' GMT'; 73 | } 74 | 75 | /** 76 | * Exception 77 | * @param Throwable|null $exception 78 | * @return Throwable|null 79 | */ 80 | public function exception(?Throwable $exception = null): ?Throwable 81 | { 82 | if ($exception) { 83 | $this->exception = $exception; 84 | } 85 | return $this->exception; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/support/view/Raw.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support\view; 16 | 17 | use Throwable; 18 | use Webman\View; 19 | use function app_path; 20 | use function array_merge; 21 | use function base_path; 22 | use function config; 23 | use function extract; 24 | use function is_array; 25 | use function ob_end_clean; 26 | use function ob_get_clean; 27 | use function ob_start; 28 | use function request; 29 | 30 | /** 31 | * Class Raw 32 | * @package support\view 33 | */ 34 | class Raw implements View 35 | { 36 | /** 37 | * Assign. 38 | * @param string|array $name 39 | * @param mixed $value 40 | */ 41 | public static function assign(string|array $name, mixed $value = null): void 42 | { 43 | $request = request(); 44 | $request->_view_vars = array_merge((array) $request->_view_vars, is_array($name) ? $name : [$name => $value]); 45 | } 46 | 47 | /** 48 | * Render. 49 | * @param string $template 50 | * @param array $vars 51 | * @param string|null $app 52 | * @param string|null $plugin 53 | * @return string 54 | */ 55 | public static function render(string $template, array $vars, ?string $app = null, ?string $plugin = null): string 56 | { 57 | $request = request(); 58 | $plugin = $plugin === null ? ($request->plugin ?? '') : $plugin; 59 | $configPrefix = $plugin ? "plugin.$plugin." : ''; 60 | $viewSuffix = config("{$configPrefix}view.options.view_suffix", 'html'); 61 | $app = $app === null ? ($request->app ?? '') : $app; 62 | $baseViewPath = $plugin ? base_path() . "/plugin/$plugin/app" : app_path(); 63 | $__template_path__ = $template[0] === '/' ? base_path() . "$template.$viewSuffix" : ($app === '' ? "$baseViewPath/view/$template.$viewSuffix" : "$baseViewPath/$app/view/$template.$viewSuffix"); 64 | if(isset($request->_view_vars)) { 65 | extract((array)$request->_view_vars); 66 | } 67 | extract($vars); 68 | ob_start(); 69 | // Try to include php file. 70 | try { 71 | include $__template_path__; 72 | } catch (Throwable $e) { 73 | ob_end_clean(); 74 | throw $e; 75 | } 76 | 77 | return ob_get_clean(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Http/UploadFile.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Http; 16 | 17 | use Webman\File; 18 | use function pathinfo; 19 | 20 | /** 21 | * Class UploadFile 22 | * @package Webman\Http 23 | */ 24 | class UploadFile extends File 25 | { 26 | /** 27 | * @var string 28 | */ 29 | protected $uploadName = null; 30 | 31 | /** 32 | * @var string 33 | */ 34 | protected $uploadMimeType = null; 35 | 36 | /** 37 | * @var int 38 | */ 39 | protected $uploadErrorCode = null; 40 | 41 | /** 42 | * UploadFile constructor. 43 | * 44 | * @param string $fileName 45 | * @param string $uploadName 46 | * @param string $uploadMimeType 47 | * @param int $uploadErrorCode 48 | */ 49 | public function __construct(string $fileName, string $uploadName, string $uploadMimeType, int $uploadErrorCode) 50 | { 51 | $this->uploadName = $uploadName; 52 | $this->uploadMimeType = $uploadMimeType; 53 | $this->uploadErrorCode = $uploadErrorCode; 54 | parent::__construct($fileName); 55 | } 56 | 57 | /** 58 | * GetUploadName 59 | * @return string 60 | */ 61 | public function getUploadName(): ?string 62 | { 63 | return $this->uploadName; 64 | } 65 | 66 | /** 67 | * GetUploadMimeType 68 | * @return string 69 | */ 70 | public function getUploadMimeType(): ?string 71 | { 72 | return $this->uploadMimeType; 73 | } 74 | 75 | /** 76 | * GetUploadExtension 77 | * @return string 78 | */ 79 | public function getUploadExtension(): string 80 | { 81 | return pathinfo($this->uploadName, PATHINFO_EXTENSION); 82 | } 83 | 84 | /** 85 | * GetUploadErrorCode 86 | * @return int 87 | */ 88 | public function getUploadErrorCode(): ?int 89 | { 90 | return $this->uploadErrorCode; 91 | } 92 | 93 | /** 94 | * IsValid 95 | * @return bool 96 | */ 97 | public function isValid(): bool 98 | { 99 | return $this->uploadErrorCode === UPLOAD_ERR_OK; 100 | } 101 | 102 | /** 103 | * GetUploadMineType 104 | * @return string 105 | * @deprecated 106 | */ 107 | public function getUploadMineType(): ?string 108 | { 109 | return $this->uploadMimeType; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/support/exception/PageNotFoundException.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support\exception; 16 | 17 | use Throwable; 18 | use Webman\Http\Request; 19 | use Webman\Http\Response; 20 | 21 | class PageNotFoundException extends NotFoundException 22 | { 23 | 24 | /** 25 | * @var string 26 | */ 27 | protected $template = '/app/view/404'; 28 | 29 | /** 30 | * PageNotFoundException constructor. 31 | * @param string $message 32 | * @param int $code 33 | * @param Throwable|null $previous 34 | */ 35 | public function __construct(string $message = '404 Not Found', int $code = 404, ?Throwable $previous = null) { 36 | parent::__construct($message, $code, $previous); 37 | } 38 | 39 | /** 40 | * Render an exception into an HTTP response. 41 | * @param Request $request 42 | * @return Response|null 43 | * @throws Throwable 44 | */ 45 | public function render(Request $request): ?Response 46 | { 47 | $code = $this->getCode() ?: 404; 48 | $data = $this->data; 49 | $message = $this->trans($this->getMessage(), $data); 50 | if ($request->expectsJson()) { 51 | $json = ['code' => $code, 'msg' => $message, 'data' => $data]; 52 | return new Response(200, ['Content-Type' => 'application/json'], 53 | json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 54 | } 55 | return new Response($code, [], $this->html($message)); 56 | } 57 | 58 | /** 59 | * Get the HTML representation of the exception. 60 | * @param string $message 61 | * @return string 62 | * @throws Throwable 63 | */ 64 | protected function html(string $message): string 65 | { 66 | $message = htmlspecialchars($message); 67 | if (is_file(base_path("$this->template.html"))) { 68 | return raw_view($this->template, ['message' => $message])->rawBody(); 69 | } 70 | return << 72 | 73 | 74 | 75 | 76 | $message 77 | 82 | 83 | 84 |

$message

85 |
86 |
webman
87 | 88 | 89 | EOF; 90 | 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/support/view/Blade.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support\view; 16 | 17 | use Jenssegers\Blade\Blade as BladeView; 18 | use Webman\View; 19 | use function app_path; 20 | use function array_merge; 21 | use function base_path; 22 | use function config; 23 | use function is_array; 24 | use function request; 25 | use function runtime_path; 26 | 27 | /** 28 | * Class Blade 29 | * composer require jenssegers/blade 30 | * @package support\view 31 | */ 32 | class Blade implements View 33 | { 34 | /** 35 | * Assign. 36 | * @param string|array $name 37 | * @param mixed $value 38 | */ 39 | public static function assign(string|array $name, mixed $value = null): void 40 | { 41 | $request = request(); 42 | $request->_view_vars = array_merge((array) $request->_view_vars, is_array($name) ? $name : [$name => $value]); 43 | } 44 | 45 | /** 46 | * Render. 47 | * @param string $template 48 | * @param array $vars 49 | * @param string|null $app 50 | * @param string|null $plugin 51 | * @return string 52 | */ 53 | public static function render(string $template, array $vars, ?string $app = null, ?string $plugin = null): string 54 | { 55 | static $views = []; 56 | $request = request(); 57 | $plugin = $plugin === null ? ($request->plugin ?? '') : $plugin; 58 | $app = $app === null ? ($request->app ?? '') : $app; 59 | $configPrefix = $plugin ? "plugin.$plugin." : ''; 60 | $baseViewPath = $plugin ? base_path() . "/plugin/$plugin/app" : app_path(); 61 | if ($template[0] === '/') { 62 | if (strpos($template, '/view/') !== false) { 63 | [$viewPath, $template] = explode('/view/', $template, 2); 64 | $viewPath = base_path("$viewPath/view"); 65 | } else { 66 | $viewPath = base_path(); 67 | $template = ltrim($template, '/'); 68 | } 69 | } else { 70 | $viewPath = $app === '' ? "$baseViewPath/view" : "$baseViewPath/$app/view"; 71 | } 72 | if (!isset($views[$viewPath])) { 73 | $views[$viewPath] = new BladeView($viewPath, runtime_path() . '/views'); 74 | $extension = config("{$configPrefix}view.extension"); 75 | if ($extension) { 76 | $extension($views[$viewPath]); 77 | } 78 | } 79 | if(isset($request->_view_vars)) { 80 | $vars = array_merge((array)$request->_view_vars, $vars); 81 | } 82 | return $views[$viewPath]->render($template, $vars); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/support/Plugin.php: -------------------------------------------------------------------------------- 1 | $path) { 22 | $pluginConst = "\\{$namespace}Install::WEBMAN_PLUGIN"; 23 | if (!defined($pluginConst)) { 24 | continue; 25 | } 26 | $installFunction = "\\{$namespace}Install::install"; 27 | if (is_callable($installFunction)) { 28 | $installFunction(true); 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Update. 35 | * @param mixed $event 36 | * @return void 37 | */ 38 | public static function update($event) 39 | { 40 | static::findHelper(); 41 | $psr4 = static::getPsr4($event); 42 | foreach ($psr4 as $namespace => $path) { 43 | $pluginConst = "\\{$namespace}Install::WEBMAN_PLUGIN"; 44 | if (!defined($pluginConst)) { 45 | continue; 46 | } 47 | $updateFunction = "\\{$namespace}Install::update"; 48 | if (is_callable($updateFunction)) { 49 | $updateFunction(); 50 | continue; 51 | } 52 | $installFunction = "\\{$namespace}Install::install"; 53 | if (is_callable($installFunction)) { 54 | $installFunction(false); 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Uninstall. 61 | * @param mixed $event 62 | * @return void 63 | */ 64 | public static function uninstall($event) 65 | { 66 | static::findHelper(); 67 | $psr4 = static::getPsr4($event); 68 | foreach ($psr4 as $namespace => $path) { 69 | $pluginConst = "\\{$namespace}Install::WEBMAN_PLUGIN"; 70 | if (!defined($pluginConst)) { 71 | continue; 72 | } 73 | $uninstallFunction = "\\{$namespace}Install::uninstall"; 74 | if (is_callable($uninstallFunction)) { 75 | $uninstallFunction(); 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * Get psr-4 info 82 | * 83 | * @param mixed $event 84 | * @return array 85 | */ 86 | protected static function getPsr4($event) 87 | { 88 | $operation = $event->getOperation(); 89 | $autoload = method_exists($operation, 'getPackage') ? $operation->getPackage()->getAutoload() : $operation->getTargetPackage()->getAutoload(); 90 | return $autoload['psr-4'] ?? []; 91 | } 92 | 93 | /** 94 | * FindHelper. 95 | * @return void 96 | */ 97 | protected static function findHelper() 98 | { 99 | // Plugin.php in webman 100 | require_once __DIR__ . '/helpers.php'; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/support/view/ThinkPHP.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support\view; 16 | 17 | use think\Template; 18 | use Webman\View; 19 | use function app_path; 20 | use function array_merge; 21 | use function base_path; 22 | use function config; 23 | use function is_array; 24 | use function ob_get_clean; 25 | use function ob_start; 26 | use function request; 27 | use function runtime_path; 28 | 29 | /** 30 | * Class Blade 31 | * @package support\view 32 | */ 33 | class ThinkPHP implements View 34 | { 35 | /** 36 | * Assign. 37 | * @param string|array $name 38 | * @param mixed $value 39 | */ 40 | public static function assign(string|array $name, mixed $value = null): void 41 | { 42 | $request = request(); 43 | $request->_view_vars = array_merge((array) $request->_view_vars, is_array($name) ? $name : [$name => $value]); 44 | } 45 | 46 | /** 47 | * Render. 48 | * @param string $template 49 | * @param array $vars 50 | * @param string|null $app 51 | * @param string|null $plugin 52 | * @return string 53 | */ 54 | public static function render(string $template, array $vars, ?string $app = null, ?string $plugin = null): string 55 | { 56 | $request = request(); 57 | $plugin = $plugin === null ? ($request->plugin ?? '') : $plugin; 58 | $app = $app === null ? ($request->app ?? '') : $app; 59 | $configPrefix = $plugin ? "plugin.$plugin." : ''; 60 | $viewSuffix = config("{$configPrefix}view.options.view_suffix", 'html'); 61 | $baseViewPath = $plugin ? base_path() . "/plugin/$plugin/app" : app_path(); 62 | if ($template[0] === '/') { 63 | if (strpos($template, '/view/') !== false) { 64 | [$viewPath, $template] = explode('/view/', $template, 2); 65 | $viewPath = base_path("$viewPath/view/"); 66 | } else { 67 | $viewPath = base_path() . dirname($template) . '/'; 68 | $template = basename($template); 69 | } 70 | } else { 71 | $viewPath = $app === '' ? "$baseViewPath/view/" : "$baseViewPath/$app/view/"; 72 | } 73 | $defaultOptions = [ 74 | 'view_path' => $viewPath, 75 | 'cache_path' => runtime_path() . '/views/', 76 | 'view_suffix' => $viewSuffix 77 | ]; 78 | $options = array_merge($defaultOptions, config("{$configPrefix}view.options", [])); 79 | $views = new Template($options); 80 | ob_start(); 81 | if(isset($request->_view_vars)) { 82 | $vars = array_merge((array)$request->_view_vars, $vars); 83 | } 84 | $views->fetch($template, $vars); 85 | return ob_get_clean(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/support/view/Twig.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support\view; 16 | 17 | use Twig\Environment; 18 | use Twig\Error\LoaderError; 19 | use Twig\Error\RuntimeError; 20 | use Twig\Error\SyntaxError; 21 | use Twig\Loader\FilesystemLoader; 22 | use Webman\View; 23 | use function app_path; 24 | use function array_merge; 25 | use function base_path; 26 | use function config; 27 | use function is_array; 28 | use function request; 29 | 30 | /** 31 | * Class Blade 32 | * @package support\view 33 | */ 34 | class Twig implements View 35 | { 36 | /** 37 | * Assign. 38 | * @param string|array $name 39 | * @param mixed $value 40 | */ 41 | public static function assign(string|array $name, mixed $value = null): void 42 | { 43 | $request = request(); 44 | $request->_view_vars = array_merge((array) $request->_view_vars, is_array($name) ? $name : [$name => $value]); 45 | } 46 | 47 | /** 48 | * Render. 49 | * @param string $template 50 | * @param array $vars 51 | * @param string|null $app 52 | * @param string|null $plugin 53 | * @return string 54 | */ 55 | public static function render(string $template, array $vars, ?string $app = null, ?string $plugin = null): string 56 | { 57 | static $views = []; 58 | $request = request(); 59 | $plugin = $plugin === null ? ($request->plugin ?? '') : $plugin; 60 | $app = $app === null ? ($request->app ?? '') : $app; 61 | $configPrefix = $plugin ? "plugin.$plugin." : ''; 62 | $viewSuffix = config("{$configPrefix}view.options.view_suffix", 'html'); 63 | $baseViewPath = $plugin ? base_path() . "/plugin/$plugin/app" : app_path(); 64 | if ($template[0] === '/') { 65 | $template = ltrim($template, '/'); 66 | if (strpos($template, '/view/') !== false) { 67 | [$viewPath, $template] = explode('/view/', $template, 2); 68 | $viewPath = base_path("$viewPath/view"); 69 | } else { 70 | $viewPath = base_path(); 71 | } 72 | } else { 73 | $viewPath = $app === '' ? "$baseViewPath/view/" : "$baseViewPath/$app/view/"; 74 | } 75 | if (!isset($views[$viewPath])) { 76 | $views[$viewPath] = new Environment(new FilesystemLoader($viewPath), config("{$configPrefix}view.options", [])); 77 | $extension = config("{$configPrefix}view.extension"); 78 | if ($extension) { 79 | $extension($views[$viewPath]); 80 | } 81 | } 82 | if(isset($request->_view_vars)) { 83 | $vars = array_merge((array)$request->_view_vars, $vars); 84 | } 85 | return $views[$viewPath]->render("$template.$viewSuffix", $vars); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Exception/BusinessException.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Exception; 16 | 17 | use RuntimeException; 18 | use Throwable; 19 | use Webman\Http\Request; 20 | use Webman\Http\Response; 21 | use function json_encode; 22 | 23 | /** 24 | * Class BusinessException 25 | * @package support\exception 26 | */ 27 | class BusinessException extends RuntimeException 28 | { 29 | 30 | /** 31 | * @var array 32 | */ 33 | protected $data = []; 34 | 35 | /** 36 | * @var bool 37 | */ 38 | protected $debug = false; 39 | 40 | /** 41 | * Render an exception into an HTTP response. 42 | * @param Request $request 43 | * @return Response|null 44 | */ 45 | public function render(Request $request): ?Response 46 | { 47 | if ($request->expectsJson()) { 48 | $code = $this->getCode(); 49 | $json = ['code' => $code ?: 500, 'msg' => $this->getMessage(), 'data' => $this->data]; 50 | return new Response(200, ['Content-Type' => 'application/json'], 51 | json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 52 | } 53 | return new Response(200, [], $this->getMessage()); 54 | } 55 | 56 | /** 57 | * Set data. 58 | * @param array|null $data 59 | * @return array|$this 60 | */ 61 | public function data(?array $data = null): array|static 62 | { 63 | if ($data === null) { 64 | return $this->data; 65 | } 66 | $this->data = $data; 67 | return $this; 68 | } 69 | 70 | /** 71 | * Set debug. 72 | * @param bool|null $value 73 | * @return $this|bool 74 | */ 75 | public function debug(?bool $value = null): bool|static 76 | { 77 | if ($value === null) { 78 | return $this->debug; 79 | } 80 | $this->debug = $value; 81 | return $this; 82 | } 83 | 84 | /** 85 | * Get data. 86 | * @return array 87 | */ 88 | public function getData(): array 89 | { 90 | return $this->data; 91 | } 92 | 93 | /** 94 | * Translate message. 95 | * @param string $message 96 | * @param array $parameters 97 | * @param string|null $domain 98 | * @param string|null $locale 99 | * @return string 100 | */ 101 | protected function trans(string $message, array $parameters = [], ?string $domain = null, ?string $locale = null): string 102 | { 103 | $args = []; 104 | foreach ($parameters as $key => $parameter) { 105 | $args[":$key"] = $parameter; 106 | } 107 | try { 108 | $message = trans($message, $args, $domain, $locale); 109 | } catch (Throwable $e) { 110 | } 111 | foreach ($parameters as $key => $value) { 112 | $message = str_replace(":$key", $value, $message); 113 | } 114 | return $message; 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/Exception/ExceptionHandler.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Exception; 16 | 17 | use Psr\Log\LoggerInterface; 18 | use Throwable; 19 | use Webman\Http\Request; 20 | use Webman\Http\Response; 21 | use function json_encode; 22 | use function nl2br; 23 | use function trim; 24 | 25 | /** 26 | * Class Handler 27 | * @package support\exception 28 | */ 29 | class ExceptionHandler implements ExceptionHandlerInterface 30 | { 31 | /** 32 | * @var LoggerInterface 33 | */ 34 | protected $logger = null; 35 | 36 | /** 37 | * @var bool 38 | */ 39 | protected $debug = false; 40 | 41 | /** 42 | * @var array 43 | */ 44 | public $dontReport = []; 45 | 46 | /** 47 | * ExceptionHandler constructor. 48 | * @param $logger 49 | * @param $debug 50 | */ 51 | public function __construct($logger, $debug) 52 | { 53 | $this->logger = $logger; 54 | $this->debug = $debug; 55 | } 56 | 57 | /** 58 | * @param Throwable $exception 59 | * @return void 60 | */ 61 | public function report(Throwable $exception) 62 | { 63 | if ($this->shouldntReport($exception)) { 64 | return; 65 | } 66 | $logs = ''; 67 | if ($request = \request()) { 68 | $logs = $request->getRealIp() . ' ' . $request->method() . ' ' . trim($request->fullUrl(), '/'); 69 | } 70 | $this->logger->error($logs . PHP_EOL . $exception); 71 | } 72 | 73 | /** 74 | * @param Request $request 75 | * @param Throwable $exception 76 | * @return Response 77 | */ 78 | public function render(Request $request, Throwable $exception): Response 79 | { 80 | if (method_exists($exception, 'render') && ($response = $exception->render($request))) { 81 | return $response; 82 | } 83 | $code = $exception->getCode(); 84 | if ($request->expectsJson()) { 85 | $json = ['code' => $code ?: 500, 'msg' => $this->debug ? $exception->getMessage() : 'Server internal error']; 86 | $this->debug && $json['traces'] = (string)$exception; 87 | return new Response(200, ['Content-Type' => 'application/json'], 88 | json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 89 | } 90 | $error = $this->debug ? nl2br((string)$exception) : 'Server internal error'; 91 | return new Response(500, [], $error); 92 | } 93 | 94 | /** 95 | * @param Throwable $e 96 | * @return bool 97 | */ 98 | protected function shouldntReport(Throwable $e): bool 99 | { 100 | foreach ($this->dontReport as $type) { 101 | if ($e instanceof $type) { 102 | return true; 103 | } 104 | } 105 | return false; 106 | } 107 | 108 | /** 109 | * Compatible $this->_debug 110 | * 111 | * @param string $name 112 | * @return bool|null 113 | */ 114 | public function __get(string $name) 115 | { 116 | if ($name === '_debug') { 117 | return $this->debug; 118 | } 119 | return null; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/support/Translation.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support; 16 | 17 | use FilesystemIterator; 18 | use RecursiveDirectoryIterator; 19 | use RecursiveIteratorIterator; 20 | use RegexIterator; 21 | use Symfony\Component\Translation\Translator; 22 | use Webman\Exception\NotFoundException; 23 | use function basename; 24 | use function config; 25 | use function get_realpath; 26 | use function pathinfo; 27 | use function request; 28 | use function substr; 29 | 30 | /** 31 | * Class Translation 32 | * @package support 33 | * @method static string trans(?string $id, array $parameters = [], string $domain = null, string $locale = null) 34 | * @method static void setLocale(string $locale) 35 | * @method static string getLocale() 36 | */ 37 | class Translation 38 | { 39 | 40 | /** 41 | * @var Translator[] 42 | */ 43 | protected static $instance = []; 44 | 45 | /** 46 | * Instance. 47 | * @param string $plugin 48 | * @param array|null $config 49 | * @return Translator 50 | * @throws NotFoundException 51 | */ 52 | public static function instance(string $plugin = '', ?array $config = null): Translator 53 | { 54 | if (!isset(static::$instance[$plugin])) { 55 | $config = $config ?? config($plugin ? "plugin.$plugin.translation" : 'translation', []); 56 | $paths = (array)($config['path'] ?? []); 57 | 58 | static::$instance[$plugin] = $translator = new Translator($config['locale']); 59 | $translator->setFallbackLocales($config['fallback_locale']); 60 | 61 | $classes = $config['loader'] ?? [ 62 | 'Symfony\Component\Translation\Loader\PhpFileLoader' => [ 63 | 'extension' => '.php', 64 | 'format' => 'phpfile' 65 | ], 66 | 'Symfony\Component\Translation\Loader\PoFileLoader' => [ 67 | 'extension' => '.po', 68 | 'format' => 'pofile' 69 | ] 70 | ]; 71 | foreach ($paths as $path) { 72 | // Phar support. Compatible with the 'realpath' function in the phar file. 73 | if (!$translationsPath = get_realpath($path)) { 74 | throw new NotFoundException("File {$path} not found"); 75 | } 76 | 77 | foreach ($classes as $class => $opts) { 78 | $translator->addLoader($opts['format'], new $class); 79 | $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($translationsPath, FilesystemIterator::SKIP_DOTS)); 80 | $files = new RegexIterator($iterator, '/^.+' . preg_quote($opts['extension']) . '$/i', RegexIterator::GET_MATCH); 81 | foreach ($files as $file) { 82 | $file = $file[0]; 83 | $domain = basename($file, $opts['extension']); 84 | $dirName = pathinfo($file, PATHINFO_DIRNAME); 85 | $locale = substr(strrchr($dirName, DIRECTORY_SEPARATOR), 1); 86 | if ($domain && $locale) { 87 | $translator->addResource($opts['format'], $file, $locale, $domain); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | return static::$instance[$plugin]; 94 | } 95 | 96 | /** 97 | * @param string $name 98 | * @param array $arguments 99 | * @return mixed 100 | * @throws NotFoundException 101 | */ 102 | public static function __callStatic(string $name, array $arguments) 103 | { 104 | $request = request(); 105 | $plugin = $request->plugin ?? ''; 106 | return static::instance($plugin)->{$name}(... $arguments); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/windows.php: -------------------------------------------------------------------------------- 1 | load(); 18 | } else { 19 | Dotenv::createMutable(base_path())->load(); 20 | } 21 | } 22 | 23 | App::loadAllConfig(['route']); 24 | 25 | $errorReporting = config('app.error_reporting'); 26 | if (isset($errorReporting)) { 27 | error_reporting($errorReporting); 28 | } 29 | 30 | $runtimeProcessPath = runtime_path() . DIRECTORY_SEPARATOR . '/windows'; 31 | $paths = [ 32 | $runtimeProcessPath, 33 | runtime_path('logs'), 34 | runtime_path('views') 35 | ]; 36 | foreach ($paths as $path) { 37 | if (!is_dir($path)) { 38 | mkdir($path, 0777, true); 39 | } 40 | } 41 | 42 | $processFiles = []; 43 | if (config('server.listen')) { 44 | $processFiles[] = __DIR__ . DIRECTORY_SEPARATOR . 'start.php'; 45 | } 46 | foreach (config('process', []) as $processName => $config) { 47 | $processFiles[] = write_process_file($runtimeProcessPath, $processName, ''); 48 | } 49 | 50 | foreach (config('plugin', []) as $firm => $projects) { 51 | foreach ($projects as $name => $project) { 52 | if (!is_array($project)) { 53 | continue; 54 | } 55 | foreach ($project['process'] ?? [] as $processName => $config) { 56 | $processFiles[] = write_process_file($runtimeProcessPath, $processName, "$firm.$name"); 57 | } 58 | } 59 | foreach ($projects['process'] ?? [] as $processName => $config) { 60 | $processFiles[] = write_process_file($runtimeProcessPath, $processName, $firm); 61 | } 62 | } 63 | 64 | function write_process_file($runtimeProcessPath, $processName, $firm): string 65 | { 66 | $processParam = $firm ? "plugin.$firm.$processName" : $processName; 67 | $configParam = $firm ? "config('plugin.$firm.process')['$processName']" : "config('process')['$processName']"; 68 | $fileContent = << true]); 119 | if (!$resource) { 120 | exit("Can not execute $cmd\r\n"); 121 | } 122 | return $resource; 123 | } 124 | 125 | $resource = popen_processes($processFiles); 126 | echo "\r\n"; 127 | while (1) { 128 | sleep(1); 129 | if (!empty($monitor) && $monitor->checkAllFilesChange()) { 130 | $status = proc_get_status($resource); 131 | $pid = $status['pid']; 132 | shell_exec("taskkill /F /T /PID $pid"); 133 | proc_close($resource); 134 | $resource = popen_processes($processFiles); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/support/bootstrap.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | use Dotenv\Dotenv; 16 | use support\Log; 17 | use Webman\Bootstrap; 18 | use Webman\Config; 19 | use Webman\Middleware; 20 | use Webman\Route; 21 | use Webman\Util; 22 | use Workerman\Events\Select; 23 | use Workerman\Worker; 24 | 25 | $worker = $worker ?? null; 26 | 27 | if (empty(Worker::$eventLoopClass)) { 28 | Worker::$eventLoopClass = Select::class; 29 | } 30 | 31 | set_error_handler(function ($level, $message, $file = '', $line = 0) { 32 | if (error_reporting() & $level) { 33 | throw new ErrorException($message, 0, $level, $file, $line); 34 | } 35 | }); 36 | 37 | if ($worker) { 38 | register_shutdown_function(function ($startTime) { 39 | if (time() - $startTime <= 0.1) { 40 | sleep(1); 41 | } 42 | }, time()); 43 | } 44 | 45 | if (class_exists('Dotenv\Dotenv') && file_exists(base_path(false) . '/.env')) { 46 | if (method_exists('Dotenv\Dotenv', 'createUnsafeMutable')) { 47 | Dotenv::createUnsafeMutable(base_path(false))->load(); 48 | } else { 49 | Dotenv::createMutable(base_path(false))->load(); 50 | } 51 | } 52 | 53 | Config::clear(); 54 | support\App::loadAllConfig(['route']); 55 | if ($timezone = config('app.default_timezone')) { 56 | date_default_timezone_set($timezone); 57 | } 58 | 59 | foreach (config('autoload.files', []) as $file) { 60 | include_once $file; 61 | } 62 | foreach (config('plugin', []) as $firm => $projects) { 63 | foreach ($projects as $name => $project) { 64 | if (!is_array($project)) { 65 | continue; 66 | } 67 | foreach ($project['autoload']['files'] ?? [] as $file) { 68 | include_once $file; 69 | } 70 | } 71 | foreach ($projects['autoload']['files'] ?? [] as $file) { 72 | include_once $file; 73 | } 74 | } 75 | 76 | Middleware::load(config('middleware', [])); 77 | foreach (config('plugin', []) as $firm => $projects) { 78 | foreach ($projects as $name => $project) { 79 | if (!is_array($project) || $name === 'static') { 80 | continue; 81 | } 82 | Middleware::load($project['middleware'] ?? []); 83 | } 84 | Middleware::load($projects['middleware'] ?? [], $firm); 85 | if ($staticMiddlewares = config("plugin.$firm.static.middleware")) { 86 | Middleware::load(['__static__' => $staticMiddlewares], $firm); 87 | } 88 | } 89 | Middleware::load(['__static__' => config('static.middleware', [])]); 90 | 91 | foreach (config('bootstrap', []) as $className) { 92 | if (!class_exists($className)) { 93 | $log = "Warning: Class $className setting in config/bootstrap.php not found\r\n"; 94 | echo $log; 95 | Log::error($log); 96 | continue; 97 | } 98 | /** @var Bootstrap $className */ 99 | $className::start($worker); 100 | } 101 | 102 | foreach (config('plugin', []) as $firm => $projects) { 103 | foreach ($projects as $name => $project) { 104 | if (!is_array($project)) { 105 | continue; 106 | } 107 | foreach ($project['bootstrap'] ?? [] as $className) { 108 | if (!class_exists($className)) { 109 | $log = "Warning: Class $className setting in config/plugin/$firm/$name/bootstrap.php not found\r\n"; 110 | echo $log; 111 | Log::error($log); 112 | continue; 113 | } 114 | /** @var Bootstrap $className */ 115 | $className::start($worker); 116 | } 117 | } 118 | foreach ($projects['bootstrap'] ?? [] as $className) { 119 | /** @var string $className */ 120 | if (!class_exists($className)) { 121 | $log = "Warning: Class $className setting in plugin/$firm/config/bootstrap.php not found\r\n"; 122 | echo $log; 123 | Log::error($log); 124 | continue; 125 | } 126 | /** @var Bootstrap $className */ 127 | $className::start($worker); 128 | } 129 | } 130 | 131 | $directory = base_path() . '/plugin'; 132 | $paths = [config_path()]; 133 | foreach (Util::scanDir($directory) as $path) { 134 | if (is_dir($path = "$path/config")) { 135 | $paths[] = $path; 136 | } 137 | } 138 | Route::load($paths); 139 | 140 | -------------------------------------------------------------------------------- /src/support/Log.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace support; 16 | 17 | use Monolog\Formatter\FormatterInterface; 18 | use Monolog\Handler\FormattableHandlerInterface; 19 | use Monolog\Handler\HandlerInterface; 20 | use Monolog\Logger; 21 | use function array_values; 22 | use function config; 23 | use function is_array; 24 | 25 | /** 26 | * Class Log 27 | * @package support 28 | * 29 | * @method static void log($level, $message, array $context = []) 30 | * @method static void debug($message, array $context = []) 31 | * @method static void info($message, array $context = []) 32 | * @method static void notice($message, array $context = []) 33 | * @method static void warning($message, array $context = []) 34 | * @method static void error($message, array $context = []) 35 | * @method static void critical($message, array $context = []) 36 | * @method static void alert($message, array $context = []) 37 | * @method static void emergency($message, array $context = []) 38 | */ 39 | class Log 40 | { 41 | /** 42 | * @var array 43 | */ 44 | protected static $instance = []; 45 | 46 | /** 47 | * Channel. 48 | * @param string $name 49 | * @return Logger 50 | */ 51 | public static function channel(string $name = 'default'): Logger 52 | { 53 | if (!isset(static::$instance[$name])) { 54 | $config = config('log', [])[$name]; 55 | $handlers = self::handlers($config); 56 | $processors = self::processors($config); 57 | $logger = new Logger($name, $handlers, $processors); 58 | if (method_exists($logger, 'useLoggingLoopDetection')) { 59 | $logger->useLoggingLoopDetection(false); 60 | } 61 | static::$instance[$name] = $logger; 62 | } 63 | return static::$instance[$name]; 64 | } 65 | 66 | /** 67 | * Handlers. 68 | * @param array $config 69 | * @return array 70 | */ 71 | protected static function handlers(array $config): array 72 | { 73 | $handlerConfigs = $config['handlers'] ?? [[]]; 74 | $handlers = []; 75 | foreach ($handlerConfigs as $value) { 76 | $class = $value['class'] ?? []; 77 | $constructor = $value['constructor'] ?? []; 78 | 79 | $formatterConfig = $value['formatter'] ?? []; 80 | 81 | $class && $handlers[] = self::handler($class, $constructor, $formatterConfig); 82 | } 83 | 84 | return $handlers; 85 | } 86 | 87 | /** 88 | * Handler. 89 | * @param string $class 90 | * @param array $constructor 91 | * @param array $formatterConfig 92 | * @return HandlerInterface 93 | */ 94 | protected static function handler(string $class, array $constructor, array $formatterConfig): HandlerInterface 95 | { 96 | /** @var HandlerInterface $handler */ 97 | $handler = new $class(... array_values($constructor)); 98 | 99 | if ($handler instanceof FormattableHandlerInterface && $formatterConfig) { 100 | $formatterClass = $formatterConfig['class']; 101 | $formatterConstructor = $formatterConfig['constructor']; 102 | 103 | /** @var FormatterInterface $formatter */ 104 | $formatter = new $formatterClass(... array_values($formatterConstructor)); 105 | 106 | $handler->setFormatter($formatter); 107 | } 108 | 109 | return $handler; 110 | } 111 | 112 | /** 113 | * Processors. 114 | * @param array $config 115 | * @return array 116 | */ 117 | protected static function processors(array $config): array 118 | { 119 | $result = []; 120 | if (!isset($config['processors']) && isset($config['processor'])) { 121 | $config['processors'] = [$config['processor']]; 122 | } 123 | 124 | foreach ($config['processors'] ?? [] as $value) { 125 | if (is_array($value) && isset($value['class'])) { 126 | $value = new $value['class'](... array_values($value['constructor'] ?? [])); 127 | } 128 | $result[] = $value; 129 | } 130 | 131 | return $result; 132 | } 133 | 134 | /** 135 | * @param string $name 136 | * @param array $arguments 137 | * @return mixed 138 | */ 139 | public static function __callStatic(string $name, array $arguments) 140 | { 141 | return static::channel()->{$name}(... $arguments); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Route/Route.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Route; 16 | 17 | use Webman\Route as Router; 18 | use function array_merge; 19 | use function count; 20 | use function preg_replace_callback; 21 | use function str_replace; 22 | 23 | /** 24 | * Class Route 25 | * @package Webman 26 | */ 27 | class Route 28 | { 29 | /** 30 | * @var string|null 31 | */ 32 | protected $name = null; 33 | 34 | /** 35 | * @var array 36 | */ 37 | protected $methods = []; 38 | 39 | /** 40 | * @var string 41 | */ 42 | protected $path = ''; 43 | 44 | /** 45 | * @var callable 46 | */ 47 | protected $callback = null; 48 | 49 | /** 50 | * @var array 51 | */ 52 | protected $middlewares = []; 53 | 54 | /** 55 | * @var array 56 | */ 57 | protected $params = []; 58 | 59 | /** 60 | * Route constructor. 61 | * @param array $methods 62 | * @param string $path 63 | * @param callable $callback 64 | */ 65 | public function __construct($methods, string $path, $callback) 66 | { 67 | $this->methods = (array)$methods; 68 | $this->path = $path; 69 | $this->callback = $callback; 70 | } 71 | 72 | /** 73 | * Get name. 74 | * @return string|null 75 | */ 76 | public function getName(): ?string 77 | { 78 | return $this->name ?? null; 79 | } 80 | 81 | /** 82 | * Name. 83 | * @param string $name 84 | * @return $this 85 | */ 86 | public function name(string $name): Route 87 | { 88 | $this->name = $name; 89 | Router::setByName($name, $this); 90 | return $this; 91 | } 92 | 93 | /** 94 | * Middleware. 95 | * @param mixed $middleware 96 | * @return $this|array 97 | */ 98 | public function middleware(mixed $middleware = null) 99 | { 100 | if ($middleware === null) { 101 | return $this->middlewares; 102 | } 103 | $this->middlewares = array_merge($this->middlewares, is_array($middleware) ? array_reverse($middleware) : [$middleware]); 104 | return $this; 105 | } 106 | 107 | /** 108 | * GetPath. 109 | * @return string 110 | */ 111 | public function getPath(): string 112 | { 113 | return $this->path; 114 | } 115 | 116 | /** 117 | * GetMethods. 118 | * @return array 119 | */ 120 | public function getMethods(): array 121 | { 122 | return $this->methods; 123 | } 124 | 125 | /** 126 | * GetCallback. 127 | * @return callable|null 128 | */ 129 | public function getCallback() 130 | { 131 | return $this->callback; 132 | } 133 | 134 | /** 135 | * GetMiddleware. 136 | * @return array 137 | */ 138 | public function getMiddleware(): array 139 | { 140 | return $this->middlewares; 141 | } 142 | 143 | /** 144 | * Param. 145 | * @param string|null $name 146 | * @param mixed $default 147 | * @return mixed 148 | */ 149 | public function param(?string $name = null, mixed $default = null) 150 | { 151 | if ($name === null) { 152 | return $this->params; 153 | } 154 | return $this->params[$name] ?? $default; 155 | } 156 | 157 | /** 158 | * SetParams. 159 | * @param array $params 160 | * @return $this 161 | */ 162 | public function setParams(array $params): Route 163 | { 164 | $this->params = array_merge($this->params, $params); 165 | return $this; 166 | } 167 | 168 | /** 169 | * Url. 170 | * @param array $parameters 171 | * @return string 172 | */ 173 | public function url(array $parameters = []): string 174 | { 175 | if (empty($parameters)) { 176 | return $this->path; 177 | } 178 | $path = str_replace(['[', ']'], '', $this->path); 179 | $path = preg_replace_callback('/\{(.*?)(?:\:[^\}]*?)*?\}/', function ($matches) use (&$parameters) { 180 | if (!$parameters) { 181 | return $matches[0]; 182 | } 183 | if (isset($parameters[$matches[1]])) { 184 | $value = $parameters[$matches[1]]; 185 | unset($parameters[$matches[1]]); 186 | return $value; 187 | } 188 | $key = key($parameters); 189 | if (is_int($key)) { 190 | $value = $parameters[$key]; 191 | unset($parameters[$key]); 192 | return $value; 193 | } 194 | return $matches[0]; 195 | }, $path); 196 | return count($parameters) > 0 ? $path . '?' . http_build_query($parameters) : $path; 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/Middleware.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman; 16 | 17 | 18 | use Closure; 19 | use ReflectionAttribute; 20 | use Webman\Route\Route; 21 | use ReflectionClass; 22 | use ReflectionMethod; 23 | use RuntimeException; 24 | use function array_merge; 25 | use function array_reverse; 26 | use function is_array; 27 | use function method_exists; 28 | 29 | class Middleware 30 | { 31 | 32 | /** 33 | * @var array 34 | */ 35 | protected static $instances = []; 36 | 37 | /** 38 | * @param mixed $allMiddlewares 39 | * @param string $plugin 40 | * @return void 41 | */ 42 | public static function load($allMiddlewares, string $plugin = '') 43 | { 44 | if (!is_array($allMiddlewares)) { 45 | return; 46 | } 47 | foreach ($allMiddlewares as $appName => $middlewares) { 48 | if (!is_array($middlewares)) { 49 | throw new RuntimeException('Bad middleware config'); 50 | } 51 | if ($appName === '@') { 52 | $plugin = ''; 53 | } 54 | if (strpos($appName, 'plugin.') !== false) { 55 | $explode = explode('.', $appName, 4); 56 | $plugin = $explode[1]; 57 | $appName = $explode[2] ?? ''; 58 | } 59 | foreach ($middlewares as $className) { 60 | if (method_exists($className, 'process')) { 61 | static::$instances[$plugin][$appName][] = [$className, 'process']; 62 | } else { 63 | // @todo Log 64 | echo "middleware $className::process not exsits\n"; 65 | } 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * @param string $plugin 72 | * @param string $appName 73 | * @param string|array|Closure $controller 74 | * @param Route|null $route 75 | * @param bool $withGlobalMiddleware 76 | * @return array 77 | */ 78 | public static function getMiddleware(string $plugin, string $appName, string|array|Closure $controller, Route|null $route, bool $withGlobalMiddleware = true): array 79 | { 80 | $isController = is_array($controller) && is_string($controller[0]); 81 | $globalMiddleware = $withGlobalMiddleware ? static::$instances['']['@'] ?? [] : []; 82 | $appGlobalMiddleware = $withGlobalMiddleware && isset(static::$instances[$plugin]['']) ? static::$instances[$plugin][''] : []; 83 | $middlewares = $routeMiddlewares = []; 84 | // Route middleware 85 | if ($route) { 86 | foreach (array_reverse($route->getMiddleware()) as $className) { 87 | $routeMiddlewares[] = [$className, 'process']; 88 | } 89 | } 90 | if ($isController && $controller[0] && class_exists($controller[0])) { 91 | // Controller middleware annotation 92 | $reflectionClass = new ReflectionClass($controller[0]); 93 | self::prepareAttributeMiddlewares($middlewares, $reflectionClass); 94 | // Controller middleware property 95 | if ($reflectionClass->hasProperty('middleware')) { 96 | $defaultProperties = $reflectionClass->getDefaultProperties(); 97 | $middlewaresClasses = $defaultProperties['middleware']; 98 | foreach ((array)$middlewaresClasses as $className) { 99 | $middlewares[] = [$className, 'process']; 100 | } 101 | } 102 | // Route middleware 103 | $middlewares = array_merge($middlewares, $routeMiddlewares); 104 | // Method middleware annotation 105 | if ($reflectionClass->hasMethod($controller[1])) { 106 | self::prepareAttributeMiddlewares($middlewares, $reflectionClass->getMethod($controller[1])); 107 | } 108 | } else { 109 | // Route middleware 110 | $middlewares = array_merge($middlewares, $routeMiddlewares); 111 | } 112 | if ($appName === '') { 113 | return array_reverse(array_merge($globalMiddleware, $appGlobalMiddleware, $middlewares)); 114 | } 115 | $appMiddleware = static::$instances[$plugin][$appName] ?? []; 116 | return array_reverse(array_merge($globalMiddleware, $appGlobalMiddleware, $appMiddleware, $middlewares)); 117 | } 118 | 119 | /** 120 | * @param array $middlewares 121 | * @param ReflectionClass|ReflectionMethod $reflection 122 | * @return void 123 | */ 124 | private static function prepareAttributeMiddlewares(array &$middlewares, ReflectionClass|ReflectionMethod $reflection): void 125 | { 126 | if ($reflection instanceof ReflectionClass && $parent_ref = $reflection->getParentClass()){ 127 | self::prepareAttributeMiddlewares($middlewares, $parent_ref); 128 | } 129 | $middlewareAttributes = $reflection->getAttributes(Annotation\Middleware::class, ReflectionAttribute::IS_INSTANCEOF); 130 | foreach ($middlewareAttributes as $middlewareAttribute) { 131 | $middlewareAttributeInstance = $middlewareAttribute->newInstance(); 132 | $middlewares = array_merge($middlewares, $middlewareAttributeInstance->getMiddlewares()); 133 | } 134 | if (method_exists($reflection, 'getParentClass') && $reflection->getParentClass()) { 135 | self::prepareAttributeMiddlewares($middlewares, $reflection->getParentClass()); 136 | } 137 | } 138 | 139 | /** 140 | * @return void 141 | * @deprecated 142 | */ 143 | public static function container($_) 144 | { 145 | 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/support/App.php: -------------------------------------------------------------------------------- 1 | load(); 34 | } else { 35 | Dotenv::createMutable(run_path())->load(); 36 | } 37 | } 38 | 39 | if (!$appConfigFile = config_path('app.php')) { 40 | throw new RuntimeException('Config file not found: app.php'); 41 | } 42 | $appConfig = require $appConfigFile; 43 | if ($timezone = $appConfig['default_timezone'] ?? '') { 44 | date_default_timezone_set($timezone); 45 | } 46 | 47 | static::loadAllConfig(['route', 'container']); 48 | 49 | if (!is_phar() && DIRECTORY_SEPARATOR === '\\' && empty(config('server.listen'))) { 50 | echo "Please run 'php windows.php' on windows system." . PHP_EOL; 51 | exit; 52 | } 53 | 54 | $errorReporting = config('app.error_reporting'); 55 | if (isset($errorReporting)) { 56 | error_reporting($errorReporting); 57 | } 58 | 59 | $runtimeLogsPath = runtime_path() . DIRECTORY_SEPARATOR . 'logs'; 60 | if (!file_exists($runtimeLogsPath) || !is_dir($runtimeLogsPath)) { 61 | if (!mkdir($runtimeLogsPath, 0777, true)) { 62 | throw new RuntimeException("Failed to create runtime logs directory. Please check the permission."); 63 | } 64 | } 65 | 66 | $runtimeViewsPath = runtime_path() . DIRECTORY_SEPARATOR . 'views'; 67 | if (!file_exists($runtimeViewsPath) || !is_dir($runtimeViewsPath)) { 68 | if (!mkdir($runtimeViewsPath, 0777, true)) { 69 | throw new RuntimeException("Failed to create runtime views directory. Please check the permission."); 70 | } 71 | } 72 | 73 | Worker::$onMasterReload = function () { 74 | if (function_exists('opcache_get_status')) { 75 | if ($status = opcache_get_status()) { 76 | if (isset($status['scripts']) && $scripts = $status['scripts']) { 77 | foreach (array_keys($scripts) as $file) { 78 | opcache_invalidate($file, true); 79 | } 80 | } 81 | } 82 | } 83 | }; 84 | 85 | $config = config('server'); 86 | Worker::$pidFile = $config['pid_file']; 87 | Worker::$stdoutFile = $config['stdout_file']; 88 | Worker::$logFile = $config['log_file']; 89 | Worker::$eventLoopClass = $config['event_loop'] ?? ''; 90 | TcpConnection::$defaultMaxPackageSize = $config['max_package_size'] ?? 10 * 1024 * 1024; 91 | if (property_exists(Worker::class, 'statusFile')) { 92 | Worker::$statusFile = $config['status_file'] ?? ''; 93 | } 94 | if (property_exists(Worker::class, 'stopTimeout')) { 95 | Worker::$stopTimeout = $config['stop_timeout'] ?? 2; 96 | } 97 | 98 | if ($config['listen'] ?? false) { 99 | $worker = new Worker($config['listen'], $config['context']); 100 | $propertyMap = [ 101 | 'name', 102 | 'count', 103 | 'user', 104 | 'group', 105 | 'reusePort', 106 | 'transport', 107 | 'protocol' 108 | ]; 109 | foreach ($propertyMap as $property) { 110 | if (isset($config[$property])) { 111 | $worker->$property = $config[$property]; 112 | } 113 | } 114 | 115 | $worker->onWorkerStart = function ($worker) { 116 | require_once base_path() . '/support/bootstrap.php'; 117 | $app = new \Webman\App(config('app.request_class', Request::class), Log::channel('default'), app_path(), public_path()); 118 | $worker->onMessage = [$app, 'onMessage']; 119 | call_user_func([$app, 'onWorkerStart'], $worker); 120 | }; 121 | } 122 | 123 | $windowsWithoutServerListen = is_phar() && DIRECTORY_SEPARATOR === '\\' && empty($config['listen']); 124 | $process = config('process', []); 125 | if ($windowsWithoutServerListen && $process) { 126 | $processName = isset($process['webman']) ? 'webman' : key($process); 127 | worker_start($processName, $process[$processName]); 128 | } else if (DIRECTORY_SEPARATOR === '/') { 129 | foreach (config('process', []) as $processName => $config) { 130 | worker_start($processName, $config); 131 | } 132 | foreach (config('plugin', []) as $firm => $projects) { 133 | foreach ($projects as $name => $project) { 134 | if (!is_array($project)) { 135 | continue; 136 | } 137 | foreach ($project['process'] ?? [] as $processName => $config) { 138 | worker_start("plugin.$firm.$name.$processName", $config); 139 | } 140 | } 141 | foreach ($projects['process'] ?? [] as $processName => $config) { 142 | worker_start("plugin.$firm.$processName", $config); 143 | } 144 | } 145 | } 146 | 147 | Worker::runAll(); 148 | } 149 | 150 | /** 151 | * LoadAllConfig. 152 | * @param array $excludes 153 | * @return void 154 | */ 155 | public static function loadAllConfig(array $excludes = []) 156 | { 157 | Config::load(config_path(), $excludes); 158 | $directory = base_path() . '/plugin'; 159 | foreach (Util::scanDir($directory, false) as $name) { 160 | $dir = "$directory/$name/config"; 161 | if (is_dir($dir)) { 162 | Config::load($dir, $excludes, "plugin.$name"); 163 | } 164 | } 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman; 16 | 17 | use FilesystemIterator; 18 | use RecursiveDirectoryIterator; 19 | use RecursiveIteratorIterator; 20 | use function array_replace_recursive; 21 | use function array_reverse; 22 | use function count; 23 | use function explode; 24 | use function in_array; 25 | use function is_array; 26 | use function is_dir; 27 | use function is_file; 28 | use function key; 29 | use function str_replace; 30 | 31 | class Config 32 | { 33 | 34 | /** 35 | * @var array 36 | */ 37 | protected static $config = []; 38 | 39 | /** 40 | * @var string 41 | */ 42 | protected static $configPath = ''; 43 | 44 | /** 45 | * @var bool 46 | */ 47 | protected static $loaded = false; 48 | 49 | /** 50 | * Load. 51 | * @param string $configPath 52 | * @param array $excludeFile 53 | * @param string|null $key 54 | * @return void 55 | */ 56 | public static function load(string $configPath, array $excludeFile = [], ?string $key = null) 57 | { 58 | static::$configPath = $configPath; 59 | if (!$configPath) { 60 | return; 61 | } 62 | static::$loaded = false; 63 | $config = static::loadFromDir($configPath, $excludeFile); 64 | if (!$config) { 65 | static::$loaded = true; 66 | return; 67 | } 68 | if ($key !== null) { 69 | foreach (array_reverse(explode('.', $key)) as $k) { 70 | $config = [$k => $config]; 71 | } 72 | } 73 | static::$config = array_replace_recursive(static::$config, $config); 74 | static::formatConfig(); 75 | static::$loaded = true; 76 | } 77 | 78 | /** 79 | * This deprecated method will certainly be removed in the future. 80 | * @param string $configPath 81 | * @param array $excludeFile 82 | * @return void 83 | * @deprecated 84 | */ 85 | public static function reload(string $configPath, array $excludeFile = []) 86 | { 87 | static::load($configPath, $excludeFile); 88 | } 89 | 90 | /** 91 | * Clear. 92 | * @return void 93 | */ 94 | public static function clear() 95 | { 96 | static::$config = []; 97 | } 98 | 99 | /** 100 | * FormatConfig. 101 | * @return void 102 | */ 103 | protected static function formatConfig() 104 | { 105 | $config = static::$config; 106 | // Merge log config 107 | foreach ($config['plugin'] ?? [] as $firm => $projects) { 108 | if (isset($projects['app'])) { 109 | foreach ($projects['log'] ?? [] as $key => $item) { 110 | $config['log']["plugin.$firm.$key"] = $item; 111 | } 112 | } 113 | foreach ($projects as $name => $project) { 114 | if (!is_array($project)) { 115 | continue; 116 | } 117 | foreach ($project['log'] ?? [] as $key => $item) { 118 | $config['log']["plugin.$firm.$name.$key"] = $item; 119 | } 120 | } 121 | } 122 | // Merge database config 123 | foreach ($config['plugin'] ?? [] as $firm => $projects) { 124 | if (isset($projects['app'])) { 125 | foreach ($projects['database']['connections'] ?? [] as $key => $connection) { 126 | $config['database']['connections']["plugin.$firm.$key"] = $connection; 127 | } 128 | } 129 | foreach ($projects as $name => $project) { 130 | if (!is_array($project)) { 131 | continue; 132 | } 133 | foreach ($project['database']['connections'] ?? [] as $key => $connection) { 134 | $config['database']['connections']["plugin.$firm.$name.$key"] = $connection; 135 | } 136 | } 137 | } 138 | if (!empty($config['database']['connections'])) { 139 | $config['database']['default'] = $config['database']['default'] ?? key($config['database']['connections']); 140 | } 141 | // Merge thinkorm config 142 | foreach ($config['plugin'] ?? [] as $firm => $projects) { 143 | if (isset($projects['app'])) { 144 | foreach ($projects['thinkorm']['connections'] ?? [] as $key => $connection) { 145 | $config['thinkorm']['connections']["plugin.$firm.$key"] = $connection; 146 | } 147 | foreach ($projects['think-orm']['connections'] ?? [] as $key => $connection) { 148 | $config['think-orm']['connections']["plugin.$firm.$key"] = $connection; 149 | } 150 | } 151 | foreach ($projects as $name => $project) { 152 | if (!is_array($project)) { 153 | continue; 154 | } 155 | foreach ($project['thinkorm']['connections'] ?? [] as $key => $connection) { 156 | $config['thinkorm']['connections']["plugin.$firm.$name.$key"] = $connection; 157 | } 158 | foreach ($project['think-orm']['connections'] ?? [] as $key => $connection) { 159 | $config['think-orm']['connections']["plugin.$firm.$name.$key"] = $connection; 160 | } 161 | } 162 | } 163 | if (!empty($config['thinkorm']['connections'])) { 164 | $config['thinkorm']['default'] = $config['thinkorm']['default'] ?? key($config['thinkorm']['connections']); 165 | } 166 | if (!empty($config['think-orm']['connections'])) { 167 | $config['think-orm']['default'] = $config['think-orm']['default'] ?? key($config['think-orm']['connections']); 168 | } 169 | // Merge redis config 170 | foreach ($config['plugin'] ?? [] as $firm => $projects) { 171 | if (isset($projects['app'])) { 172 | foreach ($projects['redis'] ?? [] as $key => $connection) { 173 | $config['redis']["plugin.$firm.$key"] = $connection; 174 | } 175 | } 176 | foreach ($projects as $name => $project) { 177 | if (!is_array($project)) { 178 | continue; 179 | } 180 | foreach ($project['redis'] ?? [] as $key => $connection) { 181 | $config['redis']["plugin.$firm.$name.$key"] = $connection; 182 | } 183 | } 184 | } 185 | static::$config = $config; 186 | } 187 | 188 | /** 189 | * LoadFromDir. 190 | * @param string $configPath 191 | * @param array $excludeFile 192 | * @return array 193 | */ 194 | public static function loadFromDir(string $configPath, array $excludeFile = []): array 195 | { 196 | $allConfig = []; 197 | $dirIterator = new RecursiveDirectoryIterator($configPath, FilesystemIterator::FOLLOW_SYMLINKS); 198 | $iterator = new RecursiveIteratorIterator($dirIterator); 199 | foreach ($iterator as $file) { 200 | /** var SplFileInfo $file */ 201 | if (is_dir($file) || $file->getExtension() != 'php' || in_array($file->getBaseName('.php'), $excludeFile)) { 202 | continue; 203 | } 204 | $appConfigFile = $file->getPath() . '/app.php'; 205 | if (!is_file($appConfigFile)) { 206 | continue; 207 | } 208 | $relativePath = str_replace($configPath . DIRECTORY_SEPARATOR, '', substr($file, 0, -4)); 209 | $explode = array_reverse(explode(DIRECTORY_SEPARATOR, $relativePath)); 210 | if (count($explode) >= 2) { 211 | $appConfig = include $appConfigFile; 212 | if (empty($appConfig['enable'])) { 213 | continue; 214 | } 215 | } 216 | $config = include $file; 217 | foreach ($explode as $section) { 218 | $tmp = []; 219 | $tmp[$section] = $config; 220 | $config = $tmp; 221 | } 222 | $allConfig = array_replace_recursive($allConfig, $config); 223 | } 224 | return $allConfig; 225 | } 226 | 227 | /** 228 | * Get. 229 | * @param string|null $key 230 | * @param mixed $default 231 | * @return mixed 232 | */ 233 | public static function get(?string $key = null, mixed $default = null) 234 | { 235 | if ($key === null) { 236 | return static::$config; 237 | } 238 | $keyArray = explode('.', $key); 239 | $value = static::$config; 240 | $found = true; 241 | foreach ($keyArray as $index) { 242 | if (!isset($value[$index])) { 243 | if (static::$loaded) { 244 | return $default; 245 | } 246 | $found = false; 247 | break; 248 | } 249 | $value = $value[$index]; 250 | } 251 | if ($found) { 252 | return $value; 253 | } 254 | return static::read($key, $default); 255 | } 256 | 257 | /** 258 | * Read. 259 | * @param string $key 260 | * @param mixed $default 261 | * @return mixed 262 | */ 263 | protected static function read(string $key, mixed $default = null) 264 | { 265 | $path = static::$configPath; 266 | if ($path === '') { 267 | return $default; 268 | } 269 | $keys = $keyArray = explode('.', $key); 270 | foreach ($keyArray as $index => $section) { 271 | unset($keys[$index]); 272 | if (is_file($file = "$path/$section.php")) { 273 | $config = include $file; 274 | return static::find($keys, $config, $default); 275 | } 276 | if (!is_dir($path = "$path/$section")) { 277 | return $default; 278 | } 279 | } 280 | return $default; 281 | } 282 | 283 | /** 284 | * Find. 285 | * @param array $keyArray 286 | * @param mixed $stack 287 | * @param mixed $default 288 | * @return array|mixed 289 | */ 290 | protected static function find(array $keyArray, $stack, $default) 291 | { 292 | if (!is_array($stack)) { 293 | return $default; 294 | } 295 | $value = $stack; 296 | foreach ($keyArray as $index) { 297 | if (!isset($value[$index])) { 298 | return $default; 299 | } 300 | $value = $value[$index]; 301 | } 302 | return $value; 303 | } 304 | 305 | } 306 | -------------------------------------------------------------------------------- /src/Http/Request.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman\Http; 16 | 17 | use Webman\Route\Route; 18 | use function current; 19 | use function filter_var; 20 | use function ip2long; 21 | use function is_array; 22 | use function strpos; 23 | use const FILTER_FLAG_IPV4; 24 | use const FILTER_FLAG_NO_PRIV_RANGE; 25 | use const FILTER_FLAG_NO_RES_RANGE; 26 | use const FILTER_VALIDATE_IP; 27 | 28 | /** 29 | * Class Request 30 | * @package Webman\Http 31 | */ 32 | class Request extends \Workerman\Protocols\Http\Request 33 | { 34 | /** 35 | * @var string 36 | */ 37 | public $plugin = null; 38 | 39 | /** 40 | * @var string 41 | */ 42 | public $app = null; 43 | 44 | /** 45 | * @var string 46 | */ 47 | public $controller = null; 48 | 49 | /** 50 | * @var string 51 | */ 52 | public $action = null; 53 | 54 | /** 55 | * @var Route 56 | */ 57 | public $route = null; 58 | 59 | /** 60 | * @var bool 61 | */ 62 | protected $isDirty = false; 63 | 64 | /** 65 | * @return mixed|null 66 | */ 67 | public function all() 68 | { 69 | return $this->get() + $this->post(); 70 | } 71 | 72 | /** 73 | * Input 74 | * @param string $name 75 | * @param mixed $default 76 | * @return mixed 77 | */ 78 | public function input(string $name, mixed $default = null) 79 | { 80 | return $this->get($name, $this->post($name, $default)); 81 | } 82 | 83 | /** 84 | * Only 85 | * @param array $keys 86 | * @return array 87 | */ 88 | public function only(array $keys): array 89 | { 90 | $all = $this->all(); 91 | $result = []; 92 | foreach ($keys as $key) { 93 | if (isset($all[$key])) { 94 | $result[$key] = $all[$key]; 95 | } 96 | } 97 | return $result; 98 | } 99 | 100 | /** 101 | * Except 102 | * @param array $keys 103 | * @return mixed|null 104 | */ 105 | public function except(array $keys) 106 | { 107 | $all = $this->all(); 108 | foreach ($keys as $key) { 109 | unset($all[$key]); 110 | } 111 | return $all; 112 | } 113 | 114 | /** 115 | * File 116 | * @param string|null $name 117 | * @return UploadFile|UploadFile[]|null 118 | */ 119 | public function file(?string $name = null): array|null|UploadFile 120 | { 121 | $files = parent::file($name); 122 | if (null === $files) { 123 | return $name === null ? [] : null; 124 | } 125 | if ($name !== null) { 126 | // Multi files 127 | if (is_array(current($files))) { 128 | return $this->parseFiles($files); 129 | } 130 | return $this->parseFile($files); 131 | } 132 | $uploadFiles = []; 133 | foreach ($files as $name => $file) { 134 | // Multi files 135 | if (is_array(current($file))) { 136 | $uploadFiles[$name] = $this->parseFiles($file); 137 | } else { 138 | $uploadFiles[$name] = $this->parseFile($file); 139 | } 140 | } 141 | return $uploadFiles; 142 | } 143 | 144 | /** 145 | * ParseFile 146 | * @param array $file 147 | * @return UploadFile 148 | */ 149 | protected function parseFile(array $file): UploadFile 150 | { 151 | return new UploadFile($file['tmp_name'], $file['name'], $file['type'], $file['error']); 152 | } 153 | 154 | /** 155 | * ParseFiles 156 | * @param array $files 157 | * @return array 158 | */ 159 | protected function parseFiles(array $files): array 160 | { 161 | $uploadFiles = []; 162 | foreach ($files as $key => $file) { 163 | if (is_array(current($file))) { 164 | $uploadFiles[$key] = $this->parseFiles($file); 165 | } else { 166 | $uploadFiles[$key] = $this->parseFile($file); 167 | } 168 | } 169 | return $uploadFiles; 170 | } 171 | 172 | /** 173 | * GetRemoteIp 174 | * @return string 175 | */ 176 | public function getRemoteIp(): string 177 | { 178 | return $this->connection ? $this->connection->getRemoteIp() : '0.0.0.0'; 179 | } 180 | 181 | /** 182 | * GetRemotePort 183 | * @return int 184 | */ 185 | public function getRemotePort(): int 186 | { 187 | return $this->connection ? $this->connection->getRemotePort() : 0; 188 | } 189 | 190 | /** 191 | * GetLocalIp 192 | * @return string 193 | */ 194 | public function getLocalIp(): string 195 | { 196 | return $this->connection ? $this->connection->getLocalIp() : '0.0.0.0'; 197 | } 198 | 199 | /** 200 | * GetLocalPort 201 | * @return int 202 | */ 203 | public function getLocalPort(): int 204 | { 205 | return $this->connection ? $this->connection->getLocalPort() : 0; 206 | } 207 | 208 | /** 209 | * GetRealIp 210 | * @param bool $safeMode 211 | * @return string 212 | */ 213 | public function getRealIp(bool $safeMode = true): string 214 | { 215 | $remoteIp = $this->getRemoteIp(); 216 | if ($safeMode && !static::isIntranetIp($remoteIp)) { 217 | return $remoteIp; 218 | } 219 | $ip = $this->header('x-forwarded-for') 220 | ?? $this->header('x-real-ip') 221 | ?? $this->header('client-ip') 222 | ?? $this->header('x-client-ip') 223 | ?? $this->header('via') 224 | ?? $remoteIp; 225 | if (is_string($ip)) { 226 | $ip = current(explode(',', $ip)); 227 | } 228 | return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : $remoteIp; 229 | } 230 | 231 | /** 232 | * Url 233 | * @return string 234 | */ 235 | public function url(): string 236 | { 237 | return '//' . $this->host() . $this->path(); 238 | } 239 | 240 | /** 241 | * FullUrl 242 | * @return string 243 | */ 244 | public function fullUrl(): string 245 | { 246 | return '//' . $this->host() . $this->uri(); 247 | } 248 | 249 | /** 250 | * IsAjax 251 | * @return bool 252 | */ 253 | public function isAjax(): bool 254 | { 255 | return $this->header('X-Requested-With') === 'XMLHttpRequest'; 256 | } 257 | 258 | /** 259 | * IsGet 260 | * @return bool 261 | */ 262 | public function isGet(): bool 263 | { 264 | return $this->method() === 'GET'; 265 | } 266 | 267 | 268 | /** 269 | * IsPost 270 | * @return bool 271 | */ 272 | public function isPost(): bool 273 | { 274 | return $this->method() === 'POST'; 275 | } 276 | 277 | 278 | /** 279 | * IsPjax 280 | * @return bool 281 | */ 282 | public function isPjax(): bool 283 | { 284 | return (bool)$this->header('X-PJAX'); 285 | } 286 | 287 | /** 288 | * ExpectsJson 289 | * @return bool 290 | */ 291 | public function expectsJson(): bool 292 | { 293 | return ($this->isAjax() && !$this->isPjax()) || $this->acceptJson(); 294 | } 295 | 296 | /** 297 | * AcceptJson 298 | * @return bool 299 | */ 300 | public function acceptJson(): bool 301 | { 302 | return false !== strpos($this->header('accept', ''), 'json'); 303 | } 304 | 305 | /** 306 | * IsIntranetIp 307 | * @param string $ip 308 | * @return bool 309 | */ 310 | public static function isIntranetIp(string $ip): bool 311 | { 312 | // Not validate ip . 313 | if (!filter_var($ip, FILTER_VALIDATE_IP)) { 314 | return false; 315 | } 316 | // Is intranet ip ? For IPv4, the result of false may not be accurate, so we need to check it manually later . 317 | if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { 318 | return true; 319 | } 320 | // Manual check only for IPv4 . 321 | if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { 322 | return false; 323 | } 324 | // Manual check . 325 | $reservedIps = [ 326 | 1681915904 => 1686110207, // 100.64.0.0 - 100.127.255.255 327 | 3221225472 => 3221225727, // 192.0.0.0 - 192.0.0.255 328 | 3221225984 => 3221226239, // 192.0.2.0 - 192.0.2.255 329 | 3227017984 => 3227018239, // 192.88.99.0 - 192.88.99.255 330 | 3323068416 => 3323199487, // 198.18.0.0 - 198.19.255.255 331 | 3325256704 => 3325256959, // 198.51.100.0 - 198.51.100.255 332 | 3405803776 => 3405804031, // 203.0.113.0 - 203.0.113.255 333 | 3758096384 => 4026531839, // 224.0.0.0 - 239.255.255.255 334 | ]; 335 | $ipLong = ip2long($ip); 336 | foreach ($reservedIps as $ipStart => $ipEnd) { 337 | if (($ipLong >= $ipStart) && ($ipLong <= $ipEnd)) { 338 | return true; 339 | } 340 | } 341 | return false; 342 | } 343 | 344 | /** 345 | * Set get. 346 | * @param array|string $input 347 | * @param mixed $value 348 | * @return Request 349 | */ 350 | public function setGet(array|string $input, mixed $value = null): Request 351 | { 352 | $this->isDirty = true; 353 | $input = is_array($input) ? $input : array_merge($this->get(), [$input => $value]); 354 | if (isset($this->data)) { 355 | $this->data['get'] = $input; 356 | } else { 357 | $this->_data['get'] = $input; 358 | } 359 | return $this; 360 | } 361 | 362 | /** 363 | * Set post. 364 | * @param array|string $input 365 | * @param mixed $value 366 | * @return Request 367 | */ 368 | public function setPost(array|string $input, mixed $value = null): Request 369 | { 370 | $this->isDirty = true; 371 | $input = is_array($input) ? $input : array_merge($this->post(), [$input => $value]); 372 | if (isset($this->data)) { 373 | $this->data['post'] = $input; 374 | } else { 375 | $this->_data['post'] = $input; 376 | } 377 | return $this; 378 | } 379 | 380 | /** 381 | * Set header. 382 | * @param array|string $input 383 | * @param mixed $value 384 | * @return Request 385 | */ 386 | public function setHeader(array|string $input, mixed $value = null): Request 387 | { 388 | $this->isDirty = true; 389 | $input = is_array($input) ? $input : array_merge($this->header(), [$input => $value]); 390 | if (isset($this->data)) { 391 | $this->data['headers'] = $input; 392 | } else { 393 | $this->_data['headers'] = $input; 394 | } 395 | return $this; 396 | } 397 | 398 | /** 399 | * Destroy 400 | */ 401 | public function destroy(): void 402 | { 403 | if ($this->isDirty) { 404 | unset($this->data['get'], $this->data['post'], $this->data['headers']); 405 | } 406 | parent::destroy(); 407 | } 408 | 409 | } 410 | -------------------------------------------------------------------------------- /src/Route.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | 15 | namespace Webman; 16 | 17 | use FastRoute\Dispatcher\GroupCountBased; 18 | use FastRoute\RouteCollector; 19 | use FilesystemIterator; 20 | use Psr\Container\ContainerExceptionInterface; 21 | use Psr\Container\NotFoundExceptionInterface; 22 | use RecursiveDirectoryIterator; 23 | use RecursiveIteratorIterator; 24 | use ReflectionAttribute; 25 | use ReflectionClass; 26 | use ReflectionException; 27 | use Webman\Annotation\DisableDefaultRoute; 28 | use Webman\Route\Route as RouteObject; 29 | use function array_diff; 30 | use function array_values; 31 | use function class_exists; 32 | use function explode; 33 | use function FastRoute\simpleDispatcher; 34 | use function in_array; 35 | use function is_array; 36 | use function is_callable; 37 | use function is_file; 38 | use function is_scalar; 39 | use function is_string; 40 | use function json_encode; 41 | use function method_exists; 42 | use function strpos; 43 | 44 | /** 45 | * Class Route 46 | * @package Webman 47 | */ 48 | class Route 49 | { 50 | /** 51 | * @var Route 52 | */ 53 | protected static $instance = null; 54 | 55 | /** 56 | * @var GroupCountBased 57 | */ 58 | protected static $dispatcher = null; 59 | 60 | /** 61 | * @var RouteCollector 62 | */ 63 | protected static $collector = null; 64 | 65 | /** 66 | * @var RouteObject[] 67 | */ 68 | protected static $fallbackRoutes = []; 69 | 70 | /** 71 | * @var array 72 | */ 73 | protected static $fallback = []; 74 | 75 | /** 76 | * @var array 77 | */ 78 | protected static $nameList = []; 79 | 80 | /** 81 | * @var string 82 | */ 83 | protected static $groupPrefix = ''; 84 | 85 | /** 86 | * @var bool 87 | */ 88 | protected static $disabledDefaultRoutes = []; 89 | 90 | /** 91 | * @var array 92 | */ 93 | protected static $disabledDefaultRouteControllers = []; 94 | 95 | /** 96 | * @var array 97 | */ 98 | protected static $disabledDefaultRouteActions = []; 99 | 100 | /** 101 | * @var RouteObject[] 102 | */ 103 | protected static $allRoutes = []; 104 | 105 | /** 106 | * @var RouteObject[] 107 | */ 108 | protected $routes = []; 109 | 110 | /** 111 | * @var Route[] 112 | */ 113 | protected $children = []; 114 | 115 | /** 116 | * @param string $path 117 | * @param callable|mixed $callback 118 | * @return RouteObject 119 | */ 120 | public static function get(string $path, $callback): RouteObject 121 | { 122 | return static::addRoute('GET', $path, $callback); 123 | } 124 | 125 | /** 126 | * @param string $path 127 | * @param callable|mixed $callback 128 | * @return RouteObject 129 | */ 130 | public static function post(string $path, $callback): RouteObject 131 | { 132 | return static::addRoute('POST', $path, $callback); 133 | } 134 | 135 | /** 136 | * @param string $path 137 | * @param callable|mixed $callback 138 | * @return RouteObject 139 | */ 140 | public static function put(string $path, $callback): RouteObject 141 | { 142 | return static::addRoute('PUT', $path, $callback); 143 | } 144 | 145 | /** 146 | * @param string $path 147 | * @param callable|mixed $callback 148 | * @return RouteObject 149 | */ 150 | public static function patch(string $path, $callback): RouteObject 151 | { 152 | return static::addRoute('PATCH', $path, $callback); 153 | } 154 | 155 | /** 156 | * @param string $path 157 | * @param callable|mixed $callback 158 | * @return RouteObject 159 | */ 160 | public static function delete(string $path, $callback): RouteObject 161 | { 162 | return static::addRoute('DELETE', $path, $callback); 163 | } 164 | 165 | /** 166 | * @param string $path 167 | * @param callable|mixed $callback 168 | * @return RouteObject 169 | */ 170 | public static function head(string $path, $callback): RouteObject 171 | { 172 | return static::addRoute('HEAD', $path, $callback); 173 | } 174 | 175 | /** 176 | * @param string $path 177 | * @param callable|mixed $callback 178 | * @return RouteObject 179 | */ 180 | public static function options(string $path, $callback): RouteObject 181 | { 182 | return static::addRoute('OPTIONS', $path, $callback); 183 | } 184 | 185 | /** 186 | * @param string $path 187 | * @param callable|mixed $callback 188 | * @return RouteObject 189 | */ 190 | public static function any(string $path, $callback): RouteObject 191 | { 192 | return static::addRoute(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'], $path, $callback); 193 | } 194 | 195 | /** 196 | * @param $method 197 | * @param string $path 198 | * @param callable|mixed $callback 199 | * @return RouteObject 200 | */ 201 | public static function add($method, string $path, $callback): RouteObject 202 | { 203 | return static::addRoute($method, $path, $callback); 204 | } 205 | 206 | /** 207 | * @param string|callable $path 208 | * @param callable|null $callback 209 | * @return static 210 | */ 211 | public static function group($path, ?callable $callback = null): Route 212 | { 213 | if ($callback === null) { 214 | $callback = $path; 215 | $path = ''; 216 | } 217 | $previousGroupPrefix = static::$groupPrefix; 218 | static::$groupPrefix = $previousGroupPrefix . $path; 219 | $previousInstance = static::$instance; 220 | $instance = static::$instance = new static; 221 | static::$collector->addGroup($path, $callback); 222 | static::$groupPrefix = $previousGroupPrefix; 223 | static::$instance = $previousInstance; 224 | if ($previousInstance) { 225 | $previousInstance->addChild($instance); 226 | } 227 | return $instance; 228 | } 229 | 230 | /** 231 | * @param string $name 232 | * @param string $controller 233 | * @param array $options 234 | * @return void 235 | */ 236 | public static function resource(string $name, string $controller, array $options = []) 237 | { 238 | $name = trim($name, '/'); 239 | if (is_array($options) && !empty($options)) { 240 | $diffOptions = array_diff($options, ['index', 'create', 'store', 'update', 'show', 'edit', 'destroy', 'recovery']); 241 | if (!empty($diffOptions)) { 242 | foreach ($diffOptions as $action) { 243 | static::any("/$name/{$action}[/{id}]", [$controller, $action])->name("$name.{$action}"); 244 | } 245 | } 246 | // 注册路由 由于顺序不同会导致路由无效 因此不适用循环注册 247 | if (in_array('index', $options)) static::get("/$name", [$controller, 'index'])->name("$name.index"); 248 | if (in_array('create', $options)) static::get("/$name/create", [$controller, 'create'])->name("$name.create"); 249 | if (in_array('store', $options)) static::post("/$name", [$controller, 'store'])->name("$name.store"); 250 | if (in_array('update', $options)) static::put("/$name/{id}", [$controller, 'update'])->name("$name.update"); 251 | if (in_array('patch', $options)) static::patch("/$name/{id}", [$controller, 'patch'])->name("$name.patch"); 252 | if (in_array('show', $options)) static::get("/$name/{id}", [$controller, 'show'])->name("$name.show"); 253 | if (in_array('edit', $options)) static::get("/$name/{id}/edit", [$controller, 'edit'])->name("$name.edit"); 254 | if (in_array('destroy', $options)) static::delete("/$name/{id}", [$controller, 'destroy'])->name("$name.destroy"); 255 | if (in_array('recovery', $options)) static::put("/$name/{id}/recovery", [$controller, 'recovery'])->name("$name.recovery"); 256 | } else { 257 | //为空时自动注册所有常用路由 258 | if (method_exists($controller, 'index')) static::get("/$name", [$controller, 'index'])->name("$name.index"); 259 | if (method_exists($controller, 'create')) static::get("/$name/create", [$controller, 'create'])->name("$name.create"); 260 | if (method_exists($controller, 'store')) static::post("/$name", [$controller, 'store'])->name("$name.store"); 261 | if (method_exists($controller, 'update')) static::put("/$name/{id}", [$controller, 'update'])->name("$name.update"); 262 | if (method_exists($controller, 'patch')) static::patch("/$name/{id}", [$controller, 'patch'])->name("$name.patch"); 263 | if (method_exists($controller, 'show')) static::get("/$name/{id}", [$controller, 'show'])->name("$name.show"); 264 | if (method_exists($controller, 'edit')) static::get("/$name/{id}/edit", [$controller, 'edit'])->name("$name.edit"); 265 | if (method_exists($controller, 'destroy')) static::delete("/$name/{id}", [$controller, 'destroy'])->name("$name.destroy"); 266 | if (method_exists($controller, 'recovery')) static::put("/$name/{id}/recovery", [$controller, 'recovery'])->name("$name.recovery"); 267 | } 268 | } 269 | 270 | /** 271 | * @return RouteObject[] 272 | */ 273 | public static function getRoutes(): array 274 | { 275 | return static::$allRoutes; 276 | } 277 | 278 | /** 279 | * disableDefaultRoute. 280 | * 281 | * @param array|string $plugin 282 | * @param string|null $app 283 | * @return bool 284 | */ 285 | public static function disableDefaultRoute(array|string $plugin = '', ?string $app = null): bool 286 | { 287 | // Is [controller action] 288 | if (is_array($plugin)) { 289 | $controllerAction = $plugin; 290 | if (!isset($controllerAction[0]) || !is_string($controllerAction[0]) || 291 | !isset($controllerAction[1]) || !is_string($controllerAction[1])) { 292 | return false; 293 | } 294 | $controller = $controllerAction[0]; 295 | $action = $controllerAction[1]; 296 | static::$disabledDefaultRouteActions[$controller][$action] = $action; 297 | return true; 298 | } 299 | // Is plugin 300 | if (is_string($plugin) && (preg_match('/^[a-zA-Z0-9_]+$/', $plugin) || $plugin === '')) { 301 | if (!isset(static::$disabledDefaultRoutes[$plugin])) { 302 | static::$disabledDefaultRoutes[$plugin] = []; 303 | } 304 | $app = $app ?? '*'; 305 | static::$disabledDefaultRoutes[$plugin][$app] = $app; 306 | return true; 307 | } 308 | // Is controller 309 | if (is_string($plugin) && class_exists($plugin)) { 310 | static::$disabledDefaultRouteControllers[$plugin] = $plugin; 311 | return true; 312 | } 313 | return false; 314 | } 315 | 316 | /** 317 | * @param array|string $plugin 318 | * @param string|null $app 319 | * @return bool 320 | */ 321 | public static function isDefaultRouteDisabled(array|string $plugin = '', ?string $app = null): bool 322 | { 323 | // Is [controller action] 324 | if (is_array($plugin)) { 325 | if (!isset($plugin[0]) || !is_string($plugin[0]) || 326 | !isset($plugin[1]) || !is_string($plugin[1])) { 327 | return false; 328 | } 329 | return isset(static::$disabledDefaultRouteActions[$plugin[0]][$plugin[1]]) || static::isDefaultRouteDisabledByAnnotation($plugin[0], $plugin[1]); 330 | } 331 | // Is plugin 332 | if (is_string($plugin) && (preg_match('/^[a-zA-Z0-9_]+$/', $plugin) || $plugin === '')) { 333 | $app = $app ?? '*'; 334 | return isset(static::$disabledDefaultRoutes[$plugin]['*']) || isset(static::$disabledDefaultRoutes[$plugin][$app]); 335 | } 336 | // Is controller 337 | if (is_string($plugin) && class_exists($plugin)) { 338 | return isset(static::$disabledDefaultRouteControllers[$plugin]); 339 | } 340 | return false; 341 | } 342 | 343 | /** 344 | * @param string $controller 345 | * @param string|null $action 346 | * @return bool 347 | */ 348 | protected static function isDefaultRouteDisabledByAnnotation(string $controller, ?string $action = null): bool 349 | { 350 | if (class_exists($controller)) { 351 | $reflectionClass = new ReflectionClass($controller); 352 | if (static::isRefHasDefaultRouteDisabledAnnotation($reflectionClass)) { 353 | return true; 354 | } 355 | if ($action && $reflectionClass->hasMethod($action)) { 356 | $reflectionMethod = $reflectionClass->getMethod($action); 357 | if ($reflectionMethod->getAttributes(DisableDefaultRoute::class, ReflectionAttribute::IS_INSTANCEOF)) { 358 | return true; 359 | } 360 | } 361 | } 362 | return false; 363 | } 364 | 365 | /** 366 | * @param ReflectionClass $reflectionClass 367 | * @return bool 368 | */ 369 | protected static function isRefHasDefaultRouteDisabledAnnotation(ReflectionClass $reflectionClass): bool 370 | { 371 | $has = $reflectionClass->getAttributes(DisableDefaultRoute::class, ReflectionAttribute::IS_INSTANCEOF); 372 | if ($has) { 373 | return true; 374 | } 375 | if (method_exists($reflectionClass, 'getParentClass')) { 376 | $parent = $reflectionClass->getParentClass(); 377 | if ($parent) { 378 | return static::isRefHasDefaultRouteDisabledAnnotation($parent); 379 | } 380 | } 381 | return false; 382 | } 383 | 384 | /** 385 | * @param $middleware 386 | * @return $this 387 | */ 388 | public function middleware($middleware): Route 389 | { 390 | foreach ($this->routes as $route) { 391 | $route->middleware($middleware); 392 | } 393 | foreach ($this->getChildren() as $child) { 394 | $child->middleware($middleware); 395 | } 396 | return $this; 397 | } 398 | 399 | /** 400 | * @param RouteObject $route 401 | */ 402 | public function collect(RouteObject $route) 403 | { 404 | $this->routes[] = $route; 405 | } 406 | 407 | /** 408 | * @param string $name 409 | * @param RouteObject $instance 410 | */ 411 | public static function setByName(string $name, RouteObject $instance) 412 | { 413 | static::$nameList[$name] = $instance; 414 | } 415 | 416 | /** 417 | * @param string $name 418 | * @return null|RouteObject 419 | */ 420 | public static function getByName(string $name): ?RouteObject 421 | { 422 | return static::$nameList[$name] ?? null; 423 | } 424 | 425 | /** 426 | * @param Route $route 427 | * @return void 428 | */ 429 | public function addChild(Route $route) 430 | { 431 | $this->children[] = $route; 432 | } 433 | 434 | /** 435 | * @return Route[] 436 | */ 437 | public function getChildren() 438 | { 439 | return $this->children; 440 | } 441 | 442 | /** 443 | * @param string $method 444 | * @param string $path 445 | * @return array 446 | */ 447 | public static function dispatch(string $method, string $path): array 448 | { 449 | return static::$dispatcher->dispatch($method, $path); 450 | } 451 | 452 | /** 453 | * @param string $path 454 | * @param callable|mixed $callback 455 | * @return callable|false|string[] 456 | */ 457 | public static function convertToCallable(string $path, $callback) 458 | { 459 | if (is_string($callback) && strpos($callback, '@')) { 460 | $callback = explode('@', $callback, 2); 461 | } 462 | 463 | if (!is_array($callback)) { 464 | if (!is_callable($callback)) { 465 | $callStr = is_scalar($callback) ? $callback : 'Closure'; 466 | echo "Route $path $callStr is not callable\n"; 467 | return false; 468 | } 469 | } else { 470 | $callback = array_values($callback); 471 | if (!isset($callback[1]) || !class_exists($callback[0]) || !method_exists($callback[0], $callback[1])) { 472 | echo "Route $path " . json_encode($callback) . " is not callable\n"; 473 | return false; 474 | } 475 | } 476 | 477 | return $callback; 478 | } 479 | 480 | /** 481 | * @param array|string $methods 482 | * @param string $path 483 | * @param callable|mixed $callback 484 | * @return RouteObject 485 | */ 486 | protected static function addRoute($methods, string $path, $callback): RouteObject 487 | { 488 | $route = new RouteObject($methods, static::$groupPrefix . $path, $callback); 489 | static::$allRoutes[] = $route; 490 | 491 | if ($callback = static::convertToCallable($path, $callback)) { 492 | static::$collector->addRoute($methods, $path, ['callback' => $callback, 'route' => $route]); 493 | } 494 | if (static::$instance) { 495 | static::$instance->collect($route); 496 | } 497 | return $route; 498 | } 499 | 500 | /** 501 | * Load. 502 | * @param mixed $paths 503 | * @return void 504 | */ 505 | public static function load($paths) 506 | { 507 | if (!is_array($paths)) { 508 | return; 509 | } 510 | static::$dispatcher = simpleDispatcher(function (RouteCollector $route) use ($paths) { 511 | Route::setCollector($route); 512 | foreach ($paths as $configPath) { 513 | $routeConfigFile = $configPath . '/route.php'; 514 | if (is_file($routeConfigFile)) { 515 | require_once $routeConfigFile; 516 | } 517 | if (!is_dir($pluginConfigPath = $configPath . '/plugin')) { 518 | continue; 519 | } 520 | $dirIterator = new RecursiveDirectoryIterator($pluginConfigPath, FilesystemIterator::FOLLOW_SYMLINKS); 521 | $iterator = new RecursiveIteratorIterator($dirIterator); 522 | foreach ($iterator as $file) { 523 | if ($file->getBaseName('.php') !== 'route') { 524 | continue; 525 | } 526 | $appConfigFile = pathinfo($file, PATHINFO_DIRNAME) . '/app.php'; 527 | if (!is_file($appConfigFile)) { 528 | continue; 529 | } 530 | $appConfig = include $appConfigFile; 531 | if (empty($appConfig['enable'])) { 532 | continue; 533 | } 534 | require_once $file; 535 | } 536 | } 537 | }); 538 | } 539 | 540 | /** 541 | * SetCollector. 542 | * @param RouteCollector $route 543 | * @return void 544 | */ 545 | public static function setCollector(RouteCollector $route) 546 | { 547 | static::$collector = $route; 548 | } 549 | 550 | /** 551 | * Fallback. 552 | * @param callable|mixed $callback 553 | * @param string $plugin 554 | * @return RouteObject 555 | */ 556 | public static function fallback(callable $callback, string $plugin = '') 557 | { 558 | $route = new RouteObject([], '', $callback); 559 | static::$fallbackRoutes[$plugin] = $route; 560 | return $route; 561 | } 562 | 563 | /** 564 | * GetFallBack. 565 | * @param string $plugin 566 | * @param int $status 567 | * @return callable|null 568 | * @throws ContainerExceptionInterface 569 | * @throws NotFoundExceptionInterface 570 | * @throws ReflectionException 571 | */ 572 | public static function getFallback(string $plugin = '', int $status = 404) 573 | { 574 | if (!isset(static::$fallback[$plugin])) { 575 | $callback = null; 576 | $route = static::$fallbackRoutes[$plugin] ?? null; 577 | static::$fallback[$plugin] = $route ? App::getCallback($plugin, 'NOT_FOUND', $route->getCallback(), ['status' => $status], false, $route) : null; 578 | } 579 | return static::$fallback[$plugin]; 580 | } 581 | 582 | /** 583 | * @return void 584 | * @deprecated 585 | */ 586 | public static function container() 587 | { 588 | 589 | } 590 | 591 | } 592 | -------------------------------------------------------------------------------- /src/support/helpers.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright walkor 12 | * @link http://www.workerman.net/ 13 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | 16 | use support\Container; 17 | use support\Request; 18 | use support\Response; 19 | use support\Translation; 20 | use support\view\Blade; 21 | use support\view\Raw; 22 | use support\view\ThinkPHP; 23 | use support\view\Twig; 24 | use Twig\Error\LoaderError; 25 | use Twig\Error\RuntimeError; 26 | use Twig\Error\SyntaxError; 27 | use Webman\App; 28 | use Webman\Config; 29 | use Webman\Route; 30 | use Workerman\Protocols\Http\Session; 31 | use Workerman\Worker; 32 | 33 | /** 34 | * Get the base path of the application 35 | */ 36 | if (!defined('BASE_PATH')) { 37 | if (!$basePath = Phar::running()) { 38 | $basePath = getcwd(); 39 | while ($basePath !== dirname($basePath)) { 40 | if (is_dir("$basePath/vendor") && is_file("$basePath/start.php")) { 41 | break; 42 | } 43 | $basePath = dirname($basePath); 44 | } 45 | if ($basePath === dirname($basePath)) { 46 | $basePath = __DIR__ . '/../../../../../'; 47 | } 48 | } 49 | define('BASE_PATH', realpath($basePath) ?: $basePath); 50 | } 51 | 52 | if (!function_exists('run_path')) { 53 | /** 54 | * return the program execute directory 55 | * @param string $path 56 | * @return string 57 | */ 58 | function run_path(string $path = ''): string 59 | { 60 | static $runPath = ''; 61 | if (!$runPath) { 62 | $runPath = is_phar() ? dirname(Phar::running(false)) : BASE_PATH; 63 | } 64 | return path_combine($runPath, $path); 65 | } 66 | } 67 | 68 | if (!function_exists('base_path')) { 69 | /** 70 | * if the param $path equal false,will return this program current execute directory 71 | * @param string|false $path 72 | * @return string 73 | */ 74 | function base_path($path = ''): string 75 | { 76 | if (false === $path) { 77 | return run_path(); 78 | } 79 | return path_combine(BASE_PATH, $path); 80 | } 81 | } 82 | 83 | if (!function_exists('app_path')) { 84 | /** 85 | * App path 86 | * @param string $path 87 | * @return string 88 | */ 89 | function app_path(string $path = ''): string 90 | { 91 | return path_combine(BASE_PATH . DIRECTORY_SEPARATOR . 'app', $path); 92 | } 93 | } 94 | 95 | if (!function_exists('public_path')) { 96 | /** 97 | * Public path 98 | * @param string $path 99 | * @param string|null $plugin 100 | * @return string 101 | */ 102 | function public_path(string $path = '', ?string $plugin = null): string 103 | { 104 | static $publicPaths = []; 105 | $plugin = $plugin ?? ''; 106 | if (isset($publicPaths[$plugin])) { 107 | $publicPath = $publicPaths[$plugin]; 108 | } else { 109 | $prefix = $plugin ? "plugin.$plugin." : ''; 110 | $pathPrefix = $plugin ? 'plugin' . DIRECTORY_SEPARATOR . $plugin . DIRECTORY_SEPARATOR : ''; 111 | $publicPath = \config("{$prefix}app.public_path", run_path("{$pathPrefix}public")); 112 | if (count($publicPaths) > 32) { 113 | $publicPaths = []; 114 | } 115 | $publicPaths[$plugin] = $publicPath; 116 | } 117 | return $path === '' ? $publicPath : path_combine($publicPath, $path); 118 | } 119 | } 120 | 121 | if (!function_exists('config_path')) { 122 | /** 123 | * Config path 124 | * @param string $path 125 | * @return string 126 | */ 127 | function config_path(string $path = ''): string 128 | { 129 | return path_combine(BASE_PATH . DIRECTORY_SEPARATOR . 'config', $path); 130 | } 131 | } 132 | 133 | if (!function_exists('runtime_path')) { 134 | /** 135 | * Runtime path 136 | * @param string $path 137 | * @return string 138 | */ 139 | function runtime_path(string $path = ''): string 140 | { 141 | static $runtimePath = ''; 142 | if (!$runtimePath) { 143 | $runtimePath = \config('app.runtime_path') ?: run_path('runtime'); 144 | } 145 | return path_combine($runtimePath, $path); 146 | } 147 | } 148 | 149 | if (!function_exists('path_combine')) { 150 | /** 151 | * Generate paths based on given information 152 | * @param string $front 153 | * @param string $back 154 | * @return string 155 | */ 156 | function path_combine(string $front, string $back): string 157 | { 158 | return $front . ($back ? (DIRECTORY_SEPARATOR . ltrim($back, DIRECTORY_SEPARATOR)) : $back); 159 | } 160 | } 161 | 162 | if (!function_exists('response')) { 163 | /** 164 | * Response 165 | * @param int $status 166 | * @param array $headers 167 | * @param string $body 168 | * @return Response 169 | */ 170 | function response(string $body = '', int $status = 200, array $headers = []): Response 171 | { 172 | return new Response($status, $headers, $body); 173 | } 174 | } 175 | 176 | if (!function_exists('json')) { 177 | /** 178 | * Json response 179 | * @param $data 180 | * @param int $options 181 | * @return Response 182 | */ 183 | function json($data, int $options = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR): Response 184 | { 185 | return new Response(200, ['Content-Type' => 'application/json'], json_encode($data, $options)); 186 | } 187 | } 188 | 189 | if (!function_exists('xml')) { 190 | /** 191 | * Xml response 192 | * @param $xml 193 | * @return Response 194 | */ 195 | function xml($xml): Response 196 | { 197 | if ($xml instanceof SimpleXMLElement) { 198 | $xml = $xml->asXML(); 199 | } 200 | return new Response(200, ['Content-Type' => 'text/xml'], $xml); 201 | } 202 | } 203 | 204 | if (!function_exists('jsonp')) { 205 | /** 206 | * Jsonp response 207 | * @param $data 208 | * @param string $callbackName 209 | * @return Response 210 | */ 211 | function jsonp($data, string $callbackName = 'callback'): Response 212 | { 213 | if (!is_scalar($data) && null !== $data) { 214 | $data = json_encode($data); 215 | } 216 | return new Response(200, [], "$callbackName($data)"); 217 | } 218 | } 219 | 220 | if (!function_exists('redirect')) { 221 | /** 222 | * Redirect response 223 | * @param string $location 224 | * @param int $status 225 | * @param array $headers 226 | * @return Response 227 | */ 228 | function redirect(string $location, int $status = 302, array $headers = []): Response 229 | { 230 | $response = new Response($status, ['Location' => $location]); 231 | if (!empty($headers)) { 232 | $response->withHeaders($headers); 233 | } 234 | return $response; 235 | } 236 | } 237 | 238 | if (!function_exists('view')) { 239 | /** 240 | * View response 241 | * @param mixed $template 242 | * @param array $vars 243 | * @param string|null $app 244 | * @param string|null $plugin 245 | * @return Response 246 | */ 247 | function view(mixed $template = null, array $vars = [], ?string $app = null, ?string $plugin = null): Response 248 | { 249 | [$template, $vars, $app, $plugin] = template_inputs($template, $vars, $app, $plugin); 250 | $handler = \config($plugin ? "plugin.$plugin.view.handler" : 'view.handler'); 251 | return new Response(200, [], $handler::render($template, $vars, $app, $plugin)); 252 | } 253 | } 254 | 255 | if (!function_exists('raw_view')) { 256 | /** 257 | * Raw view response 258 | * @param mixed $template 259 | * @param array $vars 260 | * @param string|null $app 261 | * @param string|null $plugin 262 | * @return Response 263 | * @throws Throwable 264 | */ 265 | function raw_view(mixed $template = null, array $vars = [], ?string $app = null, ?string $plugin = null): Response 266 | { 267 | return new Response(200, [], Raw::render(...template_inputs($template, $vars, $app, $plugin))); 268 | } 269 | } 270 | 271 | if (!function_exists('blade_view')) { 272 | /** 273 | * Blade view response 274 | * @param mixed $template 275 | * @param array $vars 276 | * @param string|null $app 277 | * @param string|null $plugin 278 | * @return Response 279 | */ 280 | function blade_view(mixed $template = null, array $vars = [], ?string $app = null, ?string $plugin = null): Response 281 | { 282 | return new Response(200, [], Blade::render(...template_inputs($template, $vars, $app, $plugin))); 283 | } 284 | } 285 | 286 | if (!function_exists('think_view')) { 287 | /** 288 | * Think view response 289 | * @param mixed $template 290 | * @param array $vars 291 | * @param string|null $app 292 | * @param string|null $plugin 293 | * @return Response 294 | */ 295 | function think_view(mixed $template = null, array $vars = [], ?string $app = null, ?string $plugin = null): Response 296 | { 297 | return new Response(200, [], ThinkPHP::render(...template_inputs($template, $vars, $app, $plugin))); 298 | } 299 | } 300 | 301 | if (!function_exists('twig_view')) { 302 | /** 303 | * Twig view response 304 | * @param mixed $template 305 | * @param array $vars 306 | * @param string|null $app 307 | * @param string|null $plugin 308 | * @return Response 309 | */ 310 | function twig_view(mixed $template = null, array $vars = [], ?string $app = null, ?string $plugin = null): Response 311 | { 312 | return new Response(200, [], Twig::render(...template_inputs($template, $vars, $app, $plugin))); 313 | } 314 | } 315 | 316 | if (!function_exists('request')) { 317 | /** 318 | * Get request 319 | * @return \Webman\Http\Request|Request|null 320 | */ 321 | function request() 322 | { 323 | return App::request(); 324 | } 325 | } 326 | 327 | if (!function_exists('config')) { 328 | /** 329 | * Get config 330 | * @param string|null $key 331 | * @param mixed $default 332 | * @return mixed 333 | */ 334 | function config(?string $key = null, mixed $default = null) 335 | { 336 | return Config::get($key, $default); 337 | } 338 | } 339 | 340 | if (!function_exists('route')) { 341 | /** 342 | * Create url 343 | * @param string $name 344 | * @param ...$parameters 345 | * @return string 346 | */ 347 | function route(string $name, ...$parameters): string 348 | { 349 | $route = Route::getByName($name); 350 | if (!$route) { 351 | return ''; 352 | } 353 | 354 | if (!$parameters) { 355 | return $route->url(); 356 | } 357 | 358 | if (is_array(current($parameters))) { 359 | $parameters = current($parameters); 360 | } 361 | 362 | return $route->url($parameters); 363 | } 364 | } 365 | 366 | if (!function_exists('session')) { 367 | /** 368 | * Session 369 | * @param array|string|null $key 370 | * @param mixed $default 371 | * @return mixed|bool|Session 372 | * @throws Exception 373 | */ 374 | function session(array|string|null $key = null, mixed $default = null): mixed 375 | { 376 | $session = \request()->session(); 377 | if (null === $key) { 378 | return $session; 379 | } 380 | if (is_array($key)) { 381 | $session->put($key); 382 | return null; 383 | } 384 | if (strpos($key, '.')) { 385 | $keyArray = explode('.', $key); 386 | $value = $session->all(); 387 | foreach ($keyArray as $index) { 388 | if (!isset($value[$index])) { 389 | return $default; 390 | } 391 | $value = $value[$index]; 392 | } 393 | return $value; 394 | } 395 | return $session->get($key, $default); 396 | } 397 | } 398 | 399 | if (!function_exists('trans')) { 400 | /** 401 | * Translation 402 | * @param string $id 403 | * @param array $parameters 404 | * @param string|null $domain 405 | * @param string|null $locale 406 | * @return string 407 | */ 408 | function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string 409 | { 410 | $res = Translation::trans($id, $parameters, $domain, $locale); 411 | return $res === '' ? $id : $res; 412 | } 413 | } 414 | 415 | if (!function_exists('locale')) { 416 | /** 417 | * Locale 418 | * @param string|null $locale 419 | * @return string 420 | */ 421 | function locale(?string $locale = null): string 422 | { 423 | if (!$locale) { 424 | return Translation::getLocale(); 425 | } 426 | Translation::setLocale($locale); 427 | return $locale; 428 | } 429 | } 430 | 431 | if (!function_exists('not_found')) { 432 | /** 433 | * 404 not found 434 | * @return Response 435 | */ 436 | function not_found(): Response 437 | { 438 | return new Response(404, [], file_get_contents(public_path() . '/404.html')); 439 | } 440 | } 441 | 442 | if (!function_exists('copy_dir')) { 443 | /** 444 | * Copy dir 445 | * @param string $source 446 | * @param string $dest 447 | * @param bool $overwrite 448 | * @return void 449 | */ 450 | function copy_dir(string $source, string $dest, bool $overwrite = false) 451 | { 452 | if (is_dir($source)) { 453 | if (!is_dir($dest)) { 454 | mkdir($dest); 455 | } 456 | $files = scandir($source); 457 | foreach ($files as $file) { 458 | if ($file !== "." && $file !== "..") { 459 | copy_dir("$source/$file", "$dest/$file", $overwrite); 460 | } 461 | } 462 | } else if (file_exists($source) && ($overwrite || !file_exists($dest))) { 463 | copy($source, $dest); 464 | } 465 | } 466 | } 467 | 468 | if (!function_exists('remove_dir')) { 469 | /** 470 | * Remove dir 471 | * @param string $dir 472 | * @return bool 473 | */ 474 | function remove_dir(string $dir): bool 475 | { 476 | if (is_link($dir) || is_file($dir)) { 477 | return unlink($dir); 478 | } 479 | $files = array_diff(scandir($dir), array('.', '..')); 480 | foreach ($files as $file) { 481 | (is_dir("$dir/$file") && !is_link($dir)) ? remove_dir("$dir/$file") : unlink("$dir/$file"); 482 | } 483 | return rmdir($dir); 484 | } 485 | } 486 | 487 | if (!function_exists('worker_bind')) { 488 | /** 489 | * Bind worker 490 | * @param $worker 491 | * @param $class 492 | */ 493 | function worker_bind($worker, $class) 494 | { 495 | $callbackMap = [ 496 | 'onConnect', 497 | 'onMessage', 498 | 'onClose', 499 | 'onError', 500 | 'onBufferFull', 501 | 'onBufferDrain', 502 | 'onWorkerStop', 503 | 'onWebSocketConnect', 504 | 'onWorkerReload' 505 | ]; 506 | foreach ($callbackMap as $name) { 507 | if (method_exists($class, $name)) { 508 | $worker->$name = [$class, $name]; 509 | } 510 | } 511 | if (method_exists($class, 'onWorkerStart')) { 512 | call_user_func([$class, 'onWorkerStart'], $worker); 513 | } 514 | } 515 | } 516 | 517 | if (!function_exists('worker_start')) { 518 | /** 519 | * Start worker 520 | * @param $processName 521 | * @param $config 522 | * @return void 523 | */ 524 | function worker_start($processName, $config) 525 | { 526 | if (isset($config['enable']) && !$config['enable']) { 527 | return; 528 | } 529 | // feat:custom worker class [default: Workerman\Worker] 530 | $class = is_a($class = $config['workerClass'] ?? '', Worker::class, true) ? $class : Worker::class; 531 | $worker = new $class($config['listen'] ?? null, $config['context'] ?? []); 532 | $properties = [ 533 | 'count', 534 | 'user', 535 | 'group', 536 | 'reloadable', 537 | 'reusePort', 538 | 'transport', 539 | 'protocol', 540 | 'eventLoop', 541 | ]; 542 | $worker->name = $processName; 543 | foreach ($properties as $property) { 544 | if (isset($config[$property])) { 545 | $worker->$property = $config[$property]; 546 | } 547 | } 548 | 549 | $worker->onWorkerStart = function ($worker) use ($config) { 550 | require_once base_path('/support/bootstrap.php'); 551 | if (isset($config['handler'])) { 552 | if (!class_exists($config['handler'])) { 553 | echo "process error: class {$config['handler']} not exists\r\n"; 554 | return; 555 | } 556 | 557 | $instance = Container::make($config['handler'], $config['constructor'] ?? []); 558 | worker_bind($worker, $instance); 559 | } 560 | }; 561 | } 562 | } 563 | 564 | if (!function_exists('get_realpath')) { 565 | /** 566 | * Get realpath 567 | * @param string $filePath 568 | * @return string 569 | */ 570 | function get_realpath(string $filePath): string 571 | { 572 | if (strpos($filePath, 'phar://') === 0) { 573 | return $filePath; 574 | } else { 575 | return realpath($filePath); 576 | } 577 | } 578 | } 579 | 580 | if (!function_exists('is_phar')) { 581 | /** 582 | * Is phar 583 | * @return bool 584 | */ 585 | function is_phar(): bool 586 | { 587 | return class_exists(Phar::class, false) && Phar::running(); 588 | } 589 | } 590 | 591 | if (!function_exists('template_inputs')) { 592 | /** 593 | * Get template vars 594 | * @param mixed $template 595 | * @param array $vars 596 | * @param string|null $app 597 | * @param string|null $plugin 598 | * @return array 599 | */ 600 | function template_inputs(mixed $template, array $vars, ?string $app, ?string $plugin): array 601 | { 602 | $request = \request(); 603 | $plugin = $plugin === null ? ($request->plugin ?? '') : $plugin; 604 | if (is_array($template)) { 605 | $vars = $template; 606 | $template = null; 607 | } 608 | if ($template === null && $controller = $request->controller) { 609 | $controllerSuffix = config($plugin ? "plugin.$plugin.app.controller_suffix" : "app.controller_suffix", ''); 610 | $controllerName = $controllerSuffix !== '' ? substr($controller, 0, -strlen($controllerSuffix)) : $controller; 611 | $path = str_replace(['controller', 'Controller', '\\'], ['view', 'view', '/'], $controllerName); 612 | $path = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $path)); 613 | $action = $request->action; 614 | $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 615 | foreach ($backtrace as $backtraceItem) { 616 | if (!isset($backtraceItem['class']) || !isset($backtraceItem['function'])) { 617 | continue; 618 | } 619 | if ($backtraceItem['class'] === App::class) { 620 | break; 621 | } 622 | if (preg_match('/\\\\controller\\\\/i', $backtraceItem['class'])) { 623 | $action = $backtraceItem['function']; 624 | break; 625 | } 626 | } 627 | $actionFileBaseName = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $action)); 628 | $template = "/$path/$actionFileBaseName"; 629 | } 630 | return [$template, $vars, $app, $plugin]; 631 | } 632 | } 633 | 634 | if (!function_exists('cpu_count')) { 635 | /** 636 | * Get cpu count 637 | * @return int 638 | */ 639 | function cpu_count(): int 640 | { 641 | // Windows does not support the number of processes setting. 642 | if (DIRECTORY_SEPARATOR === '\\') { 643 | return 1; 644 | } 645 | $count = 4; 646 | if (is_callable('shell_exec')) { 647 | if (strtolower(PHP_OS) === 'darwin') { 648 | $count = (int)shell_exec('sysctl -n machdep.cpu.core_count'); 649 | } else { 650 | try { 651 | $count = (int)shell_exec('nproc'); 652 | } catch (\Throwable $ex) { 653 | // Do nothing 654 | } 655 | } 656 | } 657 | return $count > 0 ? $count : 4; 658 | } 659 | } 660 | 661 | if (!function_exists('input')) { 662 | /** 663 | * Get request parameters, if no parameter name is passed, an array of all values is returned, default values is supported 664 | * @param string|null $param param's name 665 | * @param mixed $default default value 666 | * @return mixed 667 | */ 668 | function input(?string $param = null, mixed $default = null): mixed 669 | { 670 | return is_null($param) ? request()->all() : request()->input($param, $default); 671 | } 672 | } 673 | -------------------------------------------------------------------------------- /src/App.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright walkor 12 | * @link http://www.workerman.net/ 13 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | 16 | namespace Webman; 17 | 18 | use ArrayObject; 19 | use Closure; 20 | use Exception; 21 | use FastRoute\Dispatcher; 22 | use Illuminate\Database\Eloquent\Model; 23 | use Psr\Log\LoggerInterface; 24 | use ReflectionEnum; 25 | use support\exception\InputValueException; 26 | use support\exception\PageNotFoundException; 27 | use think\Model as ThinkModel; 28 | use InvalidArgumentException; 29 | use Psr\Container\ContainerExceptionInterface; 30 | use Psr\Container\ContainerInterface; 31 | use Psr\Container\NotFoundExceptionInterface; 32 | use ReflectionClass; 33 | use ReflectionException; 34 | use ReflectionFunction; 35 | use ReflectionFunctionAbstract; 36 | use ReflectionMethod; 37 | use support\exception\BusinessException; 38 | use support\exception\MissingInputException; 39 | use support\exception\RecordNotFoundException; 40 | use support\exception\InputTypeException; 41 | use Throwable; 42 | use Webman\Context; 43 | use Webman\Exception\ExceptionHandler; 44 | use Webman\Exception\ExceptionHandlerInterface; 45 | use Webman\Http\Request; 46 | use Webman\Http\Response; 47 | use Webman\Route\Route as RouteObject; 48 | use Workerman\Connection\TcpConnection; 49 | use Workerman\Protocols\Http; 50 | use Workerman\Worker; 51 | use function array_merge; 52 | use function array_pop; 53 | use function array_reduce; 54 | use function array_splice; 55 | use function array_values; 56 | use function class_exists; 57 | use function clearstatcache; 58 | use function count; 59 | use function current; 60 | use function end; 61 | use function explode; 62 | use function get_class_methods; 63 | use function gettype; 64 | use function implode; 65 | use function is_a; 66 | use function is_array; 67 | use function is_dir; 68 | use function is_file; 69 | use function is_numeric; 70 | use function is_string; 71 | use function key; 72 | use function method_exists; 73 | use function ob_get_clean; 74 | use function ob_start; 75 | use function pathinfo; 76 | use function scandir; 77 | use function str_replace; 78 | use function strpos; 79 | use function strtolower; 80 | use function substr; 81 | use function trim; 82 | 83 | /** 84 | * Class App 85 | * @package Webman 86 | */ 87 | class App 88 | { 89 | 90 | /** 91 | * @var callable[] 92 | */ 93 | protected static $callbacks = []; 94 | 95 | /** 96 | * @var Worker 97 | */ 98 | protected static $worker = null; 99 | 100 | /** 101 | * @var ?LoggerInterface 102 | */ 103 | protected static ?LoggerInterface $logger = null; 104 | 105 | /** 106 | * @var string 107 | */ 108 | protected static $appPath = ''; 109 | 110 | /** 111 | * @var string 112 | */ 113 | protected static $publicPath = ''; 114 | 115 | /** 116 | * @var string 117 | */ 118 | protected static $requestClass = ''; 119 | 120 | /** 121 | * App constructor. 122 | * @param string $requestClass 123 | * @param LoggerInterface $logger 124 | * @param string $appPath 125 | * @param string $publicPath 126 | */ 127 | public function __construct(string $requestClass, LoggerInterface $logger, string $appPath, string $publicPath) 128 | { 129 | static::$requestClass = $requestClass; 130 | static::$logger = $logger; 131 | static::$publicPath = $publicPath; 132 | static::$appPath = $appPath; 133 | } 134 | 135 | /** 136 | * OnMessage. 137 | * @param TcpConnection|mixed $connection 138 | * @param Request|mixed $request 139 | * @return null 140 | * @throws Throwable 141 | */ 142 | public function onMessage($connection, $request) 143 | { 144 | try { 145 | Context::reset(new ArrayObject([Request::class => $request])); 146 | $path = $request->path(); 147 | $key = $request->method() . $path; 148 | if (isset(static::$callbacks[$key])) { 149 | [$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$callbacks[$key]; 150 | static::send($connection, $callback($request), $request); 151 | return null; 152 | } 153 | 154 | $status = 200; 155 | if ( 156 | static::unsafeUri($connection, $path, $request) || 157 | static::findFile($connection, $path, $key, $request) || 158 | static::findRoute($connection, $path, $key, $request, $status) 159 | ) { 160 | return null; 161 | } 162 | 163 | $controllerAndAction = static::parseControllerAction($path); 164 | $plugin = $controllerAndAction['plugin'] ?? static::getPluginByPath($path); 165 | if (!$controllerAndAction || Route::isDefaultRouteDisabled($plugin, $controllerAndAction['app'] ?: '*') || 166 | Route::isDefaultRouteDisabled($controllerAndAction['controller']) || 167 | Route::isDefaultRouteDisabled([$controllerAndAction['controller'], $controllerAndAction['action']])) { 168 | $request->plugin = $plugin; 169 | $callback = static::getFallback($plugin, $status); 170 | $request->app = $request->controller = $request->action = ''; 171 | static::send($connection, $callback($request), $request); 172 | return null; 173 | } 174 | $app = $controllerAndAction['app']; 175 | $controller = $controllerAndAction['controller']; 176 | $action = $controllerAndAction['action']; 177 | $callback = static::getCallback($plugin, $app, [$controller, $action]); 178 | static::collectCallbacks($key, [$callback, $plugin, $app, $controller, $action, null]); 179 | [$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$callbacks[$key]; 180 | static::send($connection, $callback($request), $request); 181 | } catch (Throwable $e) { 182 | static::send($connection, static::exceptionResponse($e, $request), $request); 183 | } 184 | return null; 185 | } 186 | 187 | /** 188 | * OnWorkerStart. 189 | * @param $worker 190 | * @return void 191 | */ 192 | public function onWorkerStart($worker) 193 | { 194 | static::$worker = $worker; 195 | Http::requestClass(static::$requestClass); 196 | } 197 | 198 | /** 199 | * CollectCallbacks. 200 | * @param string $key 201 | * @param array $data 202 | * @return void 203 | */ 204 | protected static function collectCallbacks(string $key, array $data) 205 | { 206 | static::$callbacks[$key] = $data; 207 | if (count(static::$callbacks) >= 1024) { 208 | unset(static::$callbacks[key(static::$callbacks)]); 209 | } 210 | } 211 | 212 | /** 213 | * UnsafeUri. 214 | * @param TcpConnection $connection 215 | * @param string $path 216 | * @param $request 217 | * @return bool 218 | */ 219 | protected static function unsafeUri(TcpConnection $connection, string $path, $request): bool 220 | { 221 | if ( 222 | !$path || 223 | $path[0] !== '/' || 224 | strpos($path, '/../') !== false || 225 | substr($path, -3) === '/..' || 226 | strpos($path, "\\") !== false || 227 | strpos($path, "\0") !== false 228 | ) { 229 | $callback = static::getFallback('', 400); 230 | $request->plugin = $request->app = $request->controller = $request->action = ''; 231 | static::send($connection, $callback($request, 400), $request); 232 | return true; 233 | } 234 | return false; 235 | } 236 | 237 | /** 238 | * GetFallback. 239 | * @param string $plugin 240 | * @param int $status 241 | * @return Closure 242 | * @throws ContainerExceptionInterface 243 | * @throws NotFoundExceptionInterface 244 | * @throws ReflectionException 245 | */ 246 | protected static function getFallback(string $plugin = '', int $status = 404): Closure 247 | { 248 | // When route, controller and action not found, try to use Route::fallback 249 | return Route::getFallback($plugin, $status) ?: function () { 250 | throw new PageNotFoundException(); 251 | }; 252 | } 253 | 254 | /** 255 | * ExceptionResponse. 256 | * @param Throwable $e 257 | * @param $request 258 | * @return Response 259 | */ 260 | protected static function exceptionResponse(Throwable $e, $request): Response 261 | { 262 | try { 263 | $app = $request->app ?: ''; 264 | $plugin = $request->plugin ?: ''; 265 | $exceptionConfig = static::config($plugin, 'exception'); 266 | $appExceptionConfig = static::config("", 'exception'); 267 | if (!isset($exceptionConfig['']) && isset($appExceptionConfig['@'])) { 268 | //如果插件没有配置自己的异常处理器并且配置了全局@异常处理器 则使用全局异常处理器 269 | $defaultException = $appExceptionConfig['@'] ?? ExceptionHandler::class; 270 | } else { 271 | $defaultException = $exceptionConfig[''] ?? ExceptionHandler::class; 272 | } 273 | $exceptionHandlerClass = $exceptionConfig[$app] ?? $defaultException; 274 | 275 | /** @var ExceptionHandlerInterface $exceptionHandler */ 276 | $exceptionHandler = (static::container($plugin) ?? static::container(''))->make($exceptionHandlerClass, [ 277 | 'logger' => static::$logger, 278 | 'debug' => static::config($plugin, 'app.debug') 279 | ]); 280 | $exceptionHandler->report($e); 281 | $response = $exceptionHandler->render($request, $e); 282 | $response->exception($e); 283 | return $response; 284 | } catch (Throwable $e) { 285 | $response = new Response(500, [], static::config($plugin ?? '', 'app.debug', true) ? (string)$e : $e->getMessage()); 286 | $response->exception($e); 287 | return $response; 288 | } 289 | } 290 | 291 | /** 292 | * GetCallback. 293 | * @param string $plugin 294 | * @param string $app 295 | * @param $call 296 | * @param array $args 297 | * @param bool $withGlobalMiddleware 298 | * @param RouteObject|null $route 299 | * @return callable 300 | * @throws ContainerExceptionInterface 301 | * @throws NotFoundExceptionInterface 302 | * @throws ReflectionException 303 | */ 304 | public static function getCallback(string $plugin, string $app, $call, array $args = [], bool $withGlobalMiddleware = true, ?RouteObject $route = null) 305 | { 306 | $isController = is_array($call) && is_string($call[0]); 307 | $middlewares = Middleware::getMiddleware($plugin, $app, $call, $route, $withGlobalMiddleware); 308 | 309 | $container = static::container($plugin) ?? static::container(''); 310 | foreach ($middlewares as $key => $item) { 311 | $middleware = $item[0]; 312 | if (is_string($middleware)) { 313 | $middleware = $container->get($middleware); 314 | } elseif ($middleware instanceof Closure) { 315 | $middleware = call_user_func($middleware, $container); 316 | } 317 | $middlewares[$key][0] = $middleware; 318 | } 319 | 320 | $needInject = static::isNeedInject($call, $args); 321 | $anonymousArgs = array_values($args); 322 | if ($isController) { 323 | $controllerReuse = static::config($plugin, 'app.controller_reuse', true); 324 | if (!$controllerReuse) { 325 | if ($needInject) { 326 | $call = function ($request) use ($call, $plugin, $args, $container) { 327 | $call[0] = $container->make($call[0]); 328 | $reflector = static::getReflector($call); 329 | $args = array_values(static::resolveMethodDependencies($container, $request, array_merge($request->all(), $args), $reflector, static::config($plugin, 'app.debug'))); 330 | return $call(...$args); 331 | }; 332 | $needInject = false; 333 | } else { 334 | $call = function ($request, ...$anonymousArgs) use ($call, $plugin, $container) { 335 | $call[0] = $container->make($call[0]); 336 | return $call($request, ...$anonymousArgs); 337 | }; 338 | } 339 | } else { 340 | $call[0] = $container->get($call[0]); 341 | } 342 | } 343 | 344 | if ($needInject) { 345 | $call = static::resolveInject($plugin, $call, $args); 346 | } 347 | 348 | if ($middlewares) { 349 | $callback = array_reduce($middlewares, function ($carry, $pipe) { 350 | return function ($request) use ($carry, $pipe) { 351 | try { 352 | return $pipe($request, $carry); 353 | } catch (Throwable $e) { 354 | return static::exceptionResponse($e, $request); 355 | } 356 | }; 357 | }, function ($request) use ($call, $anonymousArgs) { 358 | try { 359 | $response = $call($request, ...$anonymousArgs); 360 | } catch (Throwable $e) { 361 | return static::exceptionResponse($e, $request); 362 | } 363 | if (!$response instanceof Response) { 364 | if (!is_string($response)) { 365 | $response = static::stringify($response); 366 | } 367 | $response = new Response(200, [], $response); 368 | } 369 | return $response; 370 | }); 371 | } else { 372 | if (!$anonymousArgs) { 373 | $callback = $call; 374 | } else { 375 | $callback = function ($request) use ($call, $anonymousArgs) { 376 | return $call($request, ...$anonymousArgs); 377 | }; 378 | } 379 | } 380 | return $callback; 381 | } 382 | 383 | /** 384 | * ResolveInject. 385 | * @param string $plugin 386 | * @param array|Closure $call 387 | * @param $args 388 | * @return Closure 389 | * @see Dependency injection through reflection information 390 | */ 391 | protected static function resolveInject(string $plugin, $call, $args): Closure 392 | { 393 | return function (Request $request) use ($plugin, $call, $args) { 394 | $reflector = static::getReflector($call); 395 | $args = array_values(static::resolveMethodDependencies(static::container($plugin), $request, 396 | array_merge($request->all(), $args), $reflector, static::config($plugin, 'app.debug'))); 397 | return $call(...$args); 398 | }; 399 | } 400 | 401 | /** 402 | * Check whether inject is required. 403 | * @param $call 404 | * @param array $args 405 | * @return bool 406 | * @throws ReflectionException 407 | */ 408 | protected static function isNeedInject($call, array &$args): bool 409 | { 410 | if (is_array($call) && !method_exists($call[0], $call[1])) { 411 | return false; 412 | } 413 | $reflector = static::getReflector($call); 414 | $reflectionParameters = $reflector->getParameters(); 415 | if (!$reflectionParameters) { 416 | return false; 417 | } 418 | $firstParameter = current($reflectionParameters); 419 | unset($reflectionParameters[key($reflectionParameters)]); 420 | $adaptersList = ['int', 'string', 'bool', 'array', 'object', 'float', 'mixed', 'resource']; 421 | $keys = []; 422 | $needInject = false; 423 | foreach ($reflectionParameters as $parameter) { 424 | $parameterName = $parameter->name; 425 | $keys[] = $parameterName; 426 | if ($parameter->hasType()) { 427 | $typeName = $parameter->getType()->getName(); 428 | if (!in_array($typeName, $adaptersList)) { 429 | $needInject = true; 430 | continue; 431 | } 432 | if (!array_key_exists($parameterName, $args)) { 433 | $needInject = true; 434 | continue; 435 | } 436 | switch ($typeName) { 437 | case 'int': 438 | case 'float': 439 | if (!is_numeric($args[$parameterName])) { 440 | return true; 441 | } 442 | $args[$parameterName] = $typeName === 'int' ? (int)$args[$parameterName]: (float)$args[$parameterName]; 443 | break; 444 | case 'bool': 445 | $args[$parameterName] = (bool)$args[$parameterName]; 446 | break; 447 | case 'array': 448 | case 'object': 449 | if (!is_array($args[$parameterName])) { 450 | return true; 451 | } 452 | $args[$parameterName] = $typeName === 'array' ? $args[$parameterName] : (object)$args[$parameterName]; 453 | break; 454 | case 'string': 455 | case 'mixed': 456 | case 'resource': 457 | break; 458 | } 459 | } 460 | } 461 | if (array_keys($args) !== $keys) { 462 | return true; 463 | } 464 | if (!$firstParameter->hasType()) { 465 | return $firstParameter->getName() !== 'request'; 466 | } 467 | if (!is_a(static::$requestClass, $firstParameter->getType()->getName(), true)) { 468 | return true; 469 | } 470 | 471 | return $needInject; 472 | } 473 | 474 | /** 475 | * Get reflector. 476 | * @param $call 477 | * @return ReflectionFunction|ReflectionMethod 478 | * @throws ReflectionException 479 | */ 480 | protected static function getReflector($call) 481 | { 482 | if ($call instanceof Closure || is_string($call)) { 483 | return new ReflectionFunction($call); 484 | } 485 | return new ReflectionMethod($call[0], $call[1]); 486 | } 487 | 488 | /** 489 | * Return dependent parameters 490 | * @param ContainerInterface $container 491 | * @param Request $request 492 | * @param array $inputs 493 | * @param ReflectionFunctionAbstract $reflector 494 | * @param bool $debug 495 | * @return array 496 | * @throws ReflectionException 497 | */ 498 | protected static function resolveMethodDependencies(ContainerInterface $container, Request $request, array $inputs, ReflectionFunctionAbstract $reflector, bool $debug): array 499 | { 500 | $parameters = []; 501 | foreach ($reflector->getParameters() as $parameter) { 502 | $parameterName = $parameter->name; 503 | $type = $parameter->getType(); 504 | $typeName = $type?->getName(); 505 | 506 | if ($typeName && is_a($request, $typeName)) { 507 | $parameters[$parameterName] = $request; 508 | continue; 509 | } 510 | 511 | if (!array_key_exists($parameterName, $inputs)) { 512 | if (!$parameter->isDefaultValueAvailable()) { 513 | if (!$typeName || (!class_exists($typeName) && !enum_exists($typeName)) || enum_exists($typeName)) { 514 | throw (new MissingInputException())->data([ 515 | 'parameter' => $parameterName, 516 | ])->debug($debug); 517 | } 518 | } else { 519 | $parameters[$parameterName] = $parameter->getDefaultValue(); 520 | continue; 521 | } 522 | } 523 | 524 | $parameterValue = $inputs[$parameterName] ?? null; 525 | 526 | switch ($typeName) { 527 | case 'int': 528 | case 'float': 529 | if (!is_numeric($parameterValue)) { 530 | throw (new InputTypeException())->data([ 531 | 'parameter' => $parameterName, 532 | 'exceptType' => $typeName, 533 | 'actualType' => gettype($parameterValue), 534 | ])->debug($debug); 535 | } 536 | $parameters[$parameterName] = $typeName === 'float' ? (float)$parameterValue : (int)$parameterValue; 537 | break; 538 | case 'bool': 539 | $parameters[$parameterName] = (bool)$parameterValue; 540 | break; 541 | case 'array': 542 | case 'object': 543 | if (!is_array($parameterValue)) { 544 | throw (new InputTypeException())->data([ 545 | 'parameter' => $parameterName, 546 | 'exceptType' => $typeName, 547 | 'actualType' => gettype($parameterValue), 548 | ])->debug($debug); 549 | } 550 | $parameters[$parameterName] = $typeName === 'object' ? (object)$parameterValue : $parameterValue; 551 | break; 552 | case 'string': 553 | case 'mixed': 554 | case 'resource': 555 | case null: 556 | $parameters[$parameterName] = $parameterValue; 557 | break; 558 | default: 559 | $subInputs = is_array($parameterValue) ? $parameterValue : []; 560 | if (is_a($typeName, Model::class, true)) { 561 | $parameters[$parameterName] = $container->make($typeName, [ 562 | 'attributes' => $subInputs 563 | ]); 564 | break; 565 | } 566 | if (is_a($typeName, ThinkModel::class, true)) { 567 | $parameters[$parameterName] = $container->make($typeName, [ 568 | 'data' => $subInputs 569 | ]); 570 | break; 571 | } 572 | if (enum_exists($typeName)) { 573 | $reflection = new ReflectionEnum($typeName); 574 | if ($reflection->hasCase($parameterValue)) { 575 | $parameters[$parameterName] = $reflection->getCase($parameterValue)->getValue(); 576 | break; 577 | } elseif ($reflection->isBacked()) { 578 | foreach ($reflection->getCases() as $case) { 579 | if ($case->getValue()->value == $parameterValue) { 580 | $parameters[$parameterName] = $case->getValue(); 581 | break; 582 | } 583 | } 584 | } 585 | if (!array_key_exists($parameterName, $parameters)) { 586 | throw (new InputValueException())->data([ 587 | 'parameter' => $parameterName, 588 | 'enum' => $typeName 589 | ])->debug($debug); 590 | } 591 | break; 592 | } 593 | if (is_array($subInputs) && $constructor = (new ReflectionClass($typeName))->getConstructor()) { 594 | $parameters[$parameterName] = $container->make($typeName, static::resolveMethodDependencies($container, $request, $subInputs, $constructor, $debug)); 595 | } else { 596 | $parameters[$parameterName] = $container->make($typeName); 597 | } 598 | break; 599 | } 600 | } 601 | return $parameters; 602 | } 603 | 604 | /** 605 | * Container. 606 | * @param string $plugin 607 | * @return ContainerInterface 608 | */ 609 | public static function container(string $plugin = '') 610 | { 611 | return static::config($plugin, 'container'); 612 | } 613 | 614 | /** 615 | * Get request. 616 | * @return Request|\support\Request 617 | */ 618 | public static function request() 619 | { 620 | return Context::get(Request::class); 621 | } 622 | 623 | /** 624 | * Get worker. 625 | * @return Worker 626 | */ 627 | public static function worker(): ?Worker 628 | { 629 | return static::$worker; 630 | } 631 | 632 | /** 633 | * Find Route. 634 | * @param TcpConnection $connection 635 | * @param string $path 636 | * @param string $key 637 | * @param $request 638 | * @param $status 639 | * @return bool 640 | * @throws ContainerExceptionInterface 641 | * @throws NotFoundExceptionInterface 642 | * @throws ReflectionException|Throwable 643 | */ 644 | protected static function findRoute(TcpConnection $connection, string $path, string $key, $request, &$status): bool 645 | { 646 | $routeInfo = Route::dispatch($request->method(), $path); 647 | if ($routeInfo[0] === Dispatcher::FOUND) { 648 | $status = 200; 649 | $routeInfo[0] = 'route'; 650 | $callback = $routeInfo[1]['callback']; 651 | $route = clone $routeInfo[1]['route']; 652 | $app = $controller = $action = ''; 653 | $args = !empty($routeInfo[2]) ? $routeInfo[2] : []; 654 | if ($args) { 655 | $route->setParams($args); 656 | } 657 | $args = array_merge($route->param(), $args); 658 | if (is_array($callback)) { 659 | $controller = $callback[0]; 660 | $plugin = static::getPluginByClass($controller); 661 | $app = static::getAppByController($controller); 662 | $action = static::getRealMethod($controller, $callback[1]) ?? ''; 663 | } else { 664 | $plugin = static::getPluginByPath($path); 665 | } 666 | $callback = static::getCallback($plugin, $app, $callback, $args, true, $route); 667 | static::collectCallbacks($key, [$callback, $plugin, $app, $controller ?: '', $action, $route]); 668 | [$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$callbacks[$key]; 669 | static::send($connection, $callback($request), $request); 670 | return true; 671 | } 672 | $status = $routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED ? 405 : 404; 673 | return false; 674 | } 675 | 676 | /** 677 | * Find File. 678 | * @param TcpConnection $connection 679 | * @param string $path 680 | * @param string $key 681 | * @param $request 682 | * @return bool 683 | * @throws ContainerExceptionInterface 684 | * @throws NotFoundExceptionInterface 685 | * @throws ReflectionException 686 | */ 687 | protected static function findFile(TcpConnection $connection, string $path, string $key, $request): bool 688 | { 689 | if (preg_match('/%[0-9a-f]{2}/i', $path)) { 690 | $path = urldecode($path); 691 | if (static::unsafeUri($connection, $path, $request)) { 692 | return true; 693 | } 694 | } 695 | 696 | $pathExplodes = explode('/', trim($path, '/')); 697 | $plugin = ''; 698 | if (isset($pathExplodes[1]) && $pathExplodes[0] === 'app') { 699 | $plugin = $pathExplodes[1]; 700 | $publicDir = static::config($plugin, 'app.public_path') ?: BASE_PATH . "/plugin/$pathExplodes[1]/public"; 701 | $path = substr($path, strlen("/app/$pathExplodes[1]/")); 702 | } else { 703 | $publicDir = static::$publicPath; 704 | } 705 | $file = "$publicDir/$path"; 706 | if (!is_file($file)) { 707 | return false; 708 | } 709 | 710 | if (pathinfo($file, PATHINFO_EXTENSION) === 'php') { 711 | if (!static::config($plugin, 'app.support_php_files', false)) { 712 | return false; 713 | } 714 | static::collectCallbacks($key, [function () use ($file) { 715 | return static::execPhpFile($file); 716 | }, '', '', '', '', null]); 717 | [, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$callbacks[$key]; 718 | static::send($connection, static::execPhpFile($file), $request); 719 | return true; 720 | } 721 | 722 | if (!static::config($plugin, 'static.enable', false)) { 723 | return false; 724 | } 725 | 726 | static::collectCallbacks($key, [static::getCallback($plugin, '__static__', function ($request) use ($file, $plugin) { 727 | clearstatcache(true, $file); 728 | if (!is_file($file)) { 729 | $callback = static::getFallback($plugin); 730 | return $callback($request); 731 | } 732 | return (new Response())->file($file); 733 | }, [], false), '', '', '', '', null]); 734 | [$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$callbacks[$key]; 735 | static::send($connection, $callback($request), $request); 736 | return true; 737 | } 738 | 739 | /** 740 | * Send. 741 | * @param TcpConnection|mixed $connection 742 | * @param mixed|Response $response 743 | * @param Request|mixed $request 744 | * @return void 745 | */ 746 | protected static function send($connection, $response, $request) 747 | { 748 | Context::destroy(); 749 | // Remove the reference of request to session. 750 | unset($request->context['session']); 751 | $keepAlive = $request->header('connection'); 752 | if (($keepAlive === null && $request->protocolVersion() === '1.1') 753 | || $keepAlive === 'keep-alive' || $keepAlive === 'Keep-Alive' 754 | || (is_a($response, Response::class) && $response->getHeader('Transfer-Encoding') === 'chunked') 755 | ) { 756 | $connection->send($response); 757 | return; 758 | } 759 | $connection->close($response); 760 | } 761 | 762 | /** 763 | * ParseControllerAction. 764 | * @param string $path 765 | * @return array|false 766 | * @throws ReflectionException 767 | */ 768 | protected static function parseControllerAction(string $path) 769 | { 770 | $path = str_replace(['-', '//'], ['', '/'], $path); 771 | static $cache = []; 772 | if (isset($cache[$path])) { 773 | return $cache[$path]; 774 | } 775 | $pathExplode = explode('/', trim($path, '/')); 776 | $isPlugin = isset($pathExplode[1]) && $pathExplode[0] === 'app'; 777 | $configPrefix = $isPlugin ? "plugin.$pathExplode[1]." : ''; 778 | $pathPrefix = $isPlugin ? "/app/$pathExplode[1]" : ''; 779 | $classPrefix = $isPlugin ? "plugin\\$pathExplode[1]" : ''; 780 | $suffix = Config::get("{$configPrefix}app.controller_suffix", ''); 781 | $relativePath = trim(substr($path, strlen($pathPrefix)), '/'); 782 | $pathExplode = $relativePath ? explode('/', $relativePath) : []; 783 | 784 | $action = 'index'; 785 | if (!$controllerAction = static::guessControllerAction($pathExplode, $action, $suffix, $classPrefix)) { 786 | if (count($pathExplode) <= 1) { 787 | return false; 788 | } 789 | $action = end($pathExplode); 790 | unset($pathExplode[count($pathExplode) - 1]); 791 | $controllerAction = static::guessControllerAction($pathExplode, $action, $suffix, $classPrefix); 792 | } 793 | if ($controllerAction && !isset($path[256])) { 794 | $cache[$path] = $controllerAction; 795 | if (count($cache) > 1024) { 796 | unset($cache[key($cache)]); 797 | } 798 | } 799 | return $controllerAction; 800 | } 801 | 802 | /** 803 | * GuessControllerAction. 804 | * @param $pathExplode 805 | * @param $action 806 | * @param $suffix 807 | * @param $classPrefix 808 | * @return array|false 809 | * @throws ReflectionException 810 | */ 811 | protected static function guessControllerAction($pathExplode, $action, $suffix, $classPrefix) 812 | { 813 | $map[] = trim("$classPrefix\\app\\controller\\" . implode('\\', $pathExplode), '\\'); 814 | foreach ($pathExplode as $index => $section) { 815 | $tmp = $pathExplode; 816 | array_splice($tmp, $index, 1, [$section, 'controller']); 817 | $map[] = trim("$classPrefix\\" . implode('\\', array_merge(['app'], $tmp)), '\\'); 818 | } 819 | foreach ($map as $item) { 820 | $map[] = $item . '\\index'; 821 | } 822 | foreach ($map as $controllerClass) { 823 | // Remove xx\xx\controller 824 | if (substr($controllerClass, -11) === '\\controller') { 825 | continue; 826 | } 827 | $controllerClass .= $suffix; 828 | if ($controllerAction = static::getControllerAction($controllerClass, $action)) { 829 | return $controllerAction; 830 | } 831 | } 832 | return false; 833 | } 834 | 835 | /** 836 | * GetControllerAction. 837 | * @param string $controllerClass 838 | * @param string $action 839 | * @return array|false 840 | * @throws ReflectionException 841 | */ 842 | protected static function getControllerAction(string $controllerClass, string $action) 843 | { 844 | // Disable calling magic methods 845 | if (strpos($action, '__') === 0) { 846 | return false; 847 | } 848 | if (($controllerClass = static::getController($controllerClass)) && ($action = static::getAction($controllerClass, $action))) { 849 | return [ 850 | 'plugin' => static::getPluginByClass($controllerClass), 851 | 'app' => static::getAppByController($controllerClass), 852 | 'controller' => $controllerClass, 853 | 'action' => $action 854 | ]; 855 | } 856 | return false; 857 | } 858 | 859 | /** 860 | * GetController. 861 | * @param string $controllerClass 862 | * @return string|false 863 | * @throws ReflectionException 864 | */ 865 | protected static function getController(string $controllerClass) 866 | { 867 | if (class_exists($controllerClass)) { 868 | return (new ReflectionClass($controllerClass))->name; 869 | } 870 | $explodes = explode('\\', strtolower(ltrim($controllerClass, '\\'))); 871 | $basePath = $explodes[0] === 'plugin' ? BASE_PATH . '/plugin' : static::$appPath; 872 | unset($explodes[0]); 873 | $fileName = array_pop($explodes) . '.php'; 874 | $found = true; 875 | foreach ($explodes as $pathSection) { 876 | if (!$found) { 877 | break; 878 | } 879 | $dirs = Util::scanDir($basePath, false); 880 | $found = false; 881 | foreach ($dirs as $name) { 882 | $path = "$basePath/$name"; 883 | 884 | if (is_dir($path) && strtolower($name) === $pathSection) { 885 | $basePath = $path; 886 | $found = true; 887 | break; 888 | } 889 | } 890 | } 891 | if (!$found) { 892 | return false; 893 | } 894 | foreach (scandir($basePath) ?: [] as $name) { 895 | if (strtolower($name) === $fileName) { 896 | require_once "$basePath/$name"; 897 | if (class_exists($controllerClass, false)) { 898 | return (new ReflectionClass($controllerClass))->name; 899 | } 900 | } 901 | } 902 | return false; 903 | } 904 | 905 | /** 906 | * GetAction. 907 | * @param string $controllerClass 908 | * @param string $action 909 | * @return string|false 910 | */ 911 | protected static function getAction(string $controllerClass, string $action) 912 | { 913 | $methods = get_class_methods($controllerClass); 914 | $lowerAction = strtolower($action); 915 | $found = false; 916 | foreach ($methods as $candidate) { 917 | if (strtolower($candidate) === $lowerAction) { 918 | $action = $candidate; 919 | $found = true; 920 | break; 921 | } 922 | } 923 | if ($found) { 924 | return $action; 925 | } 926 | // Action is not public method 927 | if (method_exists($controllerClass, $action)) { 928 | return false; 929 | } 930 | if (method_exists($controllerClass, '__call')) { 931 | return $action; 932 | } 933 | return false; 934 | } 935 | 936 | /** 937 | * GetPluginByClass. 938 | * @param string $controllerClass 939 | * @return mixed|string 940 | */ 941 | public static function getPluginByClass(string $controllerClass) 942 | { 943 | $controllerClass = trim($controllerClass, '\\'); 944 | $tmp = explode('\\', $controllerClass, 3); 945 | if ($tmp[0] !== 'plugin') { 946 | return ''; 947 | } 948 | return $tmp[1] ?? ''; 949 | } 950 | 951 | /** 952 | * GetPluginByPath. 953 | * @param string $path 954 | * @return mixed|string 955 | */ 956 | public static function getPluginByPath(string $path) 957 | { 958 | $path = trim($path, '/'); 959 | $tmp = explode('/', $path, 3); 960 | if ($tmp[0] !== 'app') { 961 | return ''; 962 | } 963 | $plugin = $tmp[1] ?? ''; 964 | if ($plugin && !static::config('', "plugin.$plugin.app")) { 965 | return ''; 966 | } 967 | return $plugin; 968 | } 969 | 970 | /** 971 | * GetAppByController. 972 | * @param string $controllerClass 973 | * @return mixed|string 974 | */ 975 | protected static function getAppByController(string $controllerClass) 976 | { 977 | $controllerClass = trim($controllerClass, '\\'); 978 | $tmp = explode('\\', $controllerClass, 5); 979 | $pos = $tmp[0] === 'plugin' ? 3 : 1; 980 | if (!isset($tmp[$pos])) { 981 | return ''; 982 | } 983 | return strtolower($tmp[$pos]) === 'controller' ? '' : $tmp[$pos]; 984 | } 985 | 986 | /** 987 | * ExecPhpFile. 988 | * @param string $file 989 | * @return false|string 990 | */ 991 | public static function execPhpFile(string $file) 992 | { 993 | ob_start(); 994 | // Try to include php file. 995 | try { 996 | include $file; 997 | } catch (Exception $e) { 998 | echo $e; 999 | } 1000 | return ob_get_clean(); 1001 | } 1002 | 1003 | /** 1004 | * GetRealMethod. 1005 | * @param string $class 1006 | * @param string $method 1007 | * @return string 1008 | */ 1009 | protected static function getRealMethod(string $class, string $method): string 1010 | { 1011 | $method = strtolower($method); 1012 | $methods = get_class_methods($class); 1013 | foreach ($methods as $candidate) { 1014 | if (strtolower($candidate) === $method) { 1015 | return $candidate; 1016 | } 1017 | } 1018 | return $method; 1019 | } 1020 | 1021 | /** 1022 | * Config. 1023 | * @param string $plugin 1024 | * @param string $key 1025 | * @param mixed $default 1026 | * @return mixed 1027 | */ 1028 | protected static function config(string $plugin, string $key, mixed $default = null) 1029 | { 1030 | return Config::get($plugin ? "plugin.$plugin.$key" : $key, $default); 1031 | } 1032 | 1033 | 1034 | /** 1035 | * @param mixed $data 1036 | * @return string 1037 | */ 1038 | protected static function stringify($data): string 1039 | { 1040 | $type = gettype($data); 1041 | switch ($type) { 1042 | case 'boolean': 1043 | return $data ? 'true' : 'false'; 1044 | case 'NULL': 1045 | return 'NULL'; 1046 | case 'array': 1047 | return 'Array'; 1048 | case 'object': 1049 | if (!method_exists($data, '__toString')) { 1050 | return 'Object'; 1051 | } 1052 | default: 1053 | return (string)$data; 1054 | } 1055 | } 1056 | } 1057 | --------------------------------------------------------------------------------