Installation:
composer require busybee/urljoin
Or manually include src/urljoin.php in your project.
First Use Case: Join a base URL with a relative path to generate an absolute URL:
use BusyBee\UrlJoin\UrlJoin;
$baseUrl = 'https://example.com/path/to/page';
$relativeUrl = '../another-page';
$absoluteUrl = UrlJoin::join($baseUrl, $relativeUrl);
// Output: https://example.com/path/another-page
Where to Look First:
tests.php in the repository for edge cases and expected behavior.src/UrlJoin.php for implementation details (e.g., handling edge cases like ../ underflow).URL Normalization:
Use UrlJoin::join() to resolve relative URLs against a base URL in:
/assets/) with relative asset paths (e.g., images/logo.png).// Example: Dynamic API endpoint
$base = route('api.base');
$endpoint = UrlJoin::join($base, 'users/profile');
Environment-Specific Bases:
Store base URLs in config (e.g., config('app.url')) and reuse UrlJoin across environments:
$base = config('app.url');
$absolute = UrlJoin::join($base, '/relative-path');
Validation Layer:
Combine with Laravel’s Url::isValid() to sanitize joined URLs:
$joinedUrl = UrlJoin::join($base, $relative);
if (!Url::isValid($joinedUrl)) {
abort(400, 'Invalid URL generated');
}
Service Providers:
Bind UrlJoin as a singleton for dependency injection:
$this->app->singleton('urlJoiner', function () {
return new UrlJoin();
});
Then inject it into controllers/services:
public function __construct(private UrlJoin $urlJoiner) {}
Blade Directives: Create a custom Blade directive for frontend templates:
Blade::directive('join', function ($expression) {
return "<?php echo BusyBee\UrlJoin\UrlJoin::join($expression[0], $expression[1]); ?>";
});
Usage:
<a href="{{ join(['route(\"home\")', 'subpage']) }}">Link</a>
Middleware: Normalize URLs in incoming requests (e.g., rewrite relative paths in query params):
public function handle($request, Closure $next) {
$request->merge([
'normalized_url' => UrlJoin::join(
$request->get('base'),
$request->get('relative')
)
]);
return $next($request);
}
Protocol Handling:
https://example.com with //subdomain.example.com may drop the protocol.// before joining:
$base = str_replace('//', '/', $base); // Normalize protocol-relative URLs
Empty Paths:
https://example.com with / may not behave as expected (e.g., returns https://example.com/).rtrim($base, '/') to avoid double slashes in edge cases.Query Strings/Fragments:
UrlJoin does not preserve query strings or fragments (e.g., #section or ?id=1).parse_url() + http_build_query() to manually reattach them:
$baseParts = parse_url($base);
$relativeParts = parse_url($relative, PHP_URL_QUERY | PHP_URL_FRAGMENT);
$joined = UrlJoin::join($base, $relative);
parse_str($relativeParts['query'] ?? '', $query);
$joined .= http_build_query($query) . ($relativeParts['fragment'] ?? '');
Windows Paths:
\) in URLs (e.g., C:\path\file.html) may cause parsing errors.$base = str_replace('\\', '/', $base);
Test Edge Cases:
Use the package’s test suite (tests.php) to validate behavior for inputs like:
../ underflow (e.g., https://example.com/a/../../b → https://example.com/b).http://example.com + https://subdomain.example.com).https://example.com/ + path vs. https://example.com + /path).Logging: Log joined URLs during development to catch silent failures:
\Log::debug('Joined URL', ['base' => $base, 'relative' => $relative, 'result' => $joined]);
Custom Normalization:
Extend UrlJoin by subclassing and overriding join():
class CustomUrlJoin extends UrlJoin {
public function join($base, $other) {
$base = $this->normalizeBase($base);
return parent::join($base, $other);
}
protected function normalizeBase($url) {
// Custom logic (e.g., append default port)
return str_replace('http://', 'http://localhost:8000', $url);
}
}
Laravel Facade: Create a facade for convenience:
// app/Facades/UrlJoiner.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class UrlJoiner extends Facade {
protected static function getFacadeAccessor() {
return 'urlJoiner';
}
}
Usage:
use App\Facades\UrlJoiner;
$url = UrlJoiner::join($base, $relative);
Performance:
Cache::remember():
$url = Cache::remember("urljoin_{$base}_{$relative}", now()->addHours(1), function () use ($base, $relative) {
return UrlJoin::join($base, $relative);
});
How can I help you explore Laravel packages today?