fix:修复BUG/升级1.1.6版本

This commit is contained in:
Ying
2023-04-25 20:11:49 +08:00
parent 445e5f9662
commit 6a6866bbaf
2357 changed files with 456920 additions and 140567 deletions

View File

@@ -4,6 +4,7 @@ namespace Webman\Console;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command as Commands;
use support\Container;
class Command extends Application
{
@@ -24,13 +25,17 @@ class Command extends Application
if ($file->getExtension() !== 'php') {
continue;
}
// abc\def.php
$relativePath = str_replace(str_replace('/', '\\', $path . '\\'), '', str_replace('/', '\\', $file->getRealPath()));
$realNamespace = trim($namspace . '\\' . trim(dirname($relativePath), '.'), '\\');
// 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 (!is_a($class_name, Commands::class, true)) {
if (!class_exists($class_name) || !is_a($class_name, Commands::class, true)) {
continue;
}
$this->add(new $class_name);
$this->add(Container::get($class_name));
}
}
}

View File

@@ -56,17 +56,17 @@ class AppPluginCreateCommand extends Command
{
$base_path = base_path();
$this->mkdir("$base_path/plugin/$name/app/controller", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/exception", 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->createExceptionFile("$base_path/plugin/$name/app/exception/Handler.php", $name);
$this->createConfigFiles("$base_path/plugin/$name/config", $name);
$this->createApiFiles("$base_path/plugin/$name/api", $name);
}
/**
@@ -139,44 +139,6 @@ EOF;
}
/**
* @param $path
* @return void
*/
protected function createExceptionFile($path, $name)
{
$content = <<<EOF
<?php
namespace plugin\\$name\\app\\exception;
use Throwable;
use Webman\\Http\\Request;
use Webman\\Http\\Response;
/**
* Class Handler
* @package Support\Exception
*/
class Handler extends \\support\\exception\\Handler
{
public function render(Request \$request, Throwable \$exception): Response
{
\$code = \$exception->getCode();
if (\$request->expectsJson()) {
\$json = ['code' => \$code ? \$code : 500, 'message' => \$this->_debug ? \$exception->getMessage() : 'Server internal error', 'type' => 'failed'];
\$this->_debug && \$json['traces'] = (string)\$exception;
return new Response(200, ['Content-Type' => 'application/json'],
\json_encode(\$json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
\$error = \$this->_debug ? \\nl2br((string)\$exception) : 'Server internal error';
return new Response(500, [], \$error);
}
}
EOF;
file_put_contents($path, $content);
}
/**
* @param $file
@@ -196,6 +158,119 @@ 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;
class Install
{
/**
* 安装
*
* @param \$version
* @return void
*/
public static function install(\$version)
{
// 导入菜单
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']);
}
}
/**
* 更新
*
* @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']);
}
// 导入新菜单
if (\$menus = static::getMenus()) {
Menu::import(\$menus);
}
}
/**
* 更新前数据收集等
*
* @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);
}
}
}
EOF;
file_put_contents("$base/Install.php", $content);
}
/**
* @param $base
* @param $name
@@ -212,12 +287,22 @@ use support\\Request;
return [
'debug' => true,
'controller_suffix' => 'Controller',
'controller_reuse' => true,
'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
@@ -251,7 +336,7 @@ EOF;
<?php
return [
'' => \\plugin\\$name\\app\\exception\\Handler::class,
'' => support\\exception\\Handler::class,
];
EOF;
@@ -351,7 +436,7 @@ return [
// Fallback language
'fallback_locale' => ['zh_CN', 'en'],
// Folder where language files are stored
'path' => "$base/resource/translations",
'path' => base_path() . "/plugin/$name/resource/translations",
];
EOF;

View File

@@ -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 AppPluginInstallCommand extends Command
{
protected static $defaultName = 'app-plugin:install';
protected static $defaultDescription = 'App Plugin Install';
/**
* @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("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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,128 @@
<?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');
$binFile = "$this->buildDir/$binFileName";
$pharFile = "$this->buildDir/$pharFileName";
$zipFile = "$this->buildDir/$microZipFileName";
$sfxFile = "$this->buildDir/php$version.micro.sfx";
// 打包
$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')) {
$client = stream_socket_client("ssl://$domain:443");
} 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));
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;
}
}

View File

@@ -2,17 +2,37 @@
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;
use Phar;
use RuntimeException;
class PharPackCommand extends Command
class BuildPharCommand extends Command
{
protected static $defaultName = 'phar:pack';
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
@@ -21,28 +41,24 @@ class PharPackCommand extends Command
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->checkEnv();
$phar_file_output_dir = config('plugin.webman.console.app.phar_file_output_dir');
if (empty($phar_file_output_dir)) {
throw new RuntimeException('Please set the phar file output directory.');
}
if (!file_exists($phar_file_output_dir) && !is_dir($phar_file_output_dir)) {
if (!mkdir($phar_file_output_dir,0777,true)) {
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');
$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($phar_file_output_dir,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $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');
$exclude_pattern = config('plugin.webman.console.app.exclude_pattern','');
$phar = new Phar($phar_file,0,'webman');
$phar->startBuffering();
@@ -66,8 +82,29 @@ class PharPackCommand extends Command
$phar->buildFromDirectory(BASE_PATH,$exclude_pattern);
$exclude_files = config('plugin.webman.console.app.exclude_files');
$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);
@@ -94,16 +131,18 @@ __HALT_COMPILER();
/**
* @throws RuntimeException
*/
private function checkEnv(): void
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 phar:pack'"
"The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0 ./webman $command'"
);
}
}
}

