From 5763db2b545765b0d087368d4588d77c06c6ab03 Mon Sep 17 00:00:00 2001 From: User Date: Mon, 29 May 2023 02:03:21 +0300 Subject: [PATCH] Init --- .gitignore | 3 + README.md | 4 + composer.json | 22 ++++ src/App.php | 207 +++++++++++++++++++++++++++++++ src/Globals.php | 261 ++++++++++++++++++++++++++++++++++++++++ src/Logger.php | 144 ++++++++++++++++++++++ src/Main.php | 97 +++++++++++++++ src/ResponseEmitter.php | 117 ++++++++++++++++++ src/Session.php | 39 ++++++ src/Utils.php | 140 +++++++++++++++++++++ 10 files changed, 1034 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 src/App.php create mode 100644 src/Globals.php create mode 100644 src/Logger.php create mode 100644 src/Main.php create mode 100644 src/ResponseEmitter.php create mode 100644 src/Session.php create mode 100644 src/Utils.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..31dc5a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +/vendor +composer.lock \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..977085a --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +### Rmphp/Kernel + +Kernel for **Rmphp** + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b1ecad0 --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "rmphp/kernel", + "license": "proprietary", + "authors": [ + { + "name": "Yuri Zuev", + "email": "y_zuev@mail.ru" + } + ], + "require": { + "php": "^8.1", + "psr/log": "^3.0.0", + "psr/container": "^1.0", + "rmphp/foundation": "1.0.x-dev" + }, + "autoload": { + "psr-4": { + "Rmphp\\Kernel\\": "src/" + } + } + +} diff --git a/src/App.php b/src/App.php new file mode 100644 index 0000000..559750e --- /dev/null +++ b/src/App.php @@ -0,0 +1,207 @@ +baseDir = dirname(__DIR__, 4); + } + + /** + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @return ResponseInterface + */ + public function handler(ServerRequestInterface $request, ResponseInterface $response) : ResponseInterface { + try{ + $this->init($request, $response); + $this->syslogger()->log("routes", "", $this->appRoutes); + + foreach ($this->appRoutes as $appRouteKey => $appHandler){ + + if(!$appHandler instanceof MatchObject) continue; + $response = null; + + if(!empty($appHandler->className)){ + if(!class_exists($appHandler->className)) { + $this->syslogger()->log("handlers", "Err - Class ".$appHandler->className." is not exists"); + continue; + } + $controllers[$appRouteKey] = new $appHandler->className; + $log = "OK - Class ".$appHandler->className; + + if(!empty($appHandler->methodName)){ + if(!method_exists($appHandler->className, $appHandler->methodName)) { + $this->syslogger()->log("handlers", "Err - Method ".$appHandler->className."/".$appHandler->methodName." is not exists"); + continue; + } + $response = (!empty($appHandler->params)) ? $controllers[$appRouteKey]->{$appHandler->methodName}(...$appHandler->params) : $controllers[$appRouteKey]->{$appHandler->methodName}(); + $log = "OK - Method ".$appHandler->className."/".$appHandler->methodName; + } + $this->syslogger()->log("handlers", $log); + /** + * 1. Если на этапе итерации уже получен ответ ResponseInterface - досрочно отдаем результат в эмиттер + */ + if($response instanceof ResponseInterface) { + return $response; + } + elseif($response === false) break; + } + } + /** + * 2. Если итерации закончились и задан обьект Content им создаем результат для эмиттера + */ + if($this->template() && !empty($this->template()->getResponse())){ + $body = $this->globals()->response()->getBody(); + $body->write($this->template()->getResponse()); + $body->rewind(); + return $this->globals()->response()->withBody($body); + } + /** + * 3. Отдаем пустой результат если не определен шаблонизатор + */ + return $this->defaultPage(404); + } + catch (AppException $appException){ + if($this->logger()) $this->logger()->warning($appException->getMessage()." on ".$appException->getFile().":".$appException->getLine()); + $this->syslogger()->warning("AppException: ".$appException->getMessage()); + } + catch (\Exception $exception) { + if($this->logger()) $this->logger()->warning($exception->getMessage()." on ".$exception->getFile().":".$exception->getLine()); + $this->syslogger()->warning("Exception: ".$exception->getMessage()." : ".$exception->getFile()." : ".$exception->getLine()); + } + catch (AppError $appError){ + if($this->logger()) $this->logger()->warning($appError->getMessage()." on ".$appError->getFile().":".$appError->getLine()); + $this->syslogger()->error("Error: ".$appError->getMessage()." : ".$appError->getFile()." : ".$appError->getLine()); + } + catch (\Error $error) { + if($this->logger()) $this->logger()->error($error->getMessage()." on ".$error->getFile().":".$error->getLine()); + $this->syslogger()->error("Error: ".$error->getMessage()." : ".$error->getFile()." : ".$error->getLine()); + } + /** + * 4. Отдаем ошибку без шаблона + */ + return $this->defaultPage(501); + } + + /** + * @param int $code + * @return ResponseInterface + */ + private function defaultPage(int $code) : ResponseInterface{ + if(is_file($this->baseDir.'/'.getenv("PAGE".$code))){ + $body = $this->globals()->response()->getBody(); + $body->write(file_get_contents($this->baseDir.'/'.getenv("PAGE".$code))); + $body->rewind(); + return $this->globals()->response()->withBody($body)->withStatus($code); + } + return $this->globals()->response()->withStatus($code); + } + + /** + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @return void + * @throws AppException + */ + private function init(ServerRequestInterface $request, ResponseInterface $response) : void { + + $this->setGlobals($request, $response); + + // init factories + if(is_file($this->baseDir."/".getenv("APP_COMPONENTS_FILE"))){ + $components = include_once $this->baseDir."/".getenv("APP_COMPONENTS_FILE"); + if(!empty($components) && is_array($components)){ + foreach ($components as $componentName => $componentValue){ + if(empty($componentValue)) { + continue; + } + elseif(is_object($componentValue)){ + $componentObject = $componentValue; + } + elseif(!file_exists($this->baseDir.'/'.$componentValue) || !is_object($componentObject = require $this->baseDir.'/'.$componentValue)){ + throw AppException::invalidObject($componentValue); + } + switch (true){ + case ($componentObject instanceof ContainerInterface): $this->setContainer($componentObject); break; + case ($componentObject instanceof TemplateInterface): $this->setTemplate($componentObject); break; + case ($componentObject instanceof LoggerInterface): $this->setLogger($componentObject); break; + case ($componentObject instanceof RouterInterface): $this->router = $componentObject; break; + } + } + } + } + + // app nodes + if(is_file($this->baseDir."/".getenv("APP_NODES_FILE"))){ + $nodes = include_once $this->baseDir."/".getenv("APP_NODES_FILE"); + } + for ($appNodeNum = 1; getenv("APP_NODE".$appNodeNum); $appNodeNum++){ + $nodesCollection[] = json_decode(getenv("APP_NODE".$appNodeNum), true); + } + if(isset($nodesCollection)) $nodes = $nodesCollection; + + if(empty($nodes) || !is_array($nodes)) throw AppException::emptyAppNodes(); + $this->getActions($nodes); + } + + /** + * @param array $appNodes + */ + private function getActions(array $appNodes) : void { + foreach ($appNodes as $appNode){ + + // по умолчанию точка монтирования ровна корню + $mountKey = (!empty($appNode['key'])) ? $appNode['key'] : '/'; + + // если url начинается не с точки монтирования смотрим далее + if (0 !== (strpos($this->globals()->request()->getUri()->getPath(), $mountKey))) continue; + + if(!empty($appNode['action'])){ + $className = $appNode['action']; + $methodName = $appNode['method']; + $params = (!empty($appNode['params']) && is_string($appNode['params'])) ? explode(",",str_replace(" ", "", $appNode['params'])) : []; + $this->appRoutes[] = new MatchObject($className, $methodName, $params); + } + elseif(!empty($appNode['router']) && file_exists($this->baseDir."/".$appNode['router'])){ + + if(empty($this->router)) throw AppError::invalidRequiredObject("Application config without router"); + $this->router->setStartPoint($mountKey); + + if(pathinfo($this->baseDir."/".$appNode['router'])['extension'] == "php") { + $this->router->withRules(include_once $this->baseDir."/".$appNode['router']); + } + elseif(pathinfo($this->baseDir."/".$appNode['router'])['extension'] == "json") { + $this->router->withRules(json_decode(file_get_contents($this->baseDir."/".$appNode['router']), true)); + } + elseif(pathinfo($this->baseDir."/".$appNode['router'])['extension'] == "yaml") { + $this->router->withRules(yaml_parse_file($this->baseDir."/".$appNode['router'])); + } + + $routes = $this->router->match($this->globals()->request()) ?? []; + foreach ($routes as $route){ + $this->appRoutes[] = $route; + } + } + } + } +} \ No newline at end of file diff --git a/src/Globals.php b/src/Globals.php new file mode 100644 index 0000000..9fd9ca8 --- /dev/null +++ b/src/Globals.php @@ -0,0 +1,261 @@ +request = $request; + $this->response = $response; + } + + + + /** + * @return ServerRequestInterface + */ + public function request() : ServerRequestInterface { + return $this->request; + } + + /** + * @return ResponseInterface + */ + public function response() : ResponseInterface { + return $this->response; + } + + /** + * @param ServerRequestInterface $request + * @return ServerRequestInterface + */ + public function setReqest(ServerRequestInterface $request) : ServerRequestInterface { + $this->request = $request; + return $this->request; + } + + /** + * @param ResponseInterface $response + * @return ResponseInterface + */ + public function setResponse(ResponseInterface $response) : ResponseInterface { + $this->response = $response; + return $this->response; + } + + + + /** + * @param string $name + * @return bool + */ + public function isGet(string $name = "") : bool { + return (!empty($name)) ? isset($this->request->getQueryParams()[$name]) : !empty($this->request->getQueryParams()); + } + + /** + * @param string $name + * @return bool + */ + public function isPost(string $name = "") : bool { + return (!empty($name)) ? isset($this->request->getParsedBody()[$name]) : !empty($this->request->getParsedBody()); + } + + /** + * @param string $name + * @return bool + */ + public function isCookie(string $name = "") : bool { + return (!empty($name)) ? isset($this->request->getCookieParams()[$name]) : !empty($this->request->getCookieParams()); + } + + /** + * @param string $name + * @return bool + */ + public function isSession(string $name = "") : bool { + if(!class_exists(Session::class)) return false; + if(!isset($this->session)) $this->session = new Session(); + return (!empty($name)) ? isset($this->session->getSession()[$name]) : !empty($this->session->getSession()); + } + + /** + * @param string $name + * @return bool + */ + public function isFile(string $name = "") : bool { + return (!empty($name)) ? isset($this->request->getUploadedFiles()[$name]) : !empty($this->request->getUploadedFiles()); + } + + /** + * @return bool + */ + public function isStream() : bool { + return !empty($this->request->getBody()->getContents()); + } + + + + /** + * @param string $name + * @param string $type + * @return array|int|string + */ + public function get(string $name = "", string $type = "") { + return $this->onGlobal($this->request->getQueryParams(), $name, $type); + } + + /** + * @param string $name + * @param string $type + * @return array|int|string + */ + public function post(string $name = "", string $type = "") { + return $this->onGlobal($this->request->getParsedBody(), $name, $type); + } + + /** + * @param string $name + * @param string $type + * @return array|int|string + */ + public function cookie(string $name = "", string $type = "") { + return $this->onGlobal($this->request->getCookieParams(), $name, $type); + } + + /** + * @param string $name + * @param string $type + * @return array|int|string + */ + public function session(string $name = "", string $type = "") { + if(!class_exists(Session::class)) return null; + if(!isset($this->session)) $this->session = new Session(); + return $this->onGlobal($this->session->getSession(), $name, $type); + } + + /** + * @param string $name + * @return array|int|string + */ + public function files(string $name = "") { + return $this->onGlobal($this->request->getUploadedFiles(), $name); + } + + /** + * @return string|null + */ + public function stream() { + return !empty($this->request->getBody()->getContents()) ? $this->request->getBody()->getContents(): null; + } + + + + /** + * @param string $name + * @param string $value + * @return void + */ + public function addHeader(string $name, string $value) : void { + $this->setResponse($this->response->withAddedHeader($name, $value)); + } + + /** + * @param string $name + * @param $value + * @return void + */ + public function setSession(string $name, $value = null) : void { + if(class_exists(Session::class)) { + if(!isset($this->session)) $this->session = new Session(); + $this->session->setSession($name, $value); + } + } + + /** + * @param string $name + * @param string $value + * @param int $expires + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httponly + * @return void + */ + public function setCookie(string $name, string $value="", int $expires = 0, string $path = "", string $domain = "", bool $secure = false, bool $httponly = false) : void { + $cookie = []; + $cookie[] = $name."=".((!empty($value)) ? $value : "deleted"); + if($expires != 0) { + $cookie[] = ($expires>time()) ? "expires=".date("D, d-M-Y H:i:s", $expires)." GMT; Max-Age=".($expires-time()) : "expires=".date("D, d-M-Y H:i:s", 0)." GMT; Max-Age=0"; + } + if(!empty($path)) $cookie[] = "path=".$path; + if(!empty($domain)) $cookie[] = "domain=".$domain; + if($secure) $cookie[] = "Secure"; + if($httponly) $cookie[] = "HttpOnly"; + $this->addHeader("Set-Cookie", implode("; ", $cookie)); + } + + /** + * @param string|null $name + * @return void + */ + public function clearSession(string $name = null) : void{ + if(class_exists(Session::class)) { + if(!isset($this->session)) $this->session = new Session(); + $this->session->clearSession($name); + } + } + + /** + * @param string $name + * @param string $path + * @return void + */ + public function clearCookie(string $name, string $path = "") : void { + $cookie = $name."=deleted; expires=".date("D, d-M-Y H:i:s", 0)." GMT; Max-Age=0"; + if(!empty($path)) $cookie.="; path=".$path; + $this->addHeader("Set-Cookie", $cookie); + } + + + + /** + * @param array $var + * @param string $name + * @param string $type + * @return array|int|string + */ + private function onGlobal(array $var, string $name, string $type = "") { + $name = strtolower($name); + if (!empty($name)) + { + if (!isset($var[$name])) return null; + + if (empty($type)) { + return $var[$name]; + } + elseif ($type == self::STRING) { + return (!empty($var[$name])) ? (string)$var[$name] : null; + } + elseif ($type == self::INT) { + return (!empty((int)$var[$name]) || $var[$name]==0) ? (int)$var[$name] : null; + } + } + return $var; + } + +} \ No newline at end of file diff --git a/src/Logger.php b/src/Logger.php new file mode 100644 index 0000000..0c57822 --- /dev/null +++ b/src/Logger.php @@ -0,0 +1,144 @@ + self::DEBUG, + 6 => self::INFO, + 5 => self::NOTICE, + 4 => self::WARNING, + 3 => self::ERROR, + 2 => self::CRITICAL, + 1 => self::ALERT, + 0 => self::EMERGENCY, + ]; + + /** + * @param \Stringable|string $message + * @param array $context + * @return void + */ + public function emergency(\Stringable|string $message, array $context=[]): void { + $this->log(self::EMERGENCY, $message, $context); + } + + /** + * @param \Stringable|string $message + * @param array $context + * @return void + */ + public function alert(\Stringable|string $message, array $context=[]): void { + $this->log(self::ALERT, $message, $context); + } + + /** + * @param \Stringable|string $message + * @param array $context + * @return void + */ + public function critical(\Stringable|string $message, array $context=[]): void { + $this->log(self::CRITICAL, $message, $context); + } + + /** + * @param \Stringable|string $message + * @param array $context + * @return void + */ + public function error(\Stringable|string $message, array $context=[]): void { + $this->log(self::ERROR, $message, $context); + } + + /** + * @param \Stringable|string $message + * @param array $context + * @return void + */ + public function warning(\Stringable|string $message, array $context=[]): void { + $this->log(self::WARNING, $message, $context); + } + + /** + * @param \Stringable|string $message + * @param array $context + * @return void + */ + public function notice(\Stringable|string $message, array $context=[]): void { + $this->log(self::NOTICE, $message, $context); + } + + /** + * @param \Stringable|string $message + * @param array $context + * @return void + */ + public function info(\Stringable|string $message, array $context=[]): void { + $this->log(self::INFO, $message, $context); + } + + /** + * @param \Stringable|string $message + * @param array $context + * @return void + */ + public function debug(\Stringable|string $message, array $context=[]): void { + $this->log(self::DEBUG, $message, $context); + } + + /** + * @param $level + * @param \Stringable|string $message + * @param array $context + * @return void + */ + public function log($level, \Stringable|string $message, array $context=[]): void { + if(is_numeric($level) && isset(self::RFC_5424_LEVELS[$level])){ + $level = self::RFC_5424_LEVELS[$level]; + } + if(!empty((string)$message))$in[] = $message; + if(!empty($context))$in[] = $context; + self::$logs[$level][] = (count($in)==1) ? $in[0] : $in;; + } + + /** + * @param $level + * @param mixed $context + * @return void + */ + public function dump($level, mixed $context) : void { + self::$logs[$level][] = $context; + } + + /** + * @param string $key + * @return array + */ + public function getLogs($level = null) : array { + if(is_numeric($level) && isset(self::RFC_5424_LEVELS[$level])){ + $level = self::RFC_5424_LEVELS[$level]; + } + $out = []; + foreach (self::$logs as $key => $logField){ + $out[$key] = (count($logField)==1) ? $logField[0] : $logField; + } + return isset($level) ? $out[$level] : $out; + } +} \ No newline at end of file diff --git a/src/Main.php b/src/Main.php new file mode 100644 index 0000000..e8629b8 --- /dev/null +++ b/src/Main.php @@ -0,0 +1,97 @@ +syslogger()->warning("Application config without countainer"); + return self::$container; + } + + /** + * @return TemplateInterface|null + */ + final public function template() : ?TemplateInterface { + if(empty(self::$template)) $this->syslogger()->warning("Application config without template"); + return self::$template; + } + + /** + * @return LoggerInterface|null + */ + final public function logger() : ?LoggerInterface { + if(empty(self::$logger)){ + $this->syslogger()->warning("Application config without logger"); + } + return self::$logger; + } +} \ No newline at end of file diff --git a/src/ResponseEmitter.php b/src/ResponseEmitter.php new file mode 100644 index 0000000..e499539 --- /dev/null +++ b/src/ResponseEmitter.php @@ -0,0 +1,117 @@ +responseChunkSize = $responseChunkSize; + } + + /** + * @param ResponseInterface $response + */ + public function emit(ResponseInterface $response): void + { + $isEmpty = $this->isResponseEmpty($response); + if (headers_sent() === false) { + $this->emitStatusLine($response); + $this->emitHeaders($response); + } + + if (!$isEmpty) { + $this->emitBody($response); + } + } + + /** + * @param ResponseInterface $response + */ + private function emitHeaders(ResponseInterface $response): void + { + foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + } + + /** + * @param ResponseInterface $response + */ + private function emitStatusLine(ResponseInterface $response): void + { + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + } + + /** + * @param ResponseInterface $response + */ + private function emitBody(ResponseInterface $response): void + { + $body = $response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + $amountToRead = (int) $response->getHeaderLine('Content-Length'); + if (!$amountToRead) { + $amountToRead = $body->getSize(); + } + + if ($amountToRead) { + while ($amountToRead > 0 && !$body->eof()) { + $length = min($this->responseChunkSize, $amountToRead); + $data = $body->read($length); + echo $data; + + $amountToRead -= strlen($data); + + if (connection_status() !== CONNECTION_NORMAL) { + break; + } + } + } else { + while (!$body->eof()) { + echo $body->read($this->responseChunkSize); + if (connection_status() !== CONNECTION_NORMAL) { + break; + } + } + } + } + + /** + * @param ResponseInterface $response + * @return bool + */ + public function isResponseEmpty(ResponseInterface $response): bool + { + if (in_array($response->getStatusCode(), [204, 205, 304], true)) { + return true; + } + $stream = $response->getBody(); + $seekable = $stream->isSeekable(); + if ($seekable) { + $stream->rewind(); + } + return $seekable ? $stream->read(1) === '' : $stream->eof(); + } +} \ No newline at end of file diff --git a/src/Session.php b/src/Session.php new file mode 100644 index 0000000..0ef4960 --- /dev/null +++ b/src/Session.php @@ -0,0 +1,39 @@ + + + + + +
+ 10) $display = false; + if (in_array(self::$deep,[3,5])) $display = false; + ?> +
+ ()   +
+ +
+ + $val) : ?> + + + + + + + + + + + + +
[]
+
+ +
+
()
+
+ '; + self::generateJSBlock(); + } + }); + } + + public static function dd(mixed $exData) : void { + register_shutdown_function(function() use ($exData) { + if(in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)){ + var_dump($exData); + } else { + self::$objid = substr(md5(time()),0,5); + self::generateCSSBlock(); + self::generateStartLine(); + self::generateHTMLBlock($exData); + echo '
'; + self::generateJSBlock(); + } + }); + exit; + } +} \ No newline at end of file