This commit is contained in:
LayFi.de
2025-01-27 20:49:47 +08:00
commit 4f3242bca7
2745 changed files with 309418 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
build
vendor
.idea
.vscode
.phpunit*
composer.lock
+3
View File
@@ -0,0 +1,3 @@
# console
webman console
https://www.workerman.net/plugin/1
+37
View File
@@ -0,0 +1,37 @@
{
"name": "webman/console",
"type": "library",
"keywords": [
"webman console"
],
"homepage": "http://www.workerman.net",
"license": "MIT",
"description": "Webman console",
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"role": "Developer"
}
],
"support": {
"email": "walkor@workerman.net",
"issues": "https://github.com/webman-php/console/issues",
"forum": "http://www.workerman.net/questions",
"wiki": "http://www.workerman.net/doc/webman",
"source": "https://github.com/webman-php/console"
},
"require": {
"symfony/console": ">=5.0",
"doctrine/inflector": "^2.0"
},
"autoload": {
"psr-4": {
"Webman\\Console\\" : "src"
}
},
"require-dev": {
"workerman/webman": "^1.0"
}
}
+115
View File
@@ -0,0 +1,115 @@
<?php
namespace Webman\Console;
use support\Container;
use support\Log;
use support\Request;
use Webman\App;
use Webman\Config;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http;
use Workerman\Worker;
use Dotenv\Dotenv;
ini_set('display_errors', 'on');
error_reporting(E_ALL);
// 为了兼容老版保留的类,后续可以删除
class Application
{
public static function run()
{
$runtime_logs_path = runtime_path() . DIRECTORY_SEPARATOR . 'logs';
if ( !file_exists($runtime_logs_path) || !is_dir($runtime_logs_path) ) {
if (!mkdir($runtime_logs_path,0777,true)) {
throw new \RuntimeException("Failed to create runtime logs directory. Please check the permission.");
}
}
$runtime_views_path = runtime_path() . DIRECTORY_SEPARATOR . 'views';
if ( !file_exists($runtime_views_path) || !is_dir($runtime_views_path) ) {
if (!mkdir($runtime_views_path,0777,true)) {
throw new \RuntimeException("Failed to create runtime views directory. Please check the permission.");
}
}
if (class_exists('Dotenv\Dotenv') && file_exists(base_path() . '/.env')) {
if (method_exists('Dotenv\Dotenv', 'createUnsafeImmutable')) {
Dotenv::createUnsafeImmutable(base_path())->load();
} else {
Dotenv::createMutable(base_path())->load();
}
}
Config::reload(config_path(), ['route', 'container']);
Worker::$onMasterReload = function (){
if (function_exists('opcache_get_status')) {
if ($status = opcache_get_status()) {
if (isset($status['scripts']) && $scripts = $status['scripts']) {
foreach (array_keys($scripts) as $file) {
opcache_invalidate($file, true);
}
}
}
}
};
$config = config('server');
Worker::$pidFile = $config['pid_file'];
Worker::$stdoutFile = $config['stdout_file'];
Worker::$logFile = $config['log_file'];
Worker::$eventLoopClass = $config['event_loop'] ?? '';
TcpConnection::$defaultMaxPackageSize = $config['max_package_size'] ?? 10*1024*1024;
if (property_exists(Worker::class, 'statusFile')) {
Worker::$statusFile = $config['status_file'] ?? '';
}
if (property_exists(Worker::class, 'stopTimeout')) {
Worker::$stopTimeout = $config['stop_timeout'] ?? 2;
}
if ($config['listen']) {
$worker = new Worker($config['listen'], $config['context']);
$property_map = [
'name',
'count',
'user',
'group',
'reusePort',
'transport',
'protocol'
];
foreach ($property_map as $property) {
if (isset($config[$property])) {
$worker->$property = $config[$property];
}
}
$worker->onWorkerStart = function ($worker) {
require_once base_path() . '/support/bootstrap.php';
$app = new App($worker, Container::instance(), Log::channel('default'), app_path(), public_path());
Http::requestClass(config('app.request_class', config('server.request_class', Request::class)));
$worker->onMessage = [$app, 'onMessage'];
};
}
// Windows does not support custom processes.
if (\DIRECTORY_SEPARATOR === '/') {
foreach (config('process', []) as $process_name => $config) {
// Remove monitor process.
if (class_exists(\Phar::class, false) && \Phar::running() && 'monitor' === $process_name) {
continue;
}
worker_start($process_name, $config);
}
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
foreach ($project['process'] ?? [] as $process_name => $config) {
worker_start("plugin.$firm.$name.$process_name", $config);
}
}
}
}
Worker::runAll();
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace Webman\Console;
use RuntimeException;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command as Commands;
use support\Container;
class Command extends Application
{
public function installInternalCommands()
{
$this->installCommands(__DIR__ . '/Commands', 'Webman\Console\Commands');
}
public function installCommands($path, $namspace = 'app\command')
{
$dir_iterator = new \RecursiveDirectoryIterator($path);
$iterator = new \RecursiveIteratorIterator($dir_iterator);
foreach ($iterator as $file) {
/** @var \SplFileInfo $file */
if (strpos($file->getFilename(), '.') === 0) {
continue;
}
if ($file->getExtension() !== 'php') {
continue;
}
// abc\def.php
$relativePath = str_replace(str_replace('/', '\\', $path . '\\'), '', str_replace('/', '\\', $file->getRealPath()));
// app\command\abc
$realNamespace = trim($namspace . '\\' . trim(dirname(str_replace('\\', DIRECTORY_SEPARATOR, $relativePath)), '.'), '\\');
$realNamespace = str_replace('/', '\\', $realNamespace);
// app\command\doc\def
$class_name = trim($realNamespace . '\\' . $file->getBasename('.php'), '\\');
if (!class_exists($class_name) || !is_a($class_name, Commands::class, true)) {
continue;
}
$reflection = new \ReflectionClass($class_name);
if ($reflection->isAbstract()) {
continue;
}
$properties = $reflection->getStaticProperties();
$name = $properties['defaultName'] ?? null;
if (!$name) {
throw new RuntimeException("Command {$class_name} has no defaultName");
}
$description = $properties['defaultDescription'] ?? '';
$command = Container::get($class_name);
$command->setName($name)->setDescription($description);
$this->add($command);
}
}
}
@@ -0,0 +1,557 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Util;
class AppPluginCreateCommand extends Command
{
protected static $defaultName = 'app-plugin:create';
protected static $defaultDescription = 'App Plugin Create';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'App plugin name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Create App Plugin $name");
if (strpos($name, '/') !== false) {
$output->writeln('<error>Bad name, name must not contain character \'/\'</error>');
return self::FAILURE;
}
// Create dir config/plugin/$name
if (is_dir($plugin_config_path = base_path()."/plugin/$name")) {
$output->writeln("<error>Dir $plugin_config_path already exists</error>");
return self::FAILURE;
}
$this->createAll($name);
return self::SUCCESS;
}
/**
* @param $name
* @return void
*/
protected function createAll($name)
{
$base_path = base_path();
$this->mkdir("$base_path/plugin/$name/app/controller", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/model", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/middleware", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/view/index", 0777, true);
$this->mkdir("$base_path/plugin/$name/config", 0777, true);
$this->mkdir("$base_path/plugin/$name/public", 0777, true);
$this->mkdir("$base_path/plugin/$name/api", 0777, true);
$this->createFunctionsFile("$base_path/plugin/$name/app/functions.php");
$this->createControllerFile("$base_path/plugin/$name/app/controller/IndexController.php", $name);
$this->createViewFile("$base_path/plugin/$name/app/view/index/index.html");
$this->createConfigFiles("$base_path/plugin/$name/config", $name);
$this->createApiFiles("$base_path/plugin/$name/api", $name);
$this->createInstallSqlFile("$base_path/plugin/$name/install.sql");
}
/**
* @param $path
* @return void
*/
protected function mkdir($path)
{
if (is_dir($path)) {
return;
}
echo "Create $path\r\n";
mkdir($path, 0777, true);
}
/**
* @param $path
* @param $name
* @return void
*/
protected function createControllerFile($path, $name)
{
$content = <<<EOF
<?php
namespace plugin\\$name\\app\\controller;
use support\\Request;
class IndexController
{
public function index()
{
return view('index/index', ['name' => '$name']);
}
}
EOF;
file_put_contents($path, $content);
}
/**
* @param $path
* @return void
*/
protected function createViewFile($path)
{
$content = <<<EOF
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico"/>
<title>webman app plugin</title>
</head>
<body>
hello <?=htmlspecialchars(\$name)?>
</body>
</html>
EOF;
file_put_contents($path, $content);
}
/**
* @param $file
* @return void
*/
protected function createFunctionsFile($file)
{
$content = <<<EOF
<?php
/**
* Here is your custom functions.
*/
EOF;
file_put_contents($file, $content);
}
/**
* @param $base
* @param $name
* @return void
*/
protected function createApiFiles($base, $name)
{
$content = <<<EOF
<?php
namespace plugin\\$name\api;
use plugin\admin\api\Menu;
use support\Db;
use Throwable;
class Install
{
/**
* 数据库连接
*/
protected static \$connection = 'plugin.admin.mysql';
/**
* 安装
*
* @param \$version
* @return void
*/
public static function install(\$version)
{
// 安装数据库
static::installSql();
// 导入菜单
if(\$menus = static::getMenus()) {
Menu::import(\$menus);
}
}
/**
* 卸载
*
* @param \$version
* @return void
*/
public static function uninstall(\$version)
{
// 删除菜单
foreach (static::getMenus() as \$menu) {
Menu::delete(\$menu['key']);
}
// 卸载数据库
static::uninstallSql();
}
/**
* 更新
*
* @param \$from_version
* @param \$to_version
* @param \$context
* @return void
*/
public static function update(\$from_version, \$to_version, \$context = null)
{
// 删除不用的菜单
if (isset(\$context['previous_menus'])) {
static::removeUnnecessaryMenus(\$context['previous_menus']);
}
// 安装数据库
static::installSql();
// 导入新菜单
if (\$menus = static::getMenus()) {
Menu::import(\$menus);
}
// 执行更新操作
\$update_file = __DIR__ . '/../update.php';
if (is_file(\$update_file)) {
include \$update_file;
}
}
/**
* 更新前数据收集等
*
* @param \$from_version
* @param \$to_version
* @return array|array[]
*/
public static function beforeUpdate(\$from_version, \$to_version)
{
// 在更新之前获得老菜单,通过context传递给 update
return ['previous_menus' => static::getMenus()];
}
/**
* 获取菜单
*
* @return array|mixed
*/
public static function getMenus()
{
clearstatcache();
if (is_file(\$menu_file = __DIR__ . '/../config/menu.php')) {
\$menus = include \$menu_file;
return \$menus ?: [];
}
return [];
}
/**
* 删除不需要的菜单
*
* @param \$previous_menus
* @return void
*/
public static function removeUnnecessaryMenus(\$previous_menus)
{
\$menus_to_remove = array_diff(Menu::column(\$previous_menus, 'name'), Menu::column(static::getMenus(), 'name'));
foreach (\$menus_to_remove as \$name) {
Menu::delete(\$name);
}
}
/**
* 安装SQL
*
* @return void
*/
protected static function installSql()
{
static::importSql(__DIR__ . '/../install.sql');
}
/**
* 卸载SQL
*
* @return void
*/
protected static function uninstallSql() {
// 如果卸载数据库文件存在责直接使用
\$uninstallSqlFile = __DIR__ . '/../uninstall.sql';
if (is_file(\$uninstallSqlFile)) {
static::importSql(\$uninstallSqlFile);
return;
}
// 否则根据install.sql生成卸载数据库文件uninstall.sql
\$installSqlFile = __DIR__ . '/../install.sql';
if (!is_file(\$installSqlFile)) {
return;
}
\$installSql = file_get_contents(\$installSqlFile);
preg_match_all('/CREATE TABLE `(.+?)`/si', \$installSql, \$matches);
\$dropSql = '';
foreach (\$matches[1] as \$table) {
\$dropSql .= "DROP TABLE IF EXISTS `\$table`;\\n";
}
file_put_contents(\$uninstallSqlFile, \$dropSql);
static::importSql(\$uninstallSqlFile);
unlink(\$uninstallSqlFile);
}
/**
* 导入数据库
*
* @return void
*/
public static function importSql(\$mysqlDumpFile)
{
if (!\$mysqlDumpFile || !is_file(\$mysqlDumpFile)) {
return;
}
foreach (explode(';', file_get_contents(\$mysqlDumpFile)) as \$sql) {
if (\$sql = trim(\$sql)) {
try {
Db::connection(static::\$connection)->statement(\$sql);
} catch (Throwable \$e) {}
}
}
}
}
EOF;
file_put_contents("$base/Install.php", $content);
}
/**
* @return void
*/
protected function createInstallSqlFile($file)
{
file_put_contents($file, '');
}
/**
* @param $base
* @param $name
* @return void
*/
protected function createConfigFiles($base, $name)
{
// app.php
$content = <<<EOF
<?php
use support\\Request;
return [
'debug' => true,
'controller_suffix' => 'Controller',
'controller_reuse' => false,
'version' => '1.0.0'
];
EOF;
file_put_contents("$base/app.php", $content);
// menu.php
$content = <<<EOF
<?php
return [];
EOF;
file_put_contents("$base/menu.php", $content);
// autoload.php
$content = <<<EOF
<?php
return [
'files' => [
base_path() . '/plugin/$name/app/functions.php',
]
];
EOF;
file_put_contents("$base/autoload.php", $content);
// container.php
$content = <<<EOF
<?php
return new Webman\\Container;
EOF;
file_put_contents("$base/container.php", $content);
// database.php
$content = <<<EOF
<?php
return [];
EOF;
file_put_contents("$base/database.php", $content);
// exception.php
$content = <<<EOF
<?php
return [
'' => support\\exception\\Handler::class,
];
EOF;
file_put_contents("$base/exception.php", $content);
// log.php
$content = <<<EOF
<?php
return [
'default' => [
'handlers' => [
[
'class' => Monolog\\Handler\\RotatingFileHandler::class,
'constructor' => [
runtime_path() . '/logs/$name.log',
7,
Monolog\\Logger::DEBUG,
],
'formatter' => [
'class' => Monolog\\Formatter\\LineFormatter::class,
'constructor' => [null, 'Y-m-d H:i:s', true],
],
]
],
],
];
EOF;
file_put_contents("$base/log.php", $content);
// middleware.php
$content = <<<EOF
<?php
return [
'' => [
]
];
EOF;
file_put_contents("$base/middleware.php", $content);
// process.php
$content = <<<EOF
<?php
return [];
EOF;
file_put_contents("$base/process.php", $content);
// redis.php
$content = <<<EOF
<?php
return [
'default' => [
'host' => '127.0.0.1',
'password' => null,
'port' => 6379,
'database' => 0,
],
];
EOF;
file_put_contents("$base/redis.php", $content);
// route.php
$content = <<<EOF
<?php
use Webman\\Route;
EOF;
file_put_contents("$base/route.php", $content);
// static.php
$content = <<<EOF
<?php
return [
'enable' => true,
'middleware' => [], // Static file Middleware
];
EOF;
file_put_contents("$base/static.php", $content);
// translation.php
$content = <<<EOF
<?php
return [
// Default language
'locale' => 'zh_CN',
// Fallback language
'fallback_locale' => ['zh_CN', 'en'],
// Folder where language files are stored
'path' => base_path() . "/plugin/$name/resource/translations",
];
EOF;
file_put_contents("$base/translation.php", $content);
// view.php
$content = <<<EOF
<?php
use support\\view\\Raw;
use support\\view\\Twig;
use support\\view\\Blade;
use support\\view\\ThinkPHP;
return [
'handler' => Raw::class
];
EOF;
file_put_contents("$base/view.php", $content);
// thinkorm.php
$content = <<<EOF
<?php
return [];
EOF;
file_put_contents("$base/thinkorm.php", $content);
}
}
@@ -0,0 +1,44 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Util;
class AppPluginInstallCommand extends Command
{
protected static $defaultName = 'app-plugin:install';
protected static $defaultDescription = 'App Plugin Install';
/**
* @return void
*/
protected function configure()
{
$this->setName(static::$defaultName)->setDescription(static::$defaultDescription);
$this->addArgument('name', InputArgument::REQUIRED, 'App plugin name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Install App Plugin $name");
$class = "\\plugin\\$name\\api\\Install";
if (!method_exists($class, 'install')) {
throw new \RuntimeException("Method $class::install not exists");
}
call_user_func([$class, 'install'], config("plugin.$name.app.version"));
return self::SUCCESS;
}
}
@@ -0,0 +1,42 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Util;
class AppPluginUninstallCommand extends Command
{
protected static $defaultName = 'app-plugin:uninstall';
protected static $defaultDescription = 'App Plugin Uninstall';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'App plugin name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Uninstall App Plugin $name");
$class = "\\plugin\\$name\\api\\Install";
if (!method_exists($class, 'uninstall')) {
throw new \RuntimeException("Method $class::uninstall not exists");
}
call_user_func([$class, 'uninstall'], config("plugin.$name.app.version"));
return self::SUCCESS;
}
}
@@ -0,0 +1,42 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Util;
class AppPluginUpdateCommand extends Command
{
protected static $defaultName = 'app-plugin:update';
protected static $defaultDescription = 'App Plugin Update';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'App plugin name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Update App Plugin $name");
$class = "\\plugin\\$name\\api\\Install";
if (!method_exists($class, 'update')) {
throw new \RuntimeException("Method $class::update not exists");
}
call_user_func([$class, 'update'], config("plugin.$name.app.version"), config("plugin.$name.app.version"));
return self::SUCCESS;
}
}
@@ -0,0 +1,85 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Util;
use ZipArchive;
use Exception;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
class AppPluginZipCommand extends Command
{
protected static $defaultName = 'app-plugin:zip';
protected static $defaultDescription = 'App Plugin Zip';
/**
* @return void
*/
protected function configure()
{
$this->setName(static::$defaultName)->setDescription(static::$defaultDescription);
$this->addArgument('name', InputArgument::REQUIRED, 'App plugin name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Zip App Plugin $name");
$sourceDir = base_path('plugin' . DIRECTORY_SEPARATOR . $name);
$zipFilePath = base_path('plugin' . DIRECTORY_SEPARATOR . $name . '.zip');
if (!is_dir($sourceDir)) {
$output->writeln("Plugin $name not exists");
return self::FAILURE;
}
if (is_file($zipFilePath)) {
unlink($zipFilePath);
}
$this->zipDirectory($name, $sourceDir, $zipFilePath);
return self::SUCCESS;
}
/**
* @param $name
* @param $sourceDir
* @param $zipFilePath
* @return bool
* @throws Exception
*/
protected function zipDirectory($name, $sourceDir, $zipFilePath) {
$zip = new ZipArchive();
if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
throw new Exception("cannot open <$zipFilePath>\n");
}
$sourceDir = realpath($sourceDir);
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($sourceDir),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = $name . DIRECTORY_SEPARATOR . substr($filePath, strlen($sourceDir) + 1);
$zip->addFile($filePath, $relativePath);
}
}
return $zip->close();
}
}
+149
View File
@@ -0,0 +1,149 @@
<?php
namespace Webman\Console\Commands;
use Phar;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZipArchive;
class BuildBinCommand extends BuildPharCommand
{
protected static $defaultName = 'build:bin';
protected static $defaultDescription = 'build bin';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('version', InputArgument::OPTIONAL, 'PHP version');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->checkEnv();
$output->writeln('Phar packing...');
$version = $input->getArgument('version');
if (!$version) {
$version = (float)PHP_VERSION;
}
$version = $version >= 8.0 ? $version : 8.1;
$supportZip = class_exists(ZipArchive::class);
$microZipFileName = $supportZip ? "php$version.micro.sfx.zip" : "php$version.micro.sfx";
$pharFileName = config('plugin.webman.console.app.phar_filename', 'webman.phar');
$binFileName = config('plugin.webman.console.app.bin_filename', 'webman.bin');
$this->buildDir = config('plugin.webman.console.app.build_dir', base_path() . '/build');
$customIni = config('plugin.webman.console.app.custom_ini', '');
$binFile = "$this->buildDir/$binFileName";
$pharFile = "$this->buildDir/$pharFileName";
$zipFile = "$this->buildDir/$microZipFileName";
$sfxFile = "$this->buildDir/php$version.micro.sfx";
$customIniHeaderFile = "$this->buildDir/custominiheader.bin";
// 打包
$command = new BuildPharCommand();
$command->execute($input, $output);
// 下载 micro.sfx.zip
if (!is_file($sfxFile) && !is_file($zipFile)) {
$domain = 'download.workerman.net';
$output->writeln("\r\nDownloading PHP$version ...");
if (extension_loaded('openssl')) {
$context = stream_context_create([
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
]
]);
$client = stream_socket_client("ssl://$domain:443", $context);
} else {
$client = stream_socket_client("tcp://$domain:80");
}
fwrite($client, "GET /php/$microZipFileName HTTP/1.0\r\nAccept: text/html\r\nHost: $domain\r\nUser-Agent: webman/console\r\n\r\n");
$bodyLength = 0;
$bodyBuffer = '';
$lastPercent = 0;
while (true) {
$buffer = fread($client, 65535);
if ($buffer !== false) {
$bodyBuffer .= $buffer;
if (!$bodyLength && $pos = strpos($bodyBuffer, "\r\n\r\n")) {
if (!preg_match('/Content-Length: (\d+)\r\n/', $bodyBuffer, $match)) {
$output->writeln("Download php$version.micro.sfx.zip failed");
return self::FAILURE;
}
$firstLine = substr($bodyBuffer, 9, strpos($bodyBuffer, "\r\n") - 9);
if (!preg_match('/200 /', $bodyBuffer)) {
$output->writeln("Download php$version.micro.sfx.zip failed, $firstLine");
return self::FAILURE;
}
$bodyLength = (int)$match[1];
$bodyBuffer = substr($bodyBuffer, $pos + 4);
}
}
$receiveLength = strlen($bodyBuffer);
$percent = ceil($receiveLength * 100 / $bodyLength);
if ($percent != $lastPercent) {
echo '[' . str_pad('', $percent, '=') . '>' . str_pad('', 100 - $percent) . "$percent%]";
echo $percent < 100 ? "\r" : "\n";
}
$lastPercent = $percent;
if ($bodyLength && $receiveLength >= $bodyLength) {
file_put_contents($zipFile, $bodyBuffer);
break;
}
if ($buffer === false || !is_resource($client) || feof($client)) {
$output->writeln("Fail donwload PHP$version ...");
return self::FAILURE;
}
}
} else {
$output->writeln("\r\nUse PHP$version ...");
}
// 解压
if (!is_file($sfxFile) && $supportZip) {
$zip = new ZipArchive;
$zip->open($zipFile, ZipArchive::CHECKCONS);
$zip->extractTo($this->buildDir);
}
// 生成二进制文件
file_put_contents($binFile, file_get_contents($sfxFile));
// 自定义INI
if (!empty($customIni)) {
if (file_exists($customIniHeaderFile)) {
unlink($customIniHeaderFile);
}
$f = fopen($customIniHeaderFile, 'wb');
fwrite($f, "\xfd\xf6\x69\xe6");
fwrite($f, pack('N', strlen($customIni)));
fwrite($f, $customIni);
fclose($f);
file_put_contents($binFile, file_get_contents($customIniHeaderFile),FILE_APPEND);
unlink($customIniHeaderFile);
}
file_put_contents($binFile, file_get_contents($pharFile), FILE_APPEND);
// 添加执行权限
chmod($binFile, 0755);
$output->writeln("\r\nSaved $binFileName to $binFile\r\nBuild Success!\r\n");
return self::SUCCESS;
}
}
+148
View File
@@ -0,0 +1,148 @@
<?php
namespace Webman\Console\Commands;
use Phar;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class BuildPharCommand extends Command
{
protected static $defaultName = 'build:phar';
protected static $defaultDescription = 'Can be easily packaged a project into phar files. Easy to distribute and use.';
protected $buildDir = '';
public function __construct(?string $name = null)
{
parent::__construct($name);
$this->buildDir = config('plugin.webman.console.app.build_dir', base_path() . '/build');
}
/**
* @return void
* @deprecated 暂时保留 phar:pack 命令,下一个版本再取消
*/
protected function configure()
{
$this->setAliases([
'phar:pack',
]);
parent::configure();
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->checkEnv();
if (!file_exists($this->buildDir) && !is_dir($this->buildDir)) {
if (!mkdir($this->buildDir,0777,true)) {
throw new RuntimeException("Failed to create phar file output directory. Please check the permission.");
}
}
$phar_filename = config('plugin.webman.console.app.phar_filename', 'webman.phar');
if (empty($phar_filename)) {
throw new RuntimeException('Please set the phar filename.');
}
$phar_file = rtrim($this->buildDir,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $phar_filename;
if (file_exists($phar_file)) {
unlink($phar_file);
}
$exclude_pattern = config('plugin.webman.console.app.exclude_pattern','');
$phar = new Phar($phar_file,0,'webman');
$phar->startBuffering();
$signature_algorithm = config('plugin.webman.console.app.signature_algorithm');
if (!in_array($signature_algorithm,[Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512,Phar::OPENSSL])) {
throw new RuntimeException('The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL.');
}
if ($signature_algorithm === Phar::OPENSSL) {
$private_key_file = config('plugin.webman.console.app.private_key_file');
if (!file_exists($private_key_file)) {
throw new RuntimeException("If the value of the signature algorithm is 'Phar::OPENSSL', you must set the private key file.");
}
$private = openssl_get_privatekey(file_get_contents($private_key_file));
$pkey = '';
openssl_pkey_export($private, $pkey);
$phar->setSignatureAlgorithm($signature_algorithm, $pkey);
} else {
$phar->setSignatureAlgorithm($signature_algorithm);
}
$phar->buildFromDirectory(BASE_PATH,$exclude_pattern);
$exclude_files = config('plugin.webman.console.app.exclude_files',[]);
// 打包生成的phar和bin文件是面向生产环境的,所以以下这些命令没有任何意义,执行的话甚至会出错,需要排除在外。
$exclude_command_files = [
'AppPluginCreateCommand.php',
'BuildBinCommand.php',
'BuildPharCommand.php',
'MakeBootstrapCommand.php',
'MakeCommandCommand.php',
'MakeControllerCommand.php',
'MakeMiddlewareCommand.php',
'MakeModelCommand.php',
'PluginCreateCommand.php',
'PluginDisableCommand.php',
'PluginEnableCommand.php',
'PluginExportCommand.php',
'PluginInstallCommand.php',
'PluginUninstallCommand.php'
];
$exclude_command_files = array_map(function ($cmd_file) {
return 'vendor/webman/console/src/Commands/'.$cmd_file;
},$exclude_command_files);
$exclude_files = array_unique(array_merge($exclude_command_files,$exclude_files));
foreach ($exclude_files as $file) {
if($phar->offsetExists($file)){
$phar->delete($file);
}
}
$output->writeln('Files collect complete, begin add file to Phar.');
$phar->setStub("#!/usr/bin/env php
<?php
define('IN_PHAR', true);
Phar::mapPhar('webman');
require 'phar://webman/webman';
__HALT_COMPILER();
");
$output->writeln('Write requests to the Phar archive, save changes to disk.');
$phar->stopBuffering();
unset($phar);
return self::SUCCESS;
}
/**
* @throws RuntimeException
*/
public function checkEnv(): void
{
if (!class_exists(Phar::class, false)) {
throw new RuntimeException("The 'phar' extension is required for build phar package");
}
if (ini_get('phar.readonly')) {
$command = static::$defaultName;
throw new RuntimeException(
"The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0 ./webman $command'"
);
}
}
}
@@ -0,0 +1,29 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Webman\Console\Application;
class ConnectionsCommand extends Command
{
protected static $defaultName = 'connections';
protected static $defaultDescription = 'Get worker connections.';
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (\class_exists(\Support\App::class)) {
\Support\App::run();
return self::SUCCESS;
}
Application::run();
return self::SUCCESS;
}
}
@@ -0,0 +1,108 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class FixDisbaleFunctionsCommand extends Command
{
protected static $defaultName = 'fix-disable-functions';
protected static $defaultDescription = 'Fix disbale_functions in php.ini';
/**
* @return void
*/
protected function configure()
{
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$php_ini_file = php_ini_loaded_file();
if (!$php_ini_file) {
$output->writeln('<error>Can not find php.ini</error>');
return self::FAILURE;
}
$output->writeln("Location $php_ini_file");
$disable_functions_str = ini_get("disable_functions");
if (!$disable_functions_str) {
$output->writeln('<success>Ok</success>');
}
$functions_required = [
"stream_socket_server",
"stream_socket_accept",
"stream_socket_client",
"pcntl_signal_dispatch",
"pcntl_signal",
"pcntl_alarm",
"pcntl_fork",
"posix_getuid",
"posix_getpwuid",
"posix_kill",
"posix_setsid",
"posix_getpid",
"posix_getpwnam",
"posix_getgrnam",
"posix_getgid",
"posix_setgid",
"posix_initgroups",
"posix_setuid",
"posix_isatty",
"proc_open",
"proc_get_status",
"proc_close",
"shell_exec",
"exec",
];
$has_disbaled_functions = false;
foreach ($functions_required as $func) {
if (strpos($disable_functions_str, $func) !== false) {
$has_disbaled_functions = true;
break;
}
}
$disable_functions = explode(",", $disable_functions_str);
$disable_functions_removed = [];
foreach ($disable_functions as $index => $func) {
$func = trim($func);
foreach ($functions_required as $func_prefix) {
if (strpos($func, $func_prefix) === 0) {
$disable_functions_removed[$func] = $func;
unset($disable_functions[$index]);
}
}
}
$php_ini_content = file_get_contents($php_ini_file);
if (!$php_ini_content) {
$output->writeln("<error>$php_ini_file content empty</error>");
return self::FAILURE;
}
$new_disable_functions_str = implode(",", $disable_functions);
$php_ini_content = preg_replace("/\ndisable_functions *?=[^\n]+/", "\ndisable_functions = $new_disable_functions_str", $php_ini_content);
file_put_contents($php_ini_file, $php_ini_content);
foreach ($disable_functions_removed as $func) {
$output->write(str_pad($func, 30));
$output->writeln('<info>enabled</info>');
}
$output->writeln('<info>Succes</info>');
return self::SUCCESS;
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class InstallCommand extends Command
{
protected static $defaultName = 'install';
protected static $defaultDescription = 'Execute webman installation script';
/**
* @return void
*/
protected function configure()
{
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln("Execute installation for webman");
$install_function = "\\Webman\\Install::install";
if (is_callable($install_function)) {
$install_function();
return self::SUCCESS;
}
$output->writeln('<error>This command requires webman-framework version >= 1.3.0</error>');
return self::FAILURE;
}
}
@@ -0,0 +1,126 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Webman\Console\Util;
class MakeBootstrapCommand extends Command
{
protected static $defaultName = 'make:bootstrap';
protected static $defaultDescription = 'Make bootstrap';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Bootstrap name');
$this->addArgument('enable', InputArgument::OPTIONAL, 'Enable or not');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$enable = in_array($input->getArgument('enable'), ['no', '0', 'false', 'n']) ? false : true;
$output->writeln("Make bootstrap $name");
$name = str_replace('\\', '/', $name);
if (!$bootstrap_str = Util::guessPath(app_path(), 'bootstrap')) {
$bootstrap_str = Util::guessPath(app_path(), 'controller') === 'Controller' ? 'Bootstrap' : 'bootstrap';
}
$upper = $bootstrap_str === 'Bootstrap';
if (!($pos = strrpos($name, '/'))) {
$name = ucfirst($name);
$file = app_path() . DIRECTORY_SEPARATOR . $bootstrap_str . DIRECTORY_SEPARATOR . "$name.php";
$namespace = $upper ? 'App\Bootstrap' : 'app\bootstrap';
} else {
if($real_name = Util::guessPath(app_path(), $name)) {
$name = $real_name;
}
if ($upper && !$real_name) {
$name = preg_replace_callback('/\/([a-z])/', function ($matches) {
return '/' . strtoupper($matches[1]);
}, ucfirst($name));
}
$path = "$bootstrap_str/" . substr($upper ? ucfirst($name) : $name, 0, $pos);
$name = ucfirst(substr($name, $pos + 1));
$file = app_path() . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . "$name.php";
$namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path);
}
if (is_file($file)) {
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion("$file already exists. Do you want to override it? (yes/no)", false);
if (!$helper->ask($input, $output, $question)) {
return Command::SUCCESS;
}
}
$this->createBootstrap($name, $namespace, $file);
if ($enable) {
$this->addConfig("$namespace\\$name", config_path() . '/bootstrap.php');
}
return self::SUCCESS;
}
/**
* @param $name
* @param $namespace
* @param $file
* @return void
*/
protected function createBootstrap($name, $namespace, $file)
{
$path = pathinfo($file, PATHINFO_DIRNAME);
if (!is_dir($path)) {
mkdir($path, 0777, true);
}
$bootstrap_content = <<<EOF
<?php
namespace $namespace;
use Webman\Bootstrap;
class $name implements Bootstrap
{
public static function start(\$worker)
{
// Is it console environment ?
\$is_console = !\$worker;
if (\$is_console) {
// If you do not want to execute this in console, just return.
return;
}
}
}
EOF;
file_put_contents($file, $bootstrap_content);
}
public function addConfig($class, $config_file)
{
$config = include $config_file;
if(!in_array($class, $config ?? [])) {
$config_file_content = file_get_contents($config_file);
$config_file_content = preg_replace('/\];/', " $class::class,\n];", $config_file_content);
file_put_contents($config_file, $config_file_content);
}
}
}
@@ -0,0 +1,126 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Webman\Console\Util;
class MakeCommandCommand extends Command
{
protected static $defaultName = 'make:command';
protected static $defaultDescription = 'Make command';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Command name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$command = $name = trim($input->getArgument('name'));
$output->writeln("Make command $name");
// make:command 不支持子目录
$name = str_replace(['\\', '/'], '', $name);
if (!$command_str = Util::guessPath(app_path(), 'command')) {
$command_str = Util::guessPath(app_path(), 'controller') === 'Controller' ? 'Command' : 'command';
}
$items= explode(':', $name);
$name='';
foreach ($items as $item) {
$name.=ucfirst($item);
}
$file = app_path() . DIRECTORY_SEPARATOR . $command_str . DIRECTORY_SEPARATOR . "$name.php";
$upper = $command_str === 'Command';
$namespace = $upper ? 'App\Command' : 'app\command';
if (is_file($file)) {
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion("$file already exists. Do you want to override it? (yes/no)", false);
if (!$helper->ask($input, $output, $question)) {
return Command::SUCCESS;
}
}
$this->createCommand($name, $namespace, $file, $command);
return self::SUCCESS;
}
protected function getClassName($name)
{
return preg_replace_callback('/:([a-zA-Z])/', function ($matches) {
return strtoupper($matches[1]);
}, ucfirst($name)) . 'Command';
}
/**
* @param $name
* @param $namespace
* @param $path
* @return void
*/
protected function createCommand($name, $namespace, $file, $command)
{
$path = pathinfo($file, PATHINFO_DIRNAME);
if (!is_dir($path)) {
mkdir($path, 0777, true);
}
$desc = str_replace(':', ' ', $command);
$command_content = <<<EOF
<?php
namespace $namespace;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
class $name extends Command
{
protected static \$defaultName = '$command';
protected static \$defaultDescription = '$desc';
/**
* @return void
*/
protected function configure()
{
\$this->addArgument('name', InputArgument::OPTIONAL, 'Name description');
}
/**
* @param InputInterface \$input
* @param OutputInterface \$output
* @return int
*/
protected function execute(InputInterface \$input, OutputInterface \$output): int
{
\$name = \$input->getArgument('name');
\$output->writeln('Hello $command');
return self::SUCCESS;
}
}
EOF;
file_put_contents($file, $command_content);
}
}
@@ -0,0 +1,113 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Webman\Console\Util;
class MakeControllerCommand extends Command
{
protected static $defaultName = 'make:controller';
protected static $defaultDescription = 'Make controller';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Controller name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Make controller $name");
$suffix = config('app.controller_suffix', '');
if ($suffix && !strpos($name, $suffix)) {
$name .= $suffix;
}
$name = str_replace('\\', '/', $name);
if (!($pos = strrpos($name, '/'))) {
$name = ucfirst($name);
$controller_str = Util::guessPath(app_path(), 'controller') ?: 'controller';
$file = app_path() . DIRECTORY_SEPARATOR . $controller_str . DIRECTORY_SEPARATOR . "$name.php";
$namespace = $controller_str === 'Controller' ? 'App\Controller' : 'app\controller';
} else {
$name_str = substr($name, 0, $pos);
if($real_name_str = Util::guessPath(app_path(), $name_str)) {
$name_str = $real_name_str;
} else if ($real_section_name = Util::guessPath(app_path(), strstr($name_str, '/', true))) {
$upper = strtolower($real_section_name[0]) !== $real_section_name[0];
} else if ($real_base_controller = Util::guessPath(app_path(), 'controller')) {
$upper = strtolower($real_base_controller[0]) !== $real_base_controller[0];
}
$upper = $upper ?? strtolower($name_str[0]) !== $name_str[0];
if ($upper && !$real_name_str) {
$name_str = preg_replace_callback('/\/([a-z])/', function ($matches) {
return '/' . strtoupper($matches[1]);
}, ucfirst($name_str));
}
$path = "$name_str/" . ($upper ? 'Controller' : 'controller');
$name = ucfirst(substr($name, $pos + 1));
$file = app_path() . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . "$name.php";
$namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path);
}
if (is_file($file)) {
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion("$file already exists. Do you want to override it? (yes/no)", false);
if (!$helper->ask($input, $output, $question)) {
return Command::SUCCESS;
}
}
$this->createController($name, $namespace, $file);
return self::SUCCESS;
}
/**
* @param $name
* @param $namespace
* @param $file
* @return void
*/
protected function createController($name, $namespace, $file)
{
$path = pathinfo($file, PATHINFO_DIRNAME);
if (!is_dir($path)) {
mkdir($path, 0777, true);
}
$controller_content = <<<EOF
<?php
namespace $namespace;
use support\Request;
class $name
{
public function index(Request \$request)
{
return response(__CLASS__);
}
}
EOF;
file_put_contents($file, $controller_content);
}
}
@@ -0,0 +1,107 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Webman\Console\Util;
class MakeMiddlewareCommand extends Command
{
protected static $defaultName = 'make:middleware';
protected static $defaultDescription = 'Make middleware';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Middleware name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Make middleware $name");
$name = str_replace('\\', '/', $name);
if (!$middleware_str = Util::guessPath(app_path(), 'middleware')) {
$middleware_str = Util::guessPath(app_path(), 'controller') === 'Controller' ? 'Middleware' : 'middleware';
}
$upper = $middleware_str === 'Middleware';
if (!($pos = strrpos($name, '/'))) {
$name = ucfirst($name);
$file = app_path() . DIRECTORY_SEPARATOR . $middleware_str . DIRECTORY_SEPARATOR . "$name.php";
$namespace = $upper ? 'App\Middleware' : 'app\middleware';
} else {
if($real_name = Util::guessPath(app_path(), $name)) {
$name = $real_name;
}
if ($upper && !$real_name) {
$name = preg_replace_callback('/\/([a-z])/', function ($matches) {
return '/' . strtoupper($matches[1]);
}, ucfirst($name));
}
$path = "$middleware_str/" . substr($upper ? ucfirst($name) : $name, 0, $pos);
$name = ucfirst(substr($name, $pos + 1));
$file = app_path() . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . "$name.php";
$namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path);
}
if (is_file($file)) {
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion("$file already exists. Do you want to override it? (yes/no)", false);
if (!$helper->ask($input, $output, $question)) {
return Command::SUCCESS;
}
}
$this->createMiddleware($name, $namespace, $file);
return self::SUCCESS;
}
/**
* @param $name
* @param $namespace
* @param $path
* @return void
*/
protected function createMiddleware($name, $namespace, $file)
{
$path = pathinfo($file, PATHINFO_DIRNAME);
if (!is_dir($path)) {
mkdir($path, 0777, true);
}
$middleware_content = <<<EOF
<?php
namespace $namespace;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class $name implements MiddlewareInterface
{
public function process(Request \$request, callable \$handler) : Response
{
return \$handler(\$request);
}
}
EOF;
file_put_contents($file, $middleware_content);
}
}
+309
View File
@@ -0,0 +1,309 @@
<?php
namespace Webman\Console\Commands;
use Doctrine\Inflector\InflectorFactory;
use support\Db;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Webman\Console\Util;
class MakeModelCommand extends Command
{
protected static $defaultName = 'make:model';
protected static $defaultDescription = 'Make model';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Model name');
$this->addArgument('type', InputArgument::OPTIONAL, 'Type');
$this->addOption('connection', 'c', InputOption::VALUE_OPTIONAL, 'Select database connection. ');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$name = Util::nameToClass($name);
$type = $input->getArgument('type');
$connection = $input->getOption('connection');
$output->writeln("Make model $name");
if (!($pos = strrpos($name, '/'))) {
$name = ucfirst($name);
$model_str = Util::guessPath(app_path(), 'model') ?: 'model';
$file = app_path() . DIRECTORY_SEPARATOR . $model_str . DIRECTORY_SEPARATOR . "$name.php";
$namespace = $model_str === 'Model' ? 'App\Model' : 'app\model';
} else {
$name_str = substr($name, 0, $pos);
if($real_name_str = Util::guessPath(app_path(), $name_str)) {
$name_str = $real_name_str;
} else if ($real_section_name = Util::guessPath(app_path(), strstr($name_str, '/', true))) {
$upper = strtolower($real_section_name[0]) !== $real_section_name[0];
} else if ($real_base_controller = Util::guessPath(app_path(), 'controller')) {
$upper = strtolower($real_base_controller[0]) !== $real_base_controller[0];
}
$upper = $upper ?? strtolower($name_str[0]) !== $name_str[0];
if ($upper && !$real_name_str) {
$name_str = preg_replace_callback('/\/([a-z])/', function ($matches) {
return '/' . strtoupper($matches[1]);
}, ucfirst($name_str));
}
$path = "$name_str/" . ($upper ? 'Model' : 'model');
$name = ucfirst(substr($name, $pos + 1));
$file = app_path() . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . "$name.php";
$namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path);
}
if (!$type) {
$database = config('database');
if (isset($database['default']) && strpos($database['default'], 'plugin.') === 0) {
$database = false;
}
$thinkorm = config('thinkorm');
if (isset($thinkorm['default']) && strpos($thinkorm['default'], 'plugin.') === 0) {
$thinkorm = false;
}
$type = !$database && $thinkorm ? 'tp' : 'laravel';
}
if (is_file($file)) {
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion("$file already exists. Do you want to override it? (yes/no)", false);
if (!$helper->ask($input, $output, $question)) {
return Command::SUCCESS;
}
}
if ($type == 'tp') {
$this->createTpModel($name, $namespace, $file, $connection);
} else {
$this->createModel($name, $namespace, $file, $connection);
}
return self::SUCCESS;
}
/**
* @param $class
* @param $namespace
* @param $file
* @param string|null $connection
* @return void
*/
protected function createModel($class, $namespace, $file, $connection = null)
{
$path = pathinfo($file, PATHINFO_DIRNAME);
if (!is_dir($path)) {
mkdir($path, 0777, true);
}
$table = Util::classToName($class);
$table_val = 'null';
$pk = 'id';
$properties = '';
$connection = $connection ?: 'mysql';
try {
$prefix = config("database.connections.$connection.prefix") ?? '';
$database = config("database.connections.$connection.database");
$inflector = InflectorFactory::create()->build();
$table_plura = $inflector->pluralize($inflector->tableize($class));
$con = Db::connection($connection);
if ($con->select("show tables like '{$prefix}{$table_plura}'")) {
$table_val = "'$table'";
$table = "{$prefix}{$table_plura}";
} else if ($con->select("show tables like '{$prefix}{$table}'")) {
$table_val = "'$table'";
$table = "{$prefix}{$table}";
}
$tableComment = $con->select('SELECT table_comment FROM information_schema.`TABLES` WHERE table_schema = ? AND table_name = ?', [$database, $table]);
if (!empty($tableComment)) {
$comments = $tableComment[0]->table_comment ?? $tableComment[0]->TABLE_COMMENT;
$properties .= " * {$table} {$comments}" . PHP_EOL;
}
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) {
if ($item->COLUMN_KEY === 'PRI') {
$pk = $item->COLUMN_NAME;
$item->COLUMN_COMMENT .= "(主键)";
}
$type = $this->getType($item->DATA_TYPE);
$properties .= " * @property $type \${$item->COLUMN_NAME} {$item->COLUMN_COMMENT}\n";
}
} catch (\Throwable $e) {
echo $e->getMessage() . PHP_EOL;
}
$properties = rtrim($properties) ?: ' *';
$model_content = <<<EOF
<?php
namespace $namespace;
use support\Model;
/**
$properties
*/
class $class extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected \$connection = '$connection';
/**
* The table associated with the model.
*
* @var string
*/
protected \$table = $table_val;
/**
* The primary key associated with the table.
*
* @var string
*/
protected \$primaryKey = '$pk';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public \$timestamps = false;
}
EOF;
file_put_contents($file, $model_content);
}
/**
* @param $class
* @param $namespace
* @param $file
* @param string|null $connection
* @return void
*/
protected function createTpModel($class, $namespace, $file, $connection = null)
{
$path = pathinfo($file, PATHINFO_DIRNAME);
if (!is_dir($path)) {
mkdir($path, 0777, true);
}
$table = Util::classToName($class);
$table_val = 'null';
$pk = 'id';
$properties = '';
$connection = $connection ?: 'mysql';
try {
$prefix = config("thinkorm.connections.$connection.prefix") ?? '';
$database = config("thinkorm.connections.$connection.database");
$con = \think\facade\Db::connect($connection);
if ($con->query("show tables like '{$prefix}{$table}'")) {
$table = "{$prefix}{$table}";
$table_val = "'$table'";
} else if ($con->query("show tables like '{$prefix}{$table}s'")) {
$table = "{$prefix}{$table}s";
$table_val = "'$table'";
}
$tableComment = $con->query('SELECT table_comment FROM information_schema.`TABLES` WHERE table_schema = ? AND table_name = ?', [$database, $table]);
if (!empty($tableComment)) {
$comments = $tableComment[0]['table_comment'] ?? $tableComment[0]['TABLE_COMMENT'];
$properties .= " * {$table} {$comments}" . PHP_EOL;
}
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) {
if ($item['COLUMN_KEY'] === 'PRI') {
$pk = $item['COLUMN_NAME'];
$item['COLUMN_COMMENT'] .= "(主键)";
}
$type = $this->getType($item['DATA_TYPE']);
$properties .= " * @property $type \${$item['COLUMN_NAME']} {$item['COLUMN_COMMENT']}\n";
}
} catch (\Throwable $e) {
echo $e->getMessage() . PHP_EOL;
}
$properties = rtrim($properties) ?: ' *';
$model_content = <<<EOF
<?php
namespace $namespace;
use think\Model;
/**
$properties
*/
class $class extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected \$connection = '$connection';
/**
* The table associated with the model.
*
* @var string
*/
protected \$table = $table_val;
/**
* The primary key associated with the table.
*
* @var string
*/
protected \$pk = '$pk';
}
EOF;
file_put_contents($file, $model_content);
}
/**
* @param string $type
* @return string
*/
protected function getType(string $type)
{
if (strpos($type, 'int') !== false) {
return 'integer';
}
switch ($type) {
case 'varchar':
case 'string':
case 'text':
case 'date':
case 'time':
case 'guid':
case 'datetimetz':
case 'datetime':
case 'decimal':
case 'enum':
return 'string';
case 'boolean':
return 'integer';
case 'float':
return 'float';
default:
return 'mixed';
}
}
}
@@ -0,0 +1,213 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Util;
class PluginCreateCommand extends Command
{
protected static $defaultName = 'plugin:create';
protected static $defaultDescription = 'Plugin create';
/**
* @return void
*/
protected function configure()
{
$this->addOption('name', 'name', InputOption::VALUE_REQUIRED, 'Plugin name, for example foo/my-admin');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = strtolower($input->getOption('name'));
$output->writeln("Create Plugin $name");
if (!strpos($name, '/')) {
$output->writeln('<error>Bad name, name must contain character \'/\' , for example foo/MyAdmin</error>');
return self::FAILURE;
}
$namespace = Util::nameToNamespace($name);
// Create dir config/plugin/$name
if (is_dir($plugin_config_path = config_path()."/plugin/$name")) {
$output->writeln("<error>Dir $plugin_config_path already exists</error>");
return self::FAILURE;
}
if (is_dir($plugin_path = base_path()."/vendor/$name")) {
$output->writeln("<error>Dir $plugin_path already exists</error>");
return self::FAILURE;
}
// Add psr-4
if ($err = $this->addAutoloadToComposerJson($name, $namespace)) {
$output->writeln("<error>$err</error>");
return self::FAILURE;
}
$this->createConfigFiles($plugin_config_path);
$this->createVendorFiles($name, $namespace, $plugin_path, $output);
return self::SUCCESS;
}
protected function addAutoloadToComposerJson($name, $namespace)
{
if (!is_file($composer_json_file = base_path()."/composer.json")) {
return "$composer_json_file not exists";
}
$composer_json = json_decode($composer_json_str = file_get_contents($composer_json_file), true);
if (!$composer_json) {
return "Bad $composer_json_file";
}
if(isset($composer_json['autoload']['psr-4'][$namespace."\\"])) {
return;
}
$namespace = str_replace("\\", "\\\\", $namespace);
$composer_json_str = str_replace('"psr-4": {', '"psr-4": {'."\n \"$namespace\\\\\" : \"vendor/$name/src\",", $composer_json_str);
file_put_contents($composer_json_file, $composer_json_str);
}
protected function createConfigFiles($plugin_config_path)
{
mkdir($plugin_config_path, 0777, true);
$app_str = <<<EOF
<?php
return [
'enable' => true,
];
EOF;
file_put_contents("$plugin_config_path/app.php", $app_str);
}
protected function createVendorFiles($name, $namespace, $plugin_path, $output)
{
mkdir("$plugin_path/src", 0777, true);
$this->createComposerJson($name, $namespace, $plugin_path);
if (is_callable('exec')) {
exec("composer dumpautoload");
} else {
$output->writeln("<info>Please run command 'composer dumpautoload'</info>");
}
}
/**
* @param $name
* @param $namespace
* @param $dest
* @return void
*/
protected function createComposerJson($name, $namespace, $dest)
{
$namespace = str_replace('\\', '\\\\', $namespace);
$composer_json_content = <<<EOT
{
"name": "$name",
"type": "library",
"license": "MIT",
"description": "Webman plugin $name",
"require": {
},
"autoload": {
"psr-4": {
"$namespace\\\\": "src"
}
}
}
EOT;
file_put_contents("$dest/composer.json", $composer_json_content);
}
/**
* @param $namespace
* @param $path_relations
* @param $dest_dir
* @return void
*/
protected function writeInstallFile($namespace, $path_relations, $dest_dir)
{
if (!is_dir($dest_dir)) {
mkdir($dest_dir, 0777, true);
}
$relations = [];
foreach($path_relations as $relation) {
$relations[$relation] = $relation;
}
$relations = var_export($relations, true);
$install_php_content = <<<EOT
<?php
namespace $namespace;
class Install
{
const WEBMAN_PLUGIN = true;
/**
* @var array
*/
protected static \$pathRelation = $relations;
/**
* Install
* @return void
*/
public static function install()
{
static::installByRelation();
}
/**
* Uninstall
* @return void
*/
public static function uninstall()
{
self::uninstallByRelation();
}
/**
* installByRelation
* @return void
*/
public static function installByRelation()
{
foreach (static::\$pathRelation as \$source => \$dest) {
if (\$pos = strrpos(\$dest, '/')) {
\$parent_dir = base_path().'/'.substr(\$dest, 0, \$pos);
if (!is_dir(\$parent_dir)) {
mkdir(\$parent_dir, 0777, true);
}
}
//symlink(__DIR__ . "/\$source", base_path()."/\$dest");
copy_dir(__DIR__ . "/\$source", base_path()."/\$dest");
}
}
/**
* uninstallByRelation
* @return void
*/
public static function uninstallByRelation()
{
foreach (static::\$pathRelation as \$source => \$dest) {
/*if (is_link(base_path()."/\$dest")) {
unlink(base_path()."/\$dest");
}*/
remove_dir(base_path()."/\$dest");
}
}
}
EOT;
file_put_contents("$dest_dir/Install.php", $install_php_content);
}
}
@@ -0,0 +1,51 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
class PluginDisableCommand extends Command
{
protected static $defaultName = 'plugin:disable';
protected static $defaultDescription = 'Disable plugin by name';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Plugin name, for example foo/my-admin');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Disable plugin $name");
if (!strpos($name, '/')) {
$output->writeln('<error>Bad name, name must contain character \'/\' , for example foo/MyAdmin</error>');
return self::FAILURE;
}
$config_file = config_path() . "/plugin/$name/app.php";
if (!is_file($config_file)) {
return self::SUCCESS;
}
$config = include $config_file;
if (empty($config['enable'])) {
return self::SUCCESS;
}
$config_content = file_get_contents($config_file);
$config_content = preg_replace('/(\'enable\' *?=> *?)(true)/', '$1false', $config_content);
file_put_contents($config_file, $config_content);
return self::SUCCESS;
}
}
@@ -0,0 +1,56 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
class PluginEnableCommand extends Command
{
protected static $defaultName = 'plugin:enable';
protected static $defaultDescription = 'Enable plugin by name';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Plugin name, for example foo/my-admin');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Enable plugin $name");
if (!strpos($name, '/')) {
$output->writeln('<error>Bad name, name must contain character \'/\' , for example foo/MyAdmin</error>');
return self::FAILURE;
}
$config_file = config_path() . "/plugin/$name/app.php";
if (!is_file($config_file)) {
$output->writeln("<error>$config_file not found</error>");
return self::FAILURE;
}
$config = include $config_file;
if (!isset($config['enable'])) {
$output->writeln("<error>Config key 'enable' not found</error>");
return self::FAILURE;
}
if ($config['enable']) {
return self::SUCCESS;
}
$config_content = file_get_contents($config_file);
$config_content = preg_replace('/(\'enable\' *?=> *?)(false)/', '$1true', $config_content);
file_put_contents($config_file, $config_content);
return self::SUCCESS;
}
}
@@ -0,0 +1,152 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Util;
class PluginExportCommand extends Command
{
protected static $defaultName = 'plugin:export';
protected static $defaultDescription = 'Plugin export';
/**
* @return void
*/
protected function configure()
{
$this->addOption('name', 'name', InputOption::VALUE_REQUIRED, 'Plugin name, for example foo/my-admin');
$this->addOption('source', 'source', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Directories to export');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('Export Plugin');
$name = strtolower($input->getOption('name'));
if (!strpos($name, '/')) {
$output->writeln('<error>Bad name, name must contain character \'/\' , for example foo/MyAdmin</error>');
return self::INVALID;
}
$namespace = Util::nameToNamespace($name);
$path_relations = $input->getOption('source');
if (!in_array("config/plugin/$name", $path_relations)) {
if (is_dir("config/plugin/$name")) {
$path_relations[] = "config/plugin/$name";
}
}
$original_dest = $dest = base_path()."/vendor/$name";
$dest .= '/src';
$this->writeInstallFile($namespace, $path_relations, $dest);
$output->writeln("<info>Create $dest/Install.php</info>");
foreach ($path_relations as $source) {
$base_path = pathinfo("$dest/$source", PATHINFO_DIRNAME);
if (!is_dir($base_path)) {
mkdir($base_path, 0777, true);
}
$output->writeln("<info>Copy $source to $dest/$source </info>");
copy_dir($source, "$dest/$source");
}
$output->writeln("<info>Saved $name to $original_dest</info>");
return self::SUCCESS;
}
/**
* @param $namespace
* @param $path_relations
* @param $dest_dir
* @return void
*/
protected function writeInstallFile($namespace, $path_relations, $dest_dir)
{
if (!is_dir($dest_dir)) {
mkdir($dest_dir, 0777, true);
}
$relations = [];
foreach($path_relations as $relation) {
$relations[$relation] = $relation;
}
$relations = var_export($relations, true);
$install_php_content = <<<EOT
<?php
namespace $namespace;
class Install
{
const WEBMAN_PLUGIN = true;
/**
* @var array
*/
protected static \$pathRelation = $relations;
/**
* Install
* @return void
*/
public static function install()
{
static::installByRelation();
}
/**
* Uninstall
* @return void
*/
public static function uninstall()
{
self::uninstallByRelation();
}
/**
* installByRelation
* @return void
*/
public static function installByRelation()
{
foreach (static::\$pathRelation as \$source => \$dest) {
if (\$pos = strrpos(\$dest, '/')) {
\$parent_dir = base_path().'/'.substr(\$dest, 0, \$pos);
if (!is_dir(\$parent_dir)) {
mkdir(\$parent_dir, 0777, true);
}
}
//symlink(__DIR__ . "/\$source", base_path()."/\$dest");
copy_dir(__DIR__ . "/\$source", base_path()."/\$dest");
echo "Create \$dest\r\n";
}
}
/**
* uninstallByRelation
* @return void
*/
public static function uninstallByRelation()
{
foreach (static::\$pathRelation as \$source => \$dest) {
\$path = base_path()."/\$dest";
if (!is_dir(\$path) && !is_file(\$path)) {
continue;
}
echo "Remove \$dest\r\n";
if (is_file(\$path) || is_link(\$path)) {
unlink(\$path);
continue;
}
remove_dir(\$path);
}
}
}
EOT;
file_put_contents("$dest_dir/Install.php", $install_php_content);
}
}
@@ -0,0 +1,43 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Webman\Console\Util;
class PluginInstallCommand extends Command
{
protected static $defaultName = 'plugin:install';
protected static $defaultDescription = 'Execute plugin installation script';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Plugin name, for example foo/my-admin');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Execute installation for plugin $name");
$namespace = Util::nameToNamespace($name);
$install_function = "\\{$namespace}\\Install::install";
$plugin_const = "\\{$namespace}\\Install::WEBMAN_PLUGIN";
if (defined($plugin_const) && is_callable($install_function)) {
$install_function();
}
return self::SUCCESS;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Webman\Console\Util;
class PluginUninstallCommand extends Command
{
protected static $defaultName = 'plugin:uninstall';
protected static $defaultDescription = 'Execute plugin uninstall script';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Plugin name, for example foo/my-admin');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Execute uninstall for plugin $name");
if (!strpos($name, '/')) {
$output->writeln('<error>Bad name, name must contain character \'/\' , for example foo/MyAdmin</error>');
return self::FAILURE;
}
$namespace = Util::nameToNamespace($name);
$uninstall_function = "\\{$namespace}\\Install::uninstall";
$plugin_const = "\\{$namespace}\\Install::WEBMAN_PLUGIN";
if (defined($plugin_const) && is_callable($uninstall_function)) {
$uninstall_function();
}
return self::SUCCESS;
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Application;
class ReStartCommand extends Command
{
protected static $defaultName = 'restart';
protected static $defaultDescription = 'Restart workers. Use mode -d to start in DAEMON mode. Use mode -g to stop gracefully.';
protected function configure() : void
{
$this
->addOption('daemon', 'd', InputOption::VALUE_NONE, 'DAEMON mode')
->addOption('graceful', 'g', InputOption::VALUE_NONE, 'graceful stop');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (\class_exists(\Support\App::class)) {
\Support\App::run();
return self::SUCCESS;
}
Application::run();
return self::SUCCESS;
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Application;
class ReloadCommand extends Command
{
protected static $defaultName = 'reload';
protected static $defaultDescription = 'Reload codes. Use mode -g to reload gracefully.';
protected function configure() : void
{
$this
->addOption('graceful', 'd', InputOption::VALUE_NONE, 'graceful reload');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (\class_exists(\Support\App::class)) {
\Support\App::run();
return self::SUCCESS;
}
Application::run();
return self::SUCCESS;
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Webman\Route;
class RouteListCommand extends Command
{
protected static $defaultName = 'route:list';
protected static $defaultDescription = 'Route list';
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$headers = ['uri', 'method', 'callback', 'middleware', 'name'];
$rows = [];
foreach (Route::getRoutes() as $route) {
foreach ($route->getMethods() as $method) {
$cb = $route->getCallback();
$cb = $cb instanceof \Closure ? 'Closure' : (is_array($cb) ? json_encode($cb) : var_export($cb, 1));
$rows[] = [$route->getPath(), $method, $cb, json_encode($route->getMiddleware() ?: null), $route->getName()];
}
}
$table = new Table($output);
$table->setHeaders($headers);
$table->setRows($rows);
$table->render();
return self::SUCCESS;
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Application;
class StartCommand extends Command
{
protected static $defaultName = 'start';
protected static $defaultDescription = 'Start worker in DEBUG mode. Use mode -d to start in DAEMON mode.';
protected function configure() : void
{
$this->addOption('daemon', 'd', InputOption::VALUE_NONE, 'DAEMON mode');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (\class_exists(\Support\App::class)) {
\Support\App::run();
return self::SUCCESS;
}
Application::run();
return self::SUCCESS;
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Application;
class StatusCommand extends Command
{
protected static $defaultName = 'status';
protected static $defaultDescription = 'Get worker status. Use mode -d to show live status.';
protected function configure() : void
{
$this->addOption('live', 'd', InputOption::VALUE_NONE, 'show live status');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (\class_exists(\Support\App::class)) {
\Support\App::run();
return self::SUCCESS;
}
Application::run();
return self::SUCCESS;
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Application;
class StopCommand extends Command
{
protected static $defaultName = 'stop';
protected static $defaultDescription = 'Stop worker. Use mode -g to stop gracefully.';
protected function configure() : void
{
$this
->addOption('graceful', 'g',InputOption::VALUE_NONE, 'graceful stop');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (\class_exists(\Support\App::class)) {
\Support\App::run();
return self::SUCCESS;
}
Application::run();
return self::SUCCESS;
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class VersionCommand extends Command
{
protected static $defaultName = 'version';
protected static $defaultDescription = 'Show webman version';
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$installed_file = base_path() . '/vendor/composer/installed.php';
if (is_file($installed_file)) {
$version_info = include $installed_file;
}
$webman_framework_version = $version_info['versions']['workerman/webman-framework']['pretty_version'] ?? '';
$output->writeln("Webman-framework $webman_framework_version");
return self::SUCCESS;
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
namespace Webman\Console;
class Install
{
const WEBMAN_PLUGIN = true;
/**
* @var array
*/
protected static $pathRelation = [
'config/plugin/webman/console' => 'config/plugin/webman/console',
];
/**
* Install
* @return void
*/
public static function install()
{
$dest = base_path() . "/webman";
if (is_dir($dest)) {
echo "Installation failed, please remove directory $dest\n";
return;
}
copy(__DIR__ . "/webman", $dest);
chmod(base_path() . "/webman", 0755);
static::installByRelation();
}
/**
* Uninstall
* @return void
*/
public static function uninstall()
{
if (is_file(base_path()."/webman")) {
unlink(base_path() . "/webman");
}
self::uninstallByRelation();
}
/**
* installByRelation
* @return void
*/
public static function installByRelation()
{
foreach (static::$pathRelation as $source => $dest) {
if ($pos = strrpos($dest, '/')) {
$parent_dir = base_path().'/'.substr($dest, 0, $pos);
if (!is_dir($parent_dir)) {
mkdir($parent_dir, 0777, true);
}
}
copy_dir(__DIR__ . "/$source", base_path()."/$dest");
}
}
/**
* uninstallByRelation
* @return void
*/
public static function uninstallByRelation()
{
foreach (static::$pathRelation as $source => $dest) {
$path = base_path()."/$dest";
if (!is_dir($path) && !is_file($path)) {
continue;
}
remove_dir($path);
}
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace Webman\Console;
use Doctrine\Inflector\InflectorFactory;
class Util
{
public static function nameToNamespace($name)
{
$namespace = ucfirst($name);
$namespace = preg_replace_callback(['/-([a-zA-Z])/', '/(\/[a-zA-Z])/'], function ($matches) {
return strtoupper($matches[1]);
}, $namespace);
return str_replace('/', '\\' ,ucfirst($namespace));
}
public static function classToName($class)
{
$class = lcfirst($class);
return preg_replace_callback(['/([A-Z])/'], function ($matches) {
return '_' . strtolower($matches[1]);
}, $class);
}
public static function nameToClass($class)
{
$class = preg_replace_callback(['/-([a-zA-Z])/', '/_([a-zA-Z])/'], function ($matches) {
return strtoupper($matches[1]);
}, $class);
if (!($pos = strrpos($class, '/'))) {
$class = ucfirst($class);
} else {
$path = substr($class, 0, $pos);
$class = ucfirst(substr($class, $pos + 1));
$class = "$path/$class";
}
return $class;
}
public static function guessPath($base_path, $name, $return_full_path = false)
{
if (!is_dir($base_path)) {
return false;
}
$names = explode('/', trim(strtolower($name), '/'));
$realname = [];
$path = $base_path;
foreach ($names as $name) {
$finded = false;
foreach (scandir($path) ?: [] as $tmp_name) {
if (strtolower($tmp_name) === $name && is_dir("$path/$tmp_name")) {
$path = "$path/$tmp_name";
$realname[] = $tmp_name;
$finded = true;
break;
}
}
if (!$finded) {
return false;
}
}
$realname = implode(DIRECTORY_SEPARATOR, $realname);
return $return_full_path ? get_realpath($base_path . DIRECTORY_SEPARATOR . $realname) : $realname;
}
}
@@ -0,0 +1,24 @@
<?php
return [
'enable' => true,
'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build',
'phar_filename' => 'webman.phar',
'bin_filename' => 'webman.bin',
'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.
'private_key_file' => '', // The file path for certificate or OpenSSL private key file.
'exclude_pattern' => '#^(?!.*(composer.json|/.github/|/.idea/|/.git/|/.setting/|/runtime/|/vendor-bin/|/build/|/vendor/webman/admin/))(.*)$#',
'exclude_files' => [
'.env', 'LICENSE', 'composer.json', 'composer.lock', 'start.php', 'webman.phar', 'webman.bin'
],
'custom_ini' => '
memory_limit = 256M
',
];
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env php
<?php
use Webman\Config;
use Webman\Console\Command;
use Webman\Console\Util;
use support\Container;
if (!Phar::running()) {
chdir(__DIR__);
}
require_once __DIR__ . '/vendor/autoload.php';
if (!$appConfigFile = config_path('app.php')) {
throw new RuntimeException('Config file not found: app.php');
}
$appConfig = require $appConfigFile;
if ($timezone = $appConfig['default_timezone'] ?? '') {
date_default_timezone_set($timezone);
}
if ($errorReporting = $appConfig['error_reporting'] ?? '') {
error_reporting($errorReporting);
}
if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) {
require_once __DIR__ . '/support/bootstrap.php';
} else {
if (class_exists('Support\App')) {
Support\App::loadAllConfig(['route']);
} else {
Config::reload(config_path(), ['route', 'container']);
}
}
$cli = new Command();
$cli->setName('webman cli');
$cli->installInternalCommands();
if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) {
$cli->installCommands($command_path);
}
foreach (config('plugin', []) as $firm => $projects) {
if (isset($projects['app'])) {
foreach (['', '/app'] as $app) {
if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) {
$command_path = base_path() . "/plugin/$firm{$app}/$command_str";
$cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str");
}
}
}
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
foreach ($project['command'] ?? [] as $class_name) {
$reflection = new \ReflectionClass($class_name);
if ($reflection->isAbstract()) {
continue;
}
$properties = $reflection->getStaticProperties();
$name = $properties['defaultName'];
if (!$name) {
throw new RuntimeException("Command {$class_name} has no defaultName");
}
$description = $properties['defaultDescription'] ?? '';
$command = Container::get($class_name);
$command->setName($name)->setDescription($description);
$cli->add($command);
}
}
}
$cli->run();
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 webman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+4
View File
@@ -0,0 +1,4 @@
# event
webman event plugin
https://www.workerman.net/plugin/64
+13
View File
@@ -0,0 +1,13 @@
{
"name": "webman/event",
"type": "library",
"license": "MIT",
"description": "Webman event plugin",
"require": {
},
"autoload": {
"psr-4": {
"Webman\\Event\\": "src"
}
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace Webman\Event;
use support\Container;
use support\Log;
class BootStrap implements \Webman\Bootstrap
{
/**
* @var array
*/
protected static $events = [];
/**
* @param $worker
* @return mixed|void
*/
public static function start($worker)
{
static::getEvents([config()]);
foreach (static::$events as $name => $events) {
// 支持排序,1 2 3 ... 9 a b c...z
ksort($events, SORT_NATURAL);
foreach ($events as $callbacks) {
foreach ($callbacks as $callback) {
Event::on($name, $callback);
}
}
}
}
/**
* @param $callbacks
* @return array|mixed
*/
protected static function convertCallable($callbacks)
{
if (\is_array($callbacks)) {
$callback = \array_values($callbacks);
if (isset($callback[1]) && \is_string($callback[0]) && \class_exists($callback[0])) {
return [Container::get($callback[0]), $callback[1]];
}
}
return $callbacks;
}
/**
* @param $configs
* @return void
*/
protected static function getEvents($configs)
{
foreach ($configs as $config) {
if (!is_array($config)) {
continue;
}
if (isset($config['event']) && is_array($config['event']) && !isset($config['event']['app']['enable'])) {
foreach ($config['event'] as $event_name => $callbacks) {
$callbacks = static::convertCallable($callbacks);
if (is_callable($callbacks)) {
static::$events[$event_name][] = [$callbacks];
continue;
}
if (!is_array($callbacks)) {
$msg = "Events: $event_name => " .var_export($callbacks, true) . " is not callable\n";
echo $msg;
Log::error($msg);
continue;
}
ksort($callbacks, SORT_NATURAL);
foreach ($callbacks as $id => $callback) {
$callback = static::convertCallable($callback);
if (is_callable($callback)) {
static::$events[$event_name][$id][] = $callback;
continue;
}
$msg = "Events: $event_name => " . var_export($callback, true) . " is not callable\n";
echo $msg;
Log::error($msg);
}
}
unset($config['event']);
}
static::getEvents($config);
}
}
}
+161
View File
@@ -0,0 +1,161 @@
<?php
namespace Webman\Event;
use Psr\Log\LoggerInterface;
use support\Log;
class Event
{
/**
* @var array
*/
protected static $eventMap = [];
/**
* @var array
*/
protected static $prefixEventMap = [];
/**
* @var int
*/
protected static $id = 0;
/**
* @var LoggerInterface
*/
protected static $logger;
/**
* @param mixed $event_name
* @param callable $listener
* @return int
*/
public static function on($event_name, callable $listener): int
{
$is_prefix_name = $event_name[strlen($event_name) - 1] === '*';
if ($is_prefix_name) {
static::$prefixEventMap[substr($event_name, 0, -1)][++static::$id] = $listener;
} else {
static::$eventMap[$event_name][++static::$id] = $listener;
}
return static::$id;
}
/**
* @param mixed $event_name
* @param integer $id
* @return int
*/
public static function off($event_name, int $id): int
{
if (isset(static::$eventMap[$event_name][$id])) {
unset(static::$eventMap[$event_name][$id]);
return 1;
}
return 0;
}
/**
* @param mixed $event_name
* @param mixed $data
* @param bool $halt
* @return array|null|mixed
*/
public static function emit($event_name, $data, bool $halt = false)
{
$listeners = static::getListeners($event_name);
$responses = [];
foreach ($listeners as $listener) {
try {
$response = $listener($data, $event_name);
} catch (\Throwable $e) {
$responses[] = $e;
if (!static::$logger && is_callable('\support\Log::error')) {
static::$logger = Log::channel();
}
if (static::$logger) {
static::$logger->error($e);
}
continue;
}
$responses[] = $response;
if ($halt && !is_null($response)) {
return $response;
}
if ($response === false) {
break;
}
}
return $halt ? null : $responses;
}
/**
* @param mixed $event_name
* @param mixed $data
* @param bool $halt
* @return array|null|mixed
*/
public static function dispatch($event_name, $data, bool $halt = false)
{
$listeners = static::getListeners($event_name);
$responses = [];
foreach ($listeners as $listener) {
$response = $listener($data, $event_name);
$responses[] = $response;
if ($halt && !is_null($response)) {
return $response;
}
if ($response === false) {
break;
}
}
return $halt ? null : $responses;
}
/**
* @return array
*/
public static function list(): array
{
$listeners = [];
foreach (static::$eventMap as $event_name => $callback_items) {
foreach ($callback_items as $id => $callback_item) {
$listeners[$id] = [$event_name, $callback_item];
}
}
foreach (static::$prefixEventMap as $event_name => $callback_items) {
foreach ($callback_items as $id => $callback_item) {
$listeners[$id] = [$event_name . '*', $callback_item];
}
}
ksort($listeners);
return $listeners;
}
/**
* @param mixed $event_name
* @return callable[]
*/
public static function getListeners($event_name): array
{
$listeners = static::$eventMap[$event_name] ?? [];
foreach (static::$prefixEventMap as $name => $callback_items) {
if (strpos($event_name, $name) === 0) {
$listeners = array_merge($listeners, $callback_items);
}
}
ksort($listeners);
return $listeners;
}
/**
* @param mixed $event_name
* @return bool
*/
public static function hasListener($event_name): bool
{
return !empty(static::getListeners($event_name));
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Webman\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class EventListCommand extends Command
{
protected static $defaultName = 'event:list';
protected static $defaultDescription = 'Show event list';
/**
* @return void
*/
protected function configure()
{
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$headers = ['id', 'event_name', 'callback'];
$rows = [];
foreach (Event::list() as $id => $item) {
$event_name = $item[0];
$callback = $item[1];
if (is_array($callback) && is_object($callback[0])) {
$callback[0] = get_class($callback[0]);
}
$cb = $callback instanceof \Closure ? 'Closure' : (is_array($callback) ? json_encode($callback) : var_export($callback, 1));
$rows[] = [$id, $event_name, $cb];
}
$table = new Table($output);
$table->setHeaders($headers);
$table->setRows($rows);
$table->render();
return self::SUCCESS;
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace Webman\Event;
class Install
{
const WEBMAN_PLUGIN = true;
/**
* @var array
*/
protected static $pathRelation = array (
'config/plugin/webman/event' => 'config/plugin/webman/event',
);
/**
* Install
* @return void
*/
public static function install()
{
static::installByRelation();
$event_config_path = config_path() . '/event.php';
if (!is_file($event_config_path)) {
file_put_contents($event_config_path, "<?php\n\nreturn [\n \n];\n");
}
}
/**
* Uninstall
* @return void
*/
public static function uninstall()
{
$event_config_path = config_path() . '/event.php';
if (is_file($event_config_path)) {
unlink($event_config_path);
}
self::uninstallByRelation();
}
/**
* installByRelation
* @return void
*/
public static function installByRelation()
{
foreach (static::$pathRelation as $source => $dest) {
if ($pos = strrpos($dest, '/')) {
$parent_dir = base_path().'/'.substr($dest, 0, $pos);
if (!is_dir($parent_dir)) {
mkdir($parent_dir, 0777, true);
}
}
//symlink(__DIR__ . "/$source", base_path()."/$dest");
copy_dir(__DIR__ . "/$source", base_path()."/$dest");
echo "Create $dest
";
}
}
/**
* uninstallByRelation
* @return void
*/
public static function uninstallByRelation()
{
foreach (static::$pathRelation as $source => $dest) {
$path = base_path()."/$dest";
if (!is_dir($path) && !is_file($path)) {
continue;
}
echo "Remove $dest
";
if (is_file($path) || is_link($path)) {
unlink($path);
continue;
}
remove_dir($path);
}
}
}
@@ -0,0 +1,4 @@
<?php
return [
'enable' => true,
];
@@ -0,0 +1,17 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
Webman\Event\BootStrap::class,
];
@@ -0,0 +1,7 @@
<?php
use Webman\Event\EventListCommand;
return [
EventListCommand::class
];