Table Of Contents
Here's the Answer
The issue is expressing complex type requirements like "A and B, or C and D". You can solve this by using DNF types with parentheses:
<?php
interface Loggable {
public function log(string $message): void;
}
interface Cacheable {
public function getFromCache(string $key): mixed;
public function putInCache(string $key, mixed $value): void;
}
interface Searchable {
public function search(string $query): array;
}
interface Indexable {
public function index(): void;
public function reindex(): void;
}
// DNF Type: (Loggable&Cacheable)|(Searchable&Indexable)
function processContent((Loggable&Cacheable)|(Searchable&Indexable) $processor): void {
// This accepts objects that are:
// - EITHER: Loggable AND Cacheable
// - OR: Searchable AND Indexable
if ($processor instanceof Loggable) {
$processor->log("Processing content with cache");
$cached = $processor->getFromCache("content");
if (!$cached) {
$processor->putInCache("content", "processed data");
}
} else {
$processor->index();
$results = $processor->search("example");
}
}
// Classes implementing different combinations
class CachedLogger implements Loggable, Cacheable {
public function log(string $message): void {
echo "Log: $message\n";
}
public function getFromCache(string $key): mixed {
return $_SESSION[$key] ?? null;
}
public function putInCache(string $key, mixed $value): void {
$_SESSION[$key] = $value;
}
}
class SearchEngine implements Searchable, Indexable {
private array $index = [];
public function search(string $query): array {
return array_filter($this->index, fn($item) => str_contains($item, $query));
}
public function index(): void {
$this->index = ['content1', 'content2', 'example data'];
}
public function reindex(): void {
$this->index = [];
$this->index();
}
}
// More complex DNF with classes and interfaces
class DatabaseService {
public function connect(): void {}
}
class FileService {
public function readFile(string $path): string { return ''; }
}
// Complex DNF: (DatabaseService&Loggable)|(FileService&Cacheable)|Searchable
function handleData((DatabaseService&Loggable)|(FileService&Cacheable)|Searchable $handler): void {
match (true) {
$handler instanceof DatabaseService => $handler->connect(),
$handler instanceof FileService => $handler->readFile('/path'),
$handler instanceof Searchable => $handler->search('query'),
default => throw new InvalidArgumentException('Invalid handler type')
};
}
// Usage
$logger = new CachedLogger();
$searcher = new SearchEngine();
processContent($logger); // ✅ Implements Loggable&Cacheable
processContent($searcher); // ✅ Implements Searchable&Indexable
// This would fail:
// processContent(new SomeOtherClass()); // ❌ Doesn't match either combination
Explanation
DNF types allow you to create sophisticated type constraints:
- Flexible Requirements: Accept multiple valid type combinations
- Precise Contracts: Each branch specifies exact interface requirements
- Better API Design: Express "this OR that" with different capabilities
- Type Safety: Compiler ensures only valid combinations are accepted
Syntax Rules:
- Use
&
for intersection (AND) - Use
|
for union (OR) - Use
()
to group intersections within unions - Format:
(A&B)|(C&D)|(E&F)
DNF types are perfect for APIs that can work with different service types, each requiring specific capabilities.
Share this article
Add Comment
No comments yet. Be the first to comment!