diff --git a/CHANGELOG.md b/CHANGELOG.md index f59357b16..fef80adab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,14 @@ The format is based on Keep a Changelog. ### Added - Added `Request::expectsJson()`: true when the `Accept` header lists a JSON media type (`application/json` or a `+json` type, parameters and case ignored), or for an AJAX request that accepts any content type (#581) -- Added boot stage lifecycle events: stages may declare `BEFORE`/`AFTER` event names and `BootPipeline` dispatches them with the `AppContext` as a named payload. The environment, config, error handler, HTTP, and modules stages declare `boot..before` and `boot..after` (#536) +- Added boot stage lifecycle events: stages may declare `BEFORE`/`AFTER` event names and the boot `Pipeline` dispatches them with the `AppContext` as a named payload. The environment, config, error handler, HTTP, and modules stages declare `boot..before` and `boot..after` (#536) - Added adapter-based Lang provider support with `DeepL` and `Google Translate` adapters plus shared remote request/caching infrastructure (#533) ### Changed +- **BREAKING:** Replaced the boot, web and console pipelines with one generic `Pipeline` (#583): + - `Pipeline` runs `StageInterface` stages in order and dispatches the `BEFORE`/`AFTER` events they declare. The context type is a PHPDoc template checked by PHPStan: a stage states it with `@implements StageInterface` (or `WebContext`, `ConsoleContext`) and takes `process(AppContext $context)`. + - Removed `BootPipeline` and `BootStageInterface`: build a `new Pipeline([...])`, and implement `StageInterface` with `@implements StageInterface` in a boot stage. `WebPipeline`, `ConsolePipeline`, `WebStageInterface` and `ConsoleStageInterface`, added earlier in 3.1.0, are removed too. + - A pipeline still rejects anything that is not a `StageInterface`, but no longer rejects a stage of another kind at runtime (a boot stage in a web pipeline); PHPStan reports it instead. - **BREAKING:** The web and console contexts now extend `AppContext` (#579): - `AppContext` keeps only what both the web and the console boot provide: `getBaseDir()`, `getContainer()`, `getEnvironment()` and `getConfig()`. Removed `AppContext::getRequest()`, `getResponse()` and `getRoutes()`, which only the web boot can serve: use `WebContext::getRequest()` and `WebContext::getRoutes()`, and `response()` for the shared response. - `WebContext` and `ConsoleContext` extend `AppContext` and are built from it with the same base directory and container; `WebContext::getAppContext()` is removed. Like `ConsoleContext`, `WebContext` reads its services from the container (the request, the routes and the view cache) and the matched route from the request, and stores only what the run produces: the response that is sent and the stopped flag. `WebContext::setMatchedRoute()` and `setViewCache()` are removed. @@ -23,8 +27,8 @@ The format is based on Keep a Changelog. - `ConsoleAppAdapter` always runs the full boot and, when it fails, keeps the failure instead of stopping. `RunCommandStage` re-throws that original failure only for a command that requires the environment, so `core:env`, `list`, and `help` still run and `core:env --yes` can repair a missing, empty, or malformed `.env`. The reduced boot for `core:env` is gone, and the Symfony `Application` is named `UNKNOWN` until a successful boot names it from the app config. - Added `CliCommand::$requiresEnvironment` (default `true`) with `CliCommand::requiresEnvironment()`; a command that works without the environment, like `EnvCommand`, sets it to `false`. `ConsoleContext` takes an optional boot failure and exposes it with `getBootFailure()`. - **BREAKING:** Refactored the web and console runtime into staged pipelines (#537): - - `WebAppAdapter::start()` now runs a `WebPipeline` of the preflight, route resolution, not found, prepare, dispatch, and send stages with a `WebContext`, and `ConsoleAppAdapter::start()` runs a `ConsolePipeline` of the register core commands, register app commands, and run stages with a `ConsoleContext`; the run stage rejects a missing or unknown command before running anything, and registration goes through the new `Quantum\Console\CommandRegistrar`. Behavior is unchanged, and runtime stage events are not dispatched yet. - - Moved the web adapter, context, and pipeline to `Quantum\App\Adapters\Web`, the console ones to `Quantum\App\Adapters\Console`, and the boot stages to `Quantum\App\Stages\Boot`. Code that goes through `AppFactory` is unaffected; code that references these classes directly must update its imports. + - `WebAppAdapter::start()` now runs a `Pipeline` of the preflight, route resolution, not found, prepare, dispatch, and send stages with a `WebContext`, and `ConsoleAppAdapter::start()` runs a `Pipeline` of the register core commands, register app commands, and run stages with a `ConsoleContext`; the run stage rejects a missing or unknown command before running anything, and registration goes through the new `Quantum\Console\CommandRegistrar`. Behavior is unchanged. The pipeline dispatches a stage's `BEFORE`/`AFTER` events when the stage declares them, but the built-in web and console stages declare none yet. + - Moved the web adapter and context to `Quantum\App\Adapters\Web`, the console ones to `Quantum\App\Adapters\Console`, and the boot stages to `Quantum\App\Stages\Boot`. Code that goes through `AppFactory` is unaffected; code that references these classes directly must update its imports. - Removed the `AppAdapter` base class and the `WebAppTrait` and `ConsoleAppTrait` traits. `WebAppAdapter` and `ConsoleAppAdapter` now implement `AppInterface` directly. - **BREAKING:** Replaced the `Quantum\Hook` package with `Quantum\Event` (#536): - `hook()` is now `event()`; `on()` and `fire()` are now `listen()` and `dispatch()`, and no aliases remain. Listeners receive one named payload array, which is empty when none is given. diff --git a/src/App/Adapters/Console/ConsoleAppAdapter.php b/src/App/Adapters/Console/ConsoleAppAdapter.php index a51338c10..21e9d0e67 100644 --- a/src/App/Adapters/Console/ConsoleAppAdapter.php +++ b/src/App/Adapters/Console/ConsoleAppAdapter.php @@ -20,8 +20,8 @@ use Quantum\App\Stages\Boot\InitConsoleStage; use Quantum\App\Stages\Boot\LoadHelpersStage; use Quantum\App\Contracts\AppInterface; -use Quantum\App\BootPipeline; use Quantum\App\AppContext; +use Quantum\App\Pipeline; use Throwable; use Exception; @@ -44,7 +44,7 @@ public function __construct(AppContext $context) $this->context = $context; try { - $pipeline = new BootPipeline([ + $pipeline = new Pipeline([ new InitConsoleStage(), new LoadHelpersStage(), new LoadEnvironmentStage(), @@ -64,7 +64,7 @@ public function __construct(AppContext $context) */ public function start(): ?int { - $pipeline = new ConsolePipeline([ + $pipeline = new Pipeline([ new RegisterCoreCommandsStage(), new RegisterAppCommandsStage(), new RunCommandStage(), diff --git a/src/App/Adapters/Console/ConsolePipeline.php b/src/App/Adapters/Console/ConsolePipeline.php deleted file mode 100644 index 15f484da8..000000000 --- a/src/App/Adapters/Console/ConsolePipeline.php +++ /dev/null @@ -1,53 +0,0 @@ -stages = $stages; - } - - public function run(ConsoleContext $context): void - { - foreach ($this->stages as $stage) { - $stage->process($context); - } - } -} diff --git a/src/App/Adapters/Web/WebAppAdapter.php b/src/App/Adapters/Web/WebAppAdapter.php index 366e55fcc..0a1fe9689 100644 --- a/src/App/Adapters/Web/WebAppAdapter.php +++ b/src/App/Adapters/Web/WebAppAdapter.php @@ -25,8 +25,8 @@ use Quantum\App\Stages\Boot\InitHttpStage; use Quantum\App\Contracts\AppInterface; use Quantum\App\Enums\ExitCode; -use Quantum\App\BootPipeline; use Quantum\App\AppContext; +use Quantum\App\Pipeline; /** * Class WebAppAdapter @@ -40,7 +40,7 @@ public function __construct(AppContext $context) { $this->context = $context; - $pipeline = new BootPipeline([ + $pipeline = new Pipeline([ new LoadHelpersStage(), new LoadEnvironmentStage(), new LoadAppConfigStage(), @@ -55,7 +55,7 @@ public function __construct(AppContext $context) public function start(): ?int { - $pipeline = new WebPipeline([ + $pipeline = new Pipeline([ new HandlePreflightStage(), new ResolveRouteStage(), new HandleRouteNotFoundStage(), diff --git a/src/App/Adapters/Web/WebPipeline.php b/src/App/Adapters/Web/WebPipeline.php deleted file mode 100644 index ed20529d2..000000000 --- a/src/App/Adapters/Web/WebPipeline.php +++ /dev/null @@ -1,54 +0,0 @@ -stages = $stages; - } - - public function run(WebContext $context): void - { - foreach ($this->stages as $stage) { - $stage->process($context); - } - } -} diff --git a/src/App/Contracts/ConsoleStageInterface.php b/src/App/Contracts/ConsoleStageInterface.php deleted file mode 100644 index 5870bd01d..000000000 --- a/src/App/Contracts/ConsoleStageInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -`. + * + * @template TContext of AppContext * @package Quantum\App */ -interface BootStageInterface +interface StageInterface { /** - * Processes a single boot stage + * Processes a single stage + * @param TContext $context */ public function process(AppContext $context): void; } diff --git a/src/App/Contracts/WebStageInterface.php b/src/App/Contracts/WebStageInterface.php deleted file mode 100644 index 88720bbb9..000000000 --- a/src/App/Contracts/WebStageInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -> */ private array $stages; /** - * @param BootStageInterface[] $stages + * @param array> $stages */ public function __construct(array $stages = []) { foreach ($stages as $stage) { - if (!$stage instanceof BootStageInterface) { - throw new InvalidArgumentException( - 'All stages must implement ' . BootStageInterface::class - ); + if (!$stage instanceof StageInterface) { + throw new InvalidArgumentException('All stages must implement ' . StageInterface::class); } } $this->stages = $stages; } + /** + * @param TContext $context + */ public function run(AppContext $context): void { foreach ($this->stages as $stage) { @@ -54,8 +60,10 @@ public function run(AppContext $context): void /** * Dispatches the event a stage declares for the given point, if any. * Skipped while the event() helper is not loaded yet, since nothing can listen then. + * @param StageInterface $stage + * @param TContext $context */ - private function dispatchStageEvent(BootStageInterface $stage, string $point, AppContext $context): void + private function dispatchStageEvent(StageInterface $stage, string $point, AppContext $context): void { $constant = $stage::class . '::' . $point; diff --git a/src/App/Stages/Boot/InitConsoleStage.php b/src/App/Stages/Boot/InitConsoleStage.php index ffc508544..c3f36d3a1 100644 --- a/src/App/Stages/Boot/InitConsoleStage.php +++ b/src/App/Stages/Boot/InitConsoleStage.php @@ -14,8 +14,8 @@ use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\ArgvInput; -use Quantum\App\Contracts\BootStageInterface; use Symfony\Component\Console\Application; +use Quantum\App\Contracts\StageInterface; use Quantum\App\AppContext; use Quantum\Di\Di; @@ -25,9 +25,10 @@ * Runs before the helpers are loaded, so that the console application, input and output exist * even when a helper file fails; for the same reason it declares no boot events. * + * @implements StageInterface * @package Quantum\App */ -class InitConsoleStage implements BootStageInterface +class InitConsoleStage implements StageInterface { public function process(AppContext $context): void { diff --git a/src/App/Stages/Boot/InitDebuggerStage.php b/src/App/Stages/Boot/InitDebuggerStage.php index 5379b3a48..3da725831 100644 --- a/src/App/Stages/Boot/InitDebuggerStage.php +++ b/src/App/Stages/Boot/InitDebuggerStage.php @@ -10,14 +10,15 @@ namespace Quantum\App\Stages\Boot; -use Quantum\App\Contracts\BootStageInterface; +use Quantum\App\Contracts\StageInterface; use Quantum\App\AppContext; /** * Class InitDebuggerStage + * @implements StageInterface * @package Quantum\App */ -class InitDebuggerStage implements BootStageInterface +class InitDebuggerStage implements StageInterface { public function process(AppContext $context): void { diff --git a/src/App/Stages/Boot/InitHttpStage.php b/src/App/Stages/Boot/InitHttpStage.php index 2bdf5175c..8eebd3637 100644 --- a/src/App/Stages/Boot/InitHttpStage.php +++ b/src/App/Stages/Boot/InitHttpStage.php @@ -10,7 +10,7 @@ namespace Quantum\App\Stages\Boot; -use Quantum\App\Contracts\BootStageInterface; +use Quantum\App\Contracts\StageInterface; use Quantum\App\AppContext; use Quantum\Http\Response; use Quantum\Http\Request; @@ -18,9 +18,10 @@ /** * Class InitHttpStage + * @implements StageInterface * @package Quantum\App */ -class InitHttpStage implements BootStageInterface +class InitHttpStage implements StageInterface { public const BEFORE = 'boot.http.before'; diff --git a/src/App/Stages/Boot/LoadAppConfigStage.php b/src/App/Stages/Boot/LoadAppConfigStage.php index 3c5555e94..828730947 100644 --- a/src/App/Stages/Boot/LoadAppConfigStage.php +++ b/src/App/Stages/Boot/LoadAppConfigStage.php @@ -10,8 +10,8 @@ namespace Quantum\App\Stages\Boot; -use Quantum\App\Contracts\BootStageInterface; use Quantum\Config\Exceptions\ConfigException; +use Quantum\App\Contracts\StageInterface; use Quantum\Di\Exceptions\DiException; use Quantum\App\AppContext; use Quantum\Config\Setup; @@ -19,9 +19,10 @@ /** * Class LoadAppConfigStage + * @implements StageInterface * @package Quantum\App */ -class LoadAppConfigStage implements BootStageInterface +class LoadAppConfigStage implements StageInterface { public const BEFORE = 'boot.config.before'; diff --git a/src/App/Stages/Boot/LoadEnvironmentStage.php b/src/App/Stages/Boot/LoadEnvironmentStage.php index 2d0485228..81fb87011 100644 --- a/src/App/Stages/Boot/LoadEnvironmentStage.php +++ b/src/App/Stages/Boot/LoadEnvironmentStage.php @@ -11,7 +11,7 @@ namespace Quantum\App\Stages\Boot; use Quantum\Environment\Exceptions\EnvException; -use Quantum\App\Contracts\BootStageInterface; +use Quantum\App\Contracts\StageInterface; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; use Quantum\Environment\Environment; @@ -20,9 +20,10 @@ /** * Class LoadEnvironmentStage + * @implements StageInterface * @package Quantum\App */ -class LoadEnvironmentStage implements BootStageInterface +class LoadEnvironmentStage implements StageInterface { public const BEFORE = 'boot.environment.before'; diff --git a/src/App/Stages/Boot/LoadHelpersStage.php b/src/App/Stages/Boot/LoadHelpersStage.php index ad696f5b4..f1c246f91 100644 --- a/src/App/Stages/Boot/LoadHelpersStage.php +++ b/src/App/Stages/Boot/LoadHelpersStage.php @@ -10,15 +10,16 @@ namespace Quantum\App\Stages\Boot; -use Quantum\App\Contracts\BootStageInterface; +use Quantum\App\Contracts\StageInterface; use Quantum\App\AppContext; use Quantum\App\App; /** * Class LoadHelpersStage + * @implements StageInterface * @package Quantum\App */ -class LoadHelpersStage implements BootStageInterface +class LoadHelpersStage implements StageInterface { public function process(AppContext $context): void { diff --git a/src/App/Stages/Boot/LoadModulesStage.php b/src/App/Stages/Boot/LoadModulesStage.php index 87fda24fd..6d2bb86a7 100644 --- a/src/App/Stages/Boot/LoadModulesStage.php +++ b/src/App/Stages/Boot/LoadModulesStage.php @@ -12,7 +12,7 @@ use Quantum\Module\Exceptions\ModuleException; use Quantum\Router\Exceptions\RouteException; -use Quantum\App\Contracts\BootStageInterface; +use Quantum\App\Contracts\StageInterface; use Quantum\Di\Exceptions\DiException; use Quantum\Router\RouteCollection; use Quantum\Router\RouteBuilder; @@ -23,9 +23,10 @@ /** * Class LoadModulesStage + * @implements StageInterface * @package Quantum\App */ -class LoadModulesStage implements BootStageInterface +class LoadModulesStage implements StageInterface { public const BEFORE = 'boot.modules.before'; diff --git a/src/App/Stages/Boot/SetupConsoleApplicationStage.php b/src/App/Stages/Boot/SetupConsoleApplicationStage.php index 3a95115b9..6a566f744 100644 --- a/src/App/Stages/Boot/SetupConsoleApplicationStage.php +++ b/src/App/Stages/Boot/SetupConsoleApplicationStage.php @@ -10,16 +10,17 @@ namespace Quantum\App\Stages\Boot; -use Quantum\App\Contracts\BootStageInterface; use Symfony\Component\Console\Application; +use Quantum\App\Contracts\StageInterface; use Quantum\App\AppContext; use Quantum\Di\Di; /** * Class SetupConsoleApplicationStage + * @implements StageInterface * @package Quantum\App */ -class SetupConsoleApplicationStage implements BootStageInterface +class SetupConsoleApplicationStage implements StageInterface { public function process(AppContext $context): void { diff --git a/src/App/Stages/Boot/SetupErrorHandlerStage.php b/src/App/Stages/Boot/SetupErrorHandlerStage.php index 583371bbd..c146a90a5 100644 --- a/src/App/Stages/Boot/SetupErrorHandlerStage.php +++ b/src/App/Stages/Boot/SetupErrorHandlerStage.php @@ -11,8 +11,8 @@ namespace Quantum\App\Stages\Boot; use Quantum\Config\Exceptions\ConfigException; -use Quantum\App\Contracts\BootStageInterface; use Quantum\Logger\Factories\LoggerFactory; +use Quantum\App\Contracts\StageInterface; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; use Quantum\Tracer\ErrorHandler; @@ -22,9 +22,10 @@ /** * Class SetupErrorHandlerStage + * @implements StageInterface * @package Quantum\App */ -class SetupErrorHandlerStage implements BootStageInterface +class SetupErrorHandlerStage implements StageInterface { public const BEFORE = 'boot.error_handler.before'; diff --git a/src/App/Stages/Console/RegisterAppCommandsStage.php b/src/App/Stages/Console/RegisterAppCommandsStage.php index 303085c61..556b5ed43 100644 --- a/src/App/Stages/Console/RegisterAppCommandsStage.php +++ b/src/App/Stages/Console/RegisterAppCommandsStage.php @@ -10,22 +10,25 @@ namespace Quantum\App\Stages\Console; -use Quantum\App\Contracts\ConsoleStageInterface; use Quantum\App\Adapters\Console\ConsoleContext; +use Quantum\App\Contracts\StageInterface; use Quantum\Console\CommandRegistrar; +use Quantum\App\AppContext; use ReflectionException; /** * Class RegisterAppCommandsStage + * @implements StageInterface * @package Quantum\App */ -class RegisterAppCommandsStage implements ConsoleStageInterface +class RegisterAppCommandsStage implements StageInterface { /** * Registers the commands of the application + * @param ConsoleContext $context * @throws ReflectionException */ - public function process(ConsoleContext $context): void + public function process(AppContext $context): void { CommandRegistrar::register( $context->getApplication(), diff --git a/src/App/Stages/Console/RegisterCoreCommandsStage.php b/src/App/Stages/Console/RegisterCoreCommandsStage.php index 63b9c91c5..7162b42c4 100644 --- a/src/App/Stages/Console/RegisterCoreCommandsStage.php +++ b/src/App/Stages/Console/RegisterCoreCommandsStage.php @@ -10,22 +10,25 @@ namespace Quantum\App\Stages\Console; -use Quantum\App\Contracts\ConsoleStageInterface; use Quantum\App\Adapters\Console\ConsoleContext; +use Quantum\App\Contracts\StageInterface; use Quantum\Console\CommandRegistrar; +use Quantum\App\AppContext; use ReflectionException; /** * Class RegisterCoreCommandsStage + * @implements StageInterface * @package Quantum\App */ -class RegisterCoreCommandsStage implements ConsoleStageInterface +class RegisterCoreCommandsStage implements StageInterface { /** * Registers the commands that ship with the framework + * @param ConsoleContext $context * @throws ReflectionException */ - public function process(ConsoleContext $context): void + public function process(AppContext $context): void { CommandRegistrar::register( $context->getApplication(), diff --git a/src/App/Stages/Console/RunCommandStage.php b/src/App/Stages/Console/RunCommandStage.php index facb4d521..a139f84b4 100644 --- a/src/App/Stages/Console/RunCommandStage.php +++ b/src/App/Stages/Console/RunCommandStage.php @@ -10,9 +10,10 @@ namespace Quantum\App\Stages\Console; -use Quantum\App\Contracts\ConsoleStageInterface; use Quantum\App\Adapters\Console\ConsoleContext; +use Quantum\App\Contracts\StageInterface; use Quantum\Console\CliCommand; +use Quantum\App\AppContext; use Exception; use Throwable; @@ -22,14 +23,16 @@ * Runs the requested command; a missing or unknown command is rejected before anything runs, * and so is a command that needs the environment after the boot failed. * + * @implements StageInterface * @package Quantum\App */ -class RunCommandStage implements ConsoleStageInterface +class RunCommandStage implements StageInterface { /** + * @param ConsoleContext $context * @throws Exception|Throwable */ - public function process(ConsoleContext $context): void + public function process(AppContext $context): void { $commandName = $context->getInput()->getFirstArgument(); diff --git a/src/App/Stages/Web/DispatchRequestStage.php b/src/App/Stages/Web/DispatchRequestStage.php index 9a06921df..e87a493a0 100644 --- a/src/App/Stages/Web/DispatchRequestStage.php +++ b/src/App/Stages/Web/DispatchRequestStage.php @@ -11,12 +11,13 @@ namespace Quantum\App\Stages\Web; use Quantum\Middleware\Exceptions\MiddlewareException; -use Quantum\App\Contracts\WebStageInterface; +use Quantum\App\Contracts\StageInterface; use Quantum\App\Exceptions\BaseException; use Quantum\Middleware\MiddlewareManager; use Quantum\App\Adapters\Web\WebContext; use Quantum\Di\Exceptions\DiException; use Quantum\Router\RouteDispatcher; +use Quantum\App\AppContext; use Quantum\Http\Response; use Quantum\Http\Request; use RuntimeException; @@ -27,14 +28,16 @@ * Produces the response of a matched request: the cached view if there is one, otherwise the * dispatched route, in both cases wrapped by the module and framework middlewares. * + * @implements StageInterface * @package Quantum\App */ -class DispatchRequestStage implements WebStageInterface +class DispatchRequestStage implements StageInterface { /** + * @param WebContext $context * @throws MiddlewareException|BaseException|DiException */ - public function process(WebContext $context): void + public function process(AppContext $context): void { if ($context->isStopped()) { return; diff --git a/src/App/Stages/Web/HandlePreflightStage.php b/src/App/Stages/Web/HandlePreflightStage.php index c911b3d36..ccf00da73 100644 --- a/src/App/Stages/Web/HandlePreflightStage.php +++ b/src/App/Stages/Web/HandlePreflightStage.php @@ -10,22 +10,25 @@ namespace Quantum\App\Stages\Web; -use Quantum\App\Contracts\WebStageInterface; +use Quantum\App\Contracts\StageInterface; use Quantum\App\Adapters\Web\WebContext; use Quantum\Di\Exceptions\DiException; use Quantum\Http\Enums\StatusCode; +use Quantum\App\AppContext; /** * Class HandlePreflightStage + * @implements StageInterface * @package Quantum\App */ -class HandlePreflightStage implements WebStageInterface +class HandlePreflightStage implements StageInterface { /** * Answers an OPTIONS request with an empty response and stops the remaining stages + * @param WebContext $context * @throws DiException */ - public function process(WebContext $context): void + public function process(AppContext $context): void { if (!$context->getRequest()->isMethod('OPTIONS')) { return; diff --git a/src/App/Stages/Web/HandleRouteNotFoundStage.php b/src/App/Stages/Web/HandleRouteNotFoundStage.php index e17471506..1b68f9505 100644 --- a/src/App/Stages/Web/HandleRouteNotFoundStage.php +++ b/src/App/Stages/Web/HandleRouteNotFoundStage.php @@ -11,23 +11,26 @@ namespace Quantum\App\Stages\Web; use Quantum\Config\Exceptions\ConfigException; -use Quantum\App\Contracts\WebStageInterface; +use Quantum\App\Contracts\StageInterface; use Quantum\App\Exceptions\BaseException; use Quantum\App\Adapters\Web\WebContext; use Quantum\Di\Exceptions\DiException; +use Quantum\App\AppContext; use ReflectionException; /** * Class HandleRouteNotFoundStage + * @implements StageInterface * @package Quantum\App */ -class HandleRouteNotFoundStage implements WebStageInterface +class HandleRouteNotFoundStage implements StageInterface { /** * Answers a request that matched no route with the page not found response and stops the remaining stages + * @param WebContext $context * @throws ConfigException|BaseException|DiException|ReflectionException */ - public function process(WebContext $context): void + public function process(AppContext $context): void { if ($context->isStopped() || $context->getMatchedRoute() !== null) { return; diff --git a/src/App/Stages/Web/PrepareRequestStage.php b/src/App/Stages/Web/PrepareRequestStage.php index 1eaabebf4..7e080d93e 100644 --- a/src/App/Stages/Web/PrepareRequestStage.php +++ b/src/App/Stages/Web/PrepareRequestStage.php @@ -11,13 +11,14 @@ namespace Quantum\App\Stages\Web; use Quantum\Config\Exceptions\ConfigException; -use Quantum\App\Contracts\WebStageInterface; use Quantum\Lang\Exceptions\LangException; +use Quantum\App\Contracts\StageInterface; use Quantum\App\Adapters\Web\WebContext; use Quantum\Lang\Factories\LangFactory; use Quantum\Di\Exceptions\DiException; use Quantum\ResourceCache\ViewCache; use Quantum\Debugger\Debugger; +use Quantum\App\AppContext; use ReflectionException; /** @@ -26,14 +27,16 @@ * Prepares a matched request: loads the language, logs debug info and sets up the view cache. * The order of these steps is kept from the former WebAppAdapter flow. * + * @implements StageInterface * @package Quantum\App */ -class PrepareRequestStage implements WebStageInterface +class PrepareRequestStage implements StageInterface { /** + * @param WebContext $context * @throws LangException|ConfigException|DiException|ReflectionException */ - public function process(WebContext $context): void + public function process(AppContext $context): void { if ($context->isStopped()) { return; diff --git a/src/App/Stages/Web/ResolveRouteStage.php b/src/App/Stages/Web/ResolveRouteStage.php index 6aee907bf..2e802cb49 100644 --- a/src/App/Stages/Web/ResolveRouteStage.php +++ b/src/App/Stages/Web/ResolveRouteStage.php @@ -11,23 +11,26 @@ namespace Quantum\App\Stages\Web; use Quantum\Router\Exceptions\RouteException; -use Quantum\App\Contracts\WebStageInterface; +use Quantum\App\Contracts\StageInterface; use Quantum\App\Exceptions\BaseException; use Quantum\App\Adapters\Web\WebContext; use Quantum\Di\Exceptions\DiException; use Quantum\Router\RouteFinder; +use Quantum\App\AppContext; /** * Class ResolveRouteStage + * @implements StageInterface * @package Quantum\App */ -class ResolveRouteStage implements WebStageInterface +class ResolveRouteStage implements StageInterface { /** * Finds the route for the current request and stores it on the request, or null when nothing matches + * @param WebContext $context * @throws RouteException|BaseException|DiException */ - public function process(WebContext $context): void + public function process(AppContext $context): void { if ($context->isStopped()) { return; diff --git a/src/App/Stages/Web/SendResponseStage.php b/src/App/Stages/Web/SendResponseStage.php index bcfac5d6a..e3874e5b5 100644 --- a/src/App/Stages/Web/SendResponseStage.php +++ b/src/App/Stages/Web/SendResponseStage.php @@ -11,9 +11,10 @@ namespace Quantum\App\Stages\Web; use Quantum\Config\Exceptions\ConfigException; -use Quantum\App\Contracts\WebStageInterface; +use Quantum\App\Contracts\StageInterface; use Quantum\App\Adapters\Web\WebContext; use Quantum\Di\Exceptions\DiException; +use Quantum\App\AppContext; use Quantum\Config\Config; use Quantum\Http\Response; use Quantum\Http\Request; @@ -28,14 +29,16 @@ * Sends the response of the request and always clears the request state afterwards, * even when sending fails. This stage never checks WebContext::isStopped(). * + * @implements StageInterface * @package Quantum\App */ -class SendResponseStage implements WebStageInterface +class SendResponseStage implements StageInterface { /** + * @param WebContext $context * @throws ConfigException|DiException|ReflectionException|Exception */ - public function process(WebContext $context): void + public function process(AppContext $context): void { try { $response = $context->getResponse(); diff --git a/tests/Unit/App/Adapters/Console/ConsoleAppAdapterTest.php b/tests/Unit/App/Adapters/Console/ConsoleAppAdapterTest.php index 87f2f92f2..07aa0d759 100644 --- a/tests/Unit/App/Adapters/Console/ConsoleAppAdapterTest.php +++ b/tests/Unit/App/Adapters/Console/ConsoleAppAdapterTest.php @@ -14,13 +14,13 @@ use Quantum\Environment\Environment; use Quantum\Tests\Unit\AppTestCase; use Quantum\Tracer\ErrorHandler; -use Quantum\App\BootPipeline; use Quantum\App\AppContext; -use Quantum\Di\Di; +use Quantum\App\Pipeline; use ReflectionProperty; use RuntimeException; -use Throwable; +use Quantum\Di\Di; use ParseError; +use Throwable; use Exception; use Mockery; @@ -259,7 +259,7 @@ public function testConsoleAppAdapterNamesTheApplicationUnknownWhenTheAppNameIsE $context = $this->createContext(); - (new BootPipeline([new LoadHelpersStage()]))->run($context); + (new Pipeline([new LoadHelpersStage()]))->run($context); config()->set('app', ['name' => null, 'version' => null]); @@ -277,7 +277,7 @@ public function testConsoleAppAdapterKeepsAnEmptyAppNameAndVersionFromTheConfig( $context = $this->createContext(); - (new BootPipeline([new LoadHelpersStage()]))->run($context); + (new Pipeline([new LoadHelpersStage()]))->run($context); config()->set('app', ['name' => '', 'version' => '0']); diff --git a/tests/Unit/App/Adapters/Console/ConsolePipelineTest.php b/tests/Unit/App/Adapters/Console/ConsolePipelineTest.php deleted file mode 100644 index 746f0e568..000000000 --- a/tests/Unit/App/Adapters/Console/ConsolePipelineTest.php +++ /dev/null @@ -1,161 +0,0 @@ -consoleContext = new ConsoleContext($this->createContext()); - } - - public function tearDown(): void - { - $this->clearAppContext(); - } - - public function testPipelineRunsStagesInOrder(): void - { - $log = []; - - $pipeline = new ConsolePipeline([ - $this->createStage(function () use (&$log): void { - $log[] = 'first'; - }), - $this->createStage(function () use (&$log): void { - $log[] = 'second'; - }), - $this->createStage(function () use (&$log): void { - $log[] = 'third'; - }), - ]); - - $pipeline->run($this->consoleContext); - - $this->assertSame(['first', 'second', 'third'], $log); - } - - public function testEmptyPipelineRunsWithoutError(): void - { - $pipeline = new ConsolePipeline([]); - - $pipeline->run($this->consoleContext); - - $this->assertSame(ExitCode::SUCCESS, $this->consoleContext->getExitCode()); - } - - public function testPipelinePassesContextToStages(): void - { - $received = null; - - $pipeline = new ConsolePipeline([ - $this->createStage(function (ConsoleContext $context) use (&$received): void { - $received = $context; - }), - ]); - - $pipeline->run($this->consoleContext); - - $this->assertSame($this->consoleContext, $received); - } - - public function testPipelineCarriesExitCodeBetweenStages(): void - { - $seen = null; - - $pipeline = new ConsolePipeline([ - $this->createStage(function (ConsoleContext $context): void { - $context->setExitCode(ExitCode::FAILURE); - }), - $this->createStage(function (ConsoleContext $context) use (&$seen): void { - $seen = $context->getExitCode(); - }), - ]); - - $pipeline->run($this->consoleContext); - - $this->assertSame(ExitCode::FAILURE, $seen); - $this->assertSame(ExitCode::FAILURE, $this->consoleContext->getExitCode()); - } - - public function testPipelinePropagatesException(): void - { - $pipeline = new ConsolePipeline([ - $this->createStage(function (): void { - throw new RuntimeException('Stage failed'); - }), - ]); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Stage failed'); - - $pipeline->run($this->consoleContext); - } - - public function testPipelineRejectsInvalidStage(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('All stages must implement'); - - new ConsolePipeline([new \stdClass()]); - } - - public function testPipelineRejectsBootStage(): void - { - $bootStage = new class () implements BootStageInterface { - public function process(AppContext $context): void - { - } - }; - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('All stages must implement'); - - new ConsolePipeline([$bootStage]); - } - - public function testPipelineRejectsRequestStage(): void - { - $requestStage = new class () implements WebStageInterface { - public function process(WebContext $context): void - { - } - }; - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('All stages must implement'); - - new ConsolePipeline([$requestStage]); - } - - private function createStage(callable $callback): ConsoleStageInterface - { - return new class ($callback) implements ConsoleStageInterface { - private $callback; - - public function __construct(callable $callback) - { - $this->callback = $callback; - } - - public function process(ConsoleContext $context): void - { - ($this->callback)($context); - } - }; - } -} diff --git a/tests/Unit/App/Adapters/Web/WebPipelineTest.php b/tests/Unit/App/Adapters/Web/WebPipelineTest.php deleted file mode 100644 index f93eaa4e1..000000000 --- a/tests/Unit/App/Adapters/Web/WebPipelineTest.php +++ /dev/null @@ -1,145 +0,0 @@ -webContext = new WebContext($this->createContext()); - } - - public function tearDown(): void - { - $this->clearAppContext(); - } - - public function testPipelineRunsStagesInOrder(): void - { - $log = []; - - $pipeline = new WebPipeline([ - $this->createStage(function () use (&$log): void { - $log[] = 'first'; - }), - $this->createStage(function () use (&$log): void { - $log[] = 'second'; - }), - $this->createStage(function () use (&$log): void { - $log[] = 'third'; - }), - ]); - - $pipeline->run($this->webContext); - - $this->assertSame(['first', 'second', 'third'], $log); - } - - public function testEmptyPipelineRunsWithoutError(): void - { - $pipeline = new WebPipeline([]); - - $pipeline->run($this->webContext); - - $this->assertFalse($this->webContext->isStopped()); - } - - public function testPipelinePassesContextToStages(): void - { - $received = null; - - $pipeline = new WebPipeline([ - $this->createStage(function (WebContext $context) use (&$received): void { - $received = $context; - }), - ]); - - $pipeline->run($this->webContext); - - $this->assertSame($this->webContext, $received); - } - - public function testPipelinePropagatesException(): void - { - $pipeline = new WebPipeline([ - $this->createStage(function (): void { - throw new RuntimeException('Stage failed'); - }), - ]); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Stage failed'); - - $pipeline->run($this->webContext); - } - - public function testPipelineKeepsRunningStagesAfterContextIsStopped(): void - { - $log = []; - - $pipeline = new WebPipeline([ - $this->createStage(function (WebContext $context) use (&$log): void { - $log[] = 'first'; - $context->stop(); - }), - $this->createStage(function () use (&$log): void { - $log[] = 'second'; - }), - ]); - - $pipeline->run($this->webContext); - - $this->assertSame(['first', 'second'], $log); - $this->assertTrue($this->webContext->isStopped()); - } - - public function testPipelineRejectsInvalidStage(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('All stages must implement'); - - new WebPipeline([new \stdClass()]); - } - - public function testPipelineRejectsBootStage(): void - { - $bootStage = new class () implements BootStageInterface { - public function process(AppContext $context): void - { - } - }; - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('All stages must implement'); - - new WebPipeline([$bootStage]); - } - - private function createStage(callable $callback): WebStageInterface - { - return new class ($callback) implements WebStageInterface { - private $callback; - - public function __construct(callable $callback) - { - $this->callback = $callback; - } - - public function process(WebContext $context): void - { - ($this->callback)($context); - } - }; - } -} diff --git a/tests/Unit/App/BootPipelineTest.php b/tests/Unit/App/PipelineTest.php similarity index 59% rename from tests/Unit/App/BootPipelineTest.php rename to tests/Unit/App/PipelineTest.php index c10b549aa..15fd13155 100644 --- a/tests/Unit/App/BootPipelineTest.php +++ b/tests/Unit/App/PipelineTest.php @@ -4,22 +4,25 @@ use Quantum\App\Stages\Boot\SetupConsoleApplicationStage; use Quantum\App\Stages\Boot\SetupErrorHandlerStage; -use Quantum\App\Contracts\BootStageInterface; use Quantum\App\Stages\Boot\LoadEnvironmentStage; +use Quantum\App\Adapters\Console\ConsoleContext; use Quantum\App\Stages\Boot\LoadAppConfigStage; use Quantum\App\Stages\Boot\InitDebuggerStage; use Quantum\App\Stages\Boot\LoadModulesStage; -use Quantum\App\Stages\Boot\LoadHelpersStage; use Quantum\App\Stages\Boot\InitConsoleStage; +use Quantum\App\Stages\Boot\LoadHelpersStage; use Quantum\App\Stages\Boot\InitHttpStage; +use Quantum\App\Contracts\StageInterface; +use Quantum\App\Adapters\Web\WebContext; use Quantum\Tests\Unit\AppTestCase; +use Quantum\App\Enums\ExitCode; use InvalidArgumentException; -use Quantum\App\BootPipeline; use Quantum\Di\DiContainer; use Quantum\App\AppContext; +use Quantum\App\Pipeline; use RuntimeException; -class BootPipelineTest extends AppTestCase +class PipelineTest extends AppTestCase { public function setUp(): void { @@ -35,80 +38,71 @@ public function testPipelineRunsStagesInOrder(): void { $log = []; - $stage1 = $this->createStage(function () use (&$log) { - $log[] = 'first'; - }); - - $stage2 = $this->createStage(function () use (&$log) { - $log[] = 'second'; - }); - - $stage3 = $this->createStage(function () use (&$log) { - $log[] = 'third'; - }); - - $pipeline = new BootPipeline([$stage1, $stage2, $stage3]); - $pipeline->run(new AppContext('', new DiContainer())); + (new Pipeline([ + $this->createStage(function () use (&$log): void { + $log[] = 'first'; + }), + $this->createStage(function () use (&$log): void { + $log[] = 'second'; + }), + $this->createStage(function () use (&$log): void { + $log[] = 'third'; + }), + ]))->run(new AppContext('', new DiContainer())); $this->assertSame(['first', 'second', 'third'], $log); } public function testEmptyPipelineRunsWithoutError(): void { - $pipeline = new BootPipeline([]); - $pipeline->run(new AppContext('', new DiContainer())); + (new Pipeline([]))->run(new AppContext('', new DiContainer())); $this->assertTrue(true); } - public function testPipelinePassesContextToStages(): void + public function testPipelinePassesTheContextToEveryStage(): void { - $receivedBaseDir = null; + $context = new AppContext('/test/dir', new DiContainer()); + $received = []; - $stage = $this->createStage(function (AppContext $context) use (&$receivedBaseDir) { - $receivedBaseDir = $context->getBaseDir(); + $stage = $this->createStage(function (AppContext $context) use (&$received): void { + $received[] = $context; }); - $pipeline = new BootPipeline([$stage]); - $pipeline->run(new AppContext('/test/dir', new DiContainer())); + (new Pipeline([$stage, $stage]))->run($context); - $this->assertSame('/test/dir', $receivedBaseDir); + $this->assertSame([$context, $context], $received); } - public function testPipelinePropagatesException(): void + public function testPipelineRejectsAnythingThatIsNotAStage(): void { - $stage = $this->createStage(function () { - throw new RuntimeException('Stage failed'); - }); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Stage failed'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('All stages must implement ' . StageInterface::class); - $pipeline = new BootPipeline([$stage]); - $pipeline->run(new AppContext('', new DiContainer())); + new Pipeline([new \stdClass()]); } - public function testPipelineDispatchesEventsAroundStage(): void + public function testPipelineDispatchesEventsAroundAStageWithTheContext(): void { $this->loadHelpers(); $log = []; - event()->listen('test.first.before', function () use (&$log): void { - $log[] = 'before'; - }); - - event()->listen('test.first.after', function () use (&$log): void { - $log[] = 'after'; - }); + foreach (['test.first.before', 'test.first.after'] as $name) { + event()->listen($name, function (array $payload) use (&$log, $name): void { + $log[] = [$name, $payload]; + }); + } - $pipeline = new BootPipeline([$this->createFirstEventStage(function () use (&$log): void { + (new Pipeline([$this->createFirstEventStage(function () use (&$log): void { $log[] = 'process'; - })]); + })]))->run($this->context); - $pipeline->run($this->context); - - $this->assertSame(['before', 'process', 'after'], $log); + $this->assertSame([ + ['test.first.before', ['context' => $this->context]], + 'process', + ['test.first.after', ['context' => $this->context]], + ], $log); } public function testPipelineDispatchesEventsInStageOrder(): void @@ -123,16 +117,14 @@ public function testPipelineDispatchesEventsInStageOrder(): void }); } - $pipeline = new BootPipeline([ + (new Pipeline([ $this->createFirstEventStage(function () use (&$log): void { $log[] = 'process first'; }), $this->createSecondEventStage(function () use (&$log): void { $log[] = 'process second'; }), - ]); - - $pipeline->run($this->context); + ]))->run($this->context); $this->assertSame([ 'test.first.before', @@ -144,32 +136,7 @@ public function testPipelineDispatchesEventsInStageOrder(): void ], $log); } - public function testPipelinePassesContextInEventPayload(): void - { - $this->loadHelpers(); - - $payloads = []; - - event()->listen('test.first.before', function (array $payload) use (&$payloads): void { - $payloads[] = $payload; - }); - - event()->listen('test.first.after', function (array $payload) use (&$payloads): void { - $payloads[] = $payload; - }); - - $pipeline = new BootPipeline([$this->createFirstEventStage(function (): void { - })]); - - $pipeline->run($this->context); - - $this->assertCount(2, $payloads); - $this->assertSame(['context'], array_keys($payloads[0])); - $this->assertSame($this->context, $payloads[0]['context']); - $this->assertSame($this->context, $payloads[1]['context']); - } - - public function testPipelineDispatchesOnlyAfterEventWhenStageDeclaresOnlyAfter(): void + public function testPipelineDispatchesOnlyTheEventAStageDeclares(): void { $this->loadHelpers(); @@ -179,11 +146,9 @@ public function testPipelineDispatchesOnlyAfterEventWhenStageDeclaresOnlyAfter() $log[] = 'after'; }); - $pipeline = new BootPipeline([$this->createAfterOnlyEventStage(function () use (&$log): void { + (new Pipeline([$this->createAfterOnlyEventStage(function () use (&$log): void { $log[] = 'process'; - })]); - - $pipeline->run($this->context); + })]))->run($this->context); $this->assertSame(['process', 'after'], $log); } @@ -198,7 +163,7 @@ public function testPipelineKeepsListenersAfterStageEvents(): void $count++; }); - $pipeline = new BootPipeline([$this->createFirstEventStage(function (): void { + $pipeline = new Pipeline([$this->createFirstEventStage(function (): void { })]); $pipeline->run($this->context); @@ -207,30 +172,27 @@ public function testPipelineKeepsListenersAfterStageEvents(): void $this->assertSame(2, $count); } - public function testPipelineStagesDeclareBootEventNames(): void + public function testPipelinePropagatesAnExceptionWithoutTheAfterEvent(): void { - $this->assertSame('boot.environment.before', LoadEnvironmentStage::BEFORE); - $this->assertSame('boot.environment.after', LoadEnvironmentStage::AFTER); - - $this->assertSame('boot.config.before', LoadAppConfigStage::BEFORE); - $this->assertSame('boot.config.after', LoadAppConfigStage::AFTER); + $this->loadHelpers(); - $this->assertSame('boot.error_handler.before', SetupErrorHandlerStage::BEFORE); - $this->assertSame('boot.error_handler.after', SetupErrorHandlerStage::AFTER); + $after = false; - $this->assertSame('boot.http.before', InitHttpStage::BEFORE); - $this->assertSame('boot.http.after', InitHttpStage::AFTER); + event()->listen('test.first.after', function () use (&$after): void { + $after = true; + }); - $this->assertSame('boot.modules.before', LoadModulesStage::BEFORE); - $this->assertSame('boot.modules.after', LoadModulesStage::AFTER); - } + try { + (new Pipeline([$this->createFirstEventStage(function (): void { + throw new RuntimeException('Stage failed'); + })]))->run($this->context); - public function testLoadHelpersInitConsoleInitDebuggerAndSetupConsoleApplicationStagesDeclareNoEvents(): void - { - foreach ([LoadHelpersStage::class, InitConsoleStage::class, InitDebuggerStage::class, SetupConsoleApplicationStage::class] as $stage) { - $this->assertFalse(defined($stage . '::BEFORE'), $stage . ' must not declare BEFORE'); - $this->assertFalse(defined($stage . '::AFTER'), $stage . ' must not declare AFTER'); + $this->fail('Expected the stage exception to propagate.'); + } catch (RuntimeException $exception) { + $this->assertSame('Stage failed', $exception->getMessage()); } + + $this->assertFalse($after); } /** @@ -243,21 +205,69 @@ public function testPipelineSkipsStageEventsWhileEventHelperIsNotLoaded(): void $processed = false; - $pipeline = new BootPipeline([$this->createFirstEventStage(function () use (&$processed): void { + (new Pipeline([$this->createFirstEventStage(function () use (&$processed): void { $processed = true; - })]); - - $pipeline->run($this->context); + })]))->run($this->context); $this->assertTrue($processed); } - public function testPipelineRejectsInvalidStage(): void + public function testPipelineCarriesTheStoppedFlagOfAWebContextBetweenStages(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('All stages must implement'); + $stoppedSeenBySecondStage = null; + + (new Pipeline([ + $this->createStage(function (WebContext $context): void { + $context->stop(); + }), + $this->createStage(function (WebContext $context) use (&$stoppedSeenBySecondStage): void { + $stoppedSeenBySecondStage = $context->isStopped(); + }), + ]))->run(new WebContext($this->context)); + + $this->assertTrue($stoppedSeenBySecondStage); + } + + public function testPipelineCarriesTheExitCodeOfAConsoleContextBetweenStages(): void + { + $exitCodeSeenBySecondStage = null; + + (new Pipeline([ + $this->createStage(function (ConsoleContext $context): void { + $context->setExitCode(ExitCode::INVALID); + }), + $this->createStage(function (ConsoleContext $context) use (&$exitCodeSeenBySecondStage): void { + $exitCodeSeenBySecondStage = $context->getExitCode(); + }), + ]))->run(new ConsoleContext($this->context)); - new BootPipeline([new \stdClass()]); + $this->assertSame(ExitCode::INVALID, $exitCodeSeenBySecondStage); + } + + public function testPipelineStagesDeclareBootEventNames(): void + { + $this->assertSame('boot.environment.before', LoadEnvironmentStage::BEFORE); + $this->assertSame('boot.environment.after', LoadEnvironmentStage::AFTER); + + $this->assertSame('boot.config.before', LoadAppConfigStage::BEFORE); + $this->assertSame('boot.config.after', LoadAppConfigStage::AFTER); + + $this->assertSame('boot.error_handler.before', SetupErrorHandlerStage::BEFORE); + $this->assertSame('boot.error_handler.after', SetupErrorHandlerStage::AFTER); + + $this->assertSame('boot.http.before', InitHttpStage::BEFORE); + $this->assertSame('boot.http.after', InitHttpStage::AFTER); + + $this->assertSame('boot.modules.before', LoadModulesStage::BEFORE); + $this->assertSame('boot.modules.after', LoadModulesStage::AFTER); + } + + public function testLoadHelpersInitConsoleInitDebuggerAndSetupConsoleApplicationStagesDeclareNoEvents(): void + { + foreach ([LoadHelpersStage::class, InitConsoleStage::class, InitDebuggerStage::class, SetupConsoleApplicationStage::class] as $stage) { + $this->assertFalse(defined($stage . '::BEFORE'), $stage . ' must not declare BEFORE'); + $this->assertFalse(defined($stage . '::AFTER'), $stage . ' must not declare AFTER'); + } } private function loadHelpers(): void @@ -265,9 +275,9 @@ private function loadHelpers(): void (new LoadHelpersStage())->process($this->context); } - private function createStage(callable $callback): BootStageInterface + private function createStage(callable $callback): StageInterface { - return new class ($callback) implements BootStageInterface { + return new class ($callback) implements StageInterface { private $callback; public function __construct(callable $callback) @@ -282,9 +292,9 @@ public function process(AppContext $context): void }; } - private function createFirstEventStage(callable $callback): BootStageInterface + private function createFirstEventStage(callable $callback): StageInterface { - return new class ($callback) implements BootStageInterface { + return new class ($callback) implements StageInterface { public const BEFORE = 'test.first.before'; public const AFTER = 'test.first.after'; @@ -302,9 +312,9 @@ public function process(AppContext $context): void }; } - private function createSecondEventStage(callable $callback): BootStageInterface + private function createSecondEventStage(callable $callback): StageInterface { - return new class ($callback) implements BootStageInterface { + return new class ($callback) implements StageInterface { public const BEFORE = 'test.second.before'; public const AFTER = 'test.second.after'; @@ -322,9 +332,9 @@ public function process(AppContext $context): void }; } - private function createAfterOnlyEventStage(callable $callback): BootStageInterface + private function createAfterOnlyEventStage(callable $callback): StageInterface { - return new class ($callback) implements BootStageInterface { + return new class ($callback) implements StageInterface { public const AFTER = 'test.after-only.after'; private $callback;