Merge branch 'master' into dispatcher-rework

dispatcher-rework
fadrian06 3 days ago
commit 1a8af5c96b

@ -16,11 +16,11 @@ This is the main FlightPHP core library for building fast, simple, and extensibl
- Run tests: `composer test` (uses phpunit/phpunit and spatie/phpunit-watcher)
- Run test server: `composer test-server` or `composer test-server-v2`
- Lint code & Check code style: `composer lint` (uses phpstan/phpstan, level 6)
- Beautify code: `composer format` (uses squizlabs/php_codesniffer, PSR1)
- Beautify code: `composer format` (uses squizlabs/php_codesniffer, PSR12)
- Test coverage: `composer test-coverage`
## Coding Standards
- Follow PSR1 coding standards (enforced by PHPCS)
- Follow PSR12 coding standards (enforced by PHPCS)
- Use strict comparisons (`===`, `!==`)
- PHPStan level 6 compliance
- Focus on PHP 7.4 compatibility (avoid PHP 8+ only features)

@ -16,11 +16,11 @@ This is the main FlightPHP core library for building fast, simple, and extensibl
- Run tests: `composer test` (uses phpunit/phpunit and spatie/phpunit-watcher)
- Run test server: `composer test-server` or `composer test-server-v2`
- Lint code & Check code style: `composer lint` (uses phpstan/phpstan, level 6)
- Beautify code: `composer format` (uses squizlabs/php_codesniffer, PSR1)
- Beautify code: `composer format` (uses squizlabs/php_codesniffer, PSR12)
- Test coverage: `composer test-coverage`
## Coding Standards
- Follow PSR1 coding standards (enforced by PHPCS)
- Follow PSR12 coding standards (enforced by PHPCS)
- Use strict comparisons (`===`, `!==`)
- PHPStan level 6 compliance
- Focus on PHP 7.4 compatibility (avoid PHP 8+ only features)

@ -16,11 +16,11 @@ This is the main FlightPHP core library for building fast, simple, and extensibl
- Run tests: `composer test` (uses phpunit/phpunit and spatie/phpunit-watcher)
- Run test server: `composer test-server` or `composer test-server-v2`
- Lint code & Check code style: `composer lint` (uses phpstan/phpstan, level 6)
- Beautify code: `composer format` (uses squizlabs/php_codesniffer, PSR1)
- Beautify code: `composer format` (uses squizlabs/php_codesniffer, PSR12)
- Test coverage: `composer test-coverage`
## Coding Standards
- Follow PSR1 coding standards (enforced by PHPCS)
- Follow PSR12 coding standards (enforced by PHPCS)
- Use strict comparisons (`===`, `!==`)
- PHPStan level 6 compliance
- Focus on PHP 7.4 compatibility (avoid PHP 8+ only features)

@ -9,19 +9,19 @@ Flight aims to be simple and fast. Anything that compromises either of those two
* **Dependencies** - We strive to be dependency free in Flight. Yes even polyfills, yes even `Interface` only repos like `psr/container`. The fewer dependencies, the fewer your exposed attack vectors.
* **Coding Standards** - We use PSR1 coding standards enforced by PHPCS. Some standards that either need additional configuration or need to be manually done are:
* **Coding Standards** - We use PSR-12 coding standards enforced by PHPCS. Some standards that either need additional configuration or need to be manually done are:
* PHPStan is at level 6.
* `===` instead of truthy or falsey statements like `==` or `!is_array()`.
* `===` instead of truthy or falsy statements like `==`.
* **PHP 7.4 Focused** - We do not make PHP 8+ focused enhancements on the framework as the focus is maintaining PHP 7.4.
* **Core functionality vs Plugin** - Have a conversation with us in the [chatroom](https://matrix.to/#/!cTfwPXhpkTXPXwVmxY:matrix.org?via=matrix.org&via=leitstelle511.net&via=integrations.ems.host) to know if your idea is worth makes sense in the framework or in a plugin.
* **Testing** - Until automated testing is put into place, any PRs must pass unit testing in PHP 7.4 and PHP 8.2+. Additionally you need to run `composer test-server` and `composer test-server-v2` and ensure all the header links work correctly.
* **Testing** - PRs must pass unit tests on PHP 7.4 through PHP 8.5+. Additionally you need to run `composer test-server` and `composer test-server-v2` and ensure all the header links work correctly.
#### **Did you find a bug?**
* **Do not open up a GitHub issue if the bug is a security vulnerability**. Instead contact maintainers directly via email to safely pass in the information related to the security vuln.
* **Do not open up a GitHub issue if the bug is a security vulnerability**. Instead contact maintainers directly via email to safely share details about the security vulnerability.
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/flightphp/core/issues).

