URL
Component Url cung cấp một value object bất biến (immutable) để phân tích, xây dựng và thao tác URL. Tất cả các phép thay đổi đều trả về instance mới, instance gốc không bị ảnh hưởng.
Tạo Url
Phân tích URL có sẵn
use Vietiso\Core\Url\Url;
$url = Url::parse('https://example.com/path?page=2#section');
$url->scheme(); // 'https'
$url->host(); // 'example.com'
$url->path(); // '/path'
$url->fragment(); // 'section'
Từ request hiện tại
// URL của request đang xử lý
$url = Url::current();
// Từ một Request cụ thể
$url = Url::fromRequest($request);
// Base URL của ứng dụng
$base = Url::base();
Tạo từ components
$url = Url::create(
scheme: 'https',
host: 'example.com',
port: 8080,
path: '/api/v1',
query: ['key' => 'value'],
fragment: 'section',
);
// https://example.com:8080/api/v1?key=value#section
echo $url;
Tạo URL từ path tương đối
// Ghép với base URL của ứng dụng
$url = Url::to('/tours', ['page' => 1]);
// HTTPS buộc
$secureUrl = Url::to('/checkout', [], secure: true);
Helper Functions
// Lấy URL hiện tại (không truyền path)
$current = url();
// Tạo URL từ path
$url = url('/tours', ['page' => 1]);
// Luôn dùng HTTPS
$url = secure_url('/payment');
// URL cho file asset (public/)
$asset = asset('images/logo.png');
// Signed URL (xem phần Signed URL bên dưới)
$signed = signed_url('tour.verify', ['id' => 42]);
Đọc Components
$url = Url::parse('https://admin:secret@example.com:8080/api/v1?page=2&sort=name#results');
$url->scheme(); // 'https'
$url->host(); // 'example.com'
$url->port(); // 8080
$url->user(); // 'admin'
$url->pass(); // 'secret'
$url->path(); // '/api/v1'
$url->queryString(); // 'page=2&sort=name'
$url->fragment(); // 'results'
// Authority = user:pass@host:port (bỏ qua port mặc định)
$url->authority(); // 'admin:secret@example.com:8080'
// Port thực dùng (fallback về port mặc định của scheme)
$url->effectivePort(); // 8080 (hoặc 443 nếu không set)
// Scheme dạng enum
$url->schemeEnum(); // Scheme::Https
Thay Đổi Immutable
Mọi phương thức with* đều trả về instance mới, instance gốc không đổi:
$original = Url::parse('http://example.com/old?a=1');
$new = $original
->withScheme('https')
->withHost('api.example.com')
->withPort(9090)
->withPath('/v2/tours')
->withQueryParam('page', 1)
->withFragment('list');
// $original không thay đổi
echo $original; // http://example.com/old?a=1
echo $new; // https://api.example.com:9090/v2/tours?a=1&page=1#list
Thay đổi scheme
use Vietiso\Core\Url\Scheme;
$url->withScheme('https');
$url->withScheme(Scheme::Https); // dùng enum
Thay đổi path
// Thay toàn bộ path
$url->withPath('/new/path');
// Tự động thêm / ở đầu khi có host
$url->withPath('no-slash'); // → '/no-slash'
// Nối thêm vào path hiện tại
$url->appendPath('v2/users'); // /api → /api/v2/users
// Trailing slash
$url->withTrailingSlash(); // /path → /path/
$url->withoutTrailingSlash(); // /path/ → /path
Thao Tác Query String
$url = Url::parse('https://example.com?page=1&sort=name');
// Thêm/cập nhật một param
$url->withQueryParam('limit', 20);
// Thêm nhiều param cùng lúc
$url->withQueryParams(['page' => 2, 'limit' => 10]);
// Thay toàn bộ query (string, array hoặc QueryBag)
$url->withQuery('a=1&b=2');
$url->withQuery(['a' => 1, 'b' => 2]);
// Xóa param
$url->withoutQueryParam('sort');
$url->withoutQueryParam(['page', 'sort']);
// Xóa toàn bộ query
$url->withoutQuery();
// Đọc param
$url->getQueryParam('page'); // '1'
$url->getQueryParam('missing', 'default');
$url->hasQueryParam('sort'); // true
// Lấy QueryBag object
$bag = $url->query();
Thao Tác Path Segments
$url = Url::parse('https://example.com/blog/2024/my-post.html');
// Tất cả segments (index 0-based)
$url->segments(); // ['blog', '2024', 'my-post.html']
// Lấy segment theo vị trí (1-based)
$url->segment(1); // 'blog'
$url->segment(2); // '2024'
$url->segment(99, 'default'); // 'default'
// Thư mục và tên file
$url->dirname(); // '/blog/2024'
$url->basename(); // 'my-post.html'
$url->extension(); // 'html'
Kiểm Tra URL
$url = Url::parse('https://example.com');
$url->isValid(); // true (có host hoặc path)
$url->isAbsolute(); // true (có cả scheme và host)
$url->isRelative(); // false
$url->isSecure(); // true (https, wss, ftps, sftp, ssh)
$url->isDefaultPort(); // true (port 443 với https)
// Kiểm tra định dạng URL hợp lệ (static)
Url::isValidUrl('https://example.com'); // true
Url::isValidUrl('mailto:user@example.com', ['mailto']); // true
QueryBag
QueryBag là object bất biến đại diện cho tập hợp query parameters. Có thể dùng độc lập:
use Vietiso\Core\Url\QueryBag;
// Tạo
$bag = new QueryBag(['page' => '1', 'sort' => 'name']);
$bag = QueryBag::fromString('page=1&sort=name');
$bag = QueryBag::fromArray(['page' => '1']);
// Đọc
$bag->get('page'); // '1'
$bag->get('missing', '0'); // '0'
$bag->has('sort'); // true
$bag->all(); // ['page' => '1', 'sort' => 'name']
$bag->keys(); // ['page', 'sort']
$bag->isEmpty(); // false
$bag->count(); // 2
// Thay đổi (immutable)
$bag->with('limit', '20');
$bag->withMany(['page' => '2', 'limit' => '20']);
$bag->without('sort');
$bag->without(['page', 'sort']);
$bag->only(['page']);
// Merge
$bag->merge(['extra' => 'value']);
$bag->merge($anotherBag);
// Functional
$bag->filter(fn($value) => $value !== '');
$bag->map(fn($value) => strtoupper($value));
// Xuất
$bag->toString(); // 'page=1&sort=name' (RFC 3986)
$bag->toString(QueryBag::ENC_RFC1738); // dùng + thay space
$bag->toArray();
json_encode($bag); // {"page":"1","sort":"name"}
// ArrayAccess (chỉ đọc)
$bag['page']; // '1'
isset($bag['sort']); // true
foreach ($bag as $key => $value) { ... }
QueryBag là immutable — gán trực tiếp
$bag['key'] = 'value'hoặcunset($bag['key'])sẽ némLogicException.
Scheme Enum
Scheme enum liệt kê các scheme được hỗ trợ với thông tin port và bảo mật:
use Vietiso\Core\Url\Scheme;
// Các scheme hỗ trợ
Scheme::Http // 'http'
Scheme::Https // 'https'
Scheme::Ftp // 'ftp'
Scheme::Ftps // 'ftps'
Scheme::Sftp // 'sftp'
Scheme::Ws // 'ws'
Scheme::Wss // 'wss'
Scheme::Mailto // 'mailto'
Scheme::Tel // 'tel'
Scheme::Ssh // 'ssh'
Scheme::File // 'file'
// Port mặc định
Scheme::Http->defaultPort(); // 80
Scheme::Https->defaultPort(); // 443
Scheme::Ftp->defaultPort(); // 21
Scheme::Sftp->defaultPort(); // 22
// Kiểm tra bảo mật
Scheme::Https->isSecure(); // true
Scheme::Http->isSecure(); // false
// Chuyển sang phiên bản secure
Scheme::Http->toSecure(); // Scheme::Https
Scheme::Ws->toSecure(); // Scheme::Wss
Scheme::Ftp->toSecure(); // Scheme::Ftps
// Parse từ string (case-insensitive)
Scheme::tryFromString('HTTPS'); // Scheme::Https
Scheme::tryFromString('xxx'); // null
Signed URL
Signed URL dùng HMAC-SHA256 để xác minh URL không bị giả mạo:
$url = Url::parse('https://example.com/verify?user=42');
// Ký URL (dùng app.key từ config)
$signed = $url->signed();
// Dùng key tùy chỉnh
$signed = $url->signed('my-secret-key');
// Xác minh
$signed->hasValidSignature(); // true
$signed->hasValidSignature('my-secret-key'); // true
// URL có thời hạn (giây hoặc DateTimeInterface)
$signed = $url->temporarySignedUrl(3600); // hết hạn sau 1 giờ
$signed = $url->temporarySignedUrl(now()->addDay()); // hết hạn sau 1 ngày
Tạo signed URL từ named route
// Dùng helper function (route phải được đặt tên)
$url = signed_url('tour.verify', ['id' => 42]);
// Có thời hạn
$url = signed_url('download.file', ['id' => 5], expiration: 3600);
$url = signed_url('download.file', ['id' => 5], expiration: now()->addHour());
Xác minh trong controller/middleware
$url = Url::fromRequest($request);
if (!$url->hasValidSignature()) {
return Response::json(['error' => 'Invalid or expired link'], 403);
}
Encode / Decode
// Encode theo RFC 3986 (rawurlencode)
Url::encode('hello world/foo&bar'); // 'hello%20world%2Ffoo%26bar'
// Decode
Url::decode('hello%20world'); // 'hello world'
Xuất Dữ Liệu
$url = Url::parse('https://example.com:8080/path?a=1#frag');
// Chuỗi URL
$url->toString(); // 'https://example.com:8080/path?a=1#frag'
(string) $url; // tương đương toString()
// Port mặc định bị ẩn
Url::parse('https://example.com:443/path')->toString();
// → 'https://example.com/path'
// Array
$url->toArray();
// [
// 'scheme' => 'https',
// 'host' => 'example.com',
// 'port' => 8080,
// 'user' => null,
// 'pass' => null,
// 'path' => '/path',
// 'query' => ['a' => '1'],
// 'fragment' => 'frag',
// ]
// JSON
$url->toJson();
json_encode($url); // serialize thành chuỗi URL
Fluent Chaining
Vì tất cả with* đều trả về instance mới, có thể chain liên tiếp:
$url = Url::parse('http://example.com')
->withScheme('https')
->withHost('api.example.com')
->withPort(9090)
->withPath('/v2/tours')
->withQueryParams(['page' => 1, 'limit' => 20])
->withFragment('results');
// https://api.example.com:9090/v2/tours?page=1&limit=20#results
echo $url;