From 49fc10b8c95eba393e895f1e690bbc3c4ed038c7 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 20 Oct 2023 13:15:20 +0200 Subject: [PATCH 1/4] test: Add test whether ambiguous columns are mapped correctly --- tests/Audit.php | 2 ++ tests/HydratorTest.php | 25 +++++++++++++++++++++++++ tests/SqlTest.php | 3 ++- tests/Subsystem.php | 29 +++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 tests/HydratorTest.php create mode 100644 tests/Subsystem.php diff --git a/tests/Audit.php b/tests/Audit.php index 52fd3f1..32f1d76 100644 --- a/tests/Audit.php +++ b/tests/Audit.php @@ -22,11 +22,13 @@ public function getColumns() return [ 'user_id', 'activity', + 'subsystem_id' ]; } public function createRelations(Relations $relations) { $relations->hasOne('user', User::class); + $relations->belongsToOne('subsystem', Subsystem::class); } } diff --git a/tests/HydratorTest.php b/tests/HydratorTest.php new file mode 100644 index 0000000..e9ce3c8 --- /dev/null +++ b/tests/HydratorTest.php @@ -0,0 +1,25 @@ +with(['audit', 'audit.user']); + + $hydrator = $query->createHydrator(); + + $subject = new Subsystem(); + $hydrator->hydrate(['subsystem_audit_user_id' => 2], $subject); + + $this->assertTrue(isset($subject->audit->user_id), 'Ambiguous columns are not mapped correctly'); + $this->assertSame($subject->audit->user_id, 2, 'Ambiguous columns are not mapped correctly'); + + $this->assertTrue(isset($subject->audit->user->id), 'Ambiguous columns are not mapped correctly'); + $this->assertSame($subject->audit->user->id, 2, 'Ambiguous columns are not mapped correctly'); + } +} diff --git a/tests/SqlTest.php b/tests/SqlTest.php index 352e986..e4847cd 100644 --- a/tests/SqlTest.php +++ b/tests/SqlTest.php @@ -354,7 +354,8 @@ public function testSelectFromModelWithNestedWith() group_user.password AS group_user_password, group_user_audit.id AS group_user_audit_id, group_user_audit.user_id AS group_user_audit_user_id, - group_user_audit.activity AS group_user_audit_activity + group_user_audit.activity AS group_user_audit_activity, + group_user_audit.subsystem_id AS group_user_audit_subsystem_id FROM group INNER JOIN diff --git a/tests/Subsystem.php b/tests/Subsystem.php new file mode 100644 index 0000000..85ee6d9 --- /dev/null +++ b/tests/Subsystem.php @@ -0,0 +1,29 @@ +hasMany('audit', Audit::class); + } +} From b272e78652a83c26255a0d5cb3dbd2a62cb6b90d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 20 Oct 2023 13:26:12 +0200 Subject: [PATCH 2/4] Hydrator: Fix that columns can only be mapped once Given the two relations `a.b` and `a.b.c` and `a.b.c_id` == `a.b.c.id`, both paths are qualified to `a_b_c_id`, resulting in only one column being returned by the datasource. Such a column must be kept and shared until the last reference is fulfilled. --- src/Hydrator.php | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/Hydrator.php b/src/Hydrator.php index e3cd23d..55617fe 100644 --- a/src/Hydrator.php +++ b/src/Hydrator.php @@ -13,6 +13,9 @@ class Hydrator /** @var array Additional hydration rules for the model's relations */ protected $hydrators = []; + /** @var array> Map of columns to referencing paths */ + protected $columnToTargetMap = []; + /** @var Query The query the hydration rules are for */ protected $query; @@ -70,11 +73,31 @@ public function add($path) } } + $this->updateColumnToTargetMap($path, $columnToPropertyMap); $this->hydrators[$path] = [$target, $relation, $columnToPropertyMap, $defaults]; return $this; } + /** + * Update which columns the given path is referencing + * + * @param string $path + * @param array $columnToPropertyMap + * + * @return void + */ + protected function updateColumnToTargetMap(string $path, array $columnToPropertyMap): void + { + foreach ($columnToPropertyMap as $qualifiedColumnPath => $_) { + if (isset($this->columnToTargetMap[$qualifiedColumnPath])) { + $this->columnToTargetMap[$qualifiedColumnPath][$path] = true; + } else { + $this->columnToTargetMap[$qualifiedColumnPath] = [$path => true]; + } + } + } + /** * Hydrate the given raw database rows into the specified model * @@ -120,7 +143,7 @@ public function hydrate(array $data, Model $model) } } - $subject->setProperties($this->extractAndMap($data, $columnToPropertyMap)); + $subject->setProperties($this->extractAndMap($data, $columnToPropertyMap, $path)); $this->query->getResolver()->getBehaviors($target)->retrieve($subject); $defaultsToApply[] = [$subject, $defaults]; } @@ -181,15 +204,23 @@ public function hydrate(array $data, Model $model) * * @param array $data * @param array $columnToPropertyMap + * @param string $path * * @return array */ - protected function extractAndMap(array &$data, array $columnToPropertyMap) + protected function extractAndMap(array &$data, array $columnToPropertyMap, string $path) { $extracted = []; foreach (array_intersect_key($columnToPropertyMap, $data) as $column => $property) { $extracted[$property] = $data[$column]; - unset($data[$column]); + + if (isset($this->columnToTargetMap[$column][$path])) { + unset($this->columnToTargetMap[$column][$path]); + if (empty($this->columnToTargetMap[$column])) { + // Only unset a column once it's really not required anymore + unset($data[$column], $this->columnToTargetMap[$column]); + } + } } return $extracted; From 8eb87400d7c41fa6d3425a47cc01d2278f1a58e6 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 20 Oct 2023 13:37:57 +0200 Subject: [PATCH 3/4] Binary: Rewind stream after reading contents --- src/Behavior/Binary.php | 5 ++++- tests/Behavior/BinaryTest.php | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Behavior/Binary.php b/src/Behavior/Binary.php index c43082a..602c9c7 100644 --- a/src/Behavior/Binary.php +++ b/src/Behavior/Binary.php @@ -29,7 +29,10 @@ public function fromDb($value, $key, $_) if ($value !== null) { if (is_resource($value)) { - return stream_get_contents($value); + $content = stream_get_contents($value); + rewind($value); + + return $content; } return $value; diff --git a/tests/Behavior/BinaryTest.php b/tests/Behavior/BinaryTest.php index 36c12c4..fc97aa7 100644 --- a/tests/Behavior/BinaryTest.php +++ b/tests/Behavior/BinaryTest.php @@ -37,6 +37,22 @@ public function testRetrievePropertyReturnsStreamContentsIfAdapterIsPostgreSQL() ); } + public function testRetrievePropertyRewindsAStreamIfAdapterIsPostgreSQL(): void + { + $stream = fopen('php://temp', 'r+'); + fputs($stream, static::TEST_BINARY_VALUE); + rewind($stream); + + $this->assertSame( + static::TEST_BINARY_VALUE, + $this->behavior(true)->retrieveProperty($stream, static::TEST_COLUMN) + ); + $this->assertSame( + static::TEST_BINARY_VALUE, + $this->behavior(true)->retrieveProperty($stream, static::TEST_COLUMN) + ); + } + public function testPersistPropertyReturnsVanillaValueIfAdapterIsNotPostgreSQL(): void { $this->assertSame( From 22fd9736f8fcbb9b66506a159dee44315fcb46c9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 20 Oct 2023 13:46:04 +0200 Subject: [PATCH 4/4] Update phpstan baseline --- phpstan-baseline.neon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d677a2d..3fc5ac7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -602,12 +602,12 @@ parameters: - message: "#^Cannot access offset non\\-falsy\\-string on mixed\\.$#" - count: 3 + count: 2 path: src/Resolver.php - message: "#^Cannot access offset string on mixed\\.$#" - count: 6 + count: 5 path: src/Resolver.php -