Navigation

Php

How to Use PHP's Union Types

Use PHP 8.0+ union types to accept multiple data types in function parameters. Create flexible APIs while maintaining type safety.

Table Of Contents

Problem

You need functions that accept multiple data types but want to maintain type safety and avoid using mixed type for everything.

Solution

// Basic union types
function processId(int|string $id): string {
    if (is_int($id)) {
        return "Processing numeric ID: " . $id;
    }
    return "Processing string ID: " . $id;
}

echo processId(123);        // Processing numeric ID: 123
echo processId("ABC123");   // Processing string ID: ABC123

// Multiple types
function formatValue(int|float|string|bool $value): string {
    return match (gettype($value)) {
        'integer' => "Integer: " . $value,
        'double' => "Float: " . number_format($value, 2),
        'string' => "String: " . $value,
        'boolean' => "Boolean: " . ($value ? 'true' : 'false'),
        default => "Unknown type"
    };
}

echo formatValue(42);        // Integer: 42
echo formatValue(3.14159);   // Float: 3.14
echo formatValue("hello");   // String: hello
echo formatValue(true);      // Boolean: true

// Nullable union types
function saveData(string|null $filename, array|object $data): bool {
    if ($filename === null) {
        $filename = 'default.json';
    }
    
    if (is_array($data)) {
        $json = json_encode($data);
    } else {
        $json = json_encode((array)$data);
    }
    
    return file_put_contents($filename, $json) !== false;
}

// Array or object handling
function processData(array|object $data): array {
    if (is_object($data)) {
        return (array)$data;
    }
    return $data;
}

$arrayData = ['name' => 'John', 'age' => 30];
$objectData = (object)['name' => 'Jane', 'age' => 25];

$result1 = processData($arrayData);  // Returns array
$result2 = processData($objectData); // Converts object to array

// Class union types
class User {
    public function __construct(public string $name) {}
}

class Admin {
    public function __construct(public string $name, public array $permissions) {}
}

function getUsername(User|Admin $person): string {
    return $person->name;
}

function getPermissions(User|Admin $person): array {
    if ($person instanceof Admin) {
        return $person->permissions;
    }
    return ['read']; // Default permissions for User
}

$user = new User("John");
$admin = new Admin("Jane", ["read", "write", "delete"]);

echo getUsername($user);     // John
echo getUsername($admin);    // Jane

// Return union types
function findRecord(int $id): User|Admin|null {
    // Simulate database lookup
    if ($id === 1) {
        return new User("John");
    }
    if ($id === 2) {
        return new Admin("Jane", ["admin"]);
    }
    return null; // Not found
}

$record = findRecord(1);
if ($record !== null) {
    echo "Found: " . $record->name;
}

// File handling with union types
function readFile(string|resource $input): string {
    if (is_string($input)) {
        return file_get_contents($input);
    }
    
    // Handle resource
    $content = '';
    while (!feof($input)) {
        $content .= fread($input, 8192);
    }
    return $content;
}

// Usage with filename
$content1 = readFile('data.txt');

// Usage with resource
$handle = fopen('data.txt', 'r');
$content2 = readFile($handle);
fclose($handle);

// Database result handling
function formatDatabaseValue(int|float|string|bool|null $value): string {
    return match (true) {
        $value === null => 'NULL',
        is_bool($value) => $value ? '1' : '0',
        is_string($value) => "'" . addslashes($value) . "'",
        default => (string)$value
    };
}

// API response handling
interface ApiResponse {}

class SuccessResponse implements ApiResponse {
    public function __construct(public array $data) {}
}

class ErrorResponse implements ApiResponse {
    public function __construct(public string $message, public int $code) {}
}

function handleApiCall(): SuccessResponse|ErrorResponse {
    // Simulate API call
    $success = rand(0, 1);
    
    if ($success) {
        return new SuccessResponse(['user_id' => 123, 'status' => 'active']);
    }
    
    return new ErrorResponse('User not found', 404);
}

$response = handleApiCall();
if ($response instanceof SuccessResponse) {
    echo "Success: " . json_encode($response->data);
} else {
    echo "Error: " . $response->message . " (Code: " . $response->code . ")";
}

// Configuration value handling
function getConfigValue(string $key): string|int|bool|array {
    $config = [
        'app_name' => 'My App',           // string
        'debug' => true,                  // bool
        'max_users' => 1000,             // int
        'allowed_ips' => ['127.0.0.1']   // array
    ];
    
    return $config[$key] ?? throw new InvalidArgumentException("Config key not found: $key");
}

$appName = getConfigValue('app_name');    // string
$debug = getConfigValue('debug');         // bool
$maxUsers = getConfigValue('max_users');  // int
$allowedIps = getConfigValue('allowed_ips'); // array

// Collection handling
class Collection {
    public function __construct(private array $items) {}
    
    public function first(): mixed {
        return $this->items[0] ?? null;
    }
    
    public function find(int|string $key): mixed {
        return $this->items[$key] ?? null;
    }
}

$collection = new Collection(['a', 'b', 'c']);
$first = $collection->first();      // Mixed type
$second = $collection->find(1);     // Mixed type
$named = $collection->find('key');  // Mixed type

// Validation with union types
function validateInput(string $field, string|int|float $value): bool {
    return match ($field) {
        'age' => is_numeric($value) && $value >= 0 && $value <= 150,
        'score' => is_numeric($value) && $value >= 0 && $value <= 100,
        'name' => is_string($value) && strlen($value) > 0,
        'email' => is_string($value) && filter_var($value, FILTER_VALIDATE_EMAIL),
        default => false
    };
}

// Math operations with union types
function calculate(string $operation, int|float $a, int|float $b): int|float {
    return match ($operation) {
        'add' => $a + $b,
        'subtract' => $a - $b,
        'multiply' => $a * $b,
        'divide' => $b !== 0 ? $a / $b : throw new DivisionByZeroError(),
        'power' => $a ** $b,
        default => throw new InvalidArgumentException("Unknown operation: $operation")
    };
}

echo calculate('add', 5, 3);        // 8
echo calculate('divide', 10, 3);    // 3.333...
echo calculate('power', 2, 8);      // 256

// Type checking with union types
function describeType(int|string|bool|null $value): string {
    $type = gettype($value);
    $description = match ($type) {
        'integer' => "Integer with value: $value",
        'string' => "String with length: " . strlen($value),
        'boolean' => "Boolean: " . ($value ? 'true' : 'false'),
        'NULL' => "Null value",
        default => "Unknown type: $type"
    };
    
    return $description;
}

// Performance consideration
function processLargeDataset(array|Iterator $data): int {
    $count = 0;
    
    if (is_array($data)) {
        // Direct array processing
        $count = count($data);
    } else {
        // Iterator processing (memory efficient)
        foreach ($data as $item) {
            $count++;
        }
    }
    
    return $count;
}

// Generator function returning union type
function generateData(bool $asArray = false): array|Generator {
    if ($asArray) {
        return range(1, 1000);
    }
    
    // Return generator for memory efficiency
    for ($i = 1; $i <= 1000; $i++) {
        yield $i;
    }
}

$arrayData = generateData(true);        // array
$generatorData = generateData(false);   // Generator

Explanation

Union types use the | operator to specify multiple acceptable types. They provide flexibility while maintaining type safety through type checking.

Use union types when functions logically accept multiple types. Combine with instanceof, is_*() functions, or match expressions to handle different types appropriately.

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Php