friendship ended with social-app. php is my new best friend

title: Helper Functions description: Learn about the helper functions available in the Fetch HTTP package#

Helper Functions#

The Fetch HTTP package provides a set of global helper functions that offer a simpler, more direct way to make HTTP requests. These functions are designed to be familiar for developers who have experience with JavaScript's fetch API or other HTTP client libraries.

Main Functions#

fetch()#

The primary helper function that mimics JavaScript's fetch() API. It provides a simple yet powerful way to make HTTP requests.

function fetch(
    string|RequestInterface|null $resource = null,
    ?array $options = []
): ResponseInterface|ClientHandlerInterface|Client

Parameters#

  • $resource: A URL string, a Request object, or null to return the client for chaining
  • $options: An array of request options, including:
    • method: HTTP method (string or Method enum)
    • headers: Request headers (array)
    • body: Request body (mixed)
    • json: JSON data to send as body (array, takes precedence over body)
    • form: Form data to send as body (array, takes precedence if no json)
    • multipart: Multipart form data (array, takes precedence if no json/form)
    • query: Query parameters (array)
    • base_uri: Base URI (string)
    • timeout: Request timeout in seconds (int)
    • retries: Number of retries (int)
    • auth: Basic auth credentials [username, password] (array)
    • token: Bearer token (string)
    • Plus other options like proxy, cookies, allow_redirects, etc.

Return Value#

  • If $resource is null: Returns the client instance for method chaining
  • If $resource is a URL string: Returns a Response object
  • If $resource is a Request object: Returns a Response object

Examples#

Basic GET request:

$response = fetch('https://api.example.com/users');
$users = $response->json();

POST request with JSON data:

$response = fetch('https://api.example.com/users', [
    'method' => 'POST',
    'json' => ['name' => 'John Doe', 'email' => 'john@example.com']
]);

Setting headers:

$response = fetch('https://api.example.com/users', [
    'headers' => [
        'Authorization' => 'Bearer token123',
        'Accept' => 'application/json'
    ]
]);

Using query parameters:

$response = fetch('https://api.example.com/users', [
    'query' => ['page' => 1, 'per_page' => 20]
]);

Method chaining:

fetch()
    ->withToken('your-token')
    ->withHeader('Accept', 'application/json')
    ->get('https://api.example.com/users');

fetch_client()#

Gets or configures the global fetch client instance.

function fetch_client(
    ?array $options = null,
    bool $reset = false
): Client

Parameters#

  • $options: Global client options
  • $reset: Whether to reset the client instance

Return Value#

Returns the global Client instance.

Examples#

Get the default client:

$client = fetch_client();

Configure the global client:

fetch_client([
    'base_uri' => 'https://api.example.com',
    'timeout' => 10,
    'headers' => [
        'User-Agent' => 'MyApp/1.0'
    ]
]);

// Now all fetch() calls will use these settings by default
$response = fetch('/users'); // Uses the base_uri

Reset the client:

fetch_client(reset: true);

Add a logger:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('http');
$logger->pushHandler(new StreamHandler('logs/http.log', Logger::DEBUG));

// Set the logger on the client
$client = fetch_client();
$client->setLogger($logger);

HTTP Method Helpers#

The package provides shorthand functions for common HTTP methods, making your code more concise and readable.

get()#

function get(
    string $url,
    ?array $query = null,
    ?array $options = []
): ResponseInterface

Examples#

Simple GET request:

$response = get('https://api.example.com/users');

GET with query parameters:

$response = get('https://api.example.com/users', [
    'page' => 1,
    'per_page' => 20
]);

GET with additional options:

$response = get('https://api.example.com/users', null, [
    'headers' => ['Accept' => 'application/json'],
    'timeout' => 5
]);

post()#

function post(
    string $url,
    mixed $data = null,
    ?array $options = []
): ResponseInterface

Examples#

POST with JSON data (arrays are automatically converted to JSON):

$response = post('https://api.example.com/users', [
    'name' => 'John Doe',
    'email' => 'john@example.com'
]);

POST with raw data:

$response = post('https://api.example.com/webhook', 'Raw content', [
    'headers' => ['Content-Type' => 'text/plain']
]);

POST with form data:

$response = post('https://api.example.com/login', null, [
    'form' => [
        'username' => 'johndoe',
        'password' => 'secret'
    ]
]);

put()#

function put(
    string $url,
    mixed $data = null,
    ?array $options = []
): ResponseInterface

Examples#

$response = put('https://api.example.com/users/123', [
    'name' => 'John Smith',
    'email' => 'john.smith@example.com'
]);

patch()#

function patch(
    string $url,
    mixed $data = null,
    ?array $options = []
): ResponseInterface

Examples#

