boson-php/uri-factory
PSR-17 URI factory for Boson PHP. Create and normalize URIs for Boson apps and WebView navigation, with simple Composer installation and docs integrated into the Boson ecosystem. PHP 8.4+ and MIT-licensed.
composer require boson-php/uri-factory
app/UriFactories/UserUriFactory.php):
use Boson\UriFactory\UriFactory;
class UserUriFactory extends UriFactory
{
public function profile(int $id): string
{
return $this->path("users/{$id}/profile");
}
}
AppServiceProvider):
$this->app->singleton(UserUriFactory::class, function ($app) {
return new UserUriFactory(config('app.url'));
});
use App\UriFactories\UserUriFactory;
class UserController extends Controller
{
public function show(UserUriFactory $factory, int $id)
{
$profileUrl = $factory->profile($id);
return redirect($profileUrl);
}
}
src/UriFactory.php: Core class to extend for custom factories.tests/: Example test cases for validation and edge cases.app://).Break down URI generation into domain-specific factories:
// API Factory
class ApiUriFactory extends UriFactory
{
public function usersIndex(): string
{
return $this->path('api/v1/users');
}
}
// Web Factory
class WebUriFactory extends UriFactory
{
public function dashboard(): string
{
return $this->path('dashboard');
}
}
Benefit: Isolate URI logic by feature (e.g., API vs. web routes).
Use PHP 8.4’s named arguments and type hints:
public function product(int $id, ?string $variant = null): string
{
$path = "products/{$id}";
if ($variant) {
$path .= "/variants/{$variant}";
}
return $this->path($path);
}
Call it:
$factory->product(123, 'premium'); // /products/123/variants/premium
Leverage the factory’s built-in query builder:
public function search(string $query, int $page = 1): string
{
return $this->path('search')
->withQuery([
'q' => $query,
'page' => $page,
]);
}
Output:
/search?q=laptop&page=2
Generate route URIs without hardcoding:
public function generateRouteUri(string $name, array $params = []): string
{
$uri = route($name, $params);
return $this->uri($uri); // Wrap in factory for consistency
}
Inject context (e.g., tenant, locale) into factories:
class TenantAwareUriFactory extends UriFactory
{
public function __construct(string $baseUrl, public string $tenantId)
{
parent::__construct($baseUrl);
}
public function tenantDashboard(): string
{
return $this->path("tenants/{$this->tenantId}/dashboard");
}
}
Use factories to construct API endpoints dynamically:
class ApiClient
{
public function __construct(private ApiUriFactory $factory) {}
public function fetchUser(int $id)
{
$url = $this->factory->usersShow($id);
return Http::get($url);
}
}
Generate time-limited, signed URIs:
public function verificationLink(string $token): string
{
return $this->path("verify-email/{$token}")
->withQuery(['expires' => now()->addHours(24)->timestamp]);
}
Standardize canonical paths:
public function canonicalProduct(int $id): string
{
return $this->path("products/{$id}")
->withQuery(['canonical' => true]);
}
Laravel Facade: Create a facade for cleaner syntax:
// app/Facades/Uri.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Uri extends Facade
{
protected static function getFacadeAccessor()
{
return 'uri.factory';
}
}
Usage:
Uri::product(123); // Instead of $factory->product(123)
Configuration:
Centralize base URLs in config/uri.php:
return [
'api' => env('API_URL', 'https://api.example.com'),
'web' => env('APP_URL', 'https://example.com'),
];
Factory:
$factory = new UriFactory(config('uri.api'));
Testing: Mock factories in tests:
$factory = Mockery::mock(UserUriFactory::class);
$factory->shouldReceive('profile')
->with(123)
->andReturn('/users/123/profile');
$this->app->instance(UserUriFactory::class, $factory);
Blade Directives: Add a Blade directive for inline URI generation:
// app/Providers/BladeServiceProvider.php
Blade::directive('uri', function ($expression) {
return "<?php echo app('uri.factory')->{$expression}; ?>";
});
Usage:
<a href="{{ uri('dashboard') }}">Dashboard</a>
Double Encoding:
%252F instead of /).rawurlencode() or the factory’s built-in query builder:
$this->withQuery(['search' => 'café']); // Handles encoding
Path Collisions:
/users/{id}/posts vs. /users/posts).public function posts(int $userId): string
{
return $this->path("users/{$userId}/posts");
}
Laravel Route Cache:
php artisan route:clear
Case Sensitivity:
/Users and /users as different paths.return $this->path(strtolower("Users/{$id}"));
Middleware Bypass:
route() for internal routes:
public function adminDashboard(): string
{
return route('admin.dashboard'); // Respects middleware
}
Inspect Generated URIs:
$uri = $factory->profile(123);
dd($uri); // Debug the output
Enable Query Logging: Add a debug method to factories:
public function debug(): void
{
\Log::debug('URI Construction', [
'path' => $this->path,
'query' => $this->query,
'base' => $this->baseUrl,
]);
}
Validate Against Laravel’s url():
$factoryUri = $factory->dashboard();
$laravelUri = url('/dashboard');
assert($factoryUri === $laravelUri, "URIs must match!");
Base URL Overrides:
$factory = new UriFactory(config('app.url'));
**Scheme
How can I help you explore Laravel packages today?