Skip to content

Commit dd2d23e

Browse files
committed
added more events, added apm changes
1 parent cece196 commit dd2d23e

File tree

10 files changed

+453
-33
lines changed

10 files changed

+453
-33
lines changed

flight/Engine.php

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
* @method void stop() Stops framework and outputs current response
3131
* @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true) Stops processing and returns a given response.
3232
*
33+
* # Class registration
34+
* @method EventDispatcher eventDispatcher() Gets event dispatcher
35+
*
3336
* # Routing
3437
* @method Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
3538
* Routes a URL to a callback function with all applicable methods
@@ -110,7 +113,6 @@ public function __construct()
110113
{
111114
$this->loader = new Loader();
112115
$this->dispatcher = new Dispatcher();
113-
$this->eventDispatcher = new EventDispatcher();
114116
$this->init();
115117
}
116118

@@ -160,6 +162,9 @@ public function init(): void
160162
$this->dispatcher->setEngine($this);
161163

162164
// Register default components
165+
$this->map('eventDispatcher', function () {
166+
return EventDispatcher::getInstance();
167+
});
163168
$this->loader->register('request', Request::class);
164169
$this->loader->register('response', Response::class);
165170
$this->loader->register('router', Router::class);
@@ -460,7 +465,9 @@ protected function processMiddleware(Route $route, string $eventName): bool
460465
// Here is the array callable $middlewareObject that we created earlier.
461466
// It looks bizarre but it's really calling [ $class, $method ]($params)
462467
// Which loosely translates to $class->$method($params)
468+
$start = microtime(true);
463469
$middlewareResult = $middlewareObject($params);
470+
$this->triggerEvent('flight.middleware.executed', $route, $middleware, microtime(true) - $start);
464471

465472
if ($useV3OutputBuffering === true) {
466473
$this->response()->write(ob_get_clean());
@@ -573,12 +580,12 @@ public function _start(): void
573580
}
574581

575582
// Call route handler
583+
$routeStart = microtime(true);
576584
$continue = $this->dispatcher->execute(
577585
$route->callback,
578586
$params
579587
);
580-
$this->triggerEvent('flight.route.executed', $route);
581-
588+
$this->triggerEvent('flight.route.executed', $route, microtime(true) - $routeStart);
582589
if ($useV3OutputBuffering === true) {
583590
$response->write(ob_get_clean());
584591
}
@@ -631,6 +638,7 @@ public function _start(): void
631638
*/
632639
public function _error(Throwable $e): void
633640
{
641+
$this->triggerEvent('flight.error', $e);
634642
$msg = sprintf(
635643
<<<'HTML'
636644
<h1>500 Internal Server Error</h1>
@@ -678,8 +686,6 @@ public function _stop(?int $code = null): void
678686
}
679687

680688
$response->send();
681-
682-
$this->triggerEvent('flight.response.sent', $response);
683689
}
684690
}
685691

@@ -831,6 +837,8 @@ public function _redirect(string $url, int $code = 303): void
831837
$url = $base . preg_replace('#/+#', '/', '/' . $url);
832838
}
833839

840+
$this->triggerEvent('flight.redirect', $url, $code);
841+
834842
$this->response()
835843
->clearBody()
836844
->status($code)
@@ -854,7 +862,9 @@ public function _render(string $file, ?array $data = null, ?string $key = null):
854862
return;
855863
}
856864

865+
$start = microtime(true);
857866
$this->view()->render($file, $data);
867+
$this->triggerEvent('flight.view.rendered', $file, microtime(true) - $start);
858868
}
859869

860870
/**
@@ -1019,7 +1029,7 @@ public function _getUrl(string $alias, array $params = []): string
10191029
*/
10201030
public function _onEvent(string $eventName, callable $callback): void
10211031
{
1022-
$this->eventDispatcher->on($eventName, $callback);
1032+
$this->eventDispatcher()->on($eventName, $callback);
10231033
}
10241034

10251035
/**
@@ -1030,6 +1040,6 @@ public function _onEvent(string $eventName, callable $callback): void
10301040
*/
10311041
public function _triggerEvent(string $eventName, ...$args): void
10321042
{
1033-
$this->eventDispatcher->trigger($eventName, ...$args);
1043+
$this->eventDispatcher()->trigger($eventName, ...$args);
10341044
}
10351045
}

flight/Flight.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use flight\net\Router;
99
use flight\template\View;
1010
use flight\net\Route;
11+
use flight\core\EventDispatcher;
1112