$response = patch('https://api.example.com/users/123', [
    'status' => 'active'
]);

delete()#

function delete(
    string $url,
    mixed $data = null,
    ?array $options = []
): ResponseInterface

Examples#

Simple DELETE:

$response = delete('https://api.example.com/users/123');

DELETE with body:

$response = delete('https://api.example.com/users/batch', [
    'ids' => [123, 456, 789]
]);

Async/Promise Functions#

The Fetch PHP package includes a set of functions for working with asynchronous requests and promises.

async()#

Wraps a function to run asynchronously and returns a promise.

function async(callable $fn): PromiseInterface

Example#

$promise = async(function() {
    return fetch('https://api.example.com/users');
});

await()#

Waits for a promise to resolve and returns its value.

function await(PromiseInterface $promise): mixed

Example#

$response = await(async(function() {
    return fetch('https://api.example.com/users');
}));

$users = $response->json();

all()#

Executes multiple promises concurrently and waits for all to complete.

function all(array $promises): PromiseInterface

Example#

$results = await(all([
    'users' => async(fn() => fetch('https://api.example.com/users')),
    'posts' => async(fn() => fetch('https://api.example.com/posts'))
]));

$users = $results['users']->json();
$posts = $results['posts']->json();

race()#

Executes multiple promises concurrently and returns the first to complete.

function race(array $promises): PromiseInterface

Example#

$response = await(race([
    async(fn() => fetch('https://api1.example.com/data')),
    async(fn() => fetch('https://api2.example.com/data'))
]));

any()#

Executes multiple promises concurrently and returns the first to succeed.

function any(array $promises): PromiseInterface

Example#

$response = await(any([
    async(fn() => fetch('https://api1.example.com/data')),
    async(fn() => fetch('https://api2.example.com/data'))
]));

map()#

Maps an array of items through an async function with controlled concurrency.

function map(array $items, callable $callback, int $concurrency = 5): PromiseInterface

Example#

$responses = await(map([1, 2, 3, 4, 5], function($id) {
    return async(function() use ($id) {
        return fetch("https://api.example.com/users/{$id}");
    });
}, 3)); // Process at most 3 items concurrently

batch()#

Processes items in batches with controlled concurrency.

function batch(
    array $items,
    callable $callback,
    int $batchSize = 10,
    int $concurrency = 5
): PromiseInterface

Example#

$results = await(batch(
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    function($batch) {
        return async(function() use ($batch) {
            $results = [];
            foreach ($batch as $id) {
                $response = await(async(fn() =>
                    fetch("https://api.example.com/users/{$id}")
                ));
                $results[] = $response->json();
            }
            return $results;
        });
    },
    3, // batch size
    2  // concurrency
));

retry()#

Retries an async operation with exponential backoff.

function retry(
    callable $fn,
    int $attempts = 3,
    callable|int $delay = 100
): PromiseInterface

Example#

$response = await(retry(
    function() {
        return async(function() {
            return fetch('https://api.example.com/unstable-endpoint');
        });
    },
    3, // max attempts
    function($attempt) {
        // Exponential backoff strategy
        return min(pow(2, $attempt) * 100, 1000);
    }
));

Using Helper Functions in Real-World Examples#

Basic API Interaction#

// Fetch a list of users
$users = get('https://api.example.com/users')->json();

// Create a new user
$user = post('https://api.example.com/users', [
    'name' => 'John Doe',
    'email' => 'john@example.com'
])->json();

// Update a user
$updatedUser = put("https://api.example.com/users/{$user['id']}", [
    'name' => 'John Smith'
])->json();

// Delete a user
delete("https://api.example.com/users/{$user['id']}");

Authentication#

// Configure client with authentication
fetch_client([
    'base_uri' => 'https://api.example.com',
    'headers' => [
        'Authorization' => 'Bearer your-oauth-token'
    ]
]);

// All requests now include authentication
$response = get('/protected-resource');

// Or using the withToken method
$response = fetch_client()
    ->withToken('your-oauth-token')
    ->get('/protected-resource');

// Basic authentication
$response = fetch_client()
    ->withAuth('username', 'password')
    ->get('/protected-resource');

File Upload#

$response = fetch('https://api.example.com/upload', [
    'method' => 'POST',
    'multipart' => [
        [
            'name' => 'file',
            'contents' => file_get_contents('/path/to/image.jpg'),
            'filename' => 'upload.jpg',
            'headers' => ['Content-Type' => 'image/jpeg']
        ],
        [
            'name' => 'description',
            'contents' => 'Profile picture'
        ]
    ]
]);

