├── .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 =htmlspecialchars(\$name)?>
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 |
--------------------------------------------------------------------------------