From 3861bf64c55547f271c19b2a5aa22b7bf85c9ae2 Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 12 Oct 2026 13:17:15 +1100 Subject: [PATCH] Rebind PDO statement parameters on every execute bindParametersFromContainer() returned early once parameters had been bound, so re-executing a prepared statement with new values (via an array, a replacement ParameterContainer, or a mutated container) ran again with the first values. Bind from the current container on every execute instead. --- lint-baseline.toml | 6 -- src/Adapter/Driver/Pdo/Statement.php | 4 -- .../unit/Adapter/Driver/Pdo/StatementTest.php | 72 +++++++++++++------ 3 files changed, 49 insertions(+), 33 deletions(-) diff --git a/lint-baseline.toml b/lint-baseline.toml index 98558a29..31d3650c 100644 --- a/lint-baseline.toml +++ b/lint-baseline.toml @@ -72,12 +72,6 @@ code = "halstead" message = "Method has a high halstead difficulty" count = 1 -[[issues]] -file = "src/Adapter/Driver/Pdo/Statement.php" -code = "kan-defect" -message = "Class has a high kan defect score." -count = 1 - [[issues]] file = "src/Adapter/Driver/Pdo/Statement.php" code = "literal-named-argument" diff --git a/src/Adapter/Driver/Pdo/Statement.php b/src/Adapter/Driver/Pdo/Statement.php index 794acd37..fd52991f 100644 --- a/src/Adapter/Driver/Pdo/Statement.php +++ b/src/Adapter/Driver/Pdo/Statement.php @@ -195,10 +195,6 @@ public function setSql(?string $sql): StatementContainerInterface /** Bind parameters from container */ protected function bindParametersFromContainer(): void { - if ($this->parametersBound) { - return; - } - $parameters = $this->parameterContainer->getNamedArray(); $errata = $this->parameterContainer->getErrataIterator()->getArrayCopy(); diff --git a/test/unit/Adapter/Driver/Pdo/StatementTest.php b/test/unit/Adapter/Driver/Pdo/StatementTest.php index f95ff1a6..0ad71ca3 100644 --- a/test/unit/Adapter/Driver/Pdo/StatementTest.php +++ b/test/unit/Adapter/Driver/Pdo/StatementTest.php @@ -10,6 +10,7 @@ use PDOStatement; use PhpDb\Adapter\Driver\Pdo\Result; use PhpDb\Adapter\Driver\Pdo\Statement; +use PhpDb\Adapter\Driver\ResultInterface; use PhpDb\Adapter\Exception\InvalidQueryException; use PhpDb\Adapter\Exception\RuntimeException; use PhpDb\Adapter\ParameterContainer; @@ -24,6 +25,7 @@ use PHPUnit\Framework\TestCase; use ReflectionProperty; +use function is_array; use function sprintf; #[CoversMethod(Statement::class, 'setDriver')] @@ -94,19 +96,6 @@ public function bindParametersDetectsNullValueType(): void static::assertInstanceOf(Result::class, $result); } - #[Test] - public function bindParametersFromContainerSkipsWhenAlreadyBound(): void - { - $pdo = new SqliteMemoryPdo(); - $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); - $this->statement->initialize($pdo); - $this->statement->setSql('SELECT :val'); - $this->statement->setParameterContainer(new ParameterContainer(['val' => 'first'])); - - $result1 = $this->statement->execute(); - static::assertInstanceOf(Result::class, $result1); - } - #[Test] public function bindParametersWithErrataTypeDefaultsToString(): void { @@ -493,19 +482,39 @@ public function prepareThrowsWhenAlreadyPrepared(): void } #[Test] - public function secondExecuteSkipsBindingWhenAlreadyBound(): void + public function reExecutingAfterMutatingTheParameterContainerUsesTheNewValues(): void { - $pdo = new SqliteMemoryPdo(); - $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); - $this->statement->initialize($pdo); - $this->statement->setSql('SELECT :val'); - $this->statement->setParameterContainer(new ParameterContainer(['val' => 'test'])); + $this->prepareLookupStatement(); + $container = new ParameterContainer(['id' => 1]); + $this->statement->setParameterContainer($container); + + $first = $this->fetchName($this->statement->execute()); + $container->offsetSet('id', 2); + $second = $this->fetchName($this->statement->execute()); + + static::assertSame(['one', 'two'], [$first, $second]); + } + + #[Test] + public function reExecutingWithNewArrayParametersUsesTheNewValues(): void + { + $this->prepareLookupStatement(); - $result1 = $this->statement->execute(); - static::assertInstanceOf(Result::class, $result1); + $first = $this->fetchName($this->statement->execute(['id' => 1])); + $second = $this->fetchName($this->statement->execute(['id' => 2])); - $result2 = $this->statement->execute(); - static::assertInstanceOf(Result::class, $result2); + static::assertSame(['one', 'two'], [$first, $second]); + } + + #[Test] + public function reExecutingWithReplacementParameterContainerUsesTheNewValues(): void + { + $this->prepareLookupStatement(); + + $first = $this->fetchName($this->statement->execute(new ParameterContainer(['id' => 1]))); + $second = $this->fetchName($this->statement->execute(new ParameterContainer(['id' => 2]))); + + static::assertSame(['one', 'two'], [$first, $second]); } #[Test] @@ -550,4 +559,21 @@ protected function setUp(): void * This method is called after a test is executed. */ protected function tearDown(): void {} + + private function fetchName(?ResultInterface $result): mixed + { + $row = $result?->current(); + + return is_array($row) ? $row['name'] ?? null : null; + } + + private function prepareLookupStatement(): void + { + $pdo = new SqliteMemoryPdo( + "CREATE TABLE t (id INTEGER, name TEXT); INSERT INTO t VALUES (1, 'one'), (2, 'two');", + ); + $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); + $this->statement->initialize($pdo); + $this->statement->setSql('SELECT name FROM t WHERE id = :id'); + } }