// Using the fluent interface
$response = fetch_client()
    ->withMultipart([
        [
            'name' => 'file',
            'contents' => fopen('/path/to/image.jpg', 'r'),
            'filename' => 'upload.jpg',
        ],
        [
            'name' => 'description',
            'contents' => 'Profile picture'
        ]
    ])
    ->post('https://api.example.com/upload');

// Check if upload was successful
if ($response->successful()) {
    $fileUrl = $response->json()['url'];
    echo "File uploaded successfully: {$fileUrl}";
}

Error Handling#

try {
    $response = get('https://api.example.com/users/999');

    if ($response->isNotFound()) {
        echo "User not found!";
    } elseif ($response->isUnauthorized()) {
        echo "Authentication required!";
    } elseif ($response->failed()) {
        echo "Request failed with status: " . $response->status();
    } else {
        $user = $response->json();
        echo "Found user: " . $user['name'];
    }
} catch (\Fetch\Exceptions\NetworkException $e) {
    echo "Network error: " . $e->getMessage();
} catch (\Fetch\Exceptions\RequestException $e) {
    echo "Request error: " . $e->getMessage();
} catch (\Exception $e) {
    echo "Error: " . $e->getMessage();
}

Parallel Requests with Modern Async/Await#

use function async;
use function await;
use function all;

await(async(function() {
    // Create multiple requests
    $results = await(all([
        'users' => async(fn() => fetch('https://api.example.com/users')),
        'posts' => async(fn() => fetch('https://api.example.com/posts')),
        'comments' => async(fn() => fetch('https://api.example.com/comments'))
    ]));

    // Process the results
    $users = $results['users']->json();
    $posts = $results['posts']->json();
    $comments = $results['comments']->json();

    echo "Fetched " . count($users) . " users, " .
         count($posts) . " posts, and " .
         count($comments) . " comments";
}));

Working with Content Types#

use Fetch\Enum\ContentType;

// Using string content types
$response = fetch_client()
    ->withBody($data, 'application/json')
    ->post('https://api.example.com/users');

// Using enum content types
$response = fetch_client()
    ->withBody($data, ContentType::JSON)
    ->post('https://api.example.com/users');

// Checking content type
if ($response->hasJsonContent()) {
    $data = $response->json();
} elseif ($response->hasHtmlContent()) {
    $html = $response->text();
}

Working with Enums#

use Fetch\Enum\Method;
use Fetch\Enum\ContentType;
use Fetch\Enum\Status;

// Use method enum
$response = fetch_client()->request(Method::POST, '/users', $userData);

// Check status with enum
if ($response->statusEnum() === Status::OK) {
    // Process successful response
}

// Content type handling
$response = fetch_client()
    ->withBody($data, ContentType::JSON)
    ->post('/users');

Tips and Best Practices#

  1. Configure Once, Use Everywhere: Use fetch_client() to set global options and defaults that will apply to all requests.

    // Set up once at the beginning of your application
    fetch_client([
        'base_uri' => 'https://api.example.com',
        'timeout' => 10,
        'headers' => [
            'User-Agent' => 'MyApp/1.0',
            'Accept' => 'application/json'
        ]
    ]);
    
    // Now use simplified calls throughout your code
    $users = get('/users')->json();
    $user = get("/users/{$id}")->json();
    
  2. Use Type-Safe Enums: Take advantage of PHP 8.1 enums for better type safety and code readability.

    use Fetch\Enum\Method;
    use Fetch\Enum\ContentType;
    use Fetch\Enum\Status;
    
    $response = fetch_client()->request(Method::POST, '/users', $userData);
    
    if ($response->statusEnum() === Status::CREATED) {
        // User was created successfully
    }
    
  3. Use the Right Helper for the Job: Choose the appropriate helper function based on the HTTP method you need.

  4. Handling JSON: Arrays passed to post(), put(), patch(), and delete() are automatically treated as JSON data.

  5. Leverage Method Chaining: When you need more control, use the fluent interface:

    fetch()
        ->baseUri('https://api.example.com')
        ->withToken('your-token')
        ->timeout(5)
        ->retry(3, 100)
        ->get('/users');
    
  6. Use the Response Methods: Take advantage of the response helper methods for cleaner code:

    // Instead of:
    if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300) {
        // ...
    }
    
    // Use:
    if ($response->successful()) {
        // ...
    }
    
  7. Modern Async/Await: Use async() and await() for cleaner asynchronous code:

    await(async(function() {
        $response = await(async(fn() => fetch('https://api.example.com/users')));
        return $response->json();
    }));
    
  8. Resource Conservation: When working with many requests, use controlled concurrency with map() or batch():

    $results = await(map($items, function($item) {
        return async(fn() => fetch("https://api.example.com/{$item}"));
    }, 5)); // No more than 5 concurrent requests
    

Next Steps#