View File

@@ -2,6 +2,7 @@
namespace Webman\Console\Commands;
use Doctrine\Inflector\InflectorFactory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -21,6 +22,7 @@ class MakeModelCommand extends Command
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Model name');
$this->addArgument('type', InputArgument::OPTIONAL, 'Type');
}
/**
@@ -32,6 +34,7 @@ class MakeModelCommand extends Command
{
$name = $input->getArgument('name');
$name = Util::nameToClass($name);
$type = $input->getArgument('type');
$output->writeln("Make model $name");
if (!($pos = strrpos($name, '/'))) {
$name = ucfirst($name);
@@ -58,15 +61,18 @@ class MakeModelCommand extends Command
$file = app_path() . "/$path/$name.php";
$namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path);
}
$database = config('database');
if (isset($database['default']) && strpos($database['default'], 'plugin.') === 0) {
$database = false;
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';
}
$thinkorm = config('thinkorm');
if (isset($thinkorm['default']) && strpos($thinkorm['default'], 'plugin.') === 0) {
$thinkorm = false;
}
if (!$database && $thinkorm) {
if ($type == 'tp') {
$this->createTpModel($name, $namespace, $file);
} else {
$this->createModel($name, $namespace, $file);
@@ -94,8 +100,10 @@ class MakeModelCommand extends Command
try {
$prefix = config('database.connections.mysql.prefix') ?? '';
$database = config('database.connections.mysql.database');
if (\support\Db::select("show tables like '{$prefix}{$table}s'")) {
$table = "{$prefix}{$table}s";
$inflector = InflectorFactory::create()->build();
$table_plura = $inflector->pluralize($inflector->tableize($class));
if (\support\Db::select("show tables like '{$prefix}{$table_plura}'")) {
$table = "{$prefix}{$table_plura}";
} else if (\support\Db::select("show tables like '{$prefix}{$table}'")) {
$table_val = "'$table'";
$table = "{$prefix}{$table}";

View File

@@ -8,9 +8,9 @@ class Install
/**
* @var array
*/
protected static $pathRelation = array (
'config/plugin/webman/console' => 'config/plugin/webman/console',
);
protected static $pathRelation = [
'config/plugin/webman/console' => 'config/plugin/webman/console',
];
/**
* Install
@@ -18,8 +18,13 @@ class Install
*/
public static function install()
{
copy(__DIR__ . "/webman", base_path()."/webman");
chmod(base_path()."/webman", 0755);
$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();
}
@@ -33,7 +38,6 @@ class Install
if (is_file(base_path()."/webman")) {
unlink(base_path() . "/webman");
}
self::uninstallByRelation();
}
@@ -50,7 +54,6 @@ class Install
mkdir($parent_dir, 0777, true);
}
}
//symlink(__DIR__ . "/$source", base_path()."/$dest");
copy_dir(__DIR__ . "/$source", base_path()."/$dest");
}
}
@@ -66,9 +69,6 @@ class Install
if (!is_dir($path) && !is_file($path)) {
continue;
}
/*if (is_link($path) {
unlink($path);
}*/
remove_dir($path);
}
}

View File

@@ -16,8 +16,10 @@ class Util
public static function classToName($class)
{
$inflector = InflectorFactory::create()->build();
return $inflector->pluralize($inflector->tableize($class));
$class = lcfirst($class);
return preg_replace_callback(['/([A-Z])/'], function ($matches) {
return '_' . strtolower($matches[1]);
}, $class);
}
public static function nameToClass($class)
@@ -59,6 +61,6 @@ class Util
}
}
$realname = implode(DIRECTORY_SEPARATOR, $realname);
return $return_full_path ? realpath($base_path . DIRECTORY_SEPARATOR . $realname) : $realname;
return $return_full_path ? get_realpath($base_path . DIRECTORY_SEPARATOR . $realname) : $realname;
}
}

View File

@@ -1,18 +1,20 @@
<?php
return [
'enable' => true,
'enable' => true,
'phar_file_output_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build',
'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build',
'phar_filename' => 'webman.phar',
'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' => '#^(?!.*(config/plugin/webman/console/app.php|webman/console/src/Commands/(PharPackCommand.php|ReloadCommand.php)|LICENSE|composer.json|.github|.idea|doc|docs|.git|.setting|runtime|test|test_old|tests|Tests|vendor-bin|.md))(.*)$#',
'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'
'.env', 'LICENSE', 'composer.json', 'composer.lock', 'start.php', 'webman.phar', 'webman.bin'
]
];

View File

@@ -4,6 +4,7 @@
use Webman\Config;
use Webman\Console\Command;
use Webman\Console\Util;
use support\Container;
require_once __DIR__ . '/vendor/autoload.php';
@@ -36,7 +37,7 @@ foreach (config('plugin', []) as $firm => $projects) {
continue;
}
foreach ($project['command'] ?? [] as $command) {
$cli->add(new $command);
$cli->add(Container::get($command));
}
}
}