diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6b5a161 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "game-worker/kiri-router", + "description": "kiri router", + "authors": [ + { + "name": "XiangLin", + "email": "as2252258@163.com" + } + ], + "license": "MIT", + "require": { + "php": ">=8.0", + "composer-runtime-api": "^2.0", + "psr/http-server-middleware": "^1.0", + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Kiri\\Router": "./src" + } + }, + "require-dev": { + } +} diff --git a/src/ActionManager.php b/src/ActionManager.php index dc0c021..12938b3 100644 --- a/src/ActionManager.php +++ b/src/ActionManager.php @@ -5,4 +5,37 @@ namespace Kiri\Router; class ActionManager { + + private static array $array = []; + + + /** + * @param string $class + * @param string $method + * @param Handler $handler + * @return void + */ + public static function add(string $class, string $method, Handler $handler): void + { + if (!isset(static::$array[$class])) { + static::$array[$class] = [$method => []]; + } + static::$array[$class][$method] = $handler; + } + + + /** + * @param string $class + * @param string $method + * @return array|null + */ + public static function get(string $class, string $method): ?Handler + { + if (isset(static::$array[$class])) { + return static::$array[$class][$method] ?? null; + } + return null; + } + + } diff --git a/src/Aspect/AbstractsAspect.php b/src/Aspect/AbstractsAspect.php index f87b728..c4e1e42 100644 --- a/src/Aspect/AbstractsAspect.php +++ b/src/Aspect/AbstractsAspect.php @@ -1,6 +1,6 @@ middlewares[$this->offset])) { + return $this->handler->handle($request); + } + + $middleware = di($this->middlewares[$this->offset]); + $this->offset += 1; + + return $middleware->process($request); + } + } diff --git a/src/Base/AuthorizationInterface.php b/src/Base/AuthorizationInterface.php index 6d1ca3f..1bdf67f 100644 --- a/src/Base/AuthorizationInterface.php +++ b/src/Base/AuthorizationInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Kiri\Message\Handler; +namespace Kiri\Router\Base; /** diff --git a/src/Base/ClosureController.php b/src/Base/ClosureController.php index 0066e9c..8660402 100644 --- a/src/Base/ClosureController.php +++ b/src/Base/ClosureController.php @@ -1,6 +1,6 @@ getAccessControlRequestMethod(); - $allowHeaders = $request->getAccessControlAllowHeaders(); - - $this->response->withAccessControlAllowOrigin('*')->withAccessControlRequestMethod($requestMethod) - ->withAccessControlAllowHeaders($allowHeaders); - + /** @var ResponseInterface $response */ + $response = \Kiri::service()->get('response'); + $response->withHeader('Access-Control-Allow-Headers', $request->header('Access-Control-Allow-Headers')) + ->withHeader('Access-Control-Request-Method', $request->header('Access-Control-Allow-Origin')) + ->withHeader('Access-Control-Allow-Origin', $request->header('Access-Control-Allow-Headers')); return $handler->handle($request); } diff --git a/src/Base/EventDispatchHelper.php b/src/Base/EventDispatchHelper.php index db5f980..54b931d 100644 --- a/src/Base/EventDispatchHelper.php +++ b/src/Base/EventDispatchHelper.php @@ -1,9 +1,7 @@ withContentType(ContentType::HTML); + $response->withContentType(ContentType::HTML)->withBody(new Stream(jTraceEx($exception, null, true))); if ($exception->getCode() == 404) { - return $response->withBody(new Stream($exception->getMessage()))->withStatus(404); + return $response->withStatus(404); + } else { + return $response->withStatus($exception->getCode() == 0 ? 500 : $exception->getCode()); } - return $response->withBody(new Stream(jTraceEx($exception, null, true))) - ->withStatus($exception->getCode() == 0 ? 500 : $exception->getCode()); } } diff --git a/src/Base/MethodErrorController.php b/src/Base/MethodErrorController.php index f8d60e5..034ae10 100644 --- a/src/Base/MethodErrorController.php +++ b/src/Base/MethodErrorController.php @@ -1,6 +1,6 @@ routeMap = new HashMap(); + $this->map = new HashMap(); + } + + + /** + * @param string $path + * @param mixed $middleware + * @return void + * @throws Exception + */ + public function addPathMiddleware(string $path, string $middleware): void + { + if ($this->routeMap->has($path)) { + $values = $this->routeMap->get($path); + if (in_array($middleware, $values)) { + return; + } + if (!in_array(MiddlewareInterface::class, class_implements($middleware))) { + return; + } + } + $this->routeMap->append($path, $middleware); + } + } diff --git a/src/Base/NotFoundController.php b/src/Base/NotFoundController.php index e5b5db1..cd95c5b 100644 --- a/src/Base/NotFoundController.php +++ b/src/Base/NotFoundController.php @@ -1,6 +1,6 @@ version; } /** @@ -33,9 +53,11 @@ class Message implements MessageInterface * @param string $version HTTP protocol version * @return static */ - public function withProtocolVersion(string $version) + public function withProtocolVersion(string $version): static { // TODO: Implement withProtocolVersion() method. + $this->version = $version; + return $this; } /** @@ -63,9 +85,10 @@ class Message implements MessageInterface * key MUST be a header name, and each value MUST be an array of strings * for that header. */ - public function getHeaders() + public function getHeaders(): array { // TODO: Implement getHeaders() method. + return $this->headers; } /** @@ -76,9 +99,10 @@ class Message implements MessageInterface * name using a case-insensitive string comparison. Returns false if * no matching header name is found in the message. */ - public function hasHeader(string $name) + public function hasHeader(string $name): bool { // TODO: Implement hasHeader() method. + return isset($this->headers[$name]) && $this->headers[$name] !== null; } /** @@ -95,9 +119,15 @@ class Message implements MessageInterface * header. If the header does not appear in the message, this method MUST * return an empty array. */ - public function getHeader(string $name) + public function getHeader(string $name): array { // TODO: Implement getHeader() method. + if (isset($this->headers[$name])) { + $header = $this->headers[$name]; + + return is_string($header) ? [$header] : $header; + } + return []; } /** @@ -119,9 +149,24 @@ class Message implements MessageInterface * concatenated together using a comma. If the header does not appear in * the message, this method MUST return an empty string. */ - public function getHeaderLine(string $name) + public function getHeaderLine(string $name): string { // TODO: Implement getHeaderLine() method. + return implode(';', $this->getHeader($name)); + } + + + /** + * @param string $name + * @param string|null $default + * @return string|null + */ + public function header(string $name, ?string $default = null): ?string + { + if (!$this->hasHeader($name)) { + return $default; + } + return $this->getHeaderLine($name); } /** @@ -137,11 +182,13 @@ class Message implements MessageInterface * @param string $name Case-insensitive header field name. * @param string|string[] $value Header value(s). * @return static - * @throws \InvalidArgumentException for invalid header names or values. + * @throws InvalidArgumentException for invalid header names or values. */ - public function withHeader(string $name, $value) + public function withHeader(string $name, $value): static { - // TODO: Implement withHeader() method. + // TODO: Implement withAddedHeader() method. + $this->headers[$name] = $value; + return $this; } /** @@ -158,11 +205,13 @@ class Message implements MessageInterface * @param string $name Case-insensitive header field name to add. * @param string|string[] $value Header value(s). * @return static - * @throws \InvalidArgumentException for invalid header names or values. + * @throws InvalidArgumentException for invalid header names or values. */ - public function withAddedHeader(string $name, $value) + public function withAddedHeader(string $name, $value): static { // TODO: Implement withAddedHeader() method. + $this->headers[$name] = $value; + return $this; } /** @@ -177,9 +226,11 @@ class Message implements MessageInterface * @param string $name Case-insensitive header field name to remove. * @return static */ - public function withoutHeader(string $name) + public function withoutHeader(string $name): static { // TODO: Implement withoutHeader() method. + unset($this->headers[$name]); + return $this; } /** @@ -187,9 +238,10 @@ class Message implements MessageInterface * * @return StreamInterface Returns the body as a stream. */ - public function getBody() + public function getBody(): StreamInterface { // TODO: Implement getBody() method. + return $this->stream; } /** @@ -203,10 +255,12 @@ class Message implements MessageInterface * * @param StreamInterface $body Body. * @return static - * @throws \InvalidArgumentException When the body is not valid. + * @throws InvalidArgumentException When the body is not valid. */ - public function withBody(StreamInterface $body) + public function withBody(StreamInterface $body): static { // TODO: Implement withBody() method. + $this->stream = $body; + return $this; } } diff --git a/src/Constrict/Request.php b/src/Constrict/Request.php index 8f3ce37..1f2e3da 100644 --- a/src/Constrict/Request.php +++ b/src/Constrict/Request.php @@ -1,6 +1,6 @@ getUri(); + } + + + /** + * @param array $headers + * @return $this + */ + public function withHeaders(array $headers): static + { + foreach ($headers as $key => $header) { + $this->withHeader($key, [$header]); + } + return $this; } /** @@ -46,9 +73,10 @@ class Request extends Message implements RequestInterface * @param string $requestTarget * @return static */ - public function withRequestTarget(string $requestTarget) + public function withRequestTarget(string $requestTarget): static { // TODO: Implement withRequestTarget() method. + return $this; } /** @@ -56,9 +84,10 @@ class Request extends Message implements RequestInterface * * @return string Returns the request method. */ - public function getMethod() + public function getMethod(): string { // TODO: Implement getMethod() method. + return $this->method; } /** @@ -76,9 +105,11 @@ class Request extends Message implements RequestInterface * @return static * @throws \InvalidArgumentException for invalid HTTP methods. */ - public function withMethod(string $method) + public function withMethod(string $method): static { // TODO: Implement withMethod() method. + $this->method = $method; + return $this; } /** @@ -90,9 +121,10 @@ class Request extends Message implements RequestInterface * @return UriInterface Returns a UriInterface instance * representing the URI of the request. */ - public function getUri() + public function getUri(): UriInterface { // TODO: Implement getUri() method. + return $this->uri; } /** @@ -125,8 +157,10 @@ class Request extends Message implements RequestInterface * @param bool $preserveHost Preserve the original state of the Host header. * @return static */ - public function withUri(UriInterface $uri, bool $preserveHost = false) + public function withUri(UriInterface $uri, bool $preserveHost = false): static { // TODO: Implement withUri() method. + $this->uri = $uri; + return $this; } } diff --git a/src/Constrict/Response.php b/src/Constrict/Response.php index 82f5495..f1e4919 100644 --- a/src/Constrict/Response.php +++ b/src/Constrict/Response.php @@ -1,12 +1,30 @@ withHeader('Content-Type', $type->name); + return $this; + } + /** * Gets the response status code. * @@ -15,9 +33,10 @@ class Response extends Message implements ResponseInterface * * @return int Status code. */ - public function getStatusCode() + public function getStatusCode(): int { // TODO: Implement getStatusCode() method. + return $this->code; } /** @@ -40,9 +59,12 @@ class Response extends Message implements ResponseInterface * @return static * @throws \InvalidArgumentException For invalid status code arguments. */ - public function withStatus(int $code, string $reasonPhrase = '') + public function withStatus(int $code, string $reasonPhrase = ''): static { // TODO: Implement withStatus() method. + $this->code = $code; + $this->reasonPhrase = $reasonPhrase; + return $this; } /** @@ -58,8 +80,9 @@ class Response extends Message implements ResponseInterface * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml * @return string Reason phrase; must return an empty string if none present. */ - public function getReasonPhrase() + public function getReasonPhrase(): string { // TODO: Implement getReasonPhrase() method. + return $this->reasonPhrase; } } diff --git a/src/Constrict/ServerRequest.php b/src/Constrict/ServerRequest.php index c0bc6ef..4eb3123 100644 --- a/src/Constrict/ServerRequest.php +++ b/src/Constrict/ServerRequest.php @@ -1,12 +1,24 @@ server; } /** @@ -31,9 +44,10 @@ class ServerRequest extends Request implements ServerRequestInterface * * @return array */ - public function getCookieParams() + public function getCookieParams(): array { // TODO: Implement getCookieParams() method. + return $this->cookies; } /** @@ -53,9 +67,11 @@ class ServerRequest extends Request implements ServerRequestInterface * @param array $cookies Array of key/value pairs representing cookies. * @return static */ - public function withCookieParams(array $cookies) + public function withCookieParams(array $cookies): static { // TODO: Implement withCookieParams() method. + $this->cookies = $cookies; + return $this; } /** @@ -70,9 +86,10 @@ class ServerRequest extends Request implements ServerRequestInterface * * @return array */ - public function getQueryParams() + public function getQueryParams(): array { // TODO: Implement getQueryParams() method. + return $this->query; } /** @@ -97,9 +114,11 @@ class ServerRequest extends Request implements ServerRequestInterface * $_GET. * @return static */ - public function withQueryParams(array $query) + public function withQueryParams(array $query): static { // TODO: Implement withQueryParams() method. + $this->query = $query; + return $this; } /** @@ -114,9 +133,10 @@ class ServerRequest extends Request implements ServerRequestInterface * @return array An array tree of UploadedFileInterface instances; an empty * array MUST be returned if no data is present. */ - public function getUploadedFiles() + public function getUploadedFiles(): array { // TODO: Implement getUploadedFiles() method. + return $this->files; } /** @@ -130,9 +150,11 @@ class ServerRequest extends Request implements ServerRequestInterface * @return static * @throws \InvalidArgumentException if an invalid structure is provided. */ - public function withUploadedFiles(array $uploadedFiles) + public function withUploadedFiles(array $uploadedFiles): static { // TODO: Implement withUploadedFiles() method. + $this->files = $uploadedFiles; + return $this; } /** @@ -150,9 +172,10 @@ class ServerRequest extends Request implements ServerRequestInterface * @return null|array|object The deserialized body parameters, if any. * These will typically be an array or object. */ - public function getParsedBody() + public function getParsedBody(): object|array|null { // TODO: Implement getParsedBody() method. + return $this->posts; } /** @@ -183,9 +206,11 @@ class ServerRequest extends Request implements ServerRequestInterface * @throws \InvalidArgumentException if an unsupported argument type is * provided. */ - public function withParsedBody($data) + public function withParsedBody($data): static { // TODO: Implement withParsedBody() method. + $this->posts = $data; + return $this; } /** @@ -199,27 +224,31 @@ class ServerRequest extends Request implements ServerRequestInterface * * @return array Attributes derived from the request. */ - public function getAttributes() + public function getAttributes(): array { // TODO: Implement getAttributes() method. - }/** - * Retrieve a single derived request attribute. - * - * Retrieves a single derived request attribute as described in - * getAttributes(). If the attribute has not been previously set, returns - * the default value as provided. - * - * This method obviates the need for a hasAttribute() method, as it allows - * specifying a default value to return if the attribute is not found. - * - * @param string $name The attribute name. - * @param mixed $default Default value to return if the attribute does not exist. - * @return mixed - * @see getAttributes() - */ - public function getAttribute(string $name, $default = null) + return []; + } + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + * @see getAttributes() + */ + public function getAttribute(string $name, $default = null): mixed { // TODO: Implement getAttribute() method. + return $default; } /** @@ -237,9 +266,10 @@ class ServerRequest extends Request implements ServerRequestInterface * @return static * @see getAttributes() */ - public function withAttribute(string $name, $value) + public function withAttribute(string $name, $value): static { // TODO: Implement withAttribute() method. + return $this; } /** @@ -256,8 +286,9 @@ class ServerRequest extends Request implements ServerRequestInterface * @return static * @see getAttributes() */ - public function withoutAttribute(string $name) + public function withoutAttribute(string $name): static { // TODO: Implement withoutAttribute() method. + return $this; } } diff --git a/src/Constrict/Stream.php b/src/Constrict/Stream.php index 0b59381..e9ab05c 100644 --- a/src/Constrict/Stream.php +++ b/src/Constrict/Stream.php @@ -1,12 +1,27 @@ content = $content; + } + + /** * Reads all data from the stream into a string, from the beginning to end. * @@ -24,6 +39,7 @@ class Stream implements StreamInterface public function __toString() { // TODO: Implement __toString() method. + return $this->content; } /** @@ -31,9 +47,14 @@ class Stream implements StreamInterface * * @return void */ - public function close() + public function close(): void { // TODO: Implement close() method. + if (is_resource($this->content)) { + fclose($this->content); + } else { + $this->content = ''; + } } /** @@ -43,9 +64,10 @@ class Stream implements StreamInterface * * @return resource|null Underlying PHP stream, if any */ - public function detach() + public function detach(): mixed { // TODO: Implement detach() method. + return null; } /** @@ -53,9 +75,10 @@ class Stream implements StreamInterface * * @return int|null Returns the size in bytes if known, or null if unknown. */ - public function getSize() + public function getSize(): ?int { // TODO: Implement getSize() method. + return $this->size; } /** @@ -64,9 +87,10 @@ class Stream implements StreamInterface * @return int Position of the file pointer * @throws \RuntimeException on error. */ - public function tell() + public function tell(): int { // TODO: Implement tell() method. + return 0; } /** @@ -74,9 +98,10 @@ class Stream implements StreamInterface * * @return bool */ - public function eof() + public function eof(): bool { // TODO: Implement eof() method. + return false; } /** @@ -84,9 +109,10 @@ class Stream implements StreamInterface * * @return bool */ - public function isSeekable() + public function isSeekable(): bool { // TODO: Implement isSeekable() method. + return true; } /** @@ -101,22 +127,28 @@ class Stream implements StreamInterface * SEEK_END: Set position to end-of-stream plus offset. * @throws \RuntimeException on failure. */ - public function seek(int $offset, int $whence = SEEK_SET) + public function seek(int $offset, int $whence = SEEK_SET): void { // TODO: Implement seek() method. - }/** - * Seek to the beginning of the stream. - * - * If the stream is not seekable, this method will raise an exception; - * otherwise, it will perform a seek(0). - * - * @throws \RuntimeException on failure. - * @link http://www.php.net/manual/en/function.fseek.php - * @see seek() - */ - public function rewind() + if (is_resource($this->content)) { + fseek($this->content, $offset, $whence); + } + } + + /** + * Seek to the beginning of the stream. + * + * If the stream is not seekable, this method will raise an exception; + * otherwise, it will perform a seek(0). + * + * @throws \RuntimeException on failure. + * @link http://www.php.net/manual/en/function.fseek.php + * @see seek() + */ + public function rewind(): void { // TODO: Implement rewind() method. + $this->seek(0); } /** @@ -124,9 +156,13 @@ class Stream implements StreamInterface * * @return bool */ - public function isWritable() + public function isWritable(): bool { // TODO: Implement isWritable() method. + if (is_resource($this->content)) { + return is_writable($this->content); + } + return true; } /** @@ -136,9 +172,17 @@ class Stream implements StreamInterface * @return int Returns the number of bytes written to the stream. * @throws \RuntimeException on failure. */ - public function write(string $string) + public function write(string $string): int { // TODO: Implement write() method. + if (is_resource($this->content)) { + $this->content = fopen($string, 'wr'); + $this->size = filesize($string); + } else { + $this->content = $string; + $this->size = mb_strlen($string); + } + return $this->size; } /** @@ -146,9 +190,13 @@ class Stream implements StreamInterface * * @return bool */ - public function isReadable() + public function isReadable(): bool { // TODO: Implement isReadable() method. + if (is_resource($this->content)) { + return is_readable($this->content); + } + return true; } /** @@ -161,9 +209,14 @@ class Stream implements StreamInterface * if no bytes are available. * @throws \RuntimeException if an error occurs. */ - public function read(int $length) + public function read(int $length): string { // TODO: Implement read() method. + if (!is_resource($this->content)) { + return mb_substr($this->content, 0, $length); + } else { + return fread($this->content, $length); + } } /** @@ -173,9 +226,13 @@ class Stream implements StreamInterface * @throws \RuntimeException if unable to read or an error occurs while * reading. */ - public function getContents() + public function getContents(): string { // TODO: Implement getContents() method. + if (is_resource($this->content)) { + return fread($this->content, $this->getSize()); + } + return $this->content; } /** @@ -190,8 +247,12 @@ class Stream implements StreamInterface * provided. Returns a specific key value if a key is provided and the * value is found, or null if the key is not found. */ - public function getMetadata(?string $key = null) + public function getMetadata(?string $key = null): mixed { // TODO: Implement getMetadata() method. + if (is_resource($this->content)) { + return stream_get_meta_data($this->content); + } + return null; } } diff --git a/src/Constrict/Uri.php b/src/Constrict/Uri.php index 86a7689..b3ba980 100644 --- a/src/Constrict/Uri.php +++ b/src/Constrict/Uri.php @@ -1,12 +1,21 @@ scheme; } /** @@ -44,9 +54,10 @@ class Uri implements UriInterface * @see https://tools.ietf.org/html/rfc3986#section-3.2 * @return string The URI authority, in "[user-info@]host[:port]" format. */ - public function getAuthority() + public function getAuthority(): string { // TODO: Implement getAuthority() method. + return ''; } /** @@ -64,9 +75,10 @@ class Uri implements UriInterface * * @return string The URI user information, in "username[:password]" format. */ - public function getUserInfo() + public function getUserInfo(): string { // TODO: Implement getUserInfo() method. + return $this->user . '[' . $this->password . ']'; } /** @@ -80,9 +92,10 @@ class Uri implements UriInterface * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 * @return string The URI host. */ - public function getHost() + public function getHost(): string { // TODO: Implement getHost() method. + return $this->host; } /** @@ -100,9 +113,10 @@ class Uri implements UriInterface * * @return null|int The URI port. */ - public function getPort() + public function getPort(): ?int { // TODO: Implement getPort() method. + return $this->port; } /** @@ -130,9 +144,10 @@ class Uri implements UriInterface * @see https://tools.ietf.org/html/rfc3986#section-3.3 * @return string The URI path. */ - public function getPath() + public function getPath(): string { // TODO: Implement getPath() method. + return $this->path; } /** @@ -155,9 +170,10 @@ class Uri implements UriInterface * @see https://tools.ietf.org/html/rfc3986#section-3.4 * @return string The URI query string. */ - public function getQuery() + public function getQuery(): string { // TODO: Implement getQuery() method. + return $this->queryString; } /** @@ -176,9 +192,10 @@ class Uri implements UriInterface * @see https://tools.ietf.org/html/rfc3986#section-3.5 * @return string The URI fragment. */ - public function getFragment() + public function getFragment(): string { // TODO: Implement getFragment() method. + return $this->fragment; } /** @@ -196,9 +213,11 @@ class Uri implements UriInterface * @return static A new instance with the specified scheme. * @throws \InvalidArgumentException for invalid or unsupported schemes. */ - public function withScheme(string $scheme) + public function withScheme(string $scheme): static { // TODO: Implement withScheme() method. + $this->scheme = $scheme; + return $this; } /** @@ -215,9 +234,12 @@ class Uri implements UriInterface * @param null|string $password The password associated with $user. * @return static A new instance with the specified user information. */ - public function withUserInfo(string $user, ?string $password = null) + public function withUserInfo(string $user, ?string $password = null): static { // TODO: Implement withUserInfo() method. + $this->user = $user; + $this->password = $password; + return $this; } /** @@ -232,9 +254,11 @@ class Uri implements UriInterface * @return static A new instance with the specified host. * @throws \InvalidArgumentException for invalid hostnames. */ - public function withHost(string $host) + public function withHost(string $host): static { // TODO: Implement withHost() method. + $this->host = $host; + return $this; } /** @@ -254,9 +278,11 @@ class Uri implements UriInterface * @return static A new instance with the specified port. * @throws \InvalidArgumentException for invalid ports. */ - public function withPort(?int $port) + public function withPort(?int $port): static { // TODO: Implement withPort() method. + $this->port = $port; + return $this; } /** @@ -281,9 +307,11 @@ class Uri implements UriInterface * @return static A new instance with the specified path. * @throws \InvalidArgumentException for invalid paths. */ - public function withPath(string $path) + public function withPath(string $path): static { // TODO: Implement withPath() method. + $this->path = $path; + return $this; } /** @@ -301,9 +329,11 @@ class Uri implements UriInterface * @return static A new instance with the specified query string. * @throws \InvalidArgumentException for invalid query strings. */ - public function withQuery(string $query) + public function withQuery(string $query): static { // TODO: Implement withQuery() method. + $this->queryString = $query; + return $this; } /** @@ -320,9 +350,11 @@ class Uri implements UriInterface * @param string $fragment The fragment to use with the new instance. * @return static A new instance with the specified fragment. */ - public function withFragment(string $fragment) + public function withFragment(string $fragment): static { // TODO: Implement withFragment() method. + $this->fragment = $fragment; + return $this; } /** @@ -351,5 +383,26 @@ class Uri implements UriInterface public function __toString() { // TODO: Implement __toString() method. + return $this->scheme . '://x.x.x.x:' . $this->port . '/' . $this->path . '?' . $this->queryString; } + + + /** + * @param \Swoole\Http\Request $request + * @return UriInterface + */ + public static function parse(\Swoole\Http\Request $request): UriInterface + { + $uri = new static(); + $uri->queryString = $request->server['query_string']; + $uri->path = $request->server['path_info']; + $uri->port = $request->server['server_port']; + if (isset($request->server['https']) && $request->server['https'] !== 'off') { + $uri->scheme = 'https'; + } else { + $uri->scheme = 'http'; + } + return $uri; + } + } diff --git a/src/ContentType.php b/src/ContentType.php index 4ec093d..71d7d20 100644 --- a/src/ContentType.php +++ b/src/ContentType.php @@ -1,6 +1,6 @@ get($className); if (is_null($reflection)) { - $reflection = \Kiri::getDi()->getReflect($className); + $reflection = \Kiri::getDi()->getReflectionClass($class::class); } - $this->resolveMethod($class, $method, $reflection); + return $this->resolveMethod($method, $reflection); + } + + + /** + * @param Closure $method + * @return Handler + * @throws ReflectionException + */ + public function addRouteByClosure(Closure $method): Handler + { + $reflection = \Kiri::getDi()->getFunctionParams($method); + + return new Handler($method, $reflection); } @@ -31,96 +44,34 @@ class ControllerInterpreter * @param object $class * @param string|ReflectionMethod $method * @param ReflectionClass|null $reflection - * @return void + * @return Handler * @throws ReflectionException */ - public function addRouteByObject(object $class, string|ReflectionMethod $method, ?ReflectionClass $reflection = null): void + public function addRouteByObject(object $class, string|ReflectionMethod $method, ?ReflectionClass $reflection = null): Handler { if (is_null($reflection)) { - $reflection = \Kiri::getDi()->getReflect($class::class); + $reflection = \Kiri::getDi()->getReflectionClass($class::class); } - $this->resolveMethod($class, $method, $reflection); + return $this->resolveMethod($method, $reflection); } /** - * @param object $class * @param string|ReflectionMethod $reflectionMethod * @param ReflectionClass $reflectionClass - * @return void + * @return Handler * @throws ReflectionException */ - public function resolveMethod(object $class, string|\ReflectionMethod $reflectionMethod, ReflectionClass $reflectionClass): void + public function resolveMethod(string|\ReflectionMethod $reflectionMethod, ReflectionClass $reflectionClass): Handler { if (is_string($reflectionMethod)) { $reflectionMethod = $reflectionClass->getMethod($reflectionMethod); } - $this->resolveProperties($reflectionClass, $class); + $container = \Kiri::getDi(); + $parameters = $container->getMethodParams($reflectionMethod); - $parameters = $this->resolveMethodParams($reflectionMethod->getParameters()); - - $handler = new Handler([$reflectionClass->getName(), $reflectionMethod->getName()], $parameters); - - ActionManager::add($reflectionClass->getName(), $reflectionMethod->getName(), $handler); + return new Handler([$reflectionClass->getName(), $reflectionMethod->getName()], $parameters); } - - /** - * @param ReflectionClass $reflectionClass - * @param object $class - * @return void - */ - public function resolveProperties(ReflectionClass $reflectionClass, object $class): void - { - $properties = $reflectionClass->getProperties(); - foreach ($properties as $property) { - $propertyAttributes = $property->getAttributes(); - - foreach ($propertyAttributes as $attribute) { - $attribute->newInstance()->dispatch($class, $property->getName()); - } - } - } - - - /** - * @param array $parameters - * @return array - */ - public function resolveMethodParams(array $parameters): array - { - $params = []; - foreach ($parameters as $parameter) { - $parameterAttributes = $parameter->getAttributes(); - if (count($parameterAttributes) < 1) { - if ($parameter->isDefaultValueAvailable()) { - $value = $parameter->getDefaultValue(); - } else if ($parameter->getType() === null) { - $value = $parameter->getType(); - } else { - $value = $parameter->getType()->getName(); - if (class_exists($value) || interface_exists($value)) { - $value = \Kiri::getDi()->get($value); - } else { - $value = match ($parameter->getType()) { - 'string' => '', - 'int', 'float' => 0, - '', null, 'object', 'mixed' => NULL, - 'bool' => false, - 'default' => null - }; - } - } - $params[$parameter->getName()] = $value; - } else { - $attribute = $parameterAttributes[0]->newInstance(); - - $params[$parameter->getName()] = $attribute->dispatch(); - } - } - return $params; - } - - } diff --git a/src/DataGrip.php b/src/DataGrip.php index 4a34df1..5db0167 100644 --- a/src/DataGrip.php +++ b/src/DataGrip.php @@ -1,8 +1,9 @@ servers[$type])) { - $this->servers[$type] = Kiri::getDi()->create(RouterCollector::class); + $this->servers[$type] = Kiri::getDi()->make(RouterCollector::class); } return $this->servers[$type]; } diff --git a/src/Handler.php b/src/Handler.php index 82f5d04..7ac0db6 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -2,7 +2,39 @@ namespace Kiri\Router; -class Handler +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; +use ReflectionException; + +class Handler implements RequestHandlerInterface { + + /** + * @param array|\Closure $handler + * @param array $parameter + */ + public function __construct(public array|\Closure $handler, public array $parameter) + { + } + + + /** + * @param ServerRequestInterface $request + * @return ResponseInterface + * @throws ReflectionException + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + // TODO: Implement handle() method. + $result = call_user_func($this->handler, ...$this->parameter); + if ($result instanceof ResponseInterface) { + return $result; + } else { + $response = \Kiri::getDi()->get(ResponseInterface::class); + return $response->rewrite(); + } + } + } diff --git a/src/HttpRequestHandler.php b/src/HttpRequestHandler.php index 2b5348a..796a463 100644 --- a/src/HttpRequestHandler.php +++ b/src/HttpRequestHandler.php @@ -2,7 +2,26 @@ namespace Kiri\Router; -class HttpRequestHandler +use Kiri\Router\Base\AbstractHandler; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; +use ReflectionException; + +class HttpRequestHandler extends AbstractHandler implements RequestHandlerInterface { + + /** + * @param ServerRequestInterface $request + * @return ResponseInterface + * @throws ReflectionException + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + // TODO: Implement handle() method. + return $this->execute($request); + } + + } diff --git a/src/HttpResponseEmitter.php b/src/HttpResponseEmitter.php index 2e74aa1..8de1f81 100644 --- a/src/HttpResponseEmitter.php +++ b/src/HttpResponseEmitter.php @@ -2,7 +2,52 @@ namespace Kiri\Router; -class HttpResponseEmitter +use Exception; +use Kiri\Di\Interface\ResponseEmitter; +use Psr\Http\Message\ResponseInterface; + + +class HttpResponseEmitter implements ResponseEmitter { + + /** + * @param Response $proxy + * @param object $response + * @return void + * @throws Exception + */ + public function sender(ResponseInterface $proxy, object $response): void + { + // TODO: Implement sender() method. + $this->writeParams($proxy, $response); + + $proxy->write($response); + } + + + /** + * @param Response $proxy + * @param object $response + * @return void + * @throws Exception + */ + private function writeParams(ResponseInterface $proxy, object $response): void + { + $response->setStatusCode($proxy->getStatusCode()); + /** @var ServerRequest $request */ + $request = \Kiri::service()->get('request'); + foreach ($request->getHeaders() as $name => $header) { + $response->header($name, implode(', ', $header)); + } + + $response->setStatusCode($proxy->getStatusCode()); + foreach ($request->getCookieParams() as $cookie) { + $response->setCookie(...$cookie); + } + $response->header('Server', 'swoole'); + $response->header('Swoole-Version', swoole_version()); + } + + } diff --git a/src/Inject/AbstractRequestMethod.php b/src/Inject/AbstractRequestMethod.php index 338bc9a..b115937 100644 --- a/src/Inject/AbstractRequestMethod.php +++ b/src/Inject/AbstractRequestMethod.php @@ -1,8 +1,69 @@ getMethod($class::class, $method); + $middleware = $reflectionMethod->getAttributes(Middleware::class); + + $middlewareManager = \Kiri::getDi()->get(MiddlewareManager::class); + foreach ($middleware as $value) { + /** @var Middleware $instance */ + $instance = $value->newInstance(); + + $middlewareManager->addPathMiddleware($this->path, $instance->middleware); + } + + if ($this->formValidate !== '') { + $middlewareManager->addPathMiddleware($this->path, new ValidatorMiddleware($this->getFormRule())); + } + } + + + /** + * @return Validator|null + * @throws ReflectionException + */ + public function getFormRule(): ?Validator + { + $validator = new Validator(); + $reflect = \Kiri::getDi()->getReflectionClass($this->formValidate); + $model = $reflect->newInstanceWithoutConstructor(); + foreach ($reflect->getProperties() as $property) { + foreach ($property->getAttributes() as $attribute) { + $rule = $attribute->newInstance(); + if ($rule instanceof ValidatorInterface) { + $validator->addRule($property->getName(), $model, $rule); + } + } + } + return $validator; + } } diff --git a/src/Inject/Aspect.php b/src/Inject/Aspect.php index d509006..3d65c36 100644 --- a/src/Inject/Aspect.php +++ b/src/Inject/Aspect.php @@ -2,7 +2,16 @@ namespace Kiri\Router\Inject; +#[\Attribute(\Attribute::TARGET_METHOD)] class Aspect { + + /** + * @param string $aspect + */ + public function __construct(readonly public string $aspect) + { + } + } diff --git a/src/Inject/Controller.php b/src/Inject/Controller.php index 4a089bc..bdc895b 100644 --- a/src/Inject/Controller.php +++ b/src/Inject/Controller.php @@ -1,49 +1,9 @@ request = \Kiri::getDi()->get(RequestInterface::class); - - } - - - /** - * @param object $class - * @return void - * @throws ReflectionException - */ - public function dispatch(object $class): void - { - // TODO: Implement dispatch() method. - $reflectionClass = \Kiri::getDi()->getReflect($class::class); - - $scheduler = \Kiri::getDi()->get(ControllerInterpreter::class); - - foreach ($reflectionClass->getMethods() as $reflectionMethod) { - $scheduler->addRouteByObject($class, $reflectionMethod, $reflectionClass); - } - } - } diff --git a/src/Inject/Delete.php b/src/Inject/Delete.php index 0913c50..b3fbf72 100644 --- a/src/Inject/Delete.php +++ b/src/Inject/Delete.php @@ -1,33 +1,33 @@ path, [$class, $method]); + + $this->registerMiddleware($class, $method); } + } diff --git a/src/Inject/Filter.php b/src/Inject/Filter.php index a2fdc18..75c58c4 100644 --- a/src/Inject/Filter.php +++ b/src/Inject/Filter.php @@ -1,6 +1,6 @@ path, [$class, $method]); + + $this->registerMiddleware($class, $method); } } diff --git a/src/Inject/Head.php b/src/Inject/Head.php index 0d96d55..afb91a8 100644 --- a/src/Inject/Head.php +++ b/src/Inject/Head.php @@ -1,28 +1,25 @@ path, [$class, $method]); } + } diff --git a/src/Inject/Interceptor.php b/src/Inject/Interceptor.php index 372d9ee..65e9685 100644 --- a/src/Inject/Interceptor.php +++ b/src/Inject/Interceptor.php @@ -1,6 +1,6 @@ middleware); } diff --git a/src/Inject/Options.php b/src/Inject/Options.php index 7b016f6..001d8e5 100644 --- a/src/Inject/Options.php +++ b/src/Inject/Options.php @@ -1,33 +1,34 @@ path, [$class, $method]); + + $this->registerMiddleware($class, $method); } + } diff --git a/src/Inject/Other.php b/src/Inject/Other.php index eaa6497..8f5f521 100644 --- a/src/Inject/Other.php +++ b/src/Inject/Other.php @@ -2,7 +2,41 @@ namespace Kiri\Router\Inject; -class Other +use Exception; +use Kiri\Router\InjectRouteInterface; +use Kiri\Router\Router; +use ReflectionException; + +#[\Attribute(\Attribute::TARGET_METHOD)] +class Other extends AbstractRequestMethod implements InjectRouteInterface { + + /** + * @param string $method + * @param string $path + * @param string $formValidate + */ + public function __construct(readonly public string $method, string $path, string $formValidate = '') + { + parent::__construct($path, $formValidate); + } + + + /** + * @param object $class + * @param string $method + * @return void + * @throws ReflectionException + * @throws Exception + */ + public function dispatch(object $class, string $method): void + { + // TODO: Implement dispatch() method. + Router::addRoute([$this->method], $this->path, [$class, $method]); + + $this->registerMiddleware($class, $method); + } + + } diff --git a/src/Inject/Post.php b/src/Inject/Post.php index 533f962..4feb289 100644 --- a/src/Inject/Post.php +++ b/src/Inject/Post.php @@ -1,33 +1,33 @@ path, [$class, $method]); + + $this->registerMiddleware($class, $method); } + } diff --git a/src/Inject/Put.php b/src/Inject/Put.php index 86386f9..7a2401f 100644 --- a/src/Inject/Put.php +++ b/src/Inject/Put.php @@ -1,33 +1,32 @@ path, [$class, $method]); + + $this->registerMiddleware($class, $method); } } diff --git a/src/Inject/QueryData.php b/src/Inject/QueryData.php index 7dc5fc0..002431d 100644 --- a/src/Inject/QueryData.php +++ b/src/Inject/QueryData.php @@ -1,6 +1,6 @@ contentType = $contentType; + return $this; + } + + + /** + * @param string $method + * @param mixed ...$params + * @return mixed + */ + private function __call__(string $method, ...$params): mixed + { + if (!Context::exists(ResponseInterface::class)) { + $response = Context::set(ResponseInterface::class, new static()); + } else { + $response = Context::get(ResponseInterface::class); + } + return $response->{$method}(...$params); + } + + + /** + * Retrieves the HTTP protocol version as a string. + * + * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0"). + * + * @return string HTTP protocol version. + */ + public function getProtocolVersion(): string + { + // TODO: Implement getProtocolVersion() method. + return $this->__call__(__FUNCTION__); + } + + /** + * Return an instance with the specified HTTP protocol version. + * + * The version string MUST contain only the HTTP version number (e.g., + * "1.1", "1.0"). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new protocol version. + * + * @param string $version HTTP protocol version + * @return static + */ + public function withProtocolVersion(string $version): ResponseInterface + { + // TODO: Implement withProtocolVersion() method. + return $this->__call__(__FUNCTION__, $version); + } + + /** + * Retrieves all message header values. + * + * The keys represent the header name as it will be sent over the wire, and + * each value is an array of strings associated with the header. + * + * // Represent the headers as a string + * foreach ($message->getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(): array + { + // TODO: Implement getHeaders() method. + return $this->__call__(__FUNCTION__); + } + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader(string $name): bool + { + // TODO: Implement hasHeader() method. + return $this->__call__(__FUNCTION__, $name); + } + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader(string $name): array + { + // TODO: Implement getHeader() method. + return $this->__call__(__FUNCTION__, $name); + } + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine(string $name): string + { + // TODO: Implement getHeaderLine() method. + return $this->__call__(__FUNCTION__, $name); + } + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws InvalidArgumentException for invalid header names or values. + */ + public function withHeader(string $name, $value): ResponseInterface + { + // TODO: Implement withHeader() method. + return $this->__call__(__FUNCTION__, $name, $value); + } + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader(string $name, $value): ResponseInterface + { + // TODO: Implement withAddedHeader() method. + return $this->__call__(__FUNCTION__, $name, $value); + } + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader(string $name): ResponseInterface + { + // TODO: Implement withoutHeader() method. + return $this->__call__(__FUNCTION__, $name); + } + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(): StreamInterface + { + // TODO: Implement getBody() method. + return $this->__call__(__FUNCTION__); + } + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body): ResponseInterface + { + // TODO: Implement withBody() method. + return $this->__call__(__FUNCTION__, $body); + } + + /** + * Gets the response status code. + * + * The status code is a 3-digit integer result code of the server's attempt + * to understand and satisfy the request. + * + * @return int Status code. + */ + public function getStatusCode(): int + { + // TODO: Implement getStatusCode() method. + return $this->__call__(__FUNCTION__); + } + + /** + * Return an instance with the specified status code and, optionally, reason phrase. + * + * If no reason phrase is specified, implementations MAY choose to default + * to the RFC 7231 or IANA recommended reason phrase for the response's + * status code. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated status and reason phrase. + * + * @link http://tools.ietf.org/html/rfc7231#section-6 + * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + * @param int $code The 3-digit integer result code to set. + * @param string $reasonPhrase The reason phrase to use with the + * provided status code; if none is provided, implementations MAY + * use the defaults as suggested in the HTTP specification. + * @return static + * @throws InvalidArgumentException For invalid status code arguments. + */ + public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface + { + // TODO: Implement withStatus() method. + return $this->__call__(__FUNCTION__, $code, $reasonPhrase); + } + + /** + * Gets the response reason phrase associated with the status code. + * + * Because a reason phrase is not a required element in a response + * status line, the reason phrase value MAY be null. Implementations MAY + * choose to return the default RFC 7231 recommended reason phrase (or those + * listed in the IANA HTTP Status Code Registry) for the response's + * status code. + * + * @link http://tools.ietf.org/html/rfc7231#section-6 + * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + * @return string Reason phrase; must return an empty string if none present. + */ + public function getReasonPhrase(): string + { + // TODO: Implement getReasonPhrase() method. + return $this->__call__(__FUNCTION__); + } + + + /** + * @param object $response + * @return void + */ + public function write(object $response): void + { + $response->end($this->getBody()->getContents()); + } } diff --git a/src/Router.php b/src/Router.php index 34e7552..c1ae932 100644 --- a/src/Router.php +++ b/src/Router.php @@ -2,7 +2,213 @@ namespace Kiri\Router; +use Closure; +use Exception; +use Kiri; +use Kiri\Annotation\Route\RequestMethod; +use ReflectionException; + +const ROUTER_TYPE_HTTP = 'http'; + +/** + * + * + * $component->set([ + * 'request' => [ + * 'middlewares' => [] + * ] + * ]) + */ class Router { + const METHODS = []; + + + /** + * @var string + */ + private static string $type = ROUTER_TYPE_HTTP; + + + /** + * @param string $name + * @param Closure $closure + */ + public static function addServer(string $name, Closure $closure): void + { + static::$type = $name; + $closure(); + static::$type = ROUTER_TYPE_HTTP; + } + + + /** + * @param Closure $handler + */ + public static function jsonp(Closure $handler): void + { + static::$type = 'json-rpc'; + $handler(); + static::$type = ROUTER_TYPE_HTTP; + } + + + /** + * @param string $route + * @param string|Closure $handler + * @throws + */ + public static function post(string $route, string|Closure $handler): void + { + $router = Kiri::getDi()->get(DataGrip::class)->get(static::$type); + $router->addRoute([RequestMethod::REQUEST_POST], $route, $handler); + } + + /** + * @param string $route + * @param string|Closure $handler + * @throws + */ + public static function get(string $route, string|Closure $handler): void + { + $router = Kiri::getDi()->get(DataGrip::class)->get(static::$type); + $router->addRoute([RequestMethod::REQUEST_GET], $route, $handler); + } + + + /** + * @param string $route + * @param string|Closure $handler + * @throws + */ + public static function options(string $route, string|Closure $handler): void + { + $router = Kiri::getDi()->get(DataGrip::class)->get(static::$type); + $router->addRoute([RequestMethod::REQUEST_OPTIONS], $route, $handler); + } + + + /** + * @param string $route + * @param string|Closure $handler + * @throws + */ + public static function any(string $route, string|Closure $handler): void + { + $router = Kiri::getDi()->get(DataGrip::class)->get(static::$type); + $router->addRoute(self::METHODS, $route, $handler); + } + + /** + * @param string $route + * @param string|Closure $handler + * @throws + */ + public static function delete(string $route, string|Closure $handler): void + { + $router = Kiri::getDi()->get(DataGrip::class)->get(static::$type); + $router->addRoute([RequestMethod::REQUEST_DELETE], $route, $handler); + } + + + /** + * @param string $route + * @param string|Closure $handler + * @throws ReflectionException + */ + public static function head(string $route, string|Closure $handler): void + { + $router = Kiri::getDi()->get(DataGrip::class)->get(static::$type); + $router->addRoute([RequestMethod::REQUEST_HEAD], $route, $handler); + } + + + /** + * @param string $route + * @param string|Closure $handler + * @throws + */ + public static function put(string $route, string|Closure $handler): void + { + $router = Kiri::getDi()->get(DataGrip::class)->get(static::$type); + $router->addRoute([RequestMethod::REQUEST_PUT], $route, $handler); + } + + + /** + * @param array|RequestMethod $methods + * @param string $route + * @param array|string|Closure $handler + * @throws ReflectionException + */ + public static function addRoute(array|RequestMethod $methods, string $route, array|string|Closure $handler): void + { + $router = Kiri::getDi()->get(DataGrip::class)->get(static::$type); + if ($methods instanceof RequestMethod) { + $methods = [$methods]; + } + $router->addRoute($methods, $route, $handler); + } + + + /** + * @param array $config + * @param Closure $closure + * @throws + */ + public static function group(array $config, Closure $closure): void + { + $router = Kiri::getDi()->get(DataGrip::class)->get(static::$type); + + $router->groupTack[] = $config; + + call_user_func($closure); + + array_pop($router->groupTack); + } + + + /** + * @throws Exception + */ + public function scan_build_route(): void + { + scan_directory(APP_PATH, 'App'); + + $this->read_dir_file(APP_PATH . 'routes'); + } + + + /** + * @param $path + * @return void + * @throws Exception + */ + private function read_dir_file($path): void + { + $files = glob($path . '/*'); + for ($i = 0; $i < count($files); $i++) { + $file = $files[$i]; + if (is_dir($file)) { + $this->read_dir_file($file); + } else { + $this->resolve_file($file); + } + } + } + + + /** + * @param $files + * @throws Exception + */ + private function resolve_file($files): void + { + try { + include "$files"; + } catch (\Throwable $throwable) { + var_dump($throwable->getMessage()); + } + } } diff --git a/src/RouterCollector.php b/src/RouterCollector.php index aa7a831..ec923b8 100644 --- a/src/RouterCollector.php +++ b/src/RouterCollector.php @@ -1,34 +1,20 @@ new MethodPut() -]; - /** * */ @@ -39,41 +25,18 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate private array $_item = []; - private array $reflex = []; - - - /** - * @var LoggerInterface - */ - #[Inject(LoggerInterface::class)] - public LoggerInterface $logger; - - - private array $globalMiddlewares = []; - - public array $groupTack = []; /** - * @var array + * @var HashMap */ - private array $methods = []; + private HashMap $methods; - /** - * - */ public function __construct() { - $this->methods = [ - RequestMethod::REQUEST_DELETE->getString() => di(MethodDelete::class), - RequestMethod::REQUEST_PUT->getString() => di(MethodPut::class), - RequestMethod::REQUEST_POST->getString() => di(MethodPost::class), - RequestMethod::REQUEST_OPTIONS->getString() => di(MethodOptions::class), - RequestMethod::REQUEST_GET->getString() => di(MethodGet::class), - RequestMethod::REQUEST_HEAD->getString() => di(MethodHead::class), - ]; + $this->methods = new HashMap(); } @@ -86,178 +49,140 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate } - /** - * @return array - */ - public function getGlobalMiddlewares(): array - { - return $this->globalMiddlewares; - } - - - /** - * @param string $handler - * @return void - * @throws Exception - */ - public function addGlobalMiddlewares(string $handler): void - { - $handler = Kiri::getDi()->get($handler); - if (!in_array(MiddlewareInterface::class, class_implements($handler))) { - throw new Exception('The Middleware must instance ' . MiddlewareInterface::class); - } - $this->globalMiddlewares[] = $handler; - } - - /** * @param RequestMethod[] $method * @param string $route - * @param string|Closure|array $closure + * @param string|Closure $closure * @throws */ - public function addRoute(array $method, string $route, string|Closure|array $closure) + public function addRoute(array $method, string $route, string|Closure $closure) { try { $route = $this->_splicing_routing($route); - $middlewares = Kiri\Abstracts\Config::get('request.middlewares', []); + $interpreter = Kiri::getDi()->get(ControllerInterpreter::class); if ($closure instanceof Closure) { - $middlewares = $this->_getRouteMiddlewares($middlewares); - } else if (is_string($closure)) { - $this->_route_analysis($closure); + $handler = $interpreter->addRouteByClosure($closure); + } else { + $handler = $this->resolve($closure, $interpreter); } foreach ($method as $value) { - $this->register($route, $value->getString(), $closure, $middlewares); + if ($value instanceof RequestMethod) { + $value = $value->getString(); + } + $this->register($route, $value, $handler); } } catch (Throwable $throwable) { - $this->logger->error($throwable->getMessage(), [throwable($throwable)]); + error($throwable->getMessage(), [throwable($throwable)]); } } + /** + * @param string $closure + * @param ControllerInterpreter $interpreter + * @return Handler + * @throws ReflectionException + */ + private function resolve(string $closure, ControllerInterpreter $interpreter): Handler + { + [$className, $method] = explode('@', $closure); + + $class = Kiri::getDi()->get($this->resetName($className)); + + return $interpreter->addRouteByString($class, $method); + } + + + /** + * @param string $className + * @return string + */ + private function resetName(string $className): string + { + $namespace = array_filter(array_column($this->groupTack, 'namespace')); + if (count($namespace) < 1) { + return $className; + } + return implode('\\', $namespace) . '\\' . $className; + } + + /** * @param string $path * @param string $method - * @param $closure - * @param $middlewares + * @param Handler $handler * @return void * @throws ReflectionException */ - public function register(string $path, string $method, $closure, $middlewares): void + public function register(string $path, string $method, Handler $handler): void { - $end = $this->methods[$method]; - - $json = str_split($path, 4); - - $handler = new Handler($path, $closure, $middlewares); - foreach ($json as $item) { - /** @var TreeLeafInterface $leaf */ - $leaf = new ($end::class)($item); - $leaf->setPath($item); - if (!$end->hasLeaf()) { - $end = $end->addLeaf($item, $leaf); + $hashMap = HashMap::Tree($this->methods, $method); + foreach (str_split($path, 4) as $item) { + if ($hashMap->has($item)) { + $hashMap = $hashMap->get($item); } else { - $search = $end->searchLeaf($item); - if ($search == null) { - $end = $end->addLeaf($item, $leaf); - } else { - $end = $search; + $hashMap = new HashMap(); + $hashMap->put($item, $hashMap); + } + } + $hashMap->put('handler', $handler); + $this->registerMiddleware($path); + } + + + /** + * @param string $path + * @return void + * @throws ReflectionException + * @throws Exception + */ + public function registerMiddleware(string $path): void + { + $middlewares = array_column($this->groupTack, 'middleware'); + if (count($middlewares) > 0) { + $manager = Kiri::getDi()->get(Middleware::class); + foreach ($middlewares as $middleware) { + if (is_string($middleware)) { + $middleware = [$middleware]; + } + foreach ($middleware as $value) { + $manager->addPathMiddleware($path, $value); } } } - $end->setHandler($handler); } /** * @param string $path * @param string $method - * @return HashTree|null - * @throws ConfigException - * @throws ReflectionException + * @return Handler|null */ public function query(string $path, string $method): ?Handler { - $parent = $this->methods[$method]; - - $string = str_split($path, 4); - foreach ($string as $item) { - $parent = $parent->searchLeaf($item); + if (!$this->methods->has($method)) { + return $this->NotFundHandler($path); + } + $parent = $this->methods->get($method); + foreach (str_split($path, 4) as $item) { + $parent = $parent->get($item); if ($parent === null) { return $this->NotFundHandler($path); } } - return $parent->getHandler(); + return $parent->get('handler'); } /** * @param string $path * @return Handler - * @throws ReflectionException */ private function NotFundHandler(string $path): Handler { - $middlewares = Kiri\Abstracts\Config::get('request.middlewares', []); - - return new Handler($path, [NotFoundController::class, 'fail'], $middlewares); + return new Handler([NotFoundController::class, 'fail'], []); } - - /** - * @param string $closure - * @throws Exception - */ - private function _route_analysis(string &$closure) - { - $closure = explode('@', $closure); - $closure[0] = $this->addNamespace($closure[0]); - if (class_exists($closure[0])) { - $this->_registerMiddleware($closure[0], $closure[1]); - } - } - - - /** - * @param array $middlewares - * @return array - * @throws Exception - */ - private function _getRouteMiddlewares(array $middlewares = []): array - { - $middleware = array_column($this->groupTack, 'middleware'); - if (count($middleware = array_filter($middleware)) > 0) { - foreach ($middleware as $value) { - if (is_string($value)) { - $value = [$value]; - } - foreach ($value as $item) { - if (!in_array(MiddlewareInterface::class, class_implements($item, true))) { - throw new Exception("middleware handler must instance MiddlewareInterface::class"); - } - $middlewares[] = $item; - } - } - } - return $middlewares; - } - - - /** - * @param string $class - * @param string $method - * @return void - * @throws Exception - */ - private function _registerMiddleware(string $class, string $method): void - { - $middleware = $this->_getRouteMiddlewares(); - foreach ($middleware as $value) { - MiddlewareManager::add($class, $method, $value); - } - } - - /** * @param string $route * @return string @@ -273,115 +198,6 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate } - /** - * @param $closure - * @param $route - * @return array - * @throws ReflectionException - * @throws Exception - */ - protected function loadMiddlewares($closure, $route): array - { - $middlewares = []; - $close = new \ReflectionFunction($closure); - if (!empty($close->getClosureThis()) && env('environmental_workerId') == 0) { - $this->logger->warning('[' . $route . '] Static functions are recommended as callback functions.'); - } - $middleware = array_column($this->groupTack, 'middleware'); - $middleware = array_unique($middleware); - if (!empty($middleware = array_filter($middleware))) { - foreach ($middleware as $mi) { - if (!is_array($mi)) { - $mi = [$mi]; - } - foreach ($mi as $item) { - if (!in_array(MiddlewareInterface::class, class_implements($item))) { - throw new Exception('The Middleware must instance ' . MiddlewareInterface::class); - } - $middlewares[$item] = $item; - } - } - $middlewares = array_values($middlewares); - } - return $middlewares; - } - - - /** - * @param $class - * @return string|null - */ - protected function addNamespace($class): ?string - { - $middleware = array_column($this->groupTack, 'namespace'); - if (empty($middleware = array_filter($middleware))) { - return $class; - } - $middleware[] = $class; - return implode('\\', array_map(function ($value) { - return trim($value, '\\'); - }, $middleware)); - } - - - /** - * @param string $path - * @param string $method - * @return Handler|int|null - * @throws ReflectionException - */ - public function find(string $path, string $method): Handler|int|null - { - $dispatcher = match ($method) { - 'OPTIONS' => $this->options($path, $method), - default => $this->other($path, $method) - }; - if (is_null($dispatcher)) { - $middlewares = Kiri\Abstracts\Config::get('request.middlewares', []); - - $dispatcher = new Handler($path, [NotFoundController::class, 'fail'], $middlewares); - } else if (is_integer($dispatcher)) { - $middlewares = Kiri\Abstracts\Config::get('request.middlewares', []); - - $dispatcher = new Handler($path, [MethodErrorController::class, 'fail'], $middlewares); - } - return $dispatcher; - } - - - /** - * @param $path - * @param $method - * @return int|mixed - */ - public function other($path, $method): mixed - { - if (!isset($this->_item[$path])) { - return 404; - } - $handler = $this->_item[$path][$method] ?? null; - if (is_null($handler)) { - return 405; - } - return $handler; - } - - - /** - * @param $path - * @param $method - * @return int|mixed - */ - public function options($path, $method): mixed - { - $handler = $this->_item[$path][$method] ?? null; - if (is_null($handler)) { - return $this->_item['/*'][$method] ?? 405; - } - return $handler; - } - - /** * @param mixed $offset * @return bool diff --git a/src/Server.php b/src/Server.php index f0542b7..779fd41 100644 --- a/src/Server.php +++ b/src/Server.php @@ -1,35 +1,25 @@ emitter = $this->container->get(Emitter::class); + $this->emitter = $this->container->get(HttpResponseEmitter::class); - $exception = Config::get('exception.http', ExceptionHandlerDispatcher::class); + $exception = Kiri::service()->get('request')->exception; if (!in_array(ExceptionHandlerInterface::class, class_implements($exception))) { $exception = ExceptionHandlerDispatcher::class; } $this->exception = $this->container->get($exception); - $this->provider->on(OnBeforeWorkerStart::class, [$this, 'onStartWaite']); - $this->provider->on(OnAfterWorkerStart::class, [$this, 'onEndWaite']); - - $this->contentType = Config::get('response.format', ContentType::JSON); - $this->router = $this->dataGrip->get('http'); - } - - - /** - * @return void - */ - public function onStartWaite(): void - { - CoordinatorManager::utility(Coordinator::WORKER_START)->waite(); - } - - - /** - * @return void - */ - public function onEndWaite(): void - { - CoordinatorManager::utility(Coordinator::WORKER_START)->done(); + $this->router = $this->container->get(DataGrip::class)->get(ROUTER_TYPE_HTTP); } @@ -127,30 +89,33 @@ class Server extends AbstractServer implements OnRequestInterface $dispatcher = $this->router->query($request->server['request_uri'], $request->getMethod()); - $PsrResponse = $dispatcher->dispatch->recover($PsrRequest); + $PsrResponse = (new HttpRequestHandler([], $dispatcher))->handle($PsrRequest); } catch (\Throwable $throwable) { $this->logger->error($throwable->getMessage(), [$throwable]); $PsrResponse = $this->exception->emit($throwable, di(Constrict\Response::class)); } finally { - $this->emitter->sender($response, $PsrResponse); + $this->emitter->sender($PsrResponse, $response); } } - - + + /** * @param Request $request * @return RequestInterface * @throws Exception */ - private function initRequestAndResponse(Request $request): \Psr\Http\Message\RequestInterface + private function initRequestAndResponse(Request $request): RequestInterface { - /** @var ResponseInterface $PsrResponse */ - $PsrResponse = Context::set(ResponseInterface::class, new Psr7Response()); - $PsrResponse->withContentType($this->contentType); + /** @var \Kiri\Router\Response $response */ + $response = Kiri::service()->get('response'); - $serverRequest = (new ServerRequest())->withData($request->getContent()) - ->withServerParams($request->server) - ->withServerTarget($request) + /** @var KrcResponse $PsrResponse */ + $PsrResponse = Context::set(ResponseInterface::class, new KrcResponse()); + $PsrResponse->withContentType($response->contentType); + + $serverRequest = (new ServerRequest())->withHeaders($request->getData()) + ->withUri(Uri::parse($request)) + ->withProtocolVersion($request->server['server_protocol']) ->withCookieParams($request->cookie ?? []) ->withQueryParams($request->get) ->withUploadedFiles($request->files) diff --git a/src/ServerRequest.php b/src/ServerRequest.php index 4497930..d0c5b8a 100644 --- a/src/ServerRequest.php +++ b/src/ServerRequest.php @@ -3,6 +3,9 @@ namespace Kiri\Router; use Kiri\Di\Context; +use Kiri\Message\Stream; +use Kiri\Router\Base\ExceptionHandlerDispatcher; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; @@ -11,10 +14,19 @@ use Psr\Http\Message\UriInterface; /** * @property-read bool $isPost */ -class Request implements ServerRequestInterface +class ServerRequest implements ServerRequestInterface { + public array $middlewares = []; + + + /** + * @var string + */ + public string $exception = ExceptionHandlerDispatcher::class; + + /** * @param string $method * @param mixed ...$params @@ -175,6 +187,38 @@ class Request implements ServerRequestInterface return $this->__call__(__FUNCTION__, $name, $value); } + + /** + * @param string $data + * @return ServerRequestInterface + */ + public function withHeaders(string $data): ServerRequestInterface + { + $headers = explode("\r\n\r\n", $data); + $headers = explode("\r\n", $headers[0]); + foreach ($headers as $header) { + $keyValue = explode(': ', $header); + if (!isset($keyValue[1])) { + $keyValue[1] = ''; + } + $keyValue[1] = explode(', ', $keyValue[1]); + $this->withHeader(...$keyValue); + } + return $this; + } + + + /** + * @param string $name + * @param string|null $default + * @return string|null + */ + public function header(string $name, ?string $default = null): ?string + { + return $this->__call__(__FUNCTION__, $name, $default); + } + + /** * Return an instance with the specified header appended with the given value. * diff --git a/src/StreamResponse.php b/src/StreamResponse.php index 9ec027e..e463d62 100644 --- a/src/StreamResponse.php +++ b/src/StreamResponse.php @@ -2,7 +2,7 @@ namespace Kiri\Router; -class ChunkResponse extends Response +class StreamResponse extends Response { public int $limit; diff --git a/src/Validator/Inject/Email.php b/src/Validator/Inject/Email.php index 8d291a2..4d6b735 100644 --- a/src/Validator/Inject/Email.php +++ b/src/Validator/Inject/Email.php @@ -1,11 +1,8 @@ get(LocalService::class)->get('request'); - if ($service->isPost) { - return filter_var($service->post($name, null), FILTER_VALIDATE_EMAIL); - } else { - return filter_var($service->query($name, null), FILTER_VALIDATE_EMAIL); - } + return filter_var($class->{$name}, FILTER_VALIDATE_EMAIL); } } diff --git a/src/Validator/Inject/Ignore.php b/src/Validator/Inject/Ignore.php index 1b62e0a..4c2b325 100644 --- a/src/Validator/Inject/Ignore.php +++ b/src/Validator/Inject/Ignore.php @@ -1,6 +1,8 @@ {$name}, $this->value); } } diff --git a/src/Validator/Inject/Length.php b/src/Validator/Inject/Length.php index 2d36b9a..cadee43 100644 --- a/src/Validator/Inject/Length.php +++ b/src/Validator/Inject/Length.php @@ -1,6 +1,8 @@ {$name}) === $this->value; } } diff --git a/src/Validator/Inject/Max.php b/src/Validator/Inject/Max.php index d3c8f62..d62eb73 100644 --- a/src/Validator/Inject/Max.php +++ b/src/Validator/Inject/Max.php @@ -1,7 +1,8 @@ {$name} <= $this->value; } } diff --git a/src/Validator/Inject/MaxLength.php b/src/Validator/Inject/MaxLength.php index 01f6e28..8c88c4a 100644 --- a/src/Validator/Inject/MaxLength.php +++ b/src/Validator/Inject/MaxLength.php @@ -1,6 +1,8 @@ {$name}) <= $this->value; } } diff --git a/src/Validator/Inject/Min.php b/src/Validator/Inject/Min.php index f4c808f..3ff21f0 100644 --- a/src/Validator/Inject/Min.php +++ b/src/Validator/Inject/Min.php @@ -1,7 +1,8 @@ {$name} >= $this->value; } } diff --git a/src/Validator/Inject/MinLength.php b/src/Validator/Inject/MinLength.php index 241e4a2..3c53391 100644 --- a/src/Validator/Inject/MinLength.php +++ b/src/Validator/Inject/MinLength.php @@ -1,6 +1,8 @@ {$name}) <= $this->value; } } diff --git a/src/Validator/Inject/Must.php b/src/Validator/Inject/Must.php index dcab14b..8719fcc 100644 --- a/src/Validator/Inject/Must.php +++ b/src/Validator/Inject/Must.php @@ -1,8 +1,10 @@ {$name} === $this->value; } } diff --git a/src/Validator/Inject/NotEmpty.php b/src/Validator/Inject/NotEmpty.php index 9b53d81..aabc741 100644 --- a/src/Validator/Inject/NotEmpty.php +++ b/src/Validator/Inject/NotEmpty.php @@ -1,18 +1,21 @@ {$name}); } } diff --git a/src/Validator/Inject/NotIn.php b/src/Validator/Inject/NotIn.php index 06e4c97..dc260d0 100644 --- a/src/Validator/Inject/NotIn.php +++ b/src/Validator/Inject/NotIn.php @@ -1,6 +1,8 @@ {$name}, $this->value); } } diff --git a/src/Validator/Inject/NotNull.php b/src/Validator/Inject/NotNull.php index 898f2ed..c8e0f11 100644 --- a/src/Validator/Inject/NotNull.php +++ b/src/Validator/Inject/NotNull.php @@ -1,6 +1,8 @@ {$name} !== null; } } diff --git a/src/Validator/Inject/Phone.php b/src/Validator/Inject/Phone.php index b5d4337..2ca7b31 100644 --- a/src/Validator/Inject/Phone.php +++ b/src/Validator/Inject/Phone.php @@ -1,11 +1,8 @@ get(LocalService::class)->get('request'); - if ($service->isPost) { - $data = $service->post($name, null); - } else { - $data = $service->query($name, null); - } - return preg_match(self::REG, $data); + return preg_match(self::REG, $class->{$name}); } } diff --git a/src/Validator/Inject/Required.php b/src/Validator/Inject/Required.php index 045457f..f58940f 100644 --- a/src/Validator/Inject/Required.php +++ b/src/Validator/Inject/Required.php @@ -1,17 +1,23 @@ $name === null; } } diff --git a/src/Validator/Inject/Round.php b/src/Validator/Inject/Round.php index 45830f3..259c6f9 100644 --- a/src/Validator/Inject/Round.php +++ b/src/Validator/Inject/Round.php @@ -1,7 +1,8 @@ value) == $name; + return round($class->$name, $this->value) === $class->$name; } } diff --git a/src/Validator/Validator.php b/src/Validator/Validator.php index 3c52baf..c6044c7 100644 --- a/src/Validator/Validator.php +++ b/src/Validator/Validator.php @@ -1,8 +1,10 @@ rules[$name] = $rule; + $this->formData = $data; + } + + + /** + * @param ServerRequestInterface|ServerRequest $request + * @return Validator + */ + public function bindData(ServerRequestInterface|ServerRequest $request): static + { + if ($request->isPost) { + $data = $request->getParsedBody(); + } else { + $data = $request->getQueryParams(); + } + foreach ($data as $key => $value) { + if (method_exists($this->formData, $key)) { + $this->formData->{$key} = $value; + } + } + return $this; } @@ -34,7 +61,7 @@ class Validator public function run(): bool { foreach ($this->rules as $name => $rule) { - if (!$rule->dispatch($name)) { + if (!$rule->dispatch($this->formData, $name)) { return false; } } diff --git a/src/Validator/ValidatorMiddleware.php b/src/Validator/ValidatorMiddleware.php index 78aa15f..7e05a5a 100644 --- a/src/Validator/ValidatorMiddleware.php +++ b/src/Validator/ValidatorMiddleware.php @@ -2,7 +2,41 @@ namespace Kiri\Router\Validator; -class ValidatorMiddleware +use Kiri\Di\Context; +use Kiri\Router\Constrict\Stream; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +class ValidatorMiddleware implements MiddlewareInterface { + + /** + * @param Validator $validator + */ + public function __construct(readonly public Validator $validator) + { + } + + + /** + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $this->validator->bindData($request); + return $handler->handle($request); + } catch (\Throwable $throwable) { + /** @var ResponseInterface $response */ + $response = Context::get(ResponseInterface::class); + $response->withStatus(407)->withBody(new Stream($throwable->getMessage())); + return $response; + } + } + }