http clients and apis
URLs And Query Strings
URLs identify what an HTTP request is targeting. Query strings add optional values such as filters, search terms, sorting, pagination, and feature flags.
When PHP calls an external API, small URL mistakes can create hard-to-find bugs: double slashes, missing encoding, broken search terms, or secrets leaking into logs.
URL parts
<?php
declare(strict_types=1);
$url = 'https://api.example.test/products?category=books&page=2';
$parts = parse_url($url);
parse_str($parts['query'] ?? '', $query);
echo 'Path: ' . $parts['path'] . PHP_EOL;
echo 'Page: ' . $query['page'] . PHP_EOL;
// Prints:
// Path: /products
// Page: 2
Useful parts include scheme (https), host (api.example.test), path (/products), and query (category=books&page=2).
Build query strings with http_build_query()
Do not concatenate query strings by hand. http_build_query() handles encoding correctly.
<?php
declare(strict_types=1);
$query = [
'category' => 'books',
'search' => 'PHP & APIs',
'page' => 2,
];
echo '/products?' . http_build_query($query) . PHP_EOL;
// Prints:
// /products?category=books&search=PHP+%26+APIs&page=2
The & inside PHP & APIs becomes %26, so it is treated as part of the search term instead of a separator between query parameters.
Combine a base URL and path carefully
API clients often have a base URL from configuration and paths from code. Trim only the joining slashes, not the scheme.
<?php
declare(strict_types=1);
function joinUrl(string $baseUrl, string $path): string
{
return rtrim($baseUrl, '/') . '/' . ltrim($path, '/');
}
echo joinUrl('https://api.example.test/', '/products/42') . PHP_EOL;
// Prints:
// https://api.example.test/products/42
Do not use trim($baseUrl, '/') on a full URL. It can damage https://.
Build a complete API URL
<?php
declare(strict_types=1);
/**
* @param array<string, scalar|null> $query
*/
function apiUrl(string $baseUrl, string $path, array $query = []): string
{
$url = rtrim($baseUrl, '/') . '/' . ltrim($path, '/');
$queryString = http_build_query($query);
return $queryString === '' ? $url : $url . '?' . $queryString;
}
echo apiUrl('https://api.example.test', '/products', [
'search' => 'PHP books',
'page' => 2,
]) . PHP_EOL;
// Prints:
// https://api.example.test/products?search=PHP+books&page=2
Repeated parameters
Some APIs accept repeated parameters such as status[]=open&status[]=paid, while others expect comma-separated values such as status=open,paid.
PHP can build array-style parameters:
<?php
declare(strict_types=1);
$query = ['status' => ['open', 'paid']];
echo http_build_query($query) . PHP_EOL;
// Prints:
// status%5B0%5D=open&status%5B1%5D=paid
Before implementing filters, check the API documentation. Different APIs expect different conventions.
Validate query values
Query parameters are still input. If your API client accepts a page number, validate it before building the URL.
<?php
declare(strict_types=1);
function validPage(int $page): int
{
return max(1, $page);
}
echo validPage(-4) . PHP_EOL;
// Prints:
// 1
For sort fields, use an allow-list. Do not send arbitrary user input as an API sort field unless the API explicitly supports it.
Do not put secrets in URLs
URLs are commonly logged by proxies, web servers, analytics tools, error trackers, and browser history. Avoid putting API keys, bearer tokens, password reset tokens, or private data in query strings when a header or request body is the correct place.
What to check in a project
Check that API URLs are built with URL-aware helpers, not manual concatenation.
Check base URLs come from configuration and are not repeated across the codebase.
Check query parameters are encoded and validated.
Check the API documentation for repeated-parameter conventions.
Check logs do not include secrets in query strings.
What you should be able to do
After this lesson, you should be able to parse URL parts, build API URLs safely, encode query strings, validate common query values, and explain why secrets do not belong in URLs.
Practice
Task: Build An API URL
Write a small PHP script that builds API URLs from a base URL, path, and query parameters.
Requirements
- Use
declare(strict_types=1);. - Create a function that joins a base URL and path without double slashes.
- Use
http_build_query()for query parameters. - Include a search term with a space or ampersand.
- Include a case with no query parameters.
- Print both URLs.
Check Your Work
Run the script and confirm the special characters are encoded and the no-query URL has no trailing ?.
Show solution
This solution keeps URL joining and query building in one place.
<?php
declare(strict_types=1);
/**
* @param array<string, scalar|null> $query
*/
function buildApiUrl(string $baseUrl, string $path, array $query = []): string
{
$url = rtrim($baseUrl, '/') . '/' . ltrim($path, '/');
$queryString = http_build_query($query);
return $queryString === '' ? $url : $url . '?' . $queryString;
}
echo buildApiUrl('https://api.example.test/', '/products', [
'search' => 'PHP & APIs',
'page' => 2,
]) . PHP_EOL;
echo buildApiUrl('https://api.example.test/', '/health') . PHP_EOL;
// Prints:
// https://api.example.test/products?search=PHP+%26+APIs&page=2
// https://api.example.test/health
Why This Works
The first URL proves special characters are encoded correctly. The second URL proves the function does not add an empty query string when there are no parameters.