I had a conversation with a developer working on this project and they suggested that I create a URL helper class to help formulate what this should look like. I'm not necessarily married to how this is built, but needed a place to put this for now.
code
```php
$parameters
*/
public function __construct(
protected array $parameters = []
) {
}
/**
* Builds a new query parameters object from a string.
*
* @param string $query
* @return QueryParameters
*/
public static function fromString(string $query = '') : QueryParameters
{
if ('' === $query) {
return new QueryParameters();
}
$parameters = [];
parse_str($query, $parameters);
$parameters = array_map(static fn ($param) => '' !== $param ? $param : null, $parameters);
return new QueryParameters($parameters);
}
/**
* Gets a query parameter by key.
*
* @param string $key
* @param mixed|null $default
* @return mixed
*/
public function get(string $key, mixed $default = null) : mixed
{
return Arr::get($this->parameters, $key, $default);
}
/**
* Determines if a query parameter exists.
*
* @param string $key
* @return bool
*/
public function has(string $key) : bool
{
return Arr::has($this->parameters, $key);
}
/**
* Gets the count of the current query parameters.
*
* @return int
*/
public function count() : int
{
return count($this->parameters);
}
/**
* Adds a query parameter.
*
* @param string $key
* @param mixed $value
* @return $this
*/
public function add(string $key, mixed $value) : QueryParameters
{
$this->parameters[$key] = $value;
return $this;
}
/**
* Adds many query parameters.
*
* @param array $parameters
* @return $this
*/
public function addMany(array $parameters) : QueryParameters
{
foreach ($parameters as $key => $value) {
$this->add($key, $value);
}
return $this;
}
/**
* Removes one or more query parameters.
*
* @param string|string[] $value
* @return $this
*/
public function remove(array|string $value) : QueryParameters
{
if (is_array($value)) {
foreach ($value as $key) {
unset($this->parameters[$key]);
}
} else {
/* @phpstan-ignore-next-line */
unset($this->parameters[$value]);
}
return $this;
}
/**
* Gets all the query parameters as an array.
*
* @return array
*/
public function toArray() : array
{
return $this->parameters;
}
/**
* Gets all the query parameters as a string.
*
* @return string
*/
public function toString() : string
{
return trim(http_build_query($this->parameters, '', '&', PHP_QUERY_RFC3986));
}
/**
* Implements the magic method to convert the query parameters to a string.
*
* @return string
*/
public function __toString() : string
{
return $this->toString();
}
}
parseUrl($url);
}
/**
* Parses a URL string to class properties.
*
* @param string $url
* @return void
* @throws InvalidUrlException|InvalidUrlSchemeException
*/
protected function parseUrl(string $url) : void
{
if (! $parts = parse_url($url)) {
throw new InvalidUrlException(sprintf('Invalid URL: %s', $url));
}
$scheme = Arr::get($parts, 'scheme', '');
$this->scheme = ! empty($scheme) ? $this->sanitizeScheme($scheme) : '';
$port = Arr::get($parts, 'port');
$this->port = is_numeric($port) ? (int) $port : null;
$this->host = Arr::get($parts, 'host', '');
$this->path = Arr::get($parts, 'path', '/');
$this->queryParameters = QueryParameters::fromString(Arr::get($parts, 'query', ''));
$this->fragment = Arr::get($parts, 'fragment', '');
}
/**
* Builds a new URL from a string.
*
* @param string $url
* @return Url
* @throws InvalidUrlException|InvalidUrlSchemeException
*/
public static function fromString(string $url) : Url
{
return new Url($url);
}
/**
* Gets valid schemes.
*
* @return string[]
*/
protected function getValidSchemes() : array
{
return [
static::SCHEME_HTTP,
static::SCHEME_HTTPS,
];
}
/**
* Sanitizes and validates a scheme.
*
* @param string $scheme
* @return string
* @throws InvalidUrlSchemeException
*/
public function sanitizeScheme(string $scheme) : string
{
$scheme = strtolower($scheme);
if (! in_array($scheme, $this->getValidSchemes(), true)) {
throw new InvalidUrlSchemeException(sprintf('Invalid scheme: %s', $scheme));
}
return $scheme;
}
/**
* Gets the scheme.
*
* @return string
*/
public function getScheme() : string
{
return $this->scheme;
}
/**
* Gets the authority.
*
* @return string
*/
public function getAuthority() : string
{
$authority = $this->host;
if (null !== $this->port) {
$authority .= ':'.$this->port;
}
return $authority;
}
/**
* Gets the host.
*
* @return string
*/
public function getHost() : string
{
return $this->host;
}
/**
* Gets the port.
*
* @return int|null
*/
public function getPort() : ?int
{
return $this->port;
}
/**
* Gets the path.
*
* @return string
*/
public function getPath() : string
{
return $this->path;
}
/**
* Gets the query parameters.
*
* @return QueryParameters|null
*/
public function getQueryParameters() : ?QueryParameters
{
return $this->queryParameters;
}
/**
* Gets a query parameter value by key.
*
* @param string $key query parameter key
* @param mixed|null $default optional return value, defaults to null
* @return mixed|null
*/
public function getQueryParameter(string $key, mixed $default = null) : mixed
{
return $this->queryParameters ? $this->queryParameters->get($key, $default) : $default;
}
/**
* Gets the query.
*
* @return string
*/
public function getQuery() : string
{
return $this->queryParameters ? $this->queryParameters->toString() : '';
}
/**
* Gets the fragment.
*
* @return string|null
*/
public function getFragment() : ?string
{
return $this->fragment;
}
/**
* Sets the scheme.
*
* @param string $value
* @return $this
* @throws InvalidUrlSchemeException
*/
public function setScheme(string $value) : Url
{
$this->scheme = $this->sanitizeScheme($value);
return $this;
}
/**
* Sets the host.
*
* @param string $value
* @return $this
*/
public function setHost(string $value) : Url
{
$this->host = $value;
return $this;
}
/**
* Sets the port.
*
* @param int $value
* @return $this
*/
public function setPort(int $value) : Url
{
$this->port = $value;
return $this;
}
/**
* Sets the path.
*
* @param string $value
* @return Url
*/
public function setPath(string $value) : Url
{
$this->path = $value;
return $this;
}
/**
* Sets the query parameters.
*
* @param QueryParameters $value
* @return $this
*/
public function setQueryParameters(QueryParameters $value) : Url
{
$this->queryParameters = $value;
return $this;
}
/**
* Determines if the URL has query parameters set and are not empty.
*
* @return bool
*/
public function hasQueryParameters() : bool
{
return null !== $this->queryParameters && '' !== $this->queryParameters->toString();
}
/**
* Add a query parameter.
*
* @param string $key
* @param mixed $value
* @return $this
*/
public function addQueryParameter(string $key, $value) : Url
{
if (! $this->queryParameters) {
$this->queryParameters = new QueryParameters();
}
$this->queryParameters->add($key, $value);
return $this;
}
/**
* Adds query parameters.
*
* @param array $parameters
* @return $this
*/
public function addQueryParameters(array $parameters) : Url
{
if (! $this->queryParameters) {
$this->queryParameters = new QueryParameters();
}
$this->queryParameters->addMany($parameters);
return $this;
}
/**
* Removes a query parameter.
*
* @param string $key
* @return $this
*/
public function removeQueryParameter(string $key) : Url
{
if (! $this->queryParameters) {
return $this;
}
$this->queryParameters->remove($key);
return $this;
}
/**
* Removes query parameters.
*
* @param string[] $keys
* @return $this
*/
public function removeQueryParameters(array $keys) : Url
{
if (! $this->queryParameters) {
return $this;
}
$this->queryParameters->remove($keys);
return $this;
}
/**
* Sets the fragment.
*
* @param string $fragment
* @return $this
*/
public function setFragment(string $fragment) : Url
{
$this->fragment = $fragment;
return $this;
}
/**
* Converts the URL object to a URL string.
*
* @return string
*/
public function toString() : string
{
$url = '';
if ('' !== $schema = $this->getScheme()) {
$url .= $schema.'://';
}
if ('' !== $auth = $this->getAuthority()) {
$url .= $auth;
}
if ('/' !== $path = $this->getPath()) {
$url .= $path;
}
if ('' !== $query = $this->getQuery()) {
$url .= '?'.$query;
}
if ('' !== $fragment = $this->getFragment()) {
$url .= '#'.$fragment;
}
return $url;
}
/**
* Implements the magic method to convert the URL to a string.
*
* @return string
*/
public function __toString() : string
{
return $this->toString();
}
}
```
While I think having a URL helper of some sort makes a lot of sense (currently this is being handled by the individual integrations, and can get a little messy.) I'm wondering where this should actually go.
I don't think it makes sense to be in Rest, since it's not really a RESTful thing, specifically.
I don't think it should be a utility, because the class isn't just a utility class with static methods, although I guess neither is Array Processor.
This makes me wonder if it makes more sense to create a new package called HTTP that holds this helper.
For context, however, other things like Request and Response are actually just interfaces that get implemented at the integration level, so it's entirely possible that this should use that pattern, too - where the Url implementation is somehow constructed from a series of interfaces that is implemented using the integration. This would help keep this flexible and also align with existing patterns in PHPNomad.
I had a conversation with a developer working on this project and they suggested that I create a URL helper class to help formulate what this should look like. I'm not necessarily married to how this is built, but needed a place to put this for now.
code
```php $parameters */ public function __construct( protected array $parameters = [] ) { } /** * Builds a new query parameters object from a string. * * @param string $query * @return QueryParameters */ public static function fromString(string $query = '') : QueryParameters { if ('' === $query) { return new QueryParameters(); } $parameters = []; parse_str($query, $parameters); $parameters = array_map(static fn ($param) => '' !== $param ? $param : null, $parameters); return new QueryParameters($parameters); } /** * Gets a query parameter by key. * * @param string $key * @param mixed|null $default * @return mixed */ public function get(string $key, mixed $default = null) : mixed { return Arr::get($this->parameters, $key, $default); } /** * Determines if a query parameter exists. * * @param string $key * @return bool */ public function has(string $key) : bool { return Arr::has($this->parameters, $key); } /** * Gets the count of the current query parameters. * * @return int */ public function count() : int { return count($this->parameters); } /** * Adds a query parameter. * * @param string $key * @param mixed $value * @return $this */ public function add(string $key, mixed $value) : QueryParameters { $this->parameters[$key] = $value; return $this; } /** * Adds many query parameters. * * @param arrayWhile I think having a URL helper of some sort makes a lot of sense (currently this is being handled by the individual integrations, and can get a little messy.) I'm wondering where this should actually go.
This makes me wonder if it makes more sense to create a new package called HTTP that holds this helper.
For context, however, other things like
Request
andResponse
are actually just interfaces that get implemented at the integration level, so it's entirely possible that this should use that pattern, too - where theUrl
implementation is somehow constructed from a series of interfaces that is implemented using the integration. This would help keep this flexible and also align with existing patterns in PHPNomad.