1213
require_once __DIR__ . '/autoload.php';
1314

@@ -29,6 +30,9 @@
2930
* Unregisters a class to a framework method.
3031
* @method static void registerContainerHandler(callable|object $containerHandler) Registers a container handler.
3132
*
33+
* # Class registration
34+
* @method EventDispatcher eventDispatcher() Gets event dispatcher
35+
*
3236
* # Routing
3337
* @method static Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
3438
* Maps a URL pattern to a callback with all applicable methods.

flight/core/EventDispatcher.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,25 @@
66

77
class EventDispatcher
88
{
9+
/** @var self|null Singleton instance of the EventDispatcher */
10+
private static ?self $instance = null;
11+
912
/** @var array<string, array<int, callable>> */
1013
protected array $listeners = [];
1114

15+
/**
16+
* Singleton instance of the EventDispatcher.
17+
*
18+
* @return self
19+
*/
20+
public static function getInstance(): self
21+
{
22+
if (self::$instance === null) {
23+
self::$instance = new self();
24+
}
25+
return self::$instance;
26+
}
27+
1228
/**
1329
* Register a callback for an event.
1430
*
@@ -42,4 +58,80 @@ public function trigger(string $event, ...$args): void
4258
}
4359
}
4460
}
61+
62+
/**
63+
* Check if an event has any registered listeners.
64+
*
65+
* @param string $event Event name
66+
*
67+
* @return bool True if the event has listeners, false otherwise
68+
*/
69+
public function hasListeners(string $event): bool
70+
{
71+
return isset($this->listeners[$event]) === true && count($this->listeners[$event]) > 0;
72+
}
73+
74+
/**
75+
* Get all listeners registered for a specific event.
76+
*
77+
* @param string $event Event name
78+
*
79+
* @return array<int, callable> Array of callbacks registered for the event
80+
*/
81+
public function getListeners(string $event): array
82+
{
83+
return $this->listeners[$event] ?? [];
84+
}
85+
86+
/**
87+
* Get a list of all events that have registered listeners.
88+
*
89+
* @return array<int, string> Array of event names
90+
*/
91+
public function getAllRegisteredEvents(): array
92+
{
93+
return array_keys($this->listeners);
94+
}
95+
96+
/**
97+
* Remove a specific listener for an event.
98+
*
99+
* @param string $event the event name
100+
* @param callable $callback the exact callback to remove
101+
*
102+
* @return void
103+
*/
104+
public function removeListener(string $event, callable $callback): void
105+
{
106+
if (isset($this->listeners[$event]) === true && count($this->listeners[$event]) > 0) {
107+
$this->listeners[$event] = array_filter($this->listeners[$event], function ($listener) use ($callback) {
108+
return $listener !== $callback;
109+
});
110+
$this->listeners[$event] = array_values($this->listeners[$event]); // Re-index the array
111+
}
112+
}
113+
114+
/**
115+
* Remove all listeners for a specific event.
116+
*
117+
* @param string $event the event name
118+
*
119+
* @return void
120+
*/
121+
public function removeAllListeners(string $event): void
122+
{
123+
if (isset($this->listeners[$event]) === true) {
124+
unset($this->listeners[$event]);
125+
}
126+
}
127+
128+
/**
129+
* Remove the current singleton instance of the EventDispatcher.
130+
*
131+
* @return void
132+
*/
133+
public static function resetInstance(): void
134+
{
135+
self::$instance = null;
136+
}
45137
}

flight/database/PdoWrapper.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,40 @@
44

55
namespace flight\database;
66

7+
use flight\core\EventDispatcher;
78
use flight\util\Collection;
89
use PDO;
910
use PDOStatement;
1011

1112
class 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
*

flight/net/Response.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace flight\net;
66

77
use Exception;
8+
use flight\core\EventDispatcher;
89

910
/**
1011
* The Response class represents an HTTP response. The object
@@ -426,6 +427,7 @@ public function send(): void
426427
}
427428
}
428429

430+
$start = microtime(true);
429431
// Only for the v3 output buffering.
430432
if ($this->v2_output_buffering === false) {
431433
$this->processResponseCallbacks();
@@ -436,8 +438,9 @@ public function send(): void
436438
}
437439

438440
echo $this->body;
439-
440441
$this->sent = true;
442+
443+
EventDispatcher::getInstance()->trigger('flight.response.sent', $this, microtime(true) - $start);
441444
}
442445

443446
/**

0 commit comments

Comments
 (0)