Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions flight/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
* @method void stop() Stops framework and outputs current response
* @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true) Stops processing and returns a given response.
*
* # Class registration
* @method EventDispatcher eventDispatcher() Gets event dispatcher
*
* # Routing
* @method Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Routes a URL to a callback function with all applicable methods
Expand Down Expand Up @@ -110,7 +113,6 @@ public function __construct()
{
$this->loader = new Loader();
$this->dispatcher = new Dispatcher();
$this->eventDispatcher = new EventDispatcher();
$this->init();
}

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

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

if ($useV3OutputBuffering === true) {
$this->response()->write(ob_get_clean());
Expand Down Expand Up @@ -573,12 +580,12 @@ public function _start(): void
}

// Call route handler
$routeStart = microtime(true);
$continue = $this->dispatcher->execute(
$route->callback,
$params
);
$this->triggerEvent('flight.route.executed', $route);

$this->triggerEvent('flight.route.executed', $route, microtime(true) - $routeStart);
if ($useV3OutputBuffering === true) {
$response->write(ob_get_clean());
}
Expand Down Expand Up @@ -631,6 +638,7 @@ public function _start(): void
*/
public function _error(Throwable $e): void
{
$this->triggerEvent('flight.error', $e);
$msg = sprintf(
<<<'HTML'
<h1>500 Internal Server Error</h1>
Expand Down Expand Up @@ -678,8 +686,6 @@ public function _stop(?int $code = null): void
}

$response->send();

$this->triggerEvent('flight.response.sent', $response);
}
}

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

$this->triggerEvent('flight.redirect', $url, $code);

$this->response()
->clearBody()
->status($code)
Expand All @@ -854,7 +862,9 @@ public function _render(string $file, ?array $data = null, ?string $key = null):
return;
}

$start = microtime(true);
$this->view()->render($file, $data);
$this->triggerEvent('flight.view.rendered', $file, microtime(true) - $start);
}

/**
Expand Down Expand Up @@ -1019,7 +1029,7 @@ public function _getUrl(string $alias, array $params = []): string
*/
public function _onEvent(string $eventName, callable $callback): void
{
$this->eventDispatcher->on($eventName, $callback);
$this->eventDispatcher()->on($eventName, $callback);
}

/**
Expand All @@ -1030,6 +1040,6 @@ public function _onEvent(string $eventName, callable $callback): void
*/
public function _triggerEvent(string $eventName, ...$args): void
{
$this->eventDispatcher->trigger($eventName, ...$args);
$this->eventDispatcher()->trigger($eventName, ...$args);
}
}
4 changes: 4 additions & 0 deletions flight/Flight.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use flight\net\Router;
use flight\template\View;
use flight\net\Route;
use flight\core\EventDispatcher;

require_once __DIR__ . '/autoload.php';

Expand All @@ -29,6 +30,9 @@
* Unregisters a class to a framework method.
* @method static void registerContainerHandler(callable|object $containerHandler) Registers a container handler.
*
* # Class registration
* @method EventDispatcher eventDispatcher() Gets event dispatcher
*
* # Routing
* @method static Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Maps a URL pattern to a callback with all applicable methods.
Expand Down
92 changes: 92 additions & 0 deletions flight/core/EventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,25 @@

class EventDispatcher
{
/** @var self|null Singleton instance of the EventDispatcher */
private static ?self $instance = null;

/** @var array<string, array<int, callable>> */
protected array $listeners = [];

/**
* Singleton instance of the EventDispatcher.
*
* @return self
*/
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}

/**
* Register a callback for an event.
*
Expand Down Expand Up @@ -42,4 +58,80 @@ public function trigger(string $event, ...$args): void
}
}
}

/**
* Check if an event has any registered listeners.
*
* @param string $event Event name
*
* @return bool True if the event has listeners, false otherwise
*/
public function hasListeners(string $event): bool
{
return isset($this->listeners[$event]) === true && count($this->listeners[$event]) > 0;
}

/**
* Get all listeners registered for a specific event.
*
* @param string $event Event name
*
* @return array<int, callable> Array of callbacks registered for the event
*/
public function getListeners(string $event): array
{
return $this->listeners[$event] ?? [];
}

/**
* Get a list of all events that have registered listeners.
*
* @return array<int, string> Array of event names
*/
public function getAllRegisteredEvents(): array
{
return array_keys($this->listeners);
}

/**
* Remove a specific listener for an event.
*
* @param string $event the event name
* @param callable $callback the exact callback to remove
*
* @return void
*/
public function removeListener(string $event, callable $callback): void
{
if (isset($this->listeners[$event]) === true && count($this->listeners[$event]) > 0) {
$this->listeners[$event] = array_filter($this->listeners[$event], function ($listener) use ($callback) {
return $listener !== $callback;
});
$this->listeners[$event] = array_values($this->listeners[$event]); // Re-index the array
}
}

/**
* Remove all listeners for a specific event.
*
* @param string $event the event name
*
* @return void
*/
public function removeAllListeners(string $event): void
{
if (isset($this->listeners[$event]) === true) {
unset($this->listeners[$event]);
}
}

/**
* Remove the current singleton instance of the EventDispatcher.
*
* @return void
*/
public static function resetInstance(): void
{
self::$instance = null;
}
}
100 changes: 100 additions & 0 deletions flight/database/PdoWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,40 @@

namespace flight\database;

use flight\core\EventDispatcher;
use flight\util\Collection;
use PDO;
use PDOStatement;

class PdoWrapper extends PDO
{
/** @var bool $trackApmQueries Whether to track application performance metrics (APM) for queries. */
protected bool $trackApmQueries = false;

/** @var array<int,array<string,mixed>> $queryMetrics Metrics related to the database connection. */
protected array $queryMetrics = [];

/** @var array<string,string> $connectionMetrics Metrics related to the database connection. */
protected array $connectionMetrics = [];

/**
* Constructor for the PdoWrapper class.
*
* @param string $dsn The Data Source Name (DSN) for the database connection.
* @param string|null $username The username for the database connection.
* @param string|null $password The password for the database connection.
* @param array<string, mixed>|null $options An array of options for the PDO connection.
* @param bool $trackApmQueries Whether to track application performance metrics (APM) for queries.
*/
public function __construct(?string $dsn = null, ?string $username = '', ?string $password = '', ?array $options = null, bool $trackApmQueries = false)
{
parent::__construct($dsn, $username, $password, $options);
$this->trackApmQueries = $trackApmQueries;
if ($this->trackApmQueries === true) {
$this->connectionMetrics = $this->pullDataFromDsn($dsn);
}
}

/**
* Use this for INSERTS, UPDATES, or if you plan on using a SELECT in a while loop
*
Expand All @@ -31,8 +59,19 @@ public function runQuery(string $sql, array $params = []): PDOStatement
$processed_sql_data = $this->processInStatementSql($sql, $params);
$sql = $processed_sql_data['sql'];
$params = $processed_sql_data['params'];
$start = $this->trackApmQueries === true ? microtime(true) : 0;
$memory_start = $this->trackApmQueries === true ? memory_get_usage() : 0;
$statement = $this->prepare($sql);
$statement->execute($params);
if ($this->trackApmQueries === true) {
$this->queryMetrics[] = [
'sql' => $sql,
'params' => $params,
'execution_time' => microtime(true) - $start,
'row_count' => $statement->rowCount(),
'memory_usage' => memory_get_usage() - $memory_start
];
}
return $statement;
}

Expand Down Expand Up @@ -88,9 +127,20 @@ public function fetchAll(string $sql, array $params = [])
$processed_sql_data = $this->processInStatementSql($sql, $params);
$sql = $processed_sql_data['sql'];
$params = $processed_sql_data['params'];
$start = $this->trackApmQueries === true ? microtime(true) : 0;
$memory_start = $this->trackApmQueries === true ? memory_get_usage() : 0;
$statement = $this->prepare($sql);
$statement->execute($params);
$results = $statement->fetchAll();
if ($this->trackApmQueries === true) {
$this->queryMetrics[] = [
'sql' => $sql,
'params' => $params,
'execution_time' => microtime(true) - $start,
'row_count' => $statement->rowCount(),
'memory_usage' => memory_get_usage() - $memory_start
];
}
if (is_array($results) === true && count($results) > 0) {
foreach ($results as &$result) {
$result = new Collection($result);
Expand All @@ -101,6 +151,56 @@ public function fetchAll(string $sql, array $params = [])
return $results;
}

/**
* Pulls the engine, database, and host from the DSN string.
*
* @param string $dsn The Data Source Name (DSN) string.
*
* @return array<string,string> An associative array containing the engine, database, and host.
*/
protected function pullDataFromDsn(string $dsn): array
{
// pull the engine from the dsn (sqlite, mysql, pgsql, etc)
preg_match('/^([a-zA-Z]+):/', $dsn, $matches);
$engine = $matches[1] ?? 'unknown';

if ($engine === 'sqlite') {
// pull the path from the dsn
preg_match('/sqlite:(.*)/', $dsn, $matches);
$dbname = basename($matches[1] ?? 'unknown');
$host = 'localhost';
} else {
// pull the database from the dsn
preg_match('/dbname=([^;]+)/', $dsn, $matches);
$dbname = $matches[1] ?? 'unknown';
// pull the host from the dsn
preg_match('/host=([^;]+)/', $dsn, $matches);
$host = $matches[1] ?? 'unknown';
}

return [
'engine' => $engine,
'database' => $dbname,
'host' => $host
];
}

/**
* Logs the executed queries through the event dispatcher.
*
* This method enables logging of all the queries executed by the PDO wrapper.
* It can be useful for debugging and monitoring purposes.
*
* @return void
*/
public function logQueries(): void
{
if ($this->trackApmQueries === true && $this->connectionMetrics !== [] && $this->queryMetrics !== []) {
EventDispatcher::getInstance()->trigger('flight.db.queries', $this->connectionMetrics, $this->queryMetrics);
$this->queryMetrics = []; // Reset after logging
}
}

/**
* Don't worry about this guy. Converts stuff for IN statements
*
Expand Down
5 changes: 4 additions & 1 deletion flight/net/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace flight\net;

use Exception;
use flight\core\EventDispatcher;

/**
* The Response class represents an HTTP response. The object
Expand Down Expand Up @@ -426,6 +427,7 @@ public function send(): void
}
}

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

echo $this->body;

$this->sent = true;

EventDispatcher::getInstance()->trigger('flight.response.sent', $this, microtime(true) - $start);
}

/**
Expand Down
Loading