引言 PHP 8.2引入的析取范式类型(Disjunctive Normal Form Types,简称DNF Types)是PHP类型系统的重大进步。作为一个经常需要处理复杂数据结构和API接口的开发者,DNF类型让我能够更精确地表达复杂的类型约束。经过近一年的实践,我想分享一些DNF类型的实际应用经验。
什么是析取范式类型 析取范式类型允许我们组合联合类型(Union Types)和交集类型(Intersection Types),创建更复杂的类型表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 interface Loggable { public function log (string $message ): void ; } interface Cacheable { public function getCacheKey ( ): string ; } interface Serializable { public function serialize ( ): string ; } interface Timestampable { public function getTimestamp ( ): int ; } function processData ( (Loggable&Cacheable )|(Serializable &Timestampable ) $data ): string { if ($data instanceof Loggable && $data instanceof Cacheable) { $data ->log ('Processing cacheable data' ); return 'cached:' . $data ->getCacheKey (); } if ($data instanceof Serializable && $data instanceof Timestampable) { return $data ->serialize () . ':' . $data ->getTimestamp (); } throw new InvalidArgumentException ('Invalid data type' ); }
类型系统的进化历程 PHP 7.0 - 标量类型 1 2 3 function oldWay (string $name , int $age ): bool { return strlen ($name ) > 0 && $age > 0 ; }
PHP 8.0 - 联合类型 1 2 3 function withUnion (string |int $id ): string { return (string )$id ; }
PHP 8.1 - 交集类型 1 2 3 function withIntersection (Countable &Iterator $data ): int { return count ($data ); }
PHP 8.2 - 析取范式类型 1 2 3 4 5 6 7 8 9 10 11 12 13 function withDNF ( (Countable &Iterator )|(ArrayAccess &Traversable ) $data ): int { if ($data instanceof Countable ) { return count ($data ); } $count = 0 ; foreach ($data as $item ) { $count ++; } return $count ; }
实际应用场景 1. 数据处理管道 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 interface Transformable { public function transform ( ): array ; } interface Validatable { public function validate ( ): bool ; } interface Cacheable { public function getCacheKey ( ): string ; public function getCacheTTL ( ): int ; } interface Loggable { public function getLogContext ( ): array ; } class DataProcessor { public function process ( (Transformable&Validatable )|(Cacheable&Loggable ) $input ): array { if ($input instanceof Transformable && $input instanceof Validatable) { if (!$input ->validate ()) { throw new InvalidArgumentException ('Data validation failed' ); } return $input ->transform (); } if ($input instanceof Cacheable && $input instanceof Loggable) { $cacheKey = $input ->getCacheKey (); $context = $input ->getLogContext (); error_log ('Processing cached data: ' . json_encode ($context )); return ['cache_key' => $cacheKey , 'ttl' => $input ->getCacheTTL ()]; } throw new InvalidArgumentException ('Unsupported input type' ); } } class UserData implements Transformable , Validatable { public function __construct ( private array $data ) {} public function transform ( ): array { return [ 'id' => $this ->data['id' ] ?? null , 'name' => $this ->data['name' ] ?? '' , 'email' => $this ->data['email' ] ?? '' ]; } public function validate ( ): bool { return isset ($this ->data['id' ], $this ->data['name' ], $this ->data['email' ]) && filter_var ($this ->data['email' ], FILTER_VALIDATE_EMAIL); } } class CachedReport implements Cacheable , Loggable { public function __construct ( private string $reportId , private array $data ) {} public function getCacheKey ( ): string { return "report:{$this->reportId} " ; } public function getCacheTTL ( ): int { return 3600 ; } public function getLogContext ( ): array { return [ 'report_id' => $this ->reportId, 'data_size' => count ($this ->data), 'timestamp' => time () ]; } } $processor = new DataProcessor ();$userData = new UserData ([ 'id' => 123 , 'name' => '张三' , 'email' => 'zhang@example.com' ]); $report = new CachedReport ('monthly-sales' , ['sales' => 10000 ]);$result1 = $processor ->process ($userData );$result2 = $processor ->process ($report );
2. API响应处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 interface JsonSerializable { public function jsonSerialize ( ): mixed ; } interface XmlSerializable { public function xmlSerialize ( ): string ; } interface Compressible { public function compress ( ): string ; } interface Encryptable { public function encrypt (string $key ): string ; } class ApiResponseHandler { public function handleResponse ( (JsonSerializable&Compressible )|(XmlSerializable&Encryptable ) $response , string $format = 'json' ): string { if ($format === 'json' && $response instanceof JsonSerializable && $response instanceof Compressible) { $json = json_encode ($response ->jsonSerialize ()); return $response ->compress ($json ); } if ($format === 'xml' && $response instanceof XmlSerializable && $response instanceof Encryptable) { $xml = $response ->xmlSerialize (); return $response ->encrypt ($xml , 'secret-key' ); } throw new InvalidArgumentException ('Unsupported response format or type' ); } } class CompressedJsonResponse implements JsonSerializable , Compressible { public function __construct ( private array $data ) {} public function jsonSerialize ( ): mixed { return $this ->data; } public function compress (string $data ): string { return gzcompress ($data ); } } class EncryptedXmlResponse implements XmlSerializable , Encryptable { public function __construct ( private array $data ) {} public function xmlSerialize ( ): string { $xml = new SimpleXMLElement ('<response/>' ); foreach ($this ->data as $key => $value ) { $xml ->addChild ($key , htmlspecialchars ((string )$value )); } return $xml ->asXML (); } public function encrypt (string $data , string $key ): string { return base64_encode (openssl_encrypt ($data , 'AES-256-CBC' , $key , 0 , str_repeat ('0' , 16 ))); } }
3. 数据库查询构建器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 interface Queryable { public function toSql ( ): string ; public function getBindings ( ): array ; } interface Cacheable { public function getCacheKey ( ): string ; public function shouldCache ( ): bool ; } interface Executable { public function execute ( ): array ; } interface Debuggable { public function getDebugInfo ( ): array ; public function explain ( ): string ; } class QueryExecutor { public function executeQuery ( (Queryable&Executable )|(Cacheable&Debuggable ) $query ): array { if ($query instanceof Queryable && $query instanceof Executable) { $sql = $query ->toSql (); $bindings = $query ->getBindings (); error_log ("Executing SQL: $sql " ); error_log ("Bindings: " . json_encode ($bindings )); return $query ->execute (); } if ($query instanceof Cacheable && $query instanceof Debuggable) { if ($query ->shouldCache ()) { $cacheKey = $query ->getCacheKey (); error_log ("Using cache key: $cacheKey " ); } $debugInfo = $query ->getDebugInfo (); $explanation = $query ->explain (); error_log ("Debug info: " . json_encode ($debugInfo )); error_log ("Query explanation: $explanation " ); return ['debug' => $debugInfo , 'explanation' => $explanation ]; } throw new InvalidArgumentException ('Unsupported query type' ); } } class SelectQuery implements Queryable , Executable { public function __construct ( private string $table , private array $columns = ['*' ], private array $conditions = [] ) {} public function toSql ( ): string { $columns = implode (', ' , $this ->columns); $sql = "SELECT $columns FROM {$this->table} " ; if (!empty ($this ->conditions)) { $where = implode (' AND ' , array_keys ($this ->conditions)); $sql .= " WHERE $where " ; } return $sql ; } public function getBindings ( ): array { return array_values ($this ->conditions); } public function execute ( ): array { return [ ['id' => 1 , 'name' => '张三' ], ['id' => 2 , 'name' => '李四' ] ]; } } class CachedAnalyticsQuery implements Cacheable , Debuggable { public function __construct ( private string $metric , private array $filters ) {} public function getCacheKey ( ): string { return 'analytics:' . $this ->metric . ':' . md5 (serialize ($this ->filters)); } public function shouldCache ( ): bool { return true ; } public function getDebugInfo ( ): array { return [ 'metric' => $this ->metric, 'filters' => $this ->filters, 'cache_key' => $this ->getCacheKey (), 'timestamp' => time () ]; } public function explain ( ): string { return "Analytics query for metric '{$this->metric} ' with " . count ($this ->filters) . " filters" ; } }
4. 文件处理系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 interface Readable { public function read ( ): string ; public function getSize ( ): int ; } interface Writable { public function write (string $data ): bool ; public function append (string $data ): bool ; } interface Compressible { public function compress ( ): string ; public function decompress (string $data ): string ; } interface Encryptable { public function encrypt (string $key ): string ; public function decrypt (string $data , string $key ): string ; } class FileProcessor { public function processFile ( (Readable&Compressible )|(Writable&Encryptable ) $file , string $operation ): string { if ($operation === 'compress' && $file instanceof Readable && $file instanceof Compressible) { $content = $file ->read (); return $file ->compress ($content ); } if ($operation === 'secure_write' && $file instanceof Writable && $file instanceof Encryptable) { $encryptedData = $file ->encrypt ('secret-key' ); $file ->write ($encryptedData ); return 'File written securely' ; } throw new InvalidArgumentException ('Unsupported operation or file type' ); } } class CompressibleTextFile implements Readable , Compressible { public function __construct ( private string $filePath ) {} public function read ( ): string { return file_get_contents ($this ->filePath); } public function getSize ( ): int { return filesize ($this ->filePath); } public function compress (string $data = null ): string { $content = $data ?? $this ->read (); return gzcompress ($content ); } public function decompress (string $data ): string { return gzuncompress ($data ); } } class SecureLogFile implements Writable , Encryptable { public function __construct ( private string $filePath ) {} public function write (string $data ): bool { return file_put_contents ($this ->filePath, $data ) !== false ; } public function append (string $data ): bool { return file_put_contents ($this ->filePath, $data , FILE_APPEND) !== false ; } public function encrypt (string $key ): string { $data = file_get_contents ($this ->filePath); return openssl_encrypt ($data , 'AES-256-CBC' , $key , 0 , str_repeat ('0' , 16 )); } public function decrypt (string $data , string $key ): string { return openssl_decrypt ($data , 'AES-256-CBC' , $key , 0 , str_repeat ('0' , 16 )); } }
5. 事件处理系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 interface Dispatchable { public function dispatch ( ): void ; public function getEventName ( ): string ; } interface Queueable { public function queue ( ): void ; public function getQueueName ( ): string ; } interface Loggable { public function log ( ): void ; public function getLogLevel ( ): string ; } interface Retryable { public function retry ( ): void ; public function getMaxRetries ( ): int ; } class EventProcessor { public function processEvent ( (Dispatchable&Loggable )|(Queueable&Retryable ) $event ): string { if ($event instanceof Dispatchable && $event instanceof Loggable) { $event ->log (); $event ->dispatch (); return "Event '{$event->getEventName()} ' dispatched and logged" ; } if ($event instanceof Queueable && $event instanceof Retryable) { try { $event ->queue (); return "Event queued to '{$event->getQueueName()} '" ; } catch (Exception $e ) { if ($event ->getMaxRetries () > 0 ) { $event ->retry (); return "Event queued for retry" ; } throw $e ; } } throw new InvalidArgumentException ('Unsupported event type' ); } } class UserRegistrationEvent implements Dispatchable , Loggable { public function __construct ( private string $userId , private string $email ) {} public function dispatch ( ): void { echo "Dispatching user registration event for user: {$this->userId} \n" ; } public function getEventName ( ): string { return 'user.registered' ; } public function log ( ): void { error_log ("User registered: {$this->userId} ({$this->email} )" ); } public function getLogLevel ( ): string { return 'info' ; } } class EmailNotificationEvent implements Queueable , Retryable { private int $retryCount = 0 ; public function __construct ( private string $recipient , private string $subject , private int $maxRetries = 3 ) {} public function queue ( ): void { if (rand (1 , 3 ) === 1 ) { throw new RuntimeException ('Queue operation failed' ); } echo "Email notification queued for: {$this->recipient} \n" ; } public function getQueueName ( ): string { return 'email-notifications' ; } public function retry ( ): void { $this ->retryCount++; echo "Retrying email notification (attempt {$this->retryCount} )\n" ; $this ->queue (); } public function getMaxRetries ( ): int { return $this ->maxRetries - $this ->retryCount; } }
高级用法和模式 1. 策略模式与DNF类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 interface Sortable { public function sort (array $data ): array ; } interface Filterable { public function filter (array $data ): array ; } interface Cacheable { public function getCacheKey ( ): string ; } interface Configurable { public function configure (array $options ): void ; } class DataProcessor { public function process ( array $data , (Sortable&Filterable )|(Cacheable&Configurable ) $strategy ): array { if ($strategy instanceof Sortable && $strategy instanceof Filterable) { $filtered = $strategy ->filter ($data ); return $strategy ->sort ($filtered ); } if ($strategy instanceof Cacheable && $strategy instanceof Configurable) { $cacheKey = $strategy ->getCacheKey (); if ($cached = $this ->getFromCache ($cacheKey )) { return $cached ; } $strategy ->configure (['data_size' => count ($data )]); $result = $this ->processWithConfig ($data , $strategy ); $this ->saveToCache ($cacheKey , $result ); return $result ; } throw new InvalidArgumentException ('Unsupported strategy type' ); } private function getFromCache (string $key ): ?array { return null ; } private function saveToCache (string $key , array $data ): void { } private function processWithConfig (array $data , Configurable $strategy ): array { return $data ; } } class SortAndFilterStrategy implements Sortable , Filterable { public function sort (array $data ): array { sort ($data ); return $data ; } public function filter (array $data ): array { return array_filter ($data , fn($item ) => !empty ($item )); } } class CachedConfigurableStrategy implements Cacheable , Configurable { private array $config = []; public function getCacheKey ( ): string { return 'strategy:' . md5 (serialize ($this ->config)); } public function configure (array $options ): void { $this ->config = array_merge ($this ->config, $options ); } }
2. 装饰器模式与DNF类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 interface Component { public function operation ( ): string ; } interface Loggable { public function log (string $message ): void ; } interface Cacheable { public function cache (string $key , mixed $value ): void ; public function getFromCache (string $key ): mixed ; } interface Measurable { public function startTimer ( ): void ; public function endTimer ( ): float ; } class ComponentProcessor { public function process ( (Component&Loggable )|(Component&Cacheable )|(Component&Measurable ) $component ): string { if ($component instanceof Component && $component instanceof Loggable) { $component ->log ('Starting operation' ); $result = $component ->operation (); $component ->log ('Operation completed' ); return $result ; } if ($component instanceof Component && $component instanceof Cacheable) { $cacheKey = 'component_result' ; if ($cached = $component ->getFromCache ($cacheKey )) { return $cached ; } $result = $component ->operation (); $component ->cache ($cacheKey , $result ); return $result ; } if ($component instanceof Component && $component instanceof Measurable) { $component ->startTimer (); $result = $component ->operation (); $duration = $component ->endTimer (); return $result . " (执行时间: {$duration} s)" ; } throw new InvalidArgumentException ('Unsupported component type' ); } } class LoggableComponent implements Component , Loggable { public function __construct ( private Component $component ) {} public function operation ( ): string { return $this ->component->operation (); } public function log (string $message ): void { error_log ("[LoggableComponent] $message " ); } } class CacheableComponent implements Component , Cacheable { private array $cache = []; public function __construct ( private Component $component ) {} public function operation ( ): string { return $this ->component->operation (); } public function cache (string $key , mixed $value ): void { $this ->cache[$key ] = $value ; } public function getFromCache (string $key ): mixed { return $this ->cache[$key ] ?? null ; } } class MeasurableComponent implements Component , Measurable { private float $startTime = 0 ; public function __construct ( private Component $component ) {} public function operation ( ): string { return $this ->component->operation (); } public function startTimer ( ): void { $this ->startTime = microtime (true ); } public function endTimer ( ): float { return microtime (true ) - $this ->startTime; } }
性能和最佳实践 1. 类型检查优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class OptimizedTypeChecker { private static array $typeCache = []; public static function checkType ( object $object , string $typeExpression ): bool { $objectClass = get_class ($object ); $cacheKey = $objectClass . ':' . $typeExpression ; if (isset (self ::$typeCache [$cacheKey ])) { return self ::$typeCache [$cacheKey ]; } $result = self ::performTypeCheck ($object , $typeExpression ); self ::$typeCache [$cacheKey ] = $result ; return $result ; } private static function performTypeCheck ( object $object , string $typeExpression ): bool { return true ; } public static function clearCache ( ): void { self ::$typeCache = []; } }
2. 错误处理和调试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class DNFTypeDebugger { public static function analyzeType ( object $object , string $expectedType ): array { $reflection = new ReflectionClass ($object ); $interfaces = $reflection ->getInterfaceNames (); $parentClasses = []; $parent = $reflection ->getParentClass (); while ($parent ) { $parentClasses [] = $parent ->getName (); $parent = $parent ->getParentClass (); } return [ 'class' => get_class ($object ), 'interfaces' => $interfaces , 'parent_classes' => $parentClasses , 'expected_type' => $expectedType , 'matches' => self ::checkTypeMatch ($object , $expectedType ) ]; } private static function checkTypeMatch ( object $object , string $expectedType ): array { return [ 'exact_match' => false , 'partial_match' => true , 'missing_interfaces' => [], 'suggestions' => [] ]; } }
注意事项和限制 1. 复杂度管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function badExample ( (A&B&C )|(D&E&F )|(G&H&I )|(J&K&L ) $param ): void { } interface ComplexTypeA extends A , B , C {}interface ComplexTypeB extends D , E , F {}function goodExample ( ComplexTypeA|ComplexTypeB $param ): void { }
2. 性能考虑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class PerformanceAwareProcessor { private array $typeCheckers = []; public function __construct ( ) { $this ->typeCheckers = [ 'type1' => fn ($obj ) => $obj instanceof A && $obj instanceof B, 'type2' => fn ($obj ) => $obj instanceof C && $obj instanceof D, ]; } public function process ( (A&B )|(C&D ) $input ): string { foreach ($this ->typeCheckers as $type => $checker ) { if ($checker ($input )) { return $this ->processType ($input , $type ); } } throw new InvalidArgumentException ('Unsupported type' ); } private function processType (object $input , string $type ): string { return "Processed as $type " ; } }
总结 PHP 8.2的析取范式类型是类型系统的重大进步,它让我们能够:
精确表达复杂类型约束 :组合联合和交集类型
提高代码安全性 :编译时类型检查
增强代码可读性 :明确的类型意图表达
支持复杂设计模式 :策略、装饰器等模式的类型安全实现
最适用的场景:
复杂的API接口设计
插件和扩展系统
数据处理管道
事件处理系统
策略模式实现
使用建议:
避免过度复杂的类型表达式
使用接口组合简化复杂类型
考虑性能影响,适当使用缓存
提供清晰的文档和示例
DNF类型让PHP的类型系统更加强大和灵活,是构建复杂应用的重要工具!