diff --git a/src/Mapping.php b/src/Mapping.php index 9227b8e..9c3b123 100644 --- a/src/Mapping.php +++ b/src/Mapping.php @@ -538,30 +538,30 @@ private function map(array $data) foreach ($this->definition->getProperties() as $property) { $mapping = new static($this->db, $property->getDefinition(), [$property->getForeignColumn()]); + $localValues = array_unique(array_filter(array_column($data, $property->getLocalColumn()), fn($v) => $v !== null)); + if ($property->getJoinTable()) { $mapping->columns[] = sprintf('%s.%s', $property->getJoinTable(), $property->getJoinForeignColumn()); $mapping ->join($property->getJoinTable(), $property->getJoinLocalColumn(), $property->getForeignColumn()) - ->in(sprintf('%s.%s', $property->getJoinTable(), $property->getJoinForeignColumn()), array_column($data, $property->getLocalColumn())); + ->in(sprintf('%s.%s', $property->getJoinTable(), $property->getJoinForeignColumn()), $localValues); $groupColumn = $property->getJoinForeignColumn(); } else { - $mapping->in($property->getForeignColumn(), array_column($data, $property->getLocalColumn())); + $mapping->in($property->getForeignColumn(), $localValues); $groupColumn = $property->getForeignColumn(); } $results = $mapping ->findAll(); - $properties[$property->getName()] = Collection::group($results, function ($result) use ($groupColumn) { - return $result[$groupColumn]; - }); + $properties[$property->getName()] = array_map(fn($group) => array_values($group), Collection::group($results, fn ($result) => $result[$groupColumn])); } $mapped = []; foreach ($data as $item) { foreach ($this->definition->getProperties() as $property) { - $value = $properties[$property->getName()][$item[$property->getLocalColumn()]] ?? []; - $value = array_values($value); + $localValue = $item[$property->getLocalColumn()]; + $value = $localValue !== null ? ($properties[$property->getName()][$localValue] ?? []) : []; if (!$property->isCollection()) { $value = array_shift($value); diff --git a/tests/MappingTest.php b/tests/MappingTest.php index b7d31dc..56d0a21 100644 --- a/tests/MappingTest.php +++ b/tests/MappingTest.php @@ -560,6 +560,75 @@ public function testFindOneWithDirectAndSecondaryLeft() $this->assertEquals(400, $customer['orders'][0]['items'][1]['amount']); } + public function testFindAllWithDuplicateLocalColumnValues() + { + $this->db->execute('CREATE TABLE categories (id INTEGER PRIMARY KEY, label TEXT)'); + $this->db->execute('CREATE TABLE products (id INTEGER PRIMARY KEY, category_id INTEGER, name TEXT)'); + $this->db->execute("INSERT INTO categories (id, label) VALUES (1, 'Electronics')"); + $this->db->execute("INSERT INTO products (id, category_id, name) VALUES (1, 1, 'Phone'), (2, 1, 'Laptop'), (3, NULL, 'Unknown')"); + + $category = (new Definition('categories'))->withColumns('id', 'label'); + $product = (new Definition('products')) + ->withColumns('id', 'category_id', 'name') + ->withOne($category, 'category', 'id', 'category_id'); + + $mapping = new Mapping($this->db, $product); + $products = $mapping->findAll(); + + $this->assertCount(3, $products); + $this->assertEquals('Electronics', $products[0]['category']['label']); + $this->assertEquals('Electronics', $products[1]['category']['label']); + $this->assertNull($products[2]['category']); + } + + public function testFindAllWithManyAndNullableLocalColumn() + { + $this->db->execute('CREATE TABLE sections (id INTEGER PRIMARY KEY, name TEXT)'); + $this->db->execute('CREATE TABLE articles (id INTEGER PRIMARY KEY, section_id INTEGER, title TEXT)'); + $this->db->execute('CREATE TABLE tags (id INTEGER PRIMARY KEY, section_id INTEGER, label TEXT)'); + $this->db->execute("INSERT INTO sections (id, name) VALUES (1, 'Tech')"); + $this->db->execute("INSERT INTO articles (id, section_id, title) VALUES (1, 1, 'PHP 8.5'), (2, NULL, 'Unclassified')"); + $this->db->execute("INSERT INTO tags (id, section_id, label) VALUES (1, 1, 'php'), (2, 1, 'programming')"); + + $tag = (new Definition('tags'))->withColumns('id', 'section_id', 'label'); + $article = (new Definition('articles')) + ->withColumns('id', 'section_id', 'title') + ->withMany($tag, 'tags', 'section_id', 'section_id'); + + $mapping = new Mapping($this->db, $article); + $articles = $mapping->findAll(); + + $this->assertCount(2, $articles); + $this->assertCount(2, $articles[0]['tags']); + $this->assertEquals('php', $articles[0]['tags'][0]['label']); + $this->assertEquals('programming', $articles[0]['tags'][1]['label']); + $this->assertCount(0, $articles[1]['tags']); + } + + public function testFindAllWithNullableLocalColumn() + { + $this->db->execute('CREATE TABLE categories (id INTEGER PRIMARY KEY, label TEXT)'); + $this->db->execute('CREATE TABLE products (id INTEGER PRIMARY KEY, category_id INTEGER, name TEXT)'); + $this->db->execute("INSERT INTO categories (id, label) VALUES (1, 'Electronics')"); + $this->db->execute("INSERT INTO products (id, category_id, name) VALUES (1, 1, 'Phone'), (2, NULL, 'Unknown')"); + + $category = (new Definition('categories'))->withColumns('id', 'label'); + $product = (new Definition('products')) + ->withColumns('id', 'category_id', 'name') + ->withOne($category, 'category', 'id', 'category_id'); + + $mapping = new Mapping($this->db, $product); + $products = $mapping->findAll(); + + $this->assertCount(2, $products); + + $this->assertEquals('Phone', $products[0]['name']); + $this->assertEquals('Electronics', $products[0]['category']['label']); + + $this->assertEquals('Unknown', $products[1]['name']); + $this->assertNull($products[1]['category']); + } + /** * Returns a new mapping for testing. *