├── .gitignore ├── README.md ├── composer.json └── src ├── Application.php ├── Command.php ├── Commands ├── AppPluginCreateCommand.php ├── AppPluginInstallCommand.php ├── AppPluginUninstallCommand.php ├── AppPluginUpdateCommand.php ├── AppPluginZipCommand.php ├── BuildBinCommand.php ├── BuildPharCommand.php ├── ConnectionsCommand.php ├── FixDisbaleFunctionsCommand.php ├── InstallCommand.php ├── MakeBootstrapCommand.php ├── MakeCommandCommand.php ├── MakeControllerCommand.php ├── MakeMiddlewareCommand.php ├── MakeModelCommand.php ├── PluginCreateCommand.php ├── PluginDisableCommand.php ├── PluginEnableCommand.php ├── PluginExportCommand.php ├── PluginInstallCommand.php ├── PluginUninstallCommand.php ├── ReStartCommand.php ├── ReloadCommand.php ├── RouteListCommand.php ├── StartCommand.php ├── StatusCommand.php ├── StopCommand.php └── VersionCommand.php ├── Install.php ├── Util.php ├── config └── plugin │ └── webman │ └── console │ └── app.php └── webman /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | vendor 3 | .idea 4 | .vscode 5 | .phpunit* 6 | composer.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # console 2 | webman console 3 | https://www.workerman.net/plugin/1 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webman/console", 3 | "type": "library", 4 | "keywords": [ 5 | "webman console" 6 | ], 7 | "homepage": "http://www.workerman.net", 8 | "license": "MIT", 9 | "description": "Webman console", 10 | "authors": [ 11 | { 12 | "name": "walkor", 13 | "email": "walkor@workerman.net", 14 | "homepage": "http://www.workerman.net", 15 | "role": "Developer" 16 | } 17 | ], 18 | "support": { 19 | "email": "walkor@workerman.net", 20 | "issues": "https://github.com/webman-php/console/issues", 21 | "forum": "http://www.workerman.net/questions", 22 | "wiki": "http://www.workerman.net/doc/webman", 23 | "source": "https://github.com/webman-php/console" 24 | }, 25 | "require": { 26 | "php": ">=8.1", 27 | "symfony/console": ">=6.0", 28 | "doctrine/inflector": "^2.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Webman\\Console\\" : "src" 33 | } 34 | }, 35 | "require-dev": { 36 | "workerman/webman": "^2.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | load(); 39 | } else { 40 | Dotenv::createMutable(base_path())->load(); 41 | } 42 | } 43 | 44 | Config::reload(config_path(), ['route', 'container']); 45 | 46 | Worker::$onMasterReload = function (){ 47 | if (function_exists('opcache_get_status')) { 48 | if ($status = opcache_get_status()) { 49 | if (isset($status['scripts']) && $scripts = $status['scripts']) { 50 | foreach (array_keys($scripts) as $file) { 51 | opcache_invalidate($file, true); 52 | } 53 | } 54 | } 55 | } 56 | }; 57 | 58 | $config = config('server'); 59 | Worker::$pidFile = $config['pid_file']; 60 | Worker::$stdoutFile = $config['stdout_file']; 61 | Worker::$logFile = $config['log_file']; 62 | Worker::$eventLoopClass = $config['event_loop'] ?? ''; 63 | TcpConnection::$defaultMaxPackageSize = $config['max_package_size'] ?? 10*1024*1024; 64 | if (property_exists(Worker::class, 'statusFile')) { 65 | Worker::$statusFile = $config['status_file'] ?? ''; 66 | } 67 | if (property_exists(Worker::class, 'stopTimeout')) { 68 | Worker::$stopTimeout = $config['stop_timeout'] ?? 2; 69 | } 70 | 71 | if ($config['listen']) { 72 | $worker = new Worker($config['listen'], $config['context']); 73 | $property_map = [ 74 | 'name', 75 | 'count', 76 | 'user', 77 | 'group', 78 | 'reusePort', 79 | 'transport', 80 | 'protocol' 81 | ]; 82 | foreach ($property_map as $property) { 83 | if (isset($config[$property])) { 84 | $worker->$property = $config[$property]; 85 | } 86 | } 87 | 88 | $worker->onWorkerStart = function ($worker) { 89 | require_once base_path() . '/support/bootstrap.php'; 90 | $app = new App($worker, Container::instance(), Log::channel('default'), app_path(), public_path()); 91 | Http::requestClass(config('app.request_class', config('server.request_class', Request::class))); 92 | $worker->onMessage = [$app, 'onMessage']; 93 | }; 94 | } 95 | 96 | // Windows does not support custom processes. 97 | if (\DIRECTORY_SEPARATOR === '/') { 98 | foreach (config('process', []) as $process_name => $config) { 99 | // Remove monitor process. 100 | if (class_exists(\Phar::class, false) && \Phar::running() && 'monitor' === $process_name) { 101 | continue; 102 | } 103 | worker_start($process_name, $config); 104 | } 105 | foreach (config('plugin', []) as $firm => $projects) { 106 | foreach ($projects as $name => $project) { 107 | foreach ($project['process'] ?? [] as $process_name => $config) { 108 | worker_start("plugin.$firm.$name.$process_name", $config); 109 | } 110 | } 111 | } 112 | } 113 | Worker::runAll(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Command.php: -------------------------------------------------------------------------------- 1 | installCommands(__DIR__ . '/Commands', 'Webman\Console\Commands'); 16 | } 17 | 18 | public function installCommands($path, $namespace = 'app\command') 19 | { 20 | $dir_iterator = new \RecursiveDirectoryIterator($path); 21 | $iterator = new \RecursiveIteratorIterator($dir_iterator); 22 | foreach ($iterator as $file) { 23 | /** @var \SplFileInfo $file */ 24 | if (strpos($file->getFilename(), '.') === 0) { 25 | continue; 26 | } 27 | if ($file->getExtension() !== 'php') { 28 | continue; 29 | } 30 | // abc\def.php 31 | $relativePath = str_replace(str_replace('/', '\\', $path . '\\'), '', str_replace('/', '\\', $file->getRealPath())); 32 | // app\command\abc 33 | $realNamespace = trim($namespace . '\\' . trim(dirname(str_replace('\\', DIRECTORY_SEPARATOR, $relativePath)), '.'), '\\'); 34 | $realNamespace = str_replace('/', '\\', $realNamespace); 35 | // app\command\doc\def 36 | $class_name = trim($realNamespace . '\\' . $file->getBasename('.php'), '\\'); 37 | if (!class_exists($class_name) || !is_a($class_name, Commands::class, true)) { 38 | continue; 39 | } 40 | 41 | $this->createCommandInstance($class_name); 42 | } 43 | } 44 | 45 | public function createCommandInstance($class_name) 46 | { 47 | $reflection = new \ReflectionClass($class_name); 48 | if ($reflection->isAbstract()) { 49 | return null; 50 | } 51 | 52 | $attributes = $reflection->getAttributes(AsCommand::class); 53 | if (!empty($attributes)) { 54 | $properties = current($attributes)->newInstance(); 55 | $name = $properties->name; 56 | $description = $properties->description; 57 | } else { 58 | $properties = $reflection->getStaticProperties(); 59 | $name = $properties['defaultName'] ?? null; 60 | if (!$name) { 61 | throw new RuntimeException("Command {$class_name} has no defaultName"); 62 | } 63 | $description = $properties['defaultDescription'] ?? ''; 64 | } 65 | $command = Container::get($class_name); 66 | $command->setName($name)->setDescription($description); 67 | $this->add($command); 68 | return $command; 69 | } 70 | } -------------------------------------------------------------------------------- /src/Commands/AppPluginCreateCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'App plugin name'); 20 | } 21 | 22 | /** 23 | * @param InputInterface $input 24 | * @param OutputInterface $output 25 | * @return int 26 | */ 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $name = $input->getArgument('name'); 30 | $output->writeln("Create App Plugin $name"); 31 | 32 | if (strpos($name, '/') !== false) { 33 | $output->writeln('Bad name, name must not contain character \'/\''); 34 | return self::FAILURE; 35 | } 36 | 37 | // Create dir config/plugin/$name 38 | if (is_dir($plugin_config_path = base_path()."/plugin/$name")) { 39 | $output->writeln("Dir $plugin_config_path already exists"); 40 | return self::FAILURE; 41 | } 42 | 43 | $this->createAll($name); 44 | 45 | return self::SUCCESS; 46 | } 47 | 48 | /** 49 | * @param $name 50 | * @return void 51 | */ 52 | protected function createAll($name) 53 | { 54 | $base_path = base_path(); 55 | $this->mkdir("$base_path/plugin/$name/app/controller", 0777, true); 56 | $this->mkdir("$base_path/plugin/$name/app/model", 0777, true); 57 | $this->mkdir("$base_path/plugin/$name/app/middleware", 0777, true); 58 | $this->mkdir("$base_path/plugin/$name/app/view/index", 0777, true); 59 | $this->mkdir("$base_path/plugin/$name/config", 0777, true); 60 | $this->mkdir("$base_path/plugin/$name/public", 0777, true); 61 | $this->mkdir("$base_path/plugin/$name/api", 0777, true); 62 | $this->createFunctionsFile("$base_path/plugin/$name/app/functions.php"); 63 | $this->createControllerFile("$base_path/plugin/$name/app/controller/IndexController.php", $name); 64 | $this->createViewFile("$base_path/plugin/$name/app/view/index/index.html"); 65 | $this->createConfigFiles("$base_path/plugin/$name/config", $name); 66 | $this->createApiFiles("$base_path/plugin/$name/api", $name); 67 | $this->createInstallSqlFile("$base_path/plugin/$name/install.sql"); 68 | } 69 | 70 | /** 71 | * @param $path 72 | * @return void 73 | */ 74 | protected function mkdir($path) 75 | { 76 | if (is_dir($path)) { 77 | return; 78 | } 79 | echo "Create $path\r\n"; 80 | mkdir($path, 0777, true); 81 | } 82 | 83 | /** 84 | * @param $path 85 | * @param $name 86 | * @return void 87 | */ 88 | protected function createControllerFile($path, $name) 89 | { 90 | $content = << '$name']); 103 | } 104 | 105 | } 106 | 107 | EOF; 108 | file_put_contents($path, $content); 109 | 110 | } 111 | 112 | /** 113 | * @param $path 114 | * @return void 115 | */ 116 | protected function createViewFile($path) 117 | { 118 | $content = << 120 | 121 | 122 | 123 | 124 | 125 | 126 | webman app plugin 127 | 128 | 129 | 130 | hello 131 | 132 | 133 | 134 | 135 | EOF; 136 | file_put_contents($path, $content); 137 | 138 | } 139 | 140 | 141 | /** 142 | * @param $file 143 | * @return void 144 | */ 145 | protected function createFunctionsFile($file) 146 | { 147 | $content = << static::getMenus()]; 253 | } 254 | 255 | /** 256 | * 获取菜单 257 | * 258 | * @return array|mixed 259 | */ 260 | public static function getMenus() 261 | { 262 | clearstatcache(); 263 | if (is_file(\$menu_file = __DIR__ . '/../config/menu.php')) { 264 | \$menus = include \$menu_file; 265 | return \$menus ?: []; 266 | } 267 | return []; 268 | } 269 | 270 | /** 271 | * 删除不需要的菜单 272 | * 273 | * @param \$previous_menus 274 | * @return void 275 | */ 276 | public static function removeUnnecessaryMenus(\$previous_menus) 277 | { 278 | \$menus_to_remove = array_diff(Menu::column(\$previous_menus, 'name'), Menu::column(static::getMenus(), 'name')); 279 | foreach (\$menus_to_remove as \$name) { 280 | Menu::delete(\$name); 281 | } 282 | } 283 | 284 | /** 285 | * 安装SQL 286 | * 287 | * @return void 288 | */ 289 | protected static function installSql() 290 | { 291 | static::importSql(__DIR__ . '/../install.sql'); 292 | } 293 | 294 | /** 295 | * 卸载SQL 296 | * 297 | * @return void 298 | */ 299 | protected static function uninstallSql() { 300 | // 如果卸载数据库文件存在责直接使用 301 | \$uninstallSqlFile = __DIR__ . '/../uninstall.sql'; 302 | if (is_file(\$uninstallSqlFile)) { 303 | static::importSql(\$uninstallSqlFile); 304 | return; 305 | } 306 | // 否则根据install.sql生成卸载数据库文件uninstall.sql 307 | \$installSqlFile = __DIR__ . '/../install.sql'; 308 | if (!is_file(\$installSqlFile)) { 309 | return; 310 | } 311 | \$installSql = file_get_contents(\$installSqlFile); 312 | preg_match_all('/CREATE TABLE `(.+?)`/si', \$installSql, \$matches); 313 | \$dropSql = ''; 314 | foreach (\$matches[1] as \$table) { 315 | \$dropSql .= "DROP TABLE IF EXISTS `\$table`;\\n"; 316 | } 317 | file_put_contents(\$uninstallSqlFile, \$dropSql); 318 | static::importSql(\$uninstallSqlFile); 319 | unlink(\$uninstallSqlFile); 320 | } 321 | 322 | /** 323 | * 导入数据库 324 | * 325 | * @return void 326 | */ 327 | public static function importSql(\$mysqlDumpFile) 328 | { 329 | if (!\$mysqlDumpFile || !is_file(\$mysqlDumpFile)) { 330 | return; 331 | } 332 | foreach (explode(';', file_get_contents(\$mysqlDumpFile)) as \$sql) { 333 | if (\$sql = trim(\$sql)) { 334 | try { 335 | Db::connection(static::\$connection)->statement(\$sql); 336 | } catch (Throwable \$e) {} 337 | } 338 | } 339 | } 340 | 341 | } 342 | EOF; 343 | 344 | file_put_contents("$base/Install.php", $content); 345 | 346 | } 347 | 348 | /** 349 | * @return void 350 | */ 351 | protected function createInstallSqlFile($file) 352 | { 353 | file_put_contents($file, ''); 354 | } 355 | 356 | /** 357 | * @param $base 358 | * @param $name 359 | * @return void 360 | */ 361 | protected function createConfigFiles($base, $name) 362 | { 363 | // app.php 364 | $content = << true, 371 | 'controller_suffix' => 'Controller', 372 | 'controller_reuse' => false, 373 | 'version' => '1.0.0' 374 | ]; 375 | 376 | EOF; 377 | file_put_contents("$base/app.php", $content); 378 | 379 | // menu.php 380 | $content = << [ 393 | base_path() . '/plugin/$name/app/functions.php', 394 | ] 395 | ]; 396 | EOF; 397 | file_put_contents("$base/autoload.php", $content); 398 | 399 | // container.php 400 | $content = << support\\exception\\Handler::class, 422 | ]; 423 | 424 | EOF; 425 | file_put_contents("$base/exception.php", $content); 426 | 427 | // log.php 428 | $content = << [ 433 | 'handlers' => [ 434 | [ 435 | 'class' => Monolog\\Handler\\RotatingFileHandler::class, 436 | 'constructor' => [ 437 | runtime_path() . '/logs/$name.log', 438 | 7, 439 | Monolog\\Logger::DEBUG, 440 | ], 441 | 'formatter' => [ 442 | 'class' => Monolog\\Formatter\\LineFormatter::class, 443 | 'constructor' => [null, 'Y-m-d H:i:s', true], 444 | ], 445 | ] 446 | ], 447 | ], 448 | ]; 449 | 450 | EOF; 451 | file_put_contents("$base/log.php", $content); 452 | 453 | // middleware.php 454 | $content = << [ 459 | 460 | ] 461 | ]; 462 | 463 | EOF; 464 | file_put_contents("$base/middleware.php", $content); 465 | 466 | // process.php 467 | $content = << [ 479 | 'host' => '127.0.0.1', 480 | 'password' => null, 481 | 'port' => 6379, 482 | 'database' => 0, 483 | ], 484 | ]; 485 | 486 | EOF; 487 | file_put_contents("$base/redis.php", $content); 488 | 489 | // route.php 490 | $content = << true, 505 | 'middleware' => [], // Static file Middleware 506 | ]; 507 | 508 | EOF; 509 | file_put_contents("$base/static.php", $content); 510 | 511 | // translation.php 512 | $content = << 'zh_CN', 518 | // Fallback language 519 | 'fallback_locale' => ['zh_CN', 'en'], 520 | // Folder where language files are stored 521 | 'path' => base_path() . "/plugin/$name/resource/translations", 522 | ]; 523 | 524 | EOF; 525 | file_put_contents("$base/translation.php", $content); 526 | 527 | // view.php 528 | $content = << Raw::class 538 | ]; 539 | 540 | EOF; 541 | file_put_contents("$base/view.php", $content); 542 | 543 | // thinkorm.php 544 | $content = <<addArgument('name', InputArgument::REQUIRED, 'App plugin name'); 20 | } 21 | 22 | /** 23 | * @param InputInterface $input 24 | * @param OutputInterface $output 25 | * @return int 26 | */ 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $name = $input->getArgument('name'); 30 | $output->writeln("Install App Plugin $name"); 31 | $class = "\\plugin\\$name\\api\\Install"; 32 | if (!method_exists($class, 'install')) { 33 | throw new \RuntimeException("Method $class::install not exists"); 34 | } 35 | call_user_func([$class, 'install'], config("plugin.$name.app.version")); 36 | return self::SUCCESS; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/Commands/AppPluginUninstallCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'App plugin name'); 20 | } 21 | 22 | /** 23 | * @param InputInterface $input 24 | * @param OutputInterface $output 25 | * @return int 26 | */ 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $name = $input->getArgument('name'); 30 | $output->writeln("Uninstall App Plugin $name"); 31 | $class = "\\plugin\\$name\\api\\Install"; 32 | if (!method_exists($class, 'uninstall')) { 33 | throw new \RuntimeException("Method $class::uninstall not exists"); 34 | } 35 | call_user_func([$class, 'uninstall'], config("plugin.$name.app.version")); 36 | return self::SUCCESS; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/Commands/AppPluginUpdateCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'App plugin name'); 20 | } 21 | 22 | /** 23 | * @param InputInterface $input 24 | * @param OutputInterface $output 25 | * @return int 26 | */ 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $name = $input->getArgument('name'); 30 | $output->writeln("Update App Plugin $name"); 31 | $class = "\\plugin\\$name\\api\\Install"; 32 | if (!method_exists($class, 'update')) { 33 | throw new \RuntimeException("Method $class::update not exists"); 34 | } 35 | call_user_func([$class, 'update'], config("plugin.$name.app.version"), config("plugin.$name.app.version")); 36 | return self::SUCCESS; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/Commands/AppPluginZipCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'App plugin name'); 24 | } 25 | 26 | /** 27 | * @param InputInterface $input 28 | * @param OutputInterface $output 29 | * @return int 30 | */ 31 | protected function execute(InputInterface $input, OutputInterface $output): int 32 | { 33 | $name = $input->getArgument('name'); 34 | $output->writeln("Zip App Plugin $name"); 35 | $sourceDir = base_path('plugin' . DIRECTORY_SEPARATOR . $name); 36 | $zipFilePath = base_path('plugin' . DIRECTORY_SEPARATOR . $name . '.zip'); 37 | if (!is_dir($sourceDir)) { 38 | $output->writeln("Plugin $name not exists"); 39 | return self::FAILURE; 40 | } 41 | if (is_file($zipFilePath)) { 42 | unlink($zipFilePath); 43 | } 44 | 45 | $excludePaths = ['node_modules', '.git', '.idea', '.vscode', '__pycache__']; 46 | 47 | $this->zipDirectory($name, $sourceDir, $zipFilePath, $excludePaths); 48 | return self::SUCCESS; 49 | } 50 | 51 | /** 52 | * @param $name 53 | * @param $sourceDir 54 | * @param $zipFilePath 55 | * @param array $excludePaths 56 | * @return bool 57 | * @throws Exception 58 | */ 59 | protected function zipDirectory($name, $sourceDir, $zipFilePath, array $excludePaths = []): bool 60 | { 61 | $zip = new ZipArchive(); 62 | 63 | if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { 64 | throw new Exception("cannot open <$zipFilePath>\n"); 65 | } 66 | 67 | $sourceDir = realpath($sourceDir); 68 | 69 | $files = new RecursiveIteratorIterator( 70 | new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS), 71 | RecursiveIteratorIterator::LEAVES_ONLY 72 | ); 73 | 74 | foreach ($files as $file) { 75 | if (!$file->isDir()) { 76 | $filePath = $file->getRealPath(); 77 | $relativePath = $name . DIRECTORY_SEPARATOR . substr($filePath, strlen($sourceDir) + 1); 78 | 79 | // 修正排除目录的判断逻辑,确保所有层级都能排除 80 | $shouldExclude = false; 81 | foreach ($excludePaths as $excludePath) { 82 | // 统一路径分隔符为正斜杠,兼容 Windows 83 | $normalizedRelativePath = str_replace('\\', '/', $relativePath); 84 | $normalizedExcludePath = str_replace('\\', '/', $excludePath); 85 | if (preg_match('#/(?:' . preg_quote($normalizedExcludePath, '#') . ')(/|$)#i', $normalizedRelativePath)) { 86 | $shouldExclude = true; 87 | break; 88 | } 89 | } 90 | if ($shouldExclude) { 91 | continue; 92 | } 93 | 94 | $zip->addFile($filePath, $relativePath); 95 | } 96 | } 97 | 98 | return $zip->close(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Commands/BuildBinCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('version', InputArgument::OPTIONAL, 'PHP version'); 20 | } 21 | 22 | /** 23 | * @param InputInterface $input 24 | * @param OutputInterface $output 25 | * @return int 26 | */ 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $this->checkEnv(); 30 | 31 | $output->writeln('Phar packing...'); 32 | 33 | $version = $input->getArgument('version'); 34 | if (!$version) { 35 | $version = (float)PHP_VERSION; 36 | } 37 | $version = max($version, 8.1); 38 | $supportZip = class_exists(ZipArchive::class); 39 | $microZipFileName = $supportZip ? "php$version.micro.sfx.zip" : "php$version.micro.sfx"; 40 | $pharFileName = config('plugin.webman.console.app.phar_filename', 'webman.phar'); 41 | $binFileName = config('plugin.webman.console.app.bin_filename', 'webman.bin'); 42 | $this->buildDir = config('plugin.webman.console.app.build_dir', base_path() . '/build'); 43 | $customIni = config('plugin.webman.console.app.custom_ini', ''); 44 | 45 | $binFile = "$this->buildDir/$binFileName"; 46 | $pharFile = "$this->buildDir/$pharFileName"; 47 | $zipFile = "$this->buildDir/$microZipFileName"; 48 | $sfxFile = "$this->buildDir/php$version.micro.sfx"; 49 | $customIniHeaderFile = "$this->buildDir/custominiheader.bin"; 50 | 51 | // 打包 52 | $command = new BuildPharCommand(); 53 | $command->execute($input, $output); 54 | 55 | // 下载 micro.sfx.zip 56 | if (!is_file($sfxFile) && !is_file($zipFile)) { 57 | $domain = 'download.workerman.net'; 58 | $output->writeln("\r\nDownloading PHP$version ..."); 59 | if (extension_loaded('openssl')) { 60 | $context = stream_context_create([ 61 | 'ssl' => [ 62 | 'verify_peer' => false, 63 | 'verify_peer_name' => false, 64 | ] 65 | ]); 66 | $client = stream_socket_client("ssl://$domain:443", $context); 67 | } else { 68 | $client = stream_socket_client("tcp://$domain:80"); 69 | } 70 | 71 | fwrite($client, "GET /php/$microZipFileName HTTP/1.1\r\nAccept: text/html\r\nHost: $domain\r\nUser-Agent: webman/console\r\n\r\n"); 72 | $bodyLength = 0; 73 | $bodyBuffer = ''; 74 | $lastPercent = 0; 75 | while (true) { 76 | $buffer = fread($client, 65535); 77 | if ($buffer !== false) { 78 | $bodyBuffer .= $buffer; 79 | if (!$bodyLength && $pos = strpos($bodyBuffer, "\r\n\r\n")) { 80 | if (!preg_match('/Content-Length: (\d+)\r\n/', $bodyBuffer, $match)) { 81 | $output->writeln("Download php$version.micro.sfx.zip failed"); 82 | return self::FAILURE; 83 | } 84 | $firstLine = substr($bodyBuffer, 9, strpos($bodyBuffer, "\r\n") - 9); 85 | if (!preg_match('/200 /', $bodyBuffer)) { 86 | $output->writeln("Download php$version.micro.sfx.zip failed, $firstLine"); 87 | return self::FAILURE; 88 | } 89 | $bodyLength = (int)$match[1]; 90 | $bodyBuffer = substr($bodyBuffer, $pos + 4); 91 | } 92 | } 93 | $receiveLength = strlen($bodyBuffer); 94 | $percent = ceil($receiveLength * 100 / $bodyLength); 95 | if ($percent != $lastPercent) { 96 | echo '[' . str_pad('', $percent, '=') . '>' . str_pad('', 100 - $percent) . "$percent%]"; 97 | echo $percent < 100 ? "\r" : "\n"; 98 | } 99 | $lastPercent = $percent; 100 | if ($bodyLength && $receiveLength >= $bodyLength) { 101 | file_put_contents($zipFile, $bodyBuffer); 102 | break; 103 | } 104 | if ($buffer === false || !is_resource($client) || feof($client)) { 105 | $output->writeln("Fail donwload PHP$version ..."); 106 | return self::FAILURE; 107 | } 108 | } 109 | } else { 110 | $output->writeln("\r\nUse PHP$version ..."); 111 | } 112 | 113 | // 解压 114 | if (!is_file($sfxFile) && $supportZip) { 115 | $zip = new ZipArchive; 116 | $zip->open($zipFile, ZipArchive::CHECKCONS); 117 | $zip->extractTo($this->buildDir); 118 | } 119 | 120 | // 生成二进制文件 121 | file_put_contents($binFile, file_get_contents($sfxFile)); 122 | // 自定义INI 123 | if (!empty($customIni)) { 124 | if (file_exists($customIniHeaderFile)) { 125 | unlink($customIniHeaderFile); 126 | } 127 | $f = fopen($customIniHeaderFile, 'wb'); 128 | fwrite($f, "\xfd\xf6\x69\xe6"); 129 | fwrite($f, pack('N', strlen($customIni))); 130 | fwrite($f, $customIni); 131 | fclose($f); 132 | file_put_contents($binFile, file_get_contents($customIniHeaderFile),FILE_APPEND); 133 | unlink($customIniHeaderFile); 134 | } 135 | file_put_contents($binFile, file_get_contents($pharFile), FILE_APPEND); 136 | 137 | // 添加执行权限 138 | chmod($binFile, 0755); 139 | 140 | $output->writeln("\r\nSaved $binFileName to $binFile\r\nBuild Success!\r\n"); 141 | 142 | return self::SUCCESS; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Commands/BuildPharCommand.php: -------------------------------------------------------------------------------- 1 | buildDir = config('plugin.webman.console.app.build_dir', base_path() . '/build'); 21 | } 22 | 23 | /** 24 | * @param InputInterface $input 25 | * @param OutputInterface $output 26 | * @return int 27 | */ 28 | protected function execute(InputInterface $input, OutputInterface $output): int 29 | { 30 | $this->checkEnv(); 31 | if (!file_exists($this->buildDir) && !is_dir($this->buildDir)) { 32 | if (!mkdir($this->buildDir,0777,true)) { 33 | throw new RuntimeException("Failed to create phar file output directory. Please check the permission."); 34 | } 35 | } 36 | 37 | $phar_filename = config('plugin.webman.console.app.phar_filename', 'webman.phar'); 38 | if (empty($phar_filename)) { 39 | throw new RuntimeException('Please set the phar filename.'); 40 | } 41 | 42 | $phar_file = rtrim($this->buildDir,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $phar_filename; 43 | if (file_exists($phar_file)) { 44 | unlink($phar_file); 45 | } 46 | 47 | $exclude_pattern = config('plugin.webman.console.app.exclude_pattern',''); 48 | 49 | $phar = new Phar($phar_file,0,'webman'); 50 | 51 | $phar->startBuffering(); 52 | 53 | $signature_algorithm = config('plugin.webman.console.app.signature_algorithm'); 54 | if (!in_array($signature_algorithm,[Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512,Phar::OPENSSL])) { 55 | throw new RuntimeException('The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL.'); 56 | } 57 | if ($signature_algorithm === Phar::OPENSSL) { 58 | $private_key_file = config('plugin.webman.console.app.private_key_file'); 59 | if (!file_exists($private_key_file)) { 60 | throw new RuntimeException("If the value of the signature algorithm is 'Phar::OPENSSL', you must set the private key file."); 61 | } 62 | $private = openssl_get_privatekey(file_get_contents($private_key_file)); 63 | $pkey = ''; 64 | openssl_pkey_export($private, $pkey); 65 | $phar->setSignatureAlgorithm($signature_algorithm, $pkey); 66 | } else { 67 | $phar->setSignatureAlgorithm($signature_algorithm); 68 | } 69 | 70 | $phar->buildFromDirectory(BASE_PATH,$exclude_pattern); 71 | 72 | 73 | $exclude_files = config('plugin.webman.console.app.exclude_files',[]); 74 | // 打包生成的phar和bin文件是面向生产环境的,所以以下这些命令没有任何意义,执行的话甚至会出错,需要排除在外。 75 | $exclude_command_files = [ 76 | 'AppPluginCreateCommand.php', 77 | 'BuildBinCommand.php', 78 | 'BuildPharCommand.php', 79 | 'MakeBootstrapCommand.php', 80 | 'MakeCommandCommand.php', 81 | 'MakeControllerCommand.php', 82 | 'MakeMiddlewareCommand.php', 83 | 'MakeModelCommand.php', 84 | 'PluginCreateCommand.php', 85 | 'PluginDisableCommand.php', 86 | 'PluginEnableCommand.php', 87 | 'PluginExportCommand.php', 88 | 'PluginInstallCommand.php', 89 | 'PluginUninstallCommand.php' 90 | ]; 91 | $exclude_command_files = array_map(function ($cmd_file) { 92 | return 'vendor/webman/console/src/Commands/'.$cmd_file; 93 | },$exclude_command_files); 94 | $exclude_files = array_unique(array_merge($exclude_command_files,$exclude_files)); 95 | foreach ($exclude_files as $file) { 96 | if($phar->offsetExists($file)){ 97 | $phar->delete($file); 98 | } 99 | } 100 | 101 | $output->writeln('Files collect complete, begin add file to Phar.'); 102 | 103 | $phar->setStub("#!/usr/bin/env php 104 | writeln('Write requests to the Phar archive, save changes to disk.'); 112 | 113 | $phar->stopBuffering(); 114 | unset($phar); 115 | return self::SUCCESS; 116 | } 117 | 118 | /** 119 | * @throws RuntimeException 120 | */ 121 | public function checkEnv(): void 122 | { 123 | if (!class_exists(Phar::class, false)) { 124 | throw new RuntimeException("The 'phar' extension is required for build phar package"); 125 | } 126 | 127 | if (ini_get('phar.readonly')) { 128 | $command = $this->getName(); 129 | throw new RuntimeException( 130 | "The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0 ./webman $command'" 131 | ); 132 | } 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/Commands/ConnectionsCommand.php: -------------------------------------------------------------------------------- 1 | writeln('Can not find php.ini'); 23 | return self::FAILURE; 24 | } 25 | $output->writeln("Location $php_ini_file"); 26 | $disable_functions_str = ini_get("disable_functions"); 27 | if (!$disable_functions_str) { 28 | $output->writeln('Ok'); 29 | } 30 | 31 | $functions_required = [ 32 | "stream_socket_server", 33 | "stream_socket_accept", 34 | "stream_socket_client", 35 | "pcntl_signal_dispatch", 36 | "pcntl_signal", 37 | "pcntl_alarm", 38 | "pcntl_fork", 39 | "posix_getuid", 40 | "posix_getpwuid", 41 | "posix_kill", 42 | "posix_setsid", 43 | "posix_getpid", 44 | "posix_getpwnam", 45 | "posix_getgrnam", 46 | "posix_getgid", 47 | "posix_setgid", 48 | "posix_initgroups", 49 | "posix_setuid", 50 | "posix_isatty", 51 | "proc_open", 52 | "proc_get_status", 53 | "proc_close", 54 | "shell_exec", 55 | "exec", 56 | ]; 57 | 58 | $has_disbaled_functions = false; 59 | foreach ($functions_required as $func) { 60 | if (strpos($disable_functions_str, $func) !== false) { 61 | $has_disbaled_functions = true; 62 | break; 63 | } 64 | } 65 | 66 | $disable_functions = explode(",", $disable_functions_str); 67 | $disable_functions_removed = []; 68 | foreach ($disable_functions as $index => $func) { 69 | $func = trim($func); 70 | foreach ($functions_required as $func_prefix) { 71 | if (strpos($func, $func_prefix) === 0) { 72 | $disable_functions_removed[$func] = $func; 73 | unset($disable_functions[$index]); 74 | } 75 | } 76 | } 77 | 78 | $php_ini_content = file_get_contents($php_ini_file); 79 | if (!$php_ini_content) { 80 | $output->writeln("$php_ini_file content empty"); 81 | return self::FAILURE; 82 | } 83 | 84 | $new_disable_functions_str = implode(",", $disable_functions); 85 | $php_ini_content = preg_replace("/\ndisable_functions *?=[^\n]+/", "\ndisable_functions = $new_disable_functions_str", $php_ini_content); 86 | 87 | file_put_contents($php_ini_file, $php_ini_content); 88 | 89 | foreach ($disable_functions_removed as $func) { 90 | $output->write(str_pad($func, 30)); 91 | $output->writeln('enabled'); 92 | } 93 | 94 | $output->writeln('Succes'); 95 | return self::SUCCESS; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/Commands/InstallCommand.php: -------------------------------------------------------------------------------- 1 | writeln("Execute installation for webman"); 21 | $install_function = "\\Webman\\Install::install"; 22 | if (is_callable($install_function)) { 23 | $install_function(); 24 | return self::SUCCESS; 25 | } 26 | $output->writeln('This command requires webman-framework version >= 1.3.0'); 27 | return self::FAILURE; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/Commands/MakeBootstrapCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'Bootstrap name'); 22 | $this->addArgument('enable', InputArgument::OPTIONAL, 'Enable or not'); 23 | } 24 | 25 | /** 26 | * @param InputInterface $input 27 | * @param OutputInterface $output 28 | * @return int 29 | */ 30 | protected function execute(InputInterface $input, OutputInterface $output): int 31 | { 32 | $name = $input->getArgument('name'); 33 | $enable = in_array($input->getArgument('enable'), ['no', '0', 'false', 'n']) ? false : true; 34 | $output->writeln("Make bootstrap $name"); 35 | 36 | $name = str_replace('\\', '/', $name); 37 | if (!$bootstrap_str = Util::guessPath(app_path(), 'bootstrap')) { 38 | $bootstrap_str = Util::guessPath(app_path(), 'controller') === 'Controller' ? 'Bootstrap' : 'bootstrap'; 39 | } 40 | $upper = $bootstrap_str === 'Bootstrap'; 41 | if (!($pos = strrpos($name, '/'))) { 42 | $name = ucfirst($name); 43 | $file = app_path() . DIRECTORY_SEPARATOR . $bootstrap_str . DIRECTORY_SEPARATOR . "$name.php"; 44 | $namespace = $upper ? 'App\Bootstrap' : 'app\bootstrap'; 45 | } else { 46 | if($real_name = Util::guessPath(app_path(), $name)) { 47 | $name = $real_name; 48 | } 49 | if ($upper && !$real_name) { 50 | $name = preg_replace_callback('/\/([a-z])/', function ($matches) { 51 | return '/' . strtoupper($matches[1]); 52 | }, ucfirst($name)); 53 | } 54 | $path = "$bootstrap_str/" . substr($upper ? ucfirst($name) : $name, 0, $pos); 55 | $name = ucfirst(substr($name, $pos + 1)); 56 | $file = app_path() . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . "$name.php"; 57 | $namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path); 58 | } 59 | 60 | if (is_file($file)) { 61 | $helper = $this->getHelper('question'); 62 | $question = new ConfirmationQuestion("$file already exists. Do you want to override it? (yes/no)", false); 63 | if (!$helper->ask($input, $output, $question)) { 64 | return Command::SUCCESS; 65 | } 66 | } 67 | 68 | $this->createBootstrap($name, $namespace, $file); 69 | if ($enable) { 70 | $this->addConfig("$namespace\\$name", config_path() . '/bootstrap.php'); 71 | } 72 | 73 | return self::SUCCESS; 74 | } 75 | 76 | /** 77 | * @param $name 78 | * @param $namespace 79 | * @param $file 80 | * @return void 81 | */ 82 | protected function createBootstrap($name, $namespace, $file) 83 | { 84 | $path = pathinfo($file, PATHINFO_DIRNAME); 85 | if (!is_dir($path)) { 86 | mkdir($path, 0777, true); 87 | } 88 | $bootstrap_content = <<addArgument('name', InputArgument::REQUIRED, 'Command name'); 22 | } 23 | 24 | /** 25 | * @param InputInterface $input 26 | * @param OutputInterface $output 27 | * @return int 28 | */ 29 | protected function execute(InputInterface $input, OutputInterface $output): int 30 | { 31 | $command = $name = trim($input->getArgument('name')); 32 | $output->writeln("Make command $name"); 33 | 34 | // make:command 不支持子目录 35 | $name = str_replace(['\\', '/'], '', $name); 36 | if (!$command_str = Util::guessPath(app_path(), 'command')) { 37 | $command_str = Util::guessPath(app_path(), 'controller') === 'Controller' ? 'Command' : 'command'; 38 | } 39 | $items= explode(':', $name); 40 | $name=''; 41 | foreach ($items as $item) { 42 | $name.=ucfirst($item); 43 | } 44 | $file = app_path() . DIRECTORY_SEPARATOR . $command_str . DIRECTORY_SEPARATOR . "$name.php"; 45 | $upper = $command_str === 'Command'; 46 | $namespace = $upper ? 'App\Command' : 'app\command'; 47 | 48 | if (is_file($file)) { 49 | $helper = $this->getHelper('question'); 50 | $question = new ConfirmationQuestion("$file already exists. Do you want to override it? (yes/no)", false); 51 | if (!$helper->ask($input, $output, $question)) { 52 | return Command::SUCCESS; 53 | } 54 | } 55 | 56 | $this->createCommand($name, $namespace, $file, $command); 57 | 58 | return self::SUCCESS; 59 | } 60 | 61 | protected function getClassName($name) 62 | { 63 | return preg_replace_callback('/:([a-zA-Z])/', function ($matches) { 64 | return strtoupper($matches[1]); 65 | }, ucfirst($name)) . 'Command'; 66 | } 67 | 68 | /** 69 | * @param $name 70 | * @param $namespace 71 | * @param $path 72 | * @return void 73 | */ 74 | protected function createCommand($name, $namespace, $file, $command) 75 | { 76 | $path = pathinfo($file, PATHINFO_DIRNAME); 77 | if (!is_dir($path)) { 78 | mkdir($path, 0777, true); 79 | } 80 | $desc = str_replace(':', ' ', $command); 81 | $command_content = <<addArgument('name', InputArgument::OPTIONAL, 'Name description'); 102 | } 103 | 104 | /** 105 | * @param InputInterface \$input 106 | * @param OutputInterface \$output 107 | * @return int 108 | */ 109 | protected function execute(InputInterface \$input, OutputInterface \$output): int 110 | { 111 | \$name = \$input->getArgument('name'); 112 | \$output->writeln('Hello $command'); 113 | return self::SUCCESS; 114 | } 115 | 116 | } 117 | 118 | EOF; 119 | file_put_contents($file, $command_content); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/Commands/MakeControllerCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'Controller name'); 22 | } 23 | 24 | /** 25 | * @param InputInterface $input 26 | * @param OutputInterface $output 27 | * @return int 28 | */ 29 | protected function execute(InputInterface $input, OutputInterface $output): int 30 | { 31 | $name = $input->getArgument('name'); 32 | $output->writeln("Make controller $name"); 33 | $suffix = config('app.controller_suffix', ''); 34 | 35 | if ($suffix && !strpos($name, $suffix)) { 36 | $name .= $suffix; 37 | } 38 | 39 | $name = str_replace('\\', '/', $name); 40 | if (!($pos = strrpos($name, '/'))) { 41 | $name = ucfirst($name); 42 | $controller_str = Util::guessPath(app_path(), 'controller') ?: 'controller'; 43 | $file = app_path() . DIRECTORY_SEPARATOR . $controller_str . DIRECTORY_SEPARATOR . "$name.php"; 44 | $namespace = $controller_str === 'Controller' ? 'App\Controller' : 'app\controller'; 45 | } else { 46 | $name_str = substr($name, 0, $pos); 47 | if($real_name_str = Util::guessPath(app_path(), $name_str)) { 48 | $name_str = $real_name_str; 49 | } else if ($real_section_name = Util::guessPath(app_path(), strstr($name_str, '/', true))) { 50 | $upper = strtolower($real_section_name[0]) !== $real_section_name[0]; 51 | } else if ($real_base_controller = Util::guessPath(app_path(), 'controller')) { 52 | $upper = strtolower($real_base_controller[0]) !== $real_base_controller[0]; 53 | } 54 | $upper = $upper ?? strtolower($name_str[0]) !== $name_str[0]; 55 | if ($upper && !$real_name_str) { 56 | $name_str = preg_replace_callback('/\/([a-z])/', function ($matches) { 57 | return '/' . strtoupper($matches[1]); 58 | }, ucfirst($name_str)); 59 | } 60 | $path = "$name_str/" . ($upper ? 'Controller' : 'controller'); 61 | $name = ucfirst(substr($name, $pos + 1)); 62 | $file = app_path() . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . "$name.php"; 63 | $namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path); 64 | } 65 | 66 | if (is_file($file)) { 67 | $helper = $this->getHelper('question'); 68 | $question = new ConfirmationQuestion("$file already exists. Do you want to override it? (yes/no)", false); 69 | if (!$helper->ask($input, $output, $question)) { 70 | return Command::SUCCESS; 71 | } 72 | } 73 | 74 | $this->createController($name, $namespace, $file); 75 | 76 | return self::SUCCESS; 77 | } 78 | 79 | /** 80 | * @param $name 81 | * @param $namespace 82 | * @param $file 83 | * @return void 84 | */ 85 | protected function createController($name, $namespace, $file) 86 | { 87 | $path = pathinfo($file, PATHINFO_DIRNAME); 88 | if (!is_dir($path)) { 89 | mkdir($path, 0777, true); 90 | } 91 | $controller_content = <<addArgument('name', InputArgument::REQUIRED, 'Middleware name'); 22 | } 23 | 24 | /** 25 | * @param InputInterface $input 26 | * @param OutputInterface $output 27 | * @return int 28 | */ 29 | protected function execute(InputInterface $input, OutputInterface $output): int 30 | { 31 | $name = $input->getArgument('name'); 32 | $output->writeln("Make middleware $name"); 33 | 34 | $name = str_replace('\\', '/', $name); 35 | if (!$middleware_str = Util::guessPath(app_path(), 'middleware')) { 36 | $middleware_str = Util::guessPath(app_path(), 'controller') === 'Controller' ? 'Middleware' : 'middleware'; 37 | } 38 | $upper = $middleware_str === 'Middleware'; 39 | if (!($pos = strrpos($name, '/'))) { 40 | $name = ucfirst($name); 41 | $file = app_path() . DIRECTORY_SEPARATOR . $middleware_str . DIRECTORY_SEPARATOR . "$name.php"; 42 | $namespace = $upper ? 'App\Middleware' : 'app\middleware'; 43 | } else { 44 | if($real_name = Util::guessPath(app_path(), $name)) { 45 | $name = $real_name; 46 | } 47 | if ($upper && !$real_name) { 48 | $name = preg_replace_callback('/\/([a-z])/', function ($matches) { 49 | return '/' . strtoupper($matches[1]); 50 | }, ucfirst($name)); 51 | } 52 | $path = "$middleware_str/" . substr($upper ? ucfirst($name) : $name, 0, $pos); 53 | $name = ucfirst(substr($name, $pos + 1)); 54 | $file = app_path() . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . "$name.php"; 55 | $namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path); 56 | } 57 | 58 | if (is_file($file)) { 59 | $helper = $this->getHelper('question'); 60 | $question = new ConfirmationQuestion("$file already exists. Do you want to override it? (yes/no)", false); 61 | if (!$helper->ask($input, $output, $question)) { 62 | return Command::SUCCESS; 63 | } 64 | } 65 | 66 | $this->createMiddleware($name, $namespace, $file); 67 | 68 | return self::SUCCESS; 69 | } 70 | 71 | 72 | /** 73 | * @param $name 74 | * @param $namespace 75 | * @param $path 76 | * @return void 77 | */ 78 | protected function createMiddleware($name, $namespace, $file) 79 | { 80 | $path = pathinfo($file, PATHINFO_DIRNAME); 81 | if (!is_dir($path)) { 82 | mkdir($path, 0777, true); 83 | } 84 | $middleware_content = <<addArgument('name', InputArgument::REQUIRED, 'Model name'); 25 | $this->addArgument('type', InputArgument::OPTIONAL, 'Type'); 26 | $this->addOption('connection', 'c', InputOption::VALUE_OPTIONAL, 'Select database connection. '); 27 | } 28 | 29 | /** 30 | * @param InputInterface $input 31 | * @param OutputInterface $output 32 | * @return int 33 | */ 34 | protected function execute(InputInterface $input, OutputInterface $output): int 35 | { 36 | $name = $input->getArgument('name'); 37 | $name = Util::nameToClass($name); 38 | $type = $input->getArgument('type'); 39 | $connection = $input->getOption('connection'); 40 | $output->writeln("Make model $name"); 41 | if (!($pos = strrpos($name, '/'))) { 42 | $name = ucfirst($name); 43 | $model_str = Util::guessPath(app_path(), 'model') ?: 'model'; 44 | $file = app_path() . DIRECTORY_SEPARATOR . $model_str . DIRECTORY_SEPARATOR . "$name.php"; 45 | $namespace = $model_str === 'Model' ? 'App\Model' : 'app\model'; 46 | } else { 47 | $name_str = substr($name, 0, $pos); 48 | if($real_name_str = Util::guessPath(app_path(), $name_str)) { 49 | $name_str = $real_name_str; 50 | } else if ($real_section_name = Util::guessPath(app_path(), strstr($name_str, '/', true))) { 51 | $upper = strtolower($real_section_name[0]) !== $real_section_name[0]; 52 | } else if ($real_base_controller = Util::guessPath(app_path(), 'controller')) { 53 | $upper = strtolower($real_base_controller[0]) !== $real_base_controller[0]; 54 | } 55 | $upper = $upper ?? strtolower($name_str[0]) !== $name_str[0]; 56 | if ($upper && !$real_name_str) { 57 | $name_str = preg_replace_callback('/\/([a-z])/', function ($matches) { 58 | return '/' . strtoupper($matches[1]); 59 | }, ucfirst($name_str)); 60 | } 61 | $path = "$name_str/" . ($upper ? 'Model' : 'model'); 62 | $name = ucfirst(substr($name, $pos + 1)); 63 | $file = app_path() . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . "$name.php"; 64 | $namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path); 65 | } 66 | if (!$type) { 67 | $database = config('database'); 68 | if (isset($database['default']) && strpos($database['default'], 'plugin.') === 0) { 69 | $database = false; 70 | } 71 | $thinkorm = config('think-orm') ?: config('thinkorm'); 72 | if (isset($thinkorm['default']) && strpos($thinkorm['default'], 'plugin.') === 0) { 73 | $thinkorm = false; 74 | } 75 | $type = !$database && $thinkorm ? 'tp' : 'laravel'; 76 | } 77 | 78 | if (is_file($file)) { 79 | $helper = $this->getHelper('question'); 80 | $question = new ConfirmationQuestion("$file already exists. Do you want to override it? (yes/no)", false); 81 | if (!$helper->ask($input, $output, $question)) { 82 | return Command::SUCCESS; 83 | } 84 | } 85 | 86 | if ($type == 'tp') { 87 | $this->createTpModel($name, $namespace, $file, $connection); 88 | } else { 89 | $this->createModel($name, $namespace, $file, $connection); 90 | } 91 | 92 | return self::SUCCESS; 93 | } 94 | 95 | /** 96 | * @param $class 97 | * @param $namespace 98 | * @param $file 99 | * @param string|null $connection 100 | * @return void 101 | */ 102 | protected function createModel($class, $namespace, $file, $connection = null) 103 | { 104 | $path = pathinfo($file, PATHINFO_DIRNAME); 105 | if (!is_dir($path)) { 106 | mkdir($path, 0777, true); 107 | } 108 | $table = Util::classToName($class); 109 | $table_val = 'null'; 110 | $pk = 'id'; 111 | $properties = ''; 112 | $connection = $connection ?: config('database.default'); 113 | $timestamps = 'false'; 114 | $hasCreatedAt = false; 115 | $hasUpdatedAt = false; 116 | try { 117 | $prefix = config("database.connections.$connection.prefix") ?? ''; 118 | $database = config("database.connections.$connection.database"); 119 | $driver = config("database.connections.$connection.driver") ?? 'mysql'; 120 | $inflector = InflectorFactory::create()->build(); 121 | $table_plura = $inflector->pluralize($inflector->tableize($class)); 122 | $con = Db::connection($connection); 123 | 124 | // 检查表是否存在(兼容MySQL和PostgreSQL) 125 | if ($driver === 'pgsql') { 126 | // PostgreSQL 表检查 127 | $schema = config("database.connections.$connection.schema") ?? 'public'; 128 | $exists_plura = $con->select("SELECT to_regclass('{$schema}.{$prefix}{$table_plura}') as table_exists"); 129 | $exists = $con->select("SELECT to_regclass('{$schema}.{$prefix}{$table}') as table_exists"); 130 | 131 | if (!empty($exists_plura[0]->table_exists)) { 132 | $table_val = "'$table_plura'"; 133 | $table = "{$prefix}{$table_plura}"; 134 | } else if (!empty($exists[0]->table_exists)) { 135 | $table_val = "'$table'"; 136 | $table = "{$prefix}{$table}"; 137 | } 138 | } else { 139 | // MySQL 表检查 140 | if ($con->select("show tables like '{$prefix}{$table_plura}'")) { 141 | $table_val = "'$table_plura'"; 142 | $table = "{$prefix}{$table_plura}"; 143 | } else if ($con->select("show tables like '{$prefix}{$table}'")) { 144 | $table_val = "'$table'"; 145 | $table = "{$prefix}{$table}"; 146 | } 147 | } 148 | 149 | // 获取表注释和列信息(兼容MySQL和PostgreSQL) 150 | if ($driver === 'pgsql') { 151 | // PostgreSQL 表注释 152 | $schema = config("database.connections.$connection.schema") ?? 'public'; 153 | $tableComment = $con->select("SELECT obj_description('{$schema}.{$table}'::regclass) as table_comment"); 154 | if (!empty($tableComment) && !empty($tableComment[0]->table_comment)) { 155 | $comments = $tableComment[0]->table_comment; 156 | $properties .= " * {$table} {$comments}" . PHP_EOL; 157 | } 158 | 159 | // PostgreSQL 列信息 160 | $columns = $con->select(" 161 | SELECT 162 | a.attname as column_name, 163 | format_type(a.atttypid, a.atttypmod) as data_type, 164 | CASE WHEN con.contype = 'p' THEN 'PRI' ELSE '' END as column_key, 165 | d.description as column_comment 166 | FROM pg_catalog.pg_attribute a 167 | LEFT JOIN pg_catalog.pg_description d ON d.objoid = a.attrelid AND d.objsubid = a.attnum 168 | LEFT JOIN pg_catalog.pg_constraint con ON con.conrelid = a.attrelid AND a.attnum = ANY(con.conkey) AND con.contype = 'p' 169 | WHERE a.attrelid = '{$schema}.{$table}'::regclass 170 | AND a.attnum > 0 AND NOT a.attisdropped 171 | ORDER BY a.attnum 172 | "); 173 | 174 | foreach ($columns as $item) { 175 | if ($item->column_key === 'PRI') { 176 | $pk = $item->column_name; 177 | $item->column_comment = ($item->column_comment ? $item->column_comment . ' ' : '') . "(主键)"; 178 | } 179 | $type = $this->getType($item->data_type); 180 | if ($item->column_name === 'created_at') { 181 | $hasCreatedAt = true; 182 | } 183 | if ($item->column_name === 'updated_at') { 184 | $hasUpdatedAt = true; 185 | } 186 | $properties .= " * @property $type \${$item->column_name} " . ($item->column_comment ?? '') . "\n"; 187 | } 188 | 189 | } else { 190 | // MySQL 表注释 191 | $tableComment = $con->select('SELECT table_comment FROM information_schema.`TABLES` WHERE table_schema = ? AND table_name = ?', [$database, $table]); 192 | if (!empty($tableComment)) { 193 | $comments = $tableComment[0]->table_comment ?? $tableComment[0]->TABLE_COMMENT; 194 | $properties .= " * {$table} {$comments}" . PHP_EOL; 195 | } 196 | 197 | // MySQL 列信息 198 | foreach ($con->select("select COLUMN_NAME,DATA_TYPE,COLUMN_KEY,COLUMN_COMMENT from INFORMATION_SCHEMA.COLUMNS where table_name = '$table' and table_schema = '$database' ORDER BY ordinal_position") as $item) { 199 | if ($item->COLUMN_KEY === 'PRI') { 200 | $pk = $item->COLUMN_NAME; 201 | $item->COLUMN_COMMENT .= "(主键)"; 202 | } 203 | $type = $this->getType($item->DATA_TYPE); 204 | if ($item->COLUMN_NAME === 'created_at') { 205 | $hasCreatedAt = true; 206 | } 207 | if ($item->COLUMN_NAME === 'updated_at') { 208 | $hasUpdatedAt = true; 209 | } 210 | $properties .= " * @property $type \${$item->COLUMN_NAME} {$item->COLUMN_COMMENT}\n"; 211 | } 212 | } 213 | } catch (\Throwable $e) { 214 | echo $e->getMessage() . PHP_EOL; 215 | } 216 | $properties = rtrim($properties) ?: ' *'; 217 | $timestamps = $hasCreatedAt && $hasUpdatedAt ? 'true' : 'false'; 218 | $model_content = <<query("SELECT to_regclass('{$schema}.{$prefix}{$table}') as table_exists"); 302 | $exists_plural = $con->query("SELECT to_regclass('{$schema}.{$prefix}{$table}s') as table_exists"); 303 | 304 | if (!empty($exists[0]['table_exists'])) { 305 | $table = "{$prefix}{$table}"; 306 | $table_val = "'$table'"; 307 | } else if (!empty($exists_plural[0]['table_exists'])) { 308 | $table = "{$prefix}{$table}s"; 309 | $table_val = "'$table'"; 310 | } 311 | } else { 312 | // MySQL 表检查 313 | if ($con->query("show tables like '{$prefix}{$table}'")) { 314 | $table = "{$prefix}{$table}"; 315 | $table_val = "'$table'"; 316 | } else if ($con->query("show tables like '{$prefix}{$table}s'")) { 317 | $table = "{$prefix}{$table}s"; 318 | $table_val = "'$table'"; 319 | } 320 | } 321 | 322 | // 获取表注释和列信息(兼容MySQL和PostgreSQL) 323 | if ($driver === 'pgsql') { 324 | // PostgreSQL 表注释 325 | $schema = config("$config_name.connections.$connection.schema") ?? 'public'; 326 | $tableComment = $con->query("SELECT obj_description('{$schema}.{$table}'::regclass) as table_comment"); 327 | if (!empty($tableComment) && !empty($tableComment[0]['table_comment'])) { 328 | $comments = $tableComment[0]['table_comment']; 329 | $properties .= " * {$table} {$comments}" . PHP_EOL; 330 | } 331 | 332 | // PostgreSQL 列信息 333 | $columns = $con->query(" 334 | SELECT 335 | a.attname as column_name, 336 | format_type(a.atttypid, a.atttypmod) as data_type, 337 | CASE WHEN con.contype = 'p' THEN 'PRI' ELSE '' END as column_key, 338 | d.description as column_comment 339 | FROM pg_catalog.pg_attribute a 340 | LEFT JOIN pg_catalog.pg_description d ON d.objoid = a.attrelid AND d.objsubid = a.attnum 341 | LEFT JOIN pg_catalog.pg_constraint con ON con.conrelid = a.attrelid AND a.attnum = ANY(con.conkey) AND con.contype = 'p' 342 | WHERE a.attrelid = '{$schema}.{$table}'::regclass 343 | AND a.attnum > 0 AND NOT a.attisdropped 344 | ORDER BY a.attnum 345 | "); 346 | 347 | foreach ($columns as $item) { 348 | if ($item['column_key'] === 'PRI') { 349 | $pk = $item['column_name']; 350 | $item['column_comment'] = ($item['column_comment'] ? $item['column_comment'] . ' ' : '') . "(主键)"; 351 | } 352 | $type = $this->getType($item['data_type']); 353 | $properties .= " * @property $type \${$item['column_name']} " . ($item['column_comment'] ?? '') . "\n"; 354 | } 355 | } else { 356 | // MySQL 表注释 357 | $tableComment = $con->query('SELECT table_comment FROM information_schema.`TABLES` WHERE table_schema = ? AND table_name = ?', [$database, $table]); 358 | if (!empty($tableComment)) { 359 | $comments = $tableComment[0]['table_comment'] ?? $tableComment[0]['TABLE_COMMENT']; 360 | $properties .= " * {$table} {$comments}" . PHP_EOL; 361 | } 362 | 363 | // MySQL 列信息 364 | foreach ($con->query("select COLUMN_NAME,DATA_TYPE,COLUMN_KEY,COLUMN_COMMENT from INFORMATION_SCHEMA.COLUMNS where table_name = '$table' and table_schema = '$database' ORDER BY ordinal_position") as $item) { 365 | if ($item['COLUMN_KEY'] === 'PRI') { 366 | $pk = $item['COLUMN_NAME']; 367 | $item['COLUMN_COMMENT'] .= "(主键)"; 368 | } 369 | $type = $this->getType($item['DATA_TYPE']); 370 | $properties .= " * @property $type \${$item['COLUMN_NAME']} {$item['COLUMN_COMMENT']}\n"; 371 | } 372 | } 373 | } catch (\Throwable $e) { 374 | echo $e; 375 | } 376 | $properties = rtrim($properties) ?: ' *'; 377 | $modelNamespace = $is_thinkorm_v2 ? 'support\think\Model' : 'think\Model'; 378 | $model_content = <<addOption('name', 'name', InputOption::VALUE_REQUIRED, 'Plugin name, for example foo/my-admin'); 21 | } 22 | 23 | /** 24 | * @param InputInterface $input 25 | * @param OutputInterface $output 26 | * @return int 27 | */ 28 | protected function execute(InputInterface $input, OutputInterface $output): int 29 | { 30 | $name = strtolower($input->getOption('name')); 31 | $output->writeln("Create Plugin $name"); 32 | if (!strpos($name, '/')) { 33 | $output->writeln('Bad name, name must contain character \'/\' , for example foo/MyAdmin'); 34 | return self::FAILURE; 35 | } 36 | 37 | $namespace = Util::nameToNamespace($name); 38 | 39 | // Create dir config/plugin/$name 40 | if (is_dir($plugin_config_path = config_path()."/plugin/$name")) { 41 | $output->writeln("Dir $plugin_config_path already exists"); 42 | return self::FAILURE; 43 | } 44 | 45 | if (is_dir($plugin_path = base_path()."/vendor/$name")) { 46 | $output->writeln("Dir $plugin_path already exists"); 47 | return self::FAILURE; 48 | } 49 | 50 | // Add psr-4 51 | if ($err = $this->addAutoloadToComposerJson($name, $namespace)) { 52 | $output->writeln("$err"); 53 | return self::FAILURE; 54 | } 55 | 56 | $this->createConfigFiles($plugin_config_path); 57 | 58 | $this->createVendorFiles($name, $namespace, $plugin_path, $output); 59 | 60 | return self::SUCCESS; 61 | } 62 | 63 | protected function addAutoloadToComposerJson($name, $namespace) 64 | { 65 | if (!is_file($composer_json_file = base_path()."/composer.json")) { 66 | return "$composer_json_file not exists"; 67 | } 68 | $composer_json = json_decode($composer_json_str = file_get_contents($composer_json_file), true); 69 | if (!$composer_json) { 70 | return "Bad $composer_json_file"; 71 | } 72 | if(isset($composer_json['autoload']['psr-4'][$namespace."\\"])) { 73 | return; 74 | } 75 | $namespace = str_replace("\\", "\\\\", $namespace); 76 | $composer_json_str = str_replace('"psr-4": {', '"psr-4": {'."\n \"$namespace\\\\\" : \"vendor/$name/src\",", $composer_json_str); 77 | file_put_contents($composer_json_file, $composer_json_str); 78 | } 79 | 80 | protected function createConfigFiles($plugin_config_path) 81 | { 82 | mkdir($plugin_config_path, 0777, true); 83 | $app_str = << true, 87 | ]; 88 | EOF; 89 | file_put_contents("$plugin_config_path/app.php", $app_str); 90 | } 91 | 92 | protected function createVendorFiles($name, $namespace, $plugin_path, $output) 93 | { 94 | mkdir("$plugin_path/src", 0777, true); 95 | $this->createComposerJson($name, $namespace, $plugin_path); 96 | if (is_callable('exec')) { 97 | exec("composer dumpautoload"); 98 | } else { 99 | $output->writeln("Please run command 'composer dumpautoload'"); 100 | } 101 | } 102 | 103 | /** 104 | * @param $name 105 | * @param $namespace 106 | * @param $dest 107 | * @return void 108 | */ 109 | protected function createComposerJson($name, $namespace, $dest) 110 | { 111 | $namespace = str_replace('\\', '\\\\', $namespace); 112 | $composer_json_content = << \$dest) { 184 | if (\$pos = strrpos(\$dest, '/')) { 185 | \$parent_dir = base_path().'/'.substr(\$dest, 0, \$pos); 186 | if (!is_dir(\$parent_dir)) { 187 | mkdir(\$parent_dir, 0777, true); 188 | } 189 | } 190 | //symlink(__DIR__ . "/\$source", base_path()."/\$dest"); 191 | copy_dir(__DIR__ . "/\$source", base_path()."/\$dest"); 192 | } 193 | } 194 | 195 | /** 196 | * uninstallByRelation 197 | * @return void 198 | */ 199 | public static function uninstallByRelation() 200 | { 201 | foreach (static::\$pathRelation as \$source => \$dest) { 202 | /*if (is_link(base_path()."/\$dest")) { 203 | unlink(base_path()."/\$dest"); 204 | }*/ 205 | remove_dir(base_path()."/\$dest"); 206 | } 207 | } 208 | } 209 | EOT; 210 | file_put_contents("$dest_dir/Install.php", $install_php_content); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/Commands/PluginDisableCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'Plugin name, for example foo/my-admin'); 20 | } 21 | 22 | /** 23 | * @param InputInterface $input 24 | * @param OutputInterface $output 25 | * @return int 26 | */ 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $name = $input->getArgument('name'); 30 | $output->writeln("Disable plugin $name"); 31 | if (!strpos($name, '/')) { 32 | $output->writeln('Bad name, name must contain character \'/\' , for example foo/MyAdmin'); 33 | return self::FAILURE; 34 | } 35 | $config_file = config_path() . "/plugin/$name/app.php"; 36 | if (!is_file($config_file)) { 37 | return self::SUCCESS; 38 | } 39 | $config = include $config_file; 40 | if (empty($config['enable'])) { 41 | return self::SUCCESS; 42 | } 43 | $config_content = file_get_contents($config_file); 44 | $config_content = preg_replace('/(\'enable\' *?=> *?)(true)/', '$1false', $config_content); 45 | file_put_contents($config_file, $config_content); 46 | return self::SUCCESS; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/Commands/PluginEnableCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'Plugin name, for example foo/my-admin'); 20 | } 21 | 22 | /** 23 | * @param InputInterface $input 24 | * @param OutputInterface $output 25 | * @return int 26 | */ 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $name = $input->getArgument('name'); 30 | $output->writeln("Enable plugin $name"); 31 | if (!strpos($name, '/')) { 32 | $output->writeln('Bad name, name must contain character \'/\' , for example foo/MyAdmin'); 33 | return self::FAILURE; 34 | } 35 | $config_file = config_path() . "/plugin/$name/app.php"; 36 | if (!is_file($config_file)) { 37 | $output->writeln("$config_file not found"); 38 | return self::FAILURE; 39 | } 40 | $config = include $config_file; 41 | if (!isset($config['enable'])) { 42 | $output->writeln("Config key 'enable' not found"); 43 | return self::FAILURE; 44 | } 45 | if ($config['enable']) { 46 | return self::SUCCESS; 47 | } 48 | $config_content = file_get_contents($config_file); 49 | $config_content = preg_replace('/(\'enable\' *?=> *?)(false)/', '$1true', $config_content); 50 | file_put_contents($config_file, $config_content); 51 | return self::SUCCESS; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/Commands/PluginExportCommand.php: -------------------------------------------------------------------------------- 1 | addOption('name', 'name', InputOption::VALUE_REQUIRED, 'Plugin name, for example foo/my-admin'); 21 | $this->addOption('source', 'source', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Directories to export'); 22 | } 23 | 24 | /** 25 | * @param InputInterface $input 26 | * @param OutputInterface $output 27 | * @return int 28 | */ 29 | protected function execute(InputInterface $input, OutputInterface $output): int 30 | { 31 | $output->writeln('Export Plugin'); 32 | $name = strtolower($input->getOption('name')); 33 | if (!strpos($name, '/')) { 34 | $output->writeln('Bad name, name must contain character \'/\' , for example foo/MyAdmin'); 35 | return self::INVALID; 36 | } 37 | $namespace = Util::nameToNamespace($name); 38 | $path_relations = $input->getOption('source'); 39 | if (!in_array("config/plugin/$name", $path_relations)) { 40 | if (is_dir("config/plugin/$name")) { 41 | $path_relations[] = "config/plugin/$name"; 42 | } 43 | } 44 | $original_dest = $dest = base_path()."/vendor/$name"; 45 | $dest .= '/src'; 46 | $this->writeInstallFile($namespace, $path_relations, $dest); 47 | $output->writeln("Create $dest/Install.php"); 48 | foreach ($path_relations as $source) { 49 | $base_path = pathinfo("$dest/$source", PATHINFO_DIRNAME); 50 | if (!is_dir($base_path)) { 51 | mkdir($base_path, 0777, true); 52 | } 53 | $output->writeln("Copy $source to $dest/$source "); 54 | copy_dir($source, "$dest/$source"); 55 | } 56 | $output->writeln("Saved $name to $original_dest"); 57 | return self::SUCCESS; 58 | } 59 | 60 | /** 61 | * @param $namespace 62 | * @param $path_relations 63 | * @param $dest_dir 64 | * @return void 65 | */ 66 | protected function writeInstallFile($namespace, $path_relations, $dest_dir) 67 | { 68 | if (!is_dir($dest_dir)) { 69 | mkdir($dest_dir, 0777, true); 70 | } 71 | $relations = []; 72 | foreach($path_relations as $relation) { 73 | $relations[$relation] = $relation; 74 | } 75 | $relations = var_export($relations, true); 76 | $install_php_content = << \$dest) { 114 | if (\$pos = strrpos(\$dest, '/')) { 115 | \$parent_dir = base_path().'/'.substr(\$dest, 0, \$pos); 116 | if (!is_dir(\$parent_dir)) { 117 | mkdir(\$parent_dir, 0777, true); 118 | } 119 | } 120 | //symlink(__DIR__ . "/\$source", base_path()."/\$dest"); 121 | copy_dir(__DIR__ . "/\$source", base_path()."/\$dest"); 122 | echo "Create \$dest\r\n"; 123 | } 124 | } 125 | 126 | /** 127 | * uninstallByRelation 128 | * @return void 129 | */ 130 | public static function uninstallByRelation() 131 | { 132 | foreach (static::\$pathRelation as \$source => \$dest) { 133 | \$path = base_path()."/\$dest"; 134 | if (!is_dir(\$path) && !is_file(\$path)) { 135 | continue; 136 | } 137 | echo "Remove \$dest\r\n"; 138 | if (is_file(\$path) || is_link(\$path)) { 139 | unlink(\$path); 140 | continue; 141 | } 142 | remove_dir(\$path); 143 | } 144 | } 145 | 146 | } 147 | EOT; 148 | file_put_contents("$dest_dir/Install.php", $install_php_content); 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/Commands/PluginInstallCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'Plugin name, for example foo/my-admin'); 21 | } 22 | 23 | /** 24 | * @param InputInterface $input 25 | * @param OutputInterface $output 26 | * @return int 27 | */ 28 | protected function execute(InputInterface $input, OutputInterface $output): int 29 | { 30 | $name = $input->getArgument('name'); 31 | $output->writeln("Execute installation for plugin $name"); 32 | $namespace = Util::nameToNamespace($name); 33 | $install_function = "\\{$namespace}\\Install::install"; 34 | $plugin_const = "\\{$namespace}\\Install::WEBMAN_PLUGIN"; 35 | if (defined($plugin_const) && is_callable($install_function)) { 36 | $install_function(); 37 | } 38 | return self::SUCCESS; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/Commands/PluginUninstallCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'Plugin name, for example foo/my-admin'); 21 | } 22 | 23 | /** 24 | * @param InputInterface $input 25 | * @param OutputInterface $output 26 | * @return int 27 | */ 28 | protected function execute(InputInterface $input, OutputInterface $output): int 29 | { 30 | $name = $input->getArgument('name'); 31 | $output->writeln("Execute uninstall for plugin $name"); 32 | if (!strpos($name, '/')) { 33 | $output->writeln('Bad name, name must contain character \'/\' , for example foo/MyAdmin'); 34 | return self::FAILURE; 35 | } 36 | $namespace = Util::nameToNamespace($name); 37 | $uninstall_function = "\\{$namespace}\\Install::uninstall"; 38 | $plugin_const = "\\{$namespace}\\Install::WEBMAN_PLUGIN"; 39 | if (defined($plugin_const) && is_callable($uninstall_function)) { 40 | $uninstall_function(); 41 | } 42 | return self::SUCCESS; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/Commands/ReStartCommand.php: -------------------------------------------------------------------------------- 1 | addOption('daemon', 'd', InputOption::VALUE_NONE, 'DAEMON mode') 19 | ->addOption('graceful', 'g', InputOption::VALUE_NONE, 'graceful stop'); 20 | } 21 | 22 | /** 23 | * @param InputInterface $input 24 | * @param OutputInterface $output 25 | * @return int 26 | */ 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | if (\class_exists(\Support\App::class)) { 30 | \Support\App::run(); 31 | return self::SUCCESS; 32 | } 33 | Application::run(); 34 | return self::SUCCESS; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Commands/ReloadCommand.php: -------------------------------------------------------------------------------- 1 | addOption('graceful', 'd', InputOption::VALUE_NONE, 'graceful reload'); 19 | } 20 | 21 | /** 22 | * @param InputInterface $input 23 | * @param OutputInterface $output 24 | * @return int 25 | */ 26 | protected function execute(InputInterface $input, OutputInterface $output): int 27 | { 28 | if (\class_exists(\Support\App::class)) { 29 | \Support\App::run(); 30 | return self::SUCCESS; 31 | } 32 | Application::run(); 33 | return self::SUCCESS; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Commands/RouteListCommand.php: -------------------------------------------------------------------------------- 1 | getMethods() as $method) { 26 | $cb = $route->getCallback(); 27 | $cb = $cb instanceof \Closure ? 'Closure' : (is_array($cb) ? json_encode($cb) : var_export($cb, 1)); 28 | $rows[] = [$route->getPath(), $method, $cb, json_encode($route->getMiddleware() ?: null), $route->getName()]; 29 | } 30 | } 31 | 32 | $table = new Table($output); 33 | $table->setHeaders($headers); 34 | $table->setRows($rows); 35 | $table->render(); 36 | return self::SUCCESS; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Commands/StartCommand.php: -------------------------------------------------------------------------------- 1 | addOption('daemon', 'd', InputOption::VALUE_NONE, 'DAEMON mode'); 18 | } 19 | 20 | /** 21 | * @param InputInterface $input 22 | * @param OutputInterface $output 23 | * @return int 24 | */ 25 | protected function execute(InputInterface $input, OutputInterface $output): int 26 | { 27 | if (\class_exists(\Support\App::class)) { 28 | \Support\App::run(); 29 | return self::SUCCESS; 30 | } 31 | Application::run(); 32 | return self::SUCCESS; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Commands/StatusCommand.php: -------------------------------------------------------------------------------- 1 | addOption('live', 'd', InputOption::VALUE_NONE, 'show live status'); 18 | } 19 | 20 | /** 21 | * @param InputInterface $input 22 | * @param OutputInterface $output 23 | * @return int 24 | */ 25 | protected function execute(InputInterface $input, OutputInterface $output): int 26 | { 27 | if (\class_exists(\Support\App::class)) { 28 | \Support\App::run(); 29 | return self::SUCCESS; 30 | } 31 | Application::run(); 32 | return self::SUCCESS; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Commands/StopCommand.php: -------------------------------------------------------------------------------- 1 | addOption('graceful', 'g',InputOption::VALUE_NONE, 'graceful stop'); 19 | } 20 | 21 | /** 22 | * @param InputInterface $input 23 | * @param OutputInterface $output 24 | * @return int 25 | */ 26 | protected function execute(InputInterface $input, OutputInterface $output): int 27 | { 28 | if (\class_exists(\Support\App::class)) { 29 | \Support\App::run(); 30 | return self::SUCCESS; 31 | } 32 | Application::run(); 33 | return self::SUCCESS; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Commands/VersionCommand.php: -------------------------------------------------------------------------------- 1 | writeln("Webman-framework $webman_framework_version"); 26 | return self::SUCCESS; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Install.php: -------------------------------------------------------------------------------- 1 | 'config/plugin/webman/console', 13 | ]; 14 | 15 | /** 16 | * Install 17 | * @return void 18 | */ 19 | public static function install() 20 | { 21 | $dest = base_path() . "/webman"; 22 | if (is_dir($dest)) { 23 | echo "Installation failed, please remove directory $dest\n"; 24 | return; 25 | } 26 | copy(__DIR__ . "/webman", $dest); 27 | chmod(base_path() . "/webman", 0755); 28 | 29 | static::installByRelation(); 30 | } 31 | 32 | /** 33 | * Uninstall 34 | * @return void 35 | */ 36 | public static function uninstall() 37 | { 38 | if (is_file(base_path()."/webman")) { 39 | unlink(base_path() . "/webman"); 40 | } 41 | self::uninstallByRelation(); 42 | } 43 | 44 | /** 45 | * installByRelation 46 | * @return void 47 | */ 48 | public static function installByRelation() 49 | { 50 | foreach (static::$pathRelation as $source => $dest) { 51 | if ($pos = strrpos($dest, '/')) { 52 | $parent_dir = base_path().'/'.substr($dest, 0, $pos); 53 | if (!is_dir($parent_dir)) { 54 | mkdir($parent_dir, 0777, true); 55 | } 56 | } 57 | copy_dir(__DIR__ . "/$source", base_path()."/$dest"); 58 | } 59 | } 60 | 61 | /** 62 | * uninstallByRelation 63 | * @return void 64 | */ 65 | public static function uninstallByRelation() 66 | { 67 | foreach (static::$pathRelation as $source => $dest) { 68 | $path = base_path()."/$dest"; 69 | if (!is_dir($path) && !is_file($path)) { 70 | continue; 71 | } 72 | remove_dir($path); 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/Util.php: -------------------------------------------------------------------------------- 1 | true, 4 | 5 | 'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build', 6 | 7 | 'phar_filename' => 'webman.phar', 8 | 9 | 'bin_filename' => 'webman.bin', 10 | 11 | 'signature_algorithm'=> Phar::SHA256, //set the signature algorithm for a phar and apply it. The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL. 12 | 13 | 'private_key_file' => '', // The file path for certificate or OpenSSL private key file. 14 | 15 | 'exclude_pattern' => '#^(?!.*(composer.json|/.github/|/.idea/|/.git/|/.setting/|/runtime/|/vendor-bin/|/build/|/vendor/webman/admin/))(.*)$#', 16 | 17 | 'exclude_files' => [ 18 | '.env', 'LICENSE', 'composer.json', 'composer.lock', 'start.php', 'webman.phar', 'webman.bin' 19 | ], 20 | 21 | 'custom_ini' => ' 22 | memory_limit = 256M 23 | ', 24 | ]; 25 | -------------------------------------------------------------------------------- /src/webman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | load(); 22 | } else { 23 | Dotenv::createMutable(run_path())->load(); 24 | } 25 | } 26 | 27 | $appConfig = require $appConfigFile; 28 | if ($timezone = $appConfig['default_timezone'] ?? '') { 29 | date_default_timezone_set($timezone); 30 | } 31 | 32 | if ($errorReporting = $appConfig['error_reporting'] ?? '') { 33 | error_reporting($errorReporting); 34 | } 35 | 36 | if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) { 37 | require_once __DIR__ . '/support/bootstrap.php'; 38 | } else { 39 | if (class_exists('Support\App')) { 40 | Support\App::loadAllConfig(['route']); 41 | } else { 42 | Config::reload(config_path(), ['route', 'container']); 43 | } 44 | } 45 | 46 | $cli = new Command(); 47 | $cli->setName('webman cli'); 48 | $cli->installInternalCommands(); 49 | if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) { 50 | $cli->installCommands($command_path); 51 | } 52 | 53 | foreach (config('plugin', []) as $firm => $projects) { 54 | if (isset($projects['app'])) { 55 | foreach (['', '/app'] as $app) { 56 | if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) { 57 | $command_path = base_path() . "/plugin/$firm{$app}/$command_str"; 58 | $cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str"); 59 | } 60 | } 61 | } 62 | foreach ($projects as $name => $project) { 63 | if (!is_array($project)) { 64 | continue; 65 | } 66 | $project['command'] ??= []; 67 | array_walk($project['command'], [$cli, 'createCommandInstance']); 68 | } 69 | } 70 | 71 | $cli->run(); 72 | --------------------------------------------------------------------------------