@ -22,13 +22,10 @@ composer require flightphp/core
or you can download a zip of this repo. Then you would have a basic `index.php` file like the following:
```php
// if installed with composer
require 'vendor/autoload.php';
// or if installed manually by zip file
// require 'flight/Flight.php';
require 'flight/autoload.php';
Flight::route('/', function () {
echo 'hello world!';
echo 'hello world!';
});
Flight::start();
@ -38,19 +35,22 @@ Flight::start();
Yes! Flight is fast. It is one of the fastest PHP frameworks available. You can see all the benchmarks at [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r18&hw=ph&test=frameworks)
See the benchmark below with some other popular PHP frameworks. This is measured in requests processed within the same timeframe.
| Framework | Plaintext Requests| JSON Requests|
| --------- | ------------ | ------------ |
| Flight | 190,421 | 182,491 |
| Yii | 145,749 | 131,434 |
| Fat-Free | 139,238 | 133,952 |
| Slim | 89,588 | 87,348 |
| Phalcon | 95,911 | 87,675 |
| Symfony | 65,053 | 63,237 |
| Lumen | 40,572 | 39,700 |
| Laravel | 26,657 | 26,901 |
| CodeIgniter | 20,628 | 19,901 |
See the benchmark below with some other popular PHP frameworks. This is measured in requests processed within the same timeframe.
```mermaid
---
config:
xyChart:
showDataLabel: true
---
xychart-beta
title "PHP Framework Performance (TechEmpower, requests/sec)"
x-axis "Framework" ["Flight", "Yii", "Fat-Free", "Slim", "Phalcon", "Symfony", "Lumen", "Laravel", "CodeIgniter"]
y-axis "Requests/sec (x1000)" 0 --> 200
%% Series 1: Plaintext, Series 2: JSON
bar [190.4, 145.7, 139.2, 89.6, 95.9, 65.1, 40.6, 26.7, 20.6]
bar [182.5, 131.4, 134.0, 87.3, 87.7, 63.2, 39.7, 26.9, 19.9]
```
## Skeleton App
@ -75,9 +75,10 @@ If you have a current project on v2, you should be able to upgrade to v3 with no
> [!IMPORTANT]
> Flight requires `PHP 7.4` or greater.
**Note:** PHP 7.4 is supported because at the current time of writing (2024) PHP 7.4 is the default version for some LTS Linux distributions. Forcing a move to PHP >8 would cause a lot of heartburn for those users.
The framework also supports PHP >8.
> [!NOTE]
> PHP 7.4 is supported because at the current time of writing (2024) PHP 7.4 is the default version for some LTS Linux distributions.
> Forcing a move to PHP 8 would cause a lot of heartburn for those users.
> The framework also supports PHP 8.
# Roadmap

@ -24,8 +24,7 @@ use Psr\Container\ContainerInterface;
* It is responsible for loading an HTTP request, running the assigned services,
* and generating an HTTP response.
*
* @license MIT, https://docs.flightphp.com/license
* @copyright Copyright (c) 2011-2025, Mike Cao <mike@mikecao.com>, n0nag0n <n0nag0n@sky-9.com>
* @copyright 2011-2026, Mike Cao https://mikecao.com, n0nag0n <n0nag0n@sky-9.com>
*
* @method void start()
* @method void stop()
@ -74,10 +73,7 @@ use Psr\Container\ContainerInterface;
* @phpstan-method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* @phpstan-method void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* @phpstan-method void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
*
* Note: IDEs will use standard @method tags for autocompletion, while PHPStan will use @phpstan-* tags for advanced type checking.
*
* phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
* @license https://docs.flightphp.com/license MIT
*/
class Engine
{

@ -15,7 +15,7 @@ use Psr\Container\ContainerInterface;
* The Flight class is a static representation of the framework.
*
* @license MIT, https://docs.flightphp.com/license
* @copyright Copyright (c) 2011-2025, Mike Cao <mike@mikecao.com>, n0nag0n <n0nag0n@sky-9.com>
* @copyright 2011-2026, Mike Cao https://mikecao.com, n0nag0n <n0nag0n@sky-9.com>
*
* @method static void start()
* @method static void path(string $dir)
@ -77,11 +77,8 @@ use Psr\Container\ContainerInterface;
* @phpstan-method static void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* @phpstan-method static void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* @phpstan-method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
*
* Note: IDEs will use standard @method tags for autocompletion,
* while PHPStan will use @phpstan-* tags for advanced type checking.
* @license https://docs.flightphp.com/license MIT
*/
// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace
class Flight
{
/**

@ -6,134 +6,83 @@ namespace flight\core;
class EventDispatcher
{
/** @var self|null Singleton instance of the EventDispatcher */
private static ?self $instance = null;
/** @var array<string, array<int, callable>> */
/** @var array<string, 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.
*
* @param string $event Event name
* @param callable $callback Callback function
*/
public function on(string $event, callable $callback): void
{
if (isset($this->listeners[$event]) === false) {
$this->listeners[$event] = [];
}
$this->listeners[$event] ??= [];
$this->listeners[$event][] = $callback;
}
/**
* Trigger an event with optional arguments.
*
* @param string $event Event name
* @param mixed ...$args Arguments to pass to the callbacks
*
* @param mixed ...$args Arguments to pass to the listeners.
* @return mixed
*/
public function trigger(string $event, ...$args)
{
$result = null;
if (isset($this->listeners[$event]) === true) {
foreach ($this->listeners[$event] as $callback) {
$result = call_user_func_array($callback, $args);
// If you return false, it will break the loop and stop the other event listeners.
if ($result === false) {
break; // Stop executing further listeners
}
$listenerReturnValue = null;
foreach ($this->getListeners($event) as $listener) {
$listenerReturnValue = $listener(...$args);
if ($listenerReturnValue === false) {
break;
}
}
return $result;
return $listenerReturnValue;
}
/**
* 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;
return (
isset($this->listeners[$event])
&& is_array($this->listeners[$event])
&& count($this->listeners[$event])
);
}
/**
* Get all listeners registered for a specific event.
*
* @param string $event Event name
*
* @return array<int, callable> Array of callbacks registered for the event
*/
/** @return callable[] */
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
*/
/** @return string[] */
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
if (!$this->hasListeners($event)) {
return;
}
$this->listeners[$event] = array_values(array_filter(
$this->getListeners($event),
static fn(callable $listener): bool => $listener !== $callback,
));
}
/**
* 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]);
}
unset($this->listeners[$event]);
}
/**
* Remove the current singleton instance of the EventDispatcher.
*
* @return void
*/
public static function resetInstance(): void
{
self::$instance = null;

@ -13,8 +13,8 @@ use Exception;
* instances with custom initialization parameters. It also performs
* class autoloading.
*
* @license MIT, http://flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @copyright 2011 Mike Cao https://mikecao.com
* @license https://docs.flightphp.com/license MIT
*/
class Loader
{

@ -11,8 +11,8 @@ use flight\util\Collection;
* all the super globals $_GET, $_POST, $_COOKIE, and $_FILES
* are stored and accessible via the Request object.
*
* @license MIT, http://flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @copyright 2011 Mike Cao https://mikecao.com
* @license https://docs.flightphp.com/license MIT
*
* The default request properties are:
*

@ -12,8 +12,8 @@ use flight\core\EventDispatcher;
* contains the response headers, HTTP status code, and response
* body.
*
* @license MIT, http://flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @copyright 2011 Mike Cao https://mikecao.com
* @license https://docs.flightphp.com/license MIT
*/
class Response
{

@ -9,8 +9,8 @@ namespace flight\net;
* an assigned callback function. The Router tries to match the
* requested URL against a series of URL patterns.
*
* @license MIT, http://flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @copyright 2011 Mike Cao https://mikecao.com
* @license https://docs.flightphp.com/license MIT
*/
class Route
{

@ -12,8 +12,8 @@ use flight\net\Route;
* an assigned callback function. The Router tries to match the
* requested URL against a series of URL patterns.
*
* @license MIT, http://flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @copyright 2011 Mike Cao https://mikecao.com
* @license https://docs.flightphp.com/license MIT
*/
class Router
{

@ -9,8 +9,8 @@ namespace flight\template;
* methods for managing view data and inserts the data into
* view templates upon rendering.
*
* @license MIT, http://flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @copyright 2011 Mike Cao https://mikecao.com
* @license https://docs.flightphp.com/license MIT
*/
class View
{

@ -13,10 +13,10 @@ use JsonSerializable;
* The Collection class allows you to access a set of data
* using both array and object notation.
*
* @license MIT, http://flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @copyright 2011 Mike Cao https://mikecao.com
* @implements ArrayAccess<string, mixed>
* @implements Iterator<string, mixed>
* @license https://docs.flightphp.com/license MIT
*/
class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
{

Loading…
Cancel
Save