diff --git a/.gitignore b/.gitignore index f629cd8..2cb622c 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ ._* .DS_Store .sass-cache -vendor \ No newline at end of file +composer.lock +vendor +.idea/ \ No newline at end of file diff --git a/Interfaces/ClientExceptionInterface.php b/Interfaces/ClientExceptionInterface.php deleted file mode 100755 index d70524a..0000000 --- a/Interfaces/ClientExceptionInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -getQuery()` - * or from the `QUERY_STRING` server param. - * - * @return array - */ - public function getQueryParams(); - - /** - * Return an instance with the specified query string arguments. - * - * These values SHOULD remain immutable over the course of the incoming - * request. They MAY be injected during instantiation, such as from PHP's - * $_GET superglobal, or MAY be derived from some other value such as the - * URI. In cases where the arguments are parsed from the URI, the data - * MUST be compatible with what PHP's parse_str() would return for - * purposes of how duplicate query parameters are handled, and how nested - * sets are handled. - * - * Setting query string arguments MUST NOT change the URI stored by the - * request, nor the values in the server params. - * - * 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 query string arguments. - * - * @param array $query Array of query string arguments, typically from - * $_GET. - * @return static - */ - public function withQueryParams(array $query); - - /** - * Retrieve normalized file upload data. - * - * This method returns upload metadata in a normalized tree, with each leaf - * an instance of Psr\Http\Message\UploadedFileInterface. - * - * These values MAY be prepared from $_FILES or the message body during - * instantiation, or MAY be injected via withUploadedFiles(). - * - * @return array An array tree of UploadedFileInterface instances; an empty - * array MUST be returned if no data is present. - */ - public function getUploadedFiles(); - - /** - * Create a new instance with the specified uploaded files. - * - * 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 body parameters. - * - * @param array $uploadedFiles An array tree of UploadedFileInterface instances. - * @return static - * @throws \InvalidArgumentException if an invalid structure is provided. - */ - public function withUploadedFiles(array $uploadedFiles); - - /** - * Retrieve any parameters provided in the request body. - * - * If the request Content-Type is either application/x-www-form-urlencoded - * or multipart/form-data, and the request method is POST, this method MUST - * return the contents of $_POST. - * - * Otherwise, this method may return any results of deserializing - * the request body content; as parsing returns structured content, the - * potential types MUST be arrays or objects only. A null value indicates - * the absence of body content. - * - * @return null|array|object The deserialized body parameters, if any. - * These will typically be an array or object. - */ - public function getParsedBody(); - - /** - * Return an instance with the specified body parameters. - * - * These MAY be injected during instantiation. - * - * If the request Content-Type is either application/x-www-form-urlencoded - * or multipart/form-data, and the request method is POST, use this method - * ONLY to inject the contents of $_POST. - * - * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of - * deserializing the request body content. Deserialization/parsing returns - * structured data, and, as such, this method ONLY accepts arrays or objects, - * or a null value if nothing was available to parse. - * - * As an example, if content negotiation determines that the request data - * is a JSON payload, this method could be used to create a request - * instance with the deserialized parameters. - * - * 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 body parameters. - * - * @param null|array|object $data The deserialized body data. This will - * typically be in an array or object. - * @return static - * @throws \InvalidArgumentException if an unsupported argument type is - * provided. - */ - public function withParsedBody($data); - - /** - * Retrieve attributes derived from the request. - * - * The request "attributes" may be used to allow injection of any - * parameters derived from the request: e.g., the results of path - * match operations; the results of decrypting cookies; the results of - * deserializing non-form-encoded message bodies; etc. Attributes - * will be application and request specific, and CAN be mutable. - * - * @return array Attributes derived from the request. - */ - public function getAttributes(); - - /** - * 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. - * - * @see getAttributes() - * @param string $name The attribute name. - * @param mixed $default Default value to return if the attribute does not exist. - * @return mixed - */ - public function getAttribute($name, $default = null); - - /** - * Return an instance with the specified derived request attribute. - * - * This method allows setting a single derived request attribute as - * described in getAttributes(). - * - * 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 attribute. - * - * @see getAttributes() - * @param string $name The attribute name. - * @param mixed $value The value of the attribute. - * @return static - */ - public function withAttribute($name, $value); - - /** - * Return an instance that removes the specified derived request attribute. - * - * This method allows removing a single derived request attribute as - * described in getAttributes(). - * - * 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 attribute. - * - * @see getAttributes() - * @param string $name The attribute name. - * @return static - */ - public function withoutAttribute($name); -} diff --git a/Interfaces/StreamInterface.php b/Interfaces/StreamInterface.php deleted file mode 100755 index 1e7cda1..0000000 --- a/Interfaces/StreamInterface.php +++ /dev/null @@ -1,170 +0,0 @@ - - * [user-info@]host[:port] - * - * - * If the port component is not set or is the standard port for the current - * scheme, it SHOULD NOT be included. - * - * @see https://tools.ietf.org/html/rfc3986#section-3.2 - * @return string The URI authority, in "[user-info@]host[:port]" format. - */ - public function getAuthority(); - - /** - * Retrieve the user information component of the URI. - * - * If no user information is present, this method MUST return an empty - * string. - * - * If a user is present in the URI, this will return that value; - * additionally, if the password is also present, it will be appended to the - * user value, with a colon (":") separating the values. - * - * The trailing "@" character is not part of the user information and MUST - * NOT be added. - * - * @return string The URI user information, in "username[:password]" format. - */ - public function getUserInfo(); - - /** - * Retrieve the host component of the URI. - * - * If no host is present, this method MUST return an empty string. - * - * The value returned MUST be normalized to lowercase, per RFC 3986 - * Section 3.2.2. - * - * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 - * @return string The URI host. - */ - public function getHost(); - - /** - * Retrieve the port component of the URI. - * - * If a port is present, and it is non-standard for the current scheme, - * this method MUST return it as an integer. If the port is the standard port - * used with the current scheme, this method SHOULD return null. - * - * If no port is present, and no scheme is present, this method MUST return - * a null value. - * - * If no port is present, but a scheme is present, this method MAY return - * the standard port for that scheme, but SHOULD return null. - * - * @return int|null The URI port - */ - public function getPort(); - - /** - * Retrieve the path component of the URI. - * - * The path can either be empty or absolute (starting with a slash) or - * rootless (not starting with a slash). Implementations MUST support all - * three syntaxes. - * - * Normally, the empty path "" and absolute path "/" are considered equal as - * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically - * do this normalization because in contexts with a trimmed base path, e.g. - * the front controller, this difference becomes significant. It's the task - * of the user to handle both "" and "/". - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.3. - * - * As an example, if the value should include a slash ("/") not intended as - * delimiter between path segments, that value MUST be passed in encoded - * form (e.g., "%2F") to the instance. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.3 - * @return string The URI path. - */ - public function getPath(); - - /** - * Retrieve the query string of the URI. - * - * If no query string is present, this method MUST return an empty string. - * - * The leading "?" character is not part of the query and MUST NOT be - * added. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.4. - * - * As an example, if a value in a key/value pair of the query string should - * include an ampersand ("&") not intended as a delimiter between values, - * that value MUST be passed in encoded form (e.g., "%26") to the instance. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.4 - * @return string The URI query string. - */ - public function getQuery(); - - /** - * Retrieve the fragment component of the URI. - * - * If no fragment is present, this method MUST return an empty string. - * - * The leading "#" character is not part of the fragment and MUST NOT be - * added. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.5. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.5 - * @return string The URI fragment. - */ - public function getFragment(); - - /** - * Return an instance with the specified scheme. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified scheme. - * - * Implementations MUST support the schemes "http" and "https" case - * insensitively, and MAY accommodate other schemes if required. - * - * An empty scheme is equivalent to removing the scheme. - * - * @param string $scheme The scheme to use with the new instance. - * @return static A new instance with the specified scheme. - * @throws \InvalidArgumentException for invalid or unsupported schemes. - */ - public function withScheme(string $scheme); - - /** - * Return an instance with the specified user information. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified user information. - * - * Password is optional, but the user information MUST include the - * user; an empty string for the user is equivalent to removing user - * information. - * - * @param string $user The user name to use for authority. - * @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); - - /** - * Return an instance with the specified host. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified host. - * - * An empty host value is equivalent to removing the host. - * - * @param string $host The hostname to use with the new instance. - * @return static A new instance with the specified host. - * @throws \InvalidArgumentException for invalid hostnames. - */ - public function withHost(string $host); - - /** - * Return an instance with the specified port. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified port. - * - * Implementations MUST raise an exception for ports outside the - * established TCP and UDP port ranges. - * - * A null value provided for the port is equivalent to removing the port - * information. - * - * @param null|int $port The port to use with the new instance; a null value - * removes the port information. - * @return static A new instance with the specified port. - * @throws \InvalidArgumentException for invalid ports. - */ - public function withPort(?int $port); - - /** - * Return an instance with the specified path. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified path. - * - * The path can either be empty or absolute (starting with a slash) or - * rootless (not starting with a slash). Implementations MUST support all - * three syntaxes. - * - * If the path is intended to be domain-relative rather than path relative then - * it must begin with a slash ("/"). Paths not starting with a slash ("/") - * are assumed to be relative to some base path known to the application or - * consumer. - * - * Users can provide both encoded and decoded path characters. - * Implementations ensure the correct encoding as outlined in getPath(). - * - * @param string $path The path to use with the new instance. - * @return static A new instance with the specified path. - * @throws \InvalidArgumentException for invalid paths. - */ - public function withPath(string $path); - - /** - * Return an instance with the specified query string. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified query string. - * - * Users can provide both encoded and decoded query characters. - * Implementations ensure the correct encoding as outlined in getQuery(). - * - * An empty query string value is equivalent to removing the query string. - * - * @param string $query The query string to use with the new instance. - * @return static A new instance with the specified query string. - * @throws \InvalidArgumentException for invalid query strings. - */ - public function withQuery(string $query); - - /** - * Return an instance with the specified URI fragment. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified URI fragment. - * - * Users can provide both encoded and decoded fragment characters. - * Implementations ensure the correct encoding as outlined in getFragment(). - * - * An empty fragment value is equivalent to removing the fragment. - * - * @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); - - /** - * Return the string representation as a URI reference. - * - * Depending on which components of the URI are present, the resulting - * string is either a full URI or relative reference according to RFC 3986, - * Section 4.1. The method concatenates the various components of the URI, - * using the appropriate delimiters: - * - * - If a scheme is present, it MUST be suffixed by ":". - * - If an authority is present, it MUST be prefixed by "//". - * - The path can be concatenated without delimiters. But there are two - * cases where the path has to be adjusted to make the URI reference - * valid as PHP does not allow to throw an exception in __toString(): - * - If the path is rootless and an authority is present, the path MUST - * be prefixed by "/". - * - If the path is starting with more than one "/" and no authority is - * present, the starting slashes MUST be reduced to one. - * - If a query is present, it MUST be prefixed by "?". - * - If a fragment is present, it MUST be prefixed by "#". - * - * @see http://tools.ietf.org/html/rfc3986#section-4.1 - * @return string - */ - public function __toString(); - - - /** - * - * Custom methods outside of PSR - * But is used by the framework - * - */ - - /** - * Get dir - * @return string (ex: http/https) - */ - public function getDir(): string; - - /** - * Get formated URI - * @return string - */ - public function getUri(): string; - - /** - * Return part if object found and has not yet been encoded - * @param string $key - * @return string|null - */ - public function getPart(string $key): ?string; - - /** - * Argv can be used with CLI command - * E.g.: new Http\Uri($response->getUriEnv(["argv" => $argv])); - * @return array - */ - public function getArgv(): array; -} diff --git a/README.md b/README.md index 3d2c332..ff80ea6 100755 --- a/README.md +++ b/README.md @@ -30,9 +30,7 @@ composer require maplephp/http To create a server request, use the `ServerRequest` class: ```php -use MaplePHP\Http\ServerRequest; -use MaplePHP\Http\Uri; -use MaplePHP\Http\Environment; +use MaplePHP\Http\Environment;use MaplePHP\Http\ServerRequest;use MaplePHP\Http\Uri; // Create an environment instance (wraps $_SERVER) $env = new Environment(); @@ -93,8 +91,7 @@ $newRequest = $request->withAttribute('user_id', 123); Create a response using the `Response` class: ```php -use MaplePHP\Http\Response; -use MaplePHP\Http\Stream; +use MaplePHP\Http\Response;use MaplePHP\Http\Stream; // Create a stream for the response body $body = new Stream('php://temp', 'rw'); @@ -245,8 +242,7 @@ Send HTTP requests using the built-in HTTP client. #### Sending a Request ```php -use MaplePHP\Http\Client; -use MaplePHP\Http\Request; + // Init request client $client = new Http\Client([CURLOPT_HTTPAUTH => CURLAUTH_DIGEST]); // Pass on Curl options diff --git a/composer.json b/composer.json index 22f390c..f231484 100644 --- a/composer.json +++ b/composer.json @@ -1,10 +1,10 @@ { "name": "maplephp/http", - "version": "v1.2.3", "type": "library", - "description": "MaplePHP/Http is a powerful and easy-to-use PHP library that fully supports the PSR-7 HTTP message interfaces.", + "description": "MaplePHP/Http is a powerful and easy-to-use PHP library that fully supports the PSR-7 HTTP message and PSR-18: HTTP Client.", "keywords": [ - "psr7", + "PSR-7", + "PSR-18", "http", "message", "http message", @@ -28,16 +28,21 @@ } ], "require": { - "php": ">=8.0", - "maplephp/dto": "^3.0" + "php": ">=8.2", + "psr/http-message": "^2.0", + "psr/http-client": "^1.0", + "maplephp/dto": "^3.1" }, "require-dev": { - "maplephp/unitary": "^1.0" + "maplephp/unitary": "^2.0" }, "autoload": { "psr-4": { - "MaplePHP\\Http\\": "" + "MaplePHP\\Http\\": "src" } }, - "minimum-stability": "dev" + "minimum-stability": "dev", + "scripts": { + "unitary": "php vendor/bin/unitary" + } } diff --git a/Client.php b/src/Client.php similarity index 96% rename from Client.php rename to src/Client.php index b5267af..59b6a06 100755 --- a/Client.php +++ b/src/Client.php @@ -4,14 +4,14 @@ namespace MaplePHP\Http; -use MaplePHP\Http\Interfaces\RequestInterface; -use MaplePHP\Http\Interfaces\ResponseInterface; -use MaplePHP\Http\Interfaces\ClientInterface; -use MaplePHP\Http\Interfaces\StreamInterface; +use InvalidArgumentException; use MaplePHP\Http\Exceptions\ClientException; -use MaplePHP\Http\Exceptions\RequestException; use MaplePHP\Http\Exceptions\NetworkException; -use InvalidArgumentException; +use MaplePHP\Http\Exceptions\RequestException; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; class Client implements ClientInterface { @@ -126,7 +126,7 @@ protected function prepareRequest(RequestInterface $request): void $this->setOption(CURLOPT_RETURNTRANSFER, true); // Default auth option if get user name - if (!$this->hasOption(CURLOPT_HTTPAUTH) && !is_null($request->getUri()->getPart("user"))) { + if (!$this->hasOption(CURLOPT_HTTPAUTH) && $request->getUri()->getPart("user") !== null) { $this->setOption(CURLOPT_HTTPAUTH, static::DEFAULT_AUTH); } diff --git a/Cookies.php b/src/Cookies.php similarity index 100% rename from Cookies.php rename to src/Cookies.php diff --git a/Dir.php b/src/Dir.php similarity index 93% rename from Dir.php rename to src/Dir.php index 9fd5d69..ac7b2f5 100755 --- a/Dir.php +++ b/src/Dir.php @@ -4,8 +4,8 @@ namespace MaplePHP\Http; -use MaplePHP\Http\Interfaces\DirInterface; use MaplePHP\Http\Interfaces\DirHandlerInterface; +use MaplePHP\Http\Interfaces\DirInterface; class Dir implements DirInterface { @@ -57,7 +57,7 @@ public function getRoot(string $path = ""): string */ public function getLogs(string $path = ""): string { - if(!is_null($this->handler)) { + if ($this->handler !== null) { return $this->handler->getLogs($path); } return $this->getRoot("storage/logs/" . $path); @@ -72,7 +72,7 @@ public function getLogs(string $path = ""): string */ public function __call($method, $args): mixed { - if (!is_null($this->handler) && method_exists($this->handler, $method)) { + if ($this->handler !== null && method_exists($this->handler, $method)) { return call_user_func_array([$this->handler, $method], $args); } else { throw new \BadMethodCallException("The method ({$method}) does not exist in \"".__CLASS__."\" (DirInterface or DirHandlerInterface).", 1); diff --git a/Env.php b/src/Env.php similarity index 98% rename from Env.php rename to src/Env.php index 9e4ac0a..540da9c 100755 --- a/Env.php +++ b/src/Env.php @@ -4,7 +4,6 @@ namespace MaplePHP\Http; -use InvalidArgumentException; use MaplePHP\DTO\Format; class Env @@ -16,7 +15,7 @@ class Env public function __construct(?string $file = null) { - if (!is_null($file) && is_file($file)) { + if ($file !== null && is_file($file)) { $this->loadEnvFile($file); } } diff --git a/Environment.php b/src/Environment.php similarity index 81% rename from Environment.php rename to src/Environment.php index 31e8337..29e07c0 100755 --- a/Environment.php +++ b/src/Environment.php @@ -2,8 +2,8 @@ namespace MaplePHP\Http; -use MaplePHP\Http\Interfaces\EnvironmentInterface; use MaplePHP\DTO\Format; +use MaplePHP\Http\Interfaces\EnvironmentInterface; class Environment implements EnvironmentInterface { @@ -17,6 +17,7 @@ public function __construct(array $env = []) /** * Get request/server environment data + * * @param string $key Server key * @param string|null $default Default value, returned if Env data is empty * @return string @@ -27,8 +28,24 @@ public function get(string $key, ?string $default = ""): string return ($this->env[$key] ?? (string)$default); } + /** + * Set a server environment value + * + * @param string $key + * @param mixed $value + * @return $this + */ + public function set(string $key, mixed $value): self + { + $inst = clone $this; + $key = strtoupper($key); + $inst->env[$key] = $value; + return $inst; + } + /** * Check if environment data exists + * * @param string $key Server key * @return boolean */ @@ -38,7 +55,8 @@ public function has($key): bool } /** - * Return all env + * Return all server environments + * * @return array */ public function fetch(): array @@ -47,7 +65,8 @@ public function fetch(): array } /** - * Get URI enviment Part data that will be passed to UriInterface and match to public object if exists. + * Get URI environment Part data that will be passed to UriInterface and match to public object if exists. + * * @return array */ public function getUriParts(array $add = []): array @@ -64,18 +83,17 @@ public function getUriParts(array $add = []): array if (!is_numeric($arr['port'])) { $arr['port'] = (int)$arr['port']; } - - $arr = array_merge($arr, $add); - return $arr; + return array_merge($arr, $add); } /** * Build and return URI Path from environment + * * @return string */ public function getPath(): string { - if (is_null($this->path)) { + if ($this->path === null) { $basePath = ''; $requestName = Format\Str::value($this->get("SCRIPT_NAME"))->getUrlPath()->get(); $requestDir = dirname($requestName); diff --git a/Exceptions/ClientException.php b/src/Exceptions/ClientException.php similarity index 79% rename from Exceptions/ClientException.php rename to src/Exceptions/ClientException.php index c5ba2da..20d1dc0 100755 --- a/Exceptions/ClientException.php +++ b/src/Exceptions/ClientException.php @@ -2,8 +2,8 @@ namespace MaplePHP\Http\Exceptions; -use MaplePHP\Http\Interfaces\ClientExceptionInterface; use Exception; +use Psr\Http\Client\ClientExceptionInterface; /** * Every HTTP client related exception MUST implement this interface. diff --git a/Exceptions/NetworkException.php b/src/Exceptions/NetworkException.php similarity index 100% rename from Exceptions/NetworkException.php rename to src/Exceptions/NetworkException.php diff --git a/Exceptions/RequestException.php b/src/Exceptions/RequestException.php similarity index 100% rename from Exceptions/RequestException.php rename to src/Exceptions/RequestException.php diff --git a/Headers.php b/src/Headers.php similarity index 98% rename from Headers.php rename to src/Headers.php index ed95553..fdf09af 100755 --- a/Headers.php +++ b/src/Headers.php @@ -106,7 +106,7 @@ public function normalizeKey(string $key, bool $preserveCase = false): string */ final public static function getGlobalHeaders($skip = false): array { - //if(is_null(static::$getGlobalHeaders)) { + //if(static::$getGlobalHeaders === null) { if (!$skip && function_exists("getallheaders")) { static::$getGlobalHeaders = getallheaders(); } else { diff --git a/Interfaces/CookiesInterface.php b/src/Interfaces/CookiesInterface.php similarity index 100% rename from Interfaces/CookiesInterface.php rename to src/Interfaces/CookiesInterface.php diff --git a/Interfaces/DirHandlerInterface.php b/src/Interfaces/DirHandlerInterface.php similarity index 99% rename from Interfaces/DirHandlerInterface.php rename to src/Interfaces/DirHandlerInterface.php index 09a43af..148eaaa 100755 --- a/Interfaces/DirHandlerInterface.php +++ b/src/Interfaces/DirHandlerInterface.php @@ -1,4 +1,5 @@ version)) { + if ($this->version === null) { $prot = explode("/", ($this->env['SERVER_PROTOCOL'] ?? "HTTP/1.1")); $this->version = end($prot); } @@ -38,7 +37,7 @@ public function getProtocolVersion() * @param string $version * @return static */ - public function withProtocolVersion($version) + public function withProtocolVersion(string $version): MessageInterface { $inst = clone $this; $inst->version = $version; @@ -67,12 +66,13 @@ public function getHeader($name): array /** * Check is a header exists - * @param string $name Header name/key (case insensitive) + * @param string $name Header name/key (case-insensitive) * @return boolean + * @throws RequestException */ public function hasHeader($name): bool { - if (is_null($this->headers)) { + if ($this->headers === null) { throw new RequestException("Missing The HTTP Headers instance", 1); } return $this->headers->hasHeader($name); @@ -80,10 +80,10 @@ public function hasHeader($name): bool /** * Get header value line - * @param string $name name/key (case insensitive) + * @param string $name name/key (case-insensitive) * @return string */ - public function getHeaderLine($name) + public function getHeaderLine($name): string { $data = $this->getHeaderLineData($name); /* @@ -181,7 +181,7 @@ public function getBody(): StreamInterface * @param StreamInterface $body * @return static */ - public function withBody(StreamInterface $body) + public function withBody(StreamInterface $body): MessageInterface { $inst = clone $this; $inst->body = $body; diff --git a/Request.php b/src/Request.php similarity index 87% rename from Request.php rename to src/Request.php index 6e9e374..e2890d7 100755 --- a/Request.php +++ b/src/Request.php @@ -2,10 +2,10 @@ namespace MaplePHP\Http; -use MaplePHP\Http\Interfaces\RequestInterface; -use MaplePHP\Http\Interfaces\UriInterface; use MaplePHP\Http\Interfaces\HeadersInterface; -use MaplePHP\Http\Interfaces\StreamInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriInterface; class Request extends Message implements RequestInterface { @@ -27,6 +27,9 @@ public function __construct( $this->uri = is_string($uri) ? new Uri($uri) : $uri; $this->headers = is_array($headers) ? new Headers($headers) : $headers; $this->body = $this->resolveRequestStream($body); + if ($this->env === null) { + $this->env = new Environment(); + } $this->setHostHeader(); } @@ -118,11 +121,11 @@ public function isSSL(): bool public function getPort(): int { $serverPort = $this->env->get("SERVER_PORT"); - return (int)(($serverPort) ? $serverPort : $this->uri->getPort()); + return (int)(((int)$serverPort > 0) ? $serverPort : $this->uri->getPort()); } /** - * Set host header if missing or overwrite if custom is set. + * Set the host header if missing or overwrite if custom is set. * @return void */ final protected function setHostHeader(): void @@ -146,7 +149,7 @@ private function resolveRequestStream(StreamInterface|array|string|null $body): $body = http_build_query($body); } $stream = new Stream(Stream::TEMP); - if (!is_null($body)) { + if ($body !== null) { $stream->write($body); $stream->rewind(); } @@ -160,7 +163,8 @@ private function resolveRequestStream(StreamInterface|array|string|null $body): */ public function getCliKeyword(): ?string { - if (is_null($this->cliKeywords)) { + if ($this->cliKeywords === null) { + $new = []; $arg = $this->getUri()->getArgv(); foreach ($arg as $val) { @@ -179,13 +183,26 @@ public function getCliKeyword(): ?string return $this->cliKeywords; } + /** + * With new CLI keyword + * + * @param string $cliKeywords + * @return $this + */ + public function withCliKeyword(string $cliKeywords): self + { + $inst = clone $this; + $inst->cliKeywords = trim($cliKeywords); + return $inst; + } + /** * Get Cli arguments * @return array */ public function getCliArgs(): array { - if (is_null($this->cliArgs)) { + if ($this->cliArgs === null) { $args = $this->getUri()->getArgv(); $this->cliArgs = []; foreach ($args as $arg) { diff --git a/Response.php b/src/Response.php similarity index 94% rename from Response.php rename to src/Response.php index 976f510..98cda59 100755 --- a/Response.php +++ b/src/Response.php @@ -4,9 +4,9 @@ namespace MaplePHP\Http; -use MaplePHP\Http\Interfaces\ResponseInterface; -use MaplePHP\Http\Interfaces\StreamInterface; use MaplePHP\Http\Interfaces\HeadersInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; class Response extends Message implements ResponseInterface { @@ -88,12 +88,12 @@ public function __construct( ) { $this->body = $body; $this->statusCode = $status; - $this->headers = is_null($headers) ? new Headers() : $headers; - $this->body = $body; - if (!is_null($version)) { + $this->headers = $headers === null ? new Headers() : $headers; + //$this->body = $body; + if ($version !== null) { $this->version = $version; } - if (!is_null($phrase)) { + if ($phrase !== null) { $this->phrase = $phrase; } } @@ -116,7 +116,7 @@ public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterf * Get current response status code * @return int */ - public function getStatusCode() + public function getStatusCode(): int { return $this->statusCode; } @@ -125,9 +125,9 @@ public function getStatusCode() * Get current response status phrase * @return string */ - public function getReasonPhrase() + public function getReasonPhrase(): string { - if (is_null($this->phrase)) { + if ($this->phrase === null) { $this->phrase = ($this::PHRASE[$this->statusCode] ?? ""); } return $this->phrase; @@ -230,7 +230,7 @@ public function location(string $url, int $statusCode = 302): void */ public function createHeaders(): void { - if (is_null($this->hasHeadersInit)) { + if ($this->hasHeadersInit === null) { $this->hasHeadersInit = true; foreach ($this->getHeaders() as $key => $_unusedVal) { $value = $this->getHeaderLine($key); diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php new file mode 100644 index 0000000..a1454a0 --- /dev/null +++ b/src/ResponseFactory.php @@ -0,0 +1,41 @@ +stream = ($stream === null) ? new Stream(Stream::TEMP) : $stream; + $this->headers = $headers; + } + + /** + * Create a new response instance. + * + * @param int $code HTTP status code + * @param string|null $reasonPhrase Optional reason phrase + * @param string|null $version HTTP version (e.g., '1.1') + * @return ResponseInterface + */ + public function createResponse( + int $code = 200, + ?string $reasonPhrase = null, + ?string $version = null + ): ResponseInterface { + return new Response($this->stream, $this->headers, $code, $reasonPhrase, $version); + } +} diff --git a/ServerRequest.php b/src/ServerRequest.php similarity index 98% rename from ServerRequest.php rename to src/ServerRequest.php index ef68bc4..0286576 100755 --- a/ServerRequest.php +++ b/src/ServerRequest.php @@ -2,10 +2,9 @@ namespace MaplePHP\Http; -use MaplePHP\Http\Interfaces\ServerRequestInterface; -use MaplePHP\Http\Interfaces\UriInterface; use MaplePHP\Http\Interfaces\EnvironmentInterface; -use MaplePHP\Http\Stream; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\UriInterface; class ServerRequest extends Request implements ServerRequestInterface { @@ -101,7 +100,7 @@ public function withCookieParams(array $cookies): self */ public function getQueryParams(): array { - if (is_null($this->queryParams)) { + if ($this->queryParams === null) { parse_str($this->getUri()->getQuery(), $this->queryParams); } return $this->queryParams; @@ -188,7 +187,7 @@ public function withUploadedFiles(array $uploadedFiles): self */ public function getParsedBody(): null|array|object { - if (is_null($this->parsedBody) && $this->getMethod() === "POST") { + if ($this->parsedBody === null && $this->getMethod() === "POST") { $header = $this->getHeader('Content-Type'); $contents = (string)$this->getBody(); switch (($header[0] ?? null)) { diff --git a/Stream.php b/src/Stream.php similarity index 88% rename from Stream.php rename to src/Stream.php index f6e0454..f4da6f3 100755 --- a/Stream.php +++ b/src/Stream.php @@ -4,8 +4,8 @@ namespace MaplePHP\Http; +use Psr\Http\Message\StreamInterface; use RuntimeException; -use MaplePHP\Http\Interfaces\StreamInterface; class Stream implements StreamInterface { @@ -35,12 +35,13 @@ class Stream implements StreamInterface /** * PSR-7 Stream - * @param mixed $stream - * @param string $permission Default stream permission is r+ + * + * @param mixed $stream + * @param string $permission The default stream permission is r+ */ public function __construct(mixed $stream = null, string $permission = "r+") { - if (is_null($stream)) { + if ($stream === null) { $stream = $this::DEFAULT_WRAPPER; } @@ -48,7 +49,7 @@ public function __construct(mixed $stream = null, string $permission = "r+") $this->resource = $stream; $this->meta = $this->getMetadata(); /* - if (is_null($this->meta)) { + if ($this->meta === null) { throw new RuntimeException("Could not access the stream metadata.", 1); } */ @@ -147,7 +148,7 @@ public function stats(?string $key = null): mixed { $stats = fstat($this->resource); if (is_array($stats)) { - return is_null($key) ? $stats : ($stats[$key] ?? false); + return $key === null ? $stats : ($stats[$key] ?? false); } return false; } @@ -174,15 +175,40 @@ public function tell(): int } /** - * Gets line from file pointer + * Gets line from a file pointer * @return string|false */ - public function getLine(): string|bool + public function getLine(): string|false { - $line = fgets($this->resource); - return trim($line); + return fgets($this->resource); } + /** + * Will get output between a from and to line number + * + * @param int $from + * @param int $to + * @return string + */ + public function getLines(int $from, int $to): string + { + $this->rewind(); + $lineNo = 0; + $out = ''; + while (($line = $this->getLine()) !== false) { + ++$lineNo; + if ($lineNo < $from) { + continue; + } + if ($lineNo > $to) { + break; + } + $out .= $line; + } + return $out; + } + + /** * Returns true if the stream is at the end of the stream. * @return bool @@ -241,10 +267,10 @@ public function rewind(): void */ public function write(string $string): int { - if (is_null($this->size)) { + if ($this->size === null) { $this->size = 0; } - return fwrite($this->resource, $string); + return (int)fwrite($this->resource, $string); } /** @@ -284,7 +310,7 @@ public function getMetadata(?string $key = null): mixed $this->readable = (bool)preg_match(self::READABLE_MATCH, $this->meta['mode']); $this->writable = (bool)preg_match(self::WRITABLE_MATCH, $this->meta['mode']); $this->seekable = $this->meta['seekable']; - return (!is_null($key) ? ($this->meta[$key] ?? null) : $this->meta); + return ($key !== null ? ($this->meta[$key] ?? null) : $this->meta); } /** diff --git a/UploadedFile.php b/src/UploadedFile.php similarity index 94% rename from UploadedFile.php rename to src/UploadedFile.php index b1e813a..7300e59 100755 --- a/UploadedFile.php +++ b/src/UploadedFile.php @@ -4,9 +4,9 @@ namespace MaplePHP\Http; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UploadedFileInterface; use RuntimeException; -use MaplePHP\Http\Interfaces\UploadedFileInterface; -use MaplePHP\Http\Interfaces\StreamInterface; class UploadedFile implements UploadedFileInterface { @@ -42,7 +42,7 @@ class UploadedFile implements UploadedFileInterface public function __construct(StreamInterface|array|string $stream, mixed ...$vars) { - if(count($vars) > 0 && is_string($stream)) { + if (count($vars) > 0 && is_string($stream)) { array_unshift($vars, $stream); $stream = array_combine(['name', 'type', 'tmp_name', 'error', 'size'], $vars); } @@ -68,7 +68,7 @@ public function __construct(StreamInterface|array|string $stream, mixed ...$vars */ public function getStream(): StreamInterface { - if (is_null($this->stream)) { + if ($this->stream === null) { throw new RuntimeException("The no stream exists. You need to construct a new stream", 1); } if (is_string($this->stream)) { @@ -91,9 +91,9 @@ public function moveTo($targetPath): void throw new RuntimeException('Target directory is not writable'); } - if (!is_null($this->stream)) { + if ($this->stream !== null) { $this->streamFile($targetPath); - } elseif (!is_null($this->tmp)) { + } elseif ($this->tmp !== null) { $this->moveUploadedFile($targetPath); } @@ -148,7 +148,7 @@ public function moveUploadedFile(string $targetPath) */ public function getSize(): ?int { - return (is_null($this->size)) ? (($this->stream instanceof StreamInterface) ? $this->stream->getSize() : null) : $this->size; + return ($this->size === null) ? (($this->stream instanceof StreamInterface) ? $this->stream->getSize() : null) : $this->size; } /** diff --git a/Uri.php b/src/Uri.php similarity index 93% rename from Uri.php rename to src/Uri.php index b5f3306..e477c2f 100755 --- a/Uri.php +++ b/src/Uri.php @@ -4,8 +4,8 @@ namespace MaplePHP\Http; -use MaplePHP\Http\Interfaces\UriInterface; use MaplePHP\DTO\Format; +use Psr\Http\Message\UriInterface; class Uri implements UriInterface { @@ -25,14 +25,13 @@ class Uri implements UriInterface private $parts = []; private $scheme; - //private $uri; private $host; private $port; private $user; private $pass; private $path; private $query; - private $fragment; // Anchor/after hash + private $fragment; private $dir; private $rootDir; private $userInfo; @@ -41,7 +40,6 @@ class Uri implements UriInterface private $encoded; private $build; - /** * URI in parts * @param array|string $uri @@ -94,7 +92,7 @@ public function getScheme(): string /** * Get dir - * @return string (ex: http/https) + * @return string */ public function getDir(): string { @@ -104,6 +102,19 @@ public function getDir(): string return (string)$this->encoded['dir']; } + /** + * With new DIR + * + * @param string $dir + * @return $this + */ + public function withDir(string $dir): self + { + $inst = clone $this; + $inst->setPart("dir", $dir); + return $inst; + } + /** * Get dir * @return string (ex: http/https) @@ -122,7 +133,7 @@ public function getRootDir(): string */ public function getAuthority(): string { - if (is_null($this->authority)) { + if ($this->authority === null) { $this->authority = ""; if (($host = $this->getHost()) && ($userInfo = $this->getUserInfo())) { @@ -143,7 +154,7 @@ public function getAuthority(): string */ public function getUserInfo(): string { - if (is_null($this->userInfo)) { + if ($this->userInfo === null) { $this->userInfo = ""; if ($user = $this->getUniquePart("user")) { $this->encoded['user'] = $user; @@ -154,7 +165,7 @@ public function getUserInfo(): string if (is_string($user) && !empty($user)) { $this->userInfo .= "{$user}"; - if (!is_null($pass)) { + if ($pass !== null) { $this->userInfo .= ":{$pass}"; } } @@ -169,7 +180,7 @@ public function getUserInfo(): string public function getHost(): string { if ($val = $this->getUniquePart("host")) { - if(($pos = strpos($val, ":")) !== false) { + if (($pos = strpos($val, ":")) !== false) { $val = substr($val, 0, $pos); } $this->encoded['host'] = Format\Str::value($val)->tolower()->get(); @@ -197,7 +208,7 @@ public function getPort(): ?int */ public function getDefaultPort(): ?int { - if (is_null($this->port) && !is_null($this->scheme)) { + if ($this->port === null && $this->scheme !== null) { $this->port = ($this::DEFAULT_PORTS[$this->getScheme()] ?? null); } if ($val = $this->getUniquePart("port")) { @@ -217,7 +228,7 @@ public function getPath(): string ->normalizeUrlEncoding() ->replace(['%2F'], ['/']) ->get(); - if($this->encoded['path']) { + if ($this->encoded['path']) { $this->encoded['path'] = "/".ltrim($this->encoded['path'], "/"); } } @@ -257,7 +268,7 @@ public function getFragment(): string */ public function getUri(): string { - if (is_null($this->build)) { + if ($this->build === null) { $this->build = ""; if ($scheme = $this->getScheme()) { $this->build .= "{$scheme}:"; @@ -393,7 +404,7 @@ public function withUriParts(array $parts): self */ private function getUniquePart(string $key): string|int|float|null { - return (!is_null($this->{$key}) && is_null($this->encoded[$key])) ? $this->{$key} : null; + return ($this->{$key} !== null && $this->encoded[$key] === null) ? $this->{$key} : null; } /** @@ -416,7 +427,7 @@ private function fillParts(): void foreach ($vars as $key => $_valueNotUsed) { $this->encoded[$key] = null; $part = ($this->parts[$key] ?? null); - if (!is_null($part)) { + if ($part !== null) { $this->{$key} = $part; } } diff --git a/Url.php b/src/Url.php similarity index 93% rename from Url.php rename to src/Url.php index 9ccdedc..307531a 100755 --- a/Url.php +++ b/src/Url.php @@ -4,10 +4,10 @@ namespace MaplePHP\Http; -use MaplePHP\Http\Interfaces\UrlInterface; use MaplePHP\Http\Interfaces\UrlHandlerInterface; -use MaplePHP\Http\Interfaces\UriInterface; -use MaplePHP\Http\Interfaces\RequestInterface; +use MaplePHP\Http\Interfaces\UrlInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\UriInterface; class Url implements UrlInterface { @@ -55,7 +55,7 @@ public function withType(null|string|array $type = null): self if (is_string($type)) { $type = [$type]; } - if (is_null($type)) { + if ($type === null) { $type = []; } @@ -111,7 +111,7 @@ public function add(array|string $arr): self if (is_string($arr)) { $arr = [$arr]; } - if (is_null($inst->vars)) { + if ($inst->vars === null) { $inst->vars = $inst->getVars(); } $inst->vars = array_merge($inst->vars, $arr); @@ -125,7 +125,7 @@ public function add(array|string $arr): self */ public function getVars(): array { - if (is_null($this->vars)) { + if ($this->vars === null) { $this->vars = explode("/", $this->realPath); } return $this->vars; @@ -146,7 +146,7 @@ public function getParts(): array */ public function getRealPath(): string { - if (is_null($this->realPath)) { + if ($this->realPath === null) { $this->realPath = str_replace(rtrim($this->getDirPath(), "/"), "", $this->uri->getPath()); } if (!is_string($this->realPath)) { @@ -161,7 +161,7 @@ public function getRealPath(): string */ public function getDirPath(): string { - if (is_null($this->dirPath)) { + if ($this->dirPath === null) { // Absolute path to the "httpdocs" directory (document root) $documentRoot = (isset($_SERVER['DOCUMENT_ROOT'])) ? $_SERVER['DOCUMENT_ROOT'] : ""; $documentRoot = htmlspecialchars($documentRoot, ENT_QUOTES, 'UTF-8'); @@ -183,7 +183,7 @@ public function getDirPath(): string public function getDirPathOLD(): string { - if (is_null($this->dirPath)) { + if ($this->dirPath === null) { $root = (isset($_SERVER['DOCUMENT_ROOT'])) ? $_SERVER['DOCUMENT_ROOT'] : ""; $root = htmlspecialchars($root, ENT_QUOTES, 'UTF-8'); $this->dirPath = str_replace($root, "", $this->request->getUri()->getDir()); @@ -225,7 +225,7 @@ public function current(): string */ public function last(): string { - if (is_null($this->vars)) { + if ($this->vars === null) { $this->vars = $this->getVars(); } return end($this->vars); @@ -237,7 +237,7 @@ public function last(): string */ public function first(): string { - if (is_null($this->vars)) { + if ($this->vars === null) { $this->vars = $this->getVars(); } return reset($this->vars); @@ -249,7 +249,7 @@ public function first(): string */ public function prev(): string { - if (is_null($this->vars)) { + if ($this->vars === null) { $this->end(); } return prev($this->vars); @@ -261,7 +261,7 @@ public function prev(): string */ public function next(): string { - if (is_null($this->vars)) { + if ($this->vars === null) { $this->reset(); } return next($this->vars); @@ -303,7 +303,7 @@ public function getRootDir(string $path = "", bool $endSlash = false): string public function getRoot(string $path = "", bool $endSlash = false): string { $url = $this->getRootDir("/"); - if (!is_null($this->handler)) { + if ($this->handler !== null) { $url .= $this->handler->getPublicDirPath(); } $url = rtrim($url, '/'); @@ -346,7 +346,7 @@ public function setHandler(UrlHandlerInterface $handler): void */ public function __call($method, $args): mixed { - if (!is_null($this->handler) && method_exists($this->handler, $method)) { + if ($this->handler !== null && method_exists($this->handler, $method)) { return call_user_func_array([$this->handler, $method], $args); } elseif (method_exists($this->uri, $method)) { return call_user_func_array([$this->uri, $method], $args); diff --git a/tests/unitary-request.php b/tests/unitary-request.php index 1319ae2..191f767 100644 --- a/tests/unitary-request.php +++ b/tests/unitary-request.php @@ -1,31 +1,37 @@ case("MaplePHP Request URI path test", function() { - $request = new MaplePHP\Http\Request( +$unit->case("MaplePHP Request URI path test", function(TestCase $case) { + + $request = new Request( "POST", // The HTTP Method (GET, POST, PUT, DELETE, PATCH) "https://admin:mypass@example.com:65535/test.php?id=5221&place=stockholm", // The Request URI ["Content-Type" => "application/x-www-form-urlencoded"], // Add Headers, empty array is allowed ["email" => "john.doe@example.com"] // Post data ); - $this->add($request->getMethod(), function() { - return $this->equal("POST"); - - }, "HTTP Request method Type is not POST"); - // Adding an error message is not required, but it is highly recommended + $case + ->error("HTTP Request method is not POST") + ->validate($request->getMethod(), function(Expect $inst) { + $inst->isEqualTo("POST"); + //assert($inst->isEqualTo("GET")->isValid(), "wdqwwdqw dwq wqdwq"); + }); - $this->add($request->getUri()->getPort(), [ - "isInt" => [], // Has no arguments = empty array - "min" => [1], // Strict way is to pass each argument to array - "max" => 65535, // But if its only one argument then this it is acceptable - "length" => [1, 5] - - ], "Is not a valid port number"); + $case->validate($request->getUri()->getPort(), function(Expect $inst) { + $inst->isInt(); + $inst->min(1); + $inst->max(65535); + $inst->length(1, 5); + }); $this->add($request->getUri()->getUserInfo(), [ "isString" => [], @@ -36,7 +42,9 @@ ], "Is not a valid port number"); - $this->add((string)$request->withUri(new \MaplePHP\Http\Uri("https://example.se"))->getUri(), [ + $this->add((string)$request->withUri(new Uri("https://example.se"))->getUri(), [ "equal" => ["https://example.se"], ], "GetUri expects https://example.se as result"); -}); \ No newline at end of file +}); + +return $unit; \ No newline at end of file diff --git a/tests/unitaryRequest.php b/tests/unitaryRequest.php new file mode 100644 index 0000000..73e104c --- /dev/null +++ b/tests/unitaryRequest.php @@ -0,0 +1,48 @@ +case("MaplePHP Request URI path test", function(TestCase $inst) { + + $request = new Request( + "POST", // The HTTP Method (GET, POST, PUT, DELETE, PATCH) + "https://admin:mypass@example.com:65535/test.php?id=5221&place=stockholm", // The Request URI + ["Content-Type" => "application/x-www-form-urlencoded"], // Add Headers, empty array is allowed + ["email" => "john.doe@example.com"] // Post data + ); + + $inst->add($request->getMethod(), function() { + return $this->equal("GET"); + + }, "HTTP Request method Type is not POST"); + // Adding an error message is not required, but it is highly recommended + + $this->add($request->getUri()->getPort(), [ + "isInt" => [], // Has no arguments = empty array + "min" => [1], // Strict way is to pass each argument to array + "max" => 65535, // But if its only one argument then this it is acceptable + "length" => [1, 5] + + ], "Is not a valid port number"); + + $this->add($request->getUri()->getUserInfo(), [ + "isString" => [], + "User validation" => function($value) { + $arr = explode(":", $value); + return ($this->withValue($arr[0])->equal("admin") && $this->withValue($arr[1])->equal("mypass")); + } + + ], "Is not a valid port number"); + + $this->add((string)$request->withUri(new \MaplePHP\Http\Uri("https://example.se"))->getUri(), [ + "equal" => ["https://example.se"], + ], "GetUri expects https://example.se as result"); +}); \ No newline at end of file