44
55namespace flight \database ;
66
7+ use flight \core \EventDispatcher ;
78use flight \util \Collection ;
89use PDO ;
910use PDOStatement ;
1011
1112class PdoWrapper extends PDO
1213{
14+ /** @var bool $trackApmQueries Whether to track application performance metrics (APM) for queries. */
15+ protected bool $ trackApmQueries = false ;
16+
17+ /** @var array<int,array<string,mixed>> $queryMetrics Metrics related to the database connection. */
18+ protected array $ queryMetrics = [];
19+
20+ /** @var array<string,string> $connectionMetrics Metrics related to the database connection. */
21+ protected array $ connectionMetrics = [];
22+
23+ /**
24+ * Constructor for the PdoWrapper class.
25+ *
26+ * @param string $dsn The Data Source Name (DSN) for the database connection.
27+ * @param string|null $username The username for the database connection.
28+ * @param string|null $password The password for the database connection.
29+ * @param array<string, mixed>|null $options An array of options for the PDO connection.
30+ * @param bool $trackApmQueries Whether to track application performance metrics (APM) for queries.
31+ */
32+ public function __construct (?string $ dsn = null , ?string $ username = '' , ?string $ password = '' , ?array $ options = null , bool $ trackApmQueries = false )
33+ {
34+ parent ::__construct ($ dsn , $ username , $ password , $ options );
35+ $ this ->trackApmQueries = $ trackApmQueries ;
36+ if ($ this ->trackApmQueries === true ) {
37+ $ this ->connectionMetrics = $ this ->pullDataFromDsn ($ dsn );
38+ }
39+ }
40+
1341 /**
1442 * Use this for INSERTS, UPDATES, or if you plan on using a SELECT in a while loop
1543 *
@@ -31,8 +59,19 @@ public function runQuery(string $sql, array $params = []): PDOStatement
3159 $ processed_sql_data = $ this ->processInStatementSql ($ sql , $ params );
3260 $ sql = $ processed_sql_data ['sql ' ];
3361 $ params = $ processed_sql_data ['params ' ];
62+ $ start = $ this ->trackApmQueries === true ? microtime (true ) : 0 ;
63+ $ memory_start = $ this ->trackApmQueries === true ? memory_get_usage () : 0 ;
3464 $ statement = $ this ->prepare ($ sql );
3565 $ statement ->execute ($ params );
66+ if ($ this ->trackApmQueries === true ) {
67+ $ this ->queryMetrics [] = [
68+ 'sql ' => $ sql ,
69+ 'params ' => $ params ,
70+ 'execution_time ' => microtime (true ) - $ start ,
71+ 'row_count ' => $ statement ->rowCount (),
72+ 'memory_usage ' => memory_get_usage () - $ memory_start
73+ ];
74+ }
3675 return $ statement ;
3776 }
3877
@@ -88,9 +127,20 @@ public function fetchAll(string $sql, array $params = [])
88127 $ processed_sql_data = $ this ->processInStatementSql ($ sql , $ params );
89128 $ sql = $ processed_sql_data ['sql ' ];
90129 $ params = $ processed_sql_data ['params ' ];
130+ $ start = $ this ->trackApmQueries === true ? microtime (true ) : 0 ;
131+ $ memory_start = $ this ->trackApmQueries === true ? memory_get_usage () : 0 ;
91132 $ statement = $ this ->prepare ($ sql );
92133 $ statement ->execute ($ params );
93134 $ results = $ statement ->fetchAll ();
135+ if ($ this ->trackApmQueries === true ) {
136+ $ this ->queryMetrics [] = [
137+ 'sql ' => $ sql ,
138+ 'params ' => $ params ,
139+ 'execution_time ' => microtime (true ) - $ start ,
140+ 'row_count ' => $ statement ->rowCount (),
141+ 'memory_usage ' => memory_get_usage () - $ memory_start
142+ ];
143+ }
94144 if (is_array ($ results ) === true && count ($ results ) > 0 ) {
95145 foreach ($ results as &$ result ) {
96146 $ result = new Collection ($ result );
@@ -101,6 +151,56 @@ public function fetchAll(string $sql, array $params = [])
101151 return $ results ;
102152 }
103153
154+ /**
155+ * Pulls the engine, database, and host from the DSN string.
156+ *
157+ * @param string $dsn The Data Source Name (DSN) string.
158+ *
159+ * @return array<string,string> An associative array containing the engine, database, and host.
160+ */
161+ protected function pullDataFromDsn (string $ dsn ): array
162+ {
163+ // pull the engine from the dsn (sqlite, mysql, pgsql, etc)
164+ preg_match ('/^([a-zA-Z]+):/ ' , $ dsn , $ matches );
165+ $ engine = $ matches [1 ] ?? 'unknown ' ;
166+
167+ if ($ engine === 'sqlite ' ) {
168+ // pull the path from the dsn
169+ preg_match ('/sqlite:(.*)/ ' , $ dsn , $ matches );
170+ $ dbname = basename ($ matches [1 ] ?? 'unknown ' );
171+ $ host = 'localhost ' ;
172+ } else {
173+ // pull the database from the dsn
174+ preg_match ('/dbname=([^;]+)/ ' , $ dsn , $ matches );
175+ $ dbname = $ matches [1 ] ?? 'unknown ' ;
176+ // pull the host from the dsn
177+ preg_match ('/host=([^;]+)/ ' , $ dsn , $ matches );
178+ $ host = $ matches [1 ] ?? 'unknown ' ;
179+ }
180+
181+ return [
182+ 'engine ' => $ engine ,
183+ 'database ' => $ dbname ,
184+ 'host ' => $ host
185+ ];
186+ }
187+
188+ /**
189+ * Logs the executed queries through the event dispatcher.
190+ *
191+ * This method enables logging of all the queries executed by the PDO wrapper.
192+ * It can be useful for debugging and monitoring purposes.
193+ *
194+ * @return void
195+ */
196+ public function logQueries (): void
197+ {
198+ if ($ this ->trackApmQueries === true && $ this ->connectionMetrics !== [] && $ this ->queryMetrics !== []) {
199+ EventDispatcher::getInstance ()->trigger ('flight.db.queries ' , $ this ->connectionMetrics , $ this ->queryMetrics );
200+ $ this ->queryMetrics = []; // Reset after logging
201+ }
202+ }
203+
104204 /**
105205 * Don't worry about this guy. Converts stuff for IN statements
106206 *
0 commit comments