Skip to content

Commit 0e67e4d

Browse files
authored
Merge branch '0.6.x' into patch-metadata-abstract-source-get-column
2 parents 357c611 + cb328d3 commit 0e67e4d

4 files changed

Lines changed: 116 additions & 5 deletions

File tree

src/Adapter/Driver/Pdo/Result.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class Result implements Iterator, ResultInterface
8181
/** @var string|int|false|null */
8282
protected $generatedValue;
8383

84-
protected Closure|int $rowCount;
84+
protected Closure|int|null $rowCount = null;
8585

8686
/**
8787
* Initialize
@@ -91,7 +91,7 @@ class Result implements Iterator, ResultInterface
9191
public function initialize(
9292
PDOStatement $resource,
9393
$generatedValue,
94-
Closure|int $rowCount = 0
94+
Closure|int|null $rowCount = null
9595
): ResultInterface&Result {
9696
$this->resource = $resource;
9797
$this->generatedValue = $generatedValue;
@@ -250,7 +250,6 @@ public function count()
250250
if (is_int($this->rowCount)) {
251251
return $this->rowCount;
252252
}
253-
/** @phpstan-ignore instanceof.alwaysTrue */
254253
if ($this->rowCount instanceof Closure) {
255254
$this->rowCount = (int) ($this->rowCount)();
256255
} else {

src/Adapter/Driver/Pdo/Statement.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
use function is_array;
2323
use function is_int;
2424
use function ltrim;
25+
use function preg_match;
26+
use function sprintf;
2527

2628
class Statement implements StatementInterface, PdoDriverAwareInterface, ProfilerAwareInterface
2729
{
@@ -223,8 +225,19 @@ protected function bindParametersFromContainer(): void
223225
};
224226
}
225227

226-
// parameter is named or positional, value is reference
227-
$parameter = is_int($name) ? $name + 1 : ':' . ltrim($name, ':');
228+
if (is_int($name)) {
229+
$parameter = $name + 1;
230+
} else {
231+
if (! preg_match('/^:?[a-zA-Z0-9_]+$/', $name)) {
232+
throw new Exception\RuntimeException(sprintf(
233+
'The PDO param "%s" contains invalid characters.'
234+
. ' Only alphabetic characters, digits, and underscores (_) are allowed.',
235+
$name
236+
));
237+
}
238+
$parameter = ':' . ltrim($name, ':');
239+
}
240+
228241
$this->resource->bindParam($parameter, $value, $type);
229242
}
230243

test/unit/Adapter/Driver/Pdo/ResultTest.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use function uniqid;
1818

1919
#[CoversMethod(Result::class, 'current')]
20+
#[CoversMethod(Result::class, 'count')]
2021
#[Group('result-pdo')]
2122
final class ResultTest extends TestCase
2223
{
@@ -108,4 +109,74 @@ public function testMultipleRewind(): void
108109
$this->assertEquals(2, $position);
109110
$this->assertEquals($data[1], $result->current());
110111
}
112+
113+
public function testCountWithNoRowCountFallsBackToStatementRowCount(): void
114+
{
115+
$stub = $this->getMockBuilder(PDOStatement::class)->getMock();
116+
$stub->expects($this->once())
117+
->method('rowCount')
118+
->willReturn(5);
119+
120+
$result = new Result();
121+
$result->initialize($stub, null);
122+
123+
self::assertSame(5, $result->count());
124+
}
125+
126+
public function testCountWithClosureInvokesClosureAndReturnsValue(): void
127+
{
128+
$stub = $this->getMockBuilder(PDOStatement::class)->getMock();
129+
$stub->expects($this->never())->method('rowCount');
130+
131+
$rowCount = static fn(): int => 42;
132+
133+
$result = new Result();
134+
$result->initialize($stub, null, $rowCount);
135+
136+
self::assertSame(42, $result->count());
137+
}
138+
139+
public function testCountWithIntReturnsProvidedValue(): void
140+
{
141+
$stub = $this->getMockBuilder(PDOStatement::class)->getMock();
142+
$stub->expects($this->never())->method('rowCount');
143+
144+
$result = new Result();
145+
$result->initialize($stub, null, 10);
146+
147+
self::assertSame(10, $result->count());
148+
}
149+
150+
public function testCountCachesResultFromClosure(): void
151+
{
152+
$callCount = 0;
153+
$rowCount = static function () use (&$callCount): int {
154+
$callCount++;
155+
return 7;
156+
};
157+
158+
$stub = $this->getMockBuilder(PDOStatement::class)->getMock();
159+
160+
$result = new Result();
161+
$result->initialize($stub, null, $rowCount);
162+
163+
$result->count();
164+
$result->count();
165+
166+
self::assertSame(1, $callCount);
167+
}
168+
169+
public function testCountCachesResultFromStatementRowCount(): void
170+
{
171+
$stub = $this->getMockBuilder(PDOStatement::class)->getMock();
172+
$stub->expects($this->once())->method('rowCount')->willReturn(3);
173+
174+
$result = new Result();
175+
$result->initialize($stub, null);
176+
177+
$result->count();
178+
$result->count();
179+
180+
self::assertSame(3, $result->count());
181+
}
111182
}

test/unit/Adapter/Driver/Pdo/StatementTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
use Override;
88
use PhpDb\Adapter\Driver\Pdo\Result;
99
use PhpDb\Adapter\Driver\Pdo\Statement;
10+
use PhpDb\Adapter\Exception\RuntimeException;
1011
use PhpDb\Adapter\ParameterContainer;
1112
use PhpDbTest\Adapter\Driver\Pdo\TestAsset\TestConnection;
1213
use PhpDbTest\Adapter\Driver\Pdo\TestAsset\TestPdo;
1314
use PHPUnit\Framework\Attributes\CoversMethod;
15+
use PHPUnit\Framework\Attributes\DataProvider;
1416
use PHPUnit\Framework\TestCase;
1517

1618
#[CoversMethod(Statement::class, 'setDriver')]
@@ -22,6 +24,7 @@
2224
#[CoversMethod(Statement::class, 'prepare')]
2325
#[CoversMethod(Statement::class, 'isPrepared')]
2426
#[CoversMethod(Statement::class, 'execute')]
27+
#[CoversMethod(Statement::class, 'bindParametersFromContainer')]
2528
final class StatementTest extends TestCase
2629
{
2730
protected Statement $statement;
@@ -111,4 +114,29 @@ public function testExecute(): void
111114
$this->statement->prepare('SELECT 1');
112115
self::assertInstanceOf(Result::class, $this->statement->execute());
113116
}
117+
118+
/** @return array<string, array{string}> */
119+
public static function invalidParameterNameProvider(): array
120+
{
121+
return [
122+
'dollar sign' => ['tz$'],
123+
'with colon' => [':tz$'],
124+
'hyphen' => ['my-param'],
125+
'space' => ['my param'],
126+
'dot' => ['my.param'],
127+
'at sign' => ['param@name'],
128+
];
129+
}
130+
131+
#[DataProvider('invalidParameterNameProvider')]
132+
public function testExecuteThrowsOnInvalidParameterName(string $name): void
133+
{
134+
$this->statement->setDriver(new TestPdo(new TestConnection($pdo = new TestAsset\SqliteMemoryPdo())));
135+
$this->statement->initialize($pdo);
136+
$this->statement->prepare('SELECT 1');
137+
138+
$this->expectException(RuntimeException::class);
139+
$this->expectExceptionMessage('contains invalid characters');
140+
$this->statement->execute([$name => 'value']);
141+
}
114142
}

0 commit comments

Comments
 (0)