sabre/uri
Lightweight PHP URI utility library compliant with RFC3986. Provides resolve, normalize, parse/build, and split helpers for working with URLs, including Windows-style path edge cases. Fully unit tested and inspired by Node.js URL handling.
Installation:
composer require sabre/uri
Ensure your composer.json specifies PHP 8.2+ for the latest features (or PHP 7.4+ for LTS support).
First Use Case: Resolve a relative URL against a base URL in a Laravel route or controller:
use Sabre\Uri\Uri;
$baseUrl = Uri::resolve('https://example.com/base/path', '../relative/path');
// Returns: 'https://example.com/relative/path'
Where to Look First:
Illuminate\Support\Facades\URL alternatives).Replace URL::to() or manual string concatenation with Uri::resolve():
use Sabre\Uri\Uri;
// In a route closure or controller
$absoluteUrl = Uri::resolve(
request()->getSchemeAndHttpHost() . request()->path(),
'subpath'
);
Ensure URLs are RFC-compliant before storage (e.g., in a database or cache):
$normalized = Uri::normalize('https://example.com/path?query=1#fragment');
// Returns: 'https://example.com/path?query=1'
Use parse() and build() for dynamic URI manipulation (e.g., in API requests):
$parsed = Uri::parse('https://user:pass@example.com/path?query=1');
$rebuilt = Uri::build([
'scheme' => 'https',
'host' => 'example.com',
'path' => '/new-path',
]);
Handle file paths safely (e.g., in Illuminate\Http\UploadedFile):
$uploadedFile = request()->file('file');
$dirname = Uri::split($uploadedFile->getPathname())['dirname'];
Create a custom facade for consistency:
// app/Providers/AppServiceProvider.php
public function boot()
{
app()->bind('uri', function () {
return new Sabre\Uri\Uri();
});
}
Usage:
$resolved = app('uri')->resolve($base, $relative);
Validate URIs in Illuminate\Foundation\Http\FormRequest:
use Sabre\Uri\Uri;
public function rules()
{
return [
'redirect_url' => [
'required',
function ($attribute, $value, $fail) {
try {
Uri::parse($value); // Throws InvalidUriException on failure
} catch (\Sabre\Uri\InvalidUriException $e) {
$fail('The :attribute must be a valid URI.');
}
},
],
];
}
Strip or normalize URIs in incoming requests:
use Sabre\Uri\Uri;
public function handle($request, Closure $next)
{
$request->merge([
'sanitized_url' => Uri::normalize($request->input('url')),
]);
return $next($request);
}
Resolve relative paths in background jobs:
use Sabre\Uri\Uri;
use Illuminate\Bus\Queueable;
class ProcessUrlJob implements Queueable
{
public function handle()
{
$absoluteUrl = Uri::resolve(
config('app.url'),
$this->relativePath
);
// Process $absoluteUrl...
}
}
Override toArray() in Illuminate\Http\Resources\Json\JsonResource:
public function toArray($request)
{
return [
'url' => Uri::build([
'scheme' => 'https',
'host' => $request->getHost(),
'path' => $this->path,
]),
];
}
Add a custom command to inspect URIs:
use Sabre\Uri\Uri;
use Illuminate\Console\Command;
class DebugUriCommand extends Command
{
protected $signature = 'uri:debug {url}';
public function handle()
{
$parsed = Uri::parse($this->argument('url'));
$this->line(print_r($parsed, true));
}
}
Windows Path Handling:
file:///C:/path may parse unexpectedly (see #81).Uri::parse() with caution for Windows paths; prefer Uri::build() for reconstruction:
$windowsPath = Uri::build([
'scheme' => 'file',
'host' => '', // Empty host for Windows
'path' => 'C:/path/to/file.txt',
]);
Fragment Handling:
Uri::resolve() may drop fragments (#) if not handled explicitly.$base = Uri::parse('https://example.com#base');
$relative = Uri::parse('subpath');
$resolved = Uri::build([
'scheme' => $base['scheme'],
'host' => $base['host'],
'path' => Uri::resolve($base['path'], $relative['path']),
'fragment' => $base['fragment'],
]);
Query String Normalization:
Uri::normalize() may not preserve query string order (RFC3986 is order-agnostic).http_build_query() for ordered queries:
$normalized = Uri::build([
'scheme' => 'https',
'host' => 'example.com',
'path' => '/path',
'query' => http_build_query(['param' => 'value']),
]);
Unicode Characters:
é, 中文) may not encode correctly.$uri = Uri::build([
'path' => Uri::encode('path/with/é/characters'),
]);
Empty Components:
Uri::parse() may return null for empty components (e.g., scheme).$parsed = Uri::parse($uri);
if (empty($parsed['scheme'])) {
throw new \InvalidArgumentException('URI must include a scheme.');
}
Use Uri::parse() for Inspection:
Dump parsed URIs to debug issues:
dd(Uri::parse('https://user:pass@example.com/path?query=1#frag'));
Test Edge Cases: Run the library’s test suite locally to verify behavior:
composer require --dev sabre/uri
vendor/bin/phpunit vendor/sabre/uri/tests
Leverage InvalidUriException:
Catch parsing errors gracefully:
try {
Uri::parse($malformedUri);
} catch (\Sabre\Uri\InvalidUriException $e) {
Log::error('Invalid URI: ' . $e->getMessage());
}
PHP Version Compatibility:
3.1.0+ for rector support and PHP 8.2 features.3.0.* for LTS compatibility.2.3.* (but avoid Windows path changes from 2.2.3).Custom URI Schemes:
s3://, mailto:), but validate them explicitly:
if (!in_array($parsed['scheme'], ['http', 'https', 's3'])) {
throw new \InvalidArgumentException("Unsupported
How can I help you explore Laravel packages today?