packages/ztd-query-mysqli-adapter/src/Driver/MysqliStatement.php
1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery\Adapter\Mysqli\Driver;
6
7use mysqli;
8use mysqli_result;
9use mysqli_stmt;
10use ZtdQuery\Connection\Exception\DatabaseException;
11use ZtdQuery\Connection\StatementInterface;
12use ZtdQuery\Platform\ResultColumnTypeResolver;
13
14/**
15 * mysqli prepared statement adapter implementing StatementInterface for ZTD layer.
16 *
17 * This class wraps a mysqli_stmt and provides the minimal interface
18 * required by the ZTD session for executing statements and fetching results.
19 */
20final class MysqliStatement implements StatementInterface
21{
22 private mysqli_stmt $statement;
23
24 private mysqli $mysqli;
25
26 /**
27 * @var mysqli_result|false|null
28 */
29 private mysqli_result|false|null $result = null;
30
31 /**
32 * Adapt a prepared native statement and retain its connection for error details.
33 */
34 public function __construct(mysqli_stmt $statement, mysqli $mysqli)
35 {
36 $this->statement = $statement;
37 $this->mysqli = $mysqli;
38 }
39
40 /**
41 * {@inheritDoc}
42 *
43 * @throws DatabaseException On database error.
44 */
45 public function execute(?array $params = null): bool
46 {
47 if ($params !== null && $params !== []) {
48 if (!$this->statement->execute($params)) {
49 if ($this->mysqli->errno !== 0) {
50 throw new DatabaseException(
51 $this->mysqli->error,
52 $this->mysqli->errno,
53 $this->mysqli->errno
54 );
55 }
56 return false;
57 }
58 } else {
59 if (!$this->statement->execute()) {
60 if ($this->mysqli->errno !== 0) {
61 throw new DatabaseException(
62 $this->mysqli->error,
63 $this->mysqli->errno,
64 $this->mysqli->errno
65 );
66 }
67 return false;
68 }
69 }
70
71 /**
72 * get_result() is deferred so ZtdMysqliStatement::get_result() can call it on the underlying stmt
73 */
74
75 return true;
76 }
77
78 /**
79 * {@inheritDoc}
80 */
81 public function fetchAll(): array
82 {
83 $result = $this->result ??= $this->statement->get_result();
84
85 if ($result === false) {
86 $this->statement->close();
87 return [];
88 }
89
90 $rows = mysqli_fetch_all($result, MYSQLI_ASSOC);
91
92 /**
93 * Free the result to avoid "Commands out of sync" errors
94 */
95 $result->free();
96 $this->result = null;
97
98 $this->statement->close();
99
100 return $rows;
101 }
102
103 /**
104 * {@inheritDoc}
105 */
106 public function resultColumns(ResultColumnTypeResolver $typeResolver): array
107 {
108 $result = $this->result ??= $this->statement->get_result();
109 if ($result === false) {
110 return [];
111 }
112
113 return MysqliResultColumnExtractor::extract($result, $typeResolver);
114 }
115
116 /**
117 * {@inheritDoc}
118 */
119 public function rowCount(): int
120 {
121 return (int) $this->statement->affected_rows;
122 }
123
124}
125