boson-php/uri-contracts
Framework-agnostic URI contracts for the Boson PHP ecosystem. Provides interface definitions for working with URIs in Boson and related packages, enabling consistent implementations and integrations across applications, windows, and webviews.
Install the Package:
composer require boson-php/uri-contracts
Ensure your project uses PHP 8.4+ and Laravel (or a non-Laravel PHP app).
Understand the Core Interfaces: The package provides three primary interfaces:
UriInterface: Represents a URI as a string and supports type-safe conversion.
use Boson\UriContracts\UriInterface;
$uri = UriInterface::fromString('https://example.com/path?query=value');
echo (string) $uri; // Outputs: https://example.com/path?query=value
UriBuilderInterface: Constructs URIs programmatically.
use Boson\UriContracts\UriBuilderInterface;
$builder = new class implements UriBuilderInterface {
public function build(string $path, array $query = []): UriInterface {
return UriInterface::fromString("https://example.com/$path?" . http_build_query($query));
}
};
UriStringable: Ensures a class can be converted to a URI string.
use Boson\UriContracts\UriStringable;
class MyClass implements UriStringable {
public function __toString(): string {
return 'https://example.com';
}
}
First Use Case: Replace Hardcoded URLs in a Service
Replace a service method that uses string URLs with the UriInterface:
// Before
public function getUserProfileUrl(int $userId): string {
return "https://api.example.com/users/$userId";
}
// After
public function getUserProfileUrl(int $userId): UriInterface {
return UriInterface::fromString("https://api.example.com/users/$userId");
}
Leverage Laravel’s Facade with an Adapter (Optional)
Create a simple adapter to bridge Laravel’s Url facade with UriInterface:
use Boson\UriContracts\UriInterface;
use Illuminate\Support\Facades\Url;
class LaravelUriAdapter implements UriInterface {
public function __construct(private string $url) {}
public static function fromString(string $url): self {
return new self($url);
}
public function __toString(): string {
return Url::to($this->url);
}
}
Use UriBuilderInterface to centralize URI logic:
class ApiUriBuilder implements UriBuilderInterface {
public function build(string $endpoint, array $query = []): UriInterface {
$base = 'https://api.example.com';
$path = ltrim($endpoint, '/');
$queryString = http_build_query($query);
return UriInterface::fromString(
"$base/$path?" . ($queryString ?? '')
);
}
}
Usage:
$builder = new ApiUriBuilder();
$uri = $builder->build('/users/1', ['active' => 'true']);
Inject UriInterface or UriBuilderInterface into services for mocking:
class UserService {
public function __construct(
private UriBuilderInterface $uriBuilder
) {}
public function getProfileLink(int $userId): string {
$uri = $this->uriBuilder->build("/users/$userId");
return (string) $uri;
}
}
Test Example:
$mockBuilder = $this->createMock(UriBuilderInterface::class);
$mockBuilder->method('build')->willReturn(UriInterface::fromString('https://mocked.com'));
$service = new UserService($mockBuilder);
$this->assertEquals('https://mocked.com', $service->getProfileLink(1));
Use UriInterface to enforce URI rules (e.g., HTTPS-only):
use Boson\UriContracts\UriInterface;
$uri = UriInterface::fromString('http://example.com');
if (str_starts_with((string) $uri, 'http://')) {
throw new \InvalidArgumentException('URI must use HTTPS');
}
Generate URIs for routes using Laravel’s Url facade via an adapter:
class RouteUriAdapter implements UriInterface {
public function __construct(private string $route) {}
public static function fromString(string $route): self {
return new self($route);
}
public function __toString(): string {
return route($this->route);
}
}
Usage:
$routeUri = RouteUriAdapter::fromString('users.show', ['user' => 1]);
echo (string) $routeUri; // Outputs: /users/1
Build URIs for external APIs or webhooks:
$webhookUri = UriInterface::fromString('https://external-service.com/webhook');
$payload = [
'url' => (string) $webhookUri,
'event' => 'user.created'
];
Service Provider Binding:
Bind the UriBuilderInterface to a concrete implementation in a service provider:
$this->app->bind(UriBuilderInterface::class, function ($app) {
return new ApiUriBuilder();
});
Middleware for URI Inspection:
Use UriStringable in middleware to validate or modify URIs:
public function handle($request, Closure $next) {
$uri = $request->getRequestUri();
$uriObject = UriInterface::fromString($uri);
if (!str_contains((string) $uriObject, 'api')) {
abort(403, 'Unauthorized route');
}
return $next($request);
}
Form Request Validation: Validate URI fields in form requests:
use Boson\UriContracts\UriInterface;
use Illuminate\Validation\Rule;
public function rules(): array {
return [
'callback_url' => [
'required',
function ($attribute, $value, $fail) {
$uri = UriInterface::fromString($value);
if (!str_starts_with((string) $uri, 'https://')) {
$fail('The callback URL must use HTTPS.');
}
},
],
];
}
Mocking URIs in Unit Tests:
$mockUri = $this->createMock(UriInterface::class);
$mockUri->method('__toString')->willReturn('https://mocked.com');
$service = new MyService($mockUri);
Data Providers for URI Tests:
public function uriProvider(): array {
return [
['https://example.com', true],
['http://example.com', false],
['ftp://example.com', false],
];
}
/** @dataProvider uriProvider */
public function testUriValidation(string $uri, bool $shouldPass): void {
$uriObject = UriInterface::fromString($uri);
$this->assertEquals($shouldPass, str_starts_with((string) $uriObject, 'https://'));
}
Laravel’s Url Facade Overhead:
Url::to() bypasses the UriInterface abstraction. Always wrap it in an adapter if you need type safety.String vs. Object URI Handling:
route(), action()) return strings, not objects. You’ll need to manually convert them to UriInterface:
$stringUri = route('users.show');
$uriObject = UriInterface::fromString($stringUri);
Query String Parsing Quirks:
parse_str() or Symfony\Component\Uri\Uri for advanced handling.$uri = UriInterface::fromString('https://example.com?key=value');
parse_str((string) $uri, $query);
Relative vs. Absolute URIs:
/path) as expected. Test edge cases like:
$relativeUri = UriInterface::fromString('/path');
echo (string) $relativeUri; // Ensure this behaves as expected in your context.
Performance with Large-Scale URI Generation:
How can I help you explore Laravel packages today?