Laravel 13 迭代器模式深度解析 迭代器模式是一种行为型设计模式,它提供一种方法顺序访问聚合对象中的元素,而不暴露其底层表示。本文将深入探讨 Laravel 13 中迭代器模式的高级用法。
迭代器模式基础 什么是迭代器模式 迭代器模式允许在不暴露集合内部结构的情况下遍历集合中的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php namespace App \Contracts ;interface IteratorInterface { public function current ( ): mixed ; public function key ( ): mixed ; public function next ( ): void ; public function rewind ( ): void ; public function valid ( ): bool ; }
1 2 3 4 5 6 7 8 <?php namespace App \Contracts ;interface AggregateInterface { public function createIterator ( ): IteratorInterface ; }
自定义迭代器 数组迭代器 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 <?php namespace App \Iterators ;use App \Contracts \IteratorInterface ;class ArrayIterator implements IteratorInterface { protected array $items ; protected int $position = 0 ; public function __construct (array $items ) { $this ->items = array_values ($items ); } public function current ( ): mixed { return $this ->items[$this ->position] ?? null ; } public function key ( ): mixed { return $this ->position; } public function next ( ): void { $this ->position++; } public function rewind ( ): void { $this ->position = 0 ; } public function valid ( ): bool { return isset ($this ->items[$this ->position]); } }
过滤迭代器 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 <?php namespace App \Iterators ;use Iterator ;class FilterIterator implements Iterator { protected Iterator $iterator ; protected $callback ; protected mixed $current ; protected int $key = 0 ; public function __construct (Iterator $iterator , callable $callback ) { $this ->iterator = $iterator ; $this ->callback = $callback ; } public function current ( ): mixed { return $this ->current; } public function key ( ): mixed { return $this ->key; } public function next ( ): void { $this ->iterator->next (); $this ->fetchNextValid (); } public function rewind ( ): void { $this ->iterator->rewind (); $this ->key = 0 ; $this ->fetchNextValid (); } public function valid ( ): bool { return $this ->current !== null ; } protected function fetchNextValid ( ): void { $this ->current = null ; while ($this ->iterator->valid ()) { $item = $this ->iterator->current (); if (($this ->callback)($item )) { $this ->current = $item ; $this ->key++; break ; } $this ->iterator->next (); } } }
映射迭代器 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 <?php namespace App \Iterators ;use Iterator ;class MapIterator implements Iterator { protected Iterator $iterator ; protected $callback ; public function __construct (Iterator $iterator , callable $callback ) { $this ->iterator = $iterator ; $this ->callback = $callback ; } public function current ( ): mixed { return ($this ->callback)($this ->iterator->current ()); } public function key ( ): mixed { return $this ->iterator->key (); } public function next ( ): void { $this ->iterator->next (); } public function rewind ( ): void { $this ->iterator->rewind (); } public function valid ( ): bool { return $this ->iterator->valid (); } }
数据库结果集迭代器 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 <?php namespace App \Iterators \Database ;use Iterator ;use Illuminate \Support \Facades \DB ;class LazyQueryIterator implements Iterator { protected string $query ; protected array $bindings ; protected int $chunkSize ; protected int $position = 0 ; protected int $chunkPosition = 0 ; protected array $currentChunk = []; protected int $totalOffset = 0 ; public function __construct (string $query , array $bindings = [], int $chunkSize = 100 ) { $this ->query = $query ; $this ->bindings = $bindings ; $this ->chunkSize = $chunkSize ; } public function current ( ): mixed { return $this ->currentChunk[$this ->chunkPosition] ?? null ; } public function key ( ): mixed { return $this ->position; } public function next ( ): void { $this ->position++; $this ->chunkPosition++; if ($this ->chunkPosition >= count ($this ->currentChunk)) { $this ->loadNextChunk (); } } public function rewind ( ): void { $this ->position = 0 ; $this ->chunkPosition = 0 ; $this ->totalOffset = 0 ; $this ->loadNextChunk (); } public function valid ( ): bool { return isset ($this ->currentChunk[$this ->chunkPosition]); } protected function loadNextChunk ( ): void { $this ->currentChunk = DB::select ( $this ->query . " LIMIT {$this->chunkSize} OFFSET {$this->totalOffset} " , $this ->bindings ); $this ->totalOffset += $this ->chunkSize; $this ->chunkPosition = 0 ; } }
树形结构迭代器 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 <?php namespace App \Iterators \Tree ;use Iterator ;use Countable ;class DepthFirstIterator implements Iterator , Countable { protected array $stack = []; protected int $position = 0 ; protected $getChildren ; public function __construct ($root , callable $getChildren ) { $this ->getChildren = $getChildren ; $this ->buildStack ($root ); } protected function buildStack ($node ): void { $this ->stack[] = $node ; $children = ($this ->getChildren)($node ); if (is_iterable ($children )) { foreach ($children as $child ) { $this ->buildStack ($child ); } } } public function current ( ): mixed { return $this ->stack[$this ->position] ?? null ; } public function key ( ): mixed { return $this ->position; } public function next ( ): void { $this ->position++; } public function rewind ( ): void { $this ->position = 0 ; } public function valid ( ): bool { return isset ($this ->stack[$this ->position]); } public function count ( ): int { return count ($this ->stack); } }
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 <?php namespace App \Iterators \Tree ;use Iterator ;class BreadthFirstIterator implements Iterator { protected array $queue = []; protected int $position = 0 ; protected $getChildren ; public function __construct ($root , callable $getChildren ) { $this ->getChildren = $getChildren ; $this ->queue = [$root ]; } public function current ( ): mixed { return $this ->queue[$this ->position] ?? null ; } public function key ( ): mixed { return $this ->position; } public function next ( ): void { $this ->position++; if (!isset ($this ->queue[$this ->position])) { $this ->loadNextLevel (); } } public function rewind ( ): void { $this ->position = 0 ; } public function valid ( ): bool { return isset ($this ->queue[$this ->position]); } protected function loadNextLevel ( ): void { $currentLevel = array_slice ($this ->queue, $this ->position); foreach ($currentLevel as $node ) { $children = ($this ->getChildren)($node ); if (is_iterable ($children )) { foreach ($children as $child ) { $this ->queue[] = $child ; } } } } }
分页迭代器 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 <?php namespace App \Iterators \Pagination ;use Iterator ;use Illuminate \Contracts \Pagination \LengthAwarePaginator ;class PaginatorIterator implements Iterator { protected $query ; protected int $perPage ; protected int $currentPage = 1 ; protected array $currentItems = []; protected int $itemPosition = 0 ; protected int $totalPosition = 0 ; protected ?int $totalItems = null ; public function __construct ($query , int $perPage = 15 ) { $this ->query = $query ; $this ->perPage = $perPage ; } public function current ( ): mixed { return $this ->currentItems[$this ->itemPosition] ?? null ; } public function key ( ): mixed { return $this ->totalPosition; } public function next ( ): void { $this ->itemPosition++; $this ->totalPosition++; if ($this ->itemPosition >= count ($this ->currentItems)) { $this ->loadNextPage (); } } public function rewind ( ): void { $this ->currentPage = 1 ; $this ->itemPosition = 0 ; $this ->totalPosition = 0 ; $this ->loadCurrentPage (); } public function valid ( ): bool { return isset ($this ->currentItems[$this ->itemPosition]); } protected function loadCurrentPage ( ): void { $paginator = $this ->query->paginate ($this ->perPage, ['*' ], 'page' , $this ->currentPage); $this ->currentItems = $paginator ->items (); if ($this ->totalItems === null ) { $this ->totalItems = $paginator ->total (); } } protected function loadNextPage ( ): void { $this ->currentPage++; $this ->itemPosition = 0 ; $this ->loadCurrentPage (); } public function getTotalItems ( ): ?int { return $this ->totalItems; } }
文件系统迭代器 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 <?php namespace App \Iterators \Filesystem ;use Iterator ;use RecursiveIteratorIterator ;use RecursiveDirectoryIterator ;class DirectoryIterator implements Iterator { protected RecursiveIteratorIterator $iterator ; protected array $filters = []; public function __construct (string $path , int $mode = RecursiveIteratorIterator ::LEAVES_ONLY ) { $directoryIterator = new RecursiveDirectoryIterator ( $path , RecursiveDirectoryIterator ::SKIP_DOTS ); $this ->iterator = new RecursiveIteratorIterator ($directoryIterator , $mode ); } public function filter (callable $callback ): self { $this ->filters[] = $callback ; return $this ; } public function byExtension (string $extension ): self { return $this ->filter (fn($file ) => $file ->getExtension () === $extension ); } public function byPattern (string $pattern ): self { return $this ->filter (fn($file ) => fnmatch ($pattern , $file ->getFilename ())); } public function current ( ): mixed { return $this ->iterator->current (); } public function key ( ): mixed { return $this ->iterator->key (); } public function next ( ): void { $this ->iterator->next (); $this ->skipFiltered (); } public function rewind ( ): void { $this ->iterator->rewind (); $this ->skipFiltered (); } public function valid ( ): bool { return $this ->iterator->valid (); } protected function skipFiltered ( ): void { while ($this ->iterator->valid () && !$this ->passesFilters ($this ->iterator->current ())) { $this ->iterator->next (); } } protected function passesFilters ($file ): bool { foreach ($this ->filters as $filter ) { if (!$filter ($file )) { return false ; } } return true ; } }
缓存迭代器 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 <?php namespace App \Iterators \Cache ;use Iterator ;use Illuminate \Support \Facades \Cache ;class CachedIterator implements Iterator { protected Iterator $iterator ; protected string $cacheKey ; protected int $ttl ; protected array $cachedItems = []; protected bool $loaded = false ; protected int $position = 0 ; public function __construct (Iterator $iterator , string $cacheKey , int $ttl = 3600 ) { $this ->iterator = $iterator ; $this ->cacheKey = $cacheKey ; $this ->ttl = $ttl ; } public function current ( ): mixed { $this ->ensureLoaded (); return $this ->cachedItems[$this ->position] ?? null ; } public function key ( ): mixed { return $this ->position; } public function next ( ): void { $this ->ensureLoaded (); $this ->position++; } public function rewind ( ): void { $this ->ensureLoaded (); $this ->position = 0 ; } public function valid ( ): bool { $this ->ensureLoaded (); return isset ($this ->cachedItems[$this ->position]); } protected function ensureLoaded ( ): void { if ($this ->loaded) { return ; } $cached = Cache ::get ($this ->cacheKey); if ($cached !== null ) { $this ->cachedItems = $cached ; } else { $this ->cachedItems = iterator_to_array ($this ->iterator); Cache ::put ($this ->cacheKey, $this ->cachedItems, $this ->ttl); } $this ->loaded = true ; } public function toArray ( ): array { $this ->ensureLoaded (); return $this ->cachedItems; } }
批处理迭代器 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 <?php namespace App \Iterators \Batch ;use Iterator ;class BatchIterator implements Iterator { protected Iterator $iterator ; protected int $batchSize ; protected array $currentBatch = []; protected int $batchNumber = 0 ; public function __construct (Iterator $iterator , int $batchSize = 100 ) { $this ->iterator = $iterator ; $this ->batchSize = $batchSize ; } public function current ( ): mixed { return $this ->currentBatch; } public function key ( ): mixed { return $this ->batchNumber; } public function next ( ): void { $this ->loadNextBatch (); } public function rewind ( ): void { $this ->iterator->rewind (); $this ->batchNumber = 0 ; $this ->loadNextBatch (); } public function valid ( ): bool { return !empty ($this ->currentBatch); } protected function loadNextBatch ( ): void { $this ->currentBatch = []; $count = 0 ; while ($this ->iterator->valid () && $count < $this ->batchSize) { $this ->currentBatch[] = $this ->iterator->current (); $this ->iterator->next (); $count ++; } if (!empty ($this ->currentBatch)) { $this ->batchNumber++; } } }
测试迭代器模式 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 <?php namespace Tests \Unit \Iterators ;use Tests \TestCase ;use App \Iterators \ArrayIterator ;use App \Iterators \FilterIterator ;use App \Iterators \MapIterator ;use App \Iterators \Batch \BatchIterator ;class IteratorTest extends TestCase { public function test_array_iterator ( ): void { $items = ['a' , 'b' , 'c' ]; $iterator = new ArrayIterator ($items ); $result = []; foreach ($iterator as $item ) { $result [] = $item ; } $this ->assertEquals ($items , $result ); } public function test_filter_iterator ( ): void { $items = [1 , 2 , 3 , 4 , 5 , 6 ]; $inner = new ArrayIterator ($items ); $iterator = new FilterIterator ($inner , fn ($n ) => $n % 2 === 0 ); $result = []; foreach ($iterator as $item ) { $result [] = $item ; } $this ->assertEquals ([2 , 4 , 6 ], $result ); } public function test_map_iterator ( ): void { $items = [1 , 2 , 3 ]; $inner = new ArrayIterator ($items ); $iterator = new MapIterator ($inner , fn ($n ) => $n * 2 ); $result = []; foreach ($iterator as $item ) { $result [] = $item ; } $this ->assertEquals ([2 , 4 , 6 ], $result ); } public function test_batch_iterator ( ): void { $items = range (1 , 10 ); $inner = new ArrayIterator ($items ); $iterator = new BatchIterator ($inner , 3 ); $batches = []; foreach ($iterator as $batch ) { $batches [] = $batch ; } $this ->assertCount (4 , $batches ); $this ->assertEquals ([1 , 2 , 3 ], $batches [0 ]); $this ->assertEquals ([10 ], $batches [3 ]); } }
最佳实践 1. 实现 Countable 接口 1 2 3 4 5 6 7 8 9 10 11 <?php class CountableIterator implements Iterator , Countable { protected array $items ; public function count ( ): int { return count ($this ->items); } }
2. 实现 SeekableIterator 接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class SeekableIterator implements \SeekableIterator { protected array $items ; protected int $position = 0 ; public function seek (int $position ): void { if (!isset ($this ->items[$position ])) { throw new \OutOfBoundsException ("Position {$position} does not exist" ); } $this ->position = $position ; } }
3. 使用 Generator 简化 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 <?php class GeneratorIterator { public static function fromArray (array $items ): \Generator { foreach ($items as $key => $value ) { yield $key => $value ; } } public static function filter (\Generator $generator , callable $callback ): \Generator { foreach ($generator as $key => $value ) { if ($callback ($value , $key )) { yield $key => $value ; } } } public static function map (\Generator $generator , callable $callback ): \Generator { foreach ($generator as $key => $value ) { yield $key => $callback ($value , $key ); } } }
总结 Laravel 13 的迭代器模式提供了一种统一的方式来遍历各种数据结构。通过合理使用迭代器模式,可以创建灵活、可组合的数据遍历逻辑,同时保持代码的清晰和可维护性。