Add map and iter to doc list
This commit is contained in:
		
							parent
							
								
									37fa200fa7
								
							
						
					
					
						commit
						57d8f9ddc1
					
				| @ -24,6 +24,9 @@ class DocumentList | ||||
|     /** @var TDoc|null $first The first item from the results  */ | ||||
|     private mixed $first = null; | ||||
| 
 | ||||
|     /** @var bool $isConsumed This is set to true once the generator has been exhausted */ | ||||
|     private bool $isConsumed = false; | ||||
| 
 | ||||
|     /** | ||||
|      * Constructor | ||||
|      * | ||||
| @ -39,6 +42,75 @@ class DocumentList | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Does this list have items remaining? | ||||
|      * | ||||
|      * @return bool True if there are items still to be retrieved from the list, false if not | ||||
|      */ | ||||
|     public function hasItems(): bool | ||||
|     { | ||||
|         return !is_null($this->result); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The items from the query result | ||||
|      * | ||||
|      * @return Generator<TDoc> The items from the document list | ||||
|      * @throws DocumentException If this is called once the generator has been consumed | ||||
|      */ | ||||
|     public function items(): Generator | ||||
|     { | ||||
|         if (!$this->result) { | ||||
|             if ($this->isConsumed) { | ||||
|                 throw new DocumentException('Cannot call items() multiple times'); | ||||
|             } | ||||
|             $this->isConsumed = true; | ||||
|             return; | ||||
|         } | ||||
|         yield $this->first; | ||||
|         while ($row = $this->result->fetch(PDO::FETCH_ASSOC)) { | ||||
|             yield $this->mapper->map($row); | ||||
|         } | ||||
|         $this->isConsumed = true; | ||||
|         $this->result     = null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Map items by consuming the generator | ||||
|      * | ||||
|      * @template U The type to which each item should be mapped | ||||
|      * @param callable(TDoc): U $map The mapping function
 | ||||
|      * @return Generator<U> The result of the mapping function
 | ||||
|      * @throws DocumentException If this is called once the generator has been consumed | ||||
|      */ | ||||
|     public function map(callable $map): Generator | ||||
|     { | ||||
|         foreach ($this->items() as $item) { | ||||
|             yield $map($item); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Iterate the generator, running the given function for each item | ||||
|      * | ||||
|      * @param callable(TDoc): void $f The function to run for each item | ||||
|      * @throws DocumentException If this is called once the generator has been consumed | ||||
|      */ | ||||
|     public function iter(callable $f): void | ||||
|     { | ||||
|         foreach ($this->items() as $item) { | ||||
|             $f($item); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Ensure the statement is destroyed if the generator is not exhausted | ||||
|      */ | ||||
|     public function __destruct() | ||||
|     { | ||||
|         if (!is_null($this->result)) $this->result = null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Construct a new document list | ||||
|      * | ||||
| @ -53,37 +125,4 @@ class DocumentList | ||||
|         $stmt = &Custom::runQuery($query, $parameters); | ||||
|         return new static($stmt, $mapper); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The items from the query result | ||||
|      * | ||||
|      * @return Generator<TDoc> The items from the document list | ||||
|      */ | ||||
|     public function items(): Generator | ||||
|     { | ||||
|         if (!$this->result) return; | ||||
|         yield $this->first; | ||||
|         while ($row = $this->result->fetch(PDO::FETCH_ASSOC)) { | ||||
|             yield $this->mapper->map($row); | ||||
|         } | ||||
|         $this->result = null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Does this list have items remaining? | ||||
|      * | ||||
|      * @return bool True if there are items still to be retrieved from the list, false if not | ||||
|      */ | ||||
|     public function hasItems(): bool | ||||
|     { | ||||
|         return !is_null($this->result); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Ensure the statement is destroyed if the generator is not exhausted | ||||
|      */ | ||||
|     public function __destruct() | ||||
|     { | ||||
|         if (!is_null($this->result)) $this->result = null; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -95,7 +95,8 @@ class Parameters | ||||
|         $mode = Configuration::mode('generate field name parameters'); | ||||
|         return match ($mode) { | ||||
|             Mode::PgSQL  => [$paramName => "{" . implode(",", $fieldNames) . "}"], | ||||
|             Mode::SQLite => array_combine(array_map(fn($idx) => $paramName . $idx, range(0, sizeof($fieldNames) - 1)), | ||||
|             Mode::SQLite => array_combine(array_map(fn($idx) => $paramName . $idx, | ||||
|                                               empty($fieldNames) ? [] : range(0, sizeof($fieldNames) - 1)), | ||||
|                                           array_map(fn($field) => "$.$field", $fieldNames)) | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| @ -8,7 +8,7 @@ declare(strict_types=1); | ||||
| 
 | ||||
| namespace Test\Integration\PostgreSQL; | ||||
| 
 | ||||
| use BitBadger\PDODocument\{DocumentList, Query}; | ||||
| use BitBadger\PDODocument\{DocumentException, DocumentList, Query}; | ||||
| use BitBadger\PDODocument\Mapper\DocumentMapper; | ||||
| use PHPUnit\Framework\Attributes\TestDox; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| @ -43,7 +43,7 @@ class DocumentListTest extends TestCase | ||||
|         $list = null; | ||||
|     } | ||||
| 
 | ||||
|     public function testItems(): void | ||||
|     public function testItemsSucceeds(): void | ||||
|     { | ||||
|         $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], | ||||
|             new DocumentMapper(TestDocument::class)); | ||||
| @ -57,6 +57,18 @@ class DocumentListTest extends TestCase | ||||
|         $this->assertEquals(5, $count, 'There should have been 5 documents returned'); | ||||
|     } | ||||
| 
 | ||||
|     public function testItemsFailsWhenAlreadyConsumed(): void | ||||
|     { | ||||
|         $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], | ||||
|             new DocumentMapper(TestDocument::class)); | ||||
|         $this->assertNotNull($list, 'There should have been a document list created'); | ||||
|         $this->assertTrue($list->hasItems(), 'There should be items in the list'); | ||||
|         $ignored = iterator_to_array($list->items()); | ||||
|         $this->assertFalse($list->hasItems(), 'The list should no longer have items'); | ||||
|         $this->expectException(DocumentException::class); | ||||
|         iterator_to_array($list->items()); | ||||
|     } | ||||
| 
 | ||||
|     public function testHasItemsSucceedsWithEmptyResults(): void | ||||
|     { | ||||
|         $list = DocumentList::create( | ||||
| @ -77,4 +89,28 @@ class DocumentListTest extends TestCase | ||||
|         } | ||||
|         $this->assertFalse($list->hasItems(), 'There should be no remaining items in the list'); | ||||
|     } | ||||
| 
 | ||||
|     public function testMapSucceeds(): void | ||||
|     { | ||||
|         $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], | ||||
|             new DocumentMapper(TestDocument::class)); | ||||
|         $this->assertNotNull($list, 'There should have been a document list created'); | ||||
|         $this->assertTrue($list->hasItems(), 'There should be items in the list'); | ||||
|         foreach ($list->map(fn($doc) => strrev($doc->id)) as $mapped) { | ||||
|             $this->assertContains($mapped, ['eno', 'owt', 'eerht', 'ruof', 'evif'], | ||||
|                 'An unexpected mapped value was returned'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function testIterSucceeds(): void | ||||
|     { | ||||
|         $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], | ||||
|             new DocumentMapper(TestDocument::class)); | ||||
|         $this->assertNotNull($list, 'There should have been a document list created'); | ||||
|         $this->assertTrue($list->hasItems(), 'There should be items in the list'); | ||||
|         $splats = []; | ||||
|         $list->iter(function ($doc) use (&$splats) { $splats[] = str_repeat('*', strlen($doc->id)); }); | ||||
|         $this->assertEquals('*** *** ***** **** ****', implode(' ', $splats), | ||||
|             'Iteration did not have the expected result'); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -8,7 +8,7 @@ declare(strict_types=1); | ||||
| 
 | ||||
| namespace Test\Integration\SQLite; | ||||
| 
 | ||||
| use BitBadger\PDODocument\{DocumentList, Query}; | ||||
| use BitBadger\PDODocument\{DocumentException, DocumentList, Query}; | ||||
| use BitBadger\PDODocument\Mapper\DocumentMapper; | ||||
| use PHPUnit\Framework\Attributes\TestDox; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| @ -43,7 +43,7 @@ class DocumentListTest extends TestCase | ||||
|         $list = null; | ||||
|     } | ||||
| 
 | ||||
|     public function testItems(): void | ||||
|     public function testItemsSucceeds(): void | ||||
|     { | ||||
|         $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], | ||||
|             new DocumentMapper(TestDocument::class)); | ||||
| @ -57,6 +57,18 @@ class DocumentListTest extends TestCase | ||||
|         $this->assertEquals(5, $count, 'There should have been 5 documents returned'); | ||||
|     } | ||||
| 
 | ||||
|     public function testItemsFailsWhenAlreadyConsumed(): void | ||||
|     { | ||||
|         $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], | ||||
|             new DocumentMapper(TestDocument::class)); | ||||
|         $this->assertNotNull($list, 'There should have been a document list created'); | ||||
|         $this->assertTrue($list->hasItems(), 'There should be items in the list'); | ||||
|         $ignored = iterator_to_array($list->items()); | ||||
|         $this->assertFalse($list->hasItems(), 'The list should no longer have items'); | ||||
|         $this->expectException(DocumentException::class); | ||||
|         iterator_to_array($list->items()); | ||||
|     } | ||||
| 
 | ||||
|     public function testHasItemsSucceedsWithEmptyResults(): void | ||||
|     { | ||||
|         $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE) . " WHERE data->>'num_value' < 0", [], | ||||
| @ -76,4 +88,27 @@ class DocumentListTest extends TestCase | ||||
|         } | ||||
|         $this->assertFalse($list->hasItems(), 'There should be no remaining items in the list'); | ||||
|     } | ||||
|     public function testMapSucceeds(): void | ||||
|     { | ||||
|         $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], | ||||
|             new DocumentMapper(TestDocument::class)); | ||||
|         $this->assertNotNull($list, 'There should have been a document list created'); | ||||
|         $this->assertTrue($list->hasItems(), 'There should be items in the list'); | ||||
|         foreach ($list->map(fn($doc) => strrev($doc->id)) as $mapped) { | ||||
|             $this->assertContains($mapped, ['eno', 'owt', 'eerht', 'ruof', 'evif'], | ||||
|                 'An unexpected mapped value was returned'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function testIterSucceeds(): void | ||||
|     { | ||||
|         $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], | ||||
|             new DocumentMapper(TestDocument::class)); | ||||
|         $this->assertNotNull($list, 'There should have been a document list created'); | ||||
|         $this->assertTrue($list->hasItems(), 'There should be items in the list'); | ||||
|         $splats = []; | ||||
|         $list->iter(function ($doc) use (&$splats) { $splats[] = str_repeat('*', strlen($doc->id)); }); | ||||
|         $this->assertEquals('*** *** ***** **** ****', implode(' ', $splats), | ||||
|             'Iteration did not have the expected result'); | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user