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!