This commit is contained in:
2021-09-24 17:27:25 +08:00
parent dbc30827f8
commit 8a0858fbd7
72 changed files with 1 additions and 1 deletions
+467
View File
@@ -0,0 +1,467 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/10/7 0007
* Time: 2:13
*/
declare(strict_types=1);
namespace Kiri\Abstracts;
use Annotation\Annotation as SAnnotation;
use Database\Connection;
use Exception;
use Http\Handler\Router;
use Server\Server;
use Kafka\KafkaProvider;
use Kiri\Async;
use Kiri\Cache\Redis;
use Kiri\Di\LocalService;
use Kiri\Error\ErrorHandler;
use Kiri\Error\Logger;
use Kiri\Events\EventProvider;
use Kiri\Exception\InitException;
use Kiri\Exception\NotFindClassException;
use Kiri\Jwt\Jwt;
use Kiri\Kiri;
use ReflectionException;
use Server\ServerManager;
use Server\SInterface\OnTaskInterface;
use Swoole\Table;
/**
* Class BaseApplication
* @package Kiri\Kiri\Base
*/
abstract class BaseApplication extends Component
{
use TraitApplication;
/**
* @var string
*/
public string $storage = APP_PATH . 'storage';
public string $envPath = APP_PATH . '.env';
/**
* Init constructor.
*
*
* @throws
*/
public function __construct()
{
Kiri::init($this);
$config = sweep(APP_PATH . '/config');
$this->moreComponents();
$this->parseInt($config);
$this->parseEvents($config);
$this->initErrorHandler();
$this->enableEnvConfig();
$this->mapping($config['mapping'] ?? []);
parent::__construct();
}
/**
* @param array $mapping
*/
public function mapping(array $mapping)
{
$di = Kiri::getDi();
foreach ($mapping as $interface => $class) {
$di->mapping($interface, $class);
}
}
/**
* @return array
*/
public function enableEnvConfig(): array
{
if (!file_exists($this->envPath)) {
return [];
}
$lines = $this->readLinesFromFile($this->envPath);
foreach ($lines as $line) {
if (!$this->isComment($line) && $this->looksLikeSetter($line)) {
[$key, $value] = explode('=', $line);
putenv(trim($key) . '=' . trim($value));
}
}
return $lines;
}
/**
* Read lines from the file, auto detecting line endings.
*
* @param string $filePath
*
* @return array
*/
protected function readLinesFromFile(string $filePath): array
{
// Read file into an array of lines with auto-detected line endings
$autodetect = ini_get('auto_detect_line_endings');
ini_set('auto_detect_line_endings', '1');
$lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
ini_set('auto_detect_line_endings', $autodetect);
return $lines;
}
/**
* Determine if the line in the file is a comment, e.g. begins with a #.
*
* @param string $line
*
* @return bool
*/
protected function isComment(string $line): bool
{
$line = ltrim($line);
return isset($line[0]) && $line[0] === '#';
}
/**
* Determine if the given line looks like it's setting a variable.
*
* @param string $line
*
* @return bool
*/
protected function looksLikeSetter(string $line): bool
{
return str_contains($line, '=');
}
/**
* @param $config
*
* @throws
*/
public function parseInt($config)
{
Config::sets($config);
if ($storage = Config::get('storage', 'storage')) {
if (!str_contains($storage, APP_PATH)) {
$storage = APP_PATH . $storage . '/';
}
if (!is_dir($storage)) {
mkdir($storage);
}
if (!is_dir($storage) || !is_writeable($storage)) {
throw new InitException("Directory {$storage} does not have write permission");
}
}
}
/**
* @param $name
* @return mixed
* @throws ReflectionException
* @throws NotFindClassException
* @throws Exception
*/
public function __get($name): mixed
{
if ($this->has($name)) {
return $this->get($name);
}
return parent::__get($name); // TODO: Change the autogenerated stub
}
/**
* @param $config
*
* @throws
*/
public function parseEvents($config)
{
if (!isset($config['events']) || !is_array($config['events'])) {
return;
}
foreach ($config['events'] as $key => $value) {
if (is_string($value)) {
$value = Kiri::createObject($value);
}
$this->addEvent($key, $value);
}
}
/**
* @param OnTaskInterface $execute
* @throws ReflectionException
*/
public function task(OnTaskInterface $execute): void
{
di(ServerManager::class)->task($execute);
}
/**
* @param $key
* @param $value
* @throws InitException
* @throws Exception
*/
private function addEvent($key, $value): void
{
$eventProvider = di(EventProvider::class);
if ($value instanceof \Closure || is_object($value)) {
$eventProvider->on($key, $value, 0);
return;
}
if (is_array($value)) {
if (is_object($value[0]) && !($value[0] instanceof \Closure)) {
$eventProvider->on($key, $value, 0);
return;
}
if (is_string($value[0])) {
$value[0] = Kiri::createObject($value[0]);
$eventProvider->on($key, $value, 0);
return;
}
foreach ($value as $item) {
if (!is_callable($item, true)) {
throw new InitException("Class does not hav callback.");
}
$eventProvider->on($key, $item, 0);
}
}
}
/**
* @param $name
* @return mixed
* @throws Exception
*/
public function clone($name): mixed
{
return clone $this->get($name);
}
/**
*
* @throws Exception
*/
public function initErrorHandler()
{
$this->get('error')->register();
}
/**
* @param $name
* @return mixed
* @throws
*/
public function get($name): mixed
{
return di(LocalService::class)->get($name);
}
/**
* @return mixed
*/
public function getLocalIps(): mixed
{
return swoole_get_local_ip();
}
/**
* @return mixed
*/
public function getFirstLocal(): mixed
{
return current($this->getLocalIps());
}
/**
* @return Logger
* @throws
*/
public function getLogger(): Logger
{
return $this->get('logger');
}
/**
* @return \Redis|Redis
* @throws
*/
public function getRedis(): Redis|\Redis
{
return Kiri::getDi()->get(Redis::class);
}
/**
* @param $ip
* @return bool
*/
public function isLocal($ip): bool
{
return $this->getFirstLocal() == $ip;
}
/**
* @return ErrorHandler
* @throws
*/
public function getError(): ErrorHandler
{
return $this->get('error');
}
/**
* @param $name
* @return Table
* @throws
*/
public function getTable($name): Table
{
return $this->get($name);
}
/**
* @return Config
* @throws
*/
public function getConfig(): Config
{
return $this->get('config');
}
/**
* @return Router
* @throws
*/
public function getRouter(): Router
{
return Kiri::getDi()->get(Router::class);
}
/**
* @return Jwt
* @throws
*/
public function getJwt(): Jwt
{
return $this->get('jwt');
}
/**
* @return Server
* @throws
*/
public function getServer(): Server
{
return $this->get('server');
}
/**
* @return \Swoole\Http\Server|\Swoole\Server|\Swoole\WebSocket\Server|null
* @throws
*/
public function getSwoole(): \Swoole\Http\Server|\Swoole\Server|\Swoole\WebSocket\Server|null
{
return di(ServerManager::class)->getServer();
}
/**
* @return SAnnotation
* @throws
*/
public function getAnnotation(): SAnnotation
{
return $this->get('annotation');
}
/**
* @return Async
* @throws
*/
public function getAsync(): Async
{
return $this->get('async');
}
/**
* @param $array
*/
private function setComponents($array): void
{
di(LocalService::class)->setComponents($array);
}
/**
* @param $id
* @param $definition
*/
public function set($id, $definition): void
{
di(LocalService::class)->set($id, $definition);
}
/**
* @param $id
* @return bool
*/
public function has($id): bool
{
return di(LocalService::class)->has($id);
}
/**
* @throws Exception
*/
protected function moreComponents(): void
{
$this->setComponents([
'error' => ['class' => ErrorHandler::class],
'config' => ['class' => Config::class],
'logger' => ['class' => Logger::class],
'annotation' => ['class' => SAnnotation::class],
'databases' => ['class' => Connection::class],
'jwt' => ['class' => Jwt::class],
'async' => ['class' => Async::class],
'kafka-container' => ['class' => KafkaProvider::class],
]);
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Kiri\Abstracts;
use Exception;
use Kiri\Core\Json;
/**
* Class BaseGoto
* @package Kiri\Abstracts
*/
class BaseGoto extends Component
{
/**
* @param string $message
* @param int $statusCode
* @return mixed
* @throws Exception
*/
public function end(string $message, int $statusCode = 200): mixed
{
throw new Exception(Json::to(12350, $message), $statusCode);
}
}
+223
View File
@@ -0,0 +1,223 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/3/30 0030
* Time: 14:10
*/
declare(strict_types=1);
namespace Kiri\Abstracts;
use Exception;
use JetBrains\PhpStorm\Pure;
use Kiri\Kiri;
use Swoole\Coroutine;
/**
* Class BaseObject
* @package Kiri\Kiri\Base
*/
class BaseObject implements Configure
{
/**
* BaseAbstract constructor.
*
* @param array $config
* @throws Exception
*/
public function __construct(array $config = [])
{
if (!empty($config) && is_array($config)) {
Kiri::configure($this, $config);
}
}
/**
* @throws Exception
*/
public function init()
{
}
/**
* @param array|callable $callback
* @param object $scope
*/
public function async_create(array|callable $callback, object $scope)
{
Coroutine::create($callback, $scope);
}
/**
* @return string
*/
#[Pure] public static function className(): string
{
return static::class;
}
/**
* @param $name
* @param $value
*
* @throws Exception
*/
public function __set($name, $value)
{
$method = 'set' . ucfirst($name);
if (method_exists($this, $method)) {
$this->{$method}($value);
} else {
throw new Exception('The set name ' . $name . ' not find in class ' . static::class);
}
}
/**
* @param $name
*
* @return mixed
* @throws Exception
*/
public function __get($name): mixed
{
$method = 'get' . ucfirst($name);
if (method_exists($this, $method)) {
return $this->$method();
} else {
throw new Exception('The get name ' . $name . ' not find in class ' . static::class);
}
}
/**
* @param $message
* @param string $model
* @return bool
* @throws Exception
*/
public function addError($message, string $model = 'app'): bool
{
if ($message instanceof \Throwable) {
$this->error(jTraceEx($message));
} else {
if (!is_string($message)) {
$message = json_encode($message, JSON_UNESCAPED_UNICODE);
}
$this->error($message);
}
return FALSE;
}
/**
* @return Logger
* @throws Exception
*/
private function logger(): Logger
{
return Kiri::getDi()->get(Logger::class);
}
/**
* @param mixed $message
* @param string $method
* @param string $file
* @throws Exception
*/
public function debug(mixed $message, string $method = __METHOD__, string $file = __FILE__)
{
if (!is_string($message)) {
$message = print_r($message, true);
}
$message = "\033[35m[" . date('Y-m-d H:i:s') . '][DEBUG]: ' . $message . "\033[0m";
$message .= PHP_EOL;
$this->logger()->debug(Logger::DEBUG, [$message, $method, $file]);
}
/**
* @param mixed $message
* @param string $method
* @param string $file
* @throws Exception
*/
public function info(mixed $message, string $method = __METHOD__, string $file = __FILE__)
{
if (!is_string($message)) {
$message = print_r($message, true);
}
$message = "\033[34m[" . date('Y-m-d H:i:s') . '][INFO]: ' . $message . "\033[0m";
$message .= PHP_EOL;
$this->logger()->info(Logger::NOTICE, [$message, $method, $file]);
}
/**
* @param mixed $message
* @param string $method
* @param string $file
* @throws Exception
*/
public function success(mixed $message, string $method = __METHOD__, string $file = __FILE__)
{
if (!is_string($message)) {
$message = print_r($message, true);
}
$message = "\033[36m[" . date('Y-m-d H:i:s') . '][SUCCESS]: ' . $message . "\033[0m";
$message .= PHP_EOL;
$this->logger()->notice(Logger::NOTICE, [$message, $method, $file]);
}
/**
* @param mixed $message
* @param string $method
* @param string $file
* @throws Exception
*/
public function warning(mixed $message, string $method = __METHOD__, string $file = __FILE__)
{
if (!is_string($message)) {
$message = print_r($message, true);
}
$message = "\033[33m[" . date('Y-m-d H:i:s') . '][WARNING]: ' . $message . "\033[0m";
$message .= PHP_EOL;
$this->logger()->critical(Logger::NOTICE, [$message, $method, $file]);
}
/**
* @param mixed $message
* @param null $method
* @param null $file
* @throws Exception
*/
public function error(mixed $message, $method = null, $file = null)
{
if ($message instanceof \Throwable) {
$message = $message->getMessage() . " on line " . $message->getLine() . " at file " . $message->getFile();
}
$content = (empty($method) ? '' : $method . ': ') . $message;
$message = "\033[41;37m[" . date('Y-m-d H:i:s') . '][ERROR]: ' . $content . "\033[0m";
if (!empty($file)) {
$message .= PHP_EOL . "\033[41;37m[" . date('Y-m-d H:i:s') . '][ERROR]: ' . $file . "\033[0m";
}
$this->logger()->error(Logger::ERROR, [$message, $method, $file]);
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Kiri\Abstracts;
abstract class Command extends Component
{
}
+55
View File
@@ -0,0 +1,55 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/3/30 0030
* Time: 14:28
*/
declare(strict_types=1);
namespace Kiri\Abstracts;
use Exception;
use JetBrains\PhpStorm\Pure;
use Kiri\Exception\ComponentException;
use Kiri\Kiri;
/**
* Class Component
* @package Kiri\Kiri\Base
*/
class Component extends BaseObject
{
/**
* @param $name
* @param $value
* @throws Exception
*/
public function __set($name, $value)
{
if (property_exists($this, $name)) {
$this->$name = $value;
} else {
parent::__set($name, $value);
}
}
/**
* @param $name
* @return mixed
* @throws Exception
*/
public function __get($name): mixed
{
if (property_exists($this, $name)) {
return $this->$name ?? null;
} else {
return parent::__get($name);
}
}
}
+124
View File
@@ -0,0 +1,124 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/5/24 0024
* Time: 11:50
*/
declare(strict_types=1);
namespace Kiri\Abstracts;
use Exception;
use Kiri\Exception\ConfigException;
use Kiri\Kiri;
/**
* Class Config
* @package Kiri\Kiri\Base
*/
class Config extends Component
{
const ERROR_MESSAGE = 'The not find %s in app configs.';
protected mixed $data = [];
/**
* @return mixed
*/
public function getData(): mixed
{
return $this->data;
}
/**
* @param $key
* @param $value
* @return mixed
*/
public function setData($key, $value): mixed
{
return $this->data[$key] = $value;
}
/**
* @param array $configs
* @throws Exception
*/
public static function sets(array $configs)
{
$config = Kiri::app()->getConfig();
if (empty($configs)) {
return;
}
$config->data = $configs;
}
/**
* @param $key
* @param bool $try
* @param mixed|null $default
* @return mixed
* @throws
*/
public static function get($key, mixed $default = null, bool $try = FALSE): mixed
{
$instance = Kiri::app()->getConfig()->getData();
if (!str_contains($key, '.')) {
return $instance[$key] ?? $default;
}
foreach (explode('.', $key) as $value) {
if (empty($value)) {
continue;
}
if (!isset($instance[$value])) {
if ($try) {
throw new ConfigException(sprintf(self::ERROR_MESSAGE, $key));
}
return $default;
}
if (!is_array($instance[$value])) {
return $instance[$value];
}
$instance = $instance[$value];
}
return empty($instance) ? $default : $instance;
}
/**
* @param $key
* @param $value
* @return mixed
* @throws Exception
*/
public static function set($key, $value): mixed
{
$config = Kiri::app()->getConfig();
return $config->setData($key, $value);
}
/**
* @param $key
* @param bool $must_not_null
* @return bool
* @throws Exception
*/
public static function has($key, bool $must_not_null = false): bool
{
$config = Kiri::app()->getConfig();
if (!isset($config->data[$key])) {
return false;
}
$config = $config->data[$key];
if ($must_not_null === false) {
return true;
}
return !empty($config);
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/3/30 0030
* Time: 14:11
*/
declare(strict_types=1);
namespace Kiri\Abstracts;
/**
* Interface Configure
* @package Kiri\Kiri\Base
*/
interface Configure
{
}
+26
View File
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Kiri\Abstracts;
use Kiri\Core\Dtl;
/**
* Interface IListener
* @package Kiri\Abstracts
*/
interface IListener
{
/**
* @param Dtl $dtl
* @return mixed
*/
public function execute(Dtl $dtl): mixed;
}
+111
View File
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Kiri\Abstracts;
use Exception;
class Input
{
private array $_argv = [];
private string $_command = '';
/**
* Input constructor.
* @param $argv
* @throws
*/
public function __construct($argv)
{
$this->_argv = $this->resolve($argv);
}
/**
* @return string
*/
public function getCommandName(): string
{
return $this->_command;
}
/**
* @param $key
* @param null $default
* @return mixed
*/
public function get($key, $default = null): mixed
{
return $this->_argv[$key] ?? $default;
}
/**
* @param $key
* @return bool
*/
public function exists($key): bool
{
return isset($this->_argv[$key]);
}
/**
* @param $key
* @param $value
* @return $this
*/
public function set($key, $value): static
{
$this->_argv[$key] = $value;
return $this;
}
/**
* @return false|string
*/
public function toJson(): bool|string
{
return json_encode($this->_argv, JSON_UNESCAPED_UNICODE);
}
/**
* @param $parameters
* @return array
* @throws Exception
*/
public function resolve($parameters): array
{
$arrays = [];
$parameters = array_slice($parameters, 1);
if (empty($parameters)) {
return $arrays;
}
$this->_command = array_shift($parameters);
foreach ($parameters as $parameter) {
$explode = explode('=', $parameter);
if (count($explode) < 2) {
continue;
}
$arrays[array_shift($explode)] = current($explode);
}
return $arrays;
}
/**
* @return string
*/
public function getCommand(): string
{
return $this->_command;
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Kiri\Abstracts;
interface Kernel
{
/**
* @return array
*/
public function getCommands(): array;
}
+18
View File
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Kiri\Abstracts;
use Exception;
/**
* Class Listener
* @package Kiri\Abstracts
* 监听的名称
*/
abstract class Listener extends Component implements IListener
{
}
+200
View File
@@ -0,0 +1,200 @@
<?php
namespace Kiri\Abstracts;
use Annotation\Inject;
use Exception;
use Kiri\Events\EventProvider;
use Kiri\Exception\ConfigException;
use Psr\Log\LoggerInterface;
use Server\Events\OnAfterRequest;
use Server\Events\OnWorkerStop;
/**
*
*/
class Logger implements LoggerInterface
{
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
/**
* @var EventProvider
*/
#[Inject(EventProvider::class)]
public EventProvider $eventProvider;
private array $_loggers = [];
const LOGGER_LEVELS = [Logger::EMERGENCY, Logger::ALERT, Logger::CRITICAL, Logger::ERROR, Logger::WARNING, Logger::NOTICE, Logger::INFO, Logger::DEBUG];
/**
* 监听事件
*/
public function init()
{
$this->eventProvider->on(OnAfterRequest::class, [$this, 'onAfterRequest']);
$this->eventProvider->on(OnWorkerStop::class, [$this, 'onAfterRequest']);
}
/**
* @param string $message
* @param array $context
* @throws ConfigException
*
* 紧急情况
*/
public function emergency($message, array $context = array())
{
// TODO: Implement emergency() method.
$this->log(Logger::EMERGENCY, $message, $context);
}
/**
* @param string $message
* @param array $context
* @throws ConfigException
*
* 应该警惕的
*/
public function alert($message, array $context = array())
{
// TODO: Implement alert() method.
$this->log(Logger::ALERT, $message, $context);
}
/**
* @param string $message
* @param array $context
* @throws ConfigException
*
* 关键性的日志
*/
public function critical($message, array $context = array())
{
// TODO: Implement critical() method.
$this->log(Logger::CRITICAL, $message, $context);
}
/**
* @param string $message
* @param array $context
* @throws ConfigException
*/
public function error($message, array $context = array())
{
// TODO: Implement error() method.
$this->log(Logger::ERROR, $message, $context);
}
/**
* @param string $message
* @param array $context
* @throws ConfigException
*/
public function warning($message, array $context = array())
{
// TODO: Implement warning() method.
$this->log(Logger::WARNING, $message, $context);
}
/**
* @param string $message
* @param array $context
* @throws ConfigException
*/
public function notice($message, array $context = array())
{
// TODO: Implement notice() method.
$this->log(Logger::NOTICE, $message, $context);
}
/**
* @param string $message
* @param array $context
* @throws ConfigException
*/
public function info($message, array $context = array())
{
// TODO: Implement info() method.
$this->log(Logger::INFO, $message, $context);
}
/**
* @param string $message
* @param array $context
* @throws ConfigException
*/
public function debug($message, array $context = array())
{
// TODO: Implement debug() method.
$this->log(Logger::DEBUG, $message, $context);
}
/**
* @param mixed $level
* @param string $message
* @param array $context
* @throws ConfigException
*/
public function log($level, $message, array $context = array())
{
// TODO: Implement log() method.
$levels = Config::get('log.level', Logger::LOGGER_LEVELS);
if (!in_array($level, $levels)) {
return;
}
$_string = '[' . now() . '] production.' . $level . ': ' . $this->_string($message, $context);
file_put_contents('php://output', $_string);
$this->_loggers[] = $_string;
}
/**
* @param OnAfterRequest|OnWorkerStop $param
* @throws Exception
*/
public function onAfterRequest(OnAfterRequest|OnWorkerStop $param)
{
$loggers = implode(PHP_EOL, $this->_loggers);
$this->_loggers = [];
if (!empty($loggers)) {
$filename = storage('log-' . date('Y-m-d') . '.log', 'logs/');
file_put_contents($filename, $loggers);
}
}
/**
* @param $message
* @param $context
* @return string
*/
private function _string($message, $context): string
{
return $message . ' ' . print_r($context, true) . PHP_EOL;
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Kiri\Abstracts;
use Kiri\Application;
interface Provider
{
public function onImport(Application $application);
}
+15
View File
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Kiri\Abstracts;
/**
* Class Providers
* @package Kiri\Abstracts
*/
abstract class Providers extends Component implements Provider
{
}
@@ -0,0 +1,38 @@
<?php
namespace Kiri\Abstracts;
use Annotation\Annotation as SAnnotation;
use Database\Connection;
use Database\DatabasesProviders;
use Http\Client\Client;
use Http\Client\Curl;
use Http\Handler\Router;
use Server\Server;
use Kiri\Crontab\Producer;
use Kiri\Async;
use Kiri\Error\Logger;
use Kiri\Jwt\Jwt;
/**
* Trait TraitApplication
* @package Kiri\Abstracts
* @property Router $router
* @property Server $server
* @property DatabasesProviders $db
* @property Async $async
* @property Logger $logger
* @property Jwt $jwt
* @property SAnnotation $annotation
* @property BaseGoto $goto
* @property Client $client
* @property Connection $databases
* @property Curl $curl
* @property Producer $crontab
*/
trait TraitApplication
{
}
+259
View File
@@ -0,0 +1,259 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/4/25 0025
* Time: 18:38
*/
declare(strict_types=1);
namespace Kiri;
use Closure;
use Database\DatabasesProviders;
use Exception;
use Kiri\Abstracts\BaseApplication;
use Kiri\Abstracts\Config;
use Kiri\Abstracts\Kernel;
use Kiri\Crontab\CrontabProviders;
use Kiri\Exception\NotFindClassException;
use Kiri\FileListen\FileChangeCustomProcess;
use ReflectionException;
use Server\Events\OnBeforeCommandExecute;
use Server\ServerCommand;
use Server\ServerProviders;
use stdClass;
use Swoole\Process;
use Swoole\Timer;
use Symfony\Component\Console\Application as ConsoleApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
/**
* Class Init
*
* @package Kiri
*
* @property-read Config $config
*/
class Application extends BaseApplication
{
/**
* @var string
*/
public string $id = 'uniqueId';
public string $state = '';
/** @var array<array<Process>> */
private array $_process = [];
/**
*/
public function init()
{
$this->import(ServerProviders::class);
$this->register(Runtime::class);
}
/**
* @throws
*/
public function withDatabase()
{
$this->import(DatabasesProviders::class);
}
/**
* @throws
*/
public function withCrontab()
{
$this->import(CrontabProviders::class);
}
/**
* @param string $class
* @param Process $process
*/
public function addProcess(string $class, Process $process)
{
if (!isset($this->_process[$class])) {
$this->_process[$class] = [];
}
$this->_process[$class][] = $process;
}
/**
* @return Process[]
*/
public function getProcess(): array
{
return $this->_process;
}
/**
* @param string $class
* @return Process|null
*/
public function getProcessName(string $class): ?Process
{
return $this->_process[$class] ?? null;
}
/**
* @throws
*/
public function withFileChangeListen()
{
$manager = $this->getServer();
$manager->addProcess(FileChangeCustomProcess::class);
enable_file_modification_listening();
}
/**
* @param Closure|array $closure
* @return $this
* @throws Exception
*/
public function middleware(Closure|array $closure): static
{
$this->getRouter()->setMiddleware($closure);
return $this;
}
/**
* @param bool $useTree
* @return $this
* @throws Exception
*/
public function setUseTree(bool $useTree): static
{
$this->getRouter()->setUseTree($useTree);
return $this;
}
/**
* @param string $service
* @return $this
* @throws
*/
public function import(string $service): static
{
if (!class_exists($service)) {
return $this;
}
$class = Kiri::getDi()->get($service);
if (method_exists($class, 'onImport')) {
$class->onImport($this);
}
return $this;
}
/**
* @param Kernel $kernel
* @return $this
*/
public function commands(Kernel $kernel): static
{
foreach ($kernel->getCommands() as $command) {
$this->register($command);
}
return $this;
}
/**
* @param string $command
* @throws
*/
public function register(string $command)
{
di(ConsoleApplication::class)->add(di($command));
}
/**
* @param array $argv
* @return void
* @throws Exception
*/
public function execute(array $argv): void
{
[$input, $output] = $this->argument($argv);
try {
$console = di(ConsoleApplication::class);
$command = $input->getFirstArgument();
if (empty($command)) {
$command = 'list';
}
$command = $console->find($command);
if ($command instanceof Command) {
$this->enableFileChange($command, $input, $output);
}
} catch (\Throwable $exception) {
$output->writeln(jTraceEx($exception));
} finally {
Timer::clearAll();
}
}
/**
* @param $argv
* @return array
*/
private function argument($argv): array
{
return [new ArgvInput($argv), new ConsoleOutput()];
}
/**
* @throws NotFindClassException
* @throws ReflectionException
* @throws Exception
*/
private function enableFileChange(Command $class, $input, $output): void
{
fire(new OnBeforeCommandExecute());
if (!($class instanceof ServerCommand)) {
scan_directory(directory('app'), 'App');
} else if (!is_enable_file_modification_listening()) {
$this->getRouter()->read_files();
scan_directory(directory('app'), 'App');
}
$class->run($input, $output);
$output->writeln('ok' . PHP_EOL);
}
/**
* @param $className
* @param null $abstracts
* @return stdClass
* @throws Exception
*/
public function make($className, $abstracts = null): stdClass
{
return make($className, $abstracts);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace Kiri;
use Exception;
use Kiri\Abstracts\Component;
use Server\ServerManager;
/**
* Class Async
* @package Kiri
*/
class Async extends Component
{
private static array $_absences = [];
/**
* @param string $name
* @param string $handler
*/
public function addAsync(string $name, string $handler)
{
static::$_absences[$name] = $handler;
}
/**
* @param string $name
* @param array $params
* @throws Exception
*/
public function dispatch(string $name, array $params = [])
{
$context = di(ServerManager::class);
$context->task(static::$_absences[$name], $params);
}
}
+161
View File
@@ -0,0 +1,161 @@
<?php
namespace Kiri\Cache\Base;
use Http\Handler\Context;
use Kiri\Abstracts\Logger;
use Kiri\Events\EventProvider;
use Kiri\Exception\RedisConnectException;
use Kiri\Kiri;
use Kiri\Pool\StopHeartbeatCheck;
use RedisException;
use Server\Events\OnWorkerExit;
use Swoole\Timer;
/**
*
*/
class Redis implements StopHeartbeatCheck
{
const DB_ERROR_MESSAGE = 'The system is busy, please try again later.';
private ?\Redis $pdo = null;
private int $_transaction = 0;
/**
* @var EventProvider
*/
private EventProvider $eventProvider;
private int $_timer = -1;
private int $_last = 0;
/**
* @param string $host
* @param int $port
* @param int $database
* @param string $auth
* @param string $prefix
* @param int $timeout
* @param int $read_timeout
*/
public function __construct(public string $host, public int $port, public int $database = 0,
public string $auth = '', public string $prefix = '', public int $timeout = 30,
public int $read_timeout = 30)
{
$this->eventProvider = Kiri::getDi()->get(EventProvider::class);
}
public function init()
{
$this->heartbeat_check();
$this->eventProvider->on(OnWorkerExit::class, [$this, 'stopHeartbeatCheck']);
}
/**
*
*/
public function heartbeat_check(): void
{
if (env('state', 'start') == 'exit') {
return;
}
if ($this->_timer === -1 && Context::inCoroutine()) {
$this->_timer = Timer::tick(1000, function () {
try {
if (env('state', 'start') == 'exit') {
Kiri::getDi()->get(Logger::class)->critical('timer end');
$this->stopHeartbeatCheck();
}
if (time() - $this->_last > 10 * 60) {
$this->stopHeartbeatCheck();
$this->pdo = null;
}
} catch (\Throwable $throwable) {
error($throwable);
}
});
}
}
/**
*
*/
public function stopHeartbeatCheck(): void
{
if ($this->_timer > -1) {
Timer::clear($this->_timer);
}
$this->_timer = -1;
}
/**
* @param string $name
* @param array $arguments
* @return mixed
* @throws RedisConnectException|RedisException
*/
public function __call(string $name, array $arguments)
{
if (!method_exists($this, $name)) {
return $this->_pdo()->{$name}(...$arguments);
}
return $this->{$name}(...$arguments);
}
/**
* @return \Redis
* @throws RedisConnectException
* @throws RedisException
*/
public function _pdo(): \Redis
{
if ($this->_timer === -1) {
$this->heartbeat_check();
}
if (!($this->pdo instanceof \Redis) || !$this->pdo->ping('isOk')) {
$this->pdo = $this->newClient();
}
return $this->pdo;
}
/**
* @return \Redis
* @throws RedisConnectException
*/
private function newClient(): \Redis
{
$redis = new \Redis();
if (!$redis->connect($this->host, $this->port, $this->timeout)) {
throw new RedisConnectException(sprintf('The Redis Connect %s::%d Fail.', $this->host, $this->port));
}
if (!empty($this->auth) && !$redis->auth($this->auth)) {
throw new RedisConnectException(sprintf('Redis Error: %s, Host %s, Auth %s', $redis->getLastError(), $this->host, $this->auth));
}
if ($this->read_timeout < 0) {
$this->read_timeout = 0;
}
$redis->select($this->database);
if ($this->read_timeout > 0) {
$redis->setOption(\Redis::OPT_READ_TIMEOUT, $this->read_timeout);
}
$redis->setOption(\Redis::OPT_PREFIX, $this->prefix);
return $redis;
}
}
+143
View File
@@ -0,0 +1,143 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/5/2 0002
* Time: 14:51
*/
declare(strict_types=1);
namespace Kiri\Cache;
use Exception;
use JetBrains\PhpStorm\Pure;
use Kiri\Abstracts\Component;
use Swoole\Coroutine\System;
/**
* Class File
* @package Kiri\Kiri\Cache
*/
class File extends Component implements ICache
{
public string $path;
/**
* @param $key
* @param $val
* @return string|int
*/
public function set($key, $val): string|int
{
if (is_array($val) || is_object($val)) {
$val = swoole_serialize($val);
}
$tmpFile = $this->getCacheKey($key);
if (!$this->exists($tmpFile)) {
touch($tmpFile);
}
return System::writeFile($tmpFile, $val, LOCK_EX);
}
/**
* @param $key
* @param array $hashKeys
* @return array|bool
*/
public function hMGet($key, array $hashKeys): array|bool
{
$hash = $this->get($key);
if (!is_array($hash)) {
return false;
}
$nowHash = [];
foreach ($hashKeys as $hashKey) {
$nowHash[$hashKey] = $hash[$hashKey] ?? null;
}
return $nowHash;
}
/**
* @param $key
* @param array $val
* @return bool|int|string
*/
public function hMSet($key, array $val): bool|int|string
{
$hash = $this->get($key);
if (!is_array($hash)) {
return false;
}
$merge = array_merge($hash, $val);
return $this->set($key, $merge);
}
/**
* @param string $key
* @param string $hashKey
* @return string|int|bool
*/
public function hGet(string $key, string $hashKey): string|int|bool
{
$hash = $this->get($key);
if (!is_array($hash)) {
return false;
}
return $hash[$hashKey] ?? false;
}
/**
* @param $key
* @param $hashKey
* @param $hashValue
* @return bool|int|string
*/
public function hSet($key, $hashKey, $hashValue): bool|int|string
{
$hash = $this->get($key);
if (!is_array($hash)) {
return false;
}
$hash[$hashKey] = $hashValue;
return $this->set($key, $hash);
}
/**
* @param $key
* @return bool
*/
#[Pure] public function exists($key): bool
{
return file_exists($key);
}
/**
* @param $key
* @return mixed|bool
*/
public function get($key): string|bool
{
$tmpFile = $this->getCacheKey($key);
if (!$this->exists($tmpFile)) {
return false;
}
$content = file_get_contents($tmpFile);
return swoole_unserialize($content);
}
/**
* @param $key
* @return string
* @throws
*/
private function getCacheKey($key): string
{
return storage($key, 'cache');
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/11/8 0008
* Time: 16:35
*/
declare(strict_types=1);
namespace Kiri\Cache;
/**
* Interface ICache
* @package Kiri\Kiri\Cache
*/
interface ICache
{
/**
* @param $key
* @param $val
* @return string|int
*/
public function set($key, $val): string|int;
/**
* @param $key
* @return string|int|bool
*/
public function get($key): string|int|bool;
/**
* @param $key
* @param array $hashKeys
* @return array|bool|null
*/
public function hMGet($key, array $hashKeys): array|bool|null;
/**
* @param $key
* @param array $val
* @return mixed
*/
public function hMSet($key, array $val): mixed;
/**
* @param string $key
* @param string $hashKey
* @return string|int|bool
*/
public function hGet(string $key, string $hashKey): string|int|bool;
/**
* @param $key
* @param $hashKey
* @param $hashValue
* @return mixed
*/
public function hSet($key, $hashKey, $hashValue): mixed;
/**
* @param $key
* @return bool
*/
public function exists($key): bool;
}
+157
View File
@@ -0,0 +1,157 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/4/27 0027
* Time: 11:00
*/
declare(strict_types=1);
namespace Kiri\Cache;
use Annotation\Inject;
use Exception;
use Server\Events\OnWorkerExit;
use Server\Events\OnWorkerStop;
use Kiri\Abstracts\Component;
use Kiri\Abstracts\Config;
use Kiri\Core\Json;
use Kiri\Events\EventProvider;
use Kiri\Exception\ConfigException;
use Kiri\Kiri;
use Kiri\Pool\Redis as PoolRedis;
/**
* Class Redis
* @package Kiri\Kiri\Cache
* @mixin \Redis
*/
class Redis extends Component
{
/**
* @var EventProvider
*/
#[Inject(EventProvider::class)]
public EventProvider $eventProvider;
/**
* @throws ConfigException
* @throws Exception
*/
public function init()
{
$connections = Kiri::getDi()->get(PoolRedis::class);
$config = $this->get_config();
$length = Config::get('connections.pool.max', 10);
$this->eventProvider->on(OnWorkerExit::class, [$this, 'destroy'], 0);
$connections->initConnections('Redis:' . $config['host'], true, $length);
}
/**
* @param $name
* @param $arguments
* @return mixed
* @throws
*/
public function __call($name, $arguments): mixed
{
$time = microtime(true);
if (method_exists($this, $name)) {
$data = $this->{$name}(...$arguments);
} else {
$data = $this->proxy()->{$name}(...$arguments);
$this->release();
}
if (microtime(true) - $time >= 0.02) {
$this->warning('Redis:' . Json::encode([$name, $arguments]) . (microtime(true) - $time));
}
return $data;
}
/**
* @param $key
* @param int $timeout
* @return bool|int
* @throws Exception
*/
public function lock($key, int $timeout = 5): bool|int
{
$script = <<<SCRIPT
local _nx = redis.call('setnx',KEYS[1], ARGV[1])
if (_nx ~= 0) then
redis.call('expire',KEYS[1], ARGV[1])
return 1
end
return 0
SCRIPT;
return $this->eval($script, ['{lock}:' . $key, $timeout], 1);
}
/**
* @param $key
* @return int
* @throws Exception
*/
public function unlock($key): int
{
return $this->del('{lock}:' . $key);
}
/**
* @throws ConfigException
* @throws Exception
*/
public function release()
{
$connections = Kiri::getDi()->get(PoolRedis::class);
$connections->release($this->get_config(), true);
}
/**
* 销毁连接池
* @throws ConfigException
* @throws Exception
*/
public function destroy()
{
$connections = Kiri::getDi()->get(PoolRedis::class);
$connections->connection_clear($this->get_config(), true);
}
/**
* @return Base\Redis
* @throws Exception
*/
public function proxy(): Base\Redis
{
$connections = Kiri::getDi()->get(PoolRedis::class);
$config = $this->get_config();
$client = $connections->get($config, true);
if (!($client instanceof Base\Redis)) {
throw new Exception('Redis connections more.');
}
return $client;
}
/**
* @return array
* @throws ConfigException
*/
public function get_config(): array
{
return Config::get('cache.redis', null, true);
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/4/4 0004
* Time: 14:57
*/
declare(strict_types=1);
namespace Kiri\Core;
use Exception;
/**
* Class ArrayAccess
* @package Kiri\Core
*/
class ArrayAccess
{
/**
* @param $data
* @return array
* @throws Exception
*/
public static function toArray($data): array
{
if (!is_object($data) && !is_array($data)) {
return [];
}
if (is_object($data)) {
$data = self::objToArray($data);
}
$tmp = [];
if (!is_array($data)) {
return $tmp;
}
foreach ($data as $key => $val) {
if (is_array($val) || is_object($val)) {
$tmp[$key] = self::toArray($val);
} else {
$tmp[$key] = $val;
}
}
return $tmp;
}
/**
* @param $data
* @return array
* @throws Exception
*/
public static function objToArray($data): array
{
if (!is_object($data)) {
return $data;
}
if (method_exists($data, 'get')) {
$data = $data->get();
if (is_array($data)) {
return $data;
}
}
if (method_exists($data, 'toArray')) {
$data = $data->toArray();
} else {
$data = get_object_vars((object)$data);
}
return $data;
}
/**
* @param array $oldArray
* @param array $newArray
* @return array
*/
public static function merge(array $oldArray, array $newArray): array
{
if (empty($oldArray)) {
return $newArray;
} else if (empty($newArray)) {
return $oldArray;
}
foreach ($newArray as $item => $value) {
if (!isset($oldArray[$item])) {
$oldArray[$item] = $value;
}
if (is_array($value)) {
$oldArray[$item] = self::merge($oldArray[$item], $value);
} else {
$oldArray[$item] = $value;
}
}
return $oldArray;
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
/**
* Created by PhpStorm.
* User: dell
* Date: 2019/1/14 0014
* Time: 13:50
*/
declare(strict_types=1);
namespace Kiri\Core;
/**
* Class DateFormat
* @package Kiri\Kiri\Core
*/
class DateFormat
{
/**
* @param $time
* @return bool|false|int|string
*/
private static function check($time): bool|int|string
{
if ($time === null) {
$time = time();
} else if (is_numeric($time)) {
$length = strlen(floatval($time));
if ($length != 10 && $length != 13) {
return false;
}
} else if (is_string($time)) {
$time = strtotime($time);
}
if (date('Y-m-d', $time)) {
return $time;
}
return false;
}
/**
* @param null $time
* @return bool|false|int
*
* 获取指定日期当周第一天的时间
*/
public static function getWeekCurrentDay($time = null): bool|int
{
if (!($time = static::check($time))) {
return false;
}
$time = strtotime('-' . (date('N') - 1) . 'days', $time);
return strtotime(date('Y-m-d'), $time);
}
/**
* @param null $time
* @return bool|false|int
*
* 获取指定日期当月第一天的时间
*/
public static function getMonthCurrentDay($time = null): bool|int
{
if (!($time = static::check($time))) {
return false;
}
return strtotime(date('Y-m', $time) . '-01');
}
/**
* @param $time
* @return bool|int|string 指定的月份有几天
* 指定的月份有几天
*/
public static function getMonthTotalDay($time): bool|int|string
{
if (!($time = static::check($time))) {
return false;
}
$time = date('t', $time);
return $time;
}
/**
* @param $startTime
* @param null $endTime
* @return string
*/
public static function mtime($startTime, $endTime = null)
{
if ($endTime === null) {
$endTime = microtime(true);
}
return sprintf('%.7f', $endTime - $startTime);
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Kiri\Core;
use Exception;
use Kiri\Abstracts\Component;
/**
* Class Dtl
* @package Kiri\Core
*/
class Dtl extends Component
{
protected array $params;
/**
* Dtl constructor.
* @param $params
*/
public function __construct($params)
{
parent::__construct([]);
$this->params = $params;
}
/**
* @return array
* @throws Exception
*/
public function toArray(): array
{
if (!is_array($this->params)) {
return ArrayAccess::toArray($this->params);
}
return $this->params;
}
/**
* @param $name
* @return mixed
* @throws Exception
*/
public function get($name): mixed
{
$array = $this->toArray();
if (!isset($array[$name])) {
return null;
}
return $array[$name];
}
}
+215
View File
@@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
namespace Kiri\Core;
use Exception;
/**
* Class Help
* @package Kiri\Kiri\Core
*/
class Help
{
/**
* @param array $data
* @return string
*/
public static function toXml(array $data): string
{
$xml = "<xml>";
foreach ($data as $key => $val) {
if (is_array($val)) {
$xml .= "<" . $key . ">" . static::xmlChild($val) . "</" . $key . ">";
} else if (is_numeric($val)) {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
} else {
$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
}
}
$xml .= "</xml>";
return $xml;
}
/**
* @param array $array
* @return string
*/
private static function xmlChild(array $array): string
{
$string = '';
foreach ($array as $key => $value) {
if (is_array($value)) {
$string .= static::xmlChild($value);
} else if (is_numeric($value)) {
$string .= "<" . $key . ">" . $value . "</" . $key . ">";
} else {
$string .= "<" . $key . "><![CDATA[" . $value . "]]></" . $key . ">";
}
}
return $string;
}
/**
* @param $xml
* @return mixed
*/
public static function toArray($xml): mixed
{
if (empty($xml)) {
return [];
} else if (is_array($xml)) {
return $xml;
}
if (!($_xml = Xml::isXml($xml))) {
return static::jsonToArray($xml);
}
return $_xml;
}
/**
* @param $xml
* @return mixed
*/
public static function jsonToArray($xml): mixed
{
$_xml = json_decode($xml, true);
if (is_null($_xml)) {
return [];
}
return $_xml;
}
/**
* @param $xml
* @return mixed
*/
public static function xmlToArray($xml): mixed
{
if (is_array($xml)) {
return $xml;
}
if (($data = @simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)) !== false) {
return json_decode(json_encode($data), TRUE);
}
if (!is_null($json = json_decode($xml, TRUE))) {
return $json;
}
return $xml;
}
/**
* @param $parameter
* @return array|false|string
* @throws Exception
*/
public static function toString($parameter): bool|array|string
{
if (!is_string($parameter)) {
$parameter = ArrayAccess::toArray($parameter);
if (is_array($parameter)) {
$parameter = Json::encode($parameter);
}
}
return $parameter;
}
/**
* @param mixed $json
* @return bool|string
*/
public static function toJson(mixed $json): bool|string
{
if (is_object($json)) {
$json = get_object_vars($json);
}
if (is_array($json)) {
return json_encode($json, JSON_UNESCAPED_UNICODE);
}
$matchQuote = '/(<\?xml.*?\?>)?<([a-zA-Z_]+)>(<([a-zA-Z_]+)><!.*?><\/\4>)+<\/\2>/';
if (preg_match($matchQuote, $json)) {
$json = self::xmlToArray($json);
} else {
$json = json_decode($json, true);
}
if (!is_array($json)) {
$json = [];
}
return json_encode($json, JSON_UNESCAPED_UNICODE);
}
/**
* @param int $length
* @return string
*
* 随机字符串
*/
public static function random($length = 20): string
{
$res = [];
$str = 'abcdefghijklmnopqrstuvwxyz';
$str .= strtoupper($str) . '1234567890';
for ($i = 0; $i < $length; $i++) {
$rand = substr($str, rand(0, strlen($str) - 2), 1);
if (empty($rand)) {
$rand = substr($str, strlen($str) - 3, 1);
}
array_push($res, $rand);
}
return implode($res);
}
/**
* @param array $array
* @param $key
* @param string $type
* @return string
*/
public static function sign(array $array, $key, string $type = 'MD5'): string
{
ksort($array, SORT_ASC);
$string = [];
foreach ($array as $hashKey => $val) {
if (empty($val)) {
continue;
}
$string[] = $hashKey . '=' . $val;
}
$string[] = 'key=' . $key;
$string = implode('&', $string);
if ($type == 'MD5') {
return strtoupper(md5($string));
} else {
return hash('sha256', $string);
}
}
/**
* @param $email
* @param string $Subject
* @param $messageContent
*/
public static function sendEmail($email, string $Subject, $messageContent)
{
$mailer = new \Swift_Mailer((new \Swift_SmtpTransport($email['host'], $email['port']))
->setUsername($email['username'])->setPassword($email['password']));
$message = (new \Swift_Message($Subject))
->setFrom([$email['send']['address'] => $email['send']['nickname']])
->setBody('Here is the message itself');
foreach ($email['receive'] as $item) {
$message->setTo([$item['address'], $item['address'] => $item['nickname']]);
}
$mailer->send($messageContent);
}
}
+122
View File
@@ -0,0 +1,122 @@
<?php
/**
* Created by PhpStorm.
* User: admin
* Date: 2019-03-20
* Time: 01:04
*/
declare(strict_types=1);
namespace Kiri\Core;
use Error;
use Exception;
use Throwable;
/**
* Class JSON
* @package Kiri\Kiri\Core
*/
class Json
{
/**
* @param $data
* @return false|string
*/
public static function encode($data): bool|string
{
if (empty($data)) {
return false;
}
if (is_array($data)) {
return json_encode($data, JSON_UNESCAPED_UNICODE);
}
return $data;
}
/**
* @param $data
* @param bool $asArray
* @return mixed
*/
public static function decode($data, $asArray = true): mixed
{
if (is_array($data) || is_numeric($data)) {
return $data;
}
if (!is_string($data)) return null;
return json_decode($data, $asArray);
}
/**
* @param $code
* @param string $message
* @param array $data
* @param int $count
* @param array $exPageInfo
* @return mixed
* @throws
*/
public static function to($code, $message = '', $data = [], $count = 0, $exPageInfo = []): mixed
{
$params['code'] = $code;
if (!is_string($message)) {
$params['param'] = $message;
if (!empty($data)) {
$params['exPageInfo'] = $data;
}
$params['message'] = 'System success.';
} else {
$params['message'] = $message;
$params['param'] = $data;
}
if (!empty($exPageInfo)) {
$params['exPageInfo'] = $exPageInfo;
}
$params['count'] = $count;
if (is_numeric($data) || !is_numeric($count)) {
$params['count'] = $data;
$params['exPageInfo'] = $count;
}
if ((int)$params['count'] == -100) {
$params['count'] = 1;
}
return static::encode($params);
}
/**
* @param Throwable|Error $throwable
* @return bool|string
*/
public static function error(Throwable|Error $throwable): bool|string
{
$array['code'] = $throwable->getCode() == 0 ? 500 : $throwable->getCode();
$array['message'] = $throwable->getMessage();
$array['param'] = [
'file' => $throwable->getFile(),
'line' => $throwable->getLine()
];
return Json::encode($array);
}
/**
* @param $state
* @param $body
* @return false|int|string
* @throws Exception
*/
public static function output($state, $body): bool|int|string
{
$params['state'] = $state;
$params['body'] = ArrayAccess::toArray($body);
return static::encode($params);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace Kiri\Core;
class Number
{
/**
* @param int $userId
* @return string
*/
public static function order(int $userId): string
{
$explode = current(explode(' ', str_replace('0.', '', microtime())));
return 'No.' . sprintf('%09d', $userId) . '.' . date('Ymd.His') . '.' . $explode;
}
/**
* @param int $userId
* @return string
*/
public static function create(int $userId): string
{
return static::order($userId);
}
}
+172
View File
@@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace Kiri\Core;
/**
* Class Reader
* @package Kiri\Kiri\Core
*/
class Reader
{
/**
* @param $filepath
* @param int $page
* @param int $size
* @return array and int
*/
public static function readerServerLogPagination($filepath, int $page = 1, int $size = 20): array
{
$count = 0;
$strings = [];
$offset = ($page - 1) * $size;
if (!file_exists($filepath)) {
return [0, []];
}
//只读方式打开文件
$fp = fopen($filepath, "r");
//开始循环读取$buffer_size
while (!feof($fp)) {
//读文件到缓冲区
$buffer = fgets($fp);
$count++;
if ($count > $offset && count($strings) < $size) {
$strings[] = [
'id' => $count,
'content' => $buffer
];
}
}
//关闭文件
fclose($fp);
unset($fp);
return ['total' => $count, 'list' => $strings];
}
/**
* @param $filename
* @param $start
* @param $lines
* @return array
*/
public static function read_backward_line($filename, $start, $lines): array
{
$lines++;
$offset = -1;
$read = '';
$fp = @fopen($filename, "r");
$tmpStart = 0;
while ($lines && fseek($fp, $offset, SEEK_END) >= 0) {
$c = fgetc($fp);
if ($c == "\n" || $c == "\r") {
if (++$tmpStart >= $start)
$lines--;
}
if ($tmpStart >= $start)
$read .= $c;
$offset--;
}
$read = trim($read);
$contents = [];
$read = array_reverse(explode("\n", strrev($read)));
foreach ($read as $key => $value) {
if (empty($value)) {
unset($read[$key]);
} else {
$contents[] = ['content' => $value, 'id' => $key + $start];
}
}
$response['total'] = self::read_count_by_file($filename);
$response['list'] = $contents;
return $response;
}
/**
* @param $filepath
* @return int
*/
private static function read_count_by_file($filepath): int
{
$count = 0;
//只读方式打开文件
$fp = fopen($filepath, "r");
//开始循环读取$buffer_size
while (fgets($fp)) {
$count++;
}
//关闭文件
fclose($fp);
unset($fp);
return $count;
}
/**
* @param $filepath
* @param int $page
* @param int $size
* @return array
*/
public static function folderPagination($filepath, int $page = 1, int $size = 20): array
{
$count = 0;
$strings = [];
$offset = ($page - 1) * $size;
if (!is_dir($filepath)) {
return [0, []];
}
foreach (glob($filepath . '/*') as $key => $value) {
$count++;
if ($key < $offset || count($strings) >= $size) {
continue;
}
$explode = explode(DIRECTORY_SEPARATOR, $value);
$addTime = fileatime($value);
$changeTime = filectime($value);
$modifyTime = filemtime($value);
$strings[] = [
'id' => $count,
'path' => $value,
'isDir' => (int)is_dir($value),
'name' => end($explode),
'atime' => [
'format' => date('Y-m-d H:i:s', $addTime),
'microtime' => $addTime
],
'ctime' => [
'format' => date('Y-m-d H:i:s', $changeTime),
'microtime' => $changeTime
],
'mtime' => [
'format' => date('Y-m-d H:i:s', $modifyTime),
'microtime' => $modifyTime
],
];
}
array_multisort($strings, array_column($strings, 'isDir'), SORT_DESC);
return ['total' => $count, 'list' => $strings];
}
}
+277
View File
@@ -0,0 +1,277 @@
<?php
declare(strict_types=1);
namespace Kiri\Core;
use Exception;
/**
* Class Str
* @package Kiri\Kiri\Core
*/
class Str
{
const STRING = 'abcdefghijklmnopqrstuvwxyz';
const NUMBER = '01234567890';
/**
* @param int $length
*
* @return string
* 获取随机字符串
*/
public static function rand(int $length = 20): string
{
$string = '';
if ($length < 1) $length = 20;
$default = self::STRING . strtoupper(self::STRING) . self::NUMBER;
$default = str_split($default);
$string .= str_repeat($default[array_rand($default)], $length);
return (string)$string;
}
/**
* @param int $length
*
* @return int|string 获取随机数字
* 获取随机数字
*/
public static function random(int $length = 20): int|string
{
$number = '';
$default = str_split(self::NUMBER);
if ($length < 1) $length = 1;
$number .= str_repeat($default[array_rand($default)], $length);
return $number;
}
/**
* @param $string
* @param $sullen
* @param bool $strip_tags
* @param string $append
*
* @return string
*/
public static function cut_str_utf8($string, $sullen, bool $strip_tags = true, string $append = '...'): string
{
if ($strip_tags) {
$string = strip_tags($string);
}//去掉签标
$pa = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|\xe0[\xa0-\xbf][\x80-\xbf]|[\xe1-\xef][\x80-\xbf][\x80-\xbf]|\xf0[\x90-\xbf][\x80-\xbf][\x80-\xbf]|[\xf1-\xf7][\x80-\xbf][\x80-\xbf][\x80-\xbf]/";
preg_match_all($pa, $string, $t_string);
$str = "";
for ($i = 0; $i < count($t_string[0]); $i++) {
$str .= $t_string[0][$i];
//转为gbk,一个汉字长度为2
if (strlen(@iconv('utf-8', 'gbk', $str)) >= $sullen) {
if ($i != count($t_string[0]) - 1) $str .= $append;
break;
}
}
return $str;
}
/**
* @param $data
*
* @param null $callback
* @return bool
* 判断是否为json字符串
*/
public static function isJson($data, $callback = null): bool
{
$json = !is_null(json_decode($data)) && !is_numeric($data);
if ($json && is_callable($callback, true)) {
return call_user_func($callback, $data);
}
return $json;
}
/**
* @param $data
*
* @param null $callBack
* @return bool
* 判断是否序列化字符串
*/
public static function isSerialize($data, $callBack = null): bool
{
$false = !empty($data) && swoole_unserialize($data) !== false;
if ($false && is_callable($callBack, true)) {
return call_user_func($callBack, $data);
}
return $false;
}
/**
* @param $string
* @param int $length
*
* @param string $append
* @return string
*/
public static function cut($string, int $length = 20, string $append = '...'): string
{
if (empty($string)) {
return '';
}
if ($length < 1) {
$length = 1;
}
$array = str_split($string);
if (count($array) <= $length) {
return implode('', $array);
}
$string = implode('', array_slice($array, 0, $length));
if (!empty($append)) {
$string .= $append;
}
return $string;
}
/**
* @param $str
* @param int $number
* @param string $key
*
* @return string
*/
public static function encrypt($str, int $number = 10, string $key = 'xshucai.com'): string
{
$res = [];
$add = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$len = strlen($key) < 0 ? 1 : (strlen($key) + 5 > strlen($add) ? strlen($add) - 5 : strlen($key));
if ($number < 1) $number = 10;
$array = str_split($str);
asort($array);
$str = implode('', $array);
for ($i = 0; $i < $number; $i++) {
$_tmp = md5($key) . md5($str) . mb_substr($add, $len, $len + 5, 'utf-8');
$res[] = md5($_tmp);
}
sort($res, SORT_STRING);
return hash('sha384', implode('', $res));
}
/**
* @param $file
* @param $type
* @return string
*/
public static function filename($file, $type): string
{
switch ($type) {
case 'image/png':
return md5_file($file) . '.png';
case 'image/jpeg':
case 'image/jpg':
return md5_file($file) . '.jpg';
case 'image/gif':
return md5_file($file) . '.gif';
break;
}
return md5_file($file);
}
/**
* @param $endTime
* @param int|null $startTime
* @return array
* 剩余天,带分秒
*/
public static function timeout($endTime, int $startTime = null): array
{
$endTime = $endTime - (!empty($startTime) ? $startTime : time());
$day = intval($endTime / (3600 * 24));
$hours = intval(($endTime - ($day * (3600 * 24))) / 3600);
$minute = intval(($endTime - ($day * (3600 * 24) + $hours * 3600)) / 60);
$scrod = intval(($endTime - ($day * (3600 * 24) + $hours * 3600 + $minute * 60)));
return [$day, $hours, $minute, $scrod];
}
/**
* @return false|int
*/
public static function get_sy_time(): bool|int
{
$time = strtotime('+1days', strtotime(date('Y-m-d')));
return $time - time();
}
/**
* @param string $string
* @return string
*/
public static function encode(string $string): string
{
return addslashes($string);
}
/**
* @param string $string
* @return string|string[]|null
* 清除标点符号
*/
public static function clear(string $string): array|string|null
{
$char = '。、!?:;﹑•"…‘’“”〝〞∕¦‖— 〈〉﹞﹝「」‹›〖〗】【»«』『〕〔》《﹐¸﹕︰﹔!¡?¿﹖﹌﹏﹋'´ˊˋ―﹫︳︴¯_ ̄﹢﹦﹤‐­˜﹟﹩﹠﹪﹡﹨﹍﹉﹎﹊ˇ︵︶︷︸︹︿﹀︺︽︾ˉ﹁﹂﹃﹄︻︼()';
return preg_replace(array("/[[:punct:]]/i", '/[' . $char . ']/u', '/[ ]{2,}/'), '', $string);
}
/**
* @param int $user
* @param array $param
* @param null $requestTime
*
* @return string
* @throws Exception
*/
public static function token(int $user, array $param = [], $requestTime = NULL): string
{
$str = '';
if (!$requestTime) {
$requestTime = microtime(true);
}
$_user = str_split(md5($user . md5((string)$user)));
ksort($_user);
foreach ($_user as $key => $val) {
$str .= md5(sha1($key . $val . 'www.xshucai.com'));
}
if (is_array($param)) {
foreach ($param as $key => $val) {
$str .= md5($str . sha1($key . md5($val)));
}
}
$str .= sha1(base64_encode((string)$requestTime));
$md5 = md5($str . $user);
return preg_replace('/(\w{10})(\w{3})(\w{4})(\w{9})(\w{6})/', '$1-$2-$3-$4-$5', $md5);
}
/**
* @param string $str
* @param bool $unfairest
* @return string
*/
public static function convertUnderline(string $str, bool $unfairest = true): string
{
$str = ucwords(str_replace('_', ' ', $str));
$str = str_replace(' ', '', lcfirst($str));
return $unfairest ? ucfirst($str) : $str;
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
/**
* Created by PhpStorm.
* User: admin
* Date: 2019-03-20
* Time: 01:03
*/
declare(strict_types=1);
namespace Kiri\Core;
use Exception;
/**
* Class Xml
* @package Kiri\Kiri\Core
*/
class Xml
{
/**
* @param $data
* @param bool $asArray
* @return array|object
* @throws Exception
*/
public static function toArray($data, bool $asArray = true): object|array
{
$data = simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
if ($data === false) {
throw new Exception('Parameter format error.');
}
$array = get_object_vars($data);
if (isset($array[0])) {
$array[$data->getName()] = $array[0];
unset($array[0]);
}
return $array;
}
/**
* @param $str
* @return array|bool|object
* @throws Exception
*/
public static function isXml($str): object|bool|array
{
$xml_parser = xml_parser_create();
if (!xml_parse($xml_parser, $str, true)) {
xml_parser_free($xml_parser);
return false;
} else {
return self::toArray($str);
}
}
}
+440
View File
@@ -0,0 +1,440 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/4/24 0024
* Time: 17:27
*/
declare(strict_types=1);
namespace Kiri\Di;
use Annotation\Inject;
use Closure;
use Exception;
use Kiri\Abstracts\BaseObject;
use Kiri\Abstracts\Logger;
use Kiri\Exception\NotFindClassException;
use Kiri\Kiri;
use Psr\Log\LoggerInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionProperty;
use Server\Constrict\Request;
use Server\Constrict\Response;
use Server\Constrict\RequestInterface;
use Server\Constrict\ResponseInterface;
/**
* Class Container
* @package Kiri\Di
*/
class Container extends BaseObject implements ContainerInterface
{
/**
* @var array
*
* instance class by className
*/
private array $_singletons = [];
/**
* @var ReflectionMethod[]
*
* class new instance construct parameter
*/
private array $_constructs = [];
/**
* @var array
*
* implements \ReflectClass
*/
private array $_reflection = [];
/** @var array */
private array $_parameters = [];
/** @var array|string[] */
private array $_interfaces = [
RequestInterface::class => Request::class,
ResponseInterface::class => Response::class,
LoggerInterface::class => Logger::class
];
/**
* @param $class
* @param array $constrict
* @param array $config
*
* @return mixed
* @throws
*/
public function get($class, array $constrict = [], array $config = []): mixed
{
if ($this->isInterface($class)) {
$class = $this->_interfaces[$class];
}
if (!isset($this->_singletons[$class])) {
$this->_singletons[$class] = $this->resolve($class, $constrict, $config);
}
return $this->_singletons[$class];
}
/**
* @param string $interface
* @param string $class
*/
public function mapping(string $interface, string $class)
{
$this->_interfaces[$interface] = $class;
}
/**
* @param $class
* @return bool
* @throws ReflectionException
*/
public function isInterface($class): bool
{
$reflect = $this->getReflect($class);
if ($reflect->isInterface()) {
return true;
}
return false;
}
/**
* @param string $interface
* @param $object
*/
public function setBindings(string $interface, $object)
{
$this->_singletons[$interface] = $object;
$this->_interfaces[$interface] = get_class($object);
}
/**
* @param $class
* @param array $constrict
* @param array $config
* @return object
* @throws
*/
public function newObject($class, array $constrict = [], array $config = []): object
{
return $this->resolve($class, $constrict, $config);
}
/**
* @param $class
* @param $constrict
* @param $config
*
* @return object
* @throws Exception
*/
private function resolve($class, $constrict, $config): object
{
$reflect = $this->resolveDependencies($class);
if (!$reflect->isInstantiable()) {
throw new ReflectionException('Class ' . $class . ' cannot be instantiated');
}
$object = $this->newInstance($reflect, $constrict);
$this->propertyInject($reflect, $object);
return $this->onAfterInit($object, $config);
}
/**
* @param ReflectionClass $reflect
* @param $dependencies
* @return object
* @throws ReflectionException
*/
private function newInstance(ReflectionClass $reflect, $dependencies): object
{
if (!isset($this->_constructs[$reflect->getName()])) {
return $reflect->newInstance();
}
$construct = $this->_constructs[$reflect->getName()];
if ($construct->getNumberOfParameters() < 1) {
return $reflect->newInstance();
}
$parameters = $this->mergeParam($this->resolveMethodParameters($construct), $dependencies);
return $reflect->newInstanceArgs($parameters);
}
/**
* @param ReflectionClass $reflect
* @param $object
* @return mixed
* @throws Exception
*/
public function propertyInject(ReflectionClass $reflect, $object): mixed
{
foreach (NoteManager::getPropertyNote($reflect) as $property => $inject) {
/** @var Inject $inject */
$inject->execute($object, $property);
}
return $object;
}
/**
* @param $className
* @param $method
* @return array
* @throws ReflectionException
*/
public function getMethodAttribute($className, $method = null): array
{
$methods = NoteManager::getMethodNote($this->getReflect($className));
if (!empty($method)) {
return $methods[$method] ?? [];
}
return $methods;
}
/**
* @param string $class
* @param string|null $property
* @return ReflectionProperty|ReflectionProperty[]|null
* @throws ReflectionException
*/
public function getClassReflectionProperty(string $class, string $property = null): ReflectionProperty|null|array
{
$lists = NoteManager::getProperty($this->getReflect($class));
if (empty($lists)) {
return null;
}
if (!empty($property)) {
return $lists[$property] ?? null;
}
return $lists;
}
/**
* @param $object
* @param $config
* @return mixed
*/
private function onAfterInit($object, $config): mixed
{
Kiri::configure($object, $config);
if (method_exists($object, 'init') && is_callable([$object, 'init'])) {
call_user_func([$object, 'init']);
}
return $object;
}
/**
* @param $class
* @return ReflectionClass
* @throws ReflectionException
*/
private function resolveDependencies($class): ReflectionClass
{
if (isset($this->_reflection[$class])) {
return $this->_reflection[$class];
}
$reflect = new ReflectionClass($class);
if ($reflect->isAbstract() || $reflect->isTrait() || $reflect->isInterface()) {
return $this->_reflection[$class] = $reflect;
}
$construct = NoteManager::resolveTarget($reflect);
if (!empty($construct) && $construct->getNumberOfParameters() > 0) {
$this->_constructs[$class] = $construct;
}
return $this->_reflection[$class] = $reflect;
}
/**
* @param ReflectionClass|string $class
* @return ReflectionMethod[]
* @throws ReflectionException
*/
public function getReflectMethods(ReflectionClass|string $class): array
{
if (is_string($class)) {
$class = $this->getReflect($class);
}
return NoteManager::getMethods($class);
}
/**
* @param ReflectionClass|string $class
* @param string $method
* @return ReflectionMethod|null
* @throws ReflectionException
*/
public function getReflectMethod(ReflectionClass|string $class, string $method): ?ReflectionMethod
{
return $this->getReflectMethods($class)[$method] ?? null;
}
/**
* @param string $className
* @param string $method
* @return array|null
* @throws ReflectionException
*/
public function getMethodParameters(string $className, string $method): ?array
{
if (isset($this->_parameters[$className]) && isset($this->_parameters[$className][$method])) {
return $this->_parameters[$className][$method];
}
$reflectMethod = $this->getReflectMethod($this->getReflect($className), $method);
if (!($reflectMethod instanceof ReflectionMethod)) {
throw new ReflectionException("Class does not have a function $className::$method");
}
$className = $reflectMethod->getDeclaringClass()->getName();
if (isset($this->_parameters[$className]) && isset($this->_parameters[$className][$reflectMethod->getName()])) {
return $this->_parameters[$className][$reflectMethod->getName()];
}
return $this->setParameters($className, $reflectMethod->getName(), $this->resolveMethodParameters($reflectMethod));
}
/**
* @param $class
* @param $method
* @param $parameters
* @return mixed
*/
private function setParameters($class, $method, $parameters): mixed
{
if (!isset($this->_parameters[$class])) {
$this->_parameters[$class] = [];
}
if (!isset($this->_parameters[$class][$method])) {
$this->_parameters[$class][$method] = [];
}
return $this->_parameters[$class][$method] = $parameters;
}
/**
* @param Closure $reflectionMethod
* @return array
* @throws ReflectionException
*/
public function getFunctionParameters(Closure $reflectionMethod): array
{
return $this->resolveMethodParameters(new ReflectionFunction($reflectionMethod));
}
/**
* @param ReflectionMethod|ReflectionFunction $reflectionMethod
* @return array
* @throws ReflectionException
*/
private function resolveMethodParameters(ReflectionMethod|ReflectionFunction $reflectionMethod): array
{
if ($reflectionMethod->getNumberOfParameters() < 1) {
return [];
}
$params = [];
foreach ($reflectionMethod->getParameters() as $key => $parameter) {
if ($parameter->isDefaultValueAvailable()) {
$params[$key] = $parameter->getDefaultValue();
} else if ($parameter->getType() === null) {
$params[$key] = $parameter->getType();
} else {
$type = $parameter->getType()->getName();
if (is_string($type) && class_exists($type) || isset($this->_interfaces[$type])) {
$type = Kiri::getDi()->get($type);
}
$params[$key] = match ($parameter->getType()) {
'string' => '',
'int', 'float' => 0,
'', null, 'object', 'mixed' => NULL,
'bool' => false,
default => $type
};
}
}
return $params;
}
/**
* @param $class
* @return ReflectionClass|null
* @throws ReflectionException
*/
public function getReflect($class): ?ReflectionClass
{
if (!isset($this->_reflection[$class])) {
return $this->resolveDependencies($class);
}
return $this->_reflection[$class];
}
/**
* @param $class
*/
public function unset($class)
{
if (is_array($class) && isset($class['class'])) {
$class = $class['class'];
} else if (is_object($class)) {
$class = $class::class;
}
unset(
$this->_reflection[$class], $this->_singletons[$class], $this->_constructs[$class]
);
}
/**
* @return $this
*/
public function flush(): static
{
$this->_reflection = [];
$this->_singletons = [];
$this->_constructs = [];
return $this;
}
/**
* @param $old
* @param $newParam
*
* @return mixed
*/
private function mergeParam($old, $newParam): array
{
if (empty($old)) {
return $newParam;
} else if (empty($newParam)) {
return $old;
}
foreach ($newParam as $key => $val) {
$old[$key] = $val;
}
return $old;
}
}
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace Kiri\Di;
/**
* @mixin Container
*/
interface ContainerInterface
{
}
+86
View File
@@ -0,0 +1,86 @@
<?php
namespace Kiri\Di;
use Annotation\Inject;
use Kiri\Abstracts\Component;
use Kiri\Kiri;
/**
* 服务定位器
*/
class LocalService extends Component
{
private array $_components = [];
private array $_definition = [];
/**
* @param $name
* @param $define
*/
public function set($name, $define)
{
unset($this->_components[$name]);
$this->_definition[$name] = $define;
}
/**
* @throws \Exception
*/
public function get(string $name, $throwException = true)
{
if (isset($this->_components[$name])) {
return $this->_components[$name];
}
if (isset($this->_definition[$name])) {
$definition = $this->_definition[$name];
if (is_object($definition) && !$definition instanceof \Closure) {
return $this->_components[$name] = $definition;
}
return $this->_components[$name] = Kiri::createObject($definition);
} else if ($throwException) {
throw new \Exception("Unknown component ID: $name");
}
return null;
}
/**
* @param array $components
*/
public function setComponents(array $components)
{
foreach ($components as $name => $component) {
$this->set($name, $component);
}
}
/**
* @param $id
* @return bool
*/
public function has($id): bool
{
return isset($this->_components[$id]) || isset($this->_definition[$id]);
}
/**
* @param $id
*/
public function remove($id): void
{
unset($this->_components[$id], $this->_definition[$id]);
}
}
+289
View File
@@ -0,0 +1,289 @@
<?php
namespace Kiri\Di;
use JetBrains\PhpStorm\Pure;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionProperty;
class NoteManager
{
private static array $_classTarget = [];
private static array $_classMethodNote = [];
private static array $_classMethod = [];
private static array $_classPropertyNote = [];
private static array $_classProperty = [];
private static array $_mapping = [];
/**
* @param ReflectionClass $class
*/
public static function setTargetNote(ReflectionClass $class)
{
$className = $class->getName();
if (!isset(static::$_classTarget[$className])) {
static::$_classTarget[$className] = [];
}
foreach ($class->getAttributes() as $attribute) {
if (!class_exists($attribute->getName())) {
continue;
}
$instance = $attribute->newInstance();
static::$_classTarget[$className][] = $instance;
self::setMappingClass($attribute, $className);
}
}
/**
* @param ReflectionAttribute $attribute
* @param string $class
*/
public static function setMappingClass(ReflectionAttribute $attribute, string $class)
{
if (!isset(static::$_mapping[$attribute->getName()])) {
static::$_mapping[$attribute->getName()] = [];
}
if (!isset(static::$_mapping[$attribute->getName()][$class])) {
static::$_mapping[$attribute->getName()][$class] = [];
}
}
/**
* @param ReflectionAttribute $attribute
* @param string $class
* @param string $method
* @param mixed $instance
*/
public static function setMappingMethod(ReflectionAttribute $attribute, string $class, string $method, mixed $instance)
{
self::setMappingClass($attribute, $class);
if (!isset(static::$_mapping[$attribute->getName()][$class]['method'])) {
static::$_mapping[$attribute->getName()][$class]['method'] = [];
}
static::$_mapping[$attribute->getName()][$class]['method'][] = [$method => $instance];
}
/**
* @param ReflectionAttribute $attribute
* @param string $class
* @param string $property
* @param $instance
*/
public static function setMappingProperty(ReflectionAttribute $attribute, string $class, string $property, $instance)
{
self::setMappingClass($attribute, $class);
$mapping = static::$_mapping[$attribute->getName()][$class];
if (!isset($mapping['property'])) {
$mapping['property'] = [];
}
$mapping['property'][] = [$property => $instance];
static::$_mapping[$attribute->getName()][$class] = $mapping;
}
/**
* @param mixed $class
* @return array
*/
public static function getTargetNote(mixed $class): array
{
if (!is_string($class)) {
$class = $class::class;
}
return static::$_classTarget[$class] ?? [];
}
/**
* @param ReflectionClass $class
*/
public static function setMethodNote(ReflectionClass $class)
{
$className = $class->getName();
static::$_classMethodNote[$className] = static::$_classMethod[$className] = [];
foreach ($class->getMethods() as $ReflectionMethod) {
static::$_classMethod[$className][$ReflectionMethod->getName()] = $ReflectionMethod;
static::$_classMethodNote[$className][$ReflectionMethod->getName()] = [];
foreach ($ReflectionMethod->getAttributes() as $attribute) {
if (!class_exists($attribute->getName())) {
continue;
}
$instance = $attribute->newInstance();
static::$_classMethodNote[$className][$ReflectionMethod->getName()][] = $instance;
self::setMappingMethod($attribute, $className, $ReflectionMethod->getName(), $instance);
}
}
}
/**
* @param string $class
* @param string $method
* @return bool
*/
public static function hasMethod(string $class, string $method): bool
{
return isset(static::$_classMethod[$class]) && isset(static::$_classMethod[$class][$method]);
}
/**
* @param ReflectionClass $class
* @return array
*/
#[Pure] public static function getMethodNote(ReflectionClass $class): array
{
return static::$_classMethodNote[$class->getName()] ?? [];
}
/**
* @param \ReflectionClass $reflect
* @return \ReflectionMethod|null
*/
public static function resolveTarget(ReflectionClass $reflect): ?\ReflectionMethod
{
NoteManager::setPropertyNote($reflect);
NoteManager::setTargetNote($reflect);
NoteManager::setMethodNote($reflect);
return $reflect->getConstructor();
}
/**
* @param ReflectionClass $class
*/
public static function setPropertyNote(ReflectionClass $class)
{
$className = $class->getName();
static::$_classProperty[$className] = static::$_classPropertyNote[$className] = [];
foreach ($class->getProperties(ReflectionProperty::IS_PRIVATE | ReflectionProperty::IS_PUBLIC |
ReflectionProperty::IS_PROTECTED) as $ReflectionMethod) {
static::$_classProperty[$className][$ReflectionMethod->getName()] = $ReflectionMethod;
foreach ($ReflectionMethod->getAttributes() as $attribute) {
if (!class_exists($attribute->getName())) {
continue;
}
$instance = $attribute->newInstance();
static::$_classPropertyNote[$className][$ReflectionMethod->getName()] = $instance;
self::setMappingProperty($attribute, $className, $ReflectionMethod->getName(), $instance);
}
}
}
/**
* @param string $attribute
* @param string|null $class
* @return array[]
*/
public static function getAttributeTrees(string $attribute, string $class = null): array
{
$mapping = static::$_mapping[$attribute] ?? [];
if (empty($mapping) || empty($class)) {
return $mapping;
}
return $mapping[$class] ?? [];
}
/**
* @param string $attribute
* @param string $class
* @param string|null $method
* @return array
*/
public static function getSpecify_annotation(string $attribute, string $class, string $method = null): mixed
{
$class = self::getAttributeTrees($attribute, $class);
if (empty($class) || !isset($class['method'])){
return null;
}
if (empty($method)) {
return $class['method'];
}
foreach ($class['method'] as $value) {
$key = key($value);
if ($method == $key) {
return $value[$key];
}
}
return null;
}
/**
* @param string $attribute
* @param string $class
* @param string $method
* @return mixed
*/
public static function getPropertyByAnnotation(string $attribute, string $class, string $method): mixed
{
$class = self::getAttributeTrees($attribute, $class);
if (empty($class) || !isset($class['property'])) {
return [];
}
foreach ($class['property'] as $value) {
$key = key($value);
if ($method == $key) {
return $value[$key];
}
}
return null;
}
/**
* @param ReflectionClass|string $class
* @return array
* @throws \ReflectionException
*/
public static function getMethods(ReflectionClass|string $class): array
{
if (is_string($class)) {
$class = self::getReflect($class);
}
return static::$_classMethod[$class->getName()] ?? [];
}
/**
* @param ReflectionClass $class
* @return ReflectionProperty[]
*/
#[Pure] public static function getProperty(ReflectionClass $class): array
{
return static::$_classProperty[$class->getName()] ?? [];
}
/**
* @param ReflectionClass $class
* @return array
*/
#[Pure] public static function getPropertyNote(ReflectionClass $class): array
{
return static::$_classPropertyNote[$class->getName()] ?? [];
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace Kiri;
use JetBrains\PhpStorm\Pure;
/**
* Class Environmental
* @package Kiri
*/
class Environmental
{
/**
* @return bool
*/
#[Pure] public function isMac(): bool
{
$output = strtolower(PHP_OS | PHP_OS_FAMILY);
if (str_contains('mac', $output)) {
return true;
} else if (str_contains('darwin', $output)) {
return true;
} else {
return false;
}
}
/**
* @return bool
*/
#[Pure] public function isLinux(): bool
{
if (!static::isMac()) {
return true;
} else {
return false;
}
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/4/26 0026
* Time: 10:00
*/
declare(strict_types=1);
namespace Kiri\Error;
use Exception;
use Http\Handler\Formatter\IFormatter;
use Kiri\Abstracts\Component;
use Kiri\Core\Json;
use Kiri\Events\EventDispatch;
use Kiri\Kiri;
use Server\Events\OnAfterRequest;
/**
* Class ErrorHandler
*
* @package Kiri\Kiri\Base
* @property-read $asError
*/
class ErrorHandler extends Component implements ErrorInterface
{
/** @var ?IFormatter $message */
private ?IFormatter $message = NULL;
public string $category = 'app';
/**
* 错误处理注册
*/
public function register()
{
// ini_set('display_errors', '1');
set_exception_handler([$this, 'exceptionHandler']);
if (defined('HHVM_VERSION')) {
set_error_handler([$this, 'errorHandler']);
} else {
set_error_handler([$this, 'errorHandler']);
}
register_shutdown_function([$this, 'shutdown']);
}
/**
* @throws Exception
*/
public function shutdown()
{
$lastError = error_get_last();
if (empty($lastError) || $lastError['type'] !== E_ERROR) {
return;
}
$this->category = 'shutdown';
$messages = explode(PHP_EOL, $lastError['message']);
$message = array_shift($messages);
$this->sendError($message, $lastError['file'], $lastError['line']);
}
/**
* @param \Throwable $exception
*
* @throws Exception
*/
public function exceptionHandler(\Throwable $exception)
{
$this->category = 'exception';
di(EventDispatch::class)->dispatch(new OnAfterRequest());
$this->sendError($exception->getMessage(), $exception->getFile(), $exception->getLine());
}
/**
* @throws Exception
*
* 以异常形式抛出错误,防止执行后续程序
*/
public function errorHandler()
{
$error = func_get_args();
$path = ['file' => $error[2], 'line' => $error[3]];
if ($error[0] === 0) {
$error[0] = 500;
}
$data = Json::to(500, $error[1], $path);
Kiri::app()->error($data, 'error');
di(EventDispatch::class)->dispatch(new OnAfterRequest());
throw new \ErrorException($error[1], $error[0], 1, $error[2], $error[3]);
}
/**
* @param $message
* @param $file
* @param $line
* @param int $code
* @return false|string
* @throws Exception
*/
public function sendError($message, $file, $line, $code = 500): bool|string
{
$path = ['file' => $file, 'line' => $line];
$data = Json::to($code, $this->category . ': ' . $message, $path);
write($data, $this->category);
return $data;
}
/**
* @return mixed
*/
public function getErrorMessage(): mixed
{
$message = $this->message;
$this->message = NULL;
return $message->getData();
}
/**
* @return bool
*/
public function getAsError(): bool
{
return $this->message !== NULL;
}
/**
* @param $message
* @param string $category
*
* @throws Exception
*/
public function writer($message, string $category = 'app')
{
Kiri::app()->debug($message, $category);
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
/**
* Created by PhpStorm.
* User: admin
* Date: 2019-03-20
* Time: 10:25
*/
declare(strict_types=1);
namespace Kiri\Error;
/**
* Interface ErrorInterface
* @package Kiri\Kiri\Error
*/
interface ErrorInterface
{
/**
* @param $message
* @param $file
* @param $line
* @param int $code
* @return mixed
*/
public function sendError($message, $file, $line, $code = 500): mixed;
}
+111
View File
@@ -0,0 +1,111 @@
<?php
/**
* Created by PhpStorm.
* User: admin
* Date: 2019-03-22
* Time: 14:36
*/
declare(strict_types=1);
namespace Kiri\Error;
use Annotation\Inject;
use Exception;
use Kiri\Abstracts\Component;
use Kiri\Core\Json;
use Kiri\Events\EventProvider;
use Kiri\Kiri;
use Psr\Log\LoggerInterface;
use Throwable;
/**
* Class Logger
* @package Kiri\Kiri\Error
* @mixin \Kiri\Abstracts\Logger
*/
class Logger extends Component
{
private array $logs = [];
/** @var EventProvider */
#[Inject(EventProvider::class)]
public EventProvider $eventProvider;
/**
* inject logger
*
* @var LoggerInterface
*/
#[Inject(LoggerInterface::class)]
public LoggerInterface $logger;
private array $sources = [];
/**
* @param string $application
* @return string
*/
public function getLastError(string $application = 'app'): string
{
return 'Unknown error.';
}
/**
* @param string $messages
* @param string $method
* @throws Exception
*/
public function write(string $messages, string $method = 'app')
{
if (empty($messages)) {
return;
}
$to_day = date('Y-m-d');
$fileName = storage('server-' . $to_day . '.log', $dirName = 'log/' . ($method ?? 'app'));
file_put_contents($fileName, '[' . date('Y-m-d H:i:s') . ']:' . PHP_EOL . $messages . PHP_EOL);
}
/**
* @param Throwable $exception
* @return mixed
* @throws Exception
*/
public function exception(Throwable $exception): mixed
{
$code = $exception->getCode() == 0 ? 500 : $exception->getCode();
$logger = Kiri::app()->getLogger();
$logger->write(jTraceEx($exception), 'exception');
return Json::to($code, $exception->getMessage(), [
'file' => $exception->getFile(),
'line' => $exception->getLine()
]);
}
/**
* @param string $name
* @param array $arguments
* @return mixed
*/
public function __call(string $name, array $arguments): mixed
{
if (!method_exists($this, $name)) {
return $this->logger->{$name}(...$arguments);
} else {
return $this->{$name}(...$arguments);
}
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace Kiri\Error;
use Exception;
use Kiri\IAspect;
/**
* Class LoggerAspect
* @package Kiri\Error
*/
class LoggerAspect implements IAspect
{
private float $time;
/**
* @param mixed $handler
* @param array $params
* @return mixed
*/
public function invoke(mixed $handler, array $params = []): mixed
{
return call_user_func($handler, ...$params);
}
/**
* @param $startTime
* @throws Exception
*/
private function print_runtime($startTime)
{
$runTime = round(microtime(true) - $startTime, 6);
echo sprintf('run %s use time %6f', request()->getUri()->__toString(), $runTime);
echo PHP_EOL;
}
public function before(): void
{
// TODO: Implement before() method.
$this->time = microtime(true);
}
/**
* @throws Exception
*/
public function after(mixed $response): void
{
// TODO: Implement after() method.
$this->print_runtime($this->time);
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
namespace Kiri\Error;
use Exception;
use JetBrains\PhpStorm\Pure;
use Kiri\Core\Json;
use Kiri\Exception\ComponentException;
use Kiri\Kiri;
use Swoole\Coroutine;
use Swoole\Process;
use Server\Abstracts\OnProcessInterface;
/**
* Class LoggerProcess
* @package Kiri\Error
*/
class LoggerProcess extends OnProcessInterface
{
/**
* @param Process $process
* @return string
*/
#[Pure] public function getProcessName(Process $process): string
{
// TODO: Implement getProcessName() method.
return get_called_class();
}
/**
* @param Process $process
* @throws ComponentException
*/
public function onHandler(Process $process): void
{
// TODO: Implement onHandler() method.
$this->message($process);
}
/**
* @param Process $process
* @throws ComponentException
* @throws Exception
*/
public function message(Process $process)
{
if ($this->checkProcessIsStop()) {
$this->exit();
return;
}
$message = Json::decode($process->read());
if (!empty($message)) {
Kiri::writeFile($this->getDirName($message), $message[0], FILE_APPEND);
$this->checkLogFile($message[1]);
}
Coroutine\System::sleep(1);
$this->message($process);
}
/**
* @param $message
* @return string
* @throws Exception
*/
private function getDirName($message): string
{
return storage('server-' . date('Y-m-d') . '.log', $message[1]);
}
/**
* @param $dirName
* @throws Exception
*/
private function checkLogFile($dirName)
{
$files = new \DirectoryIterator(storage(null, $dirName));
if ($files->getSize() < 15) {
return;
}
Coroutine\System::exec('find ' . storage(null, $dirName) . '/ -mtime +15 -name "*.log" -exec rm -rf {} \;');
}
}
+239
View File
@@ -0,0 +1,239 @@
<?php
declare(strict_types=1);
namespace Kiri;
use Exception;
use Kiri\Abstracts\BaseObject;
use Swoole\Coroutine;
/**
* Class Event
* @package Kiri
*/
class Event extends BaseObject
{
public bool $isVide = true;
private static array $_events = [];
const PIPE_MESSAGE = 'SERVER:PIPE:MESSAGE';
const TASK_FINISH = 'SERVER:TASK::FINISH';
const EVENT_AFTER_REQUEST = 'SERVER:REQUEST:AFTER:START';
const EVENT_BEFORE_REQUEST = 'SERVER:REQUEST:BEFORE:START';
const RECEIVE_CONNECTION = 'SERVER:RECEIVE:CONNECTION';
const SYSTEM_RESOURCE_RELEASES = 'SYSTEM::RESOURCE::RELEASES';
const SYSTEM_RESOURCE_CLEAN = 'SYSTEM::RESOURCE::CLEAN';
const PROCESS_WORKER_STOP = 'SERVER:PROCESS:WORKER:STOP';
const SERVER_AFTER_RELOAD = 'SERVER:AFTER:RELOAD';
const SERVER_BEFORE_RELOAD = 'SERVER:BEFORE:RELOAD';
const SERVER_CONNECT = 'SERVER:CONNECT';
const SERVER_PACKAGE = 'SERVER:PACKAGE';
const SERVER_RECEIVE = 'SERVER:RECEIVE';
const SERVER_EVENT_START = 'SERVER:EVENT:START';
const SERVER_MANAGER_START = 'SERVER:EVENT:MANAGER:START';
const SERVER_MANAGER_STOP = 'SERVER:EVENT:MANAGER:START';
const SERVER_WORKER_STOP = 'SERVER:EVENT:WORKER:STOP';
const SERVER_WORKER_START = 'SERVER:EVENT:WORKER:START';
const SERVER_AFTER_WORKER_START = 'SERVER:EVENT:AFTER:WORKER:START';
const SERVER_BEFORE_START = 'SERVER:EVENT:BEFORE:START';
const BEFORE_COMMAND_EXECUTE = 'COMMAND:EVENT:BEFORE:EXECUTE';
const AFTER_COMMAND_EXECUTE = 'COMMAND:EVENT:AFTER:EXECUTE';
const SERVER_TASK_START = 'SERVER:EVENT:TASK:START';
const SERVER_WORKER_EXIT = 'SERVER:EVENT:WORKER:EXIT';
const SERVER_WORKER_ERROR = 'SERVER:EVENT:WORKER:ERROR';
const SERVER_SHUTDOWN = 'SERVER:EVENT:SHUTDOWN';
const SERVER_HANDSHAKE = 'on handshake';
const SERVER_MESSAGE = 'on message';
const SERVER_CLIENT_CLOSE = 'SERVER:CLIENT:CLOSE';
const SERVER_ON_START = 'Start';
const SERVER_ON_SHUTDOWN = 'Shutdown';
const SERVER_ON_WORKER_START = 'WorkerStart';
const SERVER_ON_WORKER_STOP = 'WorkerStop';
const SERVER_ON_WORKER_EXIT = 'WorkerExit';
const SERVER_ON_CONNECT = 'Connect';
const SERVER_ON_RECEIVE = 'Receive';
const SERVER_ON_PACKET = 'Packet';
const SERVER_ON_REQUEST = 'request';
const SERVER_ON_CLOSE = 'Close';
const SERVER_ON_TASK = 'Task';
const SERVER_ON_FINISH = 'Finish';
const SERVER_ON_PIPE_MESSAGE = 'OnPipeMessageInterface';
const SERVER_ON_WORKER_ERROR = 'WorkerError';
const SERVER_ON_MANAGER_START = 'ManagerStart';
const SERVER_ON_MANAGER_STOP = 'ManagerStop';
const SERVER_ON_BEFORE_RELOAD = 'BeforeReload';
const SERVER_ON_AFTER_RELOAD = 'AfterReload';
/**
* @param $name
* @param $callback
* @param bool $isAppend
* @throws Exception
*/
public static function on($name, $callback, bool $isAppend = false)
{
if (!isset(static::$_events[$name])) {
static::$_events[$name] = [];
}
if (is_array($callback) && is_string($callback[0])) {
if (!class_exists($callback[0])) {
throw new Exception('Undefined callback class.');
}
$callback[0] = di($callback[0]);
}
if (static::exists($name, $callback)) {
return;
}
if (!empty(static::$_events[$name]) && $isAppend === true) {
array_unshift(static::$_events[$name], [$callback]);
} else {
static::$_events[$name][] = [$callback];
}
}
/**
* @param $name
* @param $callback
*/
public static function of($name, $callback): void
{
if (!isset(static::$_events[$name])) {
return;
}
foreach (static::$_events[$name] as $index => $event) {
[$handler] = $event;
if ($handler !== $callback) {
continue;
}
unset(static::$_events[$name][$index]);
}
}
/**
* @param $name
*/
public static function offName($name): void
{
unset(static::$_events[$name]);
}
/**
* @param $name
* @param null $callback
* @return bool
*/
public static function exists($name, $callback): bool
{
if ($callback instanceof \Closure || !isset(static::$_events[$name])) {
return false;
}
foreach (static::$_events[$name] as $event) {
[$handler] = $event;
if ($handler === $callback) {
return true;
}
}
return false;
}
/**
* @param $name
* @param $handler
* @return mixed
*/
public static function get($name, $handler): mixed
{
if (!static::exists($name, $handler)) {
return null;
}
if (empty($handler)) {
return static::$_events[$name];
}
foreach (static::$_events[$name] as $event) {
[$callback] = $event;
if ($callback === $handler) {
return [$event];
}
}
return null;
}
public static function clean()
{
static::$_events = [];
}
/**
* @param $name
* @param array $params
* @return bool
* @throws Exception
*/
public function dispatch($name, array $params = []): bool
{
return static::trigger($name, $params);
}
/**
* @param $name
* @param null $parameter
* @param false $is_remove
* @return bool
* @throws Exception
*/
public static function trigger($name, $parameter = null, bool $is_remove = false): bool
{
foreach ((static::$_events[$name] ?? []) as $key => $event) {
static::execute($event, $parameter);
if ($event instanceof \Closure) {
unset(static::$_events[$name][$key]);
}
}
if ($is_remove) {
unset(static::$_events[$name]);
}
return true;
}
/**
* @param $event
* @param $parameter
* @return void
* @throws Exception
*/
private static function execute($event, $parameter): void
{
try {
call_user_func($event[0], ...$parameter);
} catch (\Throwable $throwable) {
logger()->addError($throwable, 'throwable');
return;
}
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Kiri\Exception;
use Throwable;
/**
* Class AuthException
* @package Kiri\Exception
*/
class AuthException extends \Exception
{
/**
* AuthException constructor.
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, 4001, $previous);
}
}
@@ -0,0 +1,35 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/4/25 0025
* Time: 18:34
*/
declare(strict_types=1);
namespace Kiri\Exception;
use Throwable;
/**
* Class ComponentException
* @package Kiri\Kiri\Exception
*/
class ComponentException extends \Exception
{
/**
* ComponentException constructor.
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $message = "", int $code = 0, Throwable $previous = NULL)
{
parent::__construct($message, 5000, $previous);
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Kiri\Exception;
/**
* Class ConfigException
* @package Kiri\Exception
*/
class ConfigException extends \Exception
{
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Kiri\Exception;
/**
* Class InitException
* @package Kiri\Exception
*/
class InitException extends \Exception
{
}
@@ -0,0 +1,36 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/4/24 0024
* Time: 17:32
*/
declare(strict_types=1);
namespace Kiri\Exception;
use JetBrains\PhpStorm\Pure;
use Throwable;
/**
* Class NotFindClassException
* @package Kiri\Kiri\Exception
*/
class NotFindClassException extends \Exception
{
/**
* NotFindClassException constructor.
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
#[Pure] public function __construct(string $message = "", int $code = 0, Throwable $previous = null)
{
$message = "No class named `$message` was found, please check if the class name is correct";
parent::__construct($message, 404, $previous);
}
}
@@ -0,0 +1,35 @@
<?php
/**
* Created by PhpStorm.
* User: whwyy
* Date: 2018/4/24 0024
* Time: 17:32
*/
declare(strict_types=1);
namespace Kiri\Exception;
use Throwable;
/**
* Class NotFindClassException
* @package Kiri\Kiri\Exception
*/
class NotFindPropertyException extends \Exception
{
/**
* NotFindClassException constructor.
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $message = "", int $code = 0, Throwable $previous = null)
{
$message = "No class named `$message` was found, please check if the class name is correct";
parent::__construct($message, 404, $previous);
}
}
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Kiri\Exception;
/**
* Class RedisConnectException
* @package Kiri\Exception
*/
class RedisConnectException extends \Exception
{
}
@@ -0,0 +1,94 @@
<?php
namespace Kiri\FileListen;
use Exception;
use Kiri\Abstracts\Config;
use Kiri\Kiri;
use ReflectionException;
use Server\Abstracts\OnProcessInterface;
use Swoole\Process;
/**
*
*/
class FileChangeCustomProcess extends OnProcessInterface
{
public bool $isReloading = false;
public bool $isReloadingOut = false;
public ?array $dirs = [];
public int $events;
public int $int = -1;
/**
* @param Process $process
* @return string
*/
public function getProcessName(Process $process): string
{
// TODO: Implement getProcessName() method.
return 'file change listener.';
}
/**
* @param Process $process
* @throws Exception
*/
public function onHandler(Process $process): void
{
// TODO: Implement onHandler() method.
set_error_handler([$this, 'onErrorHandler']);
$this->dirs = Config::get('inotify', [APP_PATH . 'app']);
if (!extension_loaded('inotify')) {
$driver = Kiri::getDi()->get(Scaner::class, [$this->dirs, $this]);
} else {
$driver = Kiri::getDi()->get(Inotify::class, [$this->dirs, $this]);
}
$driver->start();
}
/**
* @param Process $process
*/
public function signListen(Process $process): void
{
}
/**
* @param $code
* @param $message
* @param $file
* @param $line
* @throws Exception
*/
public function onErrorHandler($code, $message, $file, $line)
{
if (str_contains($message, 'The file descriptor is not an inotify instance')) {
return;
}
debug('Error:' . $message . ' at ' . $file . ':' . $line);
}
/**
* 重启
* @throws Exception
*/
public function trigger_reload()
{
exec(PHP_BINARY . ' ' . APP_PATH . 'kiri.php runtime:builder', $output);
print_r(implode(PHP_EOL, $output));
Kiri::reload();
}
}
+155
View File
@@ -0,0 +1,155 @@
<?php
namespace Kiri\FileListen;
use Exception;
use Swoole\Event;
class Inotify
{
private mixed $inotify;
private mixed $events;
private array $watchFiles = [];
const IG_DIR = [APP_PATH . 'commands', APP_PATH . '.git', APP_PATH . '.gitee'];
/**
* @param array $dirs
* @param FileChangeCustomProcess $process
*/
public function __construct(protected array $dirs, public FileChangeCustomProcess $process)
{
}
/**
* @throws Exception
*/
public function start()
{
$this->inotify = inotify_init();
$this->events = IN_MODIFY | IN_DELETE | IN_CREATE | IN_MOVE;
foreach ($this->dirs as $dir) {
if (!is_dir($dir)) continue;
$this->watch($dir);
}
Event::add($this->inotify, [$this, 'check']);
Event::wait();
}
/**
* 开始监听
*/
public function check()
{
if (!($events = inotify_read($this->inotify))) {
return;
}
if ($this->process->isReloading) {
if (!$this->process->isReloadingOut) {
$this->process->isReloadingOut = true;
}
return;
}
$LISTEN_TYPE = [IN_CREATE, IN_DELETE, IN_MODIFY, IN_MOVED_TO, IN_MOVED_FROM];
foreach ($events as $ev) {
if (!in_array($ev['mask'], $LISTEN_TYPE)) {
continue;
}
//非重启类型
if (str_ends_with($ev['name'], '.php')) {
if ($this->process->int !== -1) {
return;
}
$this->process->int = @swoole_timer_after(2000, [$this, 'reload']);
$this->process->isReloading = true;
}
}
}
/**
* @throws Exception
*/
public function reload()
{
$this->process->isReloading = true;
$this->process->trigger_reload();
$this->clearWatch();
foreach ($this->dirs as $root) {
$this->watch($root);
}
$this->process->int = -1;
$this->process->isReloading = FALSE;
$this->process->isReloadingOut = FALSE;
}
/**
* @throws Exception
*/
public function clearWatch()
{
foreach ($this->watchFiles as $wd) {
try {
inotify_rm_watch($this->inotify, $wd);
} catch (\Throwable $exception) {
logger()->addError($exception, 'throwable');
}
}
$this->watchFiles = [];
}
/**
* @param $dir
* @return bool
* @throws Exception
*/
public function watch($dir): bool
{
//目录不存在
if (!is_dir($dir)) {
return logger()->addError("[$dir] is not a directory.");
}
//避免重复监听
if (isset($this->watchFiles[$dir])) {
return FALSE;
}
if (in_array($dir, self::IG_DIR)) {
return FALSE;
}
$wd = @inotify_add_watch($this->inotify, $dir, $this->events);
$this->watchFiles[$dir] = $wd;
$files = scandir($dir);
foreach ($files as $f) {
if ($f == '.' || $f == '..') {
continue;
}
$path = $dir . '/' . $f;
//递归目录
if (is_dir($path)) {
$this->watch($path);
} else if (!str_ends_with($f, '.php')) {
continue;
}
//检测文件类型
if (strstr($f, '.') == '.php') {
$wd = @inotify_add_watch($this->inotify, $path, $this->events);
$this->watchFiles[$path] = $wd;
}
}
return TRUE;
}
}
+135
View File
@@ -0,0 +1,135 @@
<?php
namespace Kiri\FileListen;
use Exception;
use Swoole\Timer;
class Scaner
{
private array $md5Map = [];
/**
* @param array $dirs
* @param FileChangeCustomProcess $process
*/
public function __construct(protected array $dirs, public FileChangeCustomProcess $process)
{
}
/**
* @throws Exception
*/
public function start(): void
{
$this->loadDirs();
$this->tick();
}
/**
* @param bool $isReload
* @throws Exception
*/
private function loadDirs(bool $isReload = false)
{
foreach ($this->dirs as $value) {
if (is_bool($path = realpath($value))) {
continue;
}
if (!is_dir($path)) continue;
$this->loadByDir($path, $isReload);
}
}
/**
* @param $path
* @param bool $isReload
* @return void
* @throws Exception
*/
private function loadByDir($path, bool $isReload = false): void
{
if (!is_string($path)) {
return;
}
$path = rtrim($path, '/');
foreach (glob(realpath($path) . '/*') as $value) {
if (is_dir($value)) {
$this->loadByDir($value, $isReload);
}
if (is_file($value)) {
if ($this->checkFile($value, $isReload)) {
$this->timerReload();
break;
}
}
}
}
/**
* @param $value
* @param $isReload
* @return bool
*/
private function checkFile($value, $isReload): bool
{
$md5 = md5($value);
$mTime = filectime($value);
if (!isset($this->md5Map[$md5])) {
if ($isReload) {
return true;
}
$this->md5Map[$md5] = $mTime;
} else {
if ($this->md5Map[$md5] != $mTime) {
if ($isReload) {
return true;
}
$this->md5Map[$md5] = $mTime;
}
}
return false;
}
/**
* @throws Exception
*/
public function timerReload()
{
$this->process->isReloading = true;
$this->process->trigger_reload();
$this->process->int = -1;
$this->loadDirs();
$this->process->isReloading = FALSE;
$this->process->isReloadingOut = FALSE;
$this->tick();
}
/**
* @throws Exception
*/
public function tick()
{
if ($this->process->isReloading) {
return;
}
$this->loadDirs(true);
Timer::after(2000, [$this, 'tick']);
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Kiri;
interface IAspect
{
public function before(): void;
/**
* @param mixed $response
*/
public function after(mixed $response): void;
/**
* @param mixed $handler
* @param array $params
* @return mixed
*/
public function invoke(mixed $handler, array $params = []): mixed;
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace Kiri;
interface IProxy
{
public function execute();
}
+57
View File
@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Kiri\Jwt;
use Annotation\Inject;
use Exception;
use Http\Message\ServerRequest;
use Kiri\Kiri;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Server\Constrict\ResponseInterface;
/**
* Class CoreMiddleware
* @package Kiri\Kiri\Route
* 跨域中间件
*/
class JWTAuthMiddleware implements MiddlewareInterface
{
/** @var int */
public int $zOrder = 0;
#[Inject(ResponseInterface::class)]
public ResponseInterface $response;
/**
* @param ServerRequest $request
* @param RequestHandlerInterface $handler
* @return \Psr\Http\Message\ResponseInterface
* @throws Exception
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): \Psr\Http\Message\ResponseInterface
{
$authorization = $request->getHeaderLine('Authorization');
if (empty($authorization)) {
return $this->response->json(['code' => 401, 'JWT voucher cannot be empty.']);
}
if (!str_starts_with($authorization, 'Bearer ')) {
return $this->response->json(['code' => 401, 'JWT Voucher Format Error.']);
}
$authorization = str_replace('Bearer ', '', $authorization);
$jwt = Kiri::app()->getJwt();
if (!$jwt->validator($authorization)) {
return $this->response->json(['code' => 401, 'JWT Validator fail.']);
}
return $handler->handle($request);
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Kiri\Jwt;
use JetBrains\PhpStorm\Pure;
use Throwable;
/**
*
*/
class JWTAuthTokenException extends \Exception
{
/**
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
#[Pure] public function __construct($message = "", $code = 4001, Throwable $previous = null)
{
parent::__construct($message, 4001, $previous);
}
}
+201
View File
@@ -0,0 +1,201 @@
<?php
declare(strict_types=1);
namespace Kiri\Jwt;
use Annotation\Inject;
use Exception;
use Kiri\Abstracts\Component;
use Kiri\Abstracts\Config;
use Kiri\Core\Json;
use Kiri\Exception\ConfigException;
use Server\Constrict\Request;
/**
* Class Jwt
* @package Kiri\Jwt
*/
class Jwt extends Component
{
use JwtHelper;
#[Inject(Request::class)]
private Request $request;
/**
* @param Request $request
*/
public function setRequest(Request $request): void
{
$this->request = $request;
}
/**
* @throws ConfigException
* @throws Exception
* 'jwt' => [
* 'scene' => 'application',
* 'timeout' => 7200,
* 'encrypt' => '',
* 'iv' => '',
* 'key' => '',
* ]
*/
public function init()
{
$this->request = di(Request::class);
$this->public = Config::get('ssl.public', $this->public);
$this->private = Config::get('ssl.private', $this->private);
$this->timeout = Config::get('jwt.timeout', 7200);
$jwt = Config::get('jwt', []);
if ($jwt) {
$this->setScene($jwt['scene'] ?? 'application');
$this->setKey($jwt['key'] ?? get_called_class());
$length = openssl_cipher_iv_length($this->encrypt);
if ($length > 0) {
$defaultIv = openssl_random_pseudo_bytes($length);
$this->setIv($jwt['iv'] ?? $defaultIv);
}
}
}
/**
* @param int $unionId
*
* @return string
* @throws Exception
*/
public function create(int $unionId): string
{
$this->user = $unionId;
$this->config['time'] = time();
$this->data = $this->request->getHeaders();
if (!isset($this->data['source'])) {
$this->data['source'] = 'browser';
}
return $this->createEncrypt($unionId);
}
/**
* @return string
*/
private function jwtHeader(): string
{
$string = openssl_encrypt(
json_encode(['type' => 'openssl', 'encrypt' => $this->encrypt]),
$this->encrypt,
$this->key,
0,
$this->iv
);
return str_replace('=', '', $string);
}
/**
* @param $unionId
* @return string
* @throws Exception
*/
private function jwtBody($unionId): string
{
$json = json_encode(['unionId' => $unionId, 'createTime' => time(), 'expire_at' => time() + $this->timeout]);
openssl_private_encrypt($json, $encode, $this->private);
return base64_encode($encode);
}
/**
* @param $unionId
* @return string
* @throws Exception
*/
private function createEncrypt($unionId): string
{
$params[] = $this->jwtHeader();
$params[] = $this->jwtBody($unionId);
$params[] = hash('sha256', $params[0] . $params[1]);
return implode('.', $params);
}
/**
* @param $token
* @return string|int
* @throws JWTAuthTokenException
*/
public function getUnionId($token): string|int
{
$unpack = $this->unpack($token);
if (!$this->_validator($unpack)) {
throw new JWTAuthTokenException('JWT certificate has expired.');
}
return $unpack['unionId'];
}
/**
* @param $token
* @return bool
* @throws JWTAuthTokenException
*/
public function validator($token): bool
{
return $this->_validator($this->unpack($token));
}
/**
* @param $unpack
* @return bool
*/
private function _validator($unpack): bool
{
if ($unpack['expire_at'] < time()) {
return false;
}
return true;
}
/**
* @param $token
* @return string
* @throws JWTAuthTokenException
* @throws Exception
*/
public function refresh($token): string
{
return $this->create($this->unpack($token)['unionId']);
}
/**
* @param string $token
* @return mixed
* @throws JWTAuthTokenException
*/
private function unpack(string $token): array
{
if (count($explode = explode('.', $token)) != 3) {
throw new JWTAuthTokenException('JWT Voucher Format Error.');
}
if (hash('sha256', $explode[0] . $explode[1]) != $explode[2]) {
throw new JWTAuthTokenException('JWT Sign Validator Fail.');
}
if (!openssl_public_decrypt(base64_decode($explode[1]), $decode, $this->public)) {
throw new JWTAuthTokenException('JWT Voucher Unpack Error.');
}
return Json::decode($decode, true);
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
namespace Kiri\Jwt;
trait JwtHelper
{
/** @var int $user */
private int $user;
private array $data;
private array $source = ['browser', 'android', 'iphone', 'pc', 'mingame'];
private array $config = ['token' => ''];
private ?int $timeout = 7200;
private string $key = 'www.xshucai.com';
private string $secret = '';
private string $scene = 'default';
private string $encrypt = 'aes-128-ecb';
private string $iv = '';
private ?string $public = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6BuML3gtLGde7QKNuNST
UCB9gdHC7XIpOc7Wx2I64Esj3UxWHTgp3URj0ge8zpy7A3FfBdppR7d1nwoD6Xad
jqfjEWpTy4WwGYsOfH0tFl3wAmse0lebF4NFsS9pzrikQT6c9qsVm88pCjvg4i5t
WhTMEnpTFDYoDR0KXlLXltQMudBBUHFaVwP0wKJ/cGX7R1Mrv35K4MXwQFOuGZkP
hsp2rO9x5LjtSKIXbexy7WhUu6QMjD/XzgsXr9UF+ExYmBGXRVWgNFLMkiaCZ2Uz
WlQhpQrA5/wKd76dCzjvqw9M32OiZl2lCKT73cV8GUvt7BNsM1SiPhqfY7nhO6y3
cwIDAQAB
-----END PUBLIC KEY-----';
private ?string $private = '-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA6BuML3gtLGde7QKNuNSTUCB9gdHC7XIpOc7Wx2I64Esj3UxW
HTgp3URj0ge8zpy7A3FfBdppR7d1nwoD6XadjqfjEWpTy4WwGYsOfH0tFl3wAmse
0lebF4NFsS9pzrikQT6c9qsVm88pCjvg4i5tWhTMEnpTFDYoDR0KXlLXltQMudBB
UHFaVwP0wKJ/cGX7R1Mrv35K4MXwQFOuGZkPhsp2rO9x5LjtSKIXbexy7WhUu6QM
jD/XzgsXr9UF+ExYmBGXRVWgNFLMkiaCZ2UzWlQhpQrA5/wKd76dCzjvqw9M32Oi
Zl2lCKT73cV8GUvt7BNsM1SiPhqfY7nhO6y3cwIDAQABAoIBADPihJHP8XktmmCs
43Vfv5Z3zNaKR2LA1Eph3E0xviuJYHkFqXJarbESqqW2qRQeoQeB/lXWnxYzAo4M
tRcpNss+6FlqRVUHi3gKR7C4Yq3PTemcfIVUpAy7gYa8LJDTYZRcJMZXNDtiMbBh
9kFZU4SBhaTTx2KLQKS9yyWOqzbBvyLXN+1+Wy477M9+MXXTKw79dO+pML6cR0yl
pNfVR5FX5L/GB5vOtQB/Aqg/CKT8NC5MzWPnKY+TPCCHZyoZuB9dLDuWOlqsN4QX
Y4B8fFca5yRwzHra5aGoqdaT/zGctt+I6V/f/KNQCo36f9LPxeXg1+FHvvtTj5WZ
N8CGPzECgYEA9R7lRMXzrHE4rK0DhxQXIFbIKKtxrimqZQdbwOUeYYD2R6CDSItK
z88RSYElmd6wiS7fYIaheXNqJ8Yu6SQFBF/yshBwjQVl9NJG94LJlgx1XnVZEju6
OZjMUOhHXBymtXnLo16pDRl8odc4MFLRH25/vLtwChUr+Qoyt54GzFUCgYEA8mjL
jdh94JAmcdnDXsKgjNOGyNWGDVvWoFmy8lEQsMXY1JJnEd3YfDM2prmv3vaoiXzi
YkSETl6ZUtJqh78MnHCBY1vI6EAcKQAF/kvP2TataRCXNcGNQwn2mtq+B+heTta6
Di8jjAdmdUAYHbmOQryBudiRYG7JEF038elzvKcCgYEAq81ByFguGBkrLev94vkz
1Fi+5bJ0dSuC4Fit+J8eEhz/gOiB26C1iL2LUkeQgS5R8XTG37K9DpDUQJhpXMMA
OTa+tgtLt6um8FdJokUq4V5ODSyWh28RcTklSzfifC8gsWVyU0kPl7zbW9uq6EPD
ixI5uaBuQMLiFSUOsx+xiBkCgYEAtqXHWeVZUy7KCNavomK7XeCzmfdovgAIw2FS
t8nk7YzlR6XYC1pAl7Ru5Ujb/v+TFaUHXkuJ9RLKK+Fna0jEU8thcl/iDTzg+vON
kIHG5j+Qga2CgXqI2Y5URXGz5XlsNbMNFUrnWcbpqEbW5O6/BgHLLSDEyQgwbygN
0zS3g9kCgYEAhssb7kOljdIul4lY5MXc67Zf1dp6S2bucLOxsG6cRW07b3pBz7QF
5aPE7ZwnkzTnA4HuGGauKj+qKGAR7ve55XClAq/XipiVFrjwV/t3LC6j5DoqTJYR
mlAZUEjsoaT9vjvjGTxl3uCm0TX5KTgtSJIt2kA1tYVjQef+/iZTHxY=
-----END RSA PRIVATE KEY-----';
/**
* @param string $publicKey
*/
public function setPublic(string $publicKey)
{
$this->public = $publicKey;
}
/**
* @param int $timeout
*/
public function setTimeout(int $timeout)
{
$this->timeout = $timeout;
}
public function setScene(string $scene)
{
$this->scene = $scene;
}
/**
* @param string $timeout
*/
public function setKey(string $timeout)
{
$this->key = $timeout;
}
/**
* @param string $privateKey
*/
public function setPrivate(string $privateKey)
{
$this->private = $privateKey;
}
/**
* @param string $privateKey
*/
public function setIv(string $privateKey)
{
$this->iv = $privateKey;
}
/**
* @param string $privateKey
*/
public function setEncrypt(string $privateKey)
{
$this->encrypt = $privateKey;
}
}
+617
View File
@@ -0,0 +1,617 @@
<?php
declare(strict_types=1);
namespace Kiri;
use Annotation\Annotation;
use Database\ActiveRecord;
use Database\Collection;
use Exception;
use JetBrains\PhpStorm\Pure;
use Kiri\Abstracts\Config;
use Kiri\Core\Json;
use Kiri\Di\Container;
use Kiri\Di\ContainerInterface;
use Kiri\Exception\NotFindClassException;
use ReflectionException;
use ReflectionProperty;
use Server\ServerManager;
use Swoole\Coroutine;
use Swoole\Process;
use Swoole\WebSocket\Server;
defined('DB_ERROR_BUSY') or define('DB_ERROR_BUSY', 'The database is busy. Please try again later.');
defined('SELECT_IS_NULL') or define('SELECT_IS_NULL', 'Query data does not exist, please check the relevant conditions.');
defined('PARAMS_IS_NULL') or define('PARAMS_IS_NULL', 'Required items cannot be empty, please add.');
defined('CONTROLLER_PATH') or define('CONTROLLER_PATH', APP_PATH . 'app/Controllers/');
defined('CRONTAB_PATH') or define('CRONTAB_PATH', APP_PATH . 'app/Crontab/');
defined('CLIENT_PATH') or define('CLIENT_PATH', APP_PATH . 'app/Client/');
defined('TASK_PATH') or define('TASK_PATH', APP_PATH . 'app/Async/');
defined('LISTENER_PATH') or define('LISTENER_PATH', APP_PATH . 'app/Listener/');
defined('KAFKA_PATH') or define('KAFKA_PATH', APP_PATH . 'app/Kafka/');
defined('RPC_CLIENT_PATH') or define('RPC_CLIENT_PATH', APP_PATH . 'app/Client/Rpc/');
defined('MODEL_PATH') or define('MODEL_PATH', APP_PATH . 'app/Models/');
/**
* Class Kiri
* @package Kiri
*/
class Kiri
{
/** @var Container */
public static Container $container;
/** @var ?Application */
private static ?Application $service = null;
/**
* @param $service
*
* 初始化服务
*/
public static function init($service)
{
static::$service = $service;
}
/**
* @param $alias
* @param array $array
* @throws Exception
*/
public static function set($alias, array $array = [])
{
static::app()->set($alias, $array);
}
/**
* @param string $name
* @return mixed
* @throws Exception
*/
public static function getApp(string $name): mixed
{
return static::app()->get($name);
}
/**
* @return Application|null
*/
public static function app(): ?Application
{
return static::$service;
}
/**
* @return Application|null
*/
public static function getFactory(): ?Application
{
return static::$service;
}
/**
* @return Application|null
*/
public static function getApplicationContext(): ?Application
{
return static::$service;
}
/**
* @return Container|null
*/
public static function getContainerContext(): ?Container
{
return static::$container;
}
/**
* @param $name
* @return bool
*/
public static function has($name): bool
{
return static::$service->has($name);
}
/**
* @param $port
* @return bool
* @throws Exception
*/
public static function port_already($port): bool
{
if (empty($port)) {
return false;
}
if (Kiri::getPlatform()->isLinux()) {
exec('netstat -tunlp | grep ' . $port, $output);
} else {
exec('lsof -i :' . $port . ' | grep -i "LISTEN"', $output);
}
return !empty($output);
}
/**
* @return Annotation
* @throws Exception
*/
public static function getAnnotation(): Annotation
{
return static::app()->getAnnotation();
}
/**
* @param $service
* @return string
*/
#[Pure] public static function listen($service): string
{
return sprintf('Check listen %s::%d -> ok', $service['host'], $service['port']);
}
/**
* @param $className
* @param array $construct
* @return mixed
* @throws Exception
*/
public static function createObject($className, array $construct = []): mixed
{
if (is_string($className) && class_exists($className)) {
return static::$container->get($className, $construct);
} else if (is_array($className) && isset($className['class'])) {
$class = $className['class'];
unset($className['class']);
return static::$container->newObject($class, $construct, $className);
} else if (is_callable($className, TRUE)) {
return call_user_func($className, $construct);
} else {
throw new Exception('Unsupported configuration type: ' . gettype($className));
}
}
/**
* @return string
* @throws Exception
*/
public static function getStoragePath(): string
{
$default = APP_PATH . 'storage' . DIRECTORY_SEPARATOR;
$path = Config::get('storage', $default);
if (!is_dir($path)) {
mkdir($path, 0777, true);
}
return $path;
}
/**
* @return bool
*/
public static function inCoroutine(): bool
{
return Coroutine::getCid() > 0;
}
/**
* @return Container
*/
public static function getDi(): Container
{
return static::$container;
}
/**
* @param $workerId
* @return mixed
* @throws Exception
*/
public static function setManagerId($workerId): mixed
{
if (empty($workerId) || static::isDocker()) {
return $workerId;
}
$tmpFile = storage($workerId . '.sock', 'pid/manager');
return self::writeFile($tmpFile, $workerId);
}
/**
* @param $workerId
* @return mixed
* @throws Exception
*/
public static function setProcessId($workerId): mixed
{
if (empty($workerId) || static::isDocker()) {
return $workerId;
}
$tmpFile = storage($workerId . '.sock', 'pid/process');
return self::writeFile($tmpFile, $workerId);
}
/**
* @return bool
*/
public static function isDocker(): bool
{
$output = shell_exec('[ -f /.dockerenv ] && echo yes || echo no');
if (trim($output) === 'yes') {
return true;
}
return false;
}
/**
* @param $workerId
* @return mixed
* @throws Exception
*/
public static function setWorkerId($workerId): mixed
{
if (empty($workerId) || static::isDocker()) {
return $workerId;
}
$tmpFile = storage($workerId . '.sock', 'pid/worker');
return self::writeFile($tmpFile, $workerId);
}
/**
* @param $workerId
* @return mixed
* @throws Exception
*/
public static function setTaskId($workerId): mixed
{
if (empty($workerId) || static::isDocker()) {
return $workerId;
}
$tmpFile = storage($workerId . '.sock', 'pid/task');
return self::writeFile($tmpFile, $workerId);
}
/**
* @param $fileName
* @param $content
* @param null $is_append
* @return mixed
*/
public static function writeFile($fileName, $content, $is_append = null): mixed
{
$params = [$fileName, (string)$content];
if ($is_append !== null) {
$params[] = $is_append;
}
return !self::inCoroutine() ? file_put_contents(...$params) : Coroutine::writeFile(...$params);
}
/**
* @param $object
* @param $config
* @return mixed
*/
public static function configure($object, $config): mixed
{
foreach ($config as $key => $value) {
if (!property_exists($object, $key)) {
continue;
}
$object->$key = $value;
}
return $object;
}
/**
* @param $workerId
* @param bool $isWorker
* @throws Exception
*/
public static function clearProcessId($workerId, bool $isWorker = false)
{
clearstatcache();
$directory = $isWorker === true ? 'pid/worker' : 'pid/task';
if (!file_exists($file = storage($workerId, $directory))) {
return;
}
shell_exec('rm -rf ' . $file);
}
/**
* @param string|null $taskPid
* @throws Exception
*/
public static function clearTaskPid(string $taskPid = null)
{
if (empty($taskPid)) {
exec('rm -rf ' . storage(null, 'pid/task'));
} else {
static::clearProcessId($taskPid);
}
}
/**
* @param $taskPid
* @throws Exception
*/
public static function clearWorkerPid($taskPid = null)
{
if (empty($taskPid)) {
exec('rm -rf ' . storage(null, 'pid/worker'));
} else {
static::clearProcessId($taskPid, true);
}
}
/**
* @return Server|null
* @throws
*/
public static function getWebSocket(): ?\Swoole\Server
{
$server = static::app()->getSwoole();
if (!($server instanceof \Swoole\Server)) {
return null;
}
return $server;
}
/**
* @return false|string
* @throws Exception
*/
public static function getMasterPid(): bool|string
{
$pid = Kiri::app()->getSwoole()->setting['pid_file'];
return file_get_contents($pid);
}
/**
* @param int $fd
* @param $data
* @return mixed
* @throws Exception
*/
public static function push(int $fd, $data): mixed
{
$server = static::getWebSocket();
if (empty($server) || !$server->isEstablished($fd)) {
return false;
}
if (!is_string($data)) {
$data = Json::encode($data);
}
return $server->push($fd, $data);
}
/**
* @return mixed
*/
public static function localhost(): mixed
{
return current(swoole_get_local_ip());
}
/**
* @param string $class
* @param array $params
* @throws ReflectionException
* @throws Exception
*/
public static function async(string $class, array $params = [])
{
$manager = di(ServerManager::class);
$manager->task(new $class(...$params));
}
/**
* @param array $v1
* @param array $v2
* @return float
*/
#[Pure] public static function distance(array $v1, array $v2): float
{
$maxX = max($v1['x'], $v2['x']);
$minX = min($v1['x'], $v2['x']);
$maxZ = max($v1['z'], $v2['z']);
$minZ = min($v1['z'], $v2['z']);
$dx = abs($maxX - $minX);
$dy = abs($maxZ - $minZ);
$sqrt = sqrt($dx * $dx + $dy * $dy);
if ($sqrt < 0) {
$sqrt = abs($sqrt);
}
return (float)$sqrt;
}
/**
* @param $process
* @throws Exception
*/
public static function shutdown($process): void
{
static::app()->getSwoole()->shutdown();
if ($process instanceof Process) {
$process->exit(0);
}
}
/**
* @param $tmp_name
* @return string
*/
public static function rename($tmp_name): string
{
$hash = md5_file($tmp_name);
$later = '.' . exif_imagetype($tmp_name);
$match = '/(\w{12})(\w{5})(\w{9})(\w{6})/';
$tmp = preg_replace($match, '$1-$2-$3-$4', $hash);
return strtoupper($tmp) . $later;
}
/**
* @return Environmental
* @throws
*/
public static function getPlatform(): Environmental
{
return Kiri::createObject(Environmental::class);
}
/**
* @return mixed
* @throws Exception
*/
public static function reload(): mixed
{
return Kiri::app()->getSwoole()->reload();
}
private static array $_autoload = [];
const PROCESS = 'process';
const TASK = 'task';
const WORKER = 'worker';
/**
* @param string $event
* @param null $data
* @return false|string
* @throws Exception
*/
public static function param(string $event, $data = NULL): bool|string
{
if (is_object($data)) {
if ($data instanceof ActiveRecord || $data instanceof Collection) {
$data = $data->getAttributes();
} else {
$data = get_object_vars($data);
}
}
if (!is_array($data)) $data = ['data' => $data];
return json_encode(array_merge(['callback' => $event], $data));
}
/**
* @return string|null
*/
#[Pure] public static function getEnvironmental(): ?string
{
return env('environmental');
}
/**
* @return bool
*/
#[Pure] public static function isTask(): bool
{
return static::getEnvironmental() == static::TASK;
}
/**
* @return bool
*/
#[Pure] public static function isWorker(): bool
{
return static::getEnvironmental() == static::WORKER;
}
/**
* @return bool
*/
#[Pure] public static function isProcess(): bool
{
return static::getEnvironmental() == static::PROCESS;
}
/**
* @param $class
* @param $file
*/
public static function setAutoload($class, $file)
{
if (isset(static::$_autoload[$class])) {
return;
}
static::$_autoload[$class] = $file;
include_once "Kiri.php";
}
/**
* @param $className
*/
public static function autoload($className)
{
if (!isset(static::$_autoload[$className])) {
return;
}
$file = static::$_autoload[$className];
require_once "Kiri.php";
}
}
//spl_autoload_register([Kiri::class, 'autoload'], true, true);
Kiri::$container = new Container();
Kiri::$container->setBindings(ContainerInterface::class, Kiri::$container);
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace Kiri\Pool;
use JetBrains\PhpStorm\Pure;
trait Alias
{
/**
* @param $cds
* @param false $isMaster
* @return string
*/
#[Pure] public function name($cds, bool $isMaster = false): string
{
if ($isMaster === true) {
return $cds . '_master';
} else {
return $cds . '_slave';
}
}
}
+215
View File
@@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
namespace Kiri\Pool;
use Closure;
use Database\Mysql\PDO;
use Exception;
use Http\Handler\Context;
use Kiri\Abstracts\Component;
use Kiri\Kiri;
use Swoole\Error;
use Throwable;
/**
* Class Connection
* @package Kiri\Pool
*/
class Connection extends Component
{
use Alias;
/**
* @param $cds
* @return bool
*
* db is in transaction
* @throws Exception
*/
public function inTransaction($cds): bool
{
$name = $this->name('Mysql:' . $cds, true);
$connection = Context::getContext($name);
if ($connection instanceof PDO) {
return $connection->inTransaction();
}
return false;
}
/**
* @param $coroutineName
* @throws Exception
*/
public function beginTransaction($coroutineName)
{
$coroutineName = $this->name('Mysql:' . $coroutineName, true);
$connection = Context::getContext($coroutineName);
if ($connection instanceof PDO) {
$connection->beginTransaction();
}
}
/**
* @param $coroutineName
* @throws Exception
*/
public function commit($coroutineName)
{
$coroutineName = $this->name('Mysql:' . $coroutineName, true);
$connection = Context::getContext($coroutineName);
if ($connection instanceof PDO) {
$connection->commit();
}
}
/**
* @param $coroutineName
* @throws Exception
*/
public function rollback($coroutineName)
{
$coroutineName = $this->name('Mysql:' . $coroutineName, true);
$connection = Context::getContext($coroutineName);
if ($connection instanceof PDO) {
$connection->rollBack();
}
}
/**
* @param mixed $config
* @param bool $isMaster
* @return PDO|null
* @throws Exception
*/
public function get(mixed $config, bool $isMaster = false): ?PDO
{
$coroutineName = $this->name('Mysql:' . $config['cds'], $isMaster);
if (($pdo = Context::getContext($coroutineName)) instanceof PDO) {
return $pdo;
}
/** @var PDO $connections */
$connections = $this->getPool()->get($coroutineName, $this->create($coroutineName, $config));
if (Context::hasContext('begin_' . $coroutineName)) {
$connections->beginTransaction();
}
return Context::setContext($coroutineName, $connections);
}
/**
* @param $coroutineName
* @param $config
* @return Closure
*/
public function create($coroutineName, $config): Closure
{
return static function () use ($coroutineName, $config) {
return Kiri::getDi()->newObject(PDO::class, [
$config['database'], $config['cds'], $config['username'], $config['password'], $config['charset'] ?? 'utf8mb4'
]);
};
}
/**
* @param $name
* @param $isMaster
* @param $max
* @throws Exception
*/
public function initConnections($name, $isMaster, $max)
{
$this->getPool()->initConnections($name, $isMaster, $max);
}
/**
* @param $coroutineName
* @param $isMaster
* @throws Exception
*/
public function release($coroutineName, $isMaster)
{
$coroutineName = $this->name('Mysql:' . $coroutineName, $isMaster);
/** @var PDO $client */
if (!($client = Context::getContext($coroutineName)) instanceof PDO) {
return;
}
if ($client->inTransaction()) {
return;
}
$this->getPool()->push($coroutineName, $client);
Context::remove($coroutineName);
}
/**
* @param $coroutineName
* @return bool
*/
private function hasClient($coroutineName): bool
{
return Context::hasContext($coroutineName);
}
/**
* batch release
* @throws Exception
*/
public function connection_clear($name, $isMaster)
{
$this->getPool()->clean($this->name($name, $isMaster));
}
/**
* @param string $name
* @param mixed $client
* @return bool
* @throws Exception
*/
public function checkCanUse(string $name, mixed $client): bool
{
try {
if (empty($client) || !($client instanceof PDO)) {
$result = false;
} else {
$result = true;
}
} catch (Error | Throwable $exception) {
$result = $this->addError($exception, 'mysql');
} finally {
return $result;
}
}
/**
* @param $coroutineName
* @param bool $isMaster
* @throws Exception
*/
public function disconnect($coroutineName, bool $isMaster = false)
{
Context::remove($coroutineName);
$coroutineName = $this->name('Mysql:' . $coroutineName, $isMaster);
$this->getPool()->clean($coroutineName);
}
/**
* @return Pool
* @throws Exception
*/
public function getPool(): Pool
{
return Kiri::getDi()->get(Pool::class);
}
}
@@ -0,0 +1,28 @@
<?php
namespace Kiri\Pool\Helper;
interface QueueInterface
{
public function isEmpty(): bool;
public function push(mixed $data, float $timeout = -1): bool;
public function pop(float $timeout = -1): mixed;
public function stats(): array;
public function close(): bool;
public function length(): int;
public function isFull(): bool;
}
+101
View File
@@ -0,0 +1,101 @@
<?php
namespace Kiri\Pool\Helper;
use JetBrains\PhpStorm\Pure;
/**
*
*/
class SplQueue implements QueueInterface
{
private \SplQueue $channel;
public int $errCode = 0;
/**
* @param int $max
*/
#[Pure] public function __construct(public int $max)
{
$this->channel = new \SplQueue();
}
/**
* @return bool
*/
public function isEmpty(): bool
{
// TODO: Implement isEmpty() method.
return $this->channel->count() < 1;
}
/**
* @param mixed $data
* @param float $timeout
* @return bool
*/
public function push(mixed $data, float $timeout = -1): bool
{
// TODO: Implement push() method.
$this->channel->enqueue($data);
return true;
}
/**
* @param float $timeout
* @return mixed
*/
public function pop(float $timeout = -1): mixed
{
// TODO: Implement pop() method.
return $this->channel->dequeue();
}
/**
* @return array
*/
public function stats(): array
{
// TODO: Implement stats() method.
return [];
}
/**
* @return bool
*/
public function close(): bool
{
// TODO: Implement close() method.
return false;
}
/**
* @return int
*/
public function length(): int
{
// TODO: Implement length() method.
return $this->channel->count();
}
/**
* @return bool
*/
public function isFull(): bool
{
// TODO: Implement isFull() method.
return $this->channel->count() >= $this->max;
}
}
+234
View File
@@ -0,0 +1,234 @@
<?php
namespace Kiri\Pool;
use Exception;
use Http\Handler\Context;
use Kiri\Abstracts\Component;
use Kiri\Abstracts\Config;
use Kiri\Exception\ConfigException;
use Kiri\Pool\Helper\SplQueue;
use Swoole\Coroutine;
use Swoole\Coroutine\Channel;
/**
* Class Pool
* @package Kiri\Pool
*/
class Pool extends Component
{
/** @var Channel[] */
private static array $_connections = [];
public int $max = 60;
use Alias;
/**
* @param $channel
* @param $retain_number
* @throws Exception
*/
public function flush($channel, $retain_number)
{
$this->pop($channel, $retain_number);
}
/**
* @param Channel $channel
* @param $retain_number
* @throws Exception
*/
protected function pop(Channel $channel, $retain_number): void
{
while ($channel->length() > $retain_number) {
if (Context::inCoroutine()) {
$connection = $channel->pop();
if ($connection instanceof StopHeartbeatCheck) {
$connection->stopHeartbeatCheck();
}
}
}
}
/**
* @param $name
* @param false $isMaster
* @param int $max
* @throws ConfigException
*/
public function initConnections($name, bool $isMaster = false, int $max = 60)
{
$name = $this->name($name, $isMaster);
if (isset(static::$_connections[$name])) {
$value = static::$_connections[$name];
if ($value instanceof Channel || $value instanceof SplQueue) {
return;
}
}
$this->newChannel($name, $max);
$this->max = $max;
}
/**
* @param $name
* @return Channel|SplQueue
* @throws ConfigException
* @throws Exception
*/
private function getChannel($name): Channel|SplQueue
{
if (!isset(static::$_connections[$name])) {
$this->newChannel($name);
}
if (static::$_connections[$name]->errCode == SWOOLE_CHANNEL_CLOSED) {
throw new Exception('Channel is Close.');
}
return static::$_connections[$name];
}
/**
* @throws ConfigException
*/
private function newChannel($name, $max = null)
{
if ($max == null) {
$max = Config::get('databases.pool.max', 10);
}
if (Coroutine::getCid() === -1) {
static::$_connections[$name] = new SplQueue($max);
} else {
static::$_connections[$name] = new Channel($max);
}
}
/**
* @param $name
* @param $callback
* @return array
* @throws ConfigException
*/
public function get($name, $callback): mixed
{
$channel = $this->getChannel($name);
if (!$channel->isEmpty()) {
return $this->maxIdleQuantity($channel);
}
return $callback();
}
/**
* @param $channel
* @return mixed
* @throws ConfigException
*/
private function maxIdleQuantity($channel): mixed
{
$connection = $channel->pop();
$minx = Config::get('databases.pool.min', 1);
if ($channel->length() > $minx) {
$this->pop($channel, $minx);
}
return $connection;
}
/**
* @param $name
* @return bool
* @throws ConfigException
*/
public function isNull($name): bool
{
return $this->getChannel($name)->isEmpty();
}
/**
* @param string $name
* @param mixed $client
* @return bool
* 检查连接可靠性
*/
public function checkCanUse(string $name, mixed $client): bool
{
return true;
}
/**
* @param string $name
* @return bool
*/
public function hasItem(string $name): bool
{
if (isset(static::$_connections[$name])) {
return !static::$_connections[$name]->isEmpty();
}
return false;
}
/**
* @param string $name
* @return mixed
*/
public function size(string $name): mixed
{
if (!isset(static::$_connections[$name])) {
return 0;
}
return static::$_connections[$name]->length();
}
/**
* @param string $name
* @param mixed $client
* @throws ConfigException
*/
public function push(string $name, mixed $client)
{
$channel = $this->getChannel($name);
if (!$channel->isFull()) {
$channel->push($client);
}
unset($client);
}
/**
* @param string $name
* @throws Exception
*/
public function clean(string $name)
{
if (!isset(static::$_connections[$name])) {
return;
}
static::$_connections[$name] = null;
unset(static::$_connections[$name]);
}
/**
* @return Channel[]
*/
protected function getChannels(): array
{
return static::$_connections;
}
}
+126
View File
@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace Kiri\Pool;
use Annotation\Inject;
use Closure;
use Exception;
use Http\Handler\Context;
use Kiri\Abstracts\Component;
use Kiri\Events\EventProvider;
use Kiri\Exception\ConfigException;
use Kiri\Kiri;
use Server\Events\OnWorkerExit;
/**
* Class RedisClient
* @package Kiri\Kiri\Pool
*/
class Redis extends Component
{
use Alias;
/**
* @param mixed $config
* @param bool $isMaster
* @return mixed
* @throws Exception
*/
public function get(mixed $config, bool $isMaster = false): mixed
{
$coroutineName = $this->name('Redis:' . $config['host'], $isMaster);
if (Context::hasContext($coroutineName)) {
return Context::getContext($coroutineName);
}
$clients = $this->getPool()->get($coroutineName, $this->create($coroutineName, $config));
return Context::setContext($coroutineName, $clients);
}
/**
* @param string $name
* @param mixed $config
* @return Closure
*/
public function create(string $name, mixed $config): Closure
{
return static function () use ($name, $config) {
return Kiri::getDi()->newObject(\Kiri\Cache\Base\Redis::class, [
$config['host'], (int)$config['port'], $config['databases'] ?? 0,
$config['auth'], $config['prefix'] ?? '', $config['timeout'] ?? 30,
$config['read_timeout'] ?? 30
]);
};
}
/**
* @param array $config
* @param bool $isMaster
* @throws ConfigException
* @throws Exception
*/
public function release(array $config, bool $isMaster = false)
{
$coroutineName = $this->name('Redis:' . $config['host'], $isMaster);
if (!Context::hasContext($coroutineName)) {
return;
}
$this->getPool()->push($coroutineName, Context::getContext($coroutineName));
Context::remove($coroutineName);
}
/**
* @param array $config
* @param bool $isMaster
* @throws Exception
*/
public function destroy(array $config, bool $isMaster = false)
{
$coroutineName = $this->name('Redis:' . $config['host'], $isMaster);
$this->getPool()->clean($coroutineName);
Context::remove($coroutineName);
}
/**
* @param array $config
* @param bool $isMaster
* @throws Exception
*/
public function connection_clear(array $config, bool $isMaster = false)
{
$coroutineName = $this->name('Redis:' . $config['host'], $isMaster);
$this->getPool()->clean($coroutineName);
}
/**
* @return Pool
* @throws Exception
*/
public function getPool(): Pool
{
return Kiri::getDi()->get(Pool::class);
}
/**
* @param $name
* @param $isMaster
* @param $max
* @throws Exception
*/
public function initConnections($name, $isMaster, $max)
{
$this->getPool()->initConnections($name, $isMaster, $max);
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Kiri\Pool;
interface StopHeartbeatCheck
{
public function stopHeartbeatCheck();
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Kiri;
class Proxy
{
/**
* Proxy constructor.
* @param IProxy $IProxy
*/
public function __construct(public IProxy $IProxy)
{
}
/**
* @return mixed
*/
public function execute(): mixed
{
return $this->IProxy->execute();
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace Kiri\Proxy;
abstract class AProxy
{
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace Kiri\Proxy;
use Annotation\Aspect;
use Http\Handler\Handler;
use Kiri\Di\NoteManager;
use Kiri\IAspect;
use Kiri\Kiri;
/**
*
*/
class AspectProxy extends AProxy implements ProxyInterface
{
/**
* @param \Http\Handler\Handler $executor
* @return mixed
*/
public function proxy(Handler $executor): mixed
{
if ($executor->callback instanceof \Closure) {
return call_user_func($executor->callback, ...$executor->params);
}
$controller = Kiri::getDi()->get($executor->callback[0]);
$aspect = $this->getAspect($executor->callback);
if (!is_null($aspect)) {
$aspect->before();
$result = $aspect->invoke([$controller, $executor->callback[1]], $executor->params);
$aspect->after($result);
} else {
$result = call_user_func([$controller, $executor->callback[1]]);
}
return $result;
}
/**
* @param array $executor
* @return ?IAspect
*/
protected function getAspect(array $executor): ?IAspect
{
$aspect = NoteManager::getSpecify_annotation(Aspect::class, $executor[0], $executor[1]);
if (!is_null($aspect)) {
$aspect = Kiri::getDi()->get($aspect->aspect);
}
return $aspect;
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace Kiri\Proxy;
use Http\Handler\Handler;
interface ProxyInterface
{
/**
* @param \Http\Handler\Handler $executor
* @return mixed
*/
public function proxy(Handler $executor): mixed;
}
+101
View File
@@ -0,0 +1,101 @@
<?php
namespace Kiri;
use Exception;
use Kiri\Abstracts\Input;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class Runtime
* @package Kiri
*/
class Runtime extends Command
{
public string $command = 'runtime:builder';
public string $description = 'create app file cache';
const CACHE_NAME = '.runtime.cache';
const CONFIG_NAME = '.config.cache';
protected function configure()
{
$this->setName('runtime:builder');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
* @throws Exception
*/
public function execute(InputInterface $input, OutputInterface $output): int
{
// TODO: Implement onHandler() method.
$annotation = Kiri::app()->getAnnotation();
$runtime = storage(static::CACHE_NAME);
$config = storage(static::CONFIG_NAME);
Kiri::writeFile($config, $this->configEach());
Kiri::writeFile($runtime, serialize($annotation->getLoader()));
return 1;
}
/**
* @return string
* @throws Exception
*/
public function configEach(): string
{
$array = [];
$configs = Kiri::app()->getConfig();
foreach ($configs->getData() as $key => $datum) {
if ($datum instanceof \Closure) {
continue;
}
if (is_array($datum)) {
$array[$key] = $this->arrayEach($datum);
} else {
$array[$key] = $datum;
}
}
return serialize($array);
}
/**
* @param array $value
* @return array
*/
private function arrayEach(array $value): array
{
$array = [];
foreach ($value as $key => $item) {
if ($item instanceof \Closure) {
continue;
}
if (is_array($item)) {
$array[$key] = $this->arrayEach($item);
} else {
$array[$key] = $item;
}
}
return $array;
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace Kiri;
interface ToArray
{
public function toArray();
}