<?phpnamespaceStorSquare\Quote\Helpers\Url;
usePHPNomad\Utils\Helpers\Arr;
/** * Object representation of a URL query parameters set. */class QueryParameters
{
/** * Constructor. * * @param array<string, mixed> $parameters */publicfunction__construct(
protectedarray$parameters = []
) {
}
/** * Builds a new query parameters object from a string. * * @param string $query * @return QueryParameters */publicstaticfunctionfromString(string$query = '') : QueryParameters
{
if ('' === $query) {
returnnewQueryParameters();
}
$parameters = [];
parse_str($query, $parameters);
$parameters = array_map(staticfn ($param) => '' !== $param ? $param : null, $parameters);
returnnewQueryParameters($parameters);
}
/** * Gets a query parameter by key. * * @param string $key * @param mixed|null $default * @return mixed */publicfunctionget(string$key, mixed$default = null) : mixed
{
return Arr::get($this->parameters, $key, $default);
}
/** * Determines if a query parameter exists. * * @param string $key * @return bool */publicfunctionhas(string$key) : bool
{
return Arr::has($this->parameters, $key);
}
/** * Gets the count of the current query parameters. * * @return int */publicfunctioncount() : int
{
returncount($this->parameters);
}
/** * Adds a query parameter. * * @param string $key * @param mixed $value * @return $this */publicfunctionadd(string$key, mixed$value) : QueryParameters
{
$this->parameters[$key] = $value;
return$this;
}
/** * Adds many query parameters. * * @param array<string, mixed> $parameters * @return $this */publicfunctionaddMany(array$parameters) : QueryParameters
{
foreach ($parametersas$key => $value) {
$this->add($key, $value);
}
return$this;
}
/** * Removes one or more query parameters. * * @param string|string[] $value * @return $this */publicfunctionremove(array|string$value) : QueryParameters
{
if (is_array($value)) {
foreach ($valueas$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<string, mixed> */publicfunctiontoArray() : array
{
return$this->parameters;
}
/** * Gets all the query parameters as a string. * * @return string */publicfunctiontoString() : string
{
returntrim(http_build_query($this->parameters, '', '&', PHP_QUERY_RFC3986));
}
/** * Implements the magic method to convert the query parameters to a string. * * @return string */publicfunction__toString() : string
{
return$this->toString();
}
}
<?php/** * Exception thrown when a URL scheme is invalid. */class InvalidUrlSchemeException extends InvalidUrlException
{
}<?phpuseException;
/** * Exception thrown when a URL is invalid. */class InvalidUrlException extends Exception
{
}<?phpusePHPNomad\Utils\Helpers\Arr;
/** * Object representation of a URL. */class Url
{
publicconstSCHEME_HTTP = 'http';
publicconstSCHEME_HTTPS = 'https';
protectedstring$scheme = '';
protectedstring$host = '';
protected ?int$port = null;
protectedstring$path = '/';
protectedstring$fragment = '';
protected ?QueryParameters$queryParameters = null;
/** * Constructor. * * @param string|null $url * @throws InvalidUrlException|InvalidUrlSchemeException */publicfunction__construct(?string$url = null)
{
if (null === $url) {
return;
}
$this->parseUrl($url);
}
/** * Parses a URL string to class properties. * * @param string $url * @return void * @throws InvalidUrlException|InvalidUrlSchemeException */protectedfunctionparseUrl(string$url) : void
{
if (! $parts = parse_url($url)) {
thrownewInvalidUrlException(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 */publicstaticfunctionfromString(string$url) : Url
{
returnnewUrl($url);
}
/** * Gets valid schemes. * * @return string[] */protectedfunctiongetValidSchemes() : array
{
return [
static::SCHEME_HTTP,
static::SCHEME_HTTPS,
];
}
/** * Sanitizes and validates a scheme. * * @param string $scheme * @return string * @throws InvalidUrlSchemeException */publicfunctionsanitizeScheme(string$scheme) : string
{
$scheme = strtolower($scheme);
if (! in_array($scheme, $this->getValidSchemes(), true)) {
thrownewInvalidUrlSchemeException(sprintf('Invalid scheme: %s', $scheme));
}
return$scheme;
}
/** * Gets the scheme. * * @return string */publicfunctiongetScheme() : string
{
return$this->scheme;
}
/** * Gets the authority. * * @return string */publicfunctiongetAuthority() : string
{
$authority = $this->host;
if (null !== $this->port) {
$authority .= ':'.$this->port;
}
return$authority;
}
/** * Gets the host. * * @return string */publicfunctiongetHost() : string
{
return$this->host;
}
/** * Gets the port. * * @return int|null */publicfunctiongetPort() : ?int
{
return$this->port;
}
/** * Gets the path. * * @return string */publicfunctiongetPath() : string
{
return$this->path;
}
/** * Gets the query parameters. * * @return QueryParameters|null */publicfunctiongetQueryParameters() : ?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 */publicfunctiongetQueryParameter(string$key, mixed$default = null) : mixed
{
return$this->queryParameters ? $this->queryParameters->get($key, $default) : $default;
}
/** * Gets the query. * * @return string */publicfunctiongetQuery() : string
{
return$this->queryParameters ? $this->queryParameters->toString() : '';
}
/** * Gets the fragment. * * @return string|null */publicfunctiongetFragment() : ?string
{
return$this->fragment;
}
/** * Sets the scheme. * * @param string $value * @return $this * @throws InvalidUrlSchemeException */publicfunctionsetScheme(string$value) : Url
{
$this->scheme = $this->sanitizeScheme($value);
return$this;
}
/** * Sets the host. * * @param string $value * @return $this */publicfunctionsetHost(string$value) : Url
{
$this->host = $value;
return$this;
}
/** * Sets the port. * * @param int $value * @return $this */publicfunctionsetPort(int$value) : Url
{
$this->port = $value;
return$this;
}
/** * Sets the path. * * @param string $value * @return Url */publicfunctionsetPath(string$value) : Url
{
$this->path = $value;
return$this;
}
/** * Sets the query parameters. * * @param QueryParameters $value * @return $this */publicfunctionsetQueryParameters(QueryParameters$value) : Url
{
$this->queryParameters = $value;
return$this;
}
/** * Determines if the URL has query parameters set and are not empty. * * @return bool */publicfunctionhasQueryParameters() : bool
{
returnnull !== $this->queryParameters && '' !== $this->queryParameters->toString();
}
/** * Add a query parameter. * * @param string $key * @param mixed $value * @return $this */publicfunctionaddQueryParameter(string$key, $value) : Url
{
if (! $this->queryParameters) {
$this->queryParameters = newQueryParameters();
}
$this->queryParameters->add($key, $value);
return$this;
}
/** * Adds query parameters. * * @param array<string, mixed> $parameters * @return $this */publicfunctionaddQueryParameters(array$parameters) : Url
{
if (! $this->queryParameters) {
$this->queryParameters = newQueryParameters();
}
$this->queryParameters->addMany($parameters);
return$this;
}
/** * Removes a query parameter. * * @param string $key * @return $this */publicfunctionremoveQueryParameter(string$key) : Url
{
if (! $this->queryParameters) {
return$this;
}
$this->queryParameters->remove($key);
return$this;
}
/** * Removes query parameters. * * @param string[] $keys * @return $this */publicfunctionremoveQueryParameters(array$keys) : Url
{
if (! $this->queryParameters) {
return$this;
}
$this->queryParameters->remove($keys);
return$this;
}
/** * Sets the fragment. * * @param string $fragment * @return $this */publicfunctionsetFragment(string$fragment) : Url
{
$this->fragment = $fragment;
return$this;
}
/** * Converts the URL object to a URL string. * * @return string */publicfunctiontoString() : 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 */publicfunction__toString() : string
{
return$this->toString();
}
}
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
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.
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
RequestandResponseare actually just interfaces that get implemented at the integration level, so it's entirely possible that this should use that pattern, too - where theUrlimplementation 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.