diff --git a/.gemini/GEMINI.md b/.gemini/GEMINI.md index 226a40d..9a45b54 100644 --- a/.gemini/GEMINI.md +++ b/.gemini/GEMINI.md @@ -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) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 226a40d..9a45b54 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -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) diff --git a/AGENTS.md b/AGENTS.md index 226a40d..9a45b54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b1af6d..1004dd2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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). diff --git a/README.md b/README.md index 9a043f7..62e4911 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/flight/Engine.php b/flight/Engine.php index 3666633..8c1d69d 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -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 , n0nag0n + * @copyright 2011-2026, Mike Cao https://mikecao.com, n0nag0n * * @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 { diff --git a/flight/Flight.php b/flight/Flight.php index ddb536f..b975197 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -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 , n0nag0n + * @copyright 2011-2026, Mike Cao https://mikecao.com, n0nag0n * * @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 { /** diff --git a/flight/core/EventDispatcher.php b/flight/core/EventDispatcher.php index 8e60d1a..1a63731 100644 --- a/flight/core/EventDispatcher.php +++ b/flight/core/EventDispatcher.php @@ -6,134 +6,83 @@ namespace flight\core; class EventDispatcher { - /** @var self|null Singleton instance of the EventDispatcher */ private static ?self $instance = null; - /** @var array> */ + /** @var array */ 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 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 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; diff --git a/flight/core/Loader.php b/flight/core/Loader.php index 92deceb..a3f52cb 100644 --- a/flight/core/Loader.php +++ b/flight/core/Loader.php @@ -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 + * @copyright 2011 Mike Cao https://mikecao.com + * @license https://docs.flightphp.com/license MIT */ class Loader { diff --git a/flight/net/Request.php b/flight/net/Request.php index 24f02a5..a2dcb48 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -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 + * @copyright 2011 Mike Cao https://mikecao.com + * @license https://docs.flightphp.com/license MIT * * The default request properties are: * diff --git a/flight/net/Response.php b/flight/net/Response.php index 1a738cf..cd0e2a1 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -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 + * @copyright 2011 Mike Cao https://mikecao.com + * @license https://docs.flightphp.com/license MIT */ class Response { diff --git a/flight/net/Route.php b/flight/net/Route.php index 47f93dc..d27df37 100644 --- a/flight/net/Route.php +++ b/flight/net/Route.php @@ -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 + * @copyright 2011 Mike Cao https://mikecao.com + * @license https://docs.flightphp.com/license MIT */ class Route { diff --git a/flight/net/Router.php b/flight/net/Router.php index 0ec9724..770ab9c 100644 --- a/flight/net/Router.php +++ b/flight/net/Router.php @@ -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 + * @copyright 2011 Mike Cao https://mikecao.com + * @license https://docs.flightphp.com/license MIT */ class Router { diff --git a/flight/template/View.php b/flight/template/View.php index f995d36..fae471f 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -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 + * @copyright 2011 Mike Cao https://mikecao.com + * @license https://docs.flightphp.com/license MIT */ class View { diff --git a/flight/util/Collection.php b/flight/util/Collection.php index e17ed37..5b2c4a3 100644 --- a/flight/util/Collection.php +++ b/flight/util/Collection.php @@ -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 + * @copyright 2011 Mike Cao https://mikecao.com * @implements ArrayAccess * @implements Iterator + * @license https://docs.flightphp.com/license MIT */ class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable {