Skip to content

Commit cf767ba

Browse files
committed
mysqli issues with findAll and unit test fixes.
1 parent e44cfab commit cf767ba

File tree

5 files changed

+117
-12
lines changed

5 files changed

+117
-12
lines changed

src/ActiveRecord.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -568,11 +568,11 @@ public function execute(string $sql, array $params = []): DatabaseStatementInter
568568
* helper function to query one record by sql and params.
569569
* @param string $sql The SQL to find record.
570570
* @param array $param The param will be bind to PDOStatement.
571-
* @param ActiveRecord $obj The object, if find record in database, will assign the attributes in to this object.
571+
* @param ActiveRecord|null $obj The object, if find record in database, will assign the attributes in to this object.
572572
* @param bool $single if set to true, will find record and fetch in current object, otherwise will find all records.
573573
* @return bool|ActiveRecord|array
574574
*/
575-
public function query(string $sql, array $param = [], ActiveRecord $obj = null, bool $single = false)
575+
public function query(string $sql, array $param = [], ?ActiveRecord $obj = null, bool $single = false)
576576
{
577577
$called_class = get_called_class();
578578
$obj = $obj ?: new $called_class($this->databaseConnection);

src/database/mysqli/MysqliStatementAdapter.php

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ class MysqliStatementAdapter implements DatabaseStatementInterface
1212
{
1313
private mysqli_stmt $statement;
1414

15+
/** @var array */
16+
private $allResults = [];
17+
18+
/** @var int */
19+
private $allResultsCount = 0;
20+
21+
/** @var int */
22+
private $resultIndex = 0;
23+
1524
/**
1625
* Construct
1726
*
@@ -46,8 +55,30 @@ public function execute(array $params = []): bool
4655
public function fetch(&$object)
4756
{
4857

49-
$raw_result = $this->statement->get_result();
50-
$result = $raw_result->fetch_assoc();
58+
// If there are no more results to fetch, return false.
59+
if ($this->allResultsCount > 0 && $this->resultIndex >= $this->allResultsCount) {
60+
return false;
61+
}
62+
63+
// If it hasn't run the query just yet, run it and store all results in the object.
64+
if ($this->resultIndex === 0) {
65+
$raw_result = $this->statement->get_result();
66+
if ($raw_result === false) {
67+
throw new Exception($this->getErrorList()[0]['error']);
68+
}
69+
70+
while ($row = $raw_result->fetch_assoc()) {
71+
$this->allResults[] = $row;
72+
++$this->allResultsCount;
73+
}
74+
}
75+
76+
// No results to fetch
77+
if ($this->allResultsCount === 0) {
78+
return false;
79+
}
80+
81+
$result = $this->allResults[$this->resultIndex++];
5182
if ($result) {
5283
foreach ($result as $key => $value) {
5384
$object->{$key} = $value;

tests/ActiveRecordMysqliTest.php

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use mysqli_stmt;
1414
use PDO;
1515
use ReflectionClass;
16+
use stdClass;
1617

1718
class ActiveRecordMysqliTest extends \PHPUnit\Framework\TestCase
1819
{
@@ -75,7 +76,7 @@ public function testQuery()
7576
$mysqli = $this->createMock(mysqli::class);
7677
$mysqli_stmt = $this->createMock(mysqli_stmt::class);
7778
$mysqli_result = $this->createMock(mysqli_result::class);
78-
$mysqli_result->method('fetch_assoc')->willReturn(['id' => 1, 'name' => 'demo', 'password' => md5('demo')]);
79+
$mysqli_result->method('fetch_assoc')->will($this->onConsecutiveCalls(['id' => 1, 'name' => 'demo', 'password' => md5('demo')], false));
7980
$mysqli_stmt->method('execute')->willReturn(true);
8081
$mysqli_stmt->method('bind_param')->willReturn(true);
8182
$mysqli_stmt->method('get_result')->willReturn($mysqli_result);
@@ -96,6 +97,68 @@ public function lastInsertId()
9697
$this->assertSame('demo', $user->name);
9798
}
9899

100+
public function testQueryBadResult()
101+
{
102+
$mysqli_stmt = $this->createMock(mysqli_stmt::class);
103+
$mysqli_stmt->method('get_result')->willReturn(false);
104+
$MysqliStatementAdapter = new class ($mysqli_stmt) extends MysqliStatementAdapter {
105+
protected function getErrorList(): array
106+
{
107+
return [ [ 'sqlstate' => 'HY000', 'errno' => 1, 'error' => 'No results found'] ];
108+
}
109+
};
110+
$this->expectException(Exception::class);
111+
$this->expectExceptionMessage('No results found');
112+
$obj = new stdClass();
113+
$MysqliStatementAdapter->fetch($obj);
114+
}
115+
116+
public function testQueryWithFindAll()
117+
{
118+
$mysqli = $this->createMock(mysqli::class);
119+
$mysqli_stmt = $this->createMock(mysqli_stmt::class);
120+
$mysqli_result = $this->createMock(mysqli_result::class);
121+
$mysqli_result->method('fetch_assoc')->will($this->onConsecutiveCalls(
122+
['id' => 1, 'name' => 'demo', 'password' => md5('demo')],
123+
['id' => 2, 'name' => 'demo2', 'password' => md5('demo2')],
124+
false
125+
));
126+
$mysqli_stmt->method('execute')->willReturn(true);
127+
$mysqli_stmt->method('bind_param')->willReturn(true);
128+
$mysqli_stmt->method('get_result')->willReturn($mysqli_result);
129+
$mysqli->method('prepare')->willReturn($mysqli_stmt);
130+
$connection = new class ($mysqli) extends MysqliAdapter {
131+
public function lastInsertId()
132+
{
133+
return 1;
134+
}
135+
};
136+
$user = new User($connection);
137+
$users = $user->isNotNull('id')->gt('id', 0)->findAll();
138+
$this->assertCount(2, $users);
139+
$this->assertSame(1, $users[0]->id);
140+
$this->assertSame('demo', $users[0]->name);
141+
$this->assertSame(2, $users[1]->id);
142+
$this->assertSame('demo2', $users[1]->name);
143+
}
144+
145+
public function testQueryWithFindAllNoResults()
146+
{
147+
$mysqli = $this->createMock(mysqli::class);
148+
$mysqli_stmt = $this->createMock(mysqli_stmt::class);
149+
$mysqli_result = $this->createMock(mysqli_result::class);
150+
$mysqli_result->method('fetch_assoc')->willReturn(false);
151+
$mysqli_stmt->method('execute')->willReturn(true);
152+
$mysqli_stmt->method('bind_param')->willReturn(true);
153+
$mysqli_stmt->method('get_result')->willReturn($mysqli_result);
154+
$mysqli->method('prepare')->willReturn($mysqli_stmt);
155+
$connection = new class ($mysqli) extends MysqliAdapter {
156+
};
157+
$user = new User($connection);
158+
$users = $user->isNotNull('id')->gt('id', 0)->findAll();
159+
$this->assertEmpty($users);
160+
}
161+
99162
public function testDelete()
100163
{
101164
$mysqli = $this->createMock(mysqli::class);
@@ -139,7 +202,7 @@ public function testFetchWithParams()
139202
{
140203
$mysqli_stmt = $this->createMock(mysqli_stmt::class);
141204
$mysqli_result = $this->createMock(mysqli_result::class);
142-
$mysqli_result->method('fetch_assoc')->willReturn(['id' => 1, 'name' => 'demo', 'password' => md5('demo')]);
205+
$mysqli_result->method('fetch_assoc')->will($this->onConsecutiveCalls(['id' => 1, 'name' => 'demo', 'password' => md5('demo')], false));
143206
$mysqli_stmt->method('get_result')->willReturn($mysqli_result);
144207

145208
$MysqliStatementAdapter = new class ($mysqli_stmt) extends MysqliStatementAdapter {

tests/ActiveRecordTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,15 @@ public function query(string $sql, array $param = [], ?ActiveRecord $obj = null,
146146
$result = $record->find()->getBuiltSql();
147147
$this->assertEquals('SELECT test_table.* FROM test_table LEFT JOIN table1 ON table1.some_id = test_table.id LEFT JOIN table2 ON table2.some_id = table1.id LIMIT 1 ', $result);
148148
}
149+
150+
public function testEscapeIdentifierSqlSrv()
151+
{
152+
$record = new class (null, 'test_table') extends ActiveRecord {
153+
public function getDatabaseEngine(): string
154+
{
155+
return 'sqlsrv';
156+
}
157+
};
158+
$this->assertEquals('[test_table]', $record->escapeIdentifier('test_table'));
159+
}
149160
}

tests/commands/RecordCommandTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ public function setUp(): void
1818
{
1919
static::$in = __DIR__ . '/input.test' . uniqid('', true);
2020
static::$ou = __DIR__ . '/output.test' . uniqid('', true);
21-
if (!file_exists(__DIR__ . '/records/')) {
22-
mkdir(__DIR__ . '/records/');
23-
}
2421
file_put_contents(static::$in, '', LOCK_EX);
2522
file_put_contents(static::$ou, '', LOCK_EX);
2623

@@ -44,9 +41,9 @@ public function tearDown(): void
4441
unlink($file);
4542
}
4643

47-
// if (file_exists(__DIR__ . '/records/')) {
48-
// rmdir(__DIR__ . '/records/');
49-
// }
44+
if (file_exists(__DIR__ . '/records/')) {
45+
rmdir(__DIR__ . '/records/');
46+
}
5047
}
5148

5249
protected function newApp(string $in = '')
@@ -87,6 +84,7 @@ public function testConfigDatabaseConfigNotSet()
8784

8885
public function testRecordAlreadyExists()
8986
{
87+
mkdir(__DIR__ . '/records/');
9088
file_put_contents(__DIR__ . '/records/CompanyRecord.php', '<?php class CompanyRecord {}');
9189
$app = $this->newApp();
9290
$app->add(new RecordCommand([
@@ -103,6 +101,7 @@ public function testRecordAlreadyExists()
103101

104102
public function testRecordAlreadyExistsMysql()
105103
{
104+
mkdir(__DIR__ . '/records/');
106105
file_put_contents(__DIR__ . '/records/TestRecord.php', '<?php class TestRecord {}');
107106
$commands = <<<TEXT
108107
mysql
@@ -124,6 +123,7 @@ public function testRecordAlreadyExistsMysql()
124123

125124
public function testRecordAlreadyExistsPgsql()
126125
{
126+
mkdir(__DIR__ . '/records/');
127127
file_put_contents(__DIR__ . '/records/SomethingRecord.php', '<?php class SomethingRecord {}');
128128
$commands = <<<TEXT
129129
mysql

0 commit comments

Comments
 (0)