diff --git a/docs/images/UrlObject url handling explained b/docs/images/UrlObject url handling explained new file mode 100644 index 0000000..ee7c4da --- /dev/null +++ b/docs/images/UrlObject url handling explained @@ -0,0 +1 @@ +7Vlbc5s6EP41fowHIa6PidPLQzvTmWSmzwrIoKlARJZrO7++KyRsMFDbPaHNae1kjFhJu9JePn3gGV4U2w+SVPlnkVI+c510O8P3M9dFgR/ARUt2RhJ7nhFkkqV20EHwwF6oFTpWumYpXXUGKiG4YlVXmIiypInqyIiUYtMdthS8a7UiGe0JHhLC+9KvLFW5kUZueJB/pCzLG8soiE1PQZrBdiernKRi0xLhdzO8kEIo0yq2C8q18xq/mHnvR3r3C5O0VOdMeMYvbPn0gT0+foqrr3KL0HNxY4PxnfC13bBdrNo1HsikWFd2GJWKbof8Tp6a4U5/XWi/W0gTKgqq5A6GWEWenbHr3m4O3g59K8vbno6tkNgIZ3vNBydAw/rhAp+gAZ8EHMzeLQVsCZKIJKYjeF7r6N0VohSrSkv3Imhl9lpPfToWWF2J4EJ2lM1c7DheEsd9ZblSkPZWAezN6LB68a3etv4/x0AQOE7fAN2SouJ0nojiZ2ZOG0iSYQMRlM6Y5rPU6pUPefl9yiQyF9dc8OgOznJRDJ+hHRCZIRgL2QHmAnCXlplvmbmmZ3yL+BzTAXyWy75pQBBZ7U64D8S9XOsNPSpxKGqTXgUUyj3SppQU3+jCrq4UJdVrZpwfiQhnWQm3nC61Bg0QDMDz1ooLlqbayN0mZ4o+VKZwNnBUgAxwpUyprkQH7jJOVivb1ou1RwGK9gseQJ82yozjWx96ToELngpb3NN4C1GudDNZS767kyT5RtWIB1sBOzgTnXDmUchyIdkLDCGNJigdAHQmyjrKUvvp9QPwE7B3B+LhThUPPIr1OeqEpalFXSo31oW3uoBh+1QOQLVYqVb5aW3dmqz+k/qPtXqnd7pA3bIyG8YCJyGlObEVYbpFav+onDYeaJ8t1QRooc9IvTx87x/uHgWk8v2N64wkuYC0W/KaOeUAJ7S8CDqOsv3VE9lqccMuiwn6iY2iIRYzGdL4f57Z4ajjEzc6k9phZyqnBH+c2sVU/41Ru4bDtXkYvr2Spitp+p2kyf810uSFcxxEHjy6+26AkR9NVMThv8ahRuLRHD3B0QO0DkMYxV5oooF6oOsFQ6CL5n5rluP7E4UvmopyVfqNy3SU60ut/kLKBWU/MIVISXYjM45Jmij57pil1es0212YBCLFSseqTM2Lpi7iXancJfXkOd16CqN57J/J5kI0d6aqm/jNETp/gOX+ZkLXvCb+XzK6Ifp14Fm/wPSu9OtvoF/x26ZfaOj9+F/Nv0YC0pwXuM+/wh4uuvEI55osTEOvGl+JZxm6MSHTsnzmDXGtK8N6xYqJ/B7D8lrQFZzHtlzHn9dI1zy2XM684PbwA2zd1/oZG7/7AQ== \ No newline at end of file diff --git a/docs/images/UrlObject url handling explained.png b/docs/images/UrlObject url handling explained.png new file mode 100644 index 0000000..c522157 Binary files /dev/null and b/docs/images/UrlObject url handling explained.png differ diff --git a/src/PHP.php b/src/PHP.php index 3a55d35..958f553 100644 --- a/src/PHP.php +++ b/src/PHP.php @@ -208,41 +208,36 @@ public static function init(null|array|Box|BasicInitConfig $args = null): BasicI return $config; } - private static $_cached_current_url = null; - /** - * @param $refresh + * * * @return ?UrlObject */ - static function currentUrl($refresh = false) { + static function currentUrl() { if (static::isCLI() && !defined('CURRENT_URL_PRETEND_NOT_CLI')) { return null; } - if (!static::$_cached_current_url || $refresh) { - $info = static::info(); - $serv = $info->server_var; - $protocol = empty($serv['HTTPS']) || $serv['HTTPS']?'https':'http'; - $host = $serv['SERVER_NAME'] ?? null; - if (empty($host)) { - $host = $serv['HTTP_HOST'] ?? null; - } - $server_port = $serv['SERVER_PORT'] ?? null - ?intval($serv['SERVER_PORT']) - :null; + $info = static::info(); + $serv = $info->server_var; + $protocol = empty($serv['HTTPS']) || $serv['HTTPS']?'https':'http'; + $host = $serv['SERVER_NAME'] ?? null; + if (empty($host)) { + $host = $serv['HTTP_HOST'] ?? null; + } - $uri = $serv['REQUEST_URI'] ?? null; + $server_port = $serv['SERVER_PORT'] ?? null + ?intval($serv['SERVER_PORT']) + :null; - static::$_cached_current_url = static::url( - host: $host, - path: $uri, - protocol: $protocol, - port: $server_port - ); - } + $uri = $serv['REQUEST_URI'] ?? null; - return static::$_cached_current_url; + return static::url( + host: $host, + path: $uri, + protocol: $protocol, + port: $server_port + ); } public static function metaMagicSpell(string|object $ref, $spell, ...$args) { @@ -926,7 +921,7 @@ public static function isCLI(): bool { static function url( null|UrlCompatible|string|Box|array $host = null, null|Box|array|string $path = null, - null|Box|array $params = null, + null|Box|array|string $params = null, ?string $protocol = null, ?string $processor = null, ?string $port = null, diff --git a/src/basic.php b/src/basic.php index c13bfff..9000f27 100644 --- a/src/basic.php +++ b/src/basic.php @@ -780,7 +780,7 @@ function ic(?string $name = null): null|InitConfig|BasicInitConfig { function url( null|UrlCompatible|string|Box|array $host = null, null|Box|array|string $path = null, - null|Box|array $params = null, + null|Box|array|string $params = null, ?string $protocol = null, ?string $processor = null, ?string $port = null, diff --git a/src/generic/BasicProtocolProcessor.php b/src/generic/BasicProtocolProcessor.php index e54fba5..12ad0e0 100644 --- a/src/generic/BasicProtocolProcessor.php +++ b/src/generic/BasicProtocolProcessor.php @@ -2,48 +2,16 @@ namespace spaf\simputils\generic; -use spaf\simputils\interfaces\UrlCompatible; -use spaf\simputils\models\Box; -use spaf\simputils\models\UrlObject; +use spaf\simputils\attributes\markers\Deprecated; /** * @property-read ?string $protocol + * @deprecated */ -abstract class BasicProtocolProcessor extends SimpleObject { +#[Deprecated( + 'Wrong naming "protocol" instead of commonly used "scheme"', + '\spaf\simputils\generic\BasicSchemeProcessor' +)] +abstract class BasicProtocolProcessor extends BasicSchemeProcessor { - static ?string $default_protocol = null; - - /** - * Returns array of supported protocol names - * - * @return Box|string[]|array|null - */ - abstract static function supportedProtocols(); - - abstract static function parse(UrlCompatible|string $value); - - abstract static function generateForSystem(UrlObject $url): string; - abstract static function generateForUser(UrlObject $url): string; - abstract static function generateRelative(UrlObject $url): string; - - // FIX -// -// #[Property(type: 'get')] -// protected ?string $_protocol = null; -// -// function __construct(string $protocol) { -// $this->_protocol = $protocol; -// } -// -// abstract function parse(UrlCompatible|string|Box|array $value, bool $is_preparsed = false, $data = null); -// -// abstract function generateForSystem($host, $path, $params, $data): string; -// -// abstract function generateForUser($host, $path, $params, $data): string; -// -// abstract function generateRelative($host, $path, $params, $data): string; - -// function __toString(): string { -// return PHP::objToNaiveString($this, ['protocol' => $this->_protocol]);//@codeCoverageIgnore -// } } diff --git a/src/generic/BasicSchemeProcessor.php b/src/generic/BasicSchemeProcessor.php new file mode 100644 index 0000000..e973b47 --- /dev/null +++ b/src/generic/BasicSchemeProcessor.php @@ -0,0 +1,70 @@ +_protocol = $protocol; +// } +// +// abstract function parse(UrlCompatible|string|Box|array $value, bool $is_preparsed = false, $data = null); +// +// abstract function generateForSystem($host, $path, $params, $data): string; +// +// abstract function generateForUser($host, $path, $params, $data): string; +// +// abstract function generateRelative($host, $path, $params, $data): string; + +// function __toString(): string { +// return PHP::objToNaiveString($this, ['protocol' => $this->_protocol]);//@codeCoverageIgnore +// } +} diff --git a/src/models/UrlObject.php b/src/models/UrlObject.php index 8f40141..bffd886 100644 --- a/src/models/UrlObject.php +++ b/src/models/UrlObject.php @@ -3,11 +3,12 @@ namespace spaf\simputils\models; use spaf\simputils\attributes\DebugHide; +use spaf\simputils\attributes\markers\Shortcut; use spaf\simputils\attributes\Property; use spaf\simputils\exceptions\NotImplementedYet; use spaf\simputils\generic\SimpleObject; use spaf\simputils\interfaces\UrlCompatible; -use spaf\simputils\models\urls\processors\HttpProtocolProcessor; +use spaf\simputils\models\urls\processors\HttpSchemeProcessor; use spaf\simputils\PHP; use spaf\simputils\Str; use spaf\simputils\traits\ForOutputsTrait; @@ -15,12 +16,19 @@ use function explode; use function is_array; use function is_null; +use function is_numeric; use function is_string; use function preg_match; use function preg_replace; use function spaf\simputils\basic\ic; +use function spaf\simputils\basic\pd; /** + * + * **Important**: `cs` prefixed methods here are "chaining setting methods" to be used + * instead of properties of the same name. For changing multiple aspects of your URL properties + * might be not the most comfortable solution. + * * @property-read UrlCompatible|string|Box|array|null $orig Contains original value of the "host". * really useful in case of parsing of * an url. @@ -41,18 +49,263 @@ class UrlObject extends SimpleObject { use ForOutputsTrait; use RedefinableComponentTrait; - static string $default_processor = HttpProtocolProcessor::class; + + /** + * CS operation "prepend", will cause to add elements to the left of the group + */ + const CS_OP_PREPEND = 'prepend'; + + /** + * CS operation "append", will cause to add elements to the right of the group + */ + const CS_OP_APPEND = 'append'; + + /** + * CS operation "replace", will cause to replace all the elements of the group + */ + const CS_OP_REPLACE = 'replace'; + + static string $default_processor = HttpSchemeProcessor::class; static array $processors = [ - 'http' => HttpProtocolProcessor::class, - 'https' => HttpProtocolProcessor::class, + 'http' => HttpSchemeProcessor::class, + 'https' => HttpSchemeProcessor::class, ]; + /** + * Chained Setting of Protocol + * + * CS - in this context stands for `Chained Setting` + * + * This naming is done for compatibility reasons, due to possibility of `set` prefixed + * methods might collide with other frameworks "getter/setter" functionality, and cause + * unexpected behaviour. + * + * This method can be used the same way as the property, + * but allows chaining due to native functions/methods nature. + * + * @param ?string $protocol + * + * @return $this + */ + #[Shortcut('$this->protocol')] + function csProto(?string $protocol): self { + $this->protocol = $protocol; + return $this; + } + + /** + * Chained Setting of Host + * + * CS - in this context stands for `Chained Setting` + * + * This naming is done for compatibility reasons, due to possibility of `set` prefixed + * methods might collide with other frameworks "getter/setter" functionality, and cause + * unexpected behaviour. + * + * This method can be used the same way as the property, + * but allows chaining due to native functions/methods nature. + * + * @param ?string $host + * + * @return $this + */ + #[Shortcut('$this->host')] + function csHost(?string $host): self { + $this->host = $host; + return $this; + } + + /** + * Chained Setting of Port + * + * CS - in this context stands for `Chained Setting` + * + * This naming is done for compatibility reasons, due to possibility of `set` prefixed + * methods might collide with other frameworks "getter/setter" functionality, and cause + * unexpected behaviour. + * + * This method can be used the same way as the property, + * but allows chaining due to native functions/methods nature. + * + * @param ?int $port + * + * @return $this + */ + #[Shortcut('$this->port')] + function csPort(?int $port): self { + $this->port = $port; + return $this; + } + + /** + * Chained Setting of User + * + * CS - in this context stands for `Chained Setting` + * + * This naming is done for compatibility reasons, due to possibility of `set` prefixed + * methods might collide with other frameworks "getter/setter" functionality, and cause + * unexpected behaviour. + * + * This method can be used the same way as the property, + * but allows chaining due to native functions/methods nature. + * + * @param ?string $user + * + * @return $this + */ + #[Shortcut('$this->user')] + function csUser(?string $user): self { + $this->user = $user; + return $this; + } + + /** + * Chained Setting of Password + * + * CS - in this context stands for `Chained Setting` + * + * This naming is done for compatibility reasons, due to possibility of `set` prefixed + * methods might collide with other frameworks "getter/setter" functionality, and cause + * unexpected behaviour. + * + * This method can be used the same way as the property, + * but allows chaining due to native functions/methods nature. + * + * @param ?string $password + * + * @return $this + */ + #[Shortcut('$this->password')] + function csPass(?string $password): self { + $this->password = $password; + return $this; + } + + /** + * Chained Setting of Sharpy + * + * CS - in this context stands for `Chained Setting` + * + * This naming is done for compatibility reasons, due to possibility of `set` prefixed + * methods might collide with other frameworks "getter/setter" functionality, and cause + * unexpected behaviour. + * + * This method can be used the same way as the property, + * but allows chaining due to native functions/methods nature. + * + * @param ?string $sharpy + * + * @return $this + */ + #[Shortcut('$this->sharpy')] + function csSharpy(?string $sharpy): self { + $this->sharpy = $sharpy; + return $this; + } + + /** + * Chained Setting of Path + * + * CS - in this context stands for `Chained Setting` + * + * This naming is done for compatibility reasons, due to possibility of `set` prefixed + * methods might collide with other frameworks "getter/setter" functionality, and cause + * unexpected behaviour. + * + * This method can be used the same way as the property, + * but allows chaining due to native functions/methods nature. + * + * @param null|Box|array|string $path + * @param string $operation + * + * @return $this + */ + #[Shortcut('$this->path')] + function csPath(null|Box|array|string $path, string $operation = self::CS_OP_REPLACE): self { + $path_new = static::preProcessPathData($path); + $path_old = $this->path ?: PHP::box()->pathAlike(); + + $res = match ($operation) { + static::CS_OP_REPLACE => $path_new, + static::CS_OP_PREPEND => static::addPathElements($path_new, $path_old), + static::CS_OP_APPEND => static::addPathElements($path_old, $path_new), + }; + + $this->path = $res; + return $this; + } + + private static function addPathElements($to, $from) { + foreach ($from as $k => $val) { + if (is_numeric($k)) { + $to[] = $val; + } else { + $to[$k] = $val; + } + } + return $to; + } + + /** + * Chained Setting of Path + * + * CS - in this context stands for `Chained Setting` + * + * This naming is done for compatibility reasons, due to possibility of `set` prefixed + * methods might collide with other frameworks "getter/setter" functionality, and cause + * unexpected behaviour. + * + * This method can be used the same way as the property, + * but allows chaining due to native functions/methods nature. + * + * @param Box|array|null $params + * @param string $operation + * + * @return $this + */ + #[Shortcut('$this->params')] + function csParams(null|Box|array $params, string $operation = self::CS_OP_REPLACE): self { + $this->params = $this->preProcessParamsData($params); + return $this; + } + + /** + * @param UrlCompatible|string|Box|array|null $host + * @param Box|array|string|null $path + * @param Box|array|null $params + * @param string|null $protocol + * @param string|null $processor + * @param string|null $port + * @param string|null $user + * @param string|null $pass + * @param mixed ...$data + * + * @return $this + */ + function update( + null|UrlCompatible|string|Box|array $host = null, + null|Box|array|string $path = null, + null|Box|array $params = null, + ?string $protocol = null, + ?string $processor = null, + ?string $port = null, + ?string $user = null, + ?string $pass = null, + mixed ...$data, + ): static { + // MARK Proceed here! + + return $this; + } + #[Property] protected ?string $_processor = null; +// FIX After implementing multiple properties (and arrayed names) #[Property] - protected ?string $_protocol = null; + #[Property('protocol')] + protected ?string $_scheme = null; #[Property] protected ?string $_user = null; @@ -73,17 +326,43 @@ class UrlObject extends SimpleObject { #[Property] protected ?Box $_params = null; - #[Property('path')] - protected function setPath($val) { + static function preProcessPathData($val) { + $res = null; if ($val instanceof Box) { $val->pathAlike(); - $this->_path = $val; + $res = $val; } else if (is_array($val)) { - $this->_path = PHP::box($val)->pathAlike(); + $res = PHP::box($val)->pathAlike(); } else if (is_string($val)) { $val = Str::removeStarting($val, '/'); - $this->_path = PHP::box(explode('/', $val))->pathAlike(); + $res = PHP::box(explode('/', $val))->pathAlike(); + } + return $res; + } + + protected function preProcessParamsData($val) { + $res = null; + if ($val instanceof Box) { + $val->paramsAlike(); + $res = $val; + } else if (is_array($val)) { + $res = PHP::box($val)->paramsAlike(); + } else if (is_string($val)) { + // FIX Proceed here with params data parsing +// pd('FIX', $val); + $proc = $this->_processor; + /** @var HttpSchemeProcessor $proc */ + $r = $proc::parse($val, part: HttpSchemeProcessor::PART_PARAMS); + pd('FIX RES', $r); +// $val = Str::removeStarting($val, '/'); +// $res = PHP::box(explode('/', $val))->pathAlike(); } + return $res; + } + + #[Property('path')] + protected function setPathProperty($val) { + $this->_path = static::preProcessPathData($val); } #[Property] @@ -100,7 +379,7 @@ protected function getSharpy(): ?string { } #[Property('sharpy')] - protected function setSharpy($val) { + protected function setSharpyProperty($val) { $this->data['sharpy'] = $val; } @@ -157,7 +436,7 @@ function isSimilar( function __construct( null|UrlCompatible|string|Box|array $host = null, null|Box|array|string $path = null, - null|Box|array $params = null, + null|Box|array|string $params = null, ?string $protocol = null, ?string $processor = null, ?string $port = null, @@ -176,7 +455,7 @@ function __construct( $parsed = $this->parseHost($host); - $this->_protocol = $protocol ?: $parsed->get('protocol'); + $this->_scheme = $protocol ?: $parsed->get('protocol'); unset($parsed['protocol']); $this->_user = $user ?: $parsed->get('user'); @@ -210,7 +489,7 @@ function __construct( if (PHP::isArrayCompatible($path)) { $pre = PHP::box($path)->pathAlike(); } else if (is_string($path)) { - /** @var \spaf\simputils\models\Box $_def_pre */ + /** @var Box $_def_pre */ $_def_pre = $this->_processor::parse($path, part: 'path'); $ll = $_def_pre->get('path'); if ($ll) { @@ -264,11 +543,16 @@ function __construct( unset($parsed['path']); if ($params) { + + if (Str::is($params)) { + $params = $this->preProcessParamsData($params); + } + $pre_data = $pre = null; if (PHP::isArrayCompatible($params)) { $pre = PHP::box($params)->paramsAlike(); } else if (is_string($path)) { - /** @var \spaf\simputils\models\Box $_def_pre */ + /** @var Box $_def_pre */ $_def_pre = $this->_processor::parse($params, part: 'params'); $pre = PHP::box($_def_pre->get('params'))->paramsAlike(); unset($_def_pre['path']); @@ -336,257 +620,6 @@ protected function getRelative(): string { return $this->_processor::generateRelative($this); } -// -// static ?string $default_host = null; -// static ?string $default_protocol = 'https'; -// -// static Box|array $processors = [ -// 'http' => HttpProtocolProcessor::class, -// 'https' => HttpProtocolProcessor::class, -// ]; -// -// #[Property(type: 'get')] -// protected UrlCompatible|string|Box|array|null $_orig = null; -// -// #[Property(type: 'get')] -// protected UrlCompatible|string|null $_host = null; -// -// #[Property] -// protected Box|array|string|null $_path = null; -// -// #[Property] -// protected Box|array|null $_params = null; -// -// #[Property] -// protected Box|array|null $_data = null; -// -// #[Property('port')] -// protected function getPort(): ?int { -// return $this->_processor->getPort($this); -// } -// -// #[Property('port')] -// protected function setPort(?int $val) { -// $this->_processor->setPort($this, $val); -// } -// -// #[Extract(false)] -// #[DebugHide] -// protected ?BasicProtocolProcessor $_processor = null; -// -// #[Property('protocol')] -// protected function getProtocol(): string { -// return $this->processor->protocol; -// } -// -// #[Property('processor')] -// protected function getProcessor(): BasicProtocolProcessor { -// return $this->_processor; -// } -// -// #[Property('processor')] -// protected function setProcessor(BasicProtocolProcessor $val) { -// $this->_processor = $val; // @codeCoverageIgnore -// } -// -// /** -// * -// * Some info: -// * * Host can be "condensed" string containing all arguments - should be parsed -// * * Host can be everything without protocol - should be parsed -// * * Host can be just portion of URL - should be parsed -// * * Path can be path portion (array, string) + params if assoc indexes -// * * Params can contain only "get" encoded arguments -// * * Protocol just a string -// * * Processor should not be explicitly specified -// * * Data provided to the processors to incorporate/use in URLs -// * -// * All the above should be incremental, and no info should be lost. So if the params -// * can be in all 3 ($host, $path and $params) all of them have to be aggregated! -// * -// * -// * @param UrlCompatible|string|Box|array|null $host Host -// * @param Box|array|string|null $path Path -// * @param Box|array|null $params Params -// * @param string|null $protocol Protocol -// * @param string|null $processor Processor object -// * @param mixed ...$data Additional data -// */ -// function __construct( -// UrlCompatible|string|Box|array $host = null, -// Box|array|string $path = null, -// Box|array $params = null, -// string $protocol = null, -// string $processor = null, -// mixed ...$data, -// ) { -// $this->_host = null; -// $this->_path = PHP::box(); -// $this->_params = PHP::box(); -// $this->_data = PHP::box(); -// $this->_orig = $host; -// -// $this->parseHost($host, $protocol, $processor); -// $this->addPath($path); -// $this->addParams($params); -// $this->addData($data); -// $this->_path->pathAlike(); -// } -// -// protected function parseHost( -// UrlCompatible|string|Box|array $host = null, -// ?string $protocol = null, -// ?string $processor = null -// ) { -// if (!empty($host)) { -// if (is_string($host)) { -// $m = []; -// $orig_host = $host; -// preg_match('#^([a-zA-Z]?[a-zA-Z0-9_-]*):(.*)$#S', $host, $m); -// if ($m) { -// $protocol = $protocol ?? $m[1] ?? null; -// $host = $m[2] ?? null; -// } -// -// // Str::startsWith($host, '//') || (!Str::startsWith($host, 'http://') && !Str::startsWith($host, 'https://')) -// $tt = bx(static::$processors); -// if (!$tt->containsKey($protocol) || bx(['http', 'https'])->containsValue($protocol)) { -// // NOTE We need to check whether it's a valid http url without protocol part, -// // or it's something else. -// $processor_class = static::$processors['http'] ?? null; -// if (empty($processor_class)) { -// throw new Exception('HTTP Protocol does not have a proper Processor Class specified for it!'); -// } -// /** @var HttpProtocolProcessor $http_processor */ -// $protocol = empty($protocol) && $processor_class::isValid($host, $protocol)?'http':$protocol; -// -// [$protocol, $user, $pass, $host, $port] = $processor_class::preParsing($orig_host, $protocol); -// $this->data['user'] = $user; -// $this->data['pass'] = $pass; -// $this->data['port'] = $port; -// } -// -//// pd($protocol); -// // FIX Parse here! -// -// } else if (is_array($host) || $host instanceof Box) { -// $host = PHP::box($host); -// -// $this->_path->mergeFrom($host->only_numeric); -// $this->_params->mergeFrom($host->only_assoc); -// $host = null; -// } -// } -// -// $protocol = $protocol?:static::$default_protocol; -// if (is_null($protocol)) { -// $protocol = 'https'; -// } -// $this->_processor = $processor ?: static::chooseProcessor($protocol); -// -// if (!empty($host)) { -// [ -// $this->_host, -// $this->_path, -// $this->_params, -// $this->_data -// ] = $this->_processor->parse($host, true, $this->data); -// } -// } -// -// function addPath(Box|array|string|null $path) { -// if (!empty($path)) { -// $proc = $this->processor; -// -// if (is_string($path)) { -// [$_, $_path, $_params, $_data] = $proc->parse($path, false); -// if (!empty($_path)) { -// $this->_path->mergeFrom($_path); -// } -// if (!empty($_params)) { -// $this->_params->mergeFrom($_params); -// } -// if (!empty($_data)) { -// $this->_data->mergeFrom($_data); -// } -// } else { -// // NOTE Path can contain params as well. Difference is in indexes -// $path = PHP::box($path); -// -// $this->_path->mergeFrom($path->only_numeric); -// $this->_params->mergeFrom($path->only_assoc); -// } -// } -// -// // TODO Re-implement properly without creating a new object every single time -// $r = PHP::box()->pathAlike(); -// foreach ($this->_path as $item) { -// $sub = preg_replace('#\s+#S', '', $item); -// if (!empty($sub)) { -// $r[] = $sub; -// } -// } -// $this->_path = $r; -// } -// -// function addParams(Box|array|null $params) { -// if (!empty($params)) { -// $this->_params->mergeFrom($params); -// } -// -// $r = PHP::box(); -// foreach ($this->_params as $k => $v) { -// $sub = preg_replace('#\s+#S', '', $k); -// if (!empty($sub)) { -// $r[$sub] = $v; -// } -// } -// $this->_params = $r; -// } -// -// function addData(Box|array|null $data) { -// if (!empty($data)) { -// $this->_data->mergeFrom($data); -// } -// } -// -// protected static function chooseProcessor(string $protocol): BasicProtocolProcessor { -// $protocols = PHP::box(static::$processors); -// if (!$protocols->containsKey($protocol)) { -// throw new ProtocolProcessorIsUndefined( -// "No processor defined for this protocol: {$protocol}" -// ); -// } -// -// $class = $protocols[$protocol]; -// return new $class($protocol); -// } -// -// #[Property('for_system')] -// protected function getForSystem(): string { -// $host = $this->_host ?? static::$default_host ?? 'localhost'; -// return $this->_processor->generateForSystem( -// $host, $this->_path, $this->_params, $this->_data -// ); -// } -// -// #[Property('for_user')] -// protected function getForUser(): string { -// $host = $this->_host ?? static::$default_host ?? 'localhost'; -// return $this->_processor->generateForUser( -// $host, $this->_path, $this->_params, $this->_data -// ); -// } -// -// #[Property('relative')] -// protected function getRelative(): string { -// $host = $this->_host ?? static::$default_host ?? 'localhost'; -// return $this->_processor->generateRelative( -// $host, $this->_path, $this->_params, $this->_data -// ); -// } - - function setFromData($data): static { $this->__construct($data['for_system']); return $this; diff --git a/src/models/files/apps/DotEnvProcessor.php b/src/models/files/apps/DotEnvProcessor.php index 5a3176e..7bd5ad5 100644 --- a/src/models/files/apps/DotEnvProcessor.php +++ b/src/models/files/apps/DotEnvProcessor.php @@ -7,6 +7,10 @@ use spaf\simputils\generic\BasicResource; use spaf\simputils\models\files\apps\settings\DotEnvSettings; use spaf\simputils\special\dotenv\ExtTypeHint; +use spaf\simputils\Str; +use function spaf\simputils\basic\pr; +use function str_replace; +use function trim; /** * DotEnv data processor @@ -47,10 +51,16 @@ public function getContent(mixed $fd, ?BasicResource $file = null): ?array { $lines = explode("\n", $content); $res = []; foreach ($lines as $line) { + if (Str::contains($line, "\r")) { + pr("Line contains \"\\r\" symbol, trimmed."); + } + + $line = trim($line); + if (empty($line)) { continue; } - $line = trim($line); + if ($line[0] === '#') { // TODO Comment-extension processing must happen here! diff --git a/src/models/urls/processors/FileSchemeProcessor.php b/src/models/urls/processors/FileSchemeProcessor.php new file mode 100644 index 0000000..4fe7993 --- /dev/null +++ b/src/models/urls/processors/FileSchemeProcessor.php @@ -0,0 +1,35 @@ + static::$initial_parser_regexp_path, - static::PART_PARAMS => static::$initial_parser_regexp_params, - default => static::$initial_parser_regexp_full, - }; - - $m = []; - // NOTE Parsing URL - preg_match($regexp, $value, $m); - - if ($part === static::PART_FULL) { - if (!$protocol) { - $protocol = static::$default_protocol; - } - $creds = $m[1] ?? null; - $host = $m[2] ?: static::$default_host; - $port = $m[3] ?: static::$default_port; - $path = $m[4] ?? null; - $params = $m[5] ?? null; - $sharpy = $m[6] ?? null; - } else if ($part === static::PART_PATH) { - $path = $m[1] ?? null; - $params = $m[2] ?? null; - $sharpy = $m[3] ?? null; -// } else if ($part === static::PART_PARAMS) { -// pd($m, $value); -// $path = $m[1] ?? null; -// $params = $m[2] ?? null; -// $sharpy = $m[3] ?? null; - } - - if ($path) { - $path_new = PHP::box()->pathAlike(); - foreach (explode('/', $path) as $key => $val) { - if ($val) { - $path_new[$key] = $val; - } - } - $path = $path_new; - } - - - if ($creds) { - $c = []; - preg_match('#^([\w\d_-]*)(?::(.*))?#S', $creds, $c); - $user = $c[1] ?? null; - $pass = $c[2] ?? null; - } - - if ($params) { - $params_pre_parsed = PHP::box(explode('&', $params)); - } - - $params_res = PHP::box()->apply( - separator: '&', - joined_to_str: true, - stretcher: true - ); - if ($params_pre_parsed) { - foreach ($params_pre_parsed as $item) { - if ($item && Str::contains($item, '=')) { - [$key, $val] = explode('=', $item); - if ($key) { - $val = Str::contains($val, '%')?urldecode($val):$val; - - $params_res[$key] = $val; - } - } - } - $params = $params_res; - } - - if ($host && !static::$do_not_lower) { - $host = Str::lower($host); - } - - $r = PHP::box([ - 'protocol' => $protocol, - 'user' => $user, - 'pass' => $pass, - 'host' => $host, - 'port' => $port, - 'path' => $path, - 'params' => $params, - 'sharpy' => $sharpy, - ]); - return $r; - } - - private static function _stringify($url, bool $obfuscate, bool $relative) { - $cred = ''; - if ($url->user) { - $cred = "{$url->user}"; - if ($url->password) { - if ($obfuscate) { - $cred .= ":".DebugHide::$default_placeholder; - } else { - $cred .= ":{$url->password}"; - } - } - $cred .= '@'; - } - - $port = ''; - if ($url->port && $url->port !== 80 && $url->port !== 443 ) { - $port = ":{$url->port}"; - } - - $path = preg_replace('#/+#S', '/', "/{$url->path}"); - - $params = ''; - if ($url->params && count($url->params) > 0) { - $params = $url->params->clone(); - foreach ($params as $key => $val) { - $params[$key] = urlencode($val); - } - $params = "?{$params}"; - } - - $sharpy = ''; - if (!empty($url->data['sharpy']) && $url->data['sharpy']) { - $sharpy = "#{$url->data['sharpy']}"; - } - - $rel_part = "{$path}{$params}{$sharpy}"; - if ($relative) { - return $rel_part; - } - - return "{$url->protocol}://{$cred}{$url->host}{$port}{$rel_part}"; - } - - static function generateForSystem(UrlObject $url): string { - return static::_stringify($url, false, false); - } - - static function generateForUser(UrlObject $url): string { - return static::_stringify($url, true, false); - } - - static function generateRelative(UrlObject $url): string { - return static::_stringify($url, true, true); - } } diff --git a/src/models/urls/processors/HttpSchemeProcessor.php b/src/models/urls/processors/HttpSchemeProcessor.php new file mode 100644 index 0000000..6dcf144 --- /dev/null +++ b/src/models/urls/processors/HttpSchemeProcessor.php @@ -0,0 +1,215 @@ + static::$initial_parser_regexp_path, + static::PART_PARAMS => static::$initial_parser_regexp_params, + default => static::$initial_parser_regexp_full, + }; + + $m = []; + // NOTE Parsing URL + preg_match($regexp, $value, $m); + + if ($part === static::PART_FULL) { + if (!$protocol) { + $protocol = static::$default_protocol; + } + $creds = $m[1] ?? null; + $host = $m[2] ?: static::$default_host; + $port = $m[3] ?: static::$default_port; + $path = $m[4] ?? null; + $params = $m[5] ?? null; + $sharpy = $m[6] ?? null; + } else if ($part === static::PART_PATH) { + $path = $m[1] ?? null; + $params = $m[2] ?? null; + $sharpy = $m[3] ?? null; + } else if ($part === static::PART_PARAMS) { + pd('PART', $regexp, $m, $value); + $path = $m[1] ?? null; + $params = $m[2] ?? null; + $sharpy = $m[3] ?? null; + } + + if ($path) { + $path_new = PHP::box()->pathAlike(); + foreach (explode('/', $path) as $key => $val) { + if ($val) { + $path_new[$key] = $val; + } + } + $path = $path_new; + } + + + if ($creds) { + $c = []; + preg_match('#^([\w\d_-]*)(?::(.*))?#S', $creds, $c); + $user = $c[1] ?? null; + $pass = $c[2] ?? null; + } + + if ($params) { + $params_pre_parsed = PHP::box(explode('&', $params)); + } + + $params_res = PHP::box()->apply( + separator: '&', + joined_to_str: true, + stretcher: true + ); + if ($params_pre_parsed) { + foreach ($params_pre_parsed as $item) { + if ($item && Str::contains($item, '=')) { + [$key, $val] = explode('=', $item); + if ($key) { + $val = Str::contains($val, '%')?urldecode($val):$val; + + $params_res[$key] = $val; + } + } + } + $params = $params_res; + } + + if ($host && !static::$do_not_lower) { + $host = Str::lower($host); + } + + $r = PHP::box([ + 'protocol' => $protocol, + 'user' => $user, + 'pass' => $pass, + 'host' => $host, + 'port' => $port, + 'path' => $path, + 'params' => $params, + 'sharpy' => $sharpy, + ]); + return $r; + } + + private static function _stringify($url, bool $obfuscate, bool $relative) { + $cred = ''; + if ($url->user) { + $cred = "{$url->user}"; + if ($url->password) { + if ($obfuscate) { + $cred .= ":".DebugHide::$default_placeholder; + } else { + $cred .= ":{$url->password}"; + } + } + $cred .= '@'; + } + + $port = ''; + if ($url->port && $url->port !== 80 && $url->port !== 443 ) { + $port = ":{$url->port}"; + } + + $path = preg_replace('#/+#S', '/', "/{$url->path}"); + + $params = ''; + if ($url->params && count($url->params) > 0) { + $params = $url->params->clone(); + foreach ($params as $key => $val) { + $params[$key] = is_null($val) + ?'' + :urlencode($val); + } + $params = "?{$params}"; + } + + $sharpy = ''; + if (!empty($url->data['sharpy']) && $url->data['sharpy']) { + $sharpy = "#{$url->data['sharpy']}"; + } + + $rel_part = "{$path}{$params}{$sharpy}"; + if ($relative) { + return $rel_part; + } + + return "{$url->protocol}://{$cred}{$url->host}{$port}{$rel_part}"; + } + + static function generateForSystem(UrlObject $url): string { + return static::_stringify($url, false, false); + } + + static function generateForUser(UrlObject $url): string { + return static::_stringify($url, true, false); + } + + static function generateRelative(UrlObject $url): string { + return static::_stringify($url, true, true); + } + +} diff --git a/tests/general/NewFeatures202206Test.php b/tests/general/NewFeatures202206Test.php index 7062c4a..625d848 100644 --- a/tests/general/NewFeatures202206Test.php +++ b/tests/general/NewFeatures202206Test.php @@ -229,7 +229,7 @@ public function ipRangesToCheck() { /** * @covers \spaf\simputils\models\UrlObject * @covers \spaf\simputils\basic\url - * @covers \spaf\simputils\models\urls\processors\HttpProtocolProcessor + * @covers \spaf\simputils\models\urls\processors\HttpSchemeProcessor * * @uses \spaf\simputils\Boolean::from * @uses \spaf\simputils\basic\bx @@ -440,7 +440,7 @@ public function datetimePeriodToCheck() { * @uses \spaf\simputils\models\Box * @uses \spaf\simputils\models\IPv4 * @uses \spaf\simputils\models\UrlObject - * @uses \spaf\simputils\models\urls\processors\HttpProtocolProcessor + * @uses \spaf\simputils\models\urls\processors\HttpSchemeProcessor * @uses \spaf\simputils\traits\PropertiesTrait::__set * @uses \spaf\simputils\traits\PropertiesTrait::_simpUtilsGetValidator * diff --git a/tests/general/UrlsTest.php b/tests/general/UrlsTest.php index 21703b0..50de07a 100644 --- a/tests/general/UrlsTest.php +++ b/tests/general/UrlsTest.php @@ -9,7 +9,7 @@ /** * @covers \spaf\simputils\models\UrlObject - * @covers \spaf\simputils\models\urls\processors\HttpProtocolProcessor + * @covers \spaf\simputils\models\urls\processors\HttpSchemeProcessor * @covers \spaf\simputils\basic\url * @covers \spaf\simputils\generic\BasicProtocolProcessor * @@ -220,6 +220,35 @@ function dataProviderBasicsStrOnly() { // 'data' => ['sharpy' => 'ggogg'], ] ], + [ + 'http://dd:gg@localhost/p1/p2/p3?empty_param=', + [ + 'protocol' => 'http', + 'host' => 'localhost', + 'port' => 80, + 'path' => ['p1', 'p2', 'p3'], + 'user' => 'dd', + 'password' => 'gg', + 'params' => [ + 'empty_param' => null, + ], + ] + ], + [ + 'http://dd:gg@localhost/p1/p2/p3?empty_param=&another-non-empty-param=test', + [ + 'protocol' => 'http', + 'host' => 'localhost', + 'port' => 80, + 'path' => ['p1', 'p2', 'p3'], + 'user' => 'dd', + 'password' => 'gg', + 'params' => [ + 'empty_param' => null, + 'another-non-empty-param' => 'test', + ], + ] + ], [ 'https://dd:@localhost/p4/p5/p6?d1=v1&d2=v2&d3=v3', [ @@ -520,4 +549,117 @@ function testBasicsExtension() { // pd($url); } + function dataProviderAdvancedExtension() { + return [ + [ + url('/path1/path2'), + 'https://localhost/path1/path2' + ], + [ + url('my-domain'), + 'https://my-domain/' + ], + [ + url('http://my-domain'), + 'http://my-domain/' + ], + [ + url('http://my-domain/path1/path2'), + 'http://my-domain/path1/path2' + ], + [ + url('http://my-domain/path1/path2/'), + 'http://my-domain/path1/path2' + ], + [ + url('http://my-domain/path1/path2?arg1=1'), + 'http://my-domain/path1/path2?arg1=1' + ], + [ + url('http://my-domain/path1/path2?arg0=&arg1=1&arg2=2'), + 'http://my-domain/path1/path2?arg0=&arg1=1&arg2=2' + ], + [ + url($host = 'http://my-domain/path1/path2?arg0=&arg1=1&arg2=2#sch'), + 'http://my-domain/path1/path2?arg0=&arg1=1&arg2=2#sch' + ], + [ // NOTE Normally in UNIX systems first slash should make path absolute. + // So the new path value must replace the old one, and not add up + url($host, '/path-1/path0'), + 'http://my-domain/path-1/path0?arg0=&arg1=1&arg2=2#sch' + ], + [ + url($host, 'path3/path4'), + 'http://my-domain/path1/path2/path3/path4?arg0=&arg1=1&arg2=2#sch' + ], + [ // NOTE Normally in UNIX systems first slash should make path absolute. + // So the new path value must replace the old one, and not add up + url($host, ['/path-2/path-1', 'path0']), + 'http://my-domain/path-2/path-1/path0?arg0=&arg1=1&arg2=2#sch' + ], + [ + url($host, ['path3/path4', 'path5']), + 'http://my-domain/path1/path2/path3/path4?arg0=&arg1=1&arg2=2#sch' + ], + [ + url($host, ['path3', 'path4', 'arg0' => 0, 'arg1' => 10, 'arg3' => 30]), + 'http://my-domain/path1/path2/path3/path4?arg0=0&arg1=10&arg2=2&arg3=30#sch' + ], + [ + url($host, ['arg0' => 0, 'arg1' => 10, 'arg3' => 30]), + 'http://my-domain/path1/path2?arg0=0&arg1=10&arg2=2&arg3=30#sch' + ], + [ + url($host, ['path3', 'path4', 'arg0' => 'new', '#' => 'new']), + 'http://my-domain/path1/path2/path3/path4?arg0=new&arg1=1&arg2=2#new' + ], + [ + url($host, ['#' => 'new']), + 'http://my-domain/path1/path2?arg0=&arg1=1&arg2=2#new' + ], + + [ + url( + $host, + $path = ['path3', 'path4', 'arg0' => 0, 'arg1' => 10, 'arg3' => 30, '#' => 'sharpy'], + [ + 'arg0' => 'new-0', + 'arg4' => 'new-4', + ] + ), + 'http://my-domain/path1/path2/path3/path4?arg0=new-0&arg1=10&arg2=2&arg3=30&arg4=new-4#sharpy' + ], + [ + url( + $host, + $path, + [ + 'arg0' => 'new-0', + 'arg4' => 'new-4', + '#' => 'replaced-sharpy' + ] + ), + 'http://my-domain/path1/path2/path3/path4?arg0=new-0&arg1=10&arg2=2&arg3=30&arg4=new-4#replaced-sharpy' + ], + [ + url( + $host, + $path, + 'arg0=new-0&arg4=new-4#replaced-sharpy' + ), + 'http://my-domain/path1/path2/path3/path4?arg0=new-0&arg1=10&arg2=2&arg3=30&arg4=new-4#replaced-sharpy' + ], + ]; + } + + /** + * @param $url + * @param $expected + * + * @dataProvider dataProviderAdvancedExtension + * @return void + */ + function testStatusQuo202308($url, $expected) { + $this->assertEquals($expected, "{$url}"); + } } diff --git a/tests/general/bugs/UrlCommonIssuesTest.php b/tests/general/bugs/UrlCommonIssuesTest.php index 42d975d..93bb7e3 100644 --- a/tests/general/bugs/UrlCommonIssuesTest.php +++ b/tests/general/bugs/UrlCommonIssuesTest.php @@ -16,7 +16,7 @@ * @uses \spaf\simputils\components\normalizers\BooleanNormalizer * @uses \spaf\simputils\components\normalizers\StringNormalizer * @uses \spaf\simputils\models\Box - * @uses \spaf\simputils\models\urls\processors\HttpProtocolProcessor + * @uses \spaf\simputils\models\urls\processors\HttpSchemeProcessor * @uses \spaf\simputils\special\CodeBlocksCacheIndex * @uses \spaf\simputils\traits\PropertiesTrait */