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 example
<?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 example
<?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 example
<?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 example
<?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 example
<?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 example
<?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 example
<?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.