Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[11.x] Introduce Support\Url and Support\UrlQueryParameters classes for easier URL handling #53370

Open
wants to merge 14 commits into
base: 11.x
Choose a base branch
from

Conversation

stevebauman
Copy link
Contributor

@stevebauman stevebauman commented Nov 1, 2024

Description

This PR introduces two new classes in the Support namespace to provide a more convenient way to work with URLs with an object-oriented representation using PHP's native parse_url and parse_str functions:

  • Illuminate\Support\Url
  • Illuminate\Support\UrlQueryParameters

I copy these same classes over from project to project as I usually end up needing them. I figured others may find these classes a more (IDE) friendly replacement to the existing parse_url and parse_str functions.

Example

use Illuminate\Support\Url;

// Parse a URL string
$url = Url::parse('https://example.com/path/to/resource?foo=bar&baz=qux');

// Access URL components
$scheme = $url->scheme; // 'https'
$host = $url->host; // 'example.com'
$path = $url->path; // '/path/to/resource'

// Work with query parameters
$queryParams = $url->query(); // UrlQueryParameters
$foo = $queryParams->get('foo'); // 'bar'
$baz = $queryParams->get('baz'); // 'qux'
$default = $queryParams->get('bar', 'default'); // 'default'

// Check if a query parameter exists
$hasBar = $queryParams->has('bar'); // true

// Convert to array
$urlArray = $url->toArray(); // ['foo' => 'bar', 'baz' => 'qux']

Let me know your thoughts! 👍 No hard feelings on closure ❤️

@niladam
Copy link

niladam commented Nov 1, 2024

I think this would be a great addition!

good job!

@heychazza
Copy link

Really great addition! I've used the parse_url call for a project and this would've made life a lot easier

@danmatthews
Copy link
Contributor

Love this, superb utility.

@Ali-Hassan-Ali
Copy link

Wow amazing, you have inspired me to do some new work

@Ali-Hassan-Ali
Copy link

$url = url()->parse('https://example.com/path/to/resource?foo=bar&baz=qux');

I like this method 😉

@joshmanders
Copy link
Contributor

Would it be out of the scope to add this functionality to the UrlGenerator under the parse() method like @Ali-Hassan-Ali says below?

Otherwise it is macroable, those who do want it could just add it ourselves.

Great work Steve!

$url = url()->parse('https://example.com/path/to/resource?foo=bar&baz=qux');

I like this method 😉

src/Illuminate/Support/Url.php Outdated Show resolved Hide resolved
src/Illuminate/Support/Url.php Outdated Show resolved Hide resolved
Copy link
Contributor

@raphaelcangucu raphaelcangucu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice !

What is the L11 standard practices for tests?

@shaedrich
Copy link

Would be nice if it also supported URI templates but that's probably a little out of scope

@hafezdivandari
Copy link
Contributor

hafezdivandari commented Nov 2, 2024

Nice addition, thanks! I think it would be more powerful if UrlQueryParameters extend Illuminate\Support\Collection:

$url->query()->get('filters'); // Or $url->query('filters')
$url->query()->has('filters');
$url->query()->count();
$url->query()->only('sort');
// ...

Also fragment can have the same functionality, and having path may be useful too:

// https://laravel.com/docs/11.x/collections

$url->path()->first(); // docs

So we can have UrlComponent abstract class that extends Collection, and UrlQueryParameters, UrlFragments, and UrlPath that implement this class.

Comment on lines +51 to +53
public function query(): UrlQueryParameters
{
return UrlQueryParameters::parse($this->query);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function query(): UrlQueryParameters
{
return UrlQueryParameters::parse($this->query);
public function query($item = null): UrlQueryParameters
{
$this->parsedQuery ??= UrlQueryParameters::parse($this->query);
return $item ? $this->parsedQuery->get($item) : $this->parsedQuery;

Copy link
Contributor Author

@stevebauman stevebauman Nov 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!

Not sure if we want to cache the parsed query here... Since the Url properties are public, they can be changed at any time (the $query prop) which wouldn't reflect the true state of the object if it's been cached already (I know this is edge case though).

Copy link
Member

@timacdonald timacdonald Nov 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that I would expect that I can pull out the query parameter object, make changes, and have it reflected on the URL.

I also agree that the public properties in their current form make that hard.

Perhaps the query property should be a instance of the UrlQueryParameters, rather than a string.

@ezequidias
Copy link

It would be nice to have documentation for this!
https://github.com/laravel/docs/pulls

@joshmanders
Copy link
Contributor

@ezequidias

It would be nice to have documentation for this! https://github.com/laravel/docs/pulls

Probably be best to wait until it gets merged before putting in the effort to document it.

@timacdonald
Copy link
Member

I've wanted a URL helper in Laravel for a while now. Glad to see this PR.

I would love to see the ability to manipulate the URL and also a way for the Request instance to return a Url instance.

I've always disliked the Laravel's Request object only allows returning the URL as a string, e.g., Request::url(), Request::fullUrl(), Request::fullUrlWithQuery([...]), Request::fullUrlWithoutQuery([...]). Having these return a string means only one operation can be performed on it before you need to parse it again.

At times I've needed to add AND remove query parameters from the current URL. I've love to see:

$url = Request::toUrl()->withQuery([...])->withoutQuery([...]);

In its current form, this helps does not seem to be able to manipulate with the URL in anyway. I always imagined the Laravel URL class would offer similar functionality to league\uri, with a more Laravel-style API.

@shaedrich
Copy link

shaedrich commented Nov 4, 2024

I would love to see the ability to manipulate the URL and also a way for the Request instance to return a Url instance.

That would be awesome! 😍

$url = Request::toUrl()->withQuery([...])->withoutQuery([...]);

Wouldn't that be

$url = Request::toUrl()->withQueryParams([...])->withoutQueryParams([...]);

as withoutQuery() would imply "without the entire query string/without the entirety of the query parameters", therefore with* chained with without* sounds like a contradiction.

Going with Laravel's convention of only() and except() like @hafezdivandari suggested probably would be more intuitive, wouldn't it?

$url->query()->only('sort');

@shaedrich
Copy link

shaedrich commented Nov 4, 2024

Other possible features:

  • host vs hostname vs authority vs origin
  • pathinfo vs href
  • Returning the host as object which implements an interface (the implementations can be hostname, IPv4, IPv6)
  • path could also be a Stringable Collection (optionally dirname, filename, and extension if present`)
  • scheme and port could be enum-like wrapper objects (isDefault(), isCustom())
  • ArrayAccess for query
  • data urls

What kind of validation do you want to have?

  • IP is valid
  • port is numeric and within range
  • string components only contain valid characters
  • URL max length (depends on browser)
  • query string max length (depends on server)

Helpers:

  • usesTls()/isSecure()
  • isOpaque()
  • isAbsolute(), isRelative()

@dennisprudlo
Copy link
Contributor

dennisprudlo commented Nov 4, 2024

Love this wrapper around parse_url. I agree that modification methods would make a nice addition as well (being able to set a specific query parameter to another value). Using a collection for the query parameters would allow for that as already proposed.

$url = Url::parse('https://example.com?foo=bar');
$foo = $url->query->foo; // bar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.