Commit 1b5f7412 authored by wolfcode's avatar wolfcode

Webman-framework v1.4.9

parent ced1e593
......@@ -25,7 +25,7 @@
},
"require": {
"php": ">=7.2",
"workerman/webman-framework": "1.4.3",
"workerman/webman-framework": "^1.4",
"monolog/monolog": "^2.0",
"webman/auto-route": "^1.0",
"webman/think-orm": "1.0.2",
......@@ -41,7 +41,7 @@
"aliyuncs/oss-sdk-php": "^2.4",
"qcloud/cos-sdk-v5": "^2.5",
"jasongrimes/paginator": "^1.0",
"webman/console": "1.0.27"
"webman/console": "^1.2"
},
"suggest": {
"ext-event": "For better performance. "
......
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1e7532d2b14481835ed4cafcfb0c520b",
"content-hash": "42b09044b63aa27ff1c20f65be9f5f09",
"packages": [
{
"name": "aliyuncs/oss-sdk-php",
......@@ -3636,19 +3636,20 @@
},
{
"name": "webman/console",
"version": "v1.0.27",
"version": "v1.2.16",
"source": {
"type": "git",
"url": "https://github.com/webman-php/console.git",
"reference": "e450967eaabc43eb0c93cfcd6d8f420c16e22b67"
"reference": "252868f948fe397e386577fa6ce0e7487ffc98ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webman-php/console/zipball/e450967eaabc43eb0c93cfcd6d8f420c16e22b67",
"reference": "e450967eaabc43eb0c93cfcd6d8f420c16e22b67",
"url": "https://api.github.com/repos/webman-php/console/zipball/252868f948fe397e386577fa6ce0e7487ffc98ba",
"reference": "252868f948fe397e386577fa6ce0e7487ffc98ba",
"shasum": ""
},
"require": {
"doctrine/inflector": "^2.0",
"symfony/console": ">=5.0"
},
"require-dev": {
......@@ -3684,7 +3685,7 @@
"source": "https://github.com/webman-php/console",
"wiki": "http://www.workerman.net/doc/webman"
},
"time": "2022-07-01T08:59:02+00:00"
"time": "2022-10-08T13:46:48+00:00"
},
{
"name": "webman/think-cache",
......@@ -3823,16 +3824,16 @@
},
{
"name": "workerman/webman-framework",
"version": "v1.4.3",
"version": "v1.4.9",
"source": {
"type": "git",
"url": "https://github.com/walkor/webman-framework.git",
"reference": "0f4d5b6c58823656bdc9603f762d4be6e41ae380"
"reference": "faf13663be7cad1608217a0ec2e3cb8913f866dc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/webman-framework/zipball/0f4d5b6c58823656bdc9603f762d4be6e41ae380",
"reference": "0f4d5b6c58823656bdc9603f762d4be6e41ae380",
"url": "https://api.github.com/repos/walkor/webman-framework/zipball/faf13663be7cad1608217a0ec2e3cb8913f866dc",
"reference": "faf13663be7cad1608217a0ec2e3cb8913f866dc",
"shasum": ""
},
"require": {
......@@ -3863,34 +3864,24 @@
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"description": "High performance HTTP Service Framework.",
"homepage": "http://www.workerman.net",
"homepage": "https://www.workerman.net",
"keywords": [
"High Performance",
"http service"
],
"support": {
"email": "walkor@workerman.net",
"forum": "http://wenda.workerman.net/",
"forum": "https://wenda.workerman.net/",
"issues": "https://github.com/walkor/webman/issues",
"source": "https://github.com/walkor/webman-framework",
"wiki": "http://doc.workerman.net/"
"wiki": "https://doc.workerman.net/"
},
"funding": [
{
"url": "https://opencollective.com/walkor",
"type": "open_collective"
},
{
"url": "https://www.patreon.com/walkor",
"type": "patreon"
}
],
"time": "2022-08-15T12:35:14+00:00"
"time": "2022-10-20T06:42:33+00:00"
},
{
"name": "workerman/workerman",
......
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
Support\App::run();
support\App::run();
......@@ -22,10 +22,6 @@ use Webman\Util;
$worker = $worker ?? null;
if ($timezone = config('app.default_timezone')) {
date_default_timezone_set($timezone);
}
set_error_handler(function ($level, $message, $file = '', $line = 0) {
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
......@@ -48,7 +44,11 @@ if (class_exists('Dotenv\Dotenv') && file_exists(base_path() . '/.env')) {
}
}
Support\App::loadAllConfig(['route']);
Config::clear();
support\App::loadAllConfig(['route']);
if ($timezone = config('app.default_timezone')) {
date_default_timezone_set($timezone);
}
foreach (config('autoload.files', []) as $file) {
include_once $file;
......
<?php
/**
* This file is part of webman.
*
......@@ -12,10 +13,10 @@
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use support\Container;
use support\Request;
use support\Response;
use support\Translation;
use support\Container;
use support\view\Raw;
use support\view\Blade;
use support\view\ThinkPHP;
......@@ -25,68 +26,95 @@ use Webman\App;
use Webman\Config;
use Webman\Route;
// Phar support.
if (\is_phar()) {
\define('BASE_PATH', dirname(__DIR__));
} else {
\define('BASE_PATH', realpath(__DIR__ . '/../'));
// Webman version
define('WEBMAN_VERSION', '1.4');
// Project base path
define('BASE_PATH', dirname(__DIR__));
/**
* Generate paths based on given information
* @param string $front
* @param string $back
* @return string
*/
function path_combine(string $front, string $back)
{
return $front . ($back ? (DIRECTORY_SEPARATOR . ltrim($back, DIRECTORY_SEPARATOR)) : $back);
}
\define('WEBMAN_VERSION', '1.4');
/**
* @param $return_phar
* return the program execute directory
* @param string $path
* @return string
*/
function run_path(string $path = '')
{
static $run_path = '';
if (!$run_path) {
$run_path = \is_phar() ? \dirname(\Phar::running(false)) : BASE_PATH;
}
return \path_combine($run_path, $path);
}
/**
* if the param $path equal false,will return this program current execute directory
* @param string|false $path
* @return false|string
*/
function base_path(bool $return_phar = true)
function base_path($path = '')
{
static $real_path = '';
if (!$real_path) {
$real_path = \is_phar() ? \dirname(Phar::running(false)) : BASE_PATH;
if (false === $path) {
return \run_path();
}
return $return_phar ? BASE_PATH : $real_path;
return \path_combine(BASE_PATH, $path);
}
/**
* @param string $path
* @return string
*/
function app_path()
function app_path(string $path = '')
{
return BASE_PATH . DIRECTORY_SEPARATOR . 'app';
return \path_combine(BASE_PATH . DIRECTORY_SEPARATOR . 'app', $path);
}
/**
* @param string $path
* @return string
*/
function public_path()
function public_path(string $path = '')
{
static $path = '';
if (!$path) {
$path = \config('app.public_path', BASE_PATH . DIRECTORY_SEPARATOR . 'public');
static $public_path = '';
if (!$public_path) {
$public_path = \config('app.public_path') ? : \run_path('public');
}
return $path;
return \path_combine($public_path, $path);
}
/**
* @param string $path
* @return string
*/
function config_path()
function config_path(string $path = '')
{
return BASE_PATH . DIRECTORY_SEPARATOR . 'config';
return \path_combine(BASE_PATH . DIRECTORY_SEPARATOR . 'config', $path);
}
/**
* Phar support.
* Compatible with the 'realpath' function in the phar file.
*
* @param string $path
* @return string
*/
function runtime_path()
function runtime_path(string $path = '')
{
static $path = '';
if (!$path) {
$path = \config('app.runtime_path', BASE_PATH . DIRECTORY_SEPARATOR . 'runtime');
static $runtime_path = '';
if (!$runtime_path) {
$runtime_path = \config('app.runtime_path') ? : \run_path('runtime');
}
return $path;
return \path_combine($runtime_path, $path);
}
/**
......@@ -210,7 +238,7 @@ function twig_view(string $template, array $vars = [], string $app = null)
}
/**
* @return Request
* @return \Webman\Http\Request|Request|null
*/
function request()
{
......@@ -379,7 +407,7 @@ function worker_bind($worker, $class)
}
}
if (\method_exists($class, 'onWorkerStart')) {
[$class, 'onWorkerStart']($worker);
\call_user_func([$class, 'onWorkerStart'], $worker);
}
}
......@@ -433,7 +461,6 @@ function worker_start($process_name, $config)
$instance = Container::make($config['handler'], $config['constructor'] ?? []);
\worker_bind($worker, $instance);
}
};
}
......
......@@ -3792,26 +3792,27 @@
},
{
"name": "webman/console",
"version": "v1.0.27",
"version_normalized": "1.0.27.0",
"version": "v1.2.16",
"version_normalized": "1.2.16.0",
"source": {
"type": "git",
"url": "https://github.com/webman-php/console.git",
"reference": "e450967eaabc43eb0c93cfcd6d8f420c16e22b67"
"reference": "252868f948fe397e386577fa6ce0e7487ffc98ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webman-php/console/zipball/e450967eaabc43eb0c93cfcd6d8f420c16e22b67",
"reference": "e450967eaabc43eb0c93cfcd6d8f420c16e22b67",
"url": "https://api.github.com/repos/webman-php/console/zipball/252868f948fe397e386577fa6ce0e7487ffc98ba",
"reference": "252868f948fe397e386577fa6ce0e7487ffc98ba",
"shasum": ""
},
"require": {
"doctrine/inflector": "^2.0",
"symfony/console": ">=5.0"
},
"require-dev": {
"workerman/webman": "^1.0"
},
"time": "2022-07-01T08:59:02+00:00",
"time": "2022-10-08T13:46:48+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
......@@ -3991,17 +3992,17 @@
},
{
"name": "workerman/webman-framework",
"version": "v1.4.3",
"version_normalized": "1.4.3.0",
"version": "v1.4.9",
"version_normalized": "1.4.9.0",
"source": {
"type": "git",
"url": "https://github.com/walkor/webman-framework.git",
"reference": "0f4d5b6c58823656bdc9603f762d4be6e41ae380"
"reference": "faf13663be7cad1608217a0ec2e3cb8913f866dc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/webman-framework/zipball/0f4d5b6c58823656bdc9603f762d4be6e41ae380",
"reference": "0f4d5b6c58823656bdc9603f762d4be6e41ae380",
"url": "https://api.github.com/repos/walkor/webman-framework/zipball/faf13663be7cad1608217a0ec2e3cb8913f866dc",
"reference": "faf13663be7cad1608217a0ec2e3cb8913f866dc",
"shasum": ""
},
"require": {
......@@ -4013,7 +4014,7 @@
"suggest": {
"ext-event": "For better performance. "
},
"time": "2022-08-15T12:35:14+00:00",
"time": "2022-10-20T06:42:33+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
......@@ -4034,33 +4035,23 @@
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"description": "High performance HTTP Service Framework.",
"homepage": "http://www.workerman.net",
"homepage": "https://www.workerman.net",
"keywords": [
"High Performance",
"http service"
],
"support": {
"email": "walkor@workerman.net",
"forum": "http://wenda.workerman.net/",
"forum": "https://wenda.workerman.net/",
"issues": "https://github.com/walkor/webman/issues",
"source": "https://github.com/walkor/webman-framework",
"wiki": "http://doc.workerman.net/"
"wiki": "https://doc.workerman.net/"
},
"funding": [
{
"url": "https://opencollective.com/walkor",
"type": "open_collective"
},
{
"url": "https://www.patreon.com/walkor",
"type": "patreon"
}
],
"install-path": "../workerman/webman-framework"
},
{
......
<?php return array(
'root' => array(
'name' => 'workerman/webman',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'ced1e593895e2164fa4d567e8d4685f231f65ae0',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
......@@ -534,9 +534,9 @@
'dev_requirement' => false,
),
'webman/console' => array(
'pretty_version' => 'v1.0.27',
'version' => '1.0.27.0',
'reference' => 'e450967eaabc43eb0c93cfcd6d8f420c16e22b67',
'pretty_version' => 'v1.2.16',
'version' => '1.2.16.0',
'reference' => '252868f948fe397e386577fa6ce0e7487ffc98ba',
'type' => 'library',
'install_path' => __DIR__ . '/../webman/console',
'aliases' => array(),
......@@ -570,18 +570,18 @@
'dev_requirement' => false,
),
'workerman/webman' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'ced1e593895e2164fa4d567e8d4685f231f65ae0',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'workerman/webman-framework' => array(
'pretty_version' => 'v1.4.3',
'version' => '1.4.3.0',
'reference' => '0f4d5b6c58823656bdc9603f762d4be6e41ae380',
'pretty_version' => 'v1.4.9',
'version' => '1.4.9.0',
'reference' => 'faf13663be7cad1608217a0ec2e3cb8913f866dc',
'type' => 'library',
'install_path' => __DIR__ . '/../workerman/webman-framework',
'aliases' => array(),
......
......@@ -23,7 +23,8 @@
"source": "https://github.com/webman-php/console"
},
"require": {
"symfony/console": ">=5.0"
"symfony/console": ">=5.0",
"doctrine/inflector": "^2.0"
},
"autoload": {
"psr-4": {
......
......@@ -17,11 +17,20 @@ class Command extends Application
$dir_iterator = new \RecursiveDirectoryIterator($path);
$iterator = new \RecursiveIteratorIterator($dir_iterator);
foreach ($iterator as $file) {
if (is_dir($file)) {
/** @var \SplFileInfo $file */
if (strpos($file->getFilename(), '.') === 0) {
continue;
}
$class_name = $namspace.'\\'.basename($file, '.php');
if (!is_a($class_name, Commands::class, true)) {
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)), '.'), '\\');
// app\command\doc\def
$class_name = trim($realNamespace . '\\' . $file->getBasename('.php'), '\\');
if (!class_exists($class_name) || !is_a($class_name, Commands::class, true)) {
continue;
}
$this->add(new $class_name);
......
<?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/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);
}
/**
* @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 $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
* @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;
class Install
{
/**
* 安装
*
* @param \$version
* @return void
*/
public static function install(\$version)
{
// 导入菜单
Menu::import(static::getMenus());
}
/**
* 卸载
*
* @param \$version
* @return void
*/
public static function uninstall(\$version)
{
// 删除菜单
foreach (static::getMenus() as \$menu) {
Menu::delete(\$menu['name']);
}
}
/**
* 更新
*
* @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']);
}
// 导入新菜单
Menu::import(static::getMenus());
}
/**
* 更新前数据收集等
*
* @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
* @return void
*/
protected function createConfigFiles($base, $name)
{
// app.php
$content = <<<EOF
<?php
use support\\Request;
return [
'debug' => true,
'controller_suffix' => 'Controller',
'controller_reuse' => false,
];
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 [
'' => \\plugin\\$name\\app\\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/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);
}
}
......@@ -29,7 +29,7 @@ class MakeCommandCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$command = $name = $input->getArgument('name');
$command = $name = trim($input->getArgument('name'));
$output->writeln("Make command $name");
// make:command 不支持子目录
......@@ -37,12 +37,14 @@ class MakeCommandCommand extends Command
if (!$command_str = Util::guessPath(app_path(), 'command')) {
$command_str = Util::guessPath(app_path(), 'controller') === 'Controller' ? 'Command' : 'command';
}
$upper = $command_str === 'Command';
$name = ucfirst($name);
$items= explode(':', $name);
$name='';
foreach ($items as $item) {
$name.=ucfirst($item);
}
$file = app_path() . "/$command_str/$name.php";
$upper = $command_str === 'Command';
$namespace = $upper ? 'App\Command' : 'app\command';
$this->createCommand($name, $namespace, $file, $command);
return self::SUCCESS;
......
......@@ -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,7 +61,18 @@ class MakeModelCommand extends Command
$file = app_path() . "/$path/$name.php";
$namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path);
}
if (!config('database') && config('thinkorm')) {
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 ($type == 'tp') {
$this->createTpModel($name, $namespace, $file);
} else {
$this->createModel($name, $namespace, $file);
......@@ -86,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}";
......
......@@ -20,13 +20,13 @@ class RouteListCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$headers = ['uri', 'method', 'callback', 'middleware'];
$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)];
$rows[] = [$route->getPath(), $method, $cb, json_encode($route->getMiddleware() ?: null), $route->getName()];
}
}
......
<?php
namespace Webman\Console;
use Doctrine\Inflector\InflectorFactory;
class Util
{
public static function nameToNamespace($name)
......
# These are supported funding model platforms
open_collective: walkor
patreon: walkor
......@@ -5,22 +5,22 @@
"high performance",
"http service"
],
"homepage": "http://www.workerman.net",
"homepage": "https://www.workerman.net",
"license": "MIT",
"description": "High performance HTTP Service Framework.",
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"support": {
"email": "walkor@workerman.net",
"issues": "https://github.com/walkor/webman/issues",
"forum": "http://wenda.workerman.net/",
"wiki": "http://doc.workerman.net/",
"forum": "https://wenda.workerman.net/",
"wiki": "https://doc.workerman.net/",
"source": "https://github.com/walkor/webman-framework"
},
"require": {
......
<?php
/**
* This file is part of webman.
*
......@@ -18,6 +19,9 @@ use Closure;
use FastRoute\Dispatcher;
use Monolog\Logger;
use Psr\Container\ContainerInterface;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use ReflectionMethod;
use Throwable;
use Webman\Exception\ExceptionHandler;
use Webman\Exception\ExceptionHandlerInterface;
......@@ -118,7 +122,8 @@ class App
return static::send($connection, $callback($request), $request);
}
if (static::unsafeUri($connection, $path, $request) ||
if (
static::unsafeUri($connection, $path, $request) ||
static::findFile($connection, $path, $key, $request) ||
static::findRoute($connection, $path, $key, $request)
) {
......@@ -126,13 +131,13 @@ class App
}
$controller_and_action = static::parseControllerAction($path);
if (!$controller_and_action || Route::hasDisableDefaultRoute()) {
$plugin = $controller_and_action['plugin'] ?? '';
if (!$controller_and_action || Route::hasDisableDefaultRoute($plugin)) {
$callback = static::getFallback();
$request->app = $request->controller = $request->action = '';
static::send($connection, $callback($request), $request);
return null;
}
$plugin = $controller_and_action['plugin'];
$app = $controller_and_action['app'];
$controller = $controller_and_action['controller'];
$action = $controller_and_action['action'];
......@@ -177,12 +182,14 @@ class App
*/
protected static function unsafeUri(TcpConnection $connection, string $path, $request)
{
if (\strpos($path, '..') !== false ||
if (
!$path ||
\strpos($path, '..') !== false ||
\strpos($path, "\\") !== false ||
\strpos($path, "\0") !== false ||
\strpos($path, '//') !== false || !$path) {
\strpos($path, "\0") !== false
) {
$callback = static::getFallback();
$request->app = $request->controller = $request->action = '';
$request->plugin = $request->app = $request->controller = $request->action = '';
static::send($connection, $callback($request), $request);
return true;
}
......@@ -196,7 +203,12 @@ class App
{
// when route, controller and action not found, try to use Route::fallback
return Route::getFallback() ?: function () {
return new Response(404, [], \file_get_contents(static::$_publicPath . '/404.html'));
try {
$notFoundContent = \file_get_contents(static::$_publicPath . '/404.html');
} catch (Throwable $e) {
$notFoundContent = '404 Not Found';
}
return new Response(404, [], $notFoundContent);
};
}
......@@ -233,7 +245,7 @@ class App
/**
* @param $app
* @param $call
* @param null $args
* @param array|null $args
* @param bool $with_global_middleware
* @param RouteObject $route
* @return callable
......@@ -251,24 +263,53 @@ class App
$middlewares = \array_merge($middlewares, Middleware::getMiddleware($plugin, $app, $with_global_middleware));
foreach ($middlewares as $key => $item) {
$middlewares[$key][0] = static::container($plugin)->get($item[0]);
$middleware = $item[0];
if (is_string($middleware)) {
$middleware = static::container($plugin)->get($middleware);
} elseif ($middleware instanceof \Closure) {
$middleware = call_user_func($middleware, static::container($plugin));
}
$controller_reuse = static::config($plugin, 'app.controller_reuse', true);
if (!$middleware instanceof MiddlewareInterface) {
throw new \InvalidArgumentException('Not support middleware type');
}
$middlewares[$key][0] = $middleware;
}
$need_inject = static::isNeedInject($call, $args);
if (\is_array($call) && \is_string($call[0])) {
$controller_reuse = static::config($plugin, 'app.controller_reuse', true);
if (!$controller_reuse) {
if ($need_inject) {
$call = function ($request, ...$args) use ($call, $plugin) {
$call[0] = static::container($plugin)->make($call[0]);
$reflector = static::getReflector($call);
$args = static::resolveMethodDependencies($plugin, $request, $args, $reflector);
return $call(...$args);
};
$need_inject = false;
} else {
$call = function ($request, ...$args) use ($call, $plugin) {
$call[0] = static::container($plugin)->make($call[0]);
return $call($request, ...$args);
};
}
} else {
$call[0] = static::container($plugin)->get($call[0]);
}
}
if ($need_inject) {
$call = static::resolveInject($plugin, $call);
}
if ($middlewares) {
$callback = \array_reduce($middlewares, function ($carry, $pipe) {
return function ($request) use ($carry, $pipe) {
try {
return $pipe($request, $carry);
} catch (Throwable $e) {
return static::exceptionResponse($e, $request);
}
};
}, function ($request) use ($call, $args) {
try {
......@@ -300,6 +341,132 @@ class App
return $callback;
}
/**
* @param string $plugin
* @param array|Closure $call
* @param null|array $args
* @return Closure
* @see Dependency injection through reflection information
*/
protected static function resolveInject(string $plugin, $call)
{
return function (Request $request, ...$args) use ($plugin, $call) {
$reflector = static::getReflector($call);
$args = static::resolveMethodDependencies($plugin, $request, $args, $reflector);
return $call(...$args);
};
}
/**
* Check whether inject is required
*
* @param $call
* @param $args
* @return bool
* @throws \ReflectionException
*/
protected static function isNeedInject($call, $args)
{
if (\is_array($call) && !\method_exists($call[0], $call[1])) {
return false;
}
$args = $args ?: [];
$reflector = static::getReflector($call);
$reflection_parameters = $reflector->getParameters();
if (!$reflection_parameters) {
return false;
}
$first_parameter = \current($reflection_parameters);
unset($reflection_parameters[\key($reflection_parameters)]);
$adapters_list = ['int', 'string', 'bool', 'array', 'object', 'float', 'mixed', 'resource'];
foreach ($reflection_parameters as $parameter) {
if ($parameter->hasType() && !\in_array($parameter->getType()->getName(), $adapters_list)) {
return true;
}
}
if (!$first_parameter->hasType()) {
if (\count($args) <= count($reflection_parameters)) {
return false;
}
return true;
} elseif (!\is_a(static::$_request, $first_parameter->getType()->getName())) {
return true;
}
return false;
}
/**
* Get reflector.
*
* @param $call
* @return void
* @throws \ReflectionException
*/
protected static function getReflector($call)
{
if ($call instanceof Closure) {
return new ReflectionFunction($call);
}
return new ReflectionMethod($call[0], $call[1]);
}
/**
* Return dependent parameters
*
* @param string $plugin
* @param Request $request
* @param array $args
* @param ReflectionFunctionAbstract $reflector
* @return array
*/
protected static function resolveMethodDependencies(string $plugin, Request $request, array $args, ReflectionFunctionAbstract $reflector)
{
// Specification parameter information
$args = \array_values($args);
$parameters = [];
// An array of reflection classes for loop parameters, with each $parameter representing a reflection object of parameters
foreach ($reflector->getParameters() as $parameter) {
// Parameter quota consumption
if ($parameter->hasType()) {
$name = $parameter->getType()->getName();
switch ($name) {
case 'int':
case 'string':
case 'bool':
case 'array':
case 'object':
case 'float':
case 'mixed':
case 'resource':
goto _else;
default:
if (\is_a(static::$_request, $name)) {
//Inject Request
$parameters[] = $request;
} else {
$parameters[] = static::container($plugin)->make($name);
}
break;
}
} else {
_else:
// The variable parameter
if (null !== \key($args)) {
$parameters[] = \current($args);
} else {
// Indicates whether the current parameter has a default value. If yes, return true
$parameters[] = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
}
// Quota of consumption variables
\next($args);
}
}
// Returns the result of parameters replacement
return $parameters;
}
/**
* @param string $plugin
* @return ContainerInterface
......@@ -310,7 +477,7 @@ class App
}
/**
* @return Request
* @return Request|\support\Request
*/
public static function request()
{
......@@ -454,6 +621,7 @@ class App
*/
protected static function parseControllerAction(string $path)
{
$path = \str_replace('-', '', $path);
$path_explode = \explode('/', trim($path, '/'));
$is_plugin = isset($path_explode[1]) && $path_explode[0] === 'app';
$config_prefix = $is_plugin ? "plugin.{$path_explode[1]}." : '';
......@@ -476,19 +644,22 @@ class App
* @param $path_explode
* @param $action
* @param $suffix
* @param $class_prefix
* @return array|false
* @throws \ReflectionException
*/
protected static function guessControllerAction($path_explode, $action, $suffix, $class_prefix)
{
$map[] = "$class_prefix\\app\\controller\\" . \implode('\\', $path_explode);
$map[] = trim("$class_prefix\\app\\controller\\" . \implode('\\', $path_explode), '\\');
foreach ($path_explode as $index => $section) {
$tmp = $path_explode;
\array_splice($tmp, $index, 1, [$section, 'controller']);
$map[] = "$class_prefix\\" . \implode('\\', \array_merge(['app'], $tmp));
$map[] = trim("$class_prefix\\" . \implode('\\', \array_merge(['app'], $tmp)), '\\');
}
foreach ($map as $item) {
$map[] = $item . '\\index';
}
$last_index = \count($map) - 1;
$map[$last_index] = \trim($map[$last_index], '\\') . '\\index';
foreach ($map as $controller_class) {
$controller_class .= $suffix;
if ($controller_action = static::getControllerAction($controller_class, $action)) {
......@@ -506,12 +677,16 @@ class App
*/
protected static function getControllerAction(string $controller_class, string $action)
{
if (static::loadController($controller_class) && ($controller_class = (new \ReflectionClass($controller_class))->name) && \method_exists($controller_class, $action)) {
// Disable calling magic methods
if (\strpos($action, '__') === 0) {
return false;
}
if (($controller_class = static::getController($controller_class)) && ($action = static::getAction($controller_class, $action))) {
return [
'plugin' => static::getPluginByClass($controller_class),
'app' => static::getAppByController($controller_class),
'controller' => $controller_class,
'action' => static::getRealMethod($controller_class, $action)
'action' => $action
];
}
return false;
......@@ -519,46 +694,81 @@ class App
/**
* @param string $controller_class
* @return bool
* @return string|false
* @throws \ReflectionException
*/
protected static function loadController(string $controller_class)
protected static function getController(string $controller_class)
{
if (\class_exists($controller_class)) {
return true;
return (new \ReflectionClass($controller_class))->name;
}
$explodes = \explode('\\', strtolower(ltrim($controller_class, '\\')));
$base_path = $explodes[0] === 'plugin' ? BASE_PATH . '/plugin' : static::$_appPath;
unset($explodes[0]);
$file_name = \array_pop($explodes) . '.php';
$finded = true;
$found = true;
foreach ($explodes as $path_section) {
if (!$finded) {
if (!$found) {
break;
}
$dirs = Util::scanDir($base_path, false);
$finded = false;
$found = false;
foreach ($dirs as $name) {
if (\strtolower($name) === $path_section) {
$base_path = "$base_path/$name";
$finded = true;
$found = true;
break;
}
}
}
if (!$finded) {
if (!$found) {
return false;
}
foreach (\scandir($base_path) ?: [] as $name) {
if (\strtolower($name) === $file_name) {
require_once "$base_path/$name";
if (\class_exists($controller_class, false)) {
return true;
return (new \ReflectionClass($controller_class))->name;
}
}
}
return false;
}
/**
* @param string $controller_class
* @param string $action
* @return string|false
*/
protected static function getAction(string $controller_class, string $action)
{
$methods = \get_class_methods($controller_class);
$action = \strtolower($action);
$found = false;
foreach ($methods as $candidate) {
if (\strtolower($candidate) === $action) {
$action = $candidate;
$found = true;
break;
}
}
if ($found) {
return $action;
}
// Action is not public method
if (\method_exists($controller_class, $action)) {
return false;
}
if (\method_exists($controller_class, '__call')) {
return $action;
}
return false;
}
/**
* @param string $controller_class
* @return mixed|string
......@@ -631,5 +841,4 @@ class App
{
return Config::get($plugin ? "plugin.$plugin.$key" : $key, $default);
}
}
......@@ -73,6 +73,14 @@ class Config
static::load($config_path, $exclude_file);
}
/**
* @return void
*/
public static function clear()
{
static::$_config = [];
}
/**
* @return void
*/
......@@ -202,18 +210,18 @@ class Config
}
$key_array = \explode('.', $key);
$value = static::$_config;
$finded = true;
$found = true;
foreach ($key_array as $index) {
if (!isset($value[$index])) {
if (static::$_loaded) {
return $default;
}
$finded = false;
$found = false;
break;
}
$value = $value[$index];
}
if ($finded) {
if ($found) {
return $value;
}
return static::read($key, $default);
......
......@@ -16,6 +16,10 @@ class Container implements ContainerInterface
* @var array
*/
protected $_instances = [];
/**
* @var array
*/
protected $_definitions = [];
/**
* @param string $name
......@@ -25,11 +29,15 @@ class Container implements ContainerInterface
public function get(string $name)
{
if (!isset($this->_instances[$name])) {
if (isset($this->_definitions[$name])) {
$this->_instances[$name] = call_user_func($this->_definitions[$name], $this);
} else {
if (!\class_exists($name)) {
throw new NotFoundException("Class '$name' not found");
}
$this->_instances[$name] = new $name();
}
}
return $this->_instances[$name];
}
......@@ -39,7 +47,8 @@ class Container implements ContainerInterface
*/
public function has(string $name): bool
{
return \array_key_exists($name, $this->_instances);
return \array_key_exists($name, $this->_instances)
|| array_key_exists($name, $this->_definitions);
}
/**
......@@ -56,4 +65,14 @@ class Container implements ContainerInterface
return new $name(... array_values($constructor));
}
/**
* @param array $definitions
* @return $this
*/
public function addDefinitions(array $definitions)
{
$this->_definitions = array_merge($this->_definitions, $definitions);
return $this;
}
}
......@@ -21,7 +21,7 @@ use Webman\Http\Response;
/**
* Class Handler
* @package Support\Exception
* @package support\exception
*/
class ExceptionHandler implements ExceptionHandlerInterface
{
......
......@@ -197,8 +197,8 @@ class Request extends \Workerman\Protocols\Http\Request
if ($safe_mode && !static::isIntranetIp($remote_ip)) {
return $remote_ip;
}
return $this->header('client-ip', $this->header('x-forwarded-for',
$this->header('x-real-ip', $this->header('x-client-ip',
return $this->header('x-real-ip', $this->header('x-forwarded-for',
$this->header('client-ip', $this->header('x-client-ip',
$this->header('via', $remote_ip)))));
}
......
......@@ -64,7 +64,7 @@ class UploadFile extends File
/**
* @return string
*/
public function getUploadMineType()
public function getUploadMimeType()
{
return $this->_uploadMimeType;
}
......@@ -93,4 +93,12 @@ class UploadFile extends File
return $this->_uploadErrorCode === UPLOAD_ERR_OK;
}
/**
* @deprecated
* @return string
*/
public function getUploadMineType()
{
return $this->_uploadMimeType;
}
}
......@@ -47,8 +47,12 @@ class Install
mkdir($parent_dir, 0777, true);
}
}
copy_dir(__DIR__ . "/$source", base_path() . "/$dest", true);
$source_file = __DIR__ . "/$source";
copy_dir($source_file, base_path() . "/$dest", true);
echo "Create $dest\r\n";
if (is_file($source_file)) {
@unlink($source_file);
}
}
}
......
......@@ -358,7 +358,7 @@ class Route
require_once $route_config_file;
}
if (!is_dir($plugin_config_path = $config_path . '/plugin')) {
return;
continue;
}
$dir_iterator = new \RecursiveDirectoryIterator($plugin_config_path, \FilesystemIterator::FOLLOW_SYMLINKS);
$iterator = new \RecursiveIteratorIterator($dir_iterator);
......
......@@ -96,7 +96,7 @@ class Route
if ($middleware === null) {
return $this->_middlewares;
}
$this->_middlewares = \array_merge($this->_middlewares, (array)$middleware);
$this->_middlewares = \array_merge($this->_middlewares, is_array($middleware) ? $middleware : [$middleware]);
return $this;
}
......
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
Support\App::run();
......@@ -19,11 +19,11 @@ class App
ini_set('display_errors', 'on');
error_reporting(E_ALL);
if (class_exists(Dotenv::class) && file_exists(base_path() . '/.env')) {
if (class_exists(Dotenv::class) && file_exists(run_path('.env'))) {
if (method_exists(Dotenv::class, 'createUnsafeImmutable')) {
Dotenv::createUnsafeImmutable(base_path())->load();
Dotenv::createUnsafeImmutable(run_path())->load();
} else {
Dotenv::createMutable(base_path())->load();
Dotenv::createMutable(run_path())->load();
}
}
......@@ -97,7 +97,7 @@ class App
require_once \base_path() . '/support/bootstrap.php';
$app = new \Webman\App(config('app.request_class', Request::class), Log::channel('default'), app_path(), public_path());
$worker->onMessage = [$app, 'onMessage'];
[$app, 'onWorkerStart']($worker);
\call_user_func([$app, 'onWorkerStart'], $worker);
};
}
......
......@@ -7,7 +7,7 @@ use Symfony\Component\Cache\Psr16Cache;
/**
* Class Cache
* @package Support\Bootstrap
* @package support\bootstrap
*
* Strings methods
* @method static mixed get($key, $default = null)
......
......@@ -19,7 +19,7 @@ use Webman\Config;
/**
* Class Container
* @package Support
* @package support
* @method static mixed get($name)
* @method static mixed make($name, array $parameters)
* @method static bool has($name)
......
......@@ -18,7 +18,7 @@ use Illuminate\Database\Capsule\Manager;
/**
* Class Db
* @package Support
* @package support
* @method static array select(string $query, $bindings = [], $useReadPdo = true)
* @method static int insert(string $query, $bindings = [])
* @method static int update(string $query, $bindings = [])
......
......@@ -21,7 +21,7 @@ use Monolog\Logger;
/**
* Class Log
* @package Support
* @package support
*
* @method static void log($level, $message, array $context = [])
* @method static void debug($message, array $context = [])
......
......@@ -23,7 +23,7 @@ use Workerman\Worker;
/**
* Class Redis
* @package Support
* @package support
*
* Strings methods
* @method static int append($key, $value)
......
......@@ -16,7 +16,7 @@ namespace support;
/**
* Class Request
* @package Support
* @package support
*/
class Request extends \Webman\Http\Request
{
......
......@@ -16,7 +16,7 @@ namespace support;
/**
* Class Response
* @package Support
* @package support
*/
class Response extends \Webman\Http\Response
{
......
......@@ -19,7 +19,7 @@ use Webman\Exception\NotFoundException;
/**
* Class Translation
* @package Support
* @package support
* @method static string trans(?string $id, array $parameters = [], string $domain = null, string $locale = null)
* @method static void setLocale(string $locale)
* @method static string getLocale()
......
<?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
*/
use Dotenv\Dotenv;
use support\Log;
use Webman\Bootstrap;
use Webman\Config;
use Webman\Route;
use Webman\Middleware;
use Webman\Util;
$worker = $worker ?? null;
if ($timezone = config('app.default_timezone')) {
date_default_timezone_set($timezone);
}
set_error_handler(function ($level, $message, $file = '', $line = 0) {
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
}
});
if ($worker) {
register_shutdown_function(function ($start_time) {
if (time() - $start_time <= 1) {
sleep(1);
}
}, time());
}
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();
}
}
Support\App::loadAllConfig(['route']);
foreach (config('autoload.files', []) as $file) {
include_once $file;
}
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
foreach ($project['autoload']['files'] ?? [] as $file) {
include_once $file;
}
}
foreach ($projects['autoload']['files'] ?? [] as $file) {
include_once $file;
}
}
Middleware::load(config('middleware', []), '');
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project) || $name === 'static') {
continue;
}
Middleware::load($project['middleware'] ?? [], '');
}
Middleware::load($projects['middleware'] ?? [], $firm);
if ($static_middlewares = config("plugin.$firm.static.middleware")) {
Middleware::load(['__static__' => $static_middlewares], $firm);
}
}
Middleware::load(['__static__' => config('static.middleware', [])], '');
foreach (config('bootstrap', []) as $class_name) {
if (!class_exists($class_name)) {
$log = "Warning: Class $class_name setting in config/bootstrap.php not found\r\n";
echo $log;
Log::error($log);
continue;
}
/** @var Bootstrap $class_name */
$class_name::start($worker);
}
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
foreach ($project['bootstrap'] ?? [] as $class_name) {
if (!class_exists($class_name)) {
$log = "Warning: Class $class_name setting in config/plugin/$firm/$name/bootstrap.php not found\r\n";
echo $log;
Log::error($log);
continue;
}
/** @var Bootstrap $class_name */
$class_name::start($worker);
}
}
foreach ($projects['bootstrap'] ?? [] as $class_name) {
if (!class_exists($class_name)) {
$log = "Warning: Class $class_name setting in plugin/$firm/config/bootstrap.php not found\r\n";
echo $log;
Log::error($log);
continue;
}
/** @var Bootstrap $class_name */
$class_name::start($worker);
}
}
$directory = base_path() . '/plugin';
$paths = [config_path()];
foreach (Util::scanDir($directory) as $path) {
if (is_dir($path = "$path/config")) {
$paths[] = $path;
}
}
Route::load($paths);
......@@ -76,20 +76,13 @@ class LaravelDb implements Bootstrap
// Heartbeat
if ($worker) {
Timer::add(55, function () use ($default, $connections) {
if (!class_exists(Connection::class, false)) {
return;
}
foreach ($connections as $key => $item) {
if ($item['driver'] == 'mysql') {
Timer::add(55, function () use ($default, $connections, $capsule) {
foreach ($capsule->getDatabaseManager()->getConnections() as $connection) {
/* @var \Illuminate\Database\MySqlConnection $connection **/
if ($connection->getConfig('driver') == 'mysql') {
try {
if ($key == $default) {
Db::select('select 1');
} else {
Db::connection($key)->select('select 1');
}
} catch (Throwable $e) {
}
$connection->select('select 1');
} catch (Throwable $e) {}
}
}
});
......@@ -97,14 +90,22 @@ class LaravelDb implements Bootstrap
// Paginator
if (class_exists(Paginator::class)) {
if (method_exists(Paginator::class, 'queryStringResolver')) {
Paginator::queryStringResolver(function () {
return request()->queryString();
$request = request();
return $request ? $request->queryString() : null;
});
}
Paginator::currentPathResolver(function () {
return request()->path();
$request = request();
return $request ? $request->path(): '/';
});
Paginator::currentPageResolver(function ($page_name = 'page') {
$page = (int)request()->input($page_name, 1);
$request = request();
if (!$request) {
return 1;
}
$page = (int)($request->input($page_name, 1));
return $page > 0 ? $page : 1;
});
}
......
......@@ -21,7 +21,7 @@ use Workerman\Worker;
/**
* Class Session
* @package Support
* @package support
*/
class Session implements Bootstrap
{
......
......@@ -15,12 +15,23 @@
namespace support\exception;
use Exception;
use Webman\Http\Response;
use Webman\Http\Request;
/**
* Class BusinessException
* @package Support\Exception
* @package support\exception
*/
class BusinessException extends Exception
{
public function render(Request $request): ?Response
{
if ($request->expectsJson()) {
$code = $this->getCode();
$json = ['code' => $code ? $code : 500, 'msg' => $this->getMessage()];
return new Response(200, ['Content-Type' => 'application/json'],
\json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
return new Response(200, [], $this->getMessage());
}
}
......@@ -21,7 +21,7 @@ use Webman\Http\Response;
/**
* Class Handler
* @package Support\Exception
* @package support\exception
*/
class Handler extends ExceptionHandler
{
......@@ -36,6 +36,11 @@ class Handler extends ExceptionHandler
public function render(Request $request, Throwable $exception): Response
{
if(($exception instanceof BusinessException) && ($response = $exception->render($request)))
{
return $response;
}
return parent::render($request, $exception);
}
......
<?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
*/
use support\Request;
use support\Response;
use support\Translation;
use support\Container;
use support\view\Raw;
use support\view\Blade;
use support\view\ThinkPHP;
use support\view\Twig;
use Workerman\Worker;
use Webman\App;
use Webman\Config;
use Webman\Route;
// Phar support.
if (\is_phar()) {
\define('BASE_PATH', dirname(__DIR__));
} else {
\define('BASE_PATH', realpath(__DIR__ . '/../'));
}
\define('WEBMAN_VERSION', '1.4');
/**
* @param $return_phar
* @return false|string
*/
function base_path(bool $return_phar = true)
{
static $real_path = '';
if (!$real_path) {
$real_path = \is_phar() ? \dirname(Phar::running(false)) : BASE_PATH;
}
return $return_phar ? BASE_PATH : $real_path;
}
/**
* @return string
*/
function app_path()
{
return BASE_PATH . DIRECTORY_SEPARATOR . 'app';
}
/**
* @return string
*/
function public_path()
{
static $path = '';
if (!$path) {
$path = \config('app.public_path', BASE_PATH . DIRECTORY_SEPARATOR . 'public');
}
return $path;
}
/**
* @return string
*/
function config_path()
{
return BASE_PATH . DIRECTORY_SEPARATOR . 'config';
}
/**
* Phar support.
* Compatible with the 'realpath' function in the phar file.
*
* @return string
*/
function runtime_path()
{
static $path = '';
if (!$path) {
$path = \config('app.runtime_path', BASE_PATH . DIRECTORY_SEPARATOR . 'runtime');
}
return $path;
}
/**
* @param int $status
* @param array $headers
* @param string $body
* @return Response
*/
function response($body = '', $status = 200, $headers = [])
{
return new Response($status, $headers, $body);
}
/**
* @param $data
* @param int $options
* @return Response
*/
function json($data, $options = JSON_UNESCAPED_UNICODE)
{
return new Response(200, ['Content-Type' => 'application/json'], \json_encode($data, $options));
}
/**
* @param $xml
* @return Response
*/
function xml($xml)
{
if ($xml instanceof SimpleXMLElement) {
$xml = $xml->asXML();
}
return new Response(200, ['Content-Type' => 'text/xml'], $xml);
}
/**
* @param $data
* @param string $callback_name
* @return Response
*/
function jsonp($data, $callback_name = 'callback')
{
if (!\is_scalar($data) && null !== $data) {
$data = \json_encode($data);
}
return new Response(200, [], "$callback_name($data)");
}
/**
* @param string $location
* @param int $status
* @param array $headers
* @return Response
*/
function redirect(string $location, int $status = 302, array $headers = [])
{
$response = new Response($status, ['Location' => $location]);
if (!empty($headers)) {
$response->withHeaders($headers);
}
return $response;
}
/**
* @param $template
* @param array $vars
* @param null $app
* @return Response
*/
function view(string $template, array $vars = [], string $app = null)
{
$request = \request();
$plugin = $request->plugin ?? '';
$handler = \config($plugin ? "plugin.$plugin.view.handler" : 'view.handler');
return new Response(200, [], $handler::render($template, $vars, $app));
}
/**
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
* @throws Throwable
*/
function raw_view(string $template, array $vars = [], string $app = null)
{
return new Response(200, [], Raw::render($template, $vars, $app));
}
/**
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
*/
function blade_view(string $template, array $vars = [], string $app = null)
{
return new Response(200, [], Blade::render($template, $vars, $app));
}
/**
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
*/
function think_view(string $template, array $vars = [], string $app = null)
{
return new Response(200, [], ThinkPHP::render($template, $vars, $app));
}
/**
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
*/
function twig_view(string $template, array $vars = [], string $app = null)
{
return new Response(200, [], Twig::render($template, $vars, $app));
}
/**
* @return Request
*/
function request()
{
return App::request();
}
/**
* @param string|null $key
* @param $default
* @return array|mixed|null
*/
function config(string $key = null, $default = null)
{
return Config::get($key, $default);
}
/**
* @param string $name
* @param ...$parameters
* @return string
*/
function route(string $name, ...$parameters)
{
$route = Route::getByName($name);
if (!$route) {
return '';
}
if (!$parameters) {
return $route->url();
}
if (\is_array(\current($parameters))) {
$parameters = \current($parameters);
}
return $route->url($parameters);
}
/**
* @param mixed $key
* @param mixed $default
* @return mixed
*/
function session($key = null, $default = null)
{
$session = \request()->session();
if (null === $key) {
return $session;
}
if (\is_array($key)) {
$session->put($key);
return null;
}
if (\strpos($key, '.')) {
$key_array = \explode('.', $key);
$value = $session->all();
foreach ($key_array as $index) {
if (!isset($value[$index])) {
return $default;
}
$value = $value[$index];
}
return $value;
}
return $session->get($key, $default);
}
/**
* @param string $id
* @param array $parameters
* @param string|null $domain
* @param string|null $locale
* @return string
*/
function trans(string $id, array $parameters = [], string $domain = null, string $locale = null)
{
$res = Translation::trans($id, $parameters, $domain, $locale);
return $res === '' ? $id : $res;
}
/**
* @param null|string $locale
* @return string
*/
function locale(string $locale = null)
{
if (!$locale) {
return Translation::getLocale();
}
Translation::setLocale($locale);
}
/**
* 404 not found
*
* @return Response
*/
function not_found()
{
return new Response(404, [], \file_get_contents(public_path() . '/404.html'));
}
/**
* Copy dir.
*
* @param string $source
* @param string $dest
* @param bool $overwrite
* @return void
*/
function copy_dir(string $source, string $dest, bool $overwrite = false)
{
if (\is_dir($source)) {
if (!is_dir($dest)) {
\mkdir($dest);
}
$files = \scandir($source);
foreach ($files as $file) {
if ($file !== "." && $file !== "..") {
\copy_dir("$source/$file", "$dest/$file");
}
}
} else if (\file_exists($source) && ($overwrite || !\file_exists($dest))) {
\copy($source, $dest);
}
}
/**
* Remove dir.
*
* @param string $dir
* @return bool
*/
function remove_dir(string $dir)
{
if (\is_link($dir) || \is_file($dir)) {
return \unlink($dir);
}
$files = \array_diff(\scandir($dir), array('.', '..'));
foreach ($files as $file) {
(\is_dir("$dir/$file") && !\is_link($dir)) ? \remove_dir("$dir/$file") : \unlink("$dir/$file");
}
return \rmdir($dir);
}
/**
* @param $worker
* @param $class
*/
function worker_bind($worker, $class)
{
$callback_map = [
'onConnect',
'onMessage',
'onClose',
'onError',
'onBufferFull',
'onBufferDrain',
'onWorkerStop',
'onWebSocketConnect'
];
foreach ($callback_map as $name) {
if (\method_exists($class, $name)) {
$worker->$name = [$class, $name];
}
}
if (\method_exists($class, 'onWorkerStart')) {
[$class, 'onWorkerStart']($worker);
}
}
/**
* @param $process_name
* @param $config
* @return void
*/
function worker_start($process_name, $config)
{
$worker = new Worker($config['listen'] ?? null, $config['context'] ?? []);
$property_map = [
'count',
'user',
'group',
'reloadable',
'reusePort',
'transport',
'protocol',
];
$worker->name = $process_name;
foreach ($property_map as $property) {
if (isset($config[$property])) {
$worker->$property = $config[$property];
}
}
$worker->onWorkerStart = function ($worker) use ($config) {
require_once \base_path() . '/support/bootstrap.php';
foreach ($config['services'] ?? [] as $server) {
if (!\class_exists($server['handler'])) {
echo "process error: class {$server['handler']} not exists\r\n";
continue;
}
$listen = new Worker($server['listen'] ?? null, $server['context'] ?? []);
if (isset($server['listen'])) {
echo "listen: {$server['listen']}\n";
}
$instance = Container::make($server['handler'], $server['constructor'] ?? []);
\worker_bind($listen, $instance);
$listen->listen();
}
if (isset($config['handler'])) {
if (!\class_exists($config['handler'])) {
echo "process error: class {$config['handler']} not exists\r\n";
return;
}
$instance = Container::make($config['handler'], $config['constructor'] ?? []);
\worker_bind($worker, $instance);
}
};
}
/**
* Phar support.
* Compatible with the 'realpath' function in the phar file.
*
* @param string $file_path
* @return string
*/
function get_realpath(string $file_path): string
{
if (\strpos($file_path, 'phar://') === 0) {
return $file_path;
} else {
return \realpath($file_path);
}
}
/**
* @return bool
*/
function is_phar()
{
return \class_exists(\Phar::class, false) && Phar::running();
}
/**
* @return int
*/
function cpu_count()
{
// Windows does not support the number of processes setting.
if (\DIRECTORY_SEPARATOR === '\\') {
return 1;
}
$count = 4;
if (\is_callable('shell_exec')) {
if (\strtolower(PHP_OS) === 'darwin') {
$count = (int)\shell_exec('sysctl -n machdep.cpu.core_count');
} else {
$count = (int)\shell_exec('nproc');
}
}
return $count > 0 ? $count : 4;
}
......@@ -12,7 +12,7 @@
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace support\View;
namespace support\view;
use Jenssegers\Blade\Blade as BladeView;
use Webman\View;
......@@ -20,7 +20,7 @@ use Webman\View;
/**
* Class Blade
* composer require jenssegers/blade
* @package Support\View
* @package support\view
*/
class Blade implements View
{
......@@ -50,13 +50,19 @@ class Blade implements View
$request = \request();
$plugin = $request->plugin ?? '';
$app = $app === null ? $request->app : $app;
$config_prefix = $plugin ? "plugin.$plugin." : '';
$base_view_path = $plugin ? \base_path() . "/plugin/$plugin/app" : \app_path();
if (!isset($views[$app])) {
$key = "{$plugin}-{$request->app}";
if (!isset($views[$key])) {
$view_path = $app === '' ? "$base_view_path/view" : "$base_view_path/$app/view";
$views[$app] = new BladeView($view_path, \runtime_path() . '/views');
$views[$key] = new BladeView($view_path, \runtime_path() . '/views');
$extension = \config("{$config_prefix}view.extension");
if ($extension) {
$extension($views[$key]);
}
}
$vars = \array_merge(static::$_vars, $vars);
$content = $views[$app]->render($template, $vars);
$content = $views[$key]->render($template, $vars);
static::$_vars = [];
return $content;
}
......
......@@ -12,14 +12,14 @@
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace support\View;
namespace support\view;
use Webman\View;
use Throwable;
/**
* Class Raw
* @package Support\View
* @package support\view
*/
class Raw implements View
{
......@@ -51,14 +51,14 @@ class Raw implements View
$view_suffix = \config("{$config_prefix}view.options.view_suffix", 'html');
$app = $app === null ? $request->app : $app;
$base_view_path = $plugin ? \base_path() . "/plugin/$plugin/app" : \app_path();
$view_path = $app === '' ? "$base_view_path/view/$template.$view_suffix" : "$base_view_path/$app/view/$template.$view_suffix";
$__template_path__ = $app === '' ? "$base_view_path/view/$template.$view_suffix" : "$base_view_path/$app/view/$template.$view_suffix";
\extract(static::$_vars, \EXTR_SKIP);
\extract($vars, \EXTR_SKIP);
\extract(static::$_vars);
\extract($vars);
\ob_start();
// Try to include php file.
try {
include $view_path;
include $__template_path__;
} catch (Throwable $e) {
static::$_vars = [];
\ob_end_clean();
......
......@@ -12,14 +12,14 @@
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace support\View;
namespace support\view;
use think\Template;
use Webman\View;
/**
* Class Blade
* @package Support\View
* @package support\view
*/
class ThinkPHP implements View
{
......
......@@ -12,7 +12,7 @@
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace support\View;
namespace support\view;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
......@@ -20,7 +20,7 @@ use Webman\View;
/**
* Class Blade
* @package Support\View
* @package support\view
*/
class Twig implements View
{
......@@ -57,6 +57,10 @@ class Twig implements View
$base_view_path = $plugin ? \base_path() . "/plugin/$plugin/app" : \app_path();
$view_path = $app === '' ? "$base_view_path/view/" : "$base_view_path/$app/view/";
$views[$key] = new Environment(new FilesystemLoader($view_path), \config("{$config_prefix}view.options", []));
$extension = \config("{$config_prefix}view.extension");
if ($extension) {
$extension($views[$key]);
}
}
$vars = \array_merge(static::$_vars, $vars);
$content = $views[$key]->render("$template.$view_suffix", $vars);
......
<?php
/**
* Start file for windows
*/
require_once __DIR__ . '/vendor/autoload.php';
use process\Monitor;
use support\App;
use Dotenv\Dotenv;
use Workerman\Worker;
ini_set('display_errors', 'on');
error_reporting(E_ALL);
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();
}
}
App::loadAllConfig(['route']);
$error_reporting = config('app.error_reporting');
if (isset($error_reporting)) {
error_reporting($error_reporting);
}
$runtime_process_path = runtime_path() . DIRECTORY_SEPARATOR . '/windows';
if (!is_dir($runtime_process_path)) {
mkdir($runtime_process_path);
}
$process_files = [
__DIR__ . DIRECTORY_SEPARATOR . 'start.php'
];
foreach (config('process', []) as $process_name => $config) {
$process_files[] = write_process_file($runtime_process_path, $process_name, '');
}
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
foreach ($project['process'] ?? [] as $process_name => $config) {
$process_files[] = write_process_file($runtime_process_path, $process_name, "$firm.$name");
}
}
foreach ($projects['process'] ?? [] as $process_name => $config) {
$process_files[] = write_process_file($runtime_process_path, $process_name, $firm);
}
}
function write_process_file($runtime_process_path, $process_name, $firm)
{
$process_param = $firm ? "plugin.$firm.$process_name" : $process_name;
$config_param = $firm ? "config('plugin.$firm.process')['$process_name']" : "config('process')['$process_name']";
$file_content = <<<EOF
<?php
require_once __DIR__ . '/../../vendor/autoload.php';
use Workerman\Worker;
use Webman\Config;
use support\App;
ini_set('display_errors', 'on');
error_reporting(E_ALL);
if (is_callable('opcache_reset')) {
opcache_reset();
}
App::loadAllConfig(['route']);
worker_start('$process_param', $config_param);
if (DIRECTORY_SEPARATOR != "/") {
Worker::\$logFile = config('server')['log_file'] ?? Worker::\$logFile;
}
Worker::runAll();
EOF;
$process_file = $runtime_process_path . DIRECTORY_SEPARATOR . "start_$process_param.php";
file_put_contents($process_file, $file_content);
return $process_file;
}
if ($monitor_config = config('process.monitor.constructor')) {
$monitor = new Monitor(...array_values($monitor_config));
}
function popen_processes($process_files)
{
$cmd = "php " . implode(' ', $process_files);
$descriptorspec = [STDIN, STDOUT, STDOUT];
$resource = proc_open($cmd, $descriptorspec, $pipes);
if (!$resource) {
exit("Can not execute $cmd\r\n");
}
return $resource;
}
$resource = popen_processes($process_files);
echo "\r\n";
while (1) {
sleep(1);
if (!empty($monitor) && $monitor->checkAllFilesChange()) {
$status = proc_get_status($resource);
$pid = $status['pid'];
shell_exec("taskkill /F /T /PID $pid");
proc_close($resource);
$resource = popen_processes($process_files);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment