From a5820c3cb2284a0a5fff824899345f62758d37d2 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 22 Jul 2024 19:50:55 +0800 Subject: [PATCH] Allow array-sourced models to provide records using generators This removes the hard "array" return type for getRecords, and allows people to use generators (or other iterable types) to provide records. This should still be backwards compatible with other uses of the ArraySource trait that may already have a "getRecords" method with an array return type, due to covariance rules in PHP. It should be noted that each record must still be an array, it's only the "container" that can be traversable. --- src/Database/Traits/ArraySource.php | 51 +++++++++++++++++------ tests/Database/Traits/ArraySourceTest.php | 35 +++++++++++++++- 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/Database/Traits/ArraySource.php b/src/Database/Traits/ArraySource.php index 3c2ff5c83..1c9e09fe7 100644 --- a/src/Database/Traits/ArraySource.php +++ b/src/Database/Traits/ArraySource.php @@ -1,4 +1,6 @@ -propertyExists('records')) { if (!is_array($this->records)) { @@ -108,12 +112,21 @@ protected function arraySourceCreateDb(): void File::put($this->arraySourceGetDbPath(), ''); } - $records = $this->getRecords(); - $this->arraySourceCreateTable(); - foreach (array_chunk($records, $this->arraySourceGetChunkSize()) as $inserts) { - static::insert($inserts); + $chunk = []; + + foreach ($this->getRecords() as $insert) { + $chunk[] = $insert; + + if (count($chunk) >= $this->arraySourceGetChunkSize()) { + static::insert($chunk); + $chunk = []; + } + } + + if (count($chunk)) { + static::insert($chunk); } } @@ -130,12 +143,22 @@ protected function arraySourceCreateTable(): void $schema = ($this->propertyExists('recordSchema')) ? $this->recordSchema : []; - $firstRecord = $this->getRecords()[0] ?? []; - if (empty($schema) && empty($firstRecord)) { - throw new ApplicationException( - 'A model using the ArraySource trait must either provide "$records" or "$recordSchema" as an array.' - ); + $firstRecord = []; + + // Otherwise, detect the schema from the first record + if (empty($schema)) { + foreach ($this->getRecords() as $record) { + $firstRecord = $record; + break; + } + + if (empty($schema) && empty($firstRecord)) { + throw new ApplicationException( + 'A model using the ArraySource trait must either provide "$records" or "$recordSchema" + as an array.' + ); + } } // Add incrementing field based on the primary key if the key is not found in the first record or schema @@ -222,6 +245,10 @@ protected function arraySourceResolveDatatype($value): string return 'dateTime'; } + if (is_array($value) || is_object($value)) { + return 'text'; + } + return 'string'; } @@ -303,6 +330,6 @@ protected function arraySourceDbNeedsUpdate(): bool */ protected function arraySourceGetChunkSize(): int { - return 100; + return 50; } } diff --git a/tests/Database/Traits/ArraySourceTest.php b/tests/Database/Traits/ArraySourceTest.php index 52b9c9628..e808c4057 100644 --- a/tests/Database/Traits/ArraySourceTest.php +++ b/tests/Database/Traits/ArraySourceTest.php @@ -92,6 +92,16 @@ public function testRelations(): void $this->assertEquals(18, $records->last()->states()->get()->last()->id); $this->assertEquals('Newfoundland and Labrador', $records->last()->states()->get()->last()->name); } + + public function testGeneratorRecords(): void + { + $records = Random::get(); + + $this->assertEquals(120, $records->count()); + + $record = Random::find(10); + $this->assertEquals('Record 10', $record->name); + } } class ArrayModel extends \Winter\Storm\Database\Model @@ -125,7 +135,10 @@ class ArrayModel extends \Winter\Storm\Database\Model ], ]; - public $arraySchema = [ + public $recordSchema = [ + 'id' => 'integer', + 'name' => 'string', + 'role' => 'string', 'start_year' => 'integer', ]; @@ -248,3 +261,23 @@ protected function arraySourceGetDbDir(): string|false return dirname(dirname(__DIR__)) . '/tmp'; } } + +class Random extends \Winter\Storm\Database\Model +{ + use \Winter\Storm\Database\Traits\ArraySource; + + public $schema = [ + 'name' => 'string', + 'random_int' => 'integer', + ]; + + public function getRecords() + { + for ($i = 1; $i <= 120; ++$i) { + yield [ + 'name' => 'Record ' . $i, + 'random_int' => random_int(1, 120), + ]; + } + } +